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用戶端詳細介紹
文章目錄
-
- 前言
- 一,Jedis使用
-
- 1,導入maven包
- 2,五種基本類型
- 3,管道
- 4,事物
- 5,哨兵
- 6,叢集
- 二,Lettuce使用
-
- 1,Lettuce簡單使用
- 2,API
-
- 2.1,同步API
- 2.2,異步API
- 2.3,反應式API
- 3,釋出/訂閱
- 4,管道
- 5,事物
- 6,普通主從模式
- 7,哨兵
- 8,叢集
前言
我們打開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"));
}
叢集節點資訊如下: