Springboot2.2.9整合Redis + ehcache2缓存(lettuce、RedisLockRegistry)
一、先记录下一些遇到的问题:
1、使用springboot 2.1.16版本时,编译警告:lettuce-core:jar:5.1.8.RELEASE is invalid
解决方法:
升级springboot版本。2.1.16版本默认引用了lettuce-core:jar:5.1.8.RELEASE版本,但是从警告可知,该版本已不可用。升级springboot到2.2.9版本,编译通过,查看引用lettuce-core版本为5.2.2,问题解决。
2、启动报错:NoClassDefFoundError: org/apache/commons/pool2/impl/GenericObjectPoolConfig
解决方法:
网上查找原因为:在springboot2.x以后官方默认使用了Redis的lettuce客户端来代替jedis客户端,替换的原因是Lettuce的底层是基于Netty实现的,性能更加出色。ettuce在底层的线程池管理中依赖了commons-pool2,如果不引入这个包就会直接报错,所以引入:commons-pool2
<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> <version>2.8.0</version> </dependency>
3、执行时报错:cannot deserialize from Object value (no delegate- or property-based Creator)
解决方法:
主要是序列化问题,在异常中找到错误的bean,加上无参构造器即可!
***************正文开始***************
二、依赖如下:
<dependencies> <!-- springboot redis引入 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!-- ehcache缓存引入 --> <dependency> <groupId>net.sf.ehcache</groupId> <artifactId>ehcache</artifactId> <version>2.10.6</version> </dependency> <!-- 分布式锁引入 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-integration</artifactId> </dependency> <dependency> <groupId>org.springframework.integration</groupId> <artifactId>spring-integration-redis</artifactId> </dependency> <!-- lettuce pool连接池 --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> <version>2.8.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!-- jackson引入 --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.11.0</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.10</version> </dependency> </dependencies>
三、springboot2.x的redis配置信息application.properties:
#######################################################
# 配置文件redis服务器器配置 #
#######################################################
# Redis数据库索引(默认为0)
spring.redis.database=0
# Redis集群
spring.redis.cluster.nodes=xxx.xxx.xxx.91:6379,xxx.xxx.xxx.92:6379
# Redis服务器地址
spring.redis.host=xxx.xxx.xxx.91
# Redis服务器连接端口
spring.redis.port=6379
# Redis服务器连接密码(默认为空)
spring.redis.password=
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.lettuce.pool.max-active=-1
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.lettuce.pool.max-wait=-1ms
# 连接池中的最大空闲连接
spring.redis.lettuce.pool.max-idle=500
# 连接池中的最小空闲连接
spring.redis.lettuce.pool.min-idle=5
# 连接超时时间(毫秒)
spring.redis.timeout=60000ms
#######################################################
# ehcache缓存配置 #
#######################################################
# 需要设置type,否则ehcache.xml不生效
spring.cache.type=ehcache
spring.cache.ehcache.config=ehcache.xml
四、ehcache.xml配置
<ehcache> <!-- 磁盘存储:将缓存中暂时不使用的对象,转移到硬盘,类似于Windows系统的虚拟内存 path:指定在硬盘上存储对象的路径 path可以配置的目录有: user.home(用户的家目录) user.dir(用户当前的工作目录) java.io.tmpdir(默认的临时目录) ehcache.disk.store.dir(ehcache的配置目录) 绝对路径(如:d:\\ehcache) 查看路径方法:String tmpDir = System.getProperty("java.io.tmpdir"); --> <diskStore path="/home/similarity-processor/ehcacheTemporaryCache"/> <!-- defaultCache:默认的缓存配置信息,如果不加特殊说明,则所有对象按照此配置项处理 name:缓存名称。 maxElementsInMemory:缓存最大个数。 eternal:对象是否永久有效,一但设置了,timeout将不起作用。 timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。 timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。 overflowToDisk:当内存中对象数量达到maxElementsInMemory时,Ehcache将会对象写到磁盘中。 diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。 maxElementsOnDisk:硬盘最大缓存个数。 diskPersistent:是否缓存虚拟机重启期数据,默认为false diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。 memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。 默认策略是 LRU(最近最少使用),FIFO(先进先出),LFU(较少使用)。 clearOnFlush:内存数量最大时是否清除。 --> <defaultCache maxElementsInMemory="100000" eternal="true" timeToIdleSeconds="259200" timeToLiveSeconds="259200" overflowToDisk="true" /> <cache name="ehcacheTemporaryCache" maxElementsInMemory="100000" eternal="true" timeToIdleSeconds="259200" timeToLiveSeconds="259200" overflowToDisk="true" /> </ehcache>
五、RedisTemplate配置,redis相关bean配置:
import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.cache.annotation.CachingConfigurerSupport; 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.*; import org.springframework.data.redis.serializer.*; import org.springframework.integration.redis.util.RedisLockRegistry; /** * <redis配置> * * @author zxx * @time 2020-07-24 14:33 * @since 1.0.0 */ @Configuration public class RedisConfig extends CachingConfigurerSupport { /** * redisTemplate自定义配置 */ @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(factory); // 使用Jackson2JsonRedisSerialize 替换默认序列化 ObjectMapper objectMapper = new ObjectMapper(); objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); objectMapper.activateDefaultTyping(objectMapper.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.WRAPPER_ARRAY); // jackson序列化方式 Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); jackson2JsonRedisSerializer.setObjectMapper(objectMapper); RedisSerializer<String> redisSerializer = new StringRedisSerializer(); // 默认jdk序列化方式 // JdkSerializationRedisSerializer jdkSerializationRedisSerializer = new JdkSerializationRedisSerializer(); // 设置value的序列化规则和 key的序列化规则 template.setKeySerializer(redisSerializer); template.setValueSerializer(jackson2JsonRedisSerializer); template.setHashKeySerializer(redisSerializer); template.setHashValueSerializer(jackson2JsonRedisSerializer); template.afterPropertiesSet(); return template; } /** * redis分布式锁设计 */ @Bean public RedisLockRegistry redisLockRegistry(RedisConnectionFactory redisConnectionFactory) { return new RedisLockRegistry(redisConnectionFactory, "Poseidon:similarity"); } /** * 对redis字符串类型数据操作 */ @Bean public ValueOperations<String, Object> valueOperations(RedisTemplate<String, Object> redisTemplate) { return redisTemplate.opsForValue(); } /** * 对hash类型的数据操作 */ @Bean public HashOperations<String, String, Object> hashOperations(RedisTemplate<String, Object> redisTemplate) { return redisTemplate.opsForHash(); } /** * 对链表类型的数据操作 */ @Bean public ListOperations<String, Object> listOperations(RedisTemplate<String, Object> redisTemplate) { return redisTemplate.opsForList(); } /** * 对无序集合类型的数据操作 */ @Bean public SetOperations<String, Object> setOperations(RedisTemplate<String, Object> redisTemplate) { return redisTemplate.opsForSet(); } /** * 对有序集合类型的数据操作 */ @Bean public ZSetOperations<String, Object> zSetOperations(RedisTemplate<String, Object> redisTemplate) { return redisTemplate.opsForZSet(); } }
说明:
- spring-redis中使用了RedisTemplate来进行redis的操作,通过泛型的K和V设置键值对的对象类型。这里使用了string作为key的对象类型,值为Object。
- 对spring-redis对redis的五种数据类型也有支持
- HashOperations:对hash类型的数据操作
- ValueOperations:对redis字符串类型数据操作
- ListOperations:对链表类型的数据操作
- SetOperations:对无序集合类型的数据操作
- ZSetOperations:对有序集合类型的数据操作
六、Redis操作工具类:
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataAccessException; import org.springframework.data.redis.connection.DataType; import org.springframework.data.redis.connection.RedisConnection; import org.springframework.data.redis.connection.RedisStringCommands; import org.springframework.data.redis.core.*; import org.springframework.data.redis.core.types.Expiration; import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.stereotype.Component; import java.util.*; import java.util.concurrent.TimeUnit; /** * <Redis工具> * * @author zxx * @time 2020-07-24 16:00 * @since 1.0.0 */ @Component public class RedisUtil { private static Logger logger = LoggerFactory.getLogger(RedisUtil.class); @Autowired private RedisTemplate<String, Object> redisTemplate; /** -------------------key相关操作--------------------- */ /** * 删除key * * @param key */ public void delete(String key) { redisTemplate.delete(key); } /** * 批量删除key * * @param keys */ public void delete(Collection<String> keys) { redisTemplate.delete(keys); } /** * 序列化key * * @param key * @return */ public byte[] dump(String key) { return redisTemplate.dump(key); } /** * 是否存在key * * @param key * @return */ public Boolean hasKey(String key) { return redisTemplate.hasKey(key); } /** * 设置过期时间 * * @param key * @param timeout * @param unit * @return */ public Boolean expire(String key, long timeout, TimeUnit unit) { return redisTemplate.expire(key, timeout, unit); } /** * 设置过期时间 * * @param key * @param date * @return */ public Boolean expireAt(String key, Date date) { return redisTemplate.expireAt(key, date); } /** * 查找匹配的key * * @param pattern * @return */ public Set<String> keys(String pattern) { return redisTemplate.keys(pattern); } /** * 将当前数据库的 key 移动到给定的数据库 db 当中 * * @param key * @param dbIndex * @return */ public Boolean move(String key, int dbIndex) { return redisTemplate.move(key, dbIndex); } /** * 移除 key 的过期时间,key 将持久保持 * * @param key * @return */ public Boolean persist(String key) { return redisTemplate.persist(key); } /** * 返回 key 的剩余的过期时间 * * @param key * @param unit * @return */ public Long getExpire(String key, TimeUnit unit) { return redisTemplate.getExpire(key, unit); } /** * 返回 key 的剩余的过期时间 * * @param key * @return */ public Long getExpire(String key) { return redisTemplate.getExpire(key); } /** * 从当前数据库中随机返回一个 key * * @return */ public String randomKey() { return redisTemplate.randomKey(); } /** * 修改 key 的名称 * * @param oldKey * @param newKey */ public void rename(String oldKey, String newKey) { redisTemplate.rename(oldKey, newKey); } /** * 仅当 newkey 不存在时,将 oldKey 改名为 newkey * * @param oldKey * @param newKey * @return */ public Boolean renameIfAbsent(String oldKey, String newKey) { return redisTemplate.renameIfAbsent(oldKey, newKey); } /** * 返回 key 所储存的值的类型 * * @param key * @return */ public DataType type(String key) { return redisTemplate.type(key); } /** -------------------string相关操作--------------------- */ /** * 设置指定 key 的值 * * @param key * @param value */ public void set(String key, Object value) { redisTemplate.opsForValue().set(key, value); } /** * 获取指定 key 的值 * * @param key * @return */ public Object get(String key) { return redisTemplate.opsForValue().get(key); } /** * 返回 key 中字符串值的子字符 * * @param key * @param start * @param end * @return */ public String getRange(String key, long start, long end) { return redisTemplate.opsForValue().get(key, start, end); } /** * 将给定 key 的值设为 value ,并返回 key 的旧值(old value) * * @param key * @param value * @return */ public Object getAndSet(String key, Object value) { return redisTemplate.opsForValue().getAndSet(key, value); } /** * 对 key 所储存的字符串值,获取指定偏移量上的位(bit) * * @param key * @param offset * @return */ public Boolean getBit(String key, long offset) { return redisTemplate.opsForValue().getBit(key, offset); } /** * 批量获取 * * @param keys * @return */ public List<Object> multiGet(Collection<String> keys) { return redisTemplate.opsForValue().multiGet(keys); } /** * 设置ASCII码, 字符串'a'的ASCII码是97, 转为二进制是'01100001', 此方法是将二进制第offset位值变为value * * @param key 位置 * @param value 值,true为1, false为0 * @return */ public boolean setBit(String key, long offset, boolean value) { return redisTemplate.opsForValue().setBit(key, offset, value); } /** * 将值 value 关联到 key ,并将 key 的过期时间设为 timeout * * @param key * @param value * @param timeout 过期时间 * @param unit 时间单位, 天:TimeUnit.DAYS 小时:TimeUnit.HOURS 分钟:TimeUnit.MINUTES * 秒:TimeUnit.SECONDS 毫秒:TimeUnit.MILLISECONDS */ public void setEx(String key, Object value, long timeout, TimeUnit unit) { redisTemplate.opsForValue().set(key, value, timeout, unit); } /** * 只有在 key 不存在时设置 key 的值 * * @param key * @param value * @return 之前已经存在返回false, 不存在返回true */ public boolean setIfAbsent(String key, String value) { return redisTemplate.opsForValue().setIfAbsent(key, value); } /** * 用 value 参数覆写给定 key 所储存的字符串值,从偏移量 offset 开始 * * @param key * @param value * @param offset 从指定位置开始覆写 */ public void setRange(String key, String value, long offset) { redisTemplate.opsForValue().set(key, value, offset); } /** * 获取字符串的长度 * * @param key * @return */ public Long size(String key) { return redisTemplate.opsForValue().size(key); } /** * 批量添加 * * @param maps */ public void multiSet(Map<String, String> maps) { redisTemplate.opsForValue().multiSet(maps); } /** * 批量添加并且设置失效时间 */ public void multiSetAndExpire(Map<String, String> map, long expiredTime, TimeUnit timeUnit) { RedisSerializer<String> serializer = redisTemplate.getStringSerializer(); redisTemplate.executePipelined(new RedisCallback<String>() { @Override public String doInRedis(RedisConnection connection) throws DataAccessException { map.forEach((key, value) -> { connection.set(serializer.serialize(key), serializer.serialize(value), // Expiration.seconds(seconds), Expiration.from(expiredTime, timeUnit), RedisStringCommands.SetOption.UPSERT); }); return null; } }, serializer); } /** * 同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在 * * @param maps * @return 之前已经存在返回false, 不存在返回true */ public boolean multiSetIfAbsent(Map<String, String> maps) { return redisTemplate.opsForValue().multiSetIfAbsent(maps); } /** * 增加(自增长), 负数则为自减 * * @param key * @return */ public Long incrBy(String key, long increment) { return redisTemplate.opsForValue().increment(key, increment); } /** * @param key * @return */ public Double incrByFloat(String key, double increment) { return redisTemplate.opsForValue().increment(key, increment); } /** * 追加到末尾 * * @param key * @param value * @return */ public Integer append(String key, String value) { return redisTemplate.opsForValue().append(key, value); } /** -------------------hash相关操作------------------------- */ /** * 获取存储在哈希表中指定字段的值 * * @param key * @param field * @return */ public Object hGet(String key, String field) { return redisTemplate.opsForHash().get(key, field); } /** * 获取所有给定字段的值 * * @param key * @return */ public Map<Object, Object> hGetAll(String key) { return redisTemplate.opsForHash().entries(key); } /** * 获取所有给定字段的值 * * @param key * @param fields * @return */ public List<Object> hMultiGet(String key, Collection<Object> fields) { return redisTemplate.opsForHash().multiGet(key, fields); } public void hPut(String key, String hashKey, String value) { redisTemplate.opsForHash().put(key, hashKey, value); } public void hPutAll(String key, Map<String, String> maps) { redisTemplate.opsForHash().putAll(key, maps); } /** * 仅当hashKey不存在时才设置 * * @param key * @param hashKey * @param value * @return */ public Boolean hPutIfAbsent(String key, String hashKey, String value) { return redisTemplate.opsForHash().putIfAbsent(key, hashKey, value); } /** * 删除一个或多个哈希表字段 * * @param key * @param fields * @return */ public Long hDelete(String key, Object... fields) { return redisTemplate.opsForHash().delete(key, fields); } /** * 查看哈希表 key 中,指定的字段是否存在 * * @param key * @param field * @return */ public boolean hExists(String key, String field) { return redisTemplate.opsForHash().hasKey(key, field); } /** * 为哈希表 key 中的指定字段的整数值加上增量 increment * * @param key * @param field * @param increment * @return */ public Long hIncrBy(String key, Object field, long increment) { return redisTemplate.opsForHash().increment(key, field, increment); } /** * 为哈希表 key 中的指定字段的整数值加上增量 increment * * @param key * @param field * @param delta * @return */ public Double hIncrByFloat(String key, Object field, double delta) { return redisTemplate.opsForHash().increment(key, field, delta); } /** * 获取所有哈希表中的字段 * * @param key * @return */ public Set<Object> hKeys(String key) { return redisTemplate.opsForHash().keys(key); } /** * 获取哈希表中字段的数量 * * @param key * @return */ public Long hSize(String key) { return redisTemplate.opsForHash().size(key); } /** * 获取哈希表中所有值 * * @param key * @return */ public List<Object> hValues(String key) { return redisTemplate.opsForHash().values(key); } /** * 迭代哈希表中的键值对 * * @param key * @param options * @return */ public Cursor<Map.Entry<Object, Object>> hScan(String key, ScanOptions options) { return redisTemplate.opsForHash().scan(key, options); } /** ------------------------list相关操作---------------------------- */ /** * 通过索引获取列表中的元素 * * @param key * @param index * @return */ public Object lIndex(String key, long index) { return redisTemplate.opsForList().index(key, index); } /** * 获取列表指定范围内的元素 * * @param key * @param start 开始位置, 0是开始位置 * @param end 结束位置, -1返回所有 * @return */ public List<Object> lRange(String key, long start, long end) { return redisTemplate.opsForList().range(key, start, end); } /** * 存储在list头部 * * @param key * @param value * @return */ public Long lLeftPush(String key, String value) { return redisTemplate.opsForList().leftPush(key, value); } /** * @param key * @param value * @return */ public Long lLeftPushAll(String key, String... value) { return redisTemplate.opsForList().leftPushAll(key, value); } /** * @param key * @param value * @return */ public Long lLeftPushAll(String key, Collection<String> value) { return redisTemplate.opsForList().leftPushAll(key, value); } /** * 当list存在的时候才加入 * * @param key * @param value * @return */ public Long lLeftPushIfPresent(String key, String value) { return redisTemplate.opsForList().leftPushIfPresent(key, value); } /** * 如果pivot存在,再pivot前面添加 * * @param key * @param pivot * @param value * @return */ public Long lLeftPush(String key, String pivot, String value) { return redisTemplate.opsForList().leftPush(key, pivot, value); } /** * @param key * @param value * @return */ public Long lRightPush(String key, String value) { return redisTemplate.opsForList().rightPush(key, value); } /** * @param key * @param value * @return */ public Long lRightPushAll(String key, String... value) { return redisTemplate.opsForList().rightPushAll(key, value); } /** * @param key * @param value * @return */ public Long lRightPushAll(String key, Collection<String> value) { return redisTemplate.opsForList().rightPushAll(key, value); } /** * 为已存在的列表添加值 * * @param key * @param value * @return */ public Long lRightPushIfPresent(String key, String value) { return redisTemplate.opsForList().rightPushIfPresent(key, value); } /** * 在pivot元素的右边添加值 * * @param key * @param pivot * @param value * @return */ public Long lRightPush(String key, String pivot, String value) { return redisTemplate.opsForList().rightPush(key, pivot, value); } /** * 通过索引设置列表元素的值 * * @param key * @param index 位置 * @param value */ public void lSet(String key, long index, String value) { redisTemplate.opsForList().set(key, index, value); } /** * 移出并获取列表的第一个元素 * * @param key * @return 删除的元素 */ public Object lLeftPop(String key) { return redisTemplate.opsForList().leftPop(key); } /** * 移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止 * * @param key * @param timeout 等待时间 * @param unit 时间单位 * @return */ public Object lBLeftPop(String key, long timeout, TimeUnit unit) { return redisTemplate.opsForList().leftPop(key, timeout, unit); } /** * 移除并获取列表最后一个元素 * * @param key * @return 删除的元素 */ public Object lRightPop(String key) { return redisTemplate.opsForList().rightPop(key); } /** * 移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止 * * @param key * @param timeout 等待时间 * @param unit 时间单位 * @return */ public Object lBRightPop(String key, long timeout, TimeUnit unit) { return redisTemplate.opsForList().rightPop(key, timeout, unit); } /** * 移除列表的最后一个元素,并将该元素添加到另一个列表并返回 * * @param sourceKey * @param destinationKey * @return */ public Object lRightPopAndLeftPush(String sourceKey, String destinationKey) { return redisTemplate.opsForList().rightPopAndLeftPush(sourceKey, destinationKey); } /** * 从列表中弹出一个值,将弹出的元素插入到另外一个列表中并返回它; 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止 * * @param sourceKey * @param destinationKey * @param timeout * @param unit * @return */ public Object lBRightPopAndLeftPush(String sourceKey, String destinationKey, long timeout, TimeUnit unit) { return redisTemplate.opsForList().rightPopAndLeftPush(sourceKey, destinationKey, timeout, unit); } /** * 删除集合中值等于value得元素 * * @param key * @param index index=0, 删除所有值等于value的元素; index>0, 从头部开始删除第一个值等于value的元素; * index<0, 从尾部开始删除第一个值等于value的元素; * @param value * @return */ public Long lRemove(String key, long index, String value) { return redisTemplate.opsForList().remove(key, index, value); } /** * 裁剪list * * @param key * @param start * @param end */ public void lTrim(String key, long start, long end) { redisTemplate.opsForList().trim(key, start, end); } /** * 获取列表长度 * * @param key * @return */ public Long lLen(String key) { return redisTemplate.opsForList().size(key); } /** --------------------set相关操作-------------------------- */ /** * set添加元素 * * @param key * @param values * @return */ public Long sAdd(String key, String... values) { return redisTemplate.opsForSet().add(key, values); } /** * set移除元素 * * @param key * @param values * @return */ public Long sRemove(String key, Object... values) { return redisTemplate.opsForSet().remove(key, values); } /** * 移除并返回集合的一个随机元素 * * @param key * @return */ public Object sPop(String key) { return redisTemplate.opsForSet().pop(key); } /** * 将元素value从一个集合移到另一个集合 * * @param key * @param value * @param destKey * @return */ public Boolean sMove(String key, String value, String destKey) { return redisTemplate.opsForSet().move(key, value, destKey); } /** * 获取集合的大小 * * @param key * @return */ public Long sSize(String key) { return redisTemplate.opsForSet().size(key); } /** * 判断集合是否包含value * * @param key * @param value * @return */ public Boolean sIsMember(String key, Object value) { return redisTemplate.opsForSet().isMember(key, value); } /** * 获取两个集合的交集 * * @param key * @param otherKey * @return */ public Set<Object> sIntersect(String key, String otherKey) { return redisTemplate.opsForSet().intersect(key, otherKey); } /** * 获取key集合与多个集合的交集 * * @param key * @param otherKeys * @return */ public Set<Object> sIntersect(String key, Collection<String> otherKeys) { return redisTemplate.opsForSet().intersect(key, otherKeys); } /** * key集合与otherKey集合的交集存储到destKey集合中 * * @param key * @param otherKey * @param destKey * @return */ public Long sIntersectAndStore(String key, String otherKey, String destKey) { return redisTemplate.opsForSet().intersectAndStore(key, otherKey, destKey); } /** * key集合与多个集合的交集存储到destKey集合中 * * @param key * @param otherKeys * @param destKey * @return */ public Long sIntersectAndStore(String key, Collection<String> otherKeys, String destKey) { return redisTemplate.opsForSet().intersectAndStore(key, otherKeys, destKey); } /** * 获取两个集合的并集 * * @param key * @param otherKeys * @return */ public Set<Object> sUnion(String key, String otherKeys) { return redisTemplate.opsForSet().union(key, otherKeys); } /** * 获取key集合与多个集合的并集 * * @param key * @param otherKeys * @return */ public Set<Object> sUnion(String key, Collection<String> otherKeys) { return redisTemplate.opsForSet().union(key, otherKeys); } /** * key集合与otherKey集合的并集存储到destKey中 * * @param key * @param otherKey * @param destKey * @return */ public Long sUnionAndStore(String key, String otherKey, String destKey) { return redisTemplate.opsForSet().unionAndStore(key, otherKey, destKey); } /** * key集合与多个集合的并集存储到destKey中 * * @param key * @param otherKeys * @param destKey * @return */ public Long sUnionAndStore(String key, Collection<String> otherKeys, String destKey) { return redisTemplate.opsForSet().unionAndStore(key, otherKeys, destKey); } /** * 获取两个集合的差集 * * @param key * @param otherKey * @return */ public Set<Object> sDifference(String key, String otherKey) { return redisTemplate.opsForSet().difference(key, otherKey); } /** * 获取key集合与多个集合的差集 * * @param key * @param otherKeys * @return */ public Set<Object> sDifference(String key, Collection<String> otherKeys) { return redisTemplate.opsForSet().difference(key, otherKeys); } /** * key集合与otherKey集合的差集存储到destKey中 * * @param key * @param otherKey * @param destKey * @return */ public Long sDifference(String key, String otherKey, String destKey) { return redisTemplate.opsForSet().differenceAndStore(key, otherKey, destKey); } /** * key集合与多个集合的差集存储到destKey中 * * @param key * @param otherKeys * @param destKey * @return */ public Long sDifference(String key, Collection<String> otherKeys, String destKey) { return redisTemplate.opsForSet().differenceAndStore(key, otherKeys, destKey); } /** * 获取集合所有元素 * * @param key * @return */ public Set<Object> sMembers(String key) { return redisTemplate.opsForSet().members(key); } /** * 随机获取集合中的一个元素 * * @param key * @return */ public Object sRandomMember(String key) { return redisTemplate.opsForSet().randomMember(key); } /** * 随机获取集合中count个元素 * * @param key * @param count * @return */ public List<Object> sRandomMembers(String key, long count) { return redisTemplate.opsForSet().randomMembers(key, count); } /** * 随机获取集合中count个元素并且去除重复的 * * @param key * @param count * @return */ public Set<Object> sDistinctRandomMembers(String key, long count) { return redisTemplate.opsForSet().distinctRandomMembers(key, count); } /** * @param key * @param options * @return */ public Cursor<Object> sScan(String key, ScanOptions options) { return redisTemplate.opsForSet().scan(key, options); } /**------------------zSet相关操作--------------------------------*/ /** * 添加元素,有序集合是按照元素的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<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 * @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); } /** * @param key * @param min * @param max * @param start * @param end * @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 * @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 * @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); } /** * @param key * @param min * @param max * @param start * @param end * @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 * @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); } /** * @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<ZSetOperations.TypedTuple<Object>> zScan(String key, ScanOptions options) { return redisTemplate.opsForZSet().scan(key, options); } }
七、Redis Key生成工具类:
/** * <redis键的生成工具类> * * @author zxx * @since 1.0.0 */ public class RedisKeyUtil { /** * redis的key,具体查看README.md * 形式为: * 表名(固定):一级索引(固定):二级索引(自定义,结尾对应在redis存储形式):键 -> 值 * * @param index2 索引2 * @param key 键 * @return */ public static String getKey(String index2, String key) { return "例如项目名:例如子项目名:" + index2 + ":" + key; } }
说明:
- 将key按照特定格式生成,有助于后期维护及相关key操作
- 尽量在存储之前设计好数据的存储格式,做好记录及注释,方便后续人员理解与操作
八、ehcache缓存service层(使用示例)
import com.abc5w.poseidon.similarityprocessor.utils.RedisUtil; import org.springframework.cache.annotation.*; import org.springframework.data.redis.core.ZSetOperations; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.util.*; import java.util.concurrent.ConcurrentHashMap; /** * <Ehcache服务类,主要包含ehcache相关的功能> * * @author zxx * @time 2020-08-31 16:00 * @since 1.0.0 */ @Service @EnableCaching public class TemporaryCacheService { @Resource private RedisUtil redisUtil; /** * 先查询缓存名ehcacheTemporaryCache是否有数据,如果有则返回,如果没有则调用方法,将结果返回的也存入缓存中 * 返回根据时间戳从小到大排序,同时带上了时间戳 * @param key hash2BasketIdKey的zset集合的key、basketIdKey的zset集合的key * @return ehcacheTemporaryCacheModel */ @Cacheable(cacheNames = "ehcacheTemporaryCache", key = "#key") public ConcurrentHashMap<String, Object> getAllHash2BasketIdKeysFromEhcache(String key) throws Exception { ConcurrentHashMap<String, Object> retMap = new ConcurrentHashMap<>(); ConcurrentHashMap<String, Double> keyMap = new ConcurrentHashMap<>(); double theLatestTimestamp = 0d; Set<ZSetOperations.TypedTuple<Object>> tmpSet = redisUtil.zRangeWithScores(key, 0, -1); for (ZSetOperations.TypedTuple<Object> objectTypedTuple : tmpSet) { String tmpKey = Objects.requireNonNull(objectTypedTuple.getValue()).toString(); double tmpValue = Objects.requireNonNull(objectTypedTuple.getScore()); keyMap.put(tmpKey, tmpValue); theLatestTimestamp = Math.max(theLatestTimestamp, tmpValue); } retMap.put("keyMap", keyMap); retMap.put("theLatestTimestamp", theLatestTimestamp); return retMap; } /** * 缓存名为ehcacheTemporaryCache,不管缓存是否有数据,都会更新 * 增量返回ehcache中不存在但redis中存在的数据,加上之前已经从缓存中的到的数据,缓存到ehcache中 * @param key hash2BasketIdKey的zset集合的key、basketIdKey的zset集合的key * @param min 时间戳排序,起点时间戳 * @param max 时间戳排序,末点时间戳 * @param map 从缓存中已经得到的数据 * @return ehcacheHash2BasketIdKeyList */ @CachePut(cacheNames = "ehcacheTemporaryCache", key = "#key") public ConcurrentHashMap<String, Object> incrementCacheFromRedis( String key, double min, double max, ConcurrentHashMap<String, Object> map) throws Exception { // 当前缓存中已存在的数据 ConcurrentHashMap<String, Double> keyMap = (ConcurrentHashMap<String, Double>) map.get("keyMap"); double theLatestTimestamp = Double.parseDouble(map.get("theLatestTimestamp").toString()); Set<ZSetOperations.TypedTuple<Object>> tmpSet = redisUtil.zRangeByScoreWithScores(key, min, max); for (ZSetOperations.TypedTuple<Object> objectTypedTuple : tmpSet) { String tmpKey = Objects.requireNonNull(objectTypedTuple.getValue()).toString(); double tmpValue = Objects.requireNonNull(objectTypedTuple.getScore()); keyMap.put(tmpKey, tmpValue); theLatestTimestamp = Math.max(theLatestTimestamp, tmpValue); } if (keyMap.isEmpty()) { map.clear(); } else { map.put("keyMap", keyMap); map.put("theLatestTimestamp", theLatestTimestamp); } return map; } // /** // * 数据库中的数据删除后,相关的缓存数据也要自动清除 // * 移除hash2BasketIdKeyZSet中过期的key // * @param hash2BasketIdKeyZSetRedisKey redis中存储所有hash2BasketIdKey的zset集合的key // * @param timestamp 过期末尾时间,即从0-timestamp为过期时间 // */ // @CacheEvict(cacheNames = "ehcacheTemporaryCache", key = "#hash2BasketIdKeyZSetRedisKey") // public void removeHash2BasketIdKeyZSetMembers(String hash2BasketIdKeyZSetRedisKey, Long timestamp) { // redisUtil.zRemoveRangeByScore(hash2BasketIdKeyZSetRedisKey, 0, timestamp); // } // /** // * 数据库中的数据删除后,相关的缓存数据也要自动清除 // * 移除basketIdKeyZSet中过期的key // * @param basketIdKeyZSetRedisKey redis中存储所有basketIdKey的zset集合的key // * @param timestamp 过期末尾时间,即从0-timestamp为过期时间 // */ // @CacheEvict(cacheNames = "ehcacheTemporaryCache", key = "#basketIdKeyZSetRedisKey") // public void removeBasketIdKeyZSetMembers(String basketIdKeyZSetRedisKey, Long timestamp) { // redisUtil.zRemoveRangeByScore(basketIdKeyZSetRedisKey, 0, timestamp); // } }
说明:
- 上述缓存的使用时按照个人需求进行了更改。只要了解@Cacheable、@CacheEvict、@CachePut等注解就可以自己定制缓存内容
- @Cacheable:先查询缓存名xxx是否有数据,如果有则返回,如果没有则调用方法,将结果返回的也存入缓存中
- @CachePut:缓存名为xxx,不管缓存是否有数据,都会更新
- @CacheEvict:数据库中的数据删除后,相关的缓存数据也要自动清除。此处删除将把key所对应的value在缓存中都会删除
九、redis具体使用方法:
只放上几个小例子,其它类似
//---------------------- set操作 ---------------------- // 封装获取所有hash2BasketIdKeySet的redis键格式 String hash2BasketIdKeySetRedisKey = RedisKeyUtil.getKey("hash2BasketIdKeySet", "allKeys"); Set<Object> hash2BasketIdKeySetRedisValue = redisUtil.sMembers(hash2BasketIdKeySetRedisKey); //---------------------- String操作(序列化set) ---------------------- String docHashRedisKey = RedisKeyUtil.getKey("hash2BasketIdSet", docHash); Set<String> hash2BasketIdSet = new HashSet<>(); hash2BasketIdSet.add(newBasketId); redisUtil.setEx(docHashRedisKey, hash2BasketIdSet, 3, TimeUnit.DAYS); String title2BasketIdSetRedisKey = RedisKeyUtil.getKey("title2BasketIdSet", docTitle); Set<String> title2BasketIdSetRedisValue = (Set<String>) redisUtil.get(title2BasketIdSetRedisKey);
import org.springframework.stereotype.Component;

浙公网安备 33010602011771号