当前位置 : 主页 > 编程语言 > java >

SpringBoot——整合Redis

来源:互联网 收集:自由互联 发布时间:2023-02-04
简介 https://spring.io/projects/spring-data-redis 我们知道常用的Redis客户端 https://redis.io/clients#java 在 spring-boot-starter-data-redis 项目 2.X 版本中 ,默认使用 Lettuce 作为 Java Redis 工具库(之前为Jedis

简介

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

https://www.cnblogs.com/ZenoLiang/p/11209980.html

https://www.cnblogs.com/ZenoLiang/p/10958158.html

上一篇:SpringBoot——web 应用开发之 WebSocket
下一篇:没有了
网友评论