一、摘要
什么是 Redisson?來(lái)自于官網(wǎng)上的描述內(nèi)容如下!
Redisson 是一個(gè)在 Redis 的基礎(chǔ)上實(shí)現(xiàn)的 Java 駐內(nèi)存數(shù)據(jù)網(wǎng)格客戶端(In-Memory Data Grid)。它不僅提供了一系列的 redis 常用數(shù)據(jù)結(jié)構(gòu)命令服務(wù),還提供了許多分布式服務(wù),例如分布式鎖、分布式對(duì)象、分布式集合、分布式遠(yuǎn)程服務(wù)、分布式調(diào)度任務(wù)服務(wù)等等。
相比于 Jedis、Lettuce 等基于 redis 命令封裝的客戶端,Redisson 提供的功能更加高端和抽象,逼格高!
更多功能特性和開發(fā)文檔說(shuō)明,可用移步github進(jìn)行獲取,訪問(wèn)地址如下:
https://github.com/redisson/redisson/wiki/目錄
接下來(lái),我們就一起來(lái)聊一下,如何使用 Redisson 操作 Redis 中的字符串、哈希、列表、集合、有序集合,以及布隆過(guò)濾器和分布式鎖等功能。
二、Redisson
2.1、基本使用
跟過(guò)去一樣,首先創(chuàng)建一個(gè) maven 項(xiàng)目,添加??Redisson?
?依賴包。
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.13.6</version>
</dependency>
單機(jī)環(huán)境下,簡(jiǎn)單樣例如下!
public class RedissonMain {
public static void main(String[] args){
Config config = new Config();
config.useSingleServer()
.setAddress("redis://127.0.0.1:6379")
.setPassword("123456")
.setDatabase(0);
//獲取客戶端
RedissonClient redissonClient = Redisson.create(config);
//獲取所有的key
redissonClient.getKeys().getKeys().forEach(key -> System.out.println(key));
//關(guān)閉客戶端
redissonClient.shutdown();
}
}
ps:創(chuàng)建 RedissonClient 對(duì)象實(shí)例的方式多鐘多樣,可以直接通過(guò)在代碼中設(shè)置 Redis 服務(wù)的相關(guān)參數(shù)創(chuàng)建,也可以通過(guò)加載 JSON 格式、 YAML 格式或者 Spring XML 配置文件來(lái)創(chuàng)建,詳細(xì)的參數(shù)配置可用移步上文提到的 Redisson 開發(fā)文檔。
2.2、字符串操作
Redisson 支持通過(guò)RBucket對(duì)象來(lái)操作字符串?dāng)?shù)據(jù)結(jié)構(gòu),通過(guò)RBucket實(shí)例可以設(shè)置value或設(shè)置value和有效期,簡(jiǎn)單樣例如下!
//字符串操作
RBucket<String> rBucket = redissonClient.getBucket("strKey");
// 設(shè)置value和key的有效期
rBucket.set("張三", 30, TimeUnit.SECONDS);
// 通過(guò)key獲取value
System.out.println(redissonClient.getBucket("strKey").get());
2.3、對(duì)象操作
Redisson 支持將對(duì)象作為value存入redis,被存儲(chǔ)的對(duì)象事先必須要實(shí)現(xiàn)序列化接口Serializable,否則會(huì)報(bào)錯(cuò),簡(jiǎn)單樣例如下!
public class Student implements Serializable {
private Long id;
private String name;
private Integer age;
//set、get...
@Override
public String toString(){
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
//Student對(duì)象
Student student = new Student();
student.setId(1L);
student.setName("張三");
student.setAge(18);
//對(duì)象操作
RBucket<Student> rBucket = redissonClient.getBucket("objKey");
// 設(shè)置value和key的有效期
rBucket.set(student, 30, TimeUnit.SECONDS);
// 通過(guò)key獲取value
System.out.println(redissonClient.getBucket("objKey").get());
2.4、哈希操作
Redisson 支持通過(guò)RMap?對(duì)象來(lái)操作哈希數(shù)據(jù)結(jié)構(gòu),簡(jiǎn)單樣例如下!
//哈希操作
RMap<String, String> rMap = redissonClient.getMap("mapkey");
// 設(shè)置map中key-value
rMap.put("id", "123");
rMap.put("name", "趙四");
rMap.put("age", "50");
//設(shè)置過(guò)期時(shí)間
rMap.expire(30, TimeUnit.SECONDS);
// 通過(guò)key獲取value
System.out.println(redissonClient.getMap("mapkey").get("name"));
2.5、列表操作
Redisson 支持通過(guò)RList對(duì)象來(lái)操作列表數(shù)據(jù)結(jié)構(gòu),簡(jiǎn)單樣例如下!
//字符串操作
RList<Student> rList = redissonClient.getList("listkey");
Student student1 = new Student();
student1.setId(1L);
student1.setName("張三");
student1.setAge(18);
rList.add(student1);
Student student2 = new Student();
student2.setId(2L);
student2.setName("李四");
student2.setAge(19);
rList.add(student2);
//設(shè)置過(guò)期時(shí)間
rList.expire(30, TimeUnit.SECONDS);
// 通過(guò)key獲取value
System.out.println(redissonClient.getList("listkey"));
2.6、集合操作
Redisson 支持通過(guò)RSet對(duì)象來(lái)操作集合數(shù)據(jù)結(jié)構(gòu),簡(jiǎn)單樣例如下!
//字符串操作
RSet<Student> rSet = redissonClient.getSet("setkey");
Student student1 = new Student();
student1.setId(1L);
student1.setName("張三");
student1.setAge(18);
rSet.add(student1);
Student student2 = new Student();
student2.setId(2L);
student2.setName("李四");
student2.setAge(19);
rSet.add(student2);
//設(shè)置過(guò)期時(shí)間
rSet.expire(30, TimeUnit.SECONDS);
// 通過(guò)key獲取value
System.out.println(redissonClient.getSet("setkey"));
2.6、有序集合操作
Redisson 支持通過(guò)RSortedSet對(duì)象來(lái)操作有序集合數(shù)據(jù)結(jié)構(gòu),在使用對(duì)象來(lái)存儲(chǔ)之前,實(shí)體對(duì)象必須先實(shí)現(xiàn)Comparable接口,并重寫比較邏輯,否則會(huì)報(bào)錯(cuò),簡(jiǎn)單樣例如下!
public class Student implements Serializable, Comparable<Student> {
private Long id;
private String name;
private Integer age;
//get、set.....
@Override
public String toString(){
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public int compareTo(Student obj){
return this.getId().compareTo(obj.getId());
}
}
//有序集合操作
RSortedSet<Student> sortSetkey = redissonClient.getSortedSet("sortSetkey");
Student student1 = new Student();
student1.setId(1L);
student1.setName("張三");
student1.setAge(18);
sortSetkey.add(student1);
Student student2 = new Student();
student2.setId(2L);
student2.setName("李四");
student2.setAge(19);
sortSetkey.add(student2);
// 通過(guò)key獲取value
System.out.println(redissonClient.getSortedSet("sortSetkey"));
2.7、布隆過(guò)濾器
布隆過(guò)濾器(Bloom Filter)是 1970 年由布隆提出的。它實(shí)際上是一個(gè)很長(zhǎng)的二進(jìn)制向量和一系列隨機(jī)映射函數(shù)。
布隆過(guò)濾器可以用于檢索一個(gè)元素是否在一個(gè)集合中。它的優(yōu)點(diǎn)是空間效率和查詢時(shí)間都比一般的算法要好的多,缺點(diǎn)是有一定的誤識(shí)別率和刪除困難。
Redisson 支持通過(guò)RBloomFilter對(duì)象來(lái)操作布隆過(guò)濾器,簡(jiǎn)單樣例如下!
RBloomFilter rBloomFilter = redissonClient.getBloomFilter("seqId");
// 初始化預(yù)期插入的數(shù)據(jù)量為10000和期望誤差率為0.01
rBloomFilter.tryInit(10000, 0.01);
// 插入部分?jǐn)?shù)據(jù)
rBloomFilter.add("100");
rBloomFilter.add("200");
rBloomFilter.add("300");
//設(shè)置過(guò)期時(shí)間
rBloomFilter.expire(30, TimeUnit.SECONDS);
// 判斷是否存在
System.out.println(rBloomFilter.contains("300"));
System.out.println(rBloomFilter.contains("200"));
System.out.println(rBloomFilter.contains("999"));
2.8、分布式自增ID
ID 是數(shù)據(jù)的唯一標(biāo)識(shí),傳統(tǒng)的做法是利用 UUID 和數(shù)據(jù)庫(kù)的自增 ID。
但由于 UUID 是無(wú)序的,不能附帶一些其他信息,因此實(shí)際作用有限。
隨著業(yè)務(wù)的發(fā)展,數(shù)據(jù)量會(huì)越來(lái)越大,需要對(duì)數(shù)據(jù)進(jìn)行分表,甚至分庫(kù)。分表后每個(gè)表的數(shù)據(jù)會(huì)按自己的節(jié)奏來(lái)自增,這樣會(huì)造成 ID 沖突,因此這時(shí)就需要一個(gè)單獨(dú)的機(jī)制來(lái)負(fù)責(zé)生成唯一 ID,redis 原生支持生成全局唯一的 ID。
簡(jiǎn)單樣例如下!
final String lockKey = "aaaa";
//通過(guò)redis的自增獲取序號(hào)
RAtomicLong atomicLong = redissonClient.getAtomicLong(lockKey);
//設(shè)置過(guò)期時(shí)間
atomicLong.expire(30, TimeUnit.SECONDS);
// 獲取值
System.out.println(atomicLong.incrementAndGet());
2.9、分布式鎖
Redisson 最大的亮點(diǎn),也是使用最多的功能,就是提供了強(qiáng)大的分布式鎖實(shí)現(xiàn),特點(diǎn)是:使用簡(jiǎn)單、安全!
簡(jiǎn)單使用樣例如下!
Config config = new Config();
config.useSingleServer()
.setAddress("redis://127.0.0.1:6379")
.setPassword("123456")
.setDatabase(0);
RedissonClient redissonClient = Redisson.create(config);
//獲取鎖對(duì)象實(shí)例
final String lockKey = "abc";
RLock rLock = redissonClient.getLock(lockKey);
try {
//嘗試5秒內(nèi)獲取鎖,如果獲取到了,最長(zhǎng)60秒自動(dòng)釋放
boolean res = rLock.tryLock(5L, 60L, TimeUnit.SECONDS);
if (res) {
//成功獲得鎖,在這里處理業(yè)務(wù)
System.out.println("獲取鎖成功");
}
} catch (Exception e) {
System.out.println("獲取鎖失敗,失敗原因:" + e.getMessage());
} finally {
//無(wú)論如何, 最后都要解鎖
rLock.unlock();
}
//關(guān)閉客戶端
redissonClient.shutdown();
以上是單機(jī)環(huán)境下的分布式鎖實(shí)現(xiàn)邏輯,如果是集群環(huán)境下,應(yīng)該如何處理呢?
Redisson 提供RedissonRedLock操作類,也被稱為紅鎖,實(shí)現(xiàn)原理簡(jiǎn)單的總結(jié)有以下幾點(diǎn):
- 如果有多個(gè) redis 集群的時(shí)候,當(dāng)且僅當(dāng)從大多數(shù)(N/2+1,比如有3個(gè) redis 節(jié)點(diǎn),那么至少有2個(gè)節(jié)點(diǎn))的 Redis 節(jié)點(diǎn)都取到鎖,并且獲取鎖使用的總耗時(shí)小于鎖失效時(shí)間時(shí),鎖才算獲取成功
- 如果獲取失敗,客戶端會(huì)在所有的 Redis 實(shí)例上進(jìn)行解鎖操作
- 集群環(huán)境下,redis 服務(wù)器直接不存在任何復(fù)制或者其他隱含的分布式協(xié)調(diào)機(jī)制,否則會(huì)存在實(shí)效的可能
RedissonRedLock簡(jiǎn)單使用樣例如下!
Config config1 = new Config();
config1.useSingleServer().setAddress("redis://192.168.3.111:6379").setPassword("a123456").setDatabase(0);
RedissonClient redissonClient1 = Redisson.create(config1);
Config config2 = new Config();
config2.useSingleServer().setAddress("redis://192.168.3.112:6379").setPassword("a123456").setDatabase(0);
RedissonClient redissonClient2 = Redisson.create(config2);
Config config3 = new Config();
config3.useSingleServer().setAddress("redis://192.168.3.113:6379").setPassword("a123456").setDatabase(0);
RedissonClient redissonClient3 = Redisson.create(config3);
//獲取多個(gè) RLock 對(duì)象
final String lockKey = "abc";
RLock lock1 = redissonClient1.getLock(lockKey);
RLock lock2 = redissonClient2.getLock(lockKey);
RLock lock3 = redissonClient3.getLock(lockKey);
//根據(jù)多個(gè) RLock 對(duì)象構(gòu)建 RedissonRedLock (最核心的差別就在這里)
RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);
try {
//嘗試5秒內(nèi)獲取鎖,如果獲取到了,最長(zhǎng)60秒自動(dòng)釋放
boolean res = redLock.tryLock(5L, 60L, TimeUnit.SECONDS);
if (res) {
//成功獲得鎖,在這里處理業(yè)務(wù)
System.out.println("獲取鎖成功");
}
} catch (Exception e) {
System.out.println("獲取鎖失敗,失敗原因:" + e.getMessage());
} finally {
//無(wú)論如何, 最后都要解鎖
redLock.unlock();
}
更加詳細(xì)的分布式鎖實(shí)現(xiàn)原理分析,可以移步到這個(gè)地址查閱。
https://blog.csdn.net/asd051377305/article/details/108384490
2.10、集群模式
以上介紹的都是單機(jī)模式,如果是集群環(huán)境,我們可以采用如下方式進(jìn)行配置:
Config config = new Config();
config.useClusterServers()
.setScanInterval(2000) // 集群狀態(tài)掃描間隔時(shí)間,單位是毫秒
//可以用"rediss://"來(lái)啟用SSL連接
.addNodeAddress("redis://127.0.0.1:7000", "redis://127.0.0.1:7001")
.addNodeAddress("redis://127.0.0.1:7002");
RedissonClient redisson = Redisson.create(config);
2.11、哨兵模式
哨兵模式,參數(shù)配置方式如下:
Config config = new Config();
config.useSentinelServers()
.setMasterName("mymaster")
//可以用"rediss://"來(lái)啟用SSL連接
.addSentinelAddress("127.0.0.1:26389", "127.0.0.1:26379")
.addSentinelAddress("127.0.0.1:26319");
RedissonClient redisson = Redisson.create(config);
2.12、主從模式
主從模式,參數(shù)配置方式如下:
Config config = new Config();
config.useMasterSlaveServers()
//可以用"rediss://"來(lái)啟用SSL連接
.setMasterAddress("redis://127.0.0.1:6379")
.addSlaveAddress("redis://127.0.0.1:6389", "redis://127.0.0.1:6332", "redis://127.0.0.1:6419")
.addSlaveAddress("redis://127.0.0.1:6399");
RedissonClient redisson = Redisson.create(config);
三、小結(jié)
在前幾篇文章中,我們?cè)敿?xì)的介紹了 Jedis、Lettuce,我們不禁會(huì)發(fā)出一個(gè)疑問(wèn):Redisson和Jedis、Lettuce有什么區(qū)別?
現(xiàn)在我們?cè)倩仡^來(lái)總結(jié)一番!
- Jedis:Redis 官方推出的用于通過(guò) Java 連接 Redis 客戶端的一個(gè)工具包,它提供了全面的類似于 Redis 原生命令的支持,是目前使用最廣的一款 java 客戶端。
- Lettuce:一個(gè)可擴(kuò)展的線程安全的 Redis 客戶端,通訊框架基于 Netty 開發(fā),支持高級(jí)的 Redis 特性,比如哨兵,集群,管道,自動(dòng)重新連接等特性。從 Spring Boot 2.x 開始, Lettuce 已取代 Jedis 成為首選 Redis 的客戶端。
- Redisson:一款架設(shè)在 Redis 基礎(chǔ)上,通訊基于 Netty 的綜合的、新型的中間件,是企業(yè)級(jí)開發(fā)中使用 Redis 的最佳范本。
總結(jié)下來(lái),Jedis 把 Redis 命令封裝的非常全面,Lettuce 則進(jìn)一步豐富了 Api,支持 Redis 各種高級(jí)特性。
但是兩者并沒(méi)有進(jìn)一步深化,只給了你操作 Redis 數(shù)據(jù)庫(kù)的工具,而 Redisson 則是基于 Redis、Lua 和 Netty 建立起了一套的分布式解決方案,比如分布式鎖的實(shí)現(xiàn),分布式對(duì)象的操作等等。
在實(shí)際使用過(guò)程中,Lettuce? + Redisson組合使用的比較多,兩者相鋪相成。
關(guān)于分布式鎖實(shí)現(xiàn)的應(yīng)用,生產(chǎn)環(huán)境推薦盡量采用單點(diǎn)環(huán)境來(lái)實(shí)現(xiàn),基本上解決絕大部分的分布式鎖問(wèn)題,如果當(dāng)前服務(wù)的環(huán)境確實(shí)很復(fù)雜,可以采用RedissonRedLock來(lái)實(shí)現(xiàn)。
四、參考
1、Redisson 開發(fā)文檔
2、王同學(xué) - 聊一聊Redis官方置頂推薦的Java客戶端Redisson
3、Venlenter - Redis分布式鎖-這一篇全了解(Redission實(shí)現(xiàn)分布式鎖完美方案)