Redis Client 之 Jedis与Lettuce
Posted 沸羊羊_
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Redis Client 之 Jedis与Lettuce相关的知识,希望对你有一定的参考价值。
前言
Jedis
Jedis
是 Redis
官方推荐的 Java 连接开发工具,要在Java开发中使用好 Redis
中间件,必须对Jedis
十分了解才行。
基本使用
Jedis
的使用非常简单,只需要创建 Jedis
对象的时候指定 host,port,password
即可。
Jedis jedis = new Jedis("ip",post);
......
jedis.close();
创建完 Jedis
对象,Jedis
底层会打开一条 Socket 通道和 Redis 进行连接,所以在使用完 Jedis
对象后,需要 jedis.close()
关闭连接,不然会占用系统资源。如果每次使用都手动创建和销毁 Jedis
对象,对应用的性能有很大影响,毕竟创建 socket 的过程是很耗时的。所以我们使用连接池的方式减少 socket 对象的创建和销毁过程。
连接池使用
Jedis
连接池是 org.apache.commons.pool2
实现的,在构建连接池对象时,需要提供连接池对象的配置对象,我们可以通过这个配置对象对连接池进行相关参数的配置,比如:最大空闲连接数,最大连接总数。
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxIdle(8);
jedisPoolConfig.setMaxTotal(18);
JedisPool jedisPool = new JedisPool(jedisPoolConfig, "ip", 6379, 2000);
Jedis jedis = jedisPool.getResource();
......
jedis.close();
jedisPool.close();
使用 Jedis
连接池后,每次用完连接对象都会把连接归还给连接池。Jedis
对 close()
和 getResource()
方法的实现。
//Jedis的close方法
@Override
public void close() {
if (dataSource != null) {
if (client.isBroken()) {
this.dataSource.returnBrokenResource(this);
} else {
this.dataSource.returnResource(this);
}
} else {
client.close();
}
}
// JedisPool.getResource()方法
// 从对象池中获取Jedis连接时,将会对dataSource进行设置
public Jedis getResource() {
Jedis jedis = super.getResource();
jedis.setDataSource(this);
return jedis;
}
高可用连接
我们知道,连接池可以大大提高应用访问Reids服务的性能,减去大量的 Socket 的创建和销毁过程。但是 Redis
为了保障高可用,服务一般都是 Sentinel
部署方式。当 Redis
服务中的主服务挂掉之后,会仲裁出另外一台 Slaves
服务充当 Master
。这个时候,我们的应用即使使用了Jedis
连接池,Master
服务挂了,还是无法连接新的 Master
服务。为了解决这个问题,Jedis
也提供了相应的 Sentinel
实现,能够在 Redis Sentinel
主从切换时候,通知我们的应用,把我们的应用连接到新的 Master
服务。
Set<String> sentinels = new HashSet<>();
sentinels.add("ip1");
sentinels.add("ip2");
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxIdle(8);
jedisPoolConfig.setMaxTotal(18);
JedisSentinelPool jedisPool = new JedisSentinelPool("mymaster", sentinels, jedisPoolConfig);
Jedis jedis = jedisPool.getResource();
......
jedis.close();
jedisPool.close();
JedisSentinelPool
的使用很简单,添加了设置服务器ip的 set 集合和 masterName
参数,Jedis Sentinel
底层基于 Redis
订阅实现 Redis
主从服务的切换通知,当 Redis
发生主从切换时,Sentinel
会发送通知主动通知 Jedis
进行连接的切换。JedisSentinelPool
在每次从连接池中获取连接对象的时候,都要对连接对象进行检测,如果此链接和 Sentinel
的 Master
服务连接参数不一致,则会关闭此连接,重新获取新的 Jedis
连接对象。
@Override
public Jedis getResource() {
while (true) {
Jedis jedis = super.getResource();
jedis.setDataSource(this);
// get a reference because it can change concurrently
final HostAndPort master = currentHostMaster;
final HostAndPort connection = new HostAndPort(jedis.getClient().getHost(), jedis.getClient()
.getPort());
if (master.equals(connection)) {
// connected to the correct master
return jedis;
} else {
returnBrokenResource(jedis);
}
}
}
使用 Spring
时可以引入 spring-data-redis
包,使用 SpringBoot
时可以直接引用 spring-boot-starter-redis
包,其内部都整合了 Jedis
。
当然,也可以单独引用 Jedis
包。
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
Lettuce
Lettuce
是基于 Netty
框架(NIO)的事件驱动的通信,支持同步和异步调用的,可扩展的 redis client,多个线程可以共享一个 RedisConnection
,线程安全。
基本使用
1、引 lettuce
依赖
<dependency>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</dependency>
2、lettuce
操作Redis
// 同步操作
@Test
public void test() {
RedisURI redisURI = RedisURI.builder().withHost(“ip”).withPort(6379).withTimeout(Duration.of(10, ChronoUnit.SECONDS)).build();
RedisClient redisClient = RedisClient.create(redisURI);
StatefulRedisConnection<String, String> connection = redisClient.connect();
RedisCommands<String, String> syncCommands = connection.sync();
String set = syncCommands.set(“k1”, “v1”);
System.out.println(set);
connection.close();
redisClient.shutdown();
}
// 异步操作
@Test
public void testAsync(){
RedisURI redisURI = RedisURI.builder().withHost(“ip”).withPort(6379).withTimeout(Duration.of(10, ChronoUnit.SECONDS)).build();
RedisClient redisClient = RedisClient.create(redisURI);
StatefulRedisConnection<String, String> connection = redisClient.connect();
RedisAsyncCommands<String, String> asyncCommands = connection.async();
RedisFuture<String> set = asyncCommands.set(“k2”, “v2”);
System.out.println(set);
connection.close();
redisClient.shutdown();
}
// 响应式API
@Test
public void testReactive() {
RedisURI redisURI = RedisURI.builder().withHost(“192.168.96.173”).withPort(6379).withTimeout(Duration.of(10, ChronoUnit.SECONDS)).build();
RedisClient redisClient = RedisClient.create(redisURI);
StatefulRedisConnection<String, String> connection = redisClient.connect();
RedisReactiveCommands<String, String> reactiveCommands = connection.reactive();
Mono<String> set = reactiveCommands.set(“name”, “feiyangyang”);
System.out.println(set.block());
高可用连接
@Test
public void masterSlave() {
RedisURI redisURI = RedisURI.builder().withHost("ip").withPort(6379).withTimeout(Duration.of(10, ChronoUnit.SECONDS)).build();
RedisClient client = RedisClient.create(redisURI);
StatefulRedisMasterReplicaConnection<String, String> connection = MasterReplica.connect(client, StringCodec.UTF8, redisURI);
// 从节点读主节点数据
connection.setReadFrom(ReadFrom.REPLICA);
RedisCommands<String, String> commands = connection.sync();
commands.set("name", "feiyangyang");
System.out.println(commands.get("name"));
connection.close();
client.shutdown();
}
// 哨兵
@Test
public void sentinel() {
List<RedisURI> uris = new ArrayList();
uris.add(RedisURI.builder().withSentinel("ip1", 26379).withSentinelMasterId("mymaster").withPassword("123456").build());
uris.add(RedisURI.builder().withSentinel("ip2", 26379).withSentinelMasterId("mymaster").withPassword("123456").build());
uris.add(RedisURI.builder().withSentinel("ip3", 26379).withSentinelMasterId("mymaster").withPassword("123456").build());
RedisClient client = RedisClient.create();
StatefulRedisMasterReplicaConnection<String, String> connection = MasterReplica.connect(client, StringCodec.UTF8, uris);
connection.setReadFrom(ReadFrom.REPLICA);
RedisCommands<String, String> commands = connection.sync();
commands.set("name", "feiyangyang");
System.out.println(commands.get("name"));
connection.close();
client.shutdown();
}
// 集群
@Test
public void cluster() {
Set<RedisURI> uris = new HashSet<>();
uris.add(RedisURI.builder().withHost("ip1").withPort(7000).withPassword("123456").build());
uris.add(RedisURI.builder().withHost("ip2").withPort(7001).withPassword("123456").build());
uris.add(RedisURI.builder().withHost("ip3").withPort(7000).withPassword("123456").build());
uris.add(RedisURI.builder().withHost("ip4").withPort(7001).withPassword("123456").build());
uris.add(RedisURI.builder().withHost("ip5").withPort(7000).withPassword("123456").build());
uris.add(RedisURI.builder().withHost("ip6").withPort(7001).withPassword("123456").build());
RedisClusterClient client = RedisClusterClient.create(uris);
StatefulRedisClusterConnection<String, String> connection = client.connect();
RedisAdvancedClusterCommands<String, String> commands = connection.sync();
commands.set("name", "feiyangyang");
System.out.println(commands.get("name"));
//选择从节点,只读
NodeSelection<String, String> replicas = commands.replicas();
NodeSelectionCommands<String, String> nodeSelectionCommands = replicas.commands();
Executions<List<String>> keys = nodeSelectionCommands.keys("*");
keys.forEach(key -> System.out.println(key));
connection.close();
client.shutdown();
}
总结
Jedis
是直连 redis server,会有线程安全问题。除非使用连接池,为每个 Jedis
实例增加物理连接。
优点:
- 简单易理解
- 全面的Redis操作API
缺点:
- 同步阻塞IO
- 不支持异步
- 线程不安全
Lettuce
是基于Netty
的,连接实例可以在多个线程间并发访问,Lettuce
还支持异步连接方式,提高网络等待和磁盘IO效率。
优点:
- 线程安全
- 基于Netty框架的事件驱动通信,可异步调用
- 适用于分布式缓存
缺点:
- 学习成本高,上手相对复杂
以上是关于Redis Client 之 Jedis与Lettuce的主要内容,如果未能解决你的问题,请参考以下文章
redis.clients.jedis.exceptions.JedisDataException: ERR Client sent AUTH, but no password is set
Redis错误:jedis.exceptions.JedisDataException: ERR Client sent AUTH, but no password is set
redis.clients.jedis.exceptions.JedisDataException: ERR Client sent AUTH, but no password is set
redis.clients.jedis.exceptions.JedisDataException: ERR Client sent AUTH, but no password is set