消息未读数量发生变化后,应在数据库更新成功后及时删除或刷新对应用户的缓存,确保查询结果与实际数据一致,通常优先采用“先更新数据库,再删除缓存”的策略,后续查询缓存未命中时重新统计未读数量并写入缓存,同时设置合理的过期时间,避免异常导致缓存长期失效,针对并发更新或删除失败,可结合延迟双删、消息队列重试等机制降低数据不一致风险。
未读消息数量改了,缓存却没更新?一文讲清缓存刷新与清除的正确姿势
做过消息中心、站内信或者聊天系统的开发者,基本都遇到过这样的情况:用户明明已经点了“全部已读”,右上角的小红点却还在;数据库里的未读数量已经变成了0,页面查询出来的结果却还是5。
这类问题看起来只是一个数字没有刷新,背后涉及的却是数据库、缓存和前端状态之间的一致性。消息未读数量对应的数据修改后,到底应该更新缓存,还是直接清除缓存?
今天就把这件事讲明白,并给出可以落地的处理思路。
先明确:未读数量的缓存究竟存了什么?
假设系统用数据库存储消息记录,每条记录包含用户ID、消息ID、是否已读等字段,查询某个用户的未读数量时,可以执行类似下面的SQL:
SELECT COUNT(*) FROM user_message WHERE user_id = ? AND is_read = 0;
用户量不大时,直接查数据库通常就够了,但如果首页、导航栏、消息页都频繁查询未读数,数据库就可能反复执行相同的统计操作。
很多系统会把结果放入Redis,
message:unread:10001 = 5
后续查询先读取Redis,缓存不存在时再查询数据库,并把结果写回缓存。
问题也随之而来:数据库里的消息状态改变了,Redis并不会自动感知。缓存刷新必须由业务逻辑或可靠的事件处理机制触发。
常规业务的首选:数据库提交成功后,删除缓存
对于大多数普通消息系统,推荐优先采用“先更新数据库,再删除缓存”的方式,而不是在每次操作后重新计算并覆盖缓存。
以“标记消息为已读”为例:
- 更新数据库中的消息状态。
- 确认数据库事务提交成功。
- 删除对应用户的未读数量缓存。
- 下一次查询未读数时,重新统计并写入缓存。
伪代码如下:
事务内:
将指定消息更新为已读
事务提交成功后:
删除 message:unread:{userId}
为什么强调“事务提交成功后”?
因为如果数据库事务尚未提交就删除缓存,其他请求可能趁缓存缺失时查到数据库里的旧数据,再把旧未读数写回Redis,这样即使事务随后成功提交,缓存仍可能是旧值。
同样,不建议先删除缓存再修改数据库,这会增加旧数据被重新加载的机会。
不过要注意,先提交数据库、再删除缓存,也不是严格一致性的保证,例如某个并发查询已经读到了旧数据,在删除动作完成后才回填缓存,仍然可能造成短暂不一致。
对一般站内信场景,可以接受短暂延迟,并通过合理过期时间、删除失败重试等方式控制影响;对一致性要求更高的场景,则需要增加版本校验、协调读写或使用更严格的数据访问方案。
为什么不直接把缓存减1?
有人会问:用户读了一条消息,执行一次Redis的DECR,不是更快吗?
确实更快,但也更容易踩坑。
用户连续点击两次,或者网络重试导致同一个“已读”请求执行两遍,如果两次都减1,实际只有一条消息变为已读,缓存却减少了两次。
正确处理至少要做到:
- 数据库更新带上“当前仍未读”的条件。
- 根据实际受影响的行数计算变化量。
- 避免同一个事件重复扣减。
- 确保缓存不存在时不会直接减成负数。
- 考虑数据库更新成功、Redis操作失败后的补偿。
UPDATE user_message SET is_read = 1 WHERE user_id = ? AND message_id = ? AND is_read = 0;
只有这次更新确实影响了1行,才代表未读数量发生了变化。
即便如此,数据库和Redis之间的双写一致性问题依然存在。原子加减适合经过专门设计的计数系统,不应被当成普通缓存的万能解决方案,如果业务量尚未达到需要精细优化的程度,删除缓存往往更简单,也更容易维护。
删除缓存失败了,该怎么办?
数据库已经提交成功,但Redis恰好超时,缓存没有删除,这是实际生产环境中必须考虑的问题。
常见的兜底方案有三种。
第一,设置合理的缓存过期时间。
例如根据业务允许的延迟设置几十秒或数分钟的有效期,并加入少量随机值,降低大量缓存同时失效的风险,过期时间只能限制旧值持续多久,不能代替正常的失效通知。
第二,建立可靠重试机制。
删除失败后记录日志并进入重试流程,而不是简单捕获异常后就不管了,对重要业务,可以使用事务消息表,也就是Outbox模式:业务修改和待处理事件在同一数据库事务里提交,再由后台任务持续投递和处理缓存失效事件。
第三,安排周期性校准。
如果采用Redis计数器维护未读数,可以定期与数据库统计结果对账,及时纠正计数漂移。
对于未读数量这类通常允许最终一致的数据,可靠失效通知加过期兜底,往往比堆叠复杂方案更划算。
后端缓存清了,前端也需要同步
还有一种常见误会:后端已经清除Redis,页面上的数字却没有变化,于是开发者继续排查Redis。
前端可能还保存着自己的查询缓存或者组件状态。
“全部已读”成功后,前端应该主动使未读数查询失效并重新请求;也可以先做乐观更新,再通过服务端返回结果校准,如果需要多端同步,可以通过WebSocket或SSE发送“未读数量已变化”的通知,让客户端重新拉取。
不要只修改当前按钮旁边的数字,却忘记导航栏、其他页面以及另一台设备上的状态。
服务器怎么选,才能让消息系统运行得更稳?
消息系统的稳定性,不仅取决于代码,也取决于数据库、Redis和应用服务的部署条件,CPU、内存、磁盘性能,以及服务之间的网络时延,都会影响查询和缓存失效操作的执行效果。
如果你正在为这类业务选购服务器,可以把慈云数据列入咨询和比较名单,根据自己的并发量、数据规模与预算,了解适合的服务器方案。
选购时不要只看价格,建议重点确认:
- 应用、数据库和Redis之间的网络连通与时延。
- 内存容量是否能够覆盖缓存规模并留有余量。
- 磁盘性能、备份能力及故障恢复安排。
- 带宽、扩容方式、服务支持范围和实际费用。
- 是否支持测试,并用接近真实业务的负载进行验证。
慈云数据的具体配置、价格与服务承诺,应以实际咨询和合同约定为准,服务器配置再高,也无法自动解决缓存一致性问题;合理的程序设计加上匹配的基础设施,才是稳定运行的关键。
先把简单方案做可靠,再考虑复杂优化
回到最初的问题:消息未读数量对应的数据修改后,如何更新或清除缓存?
对于大多数业务,一个实用的起点是:
数据库事务提交成功后,删除对应用户的未读数缓存;查询时按需重建;同时配置过期时间、删除失败重试和前端主动刷新。
如果业务规模很大,再考虑事件驱动的计数维护、幂等处理、版本控制和定期校准。
小小的未读数字,串联的是完整的数据链路,把这条链路设计清楚,再结合包括慈云数据在内的服务器方案进行实际测试和选型,才能避免“数据库早就改了,小红点却一直不消失”的尴尬。
版权声明
本文仅代表作者观点,不代表本站立场。
本文系作者授权发表,未经许可,不得转载。







最新留言