Redis 官方推薦的 Java 客戶端有Jedis、lettuce 和 Redisson糠悯。三種客戶端各有優(yōu)缺點(diǎn)柒傻,在我們業(yè)務(wù)實(shí)現(xiàn)中選擇合適的客戶端晶乔,有助于提高Redis的使用性能。要選擇合適的客戶端我們應(yīng)該大概清楚Redis客戶端的通訊方式和各種客戶端的優(yōu)缺點(diǎn)。
Redis概述
Redis支持多種語言客戶端赞弥,支持的客戶端可以從官網(wǎng)查看毅整,這里列舉一系列常見的客戶端:
- C語言
- C++
- C#
- Java
- Python
- Node.js
- PHP
Redis采用單線程方式處理多個(gè)客戶端連接,作為程序開發(fā)者應(yīng)該了解 Redis 服務(wù)端和客戶端的通信協(xié)議绽左,以及主流編程語言的 Redis 客戶端使用方法悼嫉,同時(shí)還需要了解客戶端管理的相應(yīng) API 以及開發(fā)運(yùn)維中可能遇到的問題。
Redis客戶端通訊協(xié)議
Redis制定RESP(Redis Serializable Protocol, RESP)協(xié)議拼窥,實(shí)現(xiàn)客戶端和服務(wù)端的正常交互戏蔑,這種協(xié)議簡單高效,既能夠被機(jī)器解析鲁纠,又容易被人類識(shí)別辛臊。
RESP可以序列號(hào)不同數(shù)據(jù)類型數(shù)據(jù)(整型、字符串房交、數(shù)組以及特殊類型Error)彻舰。需要執(zhí)行的Redis命令會(huì)封裝成類似字符串?dāng)?shù)組的請(qǐng)求通過客戶端發(fā)送到服務(wù)端。Redis會(huì)基于制定的命令類型選擇對(duì)應(yīng)的數(shù)據(jù)類型進(jìn)行回復(fù)候味。
RESP發(fā)送命令格式
在RESP中刃唤,發(fā)送數(shù)據(jù)的類型取決于數(shù)據(jù)報(bào)文的首個(gè)字節(jié):
- 單行字符串:單行字符串的第一個(gè)字節(jié)為+ ,最后兩個(gè)字節(jié)是\r\n白群,其他字節(jié)是字符串內(nèi)容+OK\r\nError尚胞;
- 錯(cuò)誤消息:錯(cuò)誤消息的第一個(gè)字節(jié)為-,最后兩個(gè)字節(jié)是\r\n帜慢,其他字節(jié)是異常消息的文本內(nèi)容-ERR\r\nInteger笼裳;
- 整型數(shù)字:整型數(shù)字的第一個(gè)字節(jié)是:,最后兩個(gè)字節(jié)是\r\n粱玲,其他字節(jié)是數(shù)字的文本內(nèi)容:100\r\nBulk躬柬;
- 定長字符串:定長字符串的String定長字符串第一個(gè)字節(jié)是美元符號(hào) ,緊接著的字節(jié)是內(nèi)容字符串長度\r\n抽减,最后兩個(gè)字節(jié)是\r\n允青,其他字節(jié)是字符串內(nèi)容$4\r\ndoge\r\n;
- RESP數(shù)組:RESP數(shù)組的第一個(gè)字節(jié)是卵沉,緊接著的字節(jié)是元素個(gè)數(shù)\r\n颠锉,最后兩個(gè)字節(jié)是\r\n,其他字節(jié)是各個(gè)元素的內(nèi)容史汗,每個(gè)元素可以是任意一種數(shù)據(jù)類型2\r\n:100\r\n$4\r\ndoge\r\n琼掠。
發(fā)送的命令格式如下,CRLF代表"\r\n":
Copy*<參數(shù)數(shù)量> CRLF
$<參數(shù)1的字節(jié)數(shù)量> CRLF
<參數(shù)1> CRLF
...
$<參數(shù)N的字節(jié)數(shù)量> CRLF
<參數(shù)N> CRLF
以set hello world這個(gè)命令為例停撞,發(fā)送的內(nèi)容就是這樣的:
Copy*3
$3
SET
$5
hello
$5
world
第一行*3表示有3個(gè)參數(shù)瓷蛙,5表示下一個(gè)參數(shù)有5個(gè)字節(jié)速挑,接下來是參數(shù)谤牡,$5表示下一個(gè)參數(shù)有5個(gè)字節(jié),接下來是參數(shù)姥宝。
所以set hello world最終發(fā)送給redis服務(wù)器的命令是:
Copy*3\r\n$3\r\nSET\r\n$5\r\nhello\r\n$5\r\nworld\r\n
RESP響應(yīng)內(nèi)容
CopyRedis的返回結(jié)果類型分為以下五種:
正確回復(fù):在RESP中第一個(gè)字節(jié)為"+"
錯(cuò)誤回復(fù):在RESP中第一個(gè)字節(jié)為"-"
整數(shù)回復(fù):在RESP中第一個(gè)字節(jié)為":"
字符串回復(fù):在RESP中第一個(gè)字節(jié)為"$"
多條字符串回復(fù):在RESP中第一個(gè)字節(jié)為"*"
(+) 表示一個(gè)正確的狀態(tài)信息翅萤,具體信息是當(dāng)前行+后面的字符。
(-) 表示一個(gè)錯(cuò)誤信息腊满,具體信息是當(dāng)前行-后面的字符套么。
(* ) 表示消息體總共有多少行,不包括當(dāng)前行,*后面是具體的行數(shù)碳蛋。
(美元符合) 表示下一行數(shù)據(jù)長度胚泌,不包括換行符長度\r\n,$后面則是對(duì)應(yīng)的長度的數(shù)據(jù)肃弟。
(:) 表示返回一個(gè)數(shù)值玷室,:后面是相應(yīng)的數(shù)字節(jié)符。
Java客戶端
Jedis
傳統(tǒng)老牌Java客戶端笤受,一直在更新穷缤,對(duì)redis命令有比較全面的支持。
優(yōu)點(diǎn)
- 支持全面的Redis命令箩兽,具有全面的API津肛。
缺點(diǎn)
- 阻塞式I/O,其方法都是同步調(diào)用汗贫,程序流需要等到 sockets 處理完 I/O 才能執(zhí)行身坐,不支持異步;
- Jedis 客戶端實(shí)例不是線程安全的落包,所以需要通過連接池來使用 Jedis部蛇。
lettuce
lettuce是可擴(kuò)展的線程安全的客戶端,支持異步模式妥色。如果避免阻塞和事務(wù)操作搪花,如BLPOP和MULTI/EXEC遏片,多個(gè)線程就可以共享一個(gè)連接嘹害。lettuce 底層基于 Netty,支持高級(jí)的 Redis 特性吮便,比如哨兵笔呀,集群,管道髓需,自動(dòng)重新連接和Redis數(shù)據(jù)模型许师。
優(yōu)點(diǎn):
- 支持同步異步通信模式;
- Lettuce 的 API 是線程安全的,如果不是執(zhí)行阻塞和事務(wù)操作微渠,如BLPOP和MULTI/EXEC搭幻,多個(gè)線程就可以共享一個(gè)連接。
- SpringBoot 2.x默認(rèn)Redis客戶端逞盆,對(duì)Lettuce 提供完美支持
Redisson
Redisson 是一個(gè)在 Redis 的基礎(chǔ)上實(shí)現(xiàn)的 Java 駐內(nèi)存數(shù)據(jù)網(wǎng)格(In-Memory Data Grid)檀蹋。它不僅提供了一系列的分布式的 Java 常用對(duì)象,還提供了許多分布式服務(wù)云芦。其中包括( BitSet, Set, Multimap, SortedSet, Map, List, Queue, BlockingQueue, Deque, BlockingDeque, Semaphore, Lock, AtomicLong, CountDownLatch, Publish / Subscribe, Bloom filter, Remote service, Spring cache, Executor service, Live Object service, Scheduler service) Redisson 提供了使用Redis 的最簡單和最便捷的方法俯逾。Redisson 的宗旨是促進(jìn)使用者對(duì)Redis的關(guān)注分離(Separation of Concern),從而讓使用者能夠?qū)⒕Ω械胤旁谔幚順I(yè)務(wù)邏輯上舅逸。
優(yōu)點(diǎn):
- 使用者對(duì) Redis 的關(guān)注分離桌肴,可以類比 Spring 框架,這些框架搭建了應(yīng)用程序的基礎(chǔ)框架和功能琉历,提升開發(fā)效率坠七,讓開發(fā)者有更多的時(shí)間來關(guān)注業(yè)務(wù)邏輯;
- 提供很多分布式相關(guān)操作服務(wù)旗笔,例如灼捂,分布式鎖,分布式集合换团,可通過Redis支持延遲隊(duì)列等悉稠。
缺點(diǎn): - Redisson 對(duì)字符串的操作支持比較差。
Redis Java客戶端技術(shù)選型
文章基于SpringBoot 2.x進(jìn)行選型
- 僅使用Redis進(jìn)行字符串的數(shù)據(jù)進(jìn)行緩存處理艘包,可以使用默認(rèn)的lettuce的猛。
- 需要使用Redis做分布式事務(wù),可以選擇lettuce + Redisson想虎。
- 需要緩存分布式集合數(shù)據(jù)并對(duì)集合進(jìn)行頻繁讀寫操作卦尊,可以選擇lettuce + Redisson。
- 使用Redis 做消息隊(duì)列舌厨,可以選擇lettuce + Redisson岂却。
SpringBoot 集成lettuce + Redisson
在大多數(shù)分布式互聯(lián)網(wǎng)應(yīng)用中,redis經(jīng)常用來處理復(fù)雜業(yè)務(wù)而不是單純的字符串存儲(chǔ)裙椭,比如使用Redis集合躏哩、分布式鎖、隊(duì)列等揉燃,因此集成lettuce + Redisson扫尺,按照不同業(yè)務(wù)而使用不同組件是比較優(yōu)秀的方案。
集成lettuce
- 引入依賴
<!--spring session-->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
SpringBoot 2.X默認(rèn)使用lettuce 炊汤,因此提供了比較好的支持正驻,RedisAutoConfiguration完成了對(duì)JedisConnectionFactory和LettuceConnectionFactory 的自動(dòng)配置弊攘。同時(shí)RedisProperties封裝了redis配置。
- 添加配置
application.yml
spring:
redis:
host: 192.168.128.20 #單機(jī)配置
port: 6379 #單機(jī)配置
#cluster:
#nodes: xxxurl 集群配置
# Redis默認(rèn)情況下有16個(gè)分片姑曙,配置具體使用的分片襟交,默認(rèn)為0
database: 0
lettuce:
pool:
# 連接池最大連接數(shù) 默認(rèn)8 ,負(fù)數(shù)表示沒有限制
max-active: 8
# 連接池最大阻塞等待時(shí)間(使用負(fù)值表示沒有限制) 默認(rèn)-1
max-wait: -1
# 連接池中的最大空閑連接 默認(rèn)8
max-idle: 8
# 連接池中的最小空閑連接 默認(rèn)0
min-idle: 0
集成Redission
- 引入依賴
<!--redisson客戶端-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>${redisson.version}</version>
</dependency>
- 添加配置
redisson.yml
redission:
pool:
# 連接池最大連接數(shù) 默認(rèn)8 伤靠,負(fù)數(shù)表示沒有限制
max-active: 8
# 連接池最大阻塞等待時(shí)間(使用負(fù)值表示沒有限制) 默認(rèn)-1
max-wait: -1
# 連接池中的最大空閑連接 默認(rèn)8
max-idle: 8
# 連接池中的最小空閑連接 默認(rèn)0
min-idle: 0
connTimeout: 10000
timeout: 3000
soTimeout: 3000
size: 32
#集群配置
cluster:
#集群掃描間隔時(shí)間 單位毫秒
scanInterval: 1000
# 只在從服務(wù)節(jié)點(diǎn)里讀取
readMode: "SLAVE"
slaveSubscriptionConnectionPoolSize: 50
# 從節(jié)點(diǎn)最小空閑連接數(shù)
slaveConnectionMinimumIdleSize: 32
# 從節(jié)點(diǎn)連接池大小
slaveConnectionPoolSize: 64
# 主節(jié)點(diǎn)最小空閑連接數(shù)
masterConnectionMinimumIdleSize: 32
# 主節(jié)點(diǎn)連接池大小
masterConnectionPoolSize: 64
# 命令失敗重試次數(shù)
retryAttempts: 3
# 命令重試發(fā)送時(shí)間間隔
retryInterval: 1500
# failedAttempts
failedAttempts: 3
配置客戶端
@EnableConfigurationProperties({RedissionProperties.class, RedisProperties.class})
public class RedisConfig {
@Value("${spring.profiles.active}")
private String env;
@Value("${global-conf.active-profiles.prod}")
private String activeProd;
@Autowired
private RedisProperties redisProperties;
@Autowired
private RedissionProperties redissionProperties;
/**
* springboot2.x 使用LettuceConnectionFactory 代替 RedisConnectionFactory
* 在application.yml配置基本信息后,springboot2.x RedisAutoConfiguration能夠自動(dòng)裝配 LettuceConnectionFactory 和
* RedisConnectionFactory 及其 RedisTemplate 單機(jī)模式和集群模式配置都自動(dòng)注入婿着,因?yàn)樵诙xLettuce客戶端時(shí)無需顯示指定模式
*
* @param redisConnectionFactory
* @return
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(
LettuceConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(redisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(redisSerializer());
return redisTemplate;
}
@Bean
@ConditionalOnProperty(name = "spring.redis.model", havingValue = "single")
public RedissonClient getSingleRedisson() {
Config config = new Config();
String node = redisProperties.getHost();
//用"rediss://"來啟用SSL連接
node = node.startsWith("redis://") ? node : "redis://" + node;
SingleServerConfig serverConfig = config.useSingleServer();
serverConfig.setAddress(node)
.setConnectionPoolSize(redissionProperties.getPool().getSize())
.setTimeout(redissionProperties.getPool().getTimeout())
.setConnectionMinimumIdleSize(redissionProperties.getPool().getMinIdle());
if (StringUtils.isNotBlank(redisProperties.getPassword())) {
serverConfig.setPassword(redisProperties.getPassword());
}
return Redisson.create(config);
}
@Bean
@ConditionalOnProperty(name = "spring.redis.model", havingValue = "cluster")
public RedissonClient getClusterRedisson() {
Config config = new Config();
//用"rediss://"來啟用SSL連接
List<String> newNodes = redisProperties.getCluster().getNodes()
.stream()
.map(node -> node.startsWith("redis://") ? node : "redis://" + node)
.collect(
Collectors.toList());
ClusterServersConfig serverConfig = config.useClusterServers()
.addNodeAddress(newNodes.toArray(new String[0]))
.setScanInterval(
redissionProperties.getCluster().getScanInterval())
.setIdleConnectionTimeout(
redissionProperties.getPool().getSoTimeout())
.setConnectTimeout(
redissionProperties.getPool().getConnTimeout())
.setRetryAttempts(
redissionProperties.getCluster().getRetryAttempts())
.setRetryInterval(
redissionProperties.getCluster().getRetryInterval())
.setMasterConnectionPoolSize(redissionProperties.getCluster()
.getMasterConnectionPoolSize())
.setSlaveConnectionPoolSize(redissionProperties.getCluster()
.getSlaveConnectionPoolSize())
.setTimeout(redissionProperties.getPool().getTimeout());
if (StringUtils.isNotBlank(redisProperties.getPassword())) {
serverConfig.setPassword(redisProperties.getPassword());
}
return Redisson.create(config);
}
@Bean
public RedisSerializer<Object> redisSerializer() {
//創(chuàng)建JSON序列化器
Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(
Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
//必須設(shè)置,否則無法將JSON轉(zhuǎn)化為對(duì)象醋界,會(huì)轉(zhuǎn)化成Map類型
objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,
ObjectMapper.DefaultTyping.NON_FINAL);
serializer.setObjectMapper(objectMapper);
return serializer;
}
@Bean
public RedisCacheManager redisCacheManager(LettuceConnectionFactory redisConnectionFactory) {
RedisCacheWriter redisCacheWriter = RedisCacheWriter
.nonLockingRedisCacheWriter(redisConnectionFactory);
//設(shè)置Redis緩存有效期為1天
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.serializeValuesWith(
RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer())).entryTtl(
Duration.ofDays(1));
return new RedisCacheManager(redisCacheWriter, redisCacheConfiguration);
}
}
自定義Redis工具類
通常情況key一般是String類型竟宋,而value可能不一樣,一般有兩種:String和Object形纺。
如果k-v都是String丘侠,我們直接使用StringRedisTemplate
- SimpleStringRedisUtil
@Component
public class SimpleStringRedisUtil {
@Autowired
private StringRedisTemplate redisTemplate;
/**
* 刪除key
*
* @param key
*/
public void delete(String key) {
redisTemplate.delete(key);
}
/**
* 序列化key
*
* @param key
* @return
*/
public byte[] dump(String key) {
return redisTemplate.dump(key);
}
/**
* 是否存在key
*
* @param key
* @return
*/
public Boolean exists(String key) {
return redisTemplate.hasKey(key);
}
/**
* 設(shè)置指定 key 的值
*
* @param key
* @param value
*/
public void set(String key, String value) {
redisTemplate.opsForValue().set(key, value);
}
/**
* 寫入redis緩存(設(shè)置expire存活時(shí)間)
* @param key
* @param value
* @param expire
* @return
*/
public void set(final String key, String value, Long expire){
ValueOperations operations = redisTemplate.opsForValue();
operations.set(key, value);
redisTemplate.expire(key, expire, TimeUnit.SECONDS);
}
/**
* 獲取指定 key 的值
*
* @param key
* @return
*/
public String get(String key) {
return redisTemplate.opsForValue().get(key);
}
}
如果k-是String,v-是Object,比如Json對(duì)象逐样、可序列化對(duì)象以及固定不變的集合等蜗字,那就需要需要RedisTemplate接口
- SimpleObjectRedisUtil
@Component
public class SimpleObjectRedisUtil {
@Autowired
private RedisTemplate redisTemplate;
public Set<String> keys(String keys){
try {
return redisTemplate.keys(keys);
}catch (Exception e){
e.printStackTrace();
return null;
}
}
/**
* 指定緩存失效時(shí)間
* @param key 鍵
* @param time 時(shí)間(秒)
* @return
*/
public boolean expire(String key, long time) {
try {
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 指定緩存失效時(shí)間
* @param key 鍵
* @param time 時(shí)間
* @param time timeUnit
* @return
*/
public boolean expire(String key, long time, TimeUnit timeUnit) {
try {
if (time > 0) {
redisTemplate.expire(key, time, timeUnit);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根據(jù)key 獲取過期時(shí)間
* @param key 鍵 不能為null
* @return 時(shí)間(秒) 返回0代表為永久有效
*/
public long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
/**
* 根據(jù)key 獲取過期時(shí)間
* @param key 鍵 不能為null
* @param timeUnit
* @return 時(shí)間(秒) 返回0代表為永久有效
*/
public long getExpire(String key, TimeUnit timeUnit) {
return redisTemplate.getExpire(key, timeUnit);
}
/**
* 判斷key是否存在
* @param key 鍵
* @return true 存在 false不存在
*/
public boolean hasKey(String key) {
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 刪除緩存
* @param key 可以傳一個(gè)值 或多個(gè)
*/
@SuppressWarnings("unchecked")
public void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
} else {
redisTemplate.delete(CollectionUtils.arrayToList(key));
}
}
}
/**
* 普通緩存獲取
* @param key 鍵
* @return 值
*/
public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
/**
* 普通緩存放入
* @param key 鍵
* @param value 值
* @return true成功 false失敗
*/
public boolean set(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 普通緩存放入并設(shè)置時(shí)間
* @param key 鍵
* @param value 值
* @param time 時(shí)間(秒) time要大于0 如果time小于等于0 將設(shè)置無限期
* @return true成功 false 失敗
*/
public boolean set(String key, Object value, long time) {
try {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
} else {
set(key, value);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 普通緩存放入并設(shè)置時(shí)間
* @param key 鍵
* @param value 值
* @param time 時(shí)間 time要大于0 如果time小于等于0 將設(shè)置無限期
* @param timeUnit
* @return true成功 false 失敗
*/
public boolean set(String key, Object value, long time, TimeUnit timeUnit) {
try {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, timeUnit);
} else {
set(key, value);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 遞增
* @param key 鍵
* @param delta 要增加幾(大于0)
* @return
*/
public long incr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("遞增因子必須大于0");
}
return redisTemplate.opsForValue().increment(key, delta);
}
/**
* 遞減
* @param key 鍵
* @param delta 要減少幾(小于0)
* @return
*/
public long decr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("遞減因子必須大于0");
}
return redisTemplate.opsForValue().increment(key, -delta);
}
/**
* HashGet
* @param key 鍵 不能為null
* @param item 項(xiàng) 不能為null
* @return 值
*/
public Object hget(String key, String item) {
return redisTemplate.opsForHash().get(key, item);
}
/**
* 獲取hashKey對(duì)應(yīng)的所有鍵值
* @param key 鍵
* @return 對(duì)應(yīng)的多個(gè)鍵值
*/
public Map<Object, Object> hmget(String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* HashSet
* @param key 鍵
* @param map 對(duì)應(yīng)多個(gè)鍵值
* @return true 成功 false 失敗
*/
public boolean hmset(String key, Map<String, ?> map) {
try {
redisTemplate.opsForHash().putAll(key, map);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* HashSet 并設(shè)置時(shí)間
* @param key 鍵
* @param map 對(duì)應(yīng)多個(gè)鍵值
* @param time 時(shí)間(秒)
* @return true成功 false失敗
*/
public boolean hmset(String key, Map<String, ?> map, long time) {
try {
redisTemplate.opsForHash().putAll(key, map);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一張hash表中放入數(shù)據(jù),如果不存在將創(chuàng)建
* @param key 鍵
* @param item 項(xiàng)
* @param value 值
* @return true 成功 false失敗
*/
public boolean hset(String key, String item, Object value) {
try {
redisTemplate.opsForHash().put(key, item, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一張hash表中放入數(shù)據(jù),如果不存在將創(chuàng)建
* @param key 鍵
* @param item 項(xiàng)
* @param value 值
* @param time 時(shí)間(秒) 注意:如果已存在的hash表有時(shí)間,這里將會(huì)替換原有的時(shí)間
* @return true 成功 false失敗
*/
public boolean hset(String key, String item, Object value, long time) {
try {
redisTemplate.opsForHash().put(key, item, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 刪除hash表中的值
* @param key 鍵 不能為null
* @param item 項(xiàng) 可以使多個(gè) 不能為null
*/
public void hdel(String key, Object... item) {
redisTemplate.opsForHash().delete(key, item);
}
/**
* 判斷hash表中是否有該項(xiàng)的值
* @param key 鍵 不能為null
* @param item 項(xiàng) 不能為null
* @return true 存在 false不存在
*/
public boolean hHasKey(String key, String item) {
return redisTemplate.opsForHash().hasKey(key, item);
}
/**
* hash遞增 如果不存在,就會(huì)創(chuàng)建一個(gè) 并把新增后的值返回
* @param key 鍵
* @param item 項(xiàng)
* @param by 要增加幾(大于0)
* @return
*/
public double hincr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, by);
}
/**
* hash遞減
* @param key 鍵
* @param item 項(xiàng)
* @param by 要減少記(小于0)
* @return
*/
public double hdecr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, -by);
}
// ===============================Set=================================
/**
* 根據(jù)key獲取Set中的所有值
* @param key 鍵
* @return
*/
public Set<Object> sGet(String key) {
try {
return redisTemplate.opsForSet().members(key);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 根據(jù)value從一個(gè)set中查詢,是否存在
* @param key 鍵
* @param value 值
* @return true 存在 false不存在
*/
public boolean sHasKey(String key, Object value) {
try {
return redisTemplate.opsForSet().isMember(key, value);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 將數(shù)據(jù)放入set緩存
* @param key 鍵
* @param values 值 可以是多個(gè)
* @return 成功個(gè)數(shù)
*/
public long sSet(String key, Object... values) {
try {
return redisTemplate.opsForSet().add(key, values);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 將set數(shù)據(jù)放入緩存
* @param key 鍵
* @param time 時(shí)間(秒)
* @param values 值 可以是多個(gè)
* @return 成功個(gè)數(shù)
*/
public long sSetAndTime(String key, long time, Object... values) {
try {
Long count = redisTemplate.opsForSet().add(key, values);
if (time > 0)
expire(key, time);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 獲取set緩存的長度
* @param key 鍵
* @return
*/
public long sGetSetSize(String key) {
try {
return redisTemplate.opsForSet().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 移除值為value的
* @param key 鍵
* @param values 值 可以是多個(gè)
* @return 移除的個(gè)數(shù)
*/
public long setRemove(String key, Object... values) {
try {
Long count = redisTemplate.opsForSet().remove(key, values);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
// ===============================list=================================
/**
* 獲取list緩存的內(nèi)容
* @param key 鍵
* @param start 開始
* @param end 結(jié)束 0 到 -1代表所有值
* @return
*/
public List<Object> lGet(String key, long start, long end) {
try {
return redisTemplate.opsForList().range(key, start, end);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 獲取list緩存的長度
* @param key 鍵
* @return
*/
public long lGetListSize(String key) {
try {
return redisTemplate.opsForList().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 通過索引 獲取list中的值
* @param key 鍵
* @param index 索引 index>=0時(shí), 0 表頭脂新,1 第二個(gè)元素挪捕,依次類推;index<0時(shí)争便,-1级零,表尾,-2倒數(shù)第二個(gè)元素滞乙,依次類推
* @return
*/
public Object lGetIndex(String key, long index) {
try {
return redisTemplate.opsForList().index(key, index);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 將list放入緩存
* @param key 鍵
* @param value 值
* @return
*/
public <T> boolean lSet(String key, T value) {
try {
redisTemplate.opsForList().rightPush(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 將list放入緩存
* @param key 鍵
* @param value 值
* @param time 時(shí)間(秒)
* @return
*/
public <T> boolean lSet(String key, T value, long time) {
try {
redisTemplate.opsForList().rightPush(key, value);
if (time > 0)
expire(key, time);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 將list放入緩存
* @param key 鍵
* @param value 值
* @return
*/
public <T> boolean lSet(String key, List<T> value) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 將list放入緩存
*
* @param key 鍵
* @param value 值
* @param time 時(shí)間(秒)
* @return
*/
public <T> boolean lSet(String key, List<T> value, long time) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
if (time > 0)
expire(key, time);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根據(jù)索引修改list中的某條數(shù)據(jù)
* @param key 鍵
* @param index 索引
* @param value 值
* @return
*/
public <T> boolean lUpdateIndex(String key, long index, T value) {
try {
redisTemplate.opsForList().set(key, index, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 移除N個(gè)值為value
* @param key 鍵
* @param count 移除多少個(gè)
* @param value 值
* @return 移除的個(gè)數(shù)
*/
public <T> long lRemove(String key, long count, T value) {
try {
Long remove = redisTemplate.opsForList().remove(key, count, value);
return remove;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**------------------zSet相關(guān)操作--------------------------------*/
/**
* 添加元素,有序集合是按照元素的score值由小到大排列
*
* @param key
* @param value
* @param score
* @return
*/
public Boolean zAdd(String key, String value, double score) {
return redisTemplate.opsForZSet().add(key, value, score);
}
/**
*
* @param key
* @param values
* @return
*/
public Long zAdd(String key, Set<ZSetOperations.TypedTuple<String>> values) {
return redisTemplate.opsForZSet().add(key, values);
}
/**
*
* @param key
* @param values
* @return
*/
public Long zRemove(String key, Object... values) {
return redisTemplate.opsForZSet().remove(key, values);
}
/**
* 增加元素的score值奏纪,并返回增加后的值
*
* @param key
* @param value
* @param delta
* @return
*/
public Double zIncrementScore(String key, String value, double delta) {
return redisTemplate.opsForZSet().incrementScore(key, value, delta);
}
/**
* 返回元素在集合的排名,有序集合是按照元素的score值由小到大排列
*
* @param key
* @param value
* @return 0表示第一位
*/
public Long zRank(String key, Object value) {
return redisTemplate.opsForZSet().rank(key, value);
}
/**
* 返回元素在集合的排名,按元素的score值由大到小排列
*
* @param key
* @param value
* @return
*/
public Long zReverseRank(String key, Object value) {
return redisTemplate.opsForZSet().reverseRank(key, value);
}
/**
* 獲取集合的元素, 從小到大排序
*
* @param key
* @param start
* 開始位置
* @param end
* 結(jié)束位置, -1查詢所有
* @return
*/
public Set<String> zRange(String key, long start, long end) {
return redisTemplate.opsForZSet().range(key, start, end);
}
/**
* 獲取集合元素, 并且把score值也獲取
*
* @param key
* @param start
* @param end
* @return
*/
public Set<ZSetOperations.TypedTuple<String>> zRangeWithScores(String key, long start,
long end) {
return redisTemplate.opsForZSet().rangeWithScores(key, start, end);
}
/**
* 根據(jù)Score值查詢集合元素
*
* @param key
* @param min
* 最小值
* @param max
* 最大值
* @return
*/
public Set<String> zRangeByScore(String key, double min, double max) {
return redisTemplate.opsForZSet().rangeByScore(key, min, max);
}
/**
* 根據(jù)Score值查詢集合元素, 從小到大排序
*
* @param key
* @param min
* 最小值
* @param max
* 最大值
* @return
*/
public Set<ZSetOperations.TypedTuple<String>> zRangeByScoreWithScores(String key,
double min, double max) {
return redisTemplate.opsForZSet().rangeByScoreWithScores(key, min, max);
}
/**
*
* @param key
* @param min
* @param max
* @param start
* @param end
* @return
*/
public Set<ZSetOperations.TypedTuple<String>> zRangeByScoreWithScores(String key,
double min, double max, long start, long end) {
return redisTemplate.opsForZSet().rangeByScoreWithScores(key, min, max,
start, end);
}
/**
* 獲取集合的元素, 從大到小排序
*
* @param key
* @param start
* @param end
* @return
*/
public Set<String> zReverseRange(String key, long start, long end) {
return redisTemplate.opsForZSet().reverseRange(key, start, end);
}
/**
* 獲取集合的元素, 從大到小排序, 并返回score值
*
* @param key
* @param start
* @param end
* @return
*/
public Set<ZSetOperations.TypedTuple<String>> zReverseRangeWithScores(String key,
long start, long end) {
return redisTemplate.opsForZSet().reverseRangeWithScores(key, start,
end);
}
/**
* 根據(jù)Score值查詢集合元素, 從大到小排序
*
* @param key
* @param min
* @param max
* @return
*/
public Set<String> zReverseRangeByScore(String key, double min,
double max) {
return redisTemplate.opsForZSet().reverseRangeByScore(key, min, max);
}
/**
* 根據(jù)Score值查詢集合元素, 從大到小排序
*
* @param key
* @param min
* @param max
* @return
*/
public Set<ZSetOperations.TypedTuple<String>> zReverseRangeByScoreWithScores(
String key, double min, double max) {
return redisTemplate.opsForZSet().reverseRangeByScoreWithScores(key,
min, max);
}
/**
*
* @param key
* @param min
* @param max
* @param start
* @param end
* @return
*/
public Set<String> zReverseRangeByScore(String key, double min,
double max, long start, long end) {
return redisTemplate.opsForZSet().reverseRangeByScore(key, min, max,
start, end);
}
/**
* 根據(jù)score值獲取集合元素?cái)?shù)量
*
* @param key
* @param min
* @param max
* @return
*/
public Long zCount(String key, double min, double max) {
return redisTemplate.opsForZSet().count(key, min, max);
}
/**
* 獲取集合大小
*
* @param key
* @return
*/
public Long zSize(String key) {
return redisTemplate.opsForZSet().size(key);
}
/**
* 獲取集合大小
*
* @param key
* @return
*/
public Long zZCard(String key) {
return redisTemplate.opsForZSet().zCard(key);
}
/**
* 獲取集合中value元素的score值
*
* @param key
* @param value
* @return
*/
public Double zScore(String key, Object value) {
return redisTemplate.opsForZSet().score(key, value);
}
/**
* 移除指定索引位置的成員
*
* @param key
* @param start
* @param end
* @return
*/
public Long zRemoveRange(String key, long start, long end) {
return redisTemplate.opsForZSet().removeRange(key, start, end);
}
/**
* 根據(jù)指定的score值的范圍來移除成員
*
* @param key
* @param min
* @param max
* @return
*/
public Long zRemoveRangeByScore(String key, double min, double max) {
return redisTemplate.opsForZSet().removeRangeByScore(key, min, max);
}
/**
* 獲取key和otherKey的并集并存儲(chǔ)在destKey中
*
* @param key
* @param otherKey
* @param destKey
* @return
*/
public Long zUnionAndStore(String key, String otherKey, String destKey) {
return redisTemplate.opsForZSet().unionAndStore(key, otherKey, destKey);
}
/**
*
* @param key
* @param otherKeys
* @param destKey
* @return
*/
public Long zUnionAndStore(String key, Collection<String> otherKeys,
String destKey) {
return redisTemplate.opsForZSet()
.unionAndStore(key, otherKeys, destKey);
}
/**
* 交集
*
* @param key
* @param otherKey
* @param destKey
* @return
*/
public Long zIntersectAndStore(String key, String otherKey,
String destKey) {
return redisTemplate.opsForZSet().intersectAndStore(key, otherKey,
destKey);
}
/**
* 交集
*
* @param key
* @param otherKeys
* @param destKey
* @return
*/
public Long zIntersectAndStore(String key, Collection<String> otherKeys,
String destKey) {
return redisTemplate.opsForZSet().intersectAndStore(key, otherKeys, destKey);
}
/**
*
* @param key
* @param options
* @return
*/
public Cursor<TypedTuple<String>> zScan(String key, ScanOptions options) {
return redisTemplate.opsForZSet().scan(key, options);
}
}
對(duì)于鎖、分布式集合操作斩启、隊(duì)列等分布式對(duì)象或服務(wù)序调,使用Redisson
RedissonClient對(duì)鎖、集合兔簇、隊(duì)列等分布式操作進(jìn)行了封裝发绢,開發(fā)者只需要簡單的調(diào)用其接口進(jìn)行操作:
- 使用Redisson實(shí)現(xiàn)Redis分布式鎖
Redisson RLock實(shí)現(xiàn)了java.util.concurrent.locks.Lock接口,開發(fā)者可以顯示調(diào)用Lock接口進(jìn)行相關(guān)鎖操作
//根據(jù)key獲取分布式鎖對(duì)象-普通可重入鎖
RLock lock = redissonClient.getLock("redisKey");
//根據(jù)key獲取分布式鎖對(duì)象-公平可重入鎖
RLock fairLock = redissonClient.getFairLock("redisKey");
//可重入鎖
//不指定時(shí)間垄琐,當(dāng)加鎖成功后會(huì)創(chuàng)建一個(gè)鎖監(jiān)聽看門狗的監(jiān)聽器边酒,
// Redisson實(shí)例被關(guān)閉前,不斷的延長鎖的有效期此虑。
// 默認(rèn)情況下甚纲,看門狗的檢查鎖的超時(shí)時(shí)間是30秒鐘
//fairLock.lock();
lock.lock();
try {
//...
} finally {
//fairLock.unlock();
lock.unlock(); //需要顯示釋放鎖
}
//2. 顯示指定加鎖時(shí)間
// 加鎖以后10秒鐘自動(dòng)解鎖
// 無需調(diào)用unlock方法手動(dòng)解鎖
//fairLock.lock(10, TimeUnit.SECONDS);
lock.lock(10, TimeUnit.SECONDS);
try {
//...
} finally {
//fairLock.unlock();
lock.unlock(); //需要顯示釋放鎖
}
//3. 嘗試獲取鎖
// 嘗試加鎖,最多等待100秒朦前,上鎖以后10秒自動(dòng)解鎖
boolean res = false;
try {
//res = fairLock.tryLock(100, 10, TimeUnit.SECONDS);
res = lock.tryLock(100, 10, TimeUnit.SECONDS);
if (res) {
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (res) {
//fairLock.unlock();
lock.unlock(); //需要顯示釋放鎖
}
}
- 使用Redisson實(shí)現(xiàn)集合操作
@Autowired
private RedissonClient redissonClient;
//List操作
//RList Java對(duì)象在實(shí)現(xiàn)了java.util.List介杆,同時(shí)確保了插入順序
RList nameList = client.getList("redisKey");
nameList.clear();
nameList.add("bingo");
nameList.add("yanglbme");
nameList.add("https://github.com/redisson");
nameList.remove(-1);
boolean contains = nameList.contains("redisson");
nameList.forEach(System.out::println);
//Hash表
//RMap對(duì)象實(shí)現(xiàn)了`java.util.concurrent.ConcurrentMap`接口和`java.util.Map`接/口。與HashMap不同的是韭寸,RMap保持了元素的插入順序春哨。
RMap<String, SomeObject> map = redisson.getMap("redisKey");
SomeObject prevObject = map.put("redisson1", new SomeObject());
SomeObject currentObject = map.putIfAbsent("redisson2", new SomeObject());
SomeObject obj = map.remove("redisson1");
map.fastPut("redisson1", new SomeObject());
map.fastRemove("redisson2");
//鎖定map內(nèi)元素
RLock keyLock = map.getLock("mapKey");
keyLock.lock();
try {
SomeObjectv = map.get("mapKey");
// 其他業(yè)務(wù)邏輯
} finally {
keyLock.unlock();
}
//讀寫鎖
RReadWriteLock rwLock = map.getReadWriteLock("mapKey");
rwLock.readLock().lock();
try {
MyValue v = map.get("mapKey");
// 其他業(yè)務(wù)邏輯
} finally {
keyLock.readLock().unlock();
}
- 使用Redisson實(shí)現(xiàn)延遲隊(duì)列
//延遲隊(duì)列工具類
@Component
@Slf4j
public class RedisDelayedQueue<T> {
@Autowired
RedissonClient redissonClient;
/**
* 任務(wù)回調(diào)監(jiān)聽
*
* @param
*/
public abstract static class TaskEventListener<T> {
/**
* 執(zhí)行方法
*
* @param t
*/
public abstract void invoke(T t);
}
/**
* 添加隊(duì)列
*
* @param t DTO傳輸類
* @param delay 時(shí)間數(shù)量
* @param timeUnit 時(shí)間單位
*/
public void addQueue(T t, long delay, TimeUnit timeUnit) {
RBlockingQueue blockingFairQueue = redissonClient.getBlockingQueue(t.getClass().getName());
RDelayedQueue delayedQueue = redissonClient.getDelayedQueue(blockingFairQueue);
delayedQueue.offer(t, delay, timeUnit);
delayedQueue.destroy();
}
/**
* 獲取隊(duì)列
*
* @param zClass DTO泛型
* @param taskEventListener 任務(wù)回調(diào)監(jiān)聽
* @return
*/
public void getQueue(Class zClass, TaskEventListener taskEventListener) {
RBlockingQueue blockingFairQueue = redissonClient.getBlockingQueue(zClass.getName());
((Runnable) () -> {
while (true) {
try {
T t = (T) blockingFairQueue.take();
taskEventListener.invoke(t);
} catch (InterruptedException e) {
log.error("獲取隊(duì)列異常,原因:{}", e);
}
}
}).run();
}
}
//添加延遲隊(duì)列監(jiān)聽
@Component
@Slf4j
public class NotifySubscribeListener implements CommandLineRunner {
@Autowired
private RedisDelayedQueue redisDelayedQueue;
@Autowired
private EmsEventQueueHelper emsEventQueueHelper;
@Override
public void run(String... args) throws Exception {
//監(jiān)聽延遲隊(duì)列
RedisDelayedQueue.TaskEventListener taskEventListener = new RedisDelayedQueue.TaskEventListener() {
@Override
public void invoke(Object o) {
EmsEventInfo info = (EmsEventInfo) o;
log.info("延遲隊(duì)列執(zhí)行任務(wù):{}", info.toString());
emsEventQueueHelper.publishEvent(info);
}
};
redisDelayedQueue.getQueue(EmsEventInfo.class, taskEventListener);
}
}
//隊(duì)列入列
//省略......
@Autowired
private RedisDelayedQueue redisDelayedQueue;
//省略......
//入列
redisDelayedQueue.addQueue(info, emsTmpProperties.getNotifyTime(), TimeUnit.MINUTES);
更多用法參考Redisson官網(wǎng)https://github.com/redisson/redisson