吃透Redis系列(十一):Jedis和Lettuce客户端详细介绍
作者:互联网
Redis系列文章:
吃透Redis系列(一):Linux下Redis安装
吃透Redis系列(二):Redis六大数据类型详细用法
吃透Redis系列(三):Redis管道,发布/订阅,事物,过期时间 详细介绍
吃透Redis系列(四):布隆(bloom)过滤器详细介绍
吃透Redis系列(五):RDB和AOF持久化详细介绍
吃透Redis系列(六):主从复制详细介绍
吃透Redis系列(七):哨兵机制详细介绍
吃透Redis系列(八):集群详细介绍
吃透Redis系列(九):Redis代理twemproxy和predixy详细介绍
吃透Redis系列(十)Redis内存模型详细介绍
吃透Redis系列(十一):Jedis和Lettuce客户端详细介绍
前言
我们打开Redis官网(https://redis.io/clients)发现,Redis支持以下语言:
因为Redis是二进制安全的,只存字节数组,所以只要各种语言客户端相互编解码一致,那么就可以互相通用,比如c存java取,等等。
当然,我们最关心的还是在Java中的使用,所以我们还是点开Java,看都有哪些客户端支持:
有如上几种客户端,那么你看最常用的就是标星的Jedis,Lettuce,Redisson。
接下来我们只详细了解Jedis,Lettuce,因为我们Spring中也是用了这两种客户端,具体在Spring中怎么使用,请参考挂网 Spring Data Redis
一,Jedis使用
首先打开Jedis的github:https://github.com/redis/jedis?_ga=2.241806790.538319447.1611192789-1620311179.1610019103
1,导入maven包
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.5.0</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
2,五种基本类型
public class MyRedisTest {
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1", 6379);
/**
* 1,字符串操作
*/
jedis.set("name", "bobo");
jedis.set("age", "18");
jedis.incr("age");
System.out.println("name:" + jedis.get("name") + " age:" + jedis.get("age"));
/**
* 2,list操作
*/
String keyList = "myList";
jedis.rpush(keyList, "1");
jedis.rpush(keyList, "2");
jedis.rpush(keyList, "3");
System.out.println(jedis.lrange(keyList, 0, -1));
/**
* 3,set操作
*/
String keySet1 = "mySet1";
String keySet2 = "mySet2";
jedis.sadd(keySet1, "a", "b", "c");
jedis.sadd(keySet2, "d", "b", "f");
//取交集
jedis.sinterstore("result1", keySet1, keySet2);
System.out.println("交集:" + jedis.smembers("result1"));
//取并集
jedis.sunionstore("result2", keySet1, keySet2);
System.out.println("并集:" + jedis.smembers("result2"));
/**
* 4,有序集合
*/
String keyStoreSet = "myStoreSet";
jedis.zadd(keyStoreSet, 3, "score3");
jedis.zadd(keyStoreSet, 1, "score1");
jedis.zadd(keyStoreSet, 2, "score2");
System.out.println("zSet:" + jedis.zrange(keyStoreSet, 0, -1));
/**
* 5,哈希操作
*/
String hashKey = "myHashKey";
jedis.hset(hashKey, "name", "bobo");
jedis.hset(hashKey, "age", "18");
System.out.println("hash value:" + jedis.hvals(hashKey));
}
}
运行输出:
name:bobo age:19
[1, 2, 3]
交集:[b]
并集:[f, a, b, c, d]
zSet:[score1, score2, score3]
hash value:[bobo, 18]
redis-cli查询key:
3,管道
@Test
public void testPipe() {
// 清空数据
jedis.flushDB();
Pipeline pipelined = jedis.pipelined();
pipelined.set("name", "bobo");
pipelined.zadd("storeSet", 3, "score3");
pipelined.zadd("storeSet", 1, "score1");
pipelined.zadd("storeSet", 2, "score2");
pipelined.sync();
System.out.println("name:" + jedis.get("name"));
System.out.println("storeSet:" + jedis.zrange("storeSet", 0, -1));
}
运行输出:
name:bobo
storeSet:[score1, score2, score3]
4,事物
@Test
public void testTransaction() {
jedis.flushDB();
Transaction transaction = jedis.multi();
transaction.set("name", "bobo");
transaction.lpush("myList", "a", "b", "c");
transaction.lpop("myList");
transaction.exec();
System.out.println("name:" + jedis.get("name"));
System.out.println("myList:" + jedis.lrange("myList", 0, -1));
}
运行输出:
name:bobo
myList:[b, a]
5,哨兵
部署三个哨兵节点
哨兵部署详细操作请看:吃透Redis系列(七):哨兵机制详细介绍
# sentinel_26379.conf
port 26379
daemonize yes
logfile /var/lib/sentinel_26379.log
sentinel monitor mymaster 127.0.0.1 6379 2
# sentinel_26380.conf
port 26380
daemonize yes
logfile /var/lib/sentinel_26380.log
sentinel monitor mymaster 127.0.0.1 6379 2
# sentinel_26381.conf
port 26381
daemonize yes
logfile /var/lib/sentinel_26381.log
sentinel monitor mymaster 127.0.0.1 6379 2
并启动以上三个哨兵
sudo redis-sentinel /etc/redis/sentinel_26379.conf
sudo redis-sentinel /etc/redis/sentinel_26380.conf
sudo redis-sentinel /etc/redis/sentinel_26381.conf
查看哨兵运行状态:
jedis访问:
@Test
public void testSentinel(){
Set<String> set = new HashSet<String>();
set.add("127.0.0.1:26379");
set.add("127.0.0.1:26380");
set.add("127.0.0.1:26381");
JedisSentinelPool sentinelPool = new JedisSentinelPool("mymaster", set);
Jedis jedis = sentinelPool.getResource();
jedis.set("name","bo");
System.out.println(jedis.get("name"));
}
运行输出:
bo
6,集群
部署redis cluster集群
redis cluster集群的部署详细介绍参考:吃透Redis系列(八):集群详细介绍
部署集群,其中7000,7001,7002是三个主节点
8000,8001,8002是三个从节点
搭建完成之后,查看当前集群节点状态:
Jedis访问集群:
@Test
public void testCluster(){
Set<HostAndPort> set = new HashSet<HostAndPort>();
set.add(new HostAndPort("127.0.0.1",7000));
set.add(new HostAndPort("127.0.0.1",7001));
set.add(new HostAndPort("127.0.0.1",7002));
JedisCluster jedisCluster = new JedisCluster(set);
jedisCluster.set("name","helloword");
System.out.println(jedisCluster.get("name"));
}
运行输出:
helloword
二,Lettuce使用
Lettuce是一个高性能基于Java编写的Redis驱动框架,底层集成了Project Reactor提供天然的反应式编程,通信框架集成了Netty使用了非阻塞IO,5.x版本之后融合了JDK1.8的异步编程特性,在保证高性能的同时提供了十分丰富易用的API。
Lettuce
使用的时候依赖于四个主要组件:
RedisURI
:连接信息。RedisClient
:Redis
客户端,特殊地,集群连接有一个定制的RedisClusterClient
。Connection
:Redis
连接,主要是StatefulConnection
或者StatefulRedisConnection
的子类,连接的类型主要由连接的具体方式(单机、哨兵、集群、订阅发布等等)选定,比较重要。RedisCommands
:Redis
命令API
接口,基本上覆盖了Redis
发行版本的所有命令,提供了同步(sync
)、异步(async
)、反应式(reative
)的调用方式,对于使用者而言,会经常跟RedisCommands
系列接口打交道。
使用到的软件版本:Java 1.8.0_191、Redis 6.0.6、lettuce 5.3.1.RELEASE。
Lettuce内容参考连接:https://www.cnblogs.com/throwable/p/11601538.html#lettuce
1,Lettuce简单使用
导入maven包
<dependency>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
<version>5.3.1.RELEASE</version>
</dependency>
基本使用
@Test
public void testSetGet() {
// 连接信息
RedisURI redisURI = RedisURI.builder().withHost("127.0.0.1")
.withPort(6379)
.withTimeout(Duration.of(10, ChronoUnit.SECONDS))
.build();
// redis客户端
RedisClient redisClient = RedisClient.create(redisURI);
// 创建redis连接
StatefulRedisConnection<String, String> connect = redisClient.connect();
// 同步调用方式
RedisCommands<String, String> redisCommands = connect.sync();
// set参数,设置5秒过期时间
SetArgs setArgs = SetArgs.Builder.nx().ex(5);
// 设置key,value
redisCommands.set("name", "bobo", setArgs);
// 获取name的值
System.out.println(redisCommands.get("name"));
// 关闭连接
connect.close();
// 关闭客户端
redisClient.shutdown();
}
运行输出:
bobo
2,API
Lettuce
主要提供三种API
:
- 同步(
sync
):RedisCommands
。 - 异步(
async
):RedisAsyncCommands
。 - 反应式(
reactive
):RedisReactiveCommands
。
先准备好一个单机Redis
连接备用:
// redis客户端
private RedisClient redisClient;
// redis连接
private StatefulRedisConnection<String, String> connection;
@Before
public void before() {
RedisURI redisURI = RedisURI.builder().withHost("127.0.0.1")
.withPort(6379)
.withTimeout(Duration.of(10, ChronoUnit.SECONDS))
.build();
// redis客户端
redisClient = RedisClient.create(redisURI);
// 创建redis连接
connection = redisClient.connect();
}
@After
public void after() {
connection.close();
redisClient.shutdown();
}
Redis
命令API
的具体实现可以直接从StatefulRedisConnection
实例获取,见其接口定义:
public interface StatefulRedisConnection<K, V> extends StatefulConnection<K, V> {
boolean isMulti();
RedisCommands<K, V> sync();
RedisAsyncCommands<K, V> async();
RedisReactiveCommands<K, V> reactive();
}
值得注意的是,在不指定编码解码器RedisCodec
的前提下,RedisClient
创建的StatefulRedisConnection
实例一般是泛型实例StatefulRedisConnection<String,String>
,也就是所有命令API
的KEY
和VALUE
都是String
类型,这种使用方式能满足大部分的使用场景。当然,必要的时候可以定制编码解码器RedisCodec<K,V>
。
2.1,同步API
同步API
在所有命令调用之后会立即返回结果。和上一篇介绍的Jedis
差不多,RedisCommands
的用法其实和它相差不大。
@Test
public void testSync(){
RedisCommands<String, String> redisCommands = connection.sync();
redisCommands.set("name","bobo");
System.out.println(redisCommands.get("name"));
}
2.2,异步API
@Test
public void testAsync() throws ExecutionException, InterruptedException {
RedisAsyncCommands<String, String> redisAsyncCommands = connection.async();
RedisFuture<String> redisFuture = redisAsyncCommands.set("name", "bobo");
System.out.println(redisFuture.get());
System.out.println("get2:" + redisAsyncCommands.get("name").get());
}
RedisAsyncCommands
所有方法执行返回结果都是RedisFuture
实例,而RedisFuture
接口的定义如下:
public interface RedisFuture<V> extends CompletionStage<V>, Future<V> {
String getError();
boolean await(long timeout, TimeUnit unit) throws InterruptedException;
}
也就是,RedisFuture
可以无缝使用Future
或者JDK
1.8中引入的CompletableFuture
提供的方法。
2.3,反应式API
Lettuce
引入的反应式编程框架是Project Reactor,如果没有反应式编程经验可以先自行了解一下Project Reactor
。
构建RedisReactiveCommands
实例:
@Test
public void testReactive(){
RedisReactiveCommands<String, String> redisReactiveCommands = connection.reactive();
redisReactiveCommands.set("name", "bobo");
System.out.println(redisReactiveCommands.get("name"));
redisReactiveCommands.sadd("fruit","apple","banana");
System.out.println(redisReactiveCommands.smembers("fruit"));
}
运行输出:
MonoFromPublisher
FluxSource
RedisReactiveCommands
的方法如果返回的结果只包含0或1个元素,那么返回值类型是Mono
,如果返回的结果包含0到N(N大于0)个元素,那么返回值是Flux
。
3,发布/订阅
非集群模式下的发布订阅依赖于定制的连接StatefulRedisPubSubConnection
,集群模式下的发布订阅依赖于定制的连接StatefulRedisClusterPubSubConnection
,两者分别来源于RedisClient#connectPubSub()
系列方法和RedisClusterClient#connectPubSub()
:
非集群模式:
// 可能是单机、普通主从、哨兵等非集群模式的客户端
RedisClient client = ...
StatefulRedisPubSubConnection<String, String> connection = client.connectPubSub();
connection.addListener(new RedisPubSubListener<String, String>() { ... });
// 同步命令
RedisPubSubCommands<String, String> sync = connection.sync();
sync.subscribe("channel");
// 异步命令
RedisPubSubAsyncCommands<String, String> async = connection.async();
RedisFuture<Void> future = async.subscribe("channel");
// 反应式命令
RedisPubSubReactiveCommands<String, String> reactive = connection.reactive();
reactive.subscribe("channel").subscribe();
reactive.observeChannels().doOnNext(patternMessage -> {...}).subscribe()
集群模式:
// 使用方式其实和非集群模式基本一致
RedisClusterClient clusterClient = ...
StatefulRedisClusterPubSubConnection<String, String> connection = clusterClient.connectPubSub();
connection.addListener(new RedisPubSubListener<String, String>() { ... });
RedisPubSubCommands<String, String> sync = connection.sync();
sync.subscribe("channel");
// ...
4,管道
Redis
的Pipeline
也就是管道机制可以理解为把多个命令打包在一次请求发送到Redis
服务端,然后Redis
服务端把所有的响应结果打包好一次性返回,从而节省不必要的网络资源(最主要是减少网络请求次数)。Redis
对于Pipeline
机制如何实现并没有明确的规定,也没有提供特殊的命令支持Pipeline
机制。Jedis
中底层采用BIO
(阻塞IO)通讯,所以它的做法是客户端缓存将要发送的命令,最后需要触发然后同步发送一个巨大的命令列表包,再接收和解析一个巨大的响应列表包。
Pipeline
在Lettuce
中对使用者是透明的,由于底层的通讯框架是Netty
,所以网络通讯层面的优化Lettuce
不需要过多干预,换言之可以这样理解:Netty
帮Lettuce
从底层实现了Redis
的Pipeline
机制。
5,事物
事务相关的命令就是WATCH
、UNWATCH
、EXEC
、MULTI
和DISCARD
,在RedisCommands
系列接口中有对应的方法。举个例子:
@Test
public void testMulti() {
RedisCommands<String, String> redisCommands = connection.sync();
redisCommands.multi();
redisCommands.set("name", "bobo");
redisCommands.set("age", "18");
redisCommands.exec();
System.out.println(redisCommands.get("name"));
System.out.println(redisCommands.get("age"));
}
运行输出:
bobo
18
6,普通主从模式
假设现在有三个Redis
服务形成树状主从关系如下:
- 节点一:localhost:6379,角色为Master。
- 节点二:localhost:6380,角色为Slavor,节点一的从节点。
- 节点三:localhost:6381,角色为Slavor,节点二的从节点。
@Test
public void testStaticReplica() throws Exception {
List<RedisURI> uris = new ArrayList<>();
RedisURI uri1 = RedisURI.builder().withHost("localhost").withPort(6379).build();
RedisURI uri2 = RedisURI.builder().withHost("localhost").withPort(6380).build();
RedisURI uri3 = RedisURI.builder().withHost("localhost").withPort(6381).build();
uris.add(uri1);
uris.add(uri2);
uris.add(uri3);
RedisClient redisClient = RedisClient.create();
StatefulRedisMasterSlaveConnection<String, String> connection = MasterSlave.connect(redisClient,
new Utf8StringCodec(), uris);
// 只从主节点读取数据
connection.setReadFrom(ReadFrom.MASTER);
// 执行其他Redis命令
connection.close();
redisClient.shutdown();
}
7,哨兵
由于Lettuce
自身提供了哨兵的拓扑发现机制,所以只需要随便配置一个哨兵节点的RedisURI
实例即可:
@Test
public void testDynamicSentinel() throws Exception {
RedisURI redisUri = RedisURI.builder()
.withPassword("你的密码")
.withSentinel("localhost", 26379)
.withSentinelMasterId("哨兵Master的ID")
.build();
RedisClient redisClient = RedisClient.create();
StatefulRedisMasterSlaveConnection<String, String> connection = MasterSlave.connect(redisClient, new Utf8StringCodec(), redisUri);
// 只允许从从节点读取数据
connection.setReadFrom(ReadFrom.SLAVE);
RedisCommands<String, String> command = connection.sync();
SetArgs setArgs = SetArgs.Builder.nx().ex(5);
command.set("name", "throwable", setArgs);
String value = command.get("name");
log.info("Get value:{}", value);
}
8,集群
下面的API提供跨槽位(Slot
)调用的功能:
RedisAdvancedClusterCommands
。RedisAdvancedClusterAsyncCommands
。RedisAdvancedClusterReactiveCommands
。
静态节点选择功能:
masters
:选择所有主节点执行命令。slaves
:选择所有从节点执行命令,其实就是只读模式。all nodes
:命令可以在所有节点执行。
集群拓扑视图动态更新功能:
- 手动更新,主动调用
RedisClusterClient#reloadPartitions()
。 - 后台定时更新。
- 自适应更新,基于连接断开和
MOVED/ASK
命令重定向自动更新。
简单的集群连接和使用方式如下:
@Test
public void testCluster() {
Set<RedisURI> redisURISet = new HashSet<>();
redisURISet.add(RedisURI.builder().withHost("127.0.0.1").withPort(7000).build());
redisURISet.add(RedisURI.builder().withHost("127.0.0.1").withPort(7001).build());
redisURISet.add(RedisURI.builder().withHost("127.0.0.1").withPort(7002).build());
RedisClusterClient redisClusterClient = RedisClusterClient.create(redisURISet);
StatefulRedisClusterConnection<String, String> connect = redisClusterClient.connect();
RedisAdvancedClusterCommands<String, String> clusterCommands = connect.sync();
clusterCommands.set("name","bobo");
System.out.println(clusterCommands.get("name"));
}
集群节点信息如下:
标签:set,name,Redis,Lettuce,Jedis,println,jedis,out 来源: https://blog.csdn.net/u013277209/article/details/113106946