简介
https://spring.io/projects/spring-data-redis
我们知道常用的Redis客户端 https://redis.io/clients#java
在 spring-boot-starter-data-redis 项目 2.X 版本中 ,默认使用 Lettuce 作为 Java Redis 工具库(之前为Jedis )
- jedis:采用直连, 多个线程操作的话, 是不安全的, 如果想要避免不安全, 使用 jedis pool 连接池 它更像BIO。
- lettuce:采用netty 实例可以多个线程中进行共享, 不存在线程不安全的情况, 可以减少线程数据 它更像NIO。
在SpringBoot中一般使用RedisTemplate提供的方法来操作Redis。那么使用SpringBoot整合Redis需要 那些步骤呢。
源码
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnClass({RedisOperations.class})
@EnableConfigurationProperties({RedisProperties.class})
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
public class RedisAutoConfiguration {
public RedisAutoConfiguration() {
}
@Bean
@ConditionalOnMissingBean(
name = {"redisTemplate"} // 我们自己可以自定义一个 redisTemplate 来替换这个默认的
)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
// 默认 redisTemplate 没有过多的设置, redis 对象都是需要序列化的
// 两个泛型都是 Object 类型 我们使用的时候需要强制转换 <String, Object>
RedisTemplate<Object, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnClass({ReactiveRedisConnectionFactory.class, ReactiveRedisTemplate.class, Flux.class})
@AutoConfigureAfter({RedisAutoConfiguration.class})
public class RedisReactiveAutoConfiguration {
public RedisReactiveAutoConfiguration() {
}
@Bean
@ConditionalOnMissingBean(
name = {"reactiveRedisTemplate"}
)
@ConditionalOnBean({ReactiveRedisConnectionFactory.class})
public ReactiveRedisTemplate<Object, Object> reactiveRedisTemplate(ReactiveRedisConnectionFactory reactiveRedisConnectionFactory, ResourceLoader resourceLoader) {
JdkSerializationRedisSerializer jdkSerializer = new JdkSerializationRedisSerializer(resourceLoader.getClassLoader());
RedisSerializationContext<Object, Object> serializationContext = RedisSerializationContext.newSerializationContext().key(jdkSerializer).value(jdkSerializer).hashKey(jdkSerializer).hashValue(jdkSerializer).build();
return new ReactiveRedisTemplate(reactiveRedisConnectionFactory, serializationContext);
}
@Bean
@ConditionalOnMissingBean(
name = {"reactiveStringRedisTemplate"}
)
@ConditionalOnBean({ReactiveRedisConnectionFactory.class})
public ReactiveStringRedisTemplate reactiveStringRedisTemplate(ReactiveRedisConnectionFactory reactiveRedisConnectionFactory) {
return new ReactiveStringRedisTemplate(reactiveRedisConnectionFactory);
}
}
一、整合
1.1 导入依赖
<!-- spring-boot-starter-web环境 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 高版本redis的lettuce需要commons-pool2 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.7.0</version>
</dependency>
<!-- spring-boot-starter-webflux环境 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
<!--springboot2.X默认使用lettuce连接池,需要引入commons-pool2-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
1.2 配置链接
spring:
redis:
database: 0
host: 127.0.0.1
jedis:
pool:
#最大连接数据库连接数,设 0 为没有限制
max-active: 8
#最大等待连接中的数量,设 0 为没有限制
max-idle: 8
#最大建立连接等待时间。如果超过此时间将接到异常。设为-1表示无限制。
max-wait: -1ms
#最小等待连接中的数量,设 0 为没有限制
min-idle: 0
lettuce:
pool:
max-active: 8
max-idle: 8
max-wait: -1ms
min-idle: 0
shutdown-timeout: 100ms
password:
port: 6379
1.3 编写自定义的 RedisTemplate
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.net.UnknownHostException;
@Configuration
public class Config {
@Bean
@SuppressWarnings("all")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
// 自定义 String Object
RedisTemplate<String, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
// Json 序列化配置
Jackson2JsonRedisSerializer<Object> objectJackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(Object.class);
// ObjectMapper 转译
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
objectJackson2JsonRedisSerializer.setObjectMapper(objectMapper);
// String 的序列化
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// key 采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
// hash 的key也采用 String 的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
// value 序列化方式采用 jackson
template.setValueSerializer(objectJackson2JsonRedisSerializer);
// hash 的 value 采用 jackson
template.setHashValueSerializer(objectJackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
1.4 StringRedisTemplate 与 RedisTemplate
RedisTemplate 对自身支持的数据结构分别定义了操作,下面为常见的5种操作:
redisTemplate.opsForValue():操作字符串
redisTemplate.opsForHash():操作hash
redisTemplate.opsForList():操作list
redisTemplate.opsForSet():操作set
redisTemplate.opsForZSet():操作有序set
如果操作字符串的话,建议直接用 StringRedisTemplate 。
StringRedisTemplate 与 RedisTemplate 的区别
- StringRedisTemplate 继承了 RedisTemplate。
- RedisTemplate 是一个泛型类,而 StringRedisTemplate 则不是。
- StringRedisTemplate 只能对 key=String,value=String 的键值对进行操作,RedisTemplate 可以对任何类型的 key-value 键值对操作。
- 他们各自序列化的方式不同,但最终都是得到了一个字节数组,殊途同归,StringRedisTemplate 使用的是 StringRedisSerializer 类;RedisTemplate 使用的是 JdkSerializationRedisSerializer 类。反序列化,则是一个得到 String,一个得到 Object。
- 两者的数据是不共通的,StringRedisTemplate 只能管理 StringRedisTemplate 里面的数据,RedisTemplate 只能管理 RedisTemplate中 的数据。
1.5 RedisUtils
在企业开发中, 我们80%的情况下, 都不会使用原生方式去编写代码
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisClusterConnection;
import org.springframework.data.redis.connection.RedisClusterNode;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.jedis.JedisClusterConnection;
import org.springframework.data.redis.connection.jedis.JedisConnection;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.TimeUnit;
/**
* @author: huangyibo
* @Date: 2022/2/22 11:56
* @Description: Redis工具类
*/
@Component
@Slf4j
public class RedisUtil {
//加锁失效时间,毫秒
public static final int LOCK_EXPIRE = 3000; // ms
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// =============================common============================
/**
* 指定缓存失效时间
*
* @param key 键
* @param time 时间(秒)
* @return
*/
public Boolean expire(String key, long time) {
try {
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
log.error("RedisUtils.expire 方法指定缓存失效时间异常, key:{}, time:{}", key, time, e);
return false;
}
}
/**
* 根据 key 获取过期时间
*
* @param key 键(不能为 Null)
* @return 时间(秒) 返回0代表永久有效
*/
public Long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
/**
* 判断 key 是否存在
*
* @param key 键(不能为 Null)
* @return true 存在 false 不存在
*/
public Boolean hashKey(String key) {
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
log.error("RedisUtils.hashKey 方法判断key是否存在异常, key:{}", key, e);
return false;
}
}
/**
* 使用scan遍历key
* 为什么不使用keys 因为Keys会引发Redis锁,并且增加Redis的CPU占用,特别是数据庞大的情况下。这个命令千万别在生产环境乱用。
* 支持redis单节点和集群调用
* @param matchKey
* @return
*/
public Set<String> scanMatch(String matchKey) {
Set<String> keys = new HashSet<>();
RedisConnectionFactory connectionFactory = redisTemplate.getConnectionFactory();
RedisConnection redisConnection = connectionFactory.getConnection();
Cursor<byte[]> scan = null;
if(redisConnection instanceof JedisClusterConnection){
RedisClusterConnection clusterConnection = connectionFactory.getClusterConnection();
Iterable<RedisClusterNode> redisClusterNodes = clusterConnection.clusterGetNodes();
Iterator<RedisClusterNode> iterator = redisClusterNodes.iterator();
while (iterator.hasNext()) {
RedisClusterNode next = iterator.next();
scan = clusterConnection.scan(next, ScanOptions.scanOptions().match(matchKey).count(Integer.MAX_VALUE).build());
while (scan.hasNext()) {
keys.add(new String(scan.next()));
}
try {
if(scan !=null){
scan.close();
}
} catch (IOException e) {
log.error("scan遍历key关闭游标异常",e);
}
}
return keys;
}
if(redisConnection instanceof JedisConnection){
scan = redisConnection.scan(ScanOptions.scanOptions().match(matchKey).count(Integer.MAX_VALUE).build());
while (scan.hasNext()){
//找到一次就添加一次
keys.add(new String(scan.next()));
}
try {
if(scan !=null){
scan.close();
}
} catch (IOException e) {
log.error("scan遍历key关闭游标异常",e);
}
return keys;
}
return keys;
}
/**
* 删除缓存
*
* @param key 可以传一个值 或多个
*/
public void del(String... key) {
if(key != null && key.length > 0){
if(key.length == 1){
redisTemplate.delete(key[0]);
}
if(key.length > 1) {
redisTemplate.delete(Arrays.asList(key));
}
}
}
//==================================String相关操作====================================
/**
* 普通缓存获取
*
* @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) {
log.error("RedisUtils.set 设置普通缓存异常, key:{}, value:{}",key, JSON.toJSONString(value), e);
return false;
}
}
/**
* 普通缓存放入并设置时间
*
* @param key 键
* @param value 值
* @param time 时间(秒) time > 0 若 time <= 0 将设置无限期
* @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) {
log.error("RedisUtils.set 设置普通缓存并设置时间异常, key:{}, value:{}, time:{}",key, JSON.toJSONString(value), time, e);
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().decrement(key, delta);
}
// ================================Map相关操作=================================
/**
* HashGet
*
* @param key 键 不能为null
* @param item 项 不能为null
*/
public Object hGet(String key, String item) {
return redisTemplate.opsForHash().get(key, item);
}
/**
* 获取hashKey对应的所有键值
*
* @param key 键
* @return 对应的多个键值
*/
public Map<Object, Object> hmGet(String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* HashSet
*
* @param key 键
* @param map 对应多个键值
*/
public boolean hmSet(String key, Map<String, Object> map) {
try {
redisTemplate.opsForHash().putAll(key, map);
return true;
} catch (Exception e) {
log.error("RedisUtils.hmSet HashSet设置缓存异常, key:{}, map:{}",key, JSON.toJSONString(map), e);
return false;
}
}
/**
* HashSet 并设置时间
*
* @param key 键
* @param map 对应多个键值
* @param time 时间(秒)
* @return true成功 false失败
*/
public boolean hmSet(String key, Map<String, Object> map, long time) {
try {
redisTemplate.opsForHash().putAll(key, map);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
log.error("RedisUtils.hmSet HashSet设置缓存并设置时间异常, key:{}, map:{}, time:{}", key, JSON.toJSONString(map), time, e);
return false;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key 键
* @param item 项
* @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) {
log.error("RedisUtils.hSet hash表中设置缓存异常,key:{}, item:{}, value:{}", key, item, JSON.toJSONString(value), e);
return false;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key 键
* @param item 项
* @param value 值
* @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
* @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) {
log.error("RedisUtils.hSet hash表中设置缓存异常, key:{}, item:{}, value:{}, time:{}", key, item, JSON.toJSONString(value), time, e);
return false;
}
}
/**
* 删除hash表中的值
*
* @param key 键 不能为null
* @param item 项 可以使多个 不能为null
*/
public void hDel(String key, Object... item) {
redisTemplate.opsForHash().delete(key, item);
}
/**
* 判断hash表中是否有该项的值
*
* @param key 键 不能为null
* @param item 项 不能为null
* @return true 存在 false不存在
*/
public boolean hHasKey(String key, String item) {
return redisTemplate.opsForHash().hasKey(key, item);
}
/**
* hash递增 如果不存在,就会创建一个 并把新增后的值返回
*
* @param key 键
* @param item 项
* @param by 要增加几(大于0)
*/
public double hIncr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, by);
}
/**
* hash递减
*
* @param key 键
* @param item 项
* @param by 要减少记(小于0)
*/
public double hDecr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, -by);
}
// ============================set相关操作=============================
/**
* 根据key获取Set中的所有值
*
* @param key 键
*/
public Set<Object> sGet(String key) {
try {
return redisTemplate.opsForSet().members(key);
} catch (Exception e) {
log.error("RedisUtils.sGet 根据key获取Set中的所有值异常, key:{}", key, e);
return new HashSet<>();
}
}
/**
* 根据value从一个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) {
log.error("RedisUtils.sHasKey 方法异常, key:{}, value:{}", key, JSON.toJSONString(value), e);
return false;
}
}
/**
* 将数据放入set缓存
*
* @param key 键
* @param values 值 可以是多个
* @return 成功个数
*/
public Long sSet(String key, Object... values) {
try {
return redisTemplate.opsForSet().add(key, values);
} catch (Exception e) {
log.error("RedisUtils.sSet 方法异常, key:{}, values:{}", key, JSON.toJSONString(values), e);
return 0L;
}
}
/**
* 将set数据放入缓存
*
* @param key 键
* @param time 时间(秒)
* @param values 值 可以是多个
* @return 成功个数
*/
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) {
log.error("RedisUtils.sSetAndTime 方法异常, key:{}, time:{}, values:{}", key, time, JSON.toJSONString(values), e);
return 0L;
}
}
/**
* 获取set缓存的长度
*
* @param key 键
*/
public Long sGetSetSize(String key) {
try {
return redisTemplate.opsForSet().size(key);
} catch (Exception e) {
log.error("RedisUtils.sGetSetSize 方法异常, key:{}", key, e);
return 0L;
}
}
/**
* 移除值为value的
*
* @param key 键
* @param values 值 可以是多个
* @return 移除的个数
*/
public Long setRemove(String key, Object... values) {
try {
return redisTemplate.opsForSet().remove(key, values);
} catch (Exception e) {
log.error("RedisUtils.setRemove 方法异常, key:{}, values:{}", key, JSON.toJSONString(values), e);
return 0L;
}
}
// ===============================list相关操作=================================
/**
* 获取list缓存的内容
*
* @param key 键
* @param start 开始
* @param end 结束 0 到 -1代表所有值
*/
public List<Object> lGet(String key, long start, long end) {
try {
return redisTemplate.opsForList().range(key, start, end);
} catch (Exception e) {
log.error("RedisUtils.lGet 方法异常, key:{}, start:{}, end:{}", key, start, end, e);
return new ArrayList<>();
}
}
/**
* 获取list缓存的长度
*
* @param key 键
*/
public Long lGetListSize(String key) {
try {
return redisTemplate.opsForList().size(key);
} catch (Exception e) {
log.error("RedisUtils.lGetListSize 方法异常, key:{}", key, e);
return 0L;
}
}
/**
* 通过索引 获取list中的值
*
* @param key 键
* @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
*/
public Object lGetIndex(String key, long index) {
try {
return redisTemplate.opsForList().index(key, index);
} catch (Exception e) {
log.error("RedisUtils.lGetIndex 方法异常, key:{}, index:{}", key, index, e);
return null;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
*/
public boolean lSet(String key, Object value) {
try {
redisTemplate.opsForList().rightPush(key, value);
return true;
} catch (Exception e) {
log.error("RedisUtils.lSet 方法异常, key:{}, value:{}", key, JSON.toJSONString(value), e);
return false;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @param time 时间(秒)
*/
public boolean lSet(String key, Object value, long time) {
try {
redisTemplate.opsForList().rightPush(key, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
log.error("RedisUtils.lSet 方法异常, key:{}, value:{}, time:{}", key, JSON.toJSONString(value), time, e);
return false;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @return
*/
public boolean lSet(String key, List<Object> value) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
return true;
} catch (Exception e) {
log.error("RedisUtils.lSet 方法异常, key:{}, value:{}", key, JSON.toJSONString(value), e);
return false;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return
*/
public boolean lSet(String key, List<Object> value, long time) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
log.error("RedisUtils.lSet 方法异常, key:{}, value:{}, time:{}", key, JSON.toJSONString(value), time, e);
return false;
}
}
/**
* 根据索引修改list中的某条数据
*
* @param key 键
* @param index 索引
* @param value 值
* @return
*/
public boolean lUpdateIndex(String key, long index, Object value) {
try {
redisTemplate.opsForList().set(key, index, value);
return true;
} catch (Exception e) {
log.error("RedisUtils.lUpdateIndex 方法异常, key:{}, index:{}, value:{}", key, index, JSON.toJSONString(value), e);
return false;
}
}
/**
* 移除N个值为value
*
* @param key 键
* @param count 移除多少个
* @param value 值
* @return 移除的个数
*/
public Long lRemove(String key, long count, Object value) {
try {
return redisTemplate.opsForList().remove(key, count, value);
} catch (Exception e) {
log.error("RedisUtils.lRemove 方法异常, key:{}, count:{}, value:{}", key, count, JSON.toJSONString(value), e);
return 0L;
}
}
// ===============================ZSet相关操作=================================
/**
* 添加元素,有序集合是按照元素的score值由小到大排列
*
* @param key 键
* @param value 值
* @param score score值
* @return
*/
public Boolean zAdd(String key, String value, double score) {
return redisTemplate.opsForZSet().add(key, value, score);
}
/**
* 添加元素,有序集合是按照元素的score值由小到大排列
* @param key 键
* @param values 值集合
* @return
*/
public Long zAdd(String key, Set<ZSetOperations.TypedTuple<Object>> 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 增加的score值
* @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 结束位置, -1查询所有
* @return
*/
public Set<Object> zRange(String key, long start, long end) {
return redisTemplate.opsForZSet().range(key, start, end);
}
/**
* 获取集合元素, 并且把score值也获取
*
* @param key 键
* @param start 开始位置
* @param end 结束位置, -1查询所有
* @return
*/
public Set<ZSetOperations.TypedTuple<Object>> zRangeWithScores(String key, long start, long end) {
return redisTemplate.opsForZSet().rangeWithScores(key, start, end);
}
/**
* 根据Score值查询集合元素
*
* @param key 键
* @param min 最小值
* @param max 最大值
* @return
*/
public Set<Object> zRangeByScore(String key, double min, double max) {
return redisTemplate.opsForZSet().rangeByScore(key, min, max);
}
/**
* 根据Score值查询集合元素, 从小到大排序
*
* @param key 键
* @param min 最小值
* @param max 最大值
* @return
*/
public Set<ZSetOperations.TypedTuple<Object>> zRangeByScoreWithScores(String key, double min, double max) {
return redisTemplate.opsForZSet().rangeByScoreWithScores(key, min, max);
}
/**
* 根据Score值查询集合元素, 从小到大排序
* @param key 键
* @param min 最小值
* @param max 最大值
* @param start 开始位置
* @param end 结束位置, -1查询所有
* @return
*/
public Set<ZSetOperations.TypedTuple<Object>> 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 结束位置, -1查询所有
* @return
*/
public Set<Object> zReverseRange(String key, long start, long end) {
return redisTemplate.opsForZSet().reverseRange(key, start, end);
}
/**
* 获取集合的元素, 从大到小排序, 并返回score值
*
* @param key 键
* @param start 开始位置
* @param end 结束位置, -1查询所有
* @return
*/
public Set<ZSetOperations.TypedTuple<Object>> zReverseRangeWithScores(String key, long start, long end) {
return redisTemplate.opsForZSet().reverseRangeWithScores(key, start, end);
}
/**
* 根据Score值查询集合元素, 从大到小排序
*
* @param key 键
* @param min 最小值
* @param max 最大值
* @return
*/
public Set<Object> zReverseRangeByScore(String key, double min, double max) {
return redisTemplate.opsForZSet().reverseRangeByScore(key, min, max);
}
/**
* 根据Score值查询集合元素, 从大到小排序
*
* @param key 键
* @param min 最小值
* @param max 最大值
* @return
*/
public Set<ZSetOperations.TypedTuple<Object>> zReverseRangeByScoreWithScores(
String key, double min, double max) {
return redisTemplate.opsForZSet().reverseRangeByScoreWithScores(key, min, max);
}
/**
* 根据Score值查询集合元素, 指定下标并从大到小排序
* @param key 键
* @param min 最小值
* @param max 最大值
* @param start 开始位置
* @param end 结束位置, -1查询所有
* @return
*/
public Set<Object> zReverseRangeByScore(String key, double min,
double max, long start, long end) {
return redisTemplate.opsForZSet().reverseRangeByScore(key, min, max, start, end);
}
/**
* 根据score值获取集合元素数量
*
* @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 结束位置, -1查询所有
* @return
*/
public Long zRemoveRange(String key, long start, long end) {
return redisTemplate.opsForZSet().removeRange(key, start, end);
}
/**
* 根据指定的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的并集并存储在destKey中
*
* @param key
* @param otherKey
* @param destKey
* @return
*/
public Long zUnionAndStore(String key, String otherKey, String destKey) {
return redisTemplate.opsForZSet().unionAndStore(key, otherKey, destKey);
}
/**
* 获取key和otherKeys的并集并存储在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);
}
/**
* 获取key和otherKey的交集并存储在destKey中
*
* @param key
* @param otherKey
* @param destKey
* @return
*/
public Long zIntersectAndStore(String key, String otherKey, String destKey) {
return redisTemplate.opsForZSet().intersectAndStore(key, otherKey, destKey);
}
/**
* 取key和otherKeys的交集并存储在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);
}
/**
* 匹配获取键值对,ScanOptions.NONE为获取全部键值对;
* ScanOptions.scanOptions().match("C").build()匹配获取键位map1的键值对,不能模糊匹配。
* @param key
* @param options
* @return
*/
public Cursor<ZSetOperations.TypedTuple<Object>> zScan(String key, ScanOptions options) {
return redisTemplate.opsForZSet().scan(key, options);
}
// ===============================HyperLogLog相关操作=================================
/**
* 将任意数量的元素添加到指定的 HyperLogLog 里面。
* 时间复杂度: 每添加一个元素的复杂度为 O(1) 。
* 如果 HyperLogLog 估计的近似基数(approximated cardinality)在命令执行之后出现了变化, 那么命令返回1, 否则返回0 。
* 如果命令执行时给定的键不存在, 那么程序将先创建一个空的 HyperLogLog 结构, 然后再执行命令。
* @param key
* @param value
* @return
*/
public long pfAdd(String key, String value) {
return redisTemplate.opsForHyperLogLog().add(key, value);
}
/**
* 返回给定 HyperLogLog 的基数估算值。
* PFCOUNT作用于单个key的时候,返回该key的基数。
* PFCOUNT命令作用于多个key时,返回并集的近似数。
* @param key
* @return
*/
public long pfCount(String key) {
return redisTemplate.opsForHyperLogLog().size(key);
}
public void pfRemove(String key) {
redisTemplate.opsForHyperLogLog().delete(key);
}
/**
* 将多个 HyperLogLog 合并为一个 HyperLogLog
* 将多个HyperLogLog合并为一个HyperLogLog。
* 合并后的HyperLogLog的基数接近于全部输入的可见集合的并集。合并得出的hyperLogLog会被存储到destkey中去。
*
* 返回值:成功返回ok。
* @param key1
* @param key2
*/
public void pfMerge(String key1, String key2) {
redisTemplate.opsForHyperLogLog().union(key1, key2);
}
/**
* 尝试获取锁,重试5次,5次仍然获取不到,直接返回失败
* @param lockKey
* @return
*/
public boolean tryLock(String lockKey) {
boolean lock = lock(lockKey);
if (lock) {
return true;
} else {
// 设置失败次数计数器, 当到达5次时, 返回失败
int failCount = 1;
while(failCount <= 5){
// 等待100ms重试
try {
Thread.sleep(100L);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (lock(lockKey)){
return true;
}else{
failCount ++;
}
}
return false;
}
}
/**
* 最终加强分布式锁
*
* @param lockKey key值
* @return 是否获取到
*/
public boolean lock(String lockKey){
if (StringUtils.isEmpty(lockKey)) {
return false;
}
// 利用lambda表达式
return (Boolean) redisTemplate.execute((RedisCallback) connection -> {
long expireAt = System.currentTimeMillis() + LOCK_EXPIRE + 1;
Boolean acquire = connection.setNX(lockKey.getBytes(), String.valueOf(expireAt).getBytes());
if (acquire) {
return true;
} else {
byte[] value = connection.get(lockKey.getBytes());
if (Objects.nonNull(value) && value.length > 0) {
long expireTime = Long.parseLong(new String(value));
// 如果锁已经过期
if (expireTime < System.currentTimeMillis()) {
// 重新加锁,防止死锁
byte[] oldValue = connection.getSet(lockKey.getBytes(), String.valueOf(System.currentTimeMillis() + LOCK_EXPIRE + 1).getBytes());
return Long.parseLong(new String(oldValue)) < System.currentTimeMillis();
}
}
}
return false;
});
}
/**
* 释放锁
*
* @param lockKey 锁名称
*/
public void unLock(String lockKey) {
redisTemplate.delete(lockKey);
}
}
注意:
- keys 的操作会导致数据库暂时被锁住,其他的请求都会被堵塞;业务量大的时候会出问题
- 当需要扫描key,匹配出自己需要的key时,可以使用 scan 命令
二、Redis 实现分布式锁
Redis分布式锁命令
setnx:
当且仅当 key 不存在。若给定的 key 已经存在,则 setnx不做任何动作。setnx 是『set if not exists』(如果不存在,则 set)的简写,setnx 具有原子性。
getset:
先 get 旧值,后set 新值,并返回 key 的旧值(old value),具有原子性。当 key 存在但不是字符串类型时,返回一个错误;当key 不存在的时候,返回nil ,在Java里就是 null。
expire:设置 key 的有效期
del:删除 key
与时间戳的结合
- 分布式锁的值是按系统当前时间 System.currentTimeMillis()+Key 有效期组成
GETSET可以和INCR一起使用实现支持重置的计数功能。举个例子:每当有事件发生的时候,一段程序都会调用INCR给key mycounter加1,但是有时我们需要获取计数器的值,并且自动将其重置为0。这可以通过GETSET mycounter “0”来实现。
setnx和getset这两个命令在 java 中对应为 setIfAbsent 和 getAndSet
分布式锁的实现1
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.util.concurrent.TimeUnit;
/**
* @author: huangyibo
* @Date: 2022/5/11 11:17
* @Description:
*/
@Component
@Slf4j
public class RedisLock {
public static final String LOCK_PREFIX = "redis_lock";
public static final int LOCK_EXPIRE = 300; // ms
@Autowired
private StringRedisTemplate redisTemplate;
/**
* 加锁
* @param key 锁名称
* @param value 当前时间+超时时间 的时间戳
* @param timeout 过期时间 ms
* @return
*/
public boolean lock(String key, String value, Integer timeout) {
String lockKey = LOCK_PREFIX + key;
if(timeout == null || timeout < 0){
timeout = LOCK_EXPIRE;
}
//这个其实就是setnx命令,只不过在java这边稍有变化,返回的是boolean
if (redisTemplate.opsForValue().setIfAbsent(lockKey, value, timeout, TimeUnit.MILLISECONDS)) {
return true;
}
//避免死锁,且只让一个线程拿到锁
String currentValue = redisTemplate.opsForValue().get(lockKey);
//如果锁过期了
if (!StringUtils.isEmpty(currentValue) && Long.parseLong(currentValue) < System.currentTimeMillis()) {
//获取上一个锁的时间
String oldValues = redisTemplate.opsForValue().getAndSet(lockKey, value);
/*
* 只会让一个线程拿到锁
* 如果旧的value和currentValue相等,只会有一个线程达成条件,因为第二个线程拿到的oldValue已经和currentValue不一样了
*/
if (!StringUtils.isEmpty(oldValues) && oldValues.equals(currentValue)) {
return true;
}
}
return false;
}
/**
* 解锁
* @param key 锁名称
* @param value 当前时间 + 超时时间
*/
public void unlock(String key, String value) {
try {
String currentValue = redisTemplate.opsForValue().get(key);
if (!StringUtils.isEmpty(currentValue) && currentValue.equals(value)) {
redisTemplate.opsForValue().getOperations().delete(key);
}
} catch (Exception e) {
log.error("『redis分布式锁』解锁异常,key:{}, value:{},", key, value, e);
}
}
}
分布式锁的实现2
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.util.Objects;
/**
* @author: huangyibo
* @Date: 2022/5/11 11:37
* @Description:
*/
@Component
@Slf4j
public class RedisLockUtil {
//加锁失效时间,毫秒
public static final int LOCK_EXPIRE = 3000; // ms
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 尝试获取锁,重试5次,5次仍然获取不到,直接返回失败
* @param lockKey 锁key值
* @return
*/
public boolean tryLock(String lockKey) {
boolean lock = lock(lockKey);
if (lock) {
return true;
} else {
// 设置失败次数计数器, 当到达5次时, 返回失败
int failCount = 1;
while(failCount <= 5){
// 等待100ms重试
try {
Thread.sleep(100L);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (lock(lockKey)){
return true;
}else{
failCount ++;
}
}
return false;
}
}
/**
* 最终加强分布式锁
*
* @param lockKey 锁key值
* @return 是否获取到
*/
public boolean lock(String lockKey){
if (StringUtils.isEmpty(lockKey)) {
return false;
}
// 利用lambda表达式
return (Boolean) redisTemplate.execute((RedisCallback) connection -> {
long expireAt = System.currentTimeMillis() + LOCK_EXPIRE + 1;
Boolean acquire = connection.setNX(lockKey.getBytes(), String.valueOf(expireAt).getBytes());
if (acquire) {
return true;
} else {
byte[] value = connection.get(lockKey.getBytes());
if (Objects.nonNull(value) && value.length > 0) {
long expireTime = Long.parseLong(new String(value));
// 如果锁已经过期
if (expireTime < System.currentTimeMillis()) {
// 重新加锁,防止死锁
byte[] oldValue = connection.getSet(lockKey.getBytes(), String.valueOf(System.currentTimeMillis() + LOCK_EXPIRE + 1).getBytes());
return Long.parseLong(new String(oldValue)) < System.currentTimeMillis();
}
}
}
return false;
});
}
/**
* 释放锁
*
* @param lockKey 锁key值
*/
public void unLock(String lockKey) {
redisTemplate.delete(lockKey);
}
}
redis官方推荐使用Redisson版分布式锁,高性能分布式锁。
Redisson的使用方式十分简单,详见官方文档:https://github.com/redisson/redisson/wiki/2.-%E9%85%8D%E7%BD%AE%E6%96%B9%E6%B3%95
Redisson分布式锁使用:https://www.jianshu.com/p/3bf09a41bb3c
参考:
https://cloud.tencent.com/developer/article/1863346
https://www.cnblogs.com/mzdljgz/p/14258419.html
https://www.cnblogs.com/zhzhlong/p/11434284.html
https://blog.csdn.net/weixin_45216439/article/details/91390743
https://segmentfault.com/a/1190000017057950
https://www.cnblogs.com/webwangbao/p/9247318.html
https://www.cnblogs.com/kiko2014551511/p/15411048.html