三.redis 排序

本篇文章介绍下redis排序命令.redis支持对list,set和sorted set元素的排序。排序命令是sort 完整的命令格式如下:
SORT key [BY pattern] [LIMIT start count] [GET pattern] [ASC|DESC] [ALPHA] [STORE dstkey] 
下面我们一一说明各种命令选项
(1)sort key
这个是最简单的情况,没有任何选项就是简单的对集合自身元素排序并返回排序结果.下面给个例子

redis> lpush ml 12
(integer) 1
redis> lpush ml 11
(integer) 2
redis> lpush ml 23
(integer) 3
redis> lpush ml 13
(integer) 4
redis> sort ml
1. "11"
2. "12"
3. "13"
4. "23"

(2)[ASC|DESC] [ALPHA]
sort默认的排序方式(asc)是从小到大排的,当然也可以按照逆序或者按字符顺序排。逆序可以加上desc选项,想按字母顺序排可以加alpha选项,当然alpha可以和desc一起用。下面是个按字母顺序排的例子

redis> lpush mylist baidu
(integer) 1
redis> lpush mylist hello
(integer) 2
redis> lpush mylist xhan
(integer) 3
redis> lpush mylist soso
(integer) 4
redis> sort mylist
1. "soso"
2. "xhan"
3. "hello"
4. "baidu"
redis> sort mylist alpha
1. "baidu"
2. "hello"
3. "soso"
4. "xhan"
redis> sort mylist desc alpha
1. "xhan"
2. "soso"
3. "hello"
4. "baidu"
(3)[BY pattern]
    除了可以按集合元素自身值排序外,还可以将集合元素内容按照给定pattern组合成新的key,并按照新key中对应的内容进行排序。下面的例子接着使用第一个例子中的ml集合做演示:  
redis> set name11 nihao
OK
redis> set name12 wo
OK
redis> set name13 shi
OK
redis> set name23 lala
OK
redis> sort ml by name*
1. "13"
2. "23"
3. "11"
4. "12"
*代表了ml中的元素值,所以这个排序是按照name12 name13 name23 name23这四个key对应值排序的,当然返回的还是排序后ml集合中的元素

