不支持事务,且使用的是同一个redis对象,容易造成压力
目录
- 1. 引入pom
- 2. 设置yml
- 3. 配置文件
- 4.模板接口
- 4.1 公共方法
- 4.2 string
- 4.3 hash
- 4.4 list
- 4.5 set
- 4.6 zset
- 4.7 bitmaps 二进制站位
- 4.7.1 防止热key 分组分片
- 4.8 HyperLogLog 记录数据数量,而不记录数据本身
- 4.7 Geospatial 经纬度
- 5. 实现类
1. 引入pom
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
<version>2.5.0</version>
</dependency>
<!-- redis: 因为lettuce,依赖commons-pool2,所以引入该包即可。 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.8.0</version>
</dependency>
2. 设置yml
spring:
redis:
host: 127.0.0.1
port: 6666
password: 123
# redis 共 16 个 db库,从0开始
database: 0
# 连接redis超时时间
timeout: 3000
# 自定义过期时间
#expire: 300
lettuce:
pool:
max-active: 20 #可分配的最大连接数
max-wait: -1 #连接池无可用时,最大等待时间
max-idle: 10 #连接池 最大空闲
min-idle: 0 #连接池 最小空闲
3. 配置文件
RedisTemplate 是 <object,object> 的形式, 而我们大部分都需要<string,object>的存储方式;
StringRedisTemplate 是 <String,String> 的形式;
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 com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
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.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.Duration;
@Configuration
@EnableCaching //有缓存使用缓存
public class RedisConfig {
/**
* 生成 redisTemplate<String,Object>
* @param factory
* @return
*/
@Bean
@SuppressWarnings("all")
public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory factory){
RedisTemplate<String,Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
//Json序列化
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
//String序列化
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
//key采用string的序列化形式
template.setKeySerializer(stringRedisSerializer);
//hask的key也采用String的序列化形式
template.setHashKeySerializer(stringRedisSerializer);
//value的序列化形式采用jackson
template.setValueSerializer(jackson2JsonRedisSerializer);
//hash的value系列化形式采用jackson
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
/**
* 解决缓存转换异常,配置序列化,解决乱码
* @param factory
* @return
*/
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory){
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
//解决查询缓存转换异常问题
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL,JsonAutoDetect.Visibility.ANY);
om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
jackson2JsonRedisSerializer.setObjectMapper(om);
//配置序列化,解决乱码问题,过期时间600秒
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(600))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
.disableCachingNullValues();
RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
return cacheManager;
}
}
4.模板接口
4.1 公共方法
import org.springframework.data.geo.GeoResults;
import org.springframework.data.geo.Point;
import org.springframework.data.redis.connection.RedisCommands;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.core.ZSetOperations;
import java.util.List;
import java.util.Map;
import java.util.Set;
public interface RedisTemplateUtils{
/**
* 指定缓存失效时间
* @param key 键
* @param time 失效时间
* @return 是否设置成功
*/
Boolean expire(String key,long time);
/**
* 获取缓存失效时间
* @param key 键
* @return 缓存失效时间 (秒) 0代表永久有效
*/
Long getExpire(String key);
/**
* 判断是否存在
* @param key 键
* @return true 存在 false不存在
*/
Boolean hasKey(String key);
/**
* 删除缓存
* @param keys 键
*/
@SuppressWarnings("unchecked")
void del(String... keys);
}
4.2 string
/**
* 缓存获取
* @param key
* @return 值
*/
Object get(String key);
/**
* 转换成需要的类型
* @param key 键
* @param clazz 类型
* @param <T> 类型对象
* @return 值
*/
<T>T get(String key,Class<T> clazz);
/**
* 追加字符串
* @param key
* @param value
*/
void append(String key,String value);
/**
* 缓存放入
* @param key 键
* @param value 值
* @return 是否成功
*/
Boolean set(String key, Object value);
/**
* 缓存放入 并设置时间
* @param key 键
* @param value 值
* @param time (秒) time要大于0 如果time小于等于0 将设置无限期
* @return 是否成功
*/
Boolean set(String key,Object value,long time);
/**
* 批量添加
*
* isAtom: 原子操作
* true: 若某个key已经存在,则集体添加失败
* false: 若某个key已经存在则替换为新值,其他不存在的则新增
*
* @param map value 尽量进行json转换,方便处理
* @param isAtom 原子操作
* @return
*/
Boolean setOrUpdate(Map<String,Object> map, boolean isAtom);
/**
* 批量获取
* @param keys
* @return
*/
List<Object> getList(String... keys);
/**
* 如果key已存在就不执行,并返回false 不存在就执行 并返回true
* @param key 键
* @param value 值
* @return 是否成功
*/
Boolean setNx(String key,Object value);
/**
* 如果key已存在就不执行,并返回false 不存在就执行 并返回true
* @param key 键
* @param value 值
* @param time (秒) time要大于0 如果time小于等于0 将设置无限期
* @return 是否成功
*/
Boolean setNx(String key, Object value, long time);
/**
* 如果key已存在就执行,并返回true 不存在就不执行 并返回false
* @param key 键
* @param value 值
* @return true成功 false失败
*/
Boolean setXx(String key, Object value);
/**
* 如果key已存在就执行,并返回true 不存在就不执行 并返回false
* @param key 键
* @param value 值
* @param time (秒) time要大于0 如果time小于等于0 将设置无限期
* @return true成功 false失败
*/
Boolean setXx(String key, Object value, long time);
/**
* 递增
* @param key 键
* @param delta 要增加几(大于0)
* @return
*/
Double incr(String key, double delta) ;
/**
* 递减
* @param key 键
* @param delta 要减少几(大于0)
* @return
*/
Double decr(String key, double delta) ;
4.3 hash
/**
* HashGet
* @param key 键
* @param item 项
* @return 值
*/
Object hGet(String key,String item);
/**
* 获取 hash key 的所有键值
* @param key
* @return
*/
Map<Object,Object> hmGet(String key);
/**
* 获取hashkey 所对应的值
*
* @param key
* @param hashKey
* @return values
*/
List<Object> hGetMulti(String key,String... hashKey);
/**
* 获取 hashkey 中所有的 健
*
* @param key
* @return
*/
Set hGetItemKeys(String key);
/**
* 获取 hashkey 中所有的值
* @param key
* @return
*/
List<Object> hGetItemValues(String key);
/**
* hashSet
*
* @param key hash key
* @param map hKey hValue
* @return true 成功 false 失败
*/
Boolean hmSet(String key, Map<String,Object> map);
/**
* HashSet 并设置时间
*
* @param key 键
* @param map 对应多个键值
* @param time 时间(秒)
* @return true成功 false失败
*/
Boolean hmSet(String key, Map<String, Object> map, long time);
/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key 键
* @param item 项
* @param value 值
* @return true 成功 false失败
*/
Boolean hSet(String key, String item, Object value);
/**
* 新增
* hKey
* 不存在 则新增到map中,
* 存在 则不新增也不覆盖
*
* @param key
* @param hKey
* @param hValue
* @return
*/
Boolean hSetIfAbsent(String key, String hKey, Object hValue);
/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key 键
* @param item 项
* @param value 值
* @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
* @return true 成功 false失败
*/
Boolean hSet(String key, String item, Object value, long time);
/**
* 获取长度
* @param key
* @return
*/
Long hSize(String key);
/**
* 获取指定key 的 hashKey 的 hashValue 的 length
*
* @param key
* @param hKey
* @return
*/
Long hLengthOfValue(String key,String hKey);
/**
* 删除hash表中的值
*
* @param key 键 不能为null
* @param item 项 可以使多个 不能为null
*/
void hDel(String key, Object... item);
/**
* 判断hash表中是否有该项的值
*
* @param key 键 不能为null
* @param item 项 不能为null
* @return true 存在 false不存在
*/
Boolean hHasKey(String key, String item);
/**
* hash递增 如果不存在,就会创建一个 并把新增后的值返回
*
* @param key 键
* @param item 项
* @param by 要增加几(>0)
* @return
*/
Double hIncr(String key, String item, double by);
/**
* hash递减
*
* @param key 键
* @param item 项
* @param by 要减少几 (>0)
* @return
*/
Double hDecr(String key, String item, double by);
4.4 list
/**
* 获取长度
* @param key
* @return
*/
Long lSize(String key);
/**
* 获取指定下标的元素 左边从0开始 右边从-1开始
* 超过下标,不报错,返回null
* @param key
* @param index
* @return
*/
Object lGet(String key, long index);
/**
* 获取指定下标的元素 左边从0开始 右边从-1开始
* 超过下标,不报错,返回null
* @param key
* @param object
* @return
*/
Object lGet(String key, Object object);
/**
* 追加
*
* 插入 把 value 插入最左或者最右边
* @param key
* @param value
* @param isRight 是否加到右边
* @return 长度 -1为报错失败
*/
void lAppend(String key,Object value, boolean isRight);
/**
* 追加
*
* 在 pivot 根据 isRight 字段判断是否紧邻左右 插入 newValue
* 在集合中查找 pivot 值
* 有多个 pivot 值时,从左边开始查到第一个 pivot 值即可,pivot value 值左边插入新值 newValue
* 不存在 pivot 时,不插入新值 newValue
*
* @param key
* @param pivot 已经在List中的元素
* @param newValue 新加入的元素
* @param isRight 是否要加到右边
* @return 长度 -1为报错失败
*/
void lAppendPivot(String key, Object pivot, Object newValue, boolean isRight);
/**
* 追加
* 批量插入新值
*
* @param key
* @param time <=0 则持久化
* @param isRight 是否加到右边
* @param values
* @return 长度 -1为报错失败
*/
void lAppendAll(String key, long time, boolean isRight, Object... values);
/**
* 插入
* 如果Key存在 则新增,不存在不新增
*
* @param key
* @param value
* @param isRight
* @return
*/
void lSetIfPresent(String key, String value, boolean isRight);
/**
* 延迟操作
* time秒后移除最左边的一个元素
* @param key
* @param time 秒
* @return
*/
Object lLeftPop(String key,long time);
/**
* 延迟操作
* time秒后移除最右边的一个元素
* @param key
* @param time
* @return
*/
Object lRightPop(String key,long time);
/**
* 延迟操作
*
* 移除k1中最右的值,并将移除的值插入k2中最左侧
* k1和k2不是同一个key时,k1右侧移除,k2左侧插入,k2不存在时则新增一个然后在插入
* k1和k2是同一个key时,相当于把最右侧的值移到了最左侧
* @param key1
* @param key2
* @param timeout 延迟多久操作 为null时立即操作
* @return 操作的元素
*/
Object lRightPopAndLeftPush(String key1,String key2, long timeout);
/**
* 替换
* 指定下标替换新值
* @param key
* @param value
* @param index
*/
void lReplace(String key, Object value, long index);
/**
* 1. 截取 index1 到 index2 之间的数组为 newList,
* 2. 将 key 的 value 置空
* 3. 将 key 的 value 置为 newList
*
* 注意: 会保留index1和index2所占位的对象
* 1. 左侧坐标从0开始,右侧从-1开始
* 2. 当index1超过坐标时(此时与index2无关),都会截取为空,key会被删除
* 3. 当index1为负时(此时与index2无关),都会截取为空,key会被删除
* 4. 当index1为正且在下标存在其中,index2为负数时,index1 在 index2 左侧,相当于去左去右,保留了中间的部分
* 5. 当index1为正且在下标存在其中,index2为负数时,index1 在 index2 右侧时,截取为空, key会被删除
* 6. 若 list 的长度为 n ,index1为n-, index2为n+,则截取index1 到末尾的列表
* @param key
* @param index1
* @param index2
*/
void lTrim(String key,long index1,long index2);
/**
* 获取指定下标之间的值
* 包括 start 和 end 位置的值
*
* @param key 键
* @param start 开始
* @param end 结束 0 到 -1代表所有值
* @return
*/
List<Object> lGet(String key, long start, long end);
/**
* 删除指定元素
*
* count> 0:删除等于从左到右移动的值的第一个元素;
* count< 0:删除等于从右到左移动的值的第一个元素;
* count = 0:删除等于value的所有元素。
* @param key
* @param count
* @param value
*/
void lRemove(String key, int count, Object value);
4.5 set
/**
* 根据key 获取set的所有值
* @param key
* @return
*/
Set<Object> sGet(String key);
/**
* 根据value 从一个set中查询是否存在
* @param key 键
* @param value 值
* @return true 存在 false不存在
*/
Boolean sHasKey(String key, Object value);
/**
* 将数据放入set缓存
*
* @param key 键
* @param values 值 可以是多个
* @return 成功个数
*/
Long sSet(String key, Object... values);
/**
* 将set数据放入缓存
*
* @param key 键
* @param time 时间(秒)
* @param values 值 可以是多个
* @return 成功个数
*/
Long sSetAndTime(String key, long time, Object... values);
/**
* 获取set长度
* @param key
* @return
*/
Long sGetSetSize(String key);
/**
* 移除值为value的
* @param key 键
* @return 移除个数
*/
Long sDel(String key, Object... values);
/**
* 并集 --- 把指定的所有key 合并去除重复返回
* keys.length == 1 --> sGet(keys[0])
* keys.length > 1 --> 返回去重后的并集
*
* @param keys
* @return
*/
Set<Object> sGetUnion(String... keys);
/**
* 并集 --- 把指定的所有key 合并去除重复返回
*
* @param destKey 新生成的列表的 key 名,(会存入redis,若指定key存在则会删除)
* @param otherKeys 要进行合并的 key
* @return 新生成的set长度 如果需要获取新生成的对象,请调用 sGet(String key)
*/
Long sGetUnionAndStore(String destKey, String... otherKeys);
/**
* 交集 -- 把指定的keys重复的元素进行合并返回
* @param keys
* @return
*/
Set<Object> sGetDifference(String... keys);
/**
* 交集 -- 把指定的keys重复的元素合并返回
* @param destKey 新生成的列表的 key 名,(会存入redis,若指定key存在则会删除)
* @param otherKeys 要进行合并的 key
* @return 交集的长度
*/
Long sGetDifference(String destKey, String... otherKeys);
/**
* 差集 -- 把所有不重复的返回
* @param keys
* @return
*/
Set<Object> sGetIntersect(String... keys);
/**
* 差集 -- 把所有不重复的添加到 destkey 中
* @param destKey 新生成的列表的 key 名,(会存入redis,若指定key存在则会删除)
* @param otherKeys 要进行差集计算的 key
* @return 差集的长度
*/
Long sGetIntersect(String destKey, String... otherKeys);
/**
* 将 key1 的 value 移到 key2
* @param key1
* @param value
* @param key2
* @return true 成功 false 失败
*/
Boolean sMove(String key1, Object value, String key2);
/**
* 随机获取 count 个元素 并 移除
* @param key
* @param count 为 null 时获取一个
* @return 返回单个对象 或 list
*/
Object sPop(String key, Long count);
/**
* 随机获取 count 个元素
*
* @param key
* @param count 为 null 时获取 10个
* @param isDistinct 是否去重
* @return 返回单个对象 或 list
*/
List<Object> sRandomMembers(String key, Long count, boolean isDistinct);
4.6 zset
/**
* 向指定key中添加元素,按照score值由小到大进行排列
* 集合中对应元素已存在,会被覆盖,包括score
* @param key
* @param value
* @param score
* @return 是否成功
*/
Boolean zSet(String key,Object value, double score);
/**
* 向指定key中添加元素,按照score值由小到大进行排列
* 集合中对应元素已存在,会被覆盖,包括score
*
* Set<ZSetOperations.TypedTuple<String>> tuples = new HashSet<>();
* tuples.add(new DefaultTypedTuple<>("eee",9.6));
*
* @param key
* @param tuples
* @return
*/
Boolean zSet(String key, Set<ZSetOperations.TypedTuple<Object>> tuples);
/**
* 增加key对应的集合中元素v1的score值,并返回增加后的值
* v1不存在,直接新增一个元素
* @param key
* @param value
* @param delta
* @return
*/
Double zIncrScore(String key, Object value, double delta);
/**
* 获取key对应集合中o元素的score值
* @param key
* @param value
* @return
*/
Double zGetScore(String key, Object value);
/**
* 获取集合的大小,地层调用的还是 zCard(K key)
* @param key
* @return
*/
Long zSize(String key);
/**
* 获取指定score区间里的元素个数
* 包括min、max
*
* @param key
* @param min
* @param max
* @return
*/
Long zCount(String key,double min,double max);
/**
* 获取指定下标之间的值
* (0,-1)就是获取全部
*
* @param key
* @param start
* @param end
* @param isDesc 是否倒序,默认从小到大
* @return
*/
Set<Object> zGet(String key,long start,long end, boolean isDesc);
/**
* 获取指定score区间的值
*
* @param key
* @param min
* @param max
* @return
*/
Set<Object> zGet(String key,double min, double max);
/**
* 获取指定score区间的值,然后从给定下标和给定长度获取最终值
* @param key
* @param min 最小score 包含
* @param max 最大score 包含
* @param offset 偏移 0开始
* @param count 长度
* @return
*/
Set<Object> zGet(String key,double min, double max,long offset, long count);
/**
* 获取指定元素在集合中的索引,索引从0开始
* @param key
* @param value
* @param isDesc 是否倒序,默认从小到大
* @return
*/
Long zGetRank(String key,Object value, boolean isDesc);
/**
* 移除指定值
* @param key
* @param values
* @return
*/
Long zDel(String key,Object... values);
/**
* 移除指定下标的值
* @param key
* @param start
* @param end
* @return
*/
Long zDel(String key,long start,long end);
/**
* 移除指定score之间的值
* @param key
* @param min
* @param max
* @return
*/
Long zDel(String key,double min, double max);
4.7 bitmaps 二进制站位
/**
* 将指定param的值设置为1,{@param param}会经过hash计算进行存储。
*
* @param key bitmap结构的key
* @param param 要设置偏移的key,该key会经过hash运算。
* @param value 即该位设置为1,否则设置为0
* @return 返回设置该value之前的值。
*/
Boolean bSet(String key,String param,boolean value);
/**
* 将指定offset偏移量的值设置为1;
*
* @param key bitmap结构的key
* @param offset 指定的偏移量。
* @param value true:即该位设置为1,否则设置为0
* @return 返回设置该value之前的值
*/
Boolean bSet(String key,long offset, boolean value);
/**
* 获取指定偏移位上的值,{@param param}会经过hash计算进行存储。
*
* @param key bitmap结构的key
* @param param 要移除偏移的key,该key会经过hash运算。
* @return 若偏移位上的值为1,那么返回true。
*/
Boolean bGet(String key,String param);
/**
* 获取指定偏移位上的值
*
* @param key bitmap结构的key
* @param offset 偏移量
* @return 若偏移位上的值为1,那么返回true。
*/
Boolean bGet(String key,long offset);
/**
* 统计指定范围中value为1的数量
*
* @param key bitMap中的key
* @return 在指定范围[start,end]内所有value=1的数量
*/
Long bCount(String key);
/**
* 统计指定范围中value为1的数量
* bitCount(key.getBytes,start,end); start 和 end 都是 byte (1byte = 8bit)
* {@fun bSet(key,7,true);}进行存储时,单位是bit。那么只需要统计[0,1]便可以统计到上述set的值。
*
* @param key bitMap中的key
* @param start 0开始
* @param end 结束下标
* @return 在指定范围[start,end]内所有value=1的数量
*/
Long bCount(String key,long start, long end);
/**
* 对一个或多个保存二进制的字符串key进行元操作,并将结果保存到saveKey上。
* <p>
* bitop=>and newKey keys [key...],对一个或多个key逻辑并,结果保存到newKey。
* bitop=>or newKey keys [key...],对一个或多个key逻辑或,结果保存到newKey
* bitop=>xor newKey keys [key...],对一个或多个key逻辑异或,结果保存到newKey
* bitop=>not newKey key 对一个或多个key逻辑非,结果保存到newKey
* <p>
*
* @param op 元操作类型;
* @param newKey 元操作后将结果保存到newKey所在的结构中。
* @param desKey 需要进行元操作的类型。
* @return 1:返回元操作值。
*/
Long bGetOp(RedisCommands.BitOperation op, String newKey, String... desKey);
/**
* 对一个或多个保存二进制的字符串key进行元操作,并将结果保存到saveKey上,并返回统计之后的结果。
*
* @param op 元操作类型;
* @param newKey 元操作后将结果保存到newKey所在的结构中。
* @param desKey 需要进行元操作的类型。
* @return 返回saveKey结构上value=1的所有数量值。
*/
Long bGetOpResult(RedisCommands.BitOperation op, String newKey, String... desKey);
/**
* 计算用户的 组,片,和对应的 offset
* 如果要用userId计算日活等数据,使用此方法获取BitMapKey
*
* @param userId
* @return
*/
BitMapKey bGetBitMapKey(long userId);
4.7.1 防止热key 分组分片
import java.util.StringJoiner;
public final class BitMapKey {
/**
* 组 0开始
*/
private int groupIndex;
/**
* 组中分片 0开始
*/
private int shardIndex;
/**
* (组=>分片)下的offset位置 0开始
*/
private int offset;
public BitMapKey(int groupIndex, int shardIndex, int offset) {
this.groupIndex = groupIndex;
this.shardIndex = shardIndex;
this.offset = offset;
}
public String getRedisKey(String key) {
return String.join(":", key, groupIndex + "", shardIndex + "");
}
//getter...
//setter...
4.8 HyperLogLog 记录数据数量,而不记录数据本身
/**
* 添加
* @param key
* @param values
* @return 至少有一个添加成功true,否则false
*/
Boolean hllSet(String key, Object... values);
/**
* 总共有多少个元素
* @param keys 可以多个组合查
* @return
*/
Long hllSize(String... keys);
/**
* 将所有keys合并到desKey中
* @param desKey
* @param keys
* @return
*/
Long hllUnion(String desKey,String... keys);
/**
* 删除
* @param keys
*/
void hllDelete(String... keys);
4.7 Geospatial 经纬度
/**
* 添加经纬度坐标
* <p>两极无法直接添加,一般会下载城市数据,直接通过Java程序一次性导入。</p>
* <p>有效的经度从-180度到180度。有效的纬度从-85.05112878度到85.05112878度。</p>
* <p>当坐标位置超出指定范围时,该命令将会返回一个错误。</p>
* <p>已经添加的数据,是无法再次往里面添加的。</p>
*
* @param key 健
* @param x 经度
* @param y 维度
* @param member 名称
* @return key的元素数量
*/
Long gpsSet(String key, double x,double y, String member);
/**
* 批量添加
* @param key
* @param map
* @return
*/
Long gpsSet(String key,Map<Object,Point> map);
/**
* 获得一个或多个指定地区的坐标值
* @param key
* @param members 名称
* @return
*/
List<Point> gpsGet(String key, String... members);
/**
* 获取两点之间的直线距离
* @param key
* @param member1
* @param member2
* @return 公里
*/
Double gpsGetDistance(String key,String member1,String member2);
/**
* 获取附近的城市
* @param key 健
* @param member 中心
* @param km 公里
* @return
*/
GeoResults<RedisGeoCommands.GeoLocation<Object>> gpsGetNearby(String key, String member, double km);
/**
* 获取附近的城市
* @param key 健
* @param x 坐标x
* @param y 坐标y
* @param km 公里
* @return
*/
GeoResults<RedisGeoCommands.GeoLocation<Object>> gpsGetNearby(String key, double x, double y, double km);
/**
* 删除
* @param key
* @param member
* @return 删除的元素数量
*/
Long gpsDel(String key,Object... member);
5. 实现类
package com.adleading.mediadatabase.util.redis;
import com.google.common.hash.Funnels;
import com.google.common.hash.Hashing;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.GeoResults;
import org.springframework.data.geo.Metrics;
import org.springframework.data.geo.Point;
import org.springframework.data.redis.connection.RedisCommands;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.nio.charset.Charset;
import java.util.*;
import java.util.concurrent.TimeUnit;
@Component
public class RedisTemplateUtilsImpl implements RedisTemplateUtils {
@Autowired
private RedisTemplate<String,Object> redisTemplate;
/**
* 每个组中有多少
*
* 如果userId < 100亿,则会分到7000个分组里
* 1 << 20 = 1048576
*/
private static final int ONE_BITMAP_SIZE = 1 << 20;
/**
* 同一个分组里的的useId划分到20个bitmap里
* 避免出现范围用户太多导致查询时出现热key
*/
private static final int SHARD_COUNT = 20;
public static int getOneBitmapSize() {
return ONE_BITMAP_SIZE;
}
public static int getShardCount() {
return SHARD_COUNT;
}
@Override
public Boolean expire(String key, long time) {
try {
if(time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
}catch (Exception e){
e.printStackTrace();
return false;
}
}
@Override
public Long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
@Override
public Boolean hasKey(String key) {
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
@SuppressWarnings("unchecked")
@Override
public void del(String... keys) {
if(keys != null && keys.length>0){
if(keys.length == 1) {
redisTemplate.delete(keys[0]);
}else{
redisTemplate.delete(Arrays.asList(keys));
}
}
}
//------------------------ String --------------------------------------
@Override
public Object get(String key) {
return StringUtils.hasText(key) ? redisTemplate.opsForValue().get(key) : null;
}
@Override
public <T> T get(String key, Class<T> clazz) {
Object obj = StringUtils.hasText(key) ? redisTemplate.opsForValue().get(key) : null;
if (clazz.isInstance(obj)){
return clazz.cast(obj);
}
return null;
}
@Override
public void append(String key, String value) {
redisTemplate.opsForValue().append(key, value);
}
@Override
public Boolean set(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
}catch (Exception e) {
e.printStackTrace();
return false;
}
}
@Override
public Boolean set(String key, Object value, long time) {
try {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
} else {
set(key, value);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
@Override
public Boolean setOrUpdate(Map<String, Object> map, boolean isAtom) {
try {
if(isAtom){
return redisTemplate.opsForValue().multiSetIfAbsent(map);
}
redisTemplate.opsForValue().multiSet(map);
return true;
}catch (Exception e){
e.printStackTrace();
return false;
}
}
@Override
public List<Object> getList(String... keys) {
return redisTemplate.opsForValue().multiGet(Arrays.asList(keys));
}
@Override
public Boolean setNx(String key, Object value) {
return redisTemplate.opsForValue().setIfAbsent(key, value);
}
@Override
public Boolean setNx(String key, Object value, long time) {
if(time > 0) {
return redisTemplate.opsForValue().setIfAbsent(key, value, time, TimeUnit.SECONDS);
}
return setNx(key,value);
}
@Override
public Boolean setXx(String key, Object value) {
return redisTemplate.opsForValue().setIfPresent(key, value);
}
@Override
public Boolean setXx(String key, Object value, long time) {
if(time > 0){
return redisTemplate.opsForValue().setIfPresent(key, value, time, TimeUnit.SECONDS);
}
return setXx(key,value);
}
@Override
public Double incr(String key, double delta) {
if (delta < 0) {
throw new RuntimeException("递增因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, delta);
}
@Override
public Double decr(String key, double delta) {
if (delta < 0) {
throw new RuntimeException("递减因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, -delta);
}
// ================================Map=================================
@Override
public Object hGet(String key, String item) {
return redisTemplate.opsForHash().get(key, item);
}
@Override
public Map<Object, Object> hmGet(String key) {
return redisTemplate.opsForHash().entries(key);
}
@Override
public List<Object> hGetMulti(String key, String... hashKey) {
return redisTemplate.opsForHash().multiGet(key, Arrays.asList(hashKey));
}
@Override
public Set hGetItemKeys(String key) {
return redisTemplate.opsForHash().keys(key);
}
@Override
public List<Object> hGetItemValues(String key) {
return redisTemplate.opsForHash().values(key);
}
@Override
public Boolean hmSet(String key, Map<String, Object> map) {
try {
redisTemplate.opsForHash().putAll(key, map);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
@Override
public Boolean hmSet(String key, Map<String, Object> map, long time) {
try {
redisTemplate.opsForHash().putAll(key, map);
expire(key, time);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
@Override
public Boolean hSet(String key, String item, Object value) {
try {
redisTemplate.opsForHash().put(key, item, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
@Override
public Boolean hSetIfAbsent(String key, String hKey, Object hValue) {
try {
return redisTemplate.opsForHash().putIfAbsent(key, hKey, hValue);
}catch (Exception e){
e.printStackTrace();
return false;
}
}
@Override
public Boolean hSet(String key, String item, Object value, long time) {
try {
redisTemplate.opsForHash().put(key, item, value);
expire(key, time);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
@Override
public Long hSize(String key) {
return redisTemplate.opsForHash().size(key);
}
@Override
public Long hLengthOfValue(String key, String hKey) {
return redisTemplate.opsForHash().lengthOfValue(key,hKey);
}
@Override
public void hDel(String key, Object... item) {
redisTemplate.opsForHash().delete(key, item);
}
@Override
public Boolean hHasKey(String key, String item) {
return redisTemplate.opsForHash().hasKey(key, item);
}
@Override
public Double hIncr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, by);
}
@Override
public Double hDecr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, -by);
}
// ============================set=============================
@Override
public Set<Object> sGet(String key) {
try {
return redisTemplate.opsForSet().members(key);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
@Override
public Boolean sHasKey(String key, Object value) {
try {
return redisTemplate.opsForSet().isMember(key, value);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
@Override
public Long sSet(String key, Object... values) {
try {
return redisTemplate.opsForSet().add(key, values);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
@Override
public Long sSetAndTime(String key, long time, Object... values) {
try{
Long count = redisTemplate.opsForSet().add(key, values);
expire(key,time);
return count;
}catch (Exception e){
e.printStackTrace();
return null;
}
}
@Override
public Long sGetSetSize(String key) {
try {
return redisTemplate.opsForSet().size(key);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
@Override
public Long sDel(String key, Object... values) {
try {
return redisTemplate.opsForSet().remove(key, values);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
@Override
public Set<Object> sGetUnion(String... keys){
try {
if(null != keys && keys.length == 1){
return sGet(keys[0]);
}else if(null != keys && keys.length > 0){
return redisTemplate.opsForSet().union(Arrays.asList(keys));
}
return null;
}catch (Exception e){
e.printStackTrace();
return null;
}
}
@Override
public Long sGetUnionAndStore(String destKey, String... otherKeys){
return redisTemplate.opsForSet().unionAndStore(Arrays.asList(otherKeys), destKey);
}
@Override
public Set<Object> sGetDifference(String... keys){
try {
if(null != keys && keys.length == 1){
return sGet(keys[0]);
}else if(null != keys && keys.length > 0){
return redisTemplate.opsForSet().difference(Arrays.asList(keys));
}
return null;
}catch (Exception e){
e.printStackTrace();
return null;
}
}
@Override
public Long sGetDifference(String destKey, String... otherKeys) {
return redisTemplate.opsForSet().differenceAndStore(Arrays.asList(otherKeys), destKey);
}
@Override
public Set<Object> sGetIntersect(String... keys) {
try {
if(null != keys && keys.length == 1){
return sGet(keys[0]);
}else if(null != keys && keys.length > 0){
return redisTemplate.opsForSet().intersect(Arrays.asList(keys));
}
return null;
}catch (Exception e){
e.printStackTrace();
return null;
}
}
@Override
public Long sGetIntersect(String destKey, String... otherKeys) {
return redisTemplate.opsForSet().intersectAndStore(Arrays.asList(otherKeys), destKey);
}
@Override
public Boolean sMove(String key1, Object value, String key2) {
return redisTemplate.opsForSet().move(key1, value, key2);
}
@Override
public Object sPop(String key, Long count) {
if(count < 0){
throw new RuntimeException("pop的数量不能为负数!");
}
if(count == 0){
return redisTemplate.opsForSet().pop(key);
}
return redisTemplate.opsForSet().pop(key,count);
}
@Override
public List<Object> sRandomMembers(String key, Long count, boolean isDistinct) {
count = count == null ? 10 : count;
if(isDistinct){
return new ArrayList(Objects.requireNonNull(redisTemplate.opsForSet().distinctRandomMembers(key, count)));
}
return redisTemplate.opsForSet().randomMembers(key, count);
}
// ===============================list=================================
@Override
public Long lSize(String key) {
return redisTemplate.opsForList().size(key);
}
@Override
public Object lGet(String key, long index) {
return redisTemplate.opsForList().index(key,index);
}
@Override
public Object lGet(String key, Object object) {
return redisTemplate.opsForList().indexOf(key, object);
}
@Override
public void lAppend(String key, Object value, boolean isRight) {
try {
if(isRight){
redisTemplate.opsForList().rightPush(key, value);
return;
}
redisTemplate.opsForList().leftPush(key, value);
}catch (Exception e){
e.printStackTrace();
}
}
@Override
public void lAppendPivot(String key, Object pivot, Object newValue, boolean isRight) {
try {
if(isRight){
redisTemplate.opsForList().rightPush(key, pivot, newValue);
return;
}
redisTemplate.opsForList().leftPush(key, pivot, newValue);
}catch (Exception e){
e.printStackTrace();
}
}
@Override
public void lAppendAll(String key, long time, boolean isRight, Object... values) {
try {
if (isRight) {
redisTemplate.opsForList().rightPushAll(key, values);
}else {
redisTemplate.opsForList().leftPushAll(key, values);
}
expire(key,time);
}catch (Exception e){
e.printStackTrace();
}
}
@Override
public void lSetIfPresent(String key, String value,boolean isRight) {
try{
if(isRight){
redisTemplate.opsForList().rightPushIfPresent(key,value);
}else {
redisTemplate.opsForList().leftPushIfPresent(key, value);
}
}catch (Exception e){
e.printStackTrace();
}
}
@Override
public Object lLeftPop(String key,long time) {
try {
if(time > 0){
return redisTemplate.opsForList().leftPop(key, time, TimeUnit.SECONDS);
}
return redisTemplate.opsForList().leftPop(key);
}catch (Exception e){
e.printStackTrace();
return null;
}
}
@Override
public Object lRightPop(String key,long time) {
try {
if (time > 0){
return redisTemplate.opsForList().rightPop(key, time, TimeUnit.SECONDS);
}
return redisTemplate.opsForList().rightPop(key);
}catch (Exception e){
e.printStackTrace();
return null;
}
}
@Override
public Object lRightPopAndLeftPush(String key1, String key2, long timeout) {
try {
if (timeout > 0) {
return redisTemplate.opsForList().rightPopAndLeftPush(key1, key2);
}
return redisTemplate.opsForList().rightPopAndLeftPush(key1, key2, timeout, TimeUnit.SECONDS);
}catch (Exception e){
e.printStackTrace();
return null;
}
}
@Override
public void lReplace(String key, Object value, long index) {
redisTemplate.opsForList().set(key,index,value);
}
@Override
public void lTrim(String key, long index1, long index2) {
if(index1 < 0){
throw new RuntimeException("开始下标不能为负数!");
}
redisTemplate.opsForList().trim("rightList",index1,index2);
}
@Override
public List<Object> lGet(String key, long start, long end) {
return redisTemplate.opsForList().range(key,start,end);
}
@Override
public void lRemove(String key, int count, Object value) {
redisTemplate.opsForList().remove(key,count,value);
}
// =============================== zSet 有序集合 根据 score =================================
@Override
public Boolean zSet(String key, Object value, double score) {
return redisTemplate.opsForZSet().add(key, value, score);
}
@Override
public Boolean zSet(String key, Set<ZSetOperations.TypedTuple<Object>> tuples) {
try {
redisTemplate.opsForZSet().add(key, tuples);
return true;
}catch (Exception e){
e.printStackTrace();
return false;
}
}
@Override
public Double zIncrScore(String key, Object value, double delta) {
return redisTemplate.opsForZSet().incrementScore(key, value, delta);
}
@Override
public Double zGetScore(String key, Object value) {
return redisTemplate.opsForZSet().score(key,value);
}
@Override
public Long zSize(String key) {
return redisTemplate.opsForZSet().size(key);
}
@Override
public Long zCount(String key, double min, double max) {
return redisTemplate.opsForZSet().count(key,min,max);
}
@Override
public Set<Object> zGet(String key, long start, long end, boolean isDesc) {
if(isDesc) {
return redisTemplate.opsForZSet().reverseRange(key, start, end);
}
return redisTemplate.opsForZSet().range(key, start, end);
}
@Override
public Set<Object> zGet(String key, double min, double max) {
return redisTemplate.opsForZSet().rangeByScore(key, min, max);
}
@Override
public Set<Object> zGet(String key, double min, double max, long offset, long count) {
return redisTemplate.opsForZSet().rangeByScore(key, min, max, offset, count);
}
@Override
public Long zGetRank(String key, Object value, boolean isDesc) {
if(isDesc){
return redisTemplate.opsForZSet().reverseRank(key,value);
}
return redisTemplate.opsForZSet().rank(key,value);
}
@Override
public Long zDel(String key, Object... values) {
return redisTemplate.opsForZSet().remove(key,values);
}
@Override
public Long zDel(String key, long start, long end) {
return redisTemplate.opsForZSet().removeRange(key, start, end);
}
@Override
public Long zDel(String key, double min, double max) {
return redisTemplate.opsForZSet().removeRangeByScore(key, min, max);
}
//==============================bitmaps===================================
@Override
public Boolean bSet(String key, String param, boolean value) {
return bSet(key,hash(param),value);
}
@Override
public Boolean bSet(String key, long offset, boolean value) {
return redisTemplate.opsForValue().setBit(key,offset,value);
}
@Override
public Boolean bGet(String key, String param) {
return bGet(key, hash(param));
}
@Override
public Boolean bGet(String key, long offset) {
return redisTemplate.opsForValue().getBit(key,offset);
}
@Override
public Long bCount(String key) {
return redisTemplate.execute((RedisCallback<Long>) con -> con.bitCount(key.getBytes()));
}
@Override
public Long bCount(String key, long start, long end) {
return redisTemplate.execute((RedisCallback<Long>) con -> con.bitCount(key.getBytes(), start, end));
}
@Override
public Long bGetOp(RedisCommands.BitOperation op, String newKey, String... desKey) {
byte[][] bytes = new byte[desKey.length][];
for (int i = 0; i < desKey.length; i++) {
bytes[i] = desKey[i].getBytes();
}
return redisTemplate.execute((RedisCallback<Long>) con -> con.bitOp(op, newKey.getBytes(), bytes));
}
@Override
public Long bGetOpResult(RedisCommands.BitOperation op, String newKey, String... desKey) {
bGetOp(op, newKey, desKey);
return bCount(newKey);
}
@Override
public BitMapKey bGetBitMapKey(long userId){
//获取组
long groupIndex = userId / ONE_BITMAP_SIZE;
//获取分片
int shardIndex = Math.abs((int) (hash(userId+"") % SHARD_COUNT));
//获取(组-分片)下的offset位置
int offset = (int) (userId - groupIndex * ONE_BITMAP_SIZE);
return new BitMapKey((int)groupIndex,shardIndex,offset);
}
/**
* guava依赖获取hash值。
*/
private static long hash(String key) {
Charset charset = Charset.forName("UTF-8");
return Math.abs(Hashing.murmur3_128().hashObject(key, Funnels.stringFunnel(charset)).asInt());
}
//============================== HyperLogLog ===================================
@Override
public Boolean hllSet(String key, Object... values) {
try {
Long add = redisTemplate.opsForHyperLogLog().add(key, values);
return 1 == add;
}catch (Exception e){
e.printStackTrace();
return false;
}
}
@Override
public Long hllSize(String... key) {
return redisTemplate.opsForHyperLogLog().size();
}
@Override
public Long hllUnion(String desKey, String... keys) {
try {
return redisTemplate.opsForHyperLogLog().union(desKey,keys);
}catch (Exception e){
e.printStackTrace();
return null;
}
}
@Override
public void hllDelete(String... keys) {
try {
for (String key : keys) {
redisTemplate.opsForHyperLogLog().delete(key);
}
}catch (Exception e){
e.printStackTrace();
}
}
//============================== Geospatial 经纬度===================================
@Override
public Long gpsSet(String key, double x,double y, String member){
Point point = new Point(x, y);
try {
return redisTemplate.opsForGeo().add(key, point, member);
}catch (Exception e){
e.printStackTrace();
return null;
}
}
@Override
public Long gpsSet(String key,Map<Object,Point> map){
try {
return redisTemplate.opsForGeo().add(key,map);
}catch (Exception e){
e.printStackTrace();
return null;
}
}
@Override
public List<Point> gpsGet(String key, String... members) {
return redisTemplate.opsForGeo().position(key,members);
}
@Override
public Double gpsGetDistance(String key, String member1, String member2) {
Distance distance = redisTemplate.opsForGeo().distance(key, member1, member2, Metrics.KILOMETERS);
return distance.getValue();
}
@Override
public GeoResults<RedisGeoCommands.GeoLocation<Object>> gpsGetNearby(String key, String member, double km){
return redisTemplate.opsForGeo().radius(key,member,new Distance(km,Metrics.KILOMETERS));
// GeoResults<RedisGeoCommands.GeoLocation<Object>> radius = redisTemplate.opsForGeo().radius(key, member, new Distance(km, Metrics.KILOMETERS));
// for (GeoResult<RedisGeoCommands.GeoLocation<Object>> geoLocationGeoResult : radius) {
// //地点名
// geoLocationGeoResult.getContent().getName();
// //地点坐标
// geoLocationGeoResult.getContent().getPoint();
// //地址计算器
// geoLocationGeoResult.getDistance();
// }
}
@Override
public GeoResults<RedisGeoCommands.GeoLocation<Object>> gpsGetNearby(String key, double x, double y, double km) {
return redisTemplate.opsForGeo().radius(key,new Point(x,y),new Distance(km,Metrics.KILOMETERS));
}
@Override
public Long gpsDel(String key, Object... members) {
try {
return redisTemplate.opsForGeo().remove(key, members);
}catch (Exception e){
e.printStackTrace();
return null;
}
}
}