Redis篇之操作、lettuce客戶端、Spring集成以及Spring Boot配置
目錄
一、Redis簡介
1.1 數據結構的操作
1.2 重要概念分析
二、Redis客戶端
2.1 簡介
2.2 連接
2.3 基本用法
2.4 同步與異步
2.5 消費RedisFuture<T>
2.6 使用消費者監聽器
2.7 發布訂閱(Pub/Sub)
2.8 事務(Transaction)
2.9 主從復制(Master/Replica)
2.10 集群
三、Spring Data Redis操作
3.1 簡介
3.2 配置Lettuce連接
3.3 主寫從讀模式配置
3.4 Redis哨兵模式(Sentinel)
3.5 發布訂閱(Pub/Seb)
3.6 事務(Transaction)
3.7 流水線(Pipelining)
3.8 集群(Cluster)
3.9 序列化與反序列化
四、源碼淺析
4.1 RedisTemplate
4.2 Operations類和Commands類
4.3 數據結構
4.4 SessionCallback接口
4.5 RedisCallbacke接口
4.6 總結
五、Spring Boot整合Redis
5.1 pom.xml文件
5.2 spring-boot-autoconfigure配置
5.3 RedisProperties類
5.4 LettuceConnectionConfiguration類
5.5 RedisAutoConfiguration類
六、總結
七、附錄 相關網址
一、Redis簡介
官方給出的定義是:
- Redis 是一個開源(BSD許可)的,內存中的數據結構存儲系統,它可以用作數據庫、緩存和消息中間件。
- 它支持多種類型的數據結構,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 與范圍查詢, bitmaps, hyperloglogs 和 地理空間(geospatial) 索引半徑查詢。
- Redis 內置了 復制(replication),LUA腳本(Lua scripting), LRU驅動事件(LRU eviction),事務(transactions)
- 提供不同級別的 磁盤持久化(persistence), 並通過 Redis哨兵(Sentinel)和自動 分區(Cluster)提供高可用性(high availability)
那么接下來,逐步分析官方給出的定義的重要概念。
1.1 數據結構的操作
下面簡單介紹常見命令:
(1)字符串(strings):
- 添加:set key value
- 取值:get key
- (整型)遞增: incr key
- (多值)添加:mset key1 val1 key2 val2 key3 val3
- (多值)取值:mget key1 key2 key3
- 修改:set key newVal
- 查詢(是否存在):exists key
- 刪除:del key
- 查詢類型:type key
- (創建值后)設置超時(time時間后將key對應值刪除):expire key time
- (創建值時)設置超時:set key val ex time
- 去除超時:persist key
- 查看超時剩余時間:ttl key
(2)散列(hashes):
- 添加多值:hmset yourhash field val [field val ...]
- 添加單值:hset yourhash field val
- 取多值:hmget yourhash field [field ...]
- 取單值:hget yourhash field
- 取全值: hgetall yourhash
- 刪除值:hdel yourhash field [field ...]
(3)列表(lists):
- 鏈表左邊添加:lpush list val
- 鏈表右邊添加:rpush list val
- 范圍內取值:lrange list index_start index_end
- 截取范圍內值:ltrim list index_start index_end
- 添加多值:rpush list val1 val2 val3 val4
- 左邊刪除:lpop list
- 右邊刪除: rpop list
- 阻塞式訪問左刪除:blpop list 或者 blpop list1 list2 list3
- 阻塞式訪問右刪除:brpop list 或者 brpop list1 list2 list3
- 原子性地返回並移除存儲在 list1 的列表的最后一個元素(列表尾部元素), 並把該元素放入存儲在 list2的列表的第一個元素位置(列表頭部) : rpoplpush list1 list2
- 阻塞版RPOPLPUSH:brpoplpush list1 list2
(4)集合(sets): String 的無序排列 , 適合用於表示對象間的關系 。
- 添加: sadd myset val1 val2 val3
- 查詢元素:smembers myset
- 查詢特定元素: sismember myset val
- 刪除(隨機):spop myset
(5)有序集合(sorted sets):
- 添加(更新):zadd mysortedset score val [score val ...]
- 范圍內取值:zrange mysortedset score_begin score_end
- 取索引值:zscore mysortedset val
- 刪除索引值最大的值: zpopmax mysortedset
- 刪除索引值最小的值: zpopmin mysortedset
(6)bitmaps:不是實際的數據結構,而是一個字符串類型定義的面向比特的集合。
- 添加值:setbit key offset [offset ...]
- 取值:getbit key offset
(7)hyperloglogs:是一種概率的數據結構,用於計算唯一的數據。
- 添加:pfadd key element [element ...]
- 合並: pfmerge key key1 key2
想詳細了解可通過點擊下面鏈接:
redis官方命令大全
https://redis.io/commands
1.2 重要概念分析
(1)復制(基於主從結構)
機制:
- 當主從連接正常時,master通過傳輸命令流來保持slave的數據同步;
- 當主從連接由於網絡原因或連接超時出現中斷時,slave重新連接並試圖部分重新同步(同步中斷之后的數據);
- 當部分同步無效后,slave將會申請完全同步。
特性:
- Redis采用異步復制,而主從也是異步地確認需要處理的數據量;
- 一個master可以有多個slave;
- Slave可以連接其他slave;
- 在master處,復制是非阻塞式的;
- 在slave處,大部分復制也是非阻塞式的;
- 復制可以用在實現Redis系統伸縮性,讓多個slave負責提供只讀查詢,也可以用於提高數據安全和系統高可用性。
- 可以使用復制來避免master將全部數據寫入磁盤的開銷。
(2)事務
保證機制:
- 事務中所有命令都被序列化並會按序執行,並不可打斷;
- Readis事物是原子性的,若執行則全部執行,若失敗則全部失敗。
使用:
- MULTI命令開啟事務;
- EXEC命令執行事務。
(3)持久化
持久化方式:
- RDB持久化方式能夠在指定的時間間隔進行數據集快照存儲;
- AOF持久化方式會記錄每一個sever的寫操作,當server重啟時,將重新執行記錄的寫操作構建原始數據集;
- 如果只希望數據存在於server運行時,可以不進行持久化;
- 可以結合RDB和AOF進行持久還。
(4)哨兵(Sentinel)
功能:
- 監控(monitoring):Sentinel不斷檢查master和slave以確保他們按預期運行;
- 提醒(notification):當被監控的Redis出現問題時,將會通過api向用戶或者另一個程序發送通知;
- 自動故障轉移(automatic failover):當master失效時,Sentinel會開啟故障轉移處理,將一個slave提升為master,原依附於故障master的slaves將重新配置依附於新的master,並在使用Redis server的應用連接時,告知其使用master的新地址;
- 配置提供者(Configuretion provider):Sentinel充當客戶端(clilent)服務發現的根據來源,客戶端連接Sentinel,為一個指定的服務請求當前Redis master響應的地址(若發生故障轉移,則會向所有連接Sentinel的客戶端告知master的新地址)。
(5)分區(Partitioning)
目的:
- 使用多個計算機提供的內存總和來構建更大的數據庫(若沒有分區,用戶將只能使用單台計算機提供的內存量);
- 拓展計算能力到多核和多計算機,將網絡帶寬拓展到多計算機和多適配器。
方式:
- 客戶端分區:客戶端直接在正確的節點讀取或寫入key(大部分客戶端已實現);
- 代理分區:客戶端向代理端請求數據,由代理端訪問正確的節點;
- 查詢路由:客戶端向一個隨機實例請求查詢,該實例會將請求轉移到正確節點上。
二、Redis客戶端
現在用得比較多的我比較關注的Redis Java客戶端是Jedis和Lettuce,而在Spring Data框架以及Spring Boot中也是使用這兩種客戶端。因此,我將關注與Spring Data是如何整合Redis(實現Redis的操作和功能)以及Spring Boot中是如何實現自動配置的(包括提供的配置項)。在Jedis和Lettuce中,我對Lettuce更感興趣(可能由於我比較喜歡吃生菜吧),所以,接下來的內容,將圍繞者Lettuce客戶端進行分析,以及之后的框架整合主要也是以lettuce為主。
2.1 簡介
Lettuce是可拓展的線程安全的Redis客戶端,提供同步、異步和響應式APIs。如果避免使用阻塞和事務性操作(例如,BLPOP和MULTI/EXEC),多個線程可以共享一個連接。Nttey的nio框架對多個連接提供了有效的管理。支持Redis的高級特性,例如哨兵(Sential)、集群(Cluster)以及Redis數據結構。
2.2 連接
Lettuce中RedisURI是創建連接的關鍵,那么,接下來,看看RedisURI是怎么創建的。
(1)構建方式:
- 使用uri:RedisURI.create("redis://localhost/");
- 使用Builder:RedisURI.Builder.redis("localhost",6379).auth("password").database(1).build();
- 直接使用構造方法:new RedisURI("localhost", 6379, 60, TimeUnit.SECONDS);
(2)RedisURI句式:
- Redis standalone模式:redis :// [: password@] host [: port] [/ database][? [timeout=timeout[d|h|m|s|ms|us|ns]] [&_database=database_]]
- Redis standalone模式 (SSL):rediss :// [: password@] host [: port] [/ database][? [timeout=timeout[d|h|m|s|ms|us|ns]] [&_database=database_]]
- Redis哨兵模式:redis-sentinel :// [: password@] host1[: port1] [, host2[: port2]] [, hostN[: portN]] [/ database][?[timeout=timeout[d|h|m|s|ms|us|ns]] [&_sentinelMasterId=sentinelMasterId_] [&_database=database_]
2.3 基本用法
RedisClient client = RedisClient.create(redisURI); (1)
StatefulRedisConnection<String, String> connection = client.connect(); (2)
RedisCommands<String, String> commands = connection.sync(); (3)
String value = commands.get("key"); (4)
...
connection.close(); (5)
client.shutdown(); (6)
(1)根據給出的redisURI(創建方法在2.2節)創建Redis客戶端(默認連接6379端口);
(2)打開Redis standalone模式(模式由給出的RedisURI決定的,即redis、rediss和redis-sentinel的區別)連接。
(3)獲得同步執行的命令API;
(4)發出一個GET命令獲取“foo”對應的值;
(5)關閉連接;
(6)關掉客戶端。
2.4 同步與異步
(1)RedisFuture<T>和CompleteableFuture<T>簡介
每一個異步的API命令的調用都會創建一個可以取消、等待和訂閱的RedisFuture<T>。而一個RedisFuture<T>或CompleteableFuture<T>是一個指向值計算尚未完成的最初未知結果的指針。一個RedisFuture<T>提供異步和鏈接的操作。
異步通過RedisFuture<T>進行操作,同步直接通過同步執行命令進行操作。
(2)創建RedisFuture<T>(Lettuce中)
- 獲取同步執行命令API
//根據redisURI創建客戶端
RedisClient client = RedisClient.create(redisURI);
//創建連接
StatefulRedisConnection<String, String> connection = client.connect();
//獲取同步執行命令
RedisCommands<String, String> sync = connection.sync();
//發送get請求,獲取值
String value = sync.get("key");
...
//關閉連接
connection.close();
//關掉客戶端
client.shutdown();
- 獲取異步執行命令API
RedisClient client = RedisClient.create(redisURI);
//創建連接
StatefulRedisConnection<String, String> connection = client.connect();
//獲取異步執行命令api
RedisAsyCommands<String, String> commands = connection.async();
//獲取RedisFuture<T>
RedisFuture<String> future = commands.get("key");
2.5 消費RedisFuture<T>:
- 沒有設置超時(拉模式)
RedisFuture<String> future = commands.get("key");
//使用拉模式調用get方法,阻塞調用線程,直到計算結果完成,
//最壞的情況是線程一直阻塞
String value = future.get();
System.out.println(value);
- 設置超時:
//獲取異步執行api
RedisAsyncCommands<String, String> async = client.connect().async();
//發送set請求
RedisFuture<String> set = async.set("key", "value");
//發送get請求
RedisFuture<String> get = async.get("key");
//設置set超時
set.await(1, SECONDS) == true
set.get() == "OK"
//設置get超時
get.get(1, TimeUnit.MINUTES) == "value"
2.6 使用消費者監聽器:
- 非阻塞
//發送get請求
RedisFuture<String> future = commands.get("key");
//設置監聽器
future.thenAccept(new Consumer<String>() {
//該方法將會在future.complete()方法執行后,自動執行
@Override
public void accept(String value) {
...
}
});
- 阻塞同步
try {
RedisFuture<String> future = commands.get("key");
//設置超時
String value = future.get(1, TimeUnit.MINUTES);
System.out.println(value);
} catch (Exception e) {
//超時后,將拋出TimeoutException
e.printStackTrace();
}
- 推模式
RedisFuture<String> future = commands.get("key");
//在future完成執行(即future.complete())時,將觸發該方法
//這樣的執行流程就是推模式
future.thenAccept(new Consumer<String>() {
@Override
public void accept(String value) {
System.out.println(value);
}
});
2.7 發布訂閱(Pub/Sub)
- 同步訂閱
//創建發布訂閱連接
StatefulRedisPubSubConnection<String, String> connection = client.connectPubSub()
//添加監聽器
connection.addListener(new RedisPubSubListener<String, String>() { ... })
//獲取同步發布訂閱執行命令API
RedisPubSubCommands<String, String> sync = connection.sync();
//訂閱channel通道信息
sync.subscribe("channel");
//向channel通道發送message信息,
//暫時沒找到該命令,等后期補充
...
//下面是自定義的業務代碼
...
- 異步訂閱
StatefulRedisPubSubConnection<String, String> connection = client.connectPubSub()
connection.addListener(new RedisPubSubListener<String, String>() { ... })
//獲取異步發布訂閱執行命令API
RedisPubSubAsyncCommands<String, String> async = connection.async();
//獲取向通道channel訂閱的future
RedisFuture<Void> futureSub = async.subscribe("channel");
//獲取向通道channel發布message的future
RedisFuture<Void> futurePub = async.push("channel","message");
//自定義業務代碼業務代碼
...
- Redis Cluster發布訂閱
//創建Redis Cluster發布訂閱連接
StatefulRedisClusterPubSubConnection<String, String> connection = clusterClient.connectPubSub()
//向連接中添加監聽器
connection.addListener(new RedisPubSubListener<String, String>() { ... })
//獲取發布訂閱同步執行代碼
RedisPubSubCommands<String, String> sync = connection.sync();
//向連接中訂閱channel通道信息
sync.subscribe("channel");
//自定義業務代碼
...
2.8 事務(Transaction)
Lettuce通過WATCH, UWATCH,EXEC, MULTI 和DISCARD來控制事務(Transaction),同時允許同步、異步、響應式和集群使用事務。那么,下面將分析同步和異步事務。
- 同步使用事務
//第一段代碼
//創建同步連接
RedisCommands<String, String> redis = client.connect().sync();
//開啟事務
redis.multi() //成功,則返回值為"OK"
redis.set(key, value) //未執行,返回為null
redis.exec() //執行事務,返回list("OK")
//第二段代碼
RedisCommands<String, String> redis = client.connect().sync();
//開啟事務
redis.multi() //成功,返回"OK"
redis.set(key1, value) //未執行,返回null
redis.set(key2, value) //未執行, 返回null
redis.exec() //事務執行成功,返回list("OK", "OK")
- 異步使用事務
//獲取異步執行命令API
RedisAsyncCommands<String, String> async = client.connect().async();
//獲取發送開啟事務的future
RedisFuture<String> multi = async.multi();
//執行future的set命令設置值
RedisFuture<String> set = async.set("key", "value");
//獲取提交執行事務的future
RedisFuture<List<Object>> exec = async.exec();
//獲取執行事務的結果
List<Object> objects = exec.get();
//獲取set的執行結果
String setResult = set.get();
//測試事務操作與set操作結果是否一致
//即事務操作是否成功
objects.get(0) == setResult
- 響應式使用事務
//創建響應式連接
RedisReactiveCommands<String, String> reactive = client.connect().reactive();
//開啟事務
reactive.multi().subscribe(multiResponse -> {
//編寫事務操作
reactive.set("key", "1").subscribe();
reactive.incr("key").subscribe();
//提交執行事務
reactive.exec().subscribe();
});
2.9 主從復制(Master/Replica)
(1)Redis standalone模式
//創建客戶端
RedisClient redisClient = RedisClient.create();
//創建主從連接
StatefulRedisMasterSlaveConnection<String, String> connection = MasterSlave.connect(redisClient, new Utf8StringCodec(),
RedisURI.create("redis://localhost"));
//從ReadFrom.MASTER_PREFERRED中讀取並復制,
//ReadFrom.MASTER_PREFERRED是一個ReadFromMasterPreferred類實例的引用
connection.setReadFrom(ReadFrom.MASTER_PREFERRED);
System.out.println("Connected to Redis");
//關閉連接
connection.close();
//關掉客戶端
redisClient.shutdown();
(2)Redis哨兵模式
RedisClient redisClient = RedisClient.create();
//創建哨兵模式主從復制連接
StatefulRedisMasterSlaveConnection<String, String> connection = MasterSlave.connect(redisClient, new Utf8StringCodec(),
RedisURI.create("redis-sentinel://localhost:26379,localhost:26380/0#mymaster"));
//從ReadFrom.MASTER_PREFERRED中讀取並進行復制
connection.setReadFrom(ReadFrom.MASTER_PREFERRED);
System.out.println("Connected to Redis");
connection.close();
redisClient.shutdown();
2.10 集群(Cluster)
(1)lettuce對集群的支持
- 支持所有的CLUSTER命令;
- 基於命令見hash slot的命令路由;
- 所選集群命令的高級抽象;
- 多集群節點的命令操作;
- 通過slot和host/port獲取集群節點的直接連接
- SSL和身份驗證;
- 周期性燈芯集群拓撲圖;
- 發布/訂閱。
(2)使用NodeSelection API
//創建Redis集群的高級異步連接
RedisAdvancedClusterAsyncCommands<String, String> async = clusterClient.connect().async();
//使用NodeSelection API連接所有副本
AsyncNodeSelection<String, String> replicas = connection.slaves();
//從所有副本中獲取所有的keys(密鑰)
AsyncExecutions<List<String>> executions = replicas.commands().keys("*");
//遍歷得到的keys
executions.forEach(result -> result.thenAccept(keys -> System.out.println(keys)));
(3)連接到一個集群
RedisURI redisUri = RedisURI.Builder.redis("localhost").withPassword("authentication").build();
//創建集群客戶端
RedisClusterClient clusterClient = RedisClusterClient.create(rediUri);
//創建連接
StatefulRedisClusterConnection<String, String> connection = clusterClient.connect();
//獲取同步執行命令api
RedisAdvancedClusterCommands<String, String> syncCommands = connection.sync();
...
connection.close();
clusterClient.shutdown();
(4)連接到多個子節點的Redis集群
RedisURI node1 = RedisURI.create("node1", 6379);
RedisURI node2 = RedisURI.create("node2", 6379);
//創建擁有多個子節點的集群客戶端
RedisClusterClient clusterClient = RedisClusterClient.create(Arrays.asList(node1, node2));
//創建連接
StatefulRedisClusterConnection<String, String> connection = clusterClient.connect();
//獲取同步執行命令api
RedisAdvancedClusterCommands<String, String> syncCommands = connection.sync();
...
connection.close();
clusterClient.shutdown();
(5)開啟周期性拓撲圖更新
RedisClusterClient clusterClient = RedisClusterClient.create(RedisURI.create("localhost", 6379));
//創建周期性拓撲圖更新操作的配置操作
ClusterTopologyRefreshOptions topologyRefreshOptions = ClusterTopologyRefreshOptions.builder()
.enablePeriodicRefresh(10, TimeUnit.MINUTES)
.build();
//向客戶端設置剛才配置好的操作
clusterClient.setOptions(ClusterClientOptions.builder()
.topologyRefreshOptions(topologyRefreshOptions)
.build());
...
clusterClient.shutdown();
(6)開啟自適應拓撲圖更新
RedisURI node1 = RedisURI.create("node1", 6379);
RedisURI node2 = RedisURI.create("node2", 6379);
RedisClusterClient clusterClient = RedisClusterClient.create(Arrays.asList(node1, node2));
//配置自適應拓撲圖更新操作
ClusterTopologyRefreshOptions topologyRefreshOptions = ClusterTopologyRefreshOptions.builder()
.enableAdaptiveRefreshTrigger(RefreshTrigger.MOVED_REDIRECT, RefreshTrigger.PERSISTENT_RECONNECTS)
.adaptiveRefreshTriggersTimeout(30, TimeUnit.SECONDS)
.build();
//向集群客戶端中設置剛才配置好的操作
clusterClient.setOptions(ClusterClientOptions.builder()
.topologyRefreshOptions(topologyRefreshOptions)
.build());
...
clusterClient.shutdown();
(7)獲取一個節點
RedisURI node1 = RedisURI.create("node1", 6379);
RedisURI node2 = RedisURI.create("node2", 6379);
//創建集群客戶端
RedisClusterClient clusterClient = RedisClusterClient.create(Arrays.asList(node1, node2));
//創建連接
StatefulRedisClusterConnection<String, String> connection = clusterClient.connect();
//獲取指定節點的同步執行命令api
RedisClusterCommands<String, String> node1 = connection.getConnection("host", 7379).sync();
...
//不需要關閉節點連接
connection.close();
clusterClient.shutdown();
三、Spring Data Redis操作
3.1 簡介
關注於當前最新的集成版本是Spring Data Redis,該框架通過對Jedis和Lettuce的封裝集成,將所有的Redis操作和數據類型都封裝成為了一個個單獨的操作類(Operation)和集合類(例如,RedisList,RedisMap等),同時提供各配置類(Configuration)來讓用戶自定義期望得到的客戶端,對用戶提供高級抽象的客戶端RedisTemplate,用戶不必要了解底層如何實現,只需通過RedisTemplate對Redis進行操作即可,而在最新一版Spring Data Redis 2.1中,通過Lettuce支持了主寫從讀的設置,而這正是這一版我比較感興趣的一點。
接下來的幾節里,先從操作層面嘗試Spring Data Redis是如何使用而實現Redis的功能特性,然后再從源碼層面去看框架是如何封裝Redis的特性和操作的,最后再討論Spring Data Redis整合Redis的優點和不足。
3.2 配置Lettuce連接
在Spring Data中通過提供RedisStandaloneConfiguration和RedisSentinelConfiguration兩個配置類,來提供Redis Standalone模式配置和Redis Sentinel模式配置。
- Redis standalone模式配置
@Configuration
class LettuceConnectConfig {
@Bean
public LettuceConnectionFactory redisConnectionFactory() {
//通過配置RedisStandaloneConfiguration實例來
//創建Redis Standolone模式的客戶端連接創建工廠
//配置hostname和port
return new LettuceConnectionFactory(new RedisStandaloneConfiguration("server", 6379));
}
}
3.3 主寫從讀模式配置
@Configuration
class WriteToMasterReadFromReplicaConfiguration {
//配置Lettuce連接工廠
@Bean
public LettuceConnectionFactory redisConnectionFactory() {
LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
.readFrom(SLAVE_PREFERRED) //配置從讀
.build();
//配置hostname和port
RedisStandaloneConfiguration serverConfig = new RedisStandaloneConfiguration("server", 6379);
//通過配置創建Lettuce連接工廠
return new LettuceConnectionFactory(serverConfig, clientConfig);
}
}
3.4 Redis 哨兵模式
@Configuration
public class LettuceSentinelConfig{
@Bean
public RedisConnectionFactory lettuceConnectionFactory() {
//創建哨兵配置
RedisSentinelConfiguration sentinelConfig = new RedisSentinelConfiguration()
.master("mymaster") //設置主機的NameNode
.sentinel("127.0.0.1", 26379) //配置哨兵的ip和端口
.sentinel("127.0.0.1", 26380);
return new LettuceConnectionFactory(sentinelConfig);
}
}
也可以在application.yml(項目配置文件)中通過配置spring.redis.sentinel.mater配置主節點的名字,和配置spring.redis.sentinel.nodes配置逗號分隔host:port的列表來配置節點。
3.5 發布訂閱(Pub/Sub)
(1)發布(Publish)
在Spring Data Redis中,提供了兩種方法進行發布和訂閱,即低級的RedisConnection和高級的RedisTemplate,執行效果是一樣的。那么,下面我們看看,他們的操作實例吧。
// 使用ReidsConnection發布信息
RedisConnection con = ...
//序列化發布的信息
byte[] msg = ...
//序列化發往的channel通道
byte[] channel = ...
//調用RedisConnection進行發布
//請注意,官方文檔的該參數順序出現顛倒
con.publish(channel, msg);
// 使用RedisTemplate進行序列化和發布
RedisTemplate template = ...
//該方法對序列化和發布進行了封裝
//hello為channel通道名,
//world是要發送的message
template.convertAndSend("hello!", "world");
(2)訂閱(Subscribe)
到目前為止,框架只提供低級的RedisConnection的subscribe(指定通道發布)和psubscribe(指定模式發布,即正則表達式)兩種消息發布方法。而且在Spring Data Redis框架下,訂閱操作是阻塞的,一旦開啟訂閱,一個connection將會等待消息,此時調用除了subscribe、psubscribe、unsubscribe和punsubscribe之外的命令,都會拋出異常。接下來,我們來看看,使用上有什么不同。
- subscribe方法
//創建連接
RedisConnection con = ...
//創建序列化工具
RedisSerializer<String> stringSerializer = RedisSerializer.string();
//序列化通道
Byte[] channel = stringSerializer.serialize("hello");
//創建信息監聽器,實現接口下onMessage方法
//在接收到redis發送過來的消息后執行
//使用RedisConnection進行訂閱必須實現該方法
//接收的參數從源碼上來看第二個參數都是Byte[]類型
MessageListener listener = (massage,channel)->System.out.println("Received message from "+channel);
//接下來便可以訂閱消息
con.subscribe(listener, message);
- psubscribe方法
//創建連接
RedisConnection con = ...
//創建序列化工具
RedisSerializer<String> stringSerializer = RedisSerializer.string();
//序列化正則表達式
Byte[] pattern = stringSerializer.serialize("hello*");
//創建信息監聽器,實現接口下onMessage方法
//在接收到redis發送過來的消息后執行
//使用RedisConnection進行訂閱必須實現該方法
//接收的參數從源碼上來看第二個參數都是Byte[]類型
MessageListener listener = (massage,pattern)->System.out.println("Received message from "+pattern);
//接下來便可以使用正則表達式訂閱消息
con.psubscribe(listener,pattern);
3.6 事務(Transaction)
(1)使用
RedisTemplate通過execute方法提供事務功能,但不保證所有的事務操作在同一個連接中進行。
//執行一次事務操作
//調用RedisTemplte的execute(SessionBack<T> session)方法
//需要實現SessionBack中的execute(RedisOperations<K,V> operations)方法
//使用lamda表達式
List<Object> txResults = redisTemplate.execute((RedisOperations<K, V> operations)->{
//開啟事務
operations.multi();
//添加操作
operations.opsForSet().add("key", "value1");
//這個將會返回事務操作的所有結果
return operations.exec();
}
});
//打印結果
System.out.println("Number of items added to set: " + txResults.get(0));
(2)配置
//開啟聲明式事務管理
@Configuration
@EnableTransactionManagement
public class RedisTxContextConfiguration {
//配置RedisTemplate
@Bean
public StringRedisTemplate redisTemplate() {
StringRedisTemplate template = new StringRedisTemplate(redisConnectionFactory());
//顯式啟用事務支持
template.setEnableTransactionSupport(true);
return template;
}
//配置Redis連接工廠
@Bean
public RedisConnectionFactory redisConnectionFactory() {
// jedis || Lettuce
}
//配置事務管理器,但Spring Data Redis不提供事務管理器
//需要我們自己創建實現配置事務管理器
//若使用JDBC連接,則直接使用已存在的事務管理器
@Bean
public PlatformTransactionManager transactionManager() throws SQLException {
return new DataSourceTransactionManager(dataSource());
}
//配置數據源
@Bean
public DataSource dataSource() throws SQLException {
// ...
}
}
3.7 流水線(Pipelining)
Redis的pipelining功能,可以一次向server發送多個命令而不需要等待響應,之后通過一個步驟就可以獲得所有響應。當需要連續發送多個命令時(如向同一個List添加多個元素),pipelining流水線功能將會提高應用性能。
該框架通過提供RedisTemplate的executePipelined方法,支持pipelining功能。下面來看看它的示例。
//從一個隊列中刪除指定數量的元素
List<Object> results = stringRedisTemplate.executePipelined(
//RedisCallback內部類
//實現doInRedis方法
//此處亦可以創建SessionCallack的內部類
//由用戶自己決定
new RedisCallback<Object>() {
public Object doInRedis(RedisConnection connection) throws DataAccessException {
//創建連接
StringRedisConnection stringRedisConn = (StringRedisConnection)connection;
for(int i=0; i< batchSize; i++) {
//循環調用rpop進行batchSize次右刪除
stringRedisConn.rPop("myqueue");
}
return null;
}
});
3.8 集群(Cluster)
Spring Data Redis 框架提供了Redis Cluster的配置類,即RedisClusterConfiguration類,提供兩項spring.redis.cluster.nodes和spring.redis.cluster.max-redirects屬性配置,配置集群節點和允許集群重定向的最大數量。可以通過該RedisClusterConfiguration類進行創建集群創建工廠,然后創建集群連接。
當然,在官方文檔上,也提供了讓我們自己配置以及讀取創建連接工廠的方法,下面來看看怎么自定義創建。
//創建自定義集群配置屬性
//從application的配置文件中
//讀取前綴為spring.redis.cluster相應的屬性
//注冊成為Spring bean
@Component
@ConfigurationProperties(prefix = "spring.redis.cluster")
public class ClusterConfigurationProperties {
//集群節點列表
List<String> nodes;
public List<String> getNodes() {
return nodes;
}
public void setNodes(List<String> nodes) {
this.nodes = nodes;
}
}
//配置創建集群連接工廠
@Configuration
public class LettuceClusterAppConfig {
//自動裝配配置Bean
@Autowired
ClusterConfigurationProperties clusterProperties;
@Bean
public RedisConnectionFactory connectionFactory() {
//根據上面配置bean創建連接工廠
return new LettuceConnectionFactory(
new RedisClusterConfiguration(clusterProperties.getNodes()));
}
}
3.9 序列化與反序列化
Spring Data Redis提供了RedisSerializer接口,同時提供了多個實現該接口的序列化工具,如JdkSerializationRedisSerializer、Jackson2JsonRedisSerializer和GenericToStringSerializer等。通過調用其實例的serialize和deserialize方法進行序列化和反序列化。
四、Spring Data Redis源碼
從上一章中,我們發現由多個常出現的由Spring Data Redis 封裝了的類和操作。那么,這一章主要來看其是如何封裝客戶端操作的。
4.1 RedisTemplate
該類是Spring Data Redis提供給用戶的最高級的抽象客戶端,用戶可直接通過RedisTemplate進行多種操作,那么,我們先來看看RedisTemplate封裝了哪些操作。下面這列表是RedisTemplate的繼承關系和所有方法(已過濾重載方法,共有81個方法)
//類繼承關系
//RedisAccessor是RedisTemplate定義普通屬性的基類,不直接使用
//RedisOperations是指定RedisTemplate實現的Redis connection操作的集合接口
//BeanClassLoaderAware是給其實現類是設置類加載器的接口
1.RedisTemplate<K, V> extends RedisAccessor implements RedisOperations<K, V>, BeanClassLoaderAware
//方法
//配置默認序列化與反序列化工具類
2.afterPropertiesSet
//根據參數執行相關operation操作,例如,事務
3.execute
//執行pipelining流水線相關操作
4.executePipelined
//執行指定connection連接的相關操作
5.executeWithStickyConnection
//執行session內的execute方法
6.executeSession
//創建RedisConnection代理類
7.createRedisConnectionProxy
//connection連接的預處理
8.preProcessConnection
//結果的后處理,默認什么都不做
9.postProcessResult
//是否向RedisCallback暴露本地連接
10.isExposeConnection
//設置是否向RedisCallback暴露本地連接
11.setExposeConnection
//12到26都是設置和獲取相關序列化工具類
12.isEnableDefaultSerializer
13.setEnableDefaultSerializer
14.getDefaultSerializer
15.setDefaultSerializer
16.setKeySerializer
17.getKeySerializer
18.setValueSerializer
19.getValueSerializer
20.getHashKeySerializer
21.setHashKeySerializer
22.getHashValueSerializer
23.setHashValueSerializer
24.getStringSerializer
25.setStringSerializer
26.setScriptExecutor
//27到34為私有方法,不對外提供使用
27.rawKey
28.rawString
29.rawValue
30.rawKeys
31.deserializeKey
32.deserializeMixedResults
33.deserializeSet
34.convertTupleValues
//執行事務
35.exec
36.execRaw
//刪除操作
37.delete
//接觸鏈接
38.unlink
//查看是否含有指定key
39.hasKey
40.countExistingKeys
//設置過期時間
41.expire
42.expireAt
//轉換成字節流並向channel發送message
43.convertAndSend
//獲取過期時間
44.getExpire
//根據傳入的正則表達式返回所有的key
46.keys
//取消指定key的過期時間
47.persist
//移動指定的key和index到數據庫中
48.move
//從鍵空間隨機獲取一個key
49.randomKey
//將指定key改成目標key
50.rename
//key不存在時,將指定key改成目標key
51.renameIfAbsent
//設置存儲在指定key的類型
52.type
//檢索存儲在key的值的序列化版本
53.dump
//執行Redis的restore的命令
54.restore
//標記事務阻塞的開始
55.multi
//丟棄所有在multi之后發出的命令
56.discard
//觀察指定key在事務處理開始即multi之后的修改情況
57.watch
//刷新先前觀察的所有key
58.unwatch
//為key元素排序
59.sort
//關閉客戶端連接
60.killClient
//請求連接客戶端的相關信息和統計數據
61.getClientList
//更改復制配置到新的master
62.slaveOf
//將本機更改為master
63.slaveOfNoOne
//64到79都是獲取相對應的操作
64.opsForCluster
65.opsForGeo
66.boundGeoOps
67.boundHashOps
68.opsForHash
69.opsForHyperLogLog
70.opsForList
71.boundListOps
72.boundSetOps
73.opsForSet
74.opsForStream
75.boundStreamOps
76.boundValueOps
77.opsForValue
78.boundZSetOps
79.opsForZSet
//設置是否支持事務
80.setEnableTransactionSupport
//設置bean的類加載器
81.setBeanClassLoader
4.2 Operations類和Commands類
在Spring Data Redis中,將對應的操作都封裝成為了對應的Operations接口和實現類(在org.springframework.data.redis.core包中),有ListOperations接口、HashOperations接口、GeoOperatons接口、ClusterOperations接口、HyperLoglogOperations接口等等接口以及對應的DefaultListOperations類、DefaultHashOperations類等默認實現類。Command類是對redis命令的第一次封裝,即由Redis的第三方Java客戶端提供的,例如lettuce,這些Command類的命令就在在org.springframework.data.redis.connection.lettuce包內。
下面分析一下DefaultListOperations類:
- leftPop方法源碼
//左刪除鏈表
public V leftPop(K key) {
return execute(new ValueDeserializingRedisCallback(key) {
@Override
protected byte[] inRedis(byte[] rawKey, RedisConnection connection) {
//調用connection的lpop(rawKey)方法,
//即調用底層創建出的Lettuce連接或者Jedis連接的lpop方法
//獲取同步或異步執行命令API,然后執行lpop方法
return connection.lPop(rawKey);
}
}, true);
}
- leftPush方法源碼
//左添加鏈表
//key為鏈表,value為值
public Long leftPush(K key, V value) {
//序列化鏈表名
byte[] rawKey = rawKey(key);
//序列化鏈表值
byte[] rawValue = rawValue(value);
//調用底層實現的Lettuce連接或者Jedis連接
//獲取同步或異步執行命令API,然后執行lPush命令
return execute(connection -> connection.lPush(rawKey, rawValue), true);
}
與此相同, 其他的操作最后都會調用底層創建的Lettuce或Jedis連接,即LettuceConnection或JedisConnection,然后通過相應的方法獲取相應的命令封裝類即Commands類(例如,LettuceListCommands類),最后調用Command類的方法(例如,LettuceCommands類的lPush或lPop方法)進行操作。在此之外,還有LettuceClusterConnection類(集群連接)和LettuceSentinelConnection類(哨兵連接),同樣的Jedis也有相同實現,不再贅言。
4.3 數據結構
在org.springframework.data.redis.support下的collections包里實現了列表,散列表,有序集合,集合,以及有序集合,即DefaultRedisList、DefaultRedisMap、DefaultRedisSet以及DefaultRedisZSet等,都綁定了相應的綁定類(例如,列表的綁定類為BoundLustOperations)。
在org.springframework.data.redis.support下的actomic包里,由Redis事務的watch和multi方法實現CAS的操作更新鍵值。同時,提供了Double、Integer和Long的原子操作類型。
4.5 SessionCallback接口
該接口是Redistemplate的execute方法中的參數類型,通過實現該接口唯一方法,RedisTemplate的execute(sessionCallback)方法將會執行在實現方法中的所有操作。可以通過該方式實現事務,即通過執行multi,discard,exec,watch和unwatch命令實現。
4.6 RedisCallback接口
該接口和SessionCallback接口一樣,也是Redistemplate的execute方法中的參數類型,但是是低級的redis回調方法。使用方法和上面接口一樣,該接口有一個exec方法,通常將其實現為匿名類給execute方法使用,而最常用是鏈接多個get/set/trim操作等等。
4.7 總結
該框架實現了Spring和Redis的整合,提供了高級抽象的客戶端以及相應的配置,降低了用戶的入門成本,以上是這框架的優點。而在閱讀該項目源碼時,也發現在項目源代碼結構上的一些不足,也或許是自己沒理解代碼者的行為吧。例如,org.springframework.data.redis.core包下的各類Operations操作類完全可以將其放入一個operations包內;org.springframework.data.redis.config包內居然存放的不是配置類,而是解析類。就目前而言我發現,mybatis和spring框架的源碼文件和注釋是最清晰的最整潔的,作為Spring下的項目都應該秉持着這樣的態度和風格。
五、Spring Boot 整合redis
Spring Boot提供starter的來向用戶提供完成自動配置的redis,那么,關於Spring Boot對redis的整合主要關心它所提供配置選項。下面從源碼來看看是如何整合的。
5.1 pom.xml文件
在spring-boot-starter-data-redis項目中,主要通過pom.xml進行項目依賴。下面是項目下的pom.xml依賴源碼。
<dependencies>
//添加spring-boot-starter依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
//添加spring-data-redis依賴
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<exclusions>
//排除jcl-over-slf4j依賴
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
//添加Lettuce客戶端依賴
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</dependency>
</dependencies>
而在spring-boot-starter依賴中又引入了許多相關的依賴,尤其是自動配置的依賴。
<dependencies>
//添加spring-boot依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
</dependency>
//添加spring-boot-autoconfigure項目依賴
//這個依賴很重要,和整合各式應用有關的配置都在該依賴下
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
//添加日志依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
//添加注解依賴
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
</dependency>
//添加spring核心依賴,例如ioc、aop等等
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</dependency>
//添加yaml依賴
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
下一節,重點分析在spring-boot-autoconfigure項目下的源碼配置。
5.2 spring-boot-autoconfigure項目
在該項目下的org.springframework.boot.autoconfigure.data.redis包內,提供了許多關於redis的配置,下面是該包下的截圖。
由包中的源碼可以得知,該包下提供了Jedis和Lettuce的客戶端連接配置、Redis基本連接配置、響應式配置、Redis存儲倉庫配置以及Redis所有的配置屬性。
5.3 RedisProperties類
該類提供給用戶使用,用戶可以通過application文件配置Redis相關屬性,下面是yml格式的配置列表。
spring:
redis:
database:
url:
host:
password:
port:
ssl:
timeout:
pool:
maxIdle:
minIdle:
maxActive:
maxWait:
sentinel:
master:
nodes:
cluster:
nodes:
maxRedirects:
jedis:
pool:
lettuce:
shutdownTimeout
pool
5.4 LettuceConnectionConfiguration類
該配置類通過注解@ConditionalOnClass(RedisClient.class)進行bean化。類似注解解釋如下(該解釋摘自https://blog.csdn.net/blueheart20/article/details/81020262):
- @ConditionalOnBean(僅僅在當前上下文中存在某個對象時,才會實例化一個Bean)
- @ConditionalOnClass(某個class位於類路徑上,才會實例化一個Bean),該注解的參數對應的類必須存在,否則不解析該注解修飾的配置類;
- @ConditionalOnExpression(當表達式為true的時候,才會實例化一個Bean)
- @ConditionalOnMissingBean(僅僅在當前上下文中不存在某個對象時,才會實例化一個Bean), 該注解表示,如果存在它修飾的類的bean,則不需要再創建這個bean;可以給該注解傳入參數例如@ConditionOnMissingBean(name = “example”),這個表示如果name為“example”的bean存在,這該注解修飾的代碼塊不執行。
- @ConditionalOnMissingClass(某個class類路徑上不存在的時候,才會實例化一個Bean)
- @ConditionalOnNotWebApplication(不是web應用)
- @ConditionalOnProperty是指在application.yml里配置的屬性是否為true,其他的幾個都是對class的判斷
所以,在類路徑上存在RedisClient才會bean化該類進行配置。
5.5 RedisAutoConfiguration類
該類使用了四個注解:
- @ConditionalOnClass(RedisOperations.class) :在類路徑上存在RedisOperations.class時,才會bean實例化該類。
- @EnableConfigurationProperties(RedisProperties.class) : 當@EnableConfigurationProperties注解應用到你的@Configuration時, 任何被@ConfigurationProperties注解的beans將自動被Environment屬性配置,即RedisProperties.class類將會被Environment屬性配置。
- @Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class }):將LettuceConnectionConnfiguration和JedisConnectionConfiguration導入成為bean。
- @Configuration:配置類。
該類里連個注入方法都使用了@ConditionalOnMissingBean,故只有上下文不存在RedisTemplate或StringRedisTemplate時,才會創建對應的bean。其他的配置大同小異,有興趣可以自己查閱源碼。
六、總結
至此,Redis基本操作,Redis的Lettuce客戶端,Spring Data Redis項目,以及Spring Boot 提供的配置方案的了解分析,就已經結束了。我個人認為Lettuce已經提供了比較完善的Redis的操作,並且更貼近於Redis的執行流程和思想,並且有更寬松的定制方案,不出意外的話,我會選擇使用Lettuce客戶端。但是,Spring Boot項目提供的Redis配置方案是比較完善的,如果想要自己整合Spring boot和Lettuce客戶端,可以學習它的配置思想。
而Spring Data Redis項目就效果而言,應該是很不錯的,但就其項目架構而言,算不上優美。不過,它的確降低了用戶的學習門檻。文章若有不當之處,歡迎指教。
附錄 相關網址
redis中文官方地址:http://www.redis.cn
reids英文官方地址(由於中文官方有些地方翻譯的不是很明白,可以查詢英文官方):https://redis.io/
lettuce官方地址:https://lettuce.io
Spring Data Redis官方地址:https://spring.io/projects/spring-data-redis
Spring Boot redis 自動配置源碼地址:https://github.com/spring-projects/spring-boot/tree/master/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis
