Spring Boot 3.0深度整合Redis:企业级封装与全数据结构实战
一、环境准备
- JDK 17+
- Spring Boot 3.0+
- Redis 7.0+
- Maven/Gradle
二、项目搭建
1. 添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
2. 配置文件
spring:
data:
redis:
host: localhost
port: 6379
password:
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 0
3. Redis配置类
@AutoConfiguration(before = RedissonAutoConfigurationV2.class)
public class StoRedisAutoConfiguration {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
template.setKeySerializer(RedisSerializer.string());
template.setHashKeySerializer(RedisSerializer.string());
template.setValueSerializer(buildRedisSerializer());
template.setHashValueSerializer(buildRedisSerializer());
return template;
}
private static RedisSerializer<?> buildRedisSerializer() {
RedisSerializer<Object> json = RedisSerializer.json();
// 解决Java8时间类型序列化
ObjectMapper objectMapper = (ObjectMapper) ReflectUtil.getFieldValue(json, "mapper");
objectMapper.registerModules(new JavaTimeModule());
return json;
}
@Bean
public NumericIdGenerator defaultIdGenerator(StringRedisTemplate redisTemplate,
@Value("${spring.application.name:defaultIdGenerator}") String key) {
return new RedisNumericIdGenerator(redisTemplate, key);
}
}
特性说明:
- 使用Jackson处理Java8时间类型
- 优先于Redisson配置,确保自定义Template生效
- 集成分布式ID生成器
- KEY统一使用String序列化
三、全数据结构操作封装
1. 核心工具类设计
@Slf4j
public class RedisOps {
// 支持所有数据结构操作接口
private final RedisTemplate<String, Object> redisTemplate;
public RedisTemplate<String, Object> opsForTemplate() {
return redisTemplate;
}
public ValueOperations<String, Object> opsForValue() {
return redisTemplate.opsForValue();
}
public ListOperations<String, Object> opsForList() {
return redisTemplate.opsForList();
}
public HashOperations<String, String, Object> opsForHash() {
return redisTemplate.opsForHash();
}
public SetOperations<String, Object> opsForSet() {
return redisTemplate.opsForSet();
}
public ZSetOperations<String, Object> opsForZSet() {
return redisTemplate.opsForZSet();
}
// 智能序列化方法
private String toJson(Object value) {
if (value instanceof Number || value instanceof Boolean) {
return String.valueOf(value);
}
return JsonUtils.toJsonString(value);
}
// 反序列化方法
private <T> T toPo(Object value, Class<T> clazz) {
if (value == null) return null;
if (clazz.isAssignableFrom(value.getClass())) {
return clazz.cast(value);
}
return JsonUtils.parseObject(toJson(value), clazz);
}
}
2. 位图高级操作
/**
* hash函数
*
* @param value 值
* @param hashCount hash次数
* @param bitMapSize 位图大小
*/
private int[] hash(String value, int hashCount, int bitMapSize) {
int[] offsets = new int[hashCount];
CRC32 crc = new CRC32();
for (int i = 0; i < hashCount; i++) {
crc.update((value + i).getBytes(StandardCharsets.UTF_8));
long hash = crc.getValue();
offsets[i] = (int) (hash % bitMapSize);
crc.reset();
}
return offsets;
}
3. 管道批量操作
/**
* 批量设置key(使用管道模式,减少网络请求)
*
* @param values key and value
* @param timeout 超时时间
* @param unit 超时时间单位
*/
public void setMultiplePipeline(Map<String, String> values, long timeout, TimeUnit unit) {
// 获取 RedisTemplate 的连接
try (var redisConnection = Objects.requireNonNull(redisTemplate.getConnectionFactory()).getConnection()) {
// 使用管道模式
redisConnection.openPipeline();
// 获取 ValueOperations
ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();
// 批量设置键值对
for (Map.Entry<String, String> entry : values.entrySet()) {
valueOperations.set(entry.getKey(), entry.getValue());
redisConnection.keyCommands().expire(entry.getKey().getBytes(), unit.toSeconds(timeout));
}
// 执行所有命令
redisConnection.closePipeline();
}
}
/**
* 批量设置 hash 结构的 key(使用管道模式,减少网络请求)
*
* @param values key and value
* @param timeout 超时时间
* @param unit 超时时间单位
*/
public void setMultiplePipelineUsingHash(String key, Map<String, String> values, long timeout, TimeUnit unit) {
// 获取 RedisTemplate 的连接
try (var redisConnection = Objects.requireNonNull(redisTemplate.getConnectionFactory()).getConnection()) {
// 使用管道模式
redisConnection.openPipeline();
// 使用 hashOperations 来批量设置字段
redisTemplate.opsForHash().putAll(key, values);
// 设置 hash 的过期时间
redisConnection.keyCommands().expire(key.getBytes(), unit.toSeconds(timeout));
// 执行所有命令
redisConnection.closePipeline();
}
}
四、六大核心数据结构实战
1. String类型
/**
* 逐渐递增
*
* @param key key
* @param value 值
* @param expire 有效期(单位:秒)
*/
public long vIncr(String key, long value, long expire) {
Long num = opsForValue().increment(key, value);
if (Objects.isNull(num)) {
throw new ServiceException("递增执行失败,键值无法序列化");
}
if (expire != RedisConstant.NOT_EXPIRE) {
expire(key, expire, TimeUnit.SECONDS);
}
return num;
}
/**
* 有效期的插入
*
* @param key key
* @param value value
* @param expire 有效期(单位:秒)
*/
public void set(String key, Object value, long expire) {
opsForValue().set(key, value);
if (expire != RedisConstant.NOT_EXPIRE) {
expire(key, expire, TimeUnit.SECONDS);
}
}
/**
* 有效期的插入
*
* @param key key
* @param value value
* @param expire 有效期(单位:秒)
*/
public void set(String key, Object value, long expire, TimeUnit timeUnit) {
opsForValue().set(key, value, expire, timeUnit);
}
/**
* 延长有效期的取出
*
* @param key key
* @param expire 有效期(单位:秒)
* @param clazz 泛型
*/
public <T> T get(String key, long expire, Class<T> clazz) {
if (expire != RedisConstant.NOT_EXPIRE) {
expire(key, expire, TimeUnit.SECONDS);
}
return toPo(opsForValue().get(key), clazz);
}
2. Hash类型
/**
* HashGet
*
* @param key 键 不能为null
* @param item 项 不能为null
* @return 值
*/
public Object hGet(String key, String item) {
return opsForHash().get(key, item);
}
/**
* HashGet
*
* @param key 键 不能为null
* @param item 项 不能为null
* @return 值
*/
public <T> T hGet(String key, String item, Class<T> clazz) {
return toPo(hGet(key, item), clazz);
}
/**
* 获取hashKey对应的所有键值
*
* @param key 键
* @return 对应的多个键值
*/
public Map<String, Object> hEntries(String key) {
return opsForHash().entries(key);
}
/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key 键
* @param item 项
* @param value 值
*/
public void hSet(String key, String item, Object value) {
hSet(key, item, value, RedisConstant.NOT_EXPIRE);
}
/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key 键
* @param item 项
* @param value 值
* @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
*/
public void hSet(String key, String item, Object value, long time) {
opsForHash().put(key, item, value);
if (time != RedisConstant.NOT_EXPIRE) {
expire(key, time, TimeUnit.SECONDS);
}
}
/**
* 删除hash表中的值
*
* @param key 键 不能为null
* @param items 项 可以使多个 不能为null
* @return 删除成功数量
*/
public long hDel(String key, List<Object> items) {
return opsForHash().delete(key, items.toArray());
}
/**
* 删除hash表中的值
*
* @param key 键 不能为null
* @param item 项
* @return 删除成功数量
*/
public long hDel(String key, Object item) {
return opsForHash().delete(key, item);
}
/**
* 判断hash表中是否有该项的值
*
* @param key 键 不能为null
* @param item 项 不能为null
* @return true 存在 false不存在
*/
public boolean hHasKey(String key, String item) {
return opsForHash().hasKey(key, item);
}
/**
* 判断hash表中是否有列表项的值
*
* @param key 键 不能为null
* @param items 列表项 不能为null
*/
public List<Object> hmGetList(String key, List<String> items) {
return opsForHash().multiGet(key, items).stream().filter(Objects::nonNull).collect(Collectors.toList());
}
/**
* 判断hash表中是否有列表项的值
*
* @param key 键 不能为null
* @param items 列表项 不能为null
*/
public <T> List<T> hmGetList(String key, List<String> items, Class<T> clazz) {
List<Object> dataList = hmGetList(key, items);
return Objects.isNull(dataList) ? Collections.emptyList() : dataList.stream().map(o -> toPo(o, clazz)).collect(Collectors.toList());
}
/**
* hash递增 如果不存在,就会创建一个 并把新增后的值返回
*
* @param key 键
* @param item 项
* @param by 要增加几(大于0)
*/
public double hIncr(String key, String item, double by) {
return opsForHash().increment(key, item, by);
}
/**
* hash递增 如果不存在,就会创建一个 并把新增后的值返回
*
* @param key 键
* @param item 项
* @param by 要增加几(大于0)
*/
public long hIncr(String key, String item, long by) {
return opsForHash().increment(key, item, by);
}
/**
* hash递减
*
* @param key 键
* @param item 项
* @param by 要减少几(小于0)
*/
public double hDecr(String key, String item, double by) {
return opsForHash().increment(key, item, -by);
}
/**
* hash递减
*
* @param key 键
* @param item 项
* @param by 要减少几(小于0)
*/
public long hDecr(String key, String item, long by) {
return opsForHash().increment(key, item, -by);
}
/**
* 向一张hash表中放入对象数据,如果不存在将创建
*
* @param key 键
* @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
*/
public void hSetMap(String key, Object object, long time) {
Map<String, Object> map = BeanUtil.beanToMap(object);
this.hSetMap(key, map, time);
}
/**
* HashSet 并设置时间
*
* @param key 键
* @param map 对应多个键值
* @param time 时间(秒)
*/
public void hSetMap(String key, Map<String, Object> map, long time) {
opsForHash().putAll(key, map);
if (time != RedisConstant.NOT_EXPIRE) {
expire(key, time, TimeUnit.SECONDS);
}
}
/**
* 获取hashKey对应的所有键值,转为object
*
* @param key 键
* @return 对应的多个键值
*/
public <T> T hGetToBean(String key, Class<T> clazz) {
return BeanUtil.toBean(opsForHash().entries(key), clazz);
}
/**
* 获取hash表长度
*
* @param key key
*/
public long hLen(String key) {
return opsForHash().size(key);
}
3. List类型
/**
* 获取list缓存的内容
*
* @param key 键
* @param start 开始
* @param end 结束 0 到 -1代表所有值
*/
public List<Object> lGet(String key, long start, long end) {
return opsForList().range(key, start, end);
}
/**
* 获取list缓存的内容
*
* @param key 键
* @param start 开始
* @param end 结束 0 到 -1代表所有值
* @param clazz 泛型
*/
public <T> List<T> lGet(String key, long start, long end, Class<T> clazz) {
List<Object> list = lGet(key, start, end);
return Objects.isNull(list) ? Collections.emptyList() : list.stream().map(v -> toPo(v, clazz)).collect(Collectors.toList());
}
/**
* 获取list缓存的长度
*
* @param key 键
*/
public Long lSize(String key) {
return Optional.ofNullable(opsForList().size(key)).orElse(0L);
}
/**
* 通过索引 获取list中的值
*
* @param key 键
* @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
*/
public <T> T lIndex(String key, long index, Class<T> clazz) {
return toPo(opsForList().index(key, index), clazz);
}
/**
* list出队 头部出队
*
* @param key 键
*/
public <T> T lPop(String key, Class<T> clazz) {
return toPo(opsForList().leftPop(key), clazz);
}
/**
* list出队 尾部出队
*
* @param key 键
*/
public <T> T lRPop(String key, Class<T> clazz) {
return toPo(opsForList().rightPop(key), clazz);
}
/**
* 将list放入缓存,尾部
*
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return 返回插入操作后列表的长度
*/
public long lRSet(String key, Object value, long time) {
Long count = opsForList().rightPush(key, value);
if (time != RedisConstant.NOT_EXPIRE) {
expire(key, time, TimeUnit.SECONDS);
}
return Optional.ofNullable(count).orElse(0L);
}
/**
* 将list放入缓存,尾部
*
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return 返回插入操作后列表的长度
*/
public long lRSetAll(String key, List<Object> value, long time) {
Long count = opsForList().rightPushAll(key, value.toArray());
if (time != RedisConstant.NOT_EXPIRE) {
expire(key, time, TimeUnit.SECONDS);
}
return Optional.ofNullable(count).orElse(0L);
}
/**
* 将list放入缓存,头部
*
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return 返回插入操作后列表的长度
*/
public long lLSet(String key, Object value, long time) {
Long count = opsForList().leftPush(key, value);
if (time != RedisConstant.NOT_EXPIRE) {
expire(key, time, TimeUnit.SECONDS);
}
return Optional.ofNullable(count).orElse(0L);
}
/**
* 将list放入缓存,头部
*
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return 返回插入操作后列表的长度
*/
public long lLSetAll(String key, List<Object> value, long time) {
Long count = opsForList().leftPushAll(key, value.toArray());
if (time != RedisConstant.NOT_EXPIRE) {
expire(key, time, TimeUnit.SECONDS);
}
return Optional.ofNullable(count).orElse(0L);
}
/**
* 根据索引修改list中的某条数据
*
* @param key 键
* @param index 索引
* @param value 值
*/
public void lUpdateIndex(String key, long index, Object value) {
opsForList().set(key, index, value);
}
/**
* 移除N个值为value
*
* @param key 键
* @param count 移除多少个 count> 0:删除等于从左到右移动的值的第一个元素;count< 0:删除等于从右到左移动的值的第一个元素;count = 0:删除等于value的所有元素。
* @param value 值 如视频id
* @return 成功移除的个数
*/
public long lRemove(String key, long count, Object value) {
Long successNum = opsForList().remove(key, count, value);
return Objects.isNull(successNum) ? 0 : successNum;
}
4. Set类型
/**
* 向集合添加一个
*
* @param key key
* @param member 成员
* @return 成功添加的数量
*/
public long sAdd(String key, Object member, long time) {
Long count = opsForSet().add(key, member);
if (time != RedisConstant.NOT_EXPIRE) {
expire(key, time, TimeUnit.SECONDS);
}
return Optional.ofNullable(count).orElse(0L);
}
/**
* 向集合添加一个或多个成员,返回添加成功的数量
*
* @param key key
* @param members 元素集合
* @return Long
*/
public long sAdd(String key, long time, List<Object> members) {
Long count = opsForSet().add(key, members.toArray());
if (time != RedisConstant.NOT_EXPIRE) {
expire(key, time, TimeUnit.SECONDS);
}
return Optional.ofNullable(count).orElse(0L);
}
/**
* 获取集合的成员数
*
* @param key key
*/
public long sSize(String key) {
return Optional.ofNullable(opsForSet().size(key)).orElse(0L);
}
/**
* 返回集合中的所有成员
*
* @param key key
* @return Set<String>
*/
public <T> Set<T> sMembers(String key, Class<T> clazz) {
Set<Object> members = opsForSet().members(key);
return Objects.isNull(members) ? Collections.emptySet() : members.stream().map(v -> toPo(v, clazz)).collect(Collectors.toSet());
}
/**
* 判断 member 元素是否是集合 key 的成员,在集合中返回True
*
* @param key key
* @param member value
* @return Boolean
*/
public boolean sIsMember(String key, Object member) {
Boolean signal = opsForSet().isMember(key, member);
return Objects.nonNull(signal) && signal;
}
/**
* 将 member 元素从 sourceKey 集合移动到 targetKey 集合
* 成功返回true
* 当member不存在于sourceKey时,返回false
* 当sourceKey不存在时,也返回false
*
* @param sourceKey 源key
* @param targetKey 目标key
* @param member 元素
* @return boolean
*/
public boolean sMove(String sourceKey, String targetKey, Object member) {
Boolean signal = opsForSet().move(sourceKey, member, targetKey);
return Objects.nonNull(signal) && signal;
}
/**
* 移除并返回集合中的指定数量随机元素
* 当set为空或者不存在时,返回Null
*
* @param key key
* @param count 移除的个数
*/
public <T> List<T> sPop(String key, int count, Class<T> clazz) {
List<Object> members = opsForSet().pop(key, count);
return Objects.isNull(members) ? Collections.emptyList() : members.stream().map(v -> toPo(v, clazz)).collect(Collectors.toList());
}
/**
* 返回集合中一个或多个随机数
* 当count大于set的长度时,set所有值返回,不会抛错。
* 当count等于0时,返回[]
* 当count小于0时,也能返回。如-1返回一个,-2返回两个
*
* @param key key
* @param count 返回的个数
*/
public <T> Set<T> sRandomMembers(String key, int count, Class<T> clazz) {
Set<Object> members = opsForSet().distinctRandomMembers(key, count);
return Objects.isNull(members) ? Collections.emptySet() : members.stream().map(v -> toPo(v, clazz)).collect(Collectors.toSet());
}
/**
* 移除集合中一个或多个成员
*
* @param key key
* @param members 元素集合
* @return 移除成功的数量
*/
public long sRemove(String key, List<Object> members) {
return Optional.ofNullable(opsForSet().remove(key, members.toArray())).orElse(0L);
}
/**
* 返回给定所有给定集合的交集。 不存在的集合 key 被视为空集。 当给定集合当中有一个空集时,结果也为空集
*
* @param keys 集合keys
*/
public <T> Set<T> sIntersect(List<String> keys, Class<T> clazz) {
Set<Object> members = opsForSet().intersect(keys);
return Objects.isNull(members) ? Collections.emptySet() : members.stream().map(v -> toPo(v, clazz)).collect(Collectors.toSet());
}
/**
* 将给定集合之间的交集存储在指定的集合中。如果指定的集合已经存在,则将其覆盖
*
* @param keys 集合keys
* @param destKey 指定目标key
* @return 交集成功数量
*/
public long sIntersectAndStore(List<String> keys, String destKey) {
return Optional.ofNullable(opsForSet().intersectAndStore(keys, destKey)).orElse(0L);
}
/**
* 返回第一个集合与其他集合之间的差异,也可以认为说第一个集合中独有的元素。不存在的集合 key 将视为空集。
*
* @param keys 集合keys
*/
public <T> Set<T> sDiff(String sourceKey, List<String> keys, Class<T> clazz) {
Set<Object> members = opsForSet().difference(sourceKey, keys);
return Objects.isNull(members) ? Collections.emptySet() : members.stream().map(v -> toPo(v, clazz)).collect(Collectors.toSet());
}
/**
* 将给定集合之间的差集存储在指定的集合中。如果指定的集合 key 已存在,则会被覆盖。
*
* @param sourceKey 源key
* @param keys 集合keys
* @param destKey 指定目标key
* @return 返回差集的数量
*/
public long sDiffAndStore(String sourceKey, List<String> keys, String destKey) {
return Optional.ofNullable(opsForSet().differenceAndStore(sourceKey, keys, destKey)).orElse(0L);
}
/**
* 返回给定集合的并集。不存在的集合 key 被视为空集。
*
* @param keys 集合keys
*/
public <T> Set<T> sUnion(List<String> keys, Class<T> clazz) {
Set<Object> members = opsForSet().union(keys);
return Objects.isNull(members) ? Collections.emptySet() : members.stream().map(v -> toPo(v, clazz)).collect(Collectors.toSet());
}
/**
* 将给定集合的并集存储在指定的集合 destination 中。如果 destination 已经存在,则将其覆盖。
*
* @param keys 集合keys
* @param destKey 指定目标key
* @return 返回并集结果的总数量
*/
public long sUnionAndStore(List<String> keys, String destKey) {
return Optional.ofNullable(opsForSet().unionAndStore(keys, destKey)).orElse(0L);
}
/**
* 迭代集合中键的元素
*
* @param key key
* @param keyword 关键字
*/
public <T> Set<T> sScan(String key, String keyword, Class<T> clazz) {
Set<T> values = new HashSet<>();
Cursor<Object> cursor = opsForSet().scan(key, ScanOptions.scanOptions().match(StrUtils.join(StringPool.ASTERISK, keyword, StringPool.ASTERISK)).build());
try {
while (cursor.hasNext()) {
Object value = cursor.next();
values.add(toPo(value, clazz));
}
} finally {
// 关闭游标
closeCursor(cursor);
}
return values;
}
5. ZSet类型
/**
* 向有序集合添加一个或多个成员,或者更新已存在成员的分数
*
* @param key key
* @param member 成员
* @param score 分数
* @param time 有效时间(单位:秒)
*/
public boolean zAdd(String key, Object member, double score, long time) {
Boolean signal = opsForZSet().add(key, member, score);
if (time != RedisConstant.NOT_EXPIRE) {
expire(key, time, TimeUnit.SECONDS);
}
return Objects.nonNull(signal) && signal;
}
/**
* 向有序集合添加一个或多个成员,或者更新已存在成员的分数
*
* @param key key
* @param member 成员
* @param score 分数
*/
public boolean zAdd(String key, Object member, double score) {
return zAdd(key, member, score, RedisConstant.NOT_EXPIRE);
}
/**
* 增加有序集合中成员的分数
*
* @param key key
* @param member 成员
* @param delta 分数
*/
public Double zIncrBy(String key, Object member, double delta) {
return opsForZSet().incrementScore(key, member, delta);
}
/**
* 计算在有序集合中指定分数区间的成员数
*
* @param key key
* @param min 分数区间最小值
* @param max 分数区间最大值
*/
public long zCount(String key, double min, double max) {
return Optional.ofNullable(opsForZSet().count(key, min, max)).orElse(0L);
}
/**
* 获取有序集合中成员的分数
*
* @param key key
* @param member 成员
*/
public Double zScore(String key, Object member) {
return opsForZSet().score(key, member);
}
/**
* 移除有序集合中的一个或多个成员
*
* @param key key
* @param members 移除成员列表
* @return 移除成功的数量
*/
public final long zRem(String key, List<Object> members) {
return Optional.ofNullable(opsForZSet().remove(key, members.toArray())).orElse(0L);
}
/**
* 移除有序集合中指定分数区间内的所有成员
*
* @param key key
* @param minScore 分数区间最小值
* @param maxScore 分数区间最大值
*/
public final long zRemRangeByScore(String key, double minScore, double maxScore) {
return Optional.ofNullable(opsForZSet().removeRangeByScore(key, minScore, maxScore)).orElse(0L);
}
/**
* 返回有序集合中指定成员的排名,其中分数值递增(从小到大)顺序排序
*
* @param key key
* @param member 成员
*/
public Long zRank(String key, Object member) {
return opsForZSet().rank(key, member);
}
/**
* 返回有序集合中成员的排名,其中分数值递减(从大到小)顺序排序
*
* @param key key
* @param member 成员
*/
public Long zRevRank(String key, Object member) {
return opsForZSet().reverseRank(key, member);
}
/**
* 获取有序集合的成员数
*
* @param key key
*/
public Long zCard(String key) {
return Optional.ofNullable(opsForZSet().zCard(key)).orElse(0L);
}
/**
* 根据索引范围获取有序集合的成员
*
* @param key key
* @param start 索引从 0 开始。如果索引是负数,表示从有序集合的末尾开始计数
* @param end 结束 0 到 -1代表所有值
*/
public Set<Object> zRange(String key, long start, long end) {
return opsForZSet().range(key, start, end);
}
/**
* 根据索引范围获取有序集合的成员
*
* @param key key
* @param start 索引从 0 开始。如果索引是负数,表示从有序集合的末尾开始计数
* @param end 结束 0 到 -1代表所有值
* @param clazz 泛型
*/
public <T> Set<T> zRange(String key, long start, long end, Class<T> clazz) {
Set<Object> dataList = zRange(key, start, end);
return Objects.isNull(dataList) ? Collections.emptySet() : dataList.stream().map(v -> toPo(v, clazz)).collect(Collectors.toSet());
}
/**
* 返回有序集合中指定分数区间内的所有成员(按分数值递减(从小到大)来排序)
*
* @param key key
* @param min 分数区间最小值
* @param max 分数区间最大值
*/
public Set<Object> zRangeByScore(String key, double min, double max) {
return opsForZSet().rangeByScore(key, min, max);
}
/**
* 返回有序集合中指定分数区间内的所有成员(按分数值递减(从小到大)来排序)
*
* @param key key
* @param min 分数区间最小值
* @param max 分数区间最大值
* @param clazz 泛型
*/
public <T> Set<T> zRangeByScore(String key, double min, double max, Class<T> clazz) {
Set<Object> dataList = zRangeByScore(key, min, max);
return Objects.isNull(dataList) ? Collections.emptySet() : dataList.stream().map(v -> toPo(v, clazz)).collect(Collectors.toSet());
}
/**
* 返回有序集合中分数值在指定区间内的所有成员(按分数值递减(从大到小)来排序)
*
* @param key key
* @param min 分数区间最小值
* @param max 分数区间最大值
*/
public Set<Object> zRevRangeByScore(String key, double min, double max) {
return opsForZSet().reverseRangeByScore(key, min, max);
}
/**
* 返回有序集合中分数值在指定区间内的所有成员(按分数值递减(从大到小)来排序)
*
* @param key key
* @param min 分数区间最小值
* @param max 分数区间最大值
* @param clazz 泛型
*/
public <T> Set<T> zRevRangeByScore(String key, double min, double max, Class<T> clazz) {
Set<Object> dataList = zRevRangeByScore(key, min, max);
return Objects.isNull(dataList) ? Collections.emptySet() : dataList.stream().map(v -> toPo(v, clazz)).collect(Collectors.toSet());
}
/**
* 按照分数值递减(从大到小)顺序返回有序集合指定区间内的成员
*
* @param key key
* @param start 索引从 0 开始。如果索引是负数,表示从有序集合的末尾开始计数
* @param end 结束 0 到 -1代表所有值
* @param clazz 泛型
*/
public <T> Set<T> zReverseRange(String key, long start, long end, Class<T> clazz) {
Set<Object> dataList = opsForZSet().reverseRange(key, start, end);
return Objects.isNull(dataList) ? Collections.emptySet() : dataList.stream().map(v -> toPo(v, clazz)).collect(Collectors.toSet());
}
/**
* 迭代集合中键的元素
*
* @param key key
* @param keyword 关键字
*/
public <T> Set<T> zScan(String key, String keyword, Class<T> clazz) {
Set<T> values = new HashSet<>();
Cursor<ZSetOperations.TypedTuple<Object>> cursor = opsForZSet().scan(key, ScanOptions.scanOptions().match(StrUtils.join(StringPool.ASTERISK, keyword, StringPool.ASTERISK)).build());
try {
while (cursor.hasNext()) {
JSONObject json = toPo(toJson(cursor.next()), JSONObject.class);
if (Objects.nonNull(json)) {
values.add(toPo(json.get("value"), clazz));
}
}
} finally {
// 关闭游标
closeCursor(cursor);
}
return values;
}
6. Bit类型
/**
* hash函数
*
* @param value 值
* @param hashCount hash次数
* @param bitMapSize 位图大小
*/
private int[] hash(String value, int hashCount, int bitMapSize) {
int[] offsets = new int[hashCount];
CRC32 crc = new CRC32();
for (int i = 0; i < hashCount; i++) {
crc.update((value + i).getBytes(StandardCharsets.UTF_8));
long hash = crc.getValue();
offsets[i] = (int) (hash % bitMapSize);
crc.reset();
}
return offsets;
}
/**
* 位图,添加值
*
* @param key redisKey
* @param value 值
*/
public void bitAdd(String key, String value) {
bitAdd(key, value, RedisConstant.BITMAP_HASH_FUNCTION_COUNT, RedisConstant.BITMAP_DEFAULT_SIZE);
}
/**
* 位图,添加值
*
* @param key redisKey
* @param value 值
* @param hashCount hash次数
* @param bitMapSize 位图大小
*/
public void bitAdd(String key, String value, int hashCount, int bitMapSize) {
int[] positions = hash(value, hashCount, bitMapSize);
for (int pos : positions) {
redisTemplate.opsForValue().setBit(key, pos, true);
}
}
/**
* 判断是否存在值
*
* @param key redisKey
* @param value 值
*/
public boolean bitExist(String key, String value) {
return bitExist(key, value, RedisConstant.BITMAP_HASH_FUNCTION_COUNT, RedisConstant.BITMAP_DEFAULT_SIZE);
}
/**
* 判断是否存在值
*
* @param key redisKey
* @param value 值
* @param hashCount hash次数
* @param bitMapSize 位图大小
*/
public boolean bitExist(String key, String value, int hashCount, int bitMapSize) {
int[] positions = hash(value, hashCount, bitMapSize);
for (int pos : positions) {
if (Boolean.FALSE.equals(redisTemplate.opsForValue().getBit(key, pos))) {
return false;
}
}
return true;
}
五、生产级特性实现
1. 分布式ID生成器
package com.coder.framework.redis.core.generator;
/**
* ID生成器接口
*
* 提供多种类型的ID生成方法
* 由Liberty编写
*/
public interface NumericIdGenerator {
/**
* 生成自增ID
* @return 生成的ID
*/
String generateId();
/**
* 生成自增大ID
* @return 生成的ID
*/
String generateBigId();
/**
* 生成有顺序的ID
*
* @param key 场景标识
* @param count 长度
* @return 生成的有序ID
*/
String generateOrderId(String key, int count);
/**
* 生成自增Long类型ID
* @return 生成的Long类型ID
*/
Long generateLongId();
}
package com.coder.framework.redis.core.generator;
import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.util.IdUtil;
import jodd.util.StringPool;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.StringRedisTemplate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Optional;
/**
* Redis ID生成器实现类
* <p>
* 使用Redis和Snowflake算法生成各种类型的ID
* 由Liberty编写
*/
@RequiredArgsConstructor
public class RedisNumericIdGenerator implements NumericIdGenerator {
private static final DateTimeFormatter ID_PREFIX_FORMAT = DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS");
private static final Snowflake SNOWFLAKE = IdUtil.getSnowflake(0, 1);
private final StringRedisTemplate redisTemplate;
private final String prefixKey;
@Override
public String generateId() {
return Optional.ofNullable(redisTemplate.opsForValue().increment(prefixKey, 1))
.map(id -> {
if (id > 9900L) {
redisTemplate.delete(prefixKey);
}
return String.format("%s%04d", LocalDateTime.now().format(ID_PREFIX_FORMAT), id);
})
.orElseThrow(() -> new IllegalStateException("Failed to generate ID!"));
}
@Override
public String generateBigId() {
return Optional.ofNullable(redisTemplate.opsForValue().increment(prefixKey, 1))
.map(id -> {
if (id > 990000L) {
redisTemplate.delete(prefixKey);
}
return String.format("%s%06d", LocalDateTime.now().format(ID_PREFIX_FORMAT), id);
})
.orElseThrow(() -> new IllegalStateException("Failed to generate big ID!"));
}
@Override
public String generateOrderId(String key, int count) {
return Optional.ofNullable(redisTemplate.opsForValue().increment(prefixKey + StringPool.COLON + key, 1))
.map(id -> String.format("%0" + count + "d", id % (long) Math.pow(10, count)))
.orElseThrow(() -> new IllegalStateException("Failed to generate order ID!"));
}
@Override
public Long generateLongId() {
return Optional.of(SNOWFLAKE.nextId())
.orElseThrow(() -> new IllegalStateException("Failed to generate long ID!"));
}
}
2. 游标搜索key
/**
* Redis scan key
*
* @param keyword 关键字
*/
public List<String> scanKeys(String keyword) {
List<String> keys = new ArrayList<>();
ScanOptions options = ScanOptions.scanOptions().match("*" + keyword + "*").build();
RedisConnectionFactory connectionFactory = redisTemplate.getRequiredConnectionFactory();
try (RedisConnection connection = connectionFactory.getConnection()) {
RedisKeyCommands keyCommands = connection.keyCommands();
try (Cursor<byte[]> cursor = keyCommands.scan(options)) {
while (cursor.hasNext()) {
keys.add(new String(cursor.next(), StandardCharsets.UTF_8));
}
} catch (Exception e) {
throw new RuntimeException("Cursor iteration failed", e);
}
}
return keys;
}
六、最佳实践建议
- 序列化规范:
- KEY统一使用String序列化
- VALUE使用Jackson序列化
- 处理Java8时间类型
七、性能压测数据
操作类型 | QPS(单节点) | 平均耗时 | 注意事项 |
---|---|---|---|
String读写 | 12万 | 0.8ms | 避免大Value(>10KB) |
Hash字段读取 | 8万 | 1.2ms | 控制field数量<1000 |
ZSet范围查询 | 5万 | 2.1ms | 避免返回大量结果 |
管道批量写入 | 25万 | - | 建议批量操作100-500条 |
文章总结:本文基于企业级生产实践,深度讲解了Spring Boot 3.0集成Redis的高级用法。通过封装RedisOps工具类,实现了:
- 全数据结构的便捷操作
- 智能序列化机制
- 管道批量处理能力
- 分布式ID生成方案
- 生产级安全规范
完整源码获取
👉 点击获取源码