Redis是否适合带复合键的通知服务?需频繁按部分键查询
一、Redis完全适配你的场景
你的需求里数据具备临时性(天然支持TTL自动过期)、峰值规模仅30万条,这个量级Redis完全可以轻松承载,无需额外维护过期数据清理逻辑,比关系型数据库更省心,没必要换用关系型DB。
二、复合键方案的优化:替换KEYS命令
你用userId:notificationType:notificaionId作为键的思路是合理的,但KEYS [userId]*在生产环境绝对不能用——该命令会遍历Redis所有键,数据量上升时会阻塞服务。推荐几个更优的替代方案:
1. 用Sorted Set维护用户通知集合
给每个用户单独维护一个Sorted Set,键名设为user:notifications:{userId},将每个通知的完整键(userId:notificationType:notificaionId)作为Set成员,用通知创建时间作为score。这样做的优势:
- 获取用户所有通知:执行
ZRANGE user:notifications:{userId} 0 -1即可拿到所有通知键,再用MGET批量获取具体内容 - 天然支持按时间排序,完美匹配通知抽屉的展示需求
- 单个通知键可独立设置TTL,过期后自动失效(Redis 4.0+可通过键过期事件自动从Sorted Set移除无效成员,也可定期清理)
- Spring Redis中的实现示例:
// 注入ZSetOperations @Autowired private ZSetOperations<String, String> zSetOperations; @Autowired private StringRedisTemplate stringRedisTemplate; // 添加通知到用户的Sorted Set public void addNotificationToUser(String userId, String notificationKey, long createTimestamp) { zSetOperations.add("user:notifications:" + userId, notificationKey, createTimestamp); } // 获取用户所有通知 public List<Notification> getUserNotifications(String userId) { Set<String> notificationKeys = zSetOperations.range("user:notifications:" + userId, 0, -1); return stringRedisTemplate.opsForValue().multiGet(notificationKeys); }
2. 用Hash结构直接存储用户通知
如果通知不需要按时间排序,也可以用Hash结构:键名设为user:notifications:{userId},field为notificationType:notificaionId,value为序列化后的通知内容。优势:
- 获取所有通知:
HGETALL user:notifications:{userId}可直接拿到所有键值对 - 单个通知的更新/删除用
HSET/HDEL即可完成 - 可给整个Hash设置TTL,或额外用Sorted Set记录field的过期时间做定期清理
3. 退而求其次:用SCAN替代KEYS
如果不想调整现有键结构,可用SCAN命令渐进式遍历匹配{userId}:*的键,它不会阻塞Redis服务。Spring Redis中的实现示例:
public Set<String> getUserNotificationKeys(String userId) { Set<String> keys = new HashSet<>(); ScanOptions options = ScanOptions.scanOptions().match(userId + ":*").count(100).build(); Cursor<String> cursor = stringRedisTemplate.executeWithStickyConnection(redisConnection -> redisConnection.scan(options)); while (cursor.hasNext()) { keys.add(cursor.next()); } cursor.close(); return keys; }
三、要不要转关系型数据库?
如果你的需求未来不会涉及复杂多条件组合查询(比如同时按通知类型、时间范围、已读状态筛选),完全没必要转。关系型数据库虽然索引灵活,但需要自己编写定时任务清理过期数据,且针对这种高频读、临时数据的场景,Redis的性能和易用性更具优势。如果未来确实需要复杂查询,再考虑Redis做缓存、关系型DB做持久化的混合方案也不迟。
内容的提问来源于stack exchange,提问作者intA