(4)[GET pattern]
上面的例子都是返回的ml集合中的元素。我们也可以通过get选项去获取指定pattern作为新key对应的值。看个组合起来的例子 
redis> sort ml by name* get name*  alpha
1. "lala"
2. "nihao"
3. "shi"
4. "wo"
这 次返回的就不在是ml中的元素了,而是name12 name13 name23 name23对应的值。当然排序是按照name12 name13 name23 name23值并根据字母顺序排的。另外get选项可以有多个。看例子(#特殊符号引用的是原始集合也就是ml)
redis> sort ml by name* get name* get #  alpha
1. "lala"
2. "23"
3. "nihao"
4. "11"
5. "shi"
6. "13"
7. "wo"
8. "12"
最后在还有一个引用hash类型字段的特殊字符->,下面是例子
redis> hset user1 name hanjie
(integer) 1
redis> hset user11 name hanjie
(integer) 1
redis> hset user12 name 86
(integer) 1
redis> hset user13 name lxl
(integer) 1
redis> sort ml get user*->name
1. "hanjie"
2. "86"
3. "lxl"
4. (nil)
很容易理解,注意当对应的user23不存在时候返回的是nil

(5) [LIMIT start count]

上面例子返回结果都是全部。limit选项可以限定返回结果的数量。例子
redis> sort ml get name* limit 1 2
1. "wo"
2. "shi"
start下标是从0开始的,这里的limit选项意思是从第二个元素开始获取2个

(6)[STORE dstkey] 
如果对集合经常按照固定的模式去排序,那么把排序结果缓存起来会减少不少cpu开销.使用store选项可以将排序内容保存到指定key中。保存的类型是list
redis> sort ml get name* limit 1 2 store cl
(integer) 2
redis> type cl
list
redis> lrange cl 0 -1
1. "wo"
2. "shi"
这个例子我们将排序结果保存到了cl中
      功能介绍完后,再讨论下关于排序的一些问题。如果我们有多个redis server的话,不同的key可能存在于不同的server上。比如name12 name13 name23 name23,很有可能分别在四个不同的server上存贮着。这种情况会对排序性能造成很大的影响。redis作者在他的blog上提到了这个问题的解 决办法,就是通过key tag将需要排序的key都放到同一个server上 。由于具体决定哪个key存在哪个服务器上一般都是在client端hash的办法来做的。我们可以通过只对key的部分进行hash.举个例子假如我们 的client如果发现key中包含[]。那么只对key中[]包含的内容进行hash。我们将四个name相关的key,都这样命名[name]12 [name]13 [name]23 [name]23,于是client 程序就会把他们都放到同一server上。不知道jredis实现了没。
       还有一个问题也比较严重。如果要sort的集合非常大的话排序就会消耗很长时间。由于redis单线程的,所以长时间的排序操作会阻塞其他client的 请求。解决办法是通过主从复制机制将数据复制到多个slave上。然后我们只在slave上做排序操作。并进可能的对排序结果缓存。另外就是一个方案是就 是采用sorted set对需要按某个顺序访问的集合建立索引。
 
转自:http://www.cnblogs.com/xhan/archive/2011/02/03/1948970.html
<think>我们正在处理一个关于Spring Data Redis的异常:RedisSystemException嵌套了一个ClassCastException。 根据经验,这种异常通常发生在从Redis中获取数据并尝试转换为期望类型时,实际存储的类型与期望的类型不匹配。 我们参考了用户提供的引用内容,但并没有直接相关的引用。因此,我们将基于Spring Data Redis的常见问题来解决。 可能的原因: 1. 存储到Redis中的对象类型与从Redis中读取时期望的类型不一致。 2. 使用了不同的序列化方式,导致存储和读取时类型信息不一致。 解决方案: 1. 检查存储和读取的代码,确保类型一致。 2. 配置RedisTemplate使用一致的序列化器,特别是key和value的序列化器。 步骤: 1. 确认异常发生的具体位置,查看堆栈信息,确定是哪个Key在转换时出错。 2. 检查存储该Key的代码和读取该Key的代码,确保两者使用的类型是兼容的。 3. 确保RedisTemplate配置了合适的序列化器。通常,我们建议使用Jackson2JsonRedisSerializer或者GenericJackson2JsonRedisSerializer来序列化值,这样可以在存储时包含类型信息。 配置示例: 我们可以配置RedisTemplate如下: ```java @Configuration public class RedisConfig { @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(factory); // 使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值 Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class); ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(om); // 设置key的序列化器为String序列化器 template.setKeySerializer(new StringRedisSerializer()); template.setValueSerializer(jackson2JsonRedisSerializer); // 设置hash key和value的序列化器 template.setHashKeySerializer(new StringRedisSerializer()); template.setHashValueSerializer(jackson2JsonRedisSerializer); template.afterPropertiesSet(); return template; } } ``` 在这个配置中,我们使用Jackson2JsonRedisSerializer来序列化值,它会在存储时添加类型信息,这样在读取时就能正确地反序列化为原始类型。 4. 如果已经存在了使用其他序列化方式存储的数据,那么可能需要清除旧数据或者编写迁移脚本。 5. 另外,如果使用了多个应用操作同一个Redis,也要确保它们使用相同的序列化配置。 6. 注意:如果存储的是一个集合类型(如List、Set等),而读取时试图转换为一个不同的集合类型(或者单个对象),也会导致类型转换异常。 举例: 如果你存储了一个List<User>,但是读取时试图将其转换为一个User对象,那么就会抛出ClassCastException。 因此,需要确保读取时的类型与存储时的类型完全匹配。 7. 使用Redis命令查看存储的数据类型和内容: - 使用redis-cli连接到Redis服务器。 - 使用`TYPE key`命令查看key的类型。 - 使用`GET key`(对于字符串)或`HGETALL key`(对于哈希)等命令查看存储的内容,可以判断序列化是否正确。 8. 如果问题仍然存在,可以尝试在读取代码中捕获异常,并打印出实际存储的值(以十六进制或字符串形式),然后分析其序列化方式。 总结: 解决这个问题的关键是确保序列化方式的一致性和类型的一致性。 相关问题: 1. 如何配置Spring Data Redis使用JSON序列化? 2. 如何在Redis中查看存储的数据? 3. 如何处理Redis中已有的旧数据与新序列化方式不兼容的问题? 4. Spring Data Redis有哪些常见的序列化器? 5. 如何避免Redis中的数据因序列化方式改变而无法读取? </think>### 解决 `RedisSystemException` 嵌套 `ClassCastException` 异常的方案 #### 问题原因分析 该异常通常是由 **Redis 存储的数据类型与 Java 代码预期的类型不匹配** 导致,常见于以下场景: 1. **序列化/反序列化不一致** - 存入 Redis 时使用一种序列化器(如 JDK 序列化),读取时使用另一种(如 JSON 序列化)[^1][^4]。 2. **数据类型误用** 例如:存储的是 `Hash` 结构,但尝试作为 `String` 读取;或存储 `List` 却试图转换为 `Map`。 3. **泛型类型擦除** 反序列化时因泛型信息丢失,无法正确还原原始类型(如 `List<User>` 被反序列化为 `List<Object>`)。 4. **多线程环境问题** 线程竞争导致部分写入未完成时被读取(需结合线程池策略和同步机制)[^2][^3]。 --- #### 解决方案(按优先级排序) ##### 1. 统一序列化配置(推荐) 在 `RedisTemplate` 中显式配置序列化器,确保读写一致: ```java @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(factory); // 使用 JSON 序列化器(避免 ClassCastException) template.setKeySerializer(new StringRedisSerializer()); template.setValueSerializer(new GenericJackson2JsonRedisSerializer()); // 针对 Hash 结构的序列化 template.setHashKeySerializer(new StringRedisSerializer()); template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer()); template.afterPropertiesSet(); return template; } ``` > **关键点**: > - `GenericJackson2JsonRedisSerializer` 会在 JSON 中存储类型信息(如 `"@class": "com.example.User"`),解决类型擦除问题。 > - 避免混用 `JdkSerializationRedisSerializer`(易导致二进制数据不兼容)。 ##### 2. 显式指定数据类型 在读写操作中明确声明类型: ```java // 写入时指定类型 redisTemplate.opsForValue().set("user:1", new User("Alice"), Duration.ofMinutes(30)); // 读取时强制转换(确保类型安全) User user = (User) redisTemplate.opsForValue().get("user:1"); ``` ##### 3. 检查 Redis 实际存储的数据类型 通过 Redis CLI 验证数据类型是否与代码预期一致: ```bash # 查看 Key 的类型 > TYPE your_key # 输出应为 string/hash/list 等 # 检查存储的值 > GET your_key # String 类型 > HGETALL your_key # Hash 类型 ``` 若发现数据类型错误,需修复写入逻辑。 ##### 4. 使用异常处理器兜底 结合 `@ControllerAdvice` 捕获并处理异常,避免服务中断[^1]: ```java @ControllerAdvice public class RedisExceptionHandler { @ExceptionHandler(RedisSystemException.class) public ResponseEntity<String> handleRedisException(RedisSystemException ex) { if (ex.getCause() instanceof ClassCastException) { return ResponseEntity.badRequest().body("Redis 数据类型不兼容: " + ex.getMessage()); } return ResponseEntity.internalServerError().body("Redis 服务异常"); } } ``` --- #### 预防措施 1. **规范序列化**: - 所有服务使用相同的序列化配置(如统一 JSON 序列化)。 2. **类型隔离**: - 不同业务使用独立 Key 前缀(如 `order:{id}`, `user:{id}`)。 3. **写入验证**: - 在写入前检查数据类型(如 `instanceof`)。 4. **线程安全**: - 对 Redis 操作加锁(使用 `synchronized` 或 `ReentrantLock`)[^3]。 - 配置线程池拒绝策略(如 `CallerRunsPolicy` 防止任务丢失)[^2]。 > **注意**:若使用第方序列化工具(如 CGLib 动态代理),需确保代理类可被序列化[^4]。 --- #### 相关问题 1. **如何选择 Redis 序列化器?** JSON 序列化 vs JDK 序列化的性能差异是什么? 2. **Spring Data Redis 中 `GenericJackson2JsonRedisSerializer` 的优缺点?** 它如何处理嵌套泛型类型? 3. **Redis 集群环境下类型转换异常有何特殊性?** 如何保证多节点间的序列化一致性? 4. **如何设计 Redis Key 命名规范以避免类型冲突?** 有哪些业界最佳实践? [^1]: 引用自异常处理配置示例 [^2]: 引用自线程池拒绝策略说明 [^3]: 引用自同步锁原理 [^4]: 引用自动态代理序列化实现
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值