缓存同步之后,还要考虑没收到消息的节点

假设 IMBoy 部署了两个服务节点,A 和 B。

用户把昵称从"小张"改成"老张"。A 更新了数据库,也清掉了自己的旧缓存。但下一次请求落到 B,页面上显示的还是"小张"。

原因很简单:B 还留着修改前的缓存。

前面两篇文章,就是围绕这件事一步步展开的。第一篇介绍为什么选择 depcache,解决本地缓存的存取、过期等问题;第二篇给它加上节点间广播,让一个节点的变更能通知其他节点。

沿着这个思路,还得再问一句:如果 B 当时没收到通知呢?

这就是这次继续改进 imboy_cache 的原因。

发出通知,不等于大家都更新了

两个节点一直连通时,广播很好理解:A 发消息,B 收到后处理。

但如果 A 更新昵称时,A 和 B 之间刚好断网,B 就可能错过这次通知。网络恢复以后,B 仍然可以提供服务,却未必知道自己少收了一条消息。

节点重启也是类似的问题,不过表现不同:本地内存缓存会丢失。它重新加入集群以后,要考虑的是如何恢复需要的缓存,以及恢复期间怎样处理其他节点的新变化。

所以,前篇提到的"自动同步"和"最终一致",需要补上条件。广播提供了通知通道,但漏掉的消息能不能发现、发现后怎么办,还得另做处理。

有些数据,通知对方删掉就够了

拿用户昵称来说,数据库里已经有最新值。B 没必要非得收到一整份用户资料;它只要知道"这份缓存过时了",下次查询时重新读数据库即可。

两种做法可以这样区分:

text 复制代码
复制缓存:A 把新值发给 B,B 保存新值。
使缓存失效:A 通知 B 丢掉旧值,B 下次需要时重新查询。

它们都用到了节点间通信,但要解决的问题不同。

复制值时,要考虑收到的究竟是新值还是旧值。只通知失效,也仍要考虑通知有没有漏掉。不能因为消息里只带了一个键,就认为同步问题已经消失了。

现在的 imboy_cache 把本地缓存、复制值、通知失效区分开来。哪些数据允许复制,需要显式登记,不能把任意缓存都直接发到其他节点。

对可以从数据库重新查到的数据,我更愿意先问:只让旧缓存失效,够不够?够用的话,就不必多维护一份跨节点副本。

先发现自己漏收了消息

假设 A 发出的更新依次编号为 21、22、23。

B 已经处理到 21,下一条却收到了 23,它就能发现中间少了一条。只有变更内容、没有编号的广播,做不到这一点。

这次实现给同步消息加上了版本信息,也区分节点重启前后的身份。这样,旧进程留下的迟到消息,就不会只凭节点名称相同,被直接当成当前更新。

不过,编号还解决不了一种情况:B 漏掉的恰好是最后一条通知,后面一直没有新消息。

没有下一条消息提醒它跳号,B 可能一直不知道自己落后了。因此还需要定期向其他节点核对进度,而不能完全依赖下一次业务写入。

这里多做的这些事,目的都一样:让节点有机会发现"我现在这份缓存可能不可靠"。

恢复缓存时,最怕把旧值又放回来

发现缺口以后,可以向其他节点获取一份缓存快照,也就是把对方当前保存的数据分批拿过来。

难点在于,传输期间系统还在运行。

例如,B 正在接收某个用户的资料,A 刚好删除了这份缓存。如果 B 随后把先前收到的旧资料写进去,被删掉的数据就又出现了。

同样的问题也会发生在数据库查询上:一个请求开始查询旧资料,查询还没结束,另一个请求已经修改资料并清掉缓存;前一个请求回来后,又把旧结果填进缓存。

这次实现用版本和缓存代次检查这些写入。简单说,就是写回之前再确认:这份结果现在还算数吗? 已经失效的旧结果,不能仅仅因为回来得晚,就重新生效。

恢复也不能无限等待或无限占内存。快照需要分页,条目和字节数要有限制,恢复过程要有期限。条件不满足时,应进入降级状态,不能把"收到了一部分"当作"已经恢复完整"。

本地缓存仍然交给 depcache

这些改动没有替换 depcache。它仍然负责本地缓存,新增的同步模块负责节点间的版本、通知和恢复。

新机制也没有默认套到所有缓存上。只有配置中明确启用的缓存类别才走这套保护,方便逐步接入和验证。

还有一个容易忽略的细节:滚动升级时,新旧节点可能同时运行。新节点发出的失效通知,旧节点也必须能理解。因此兼容期间仍要保留旧格式的失效消息。

这类改动在正常读写时不显眼,却决定了断网、重启、升级时会发生什么。

这次做到哪里

当前代码已经加入版本检查、缺口发现、定期核对、快照恢复,以及阻止旧结果回填的保护。

但完整恢复仍有未验收的部分,尤其是多个来源同时变化时,快照和后续更新能否完整衔接。已有的逐项保护,不能直接推导成"任意故障后都能恢复正确",也不能据此宣称生产验证已经完成。

如果你也在给本地缓存加广播,可以先画出一个最简单的过程:A 更新,B 断网,A 再更新,B 恢复连接。然后检查 B 靠什么发现缺口、接下来读哪里、旧查询结果还能不能写回来。

把这几步说明白,比先加更多同步接口更有用。


实现参考:IMBoy 仓库的 https://gitee.com/imboy-pub/imboy/blob/main/src/lib/imboy_cache.erl、src/lib/imboy_cache_sync*.erl;设计与验证边界见 https://gitee.com/imboy-pub/imboy/blob/main/docs/architecture/imboy-distributed-cache-v2.md。

相关推荐
IT_陈寒1 小时前
Redis的原子性竟然失效了?记一次分布式锁翻车实录
前端·人工智能·后端
shengjk11 小时前
既然 gRPC 底层也是 HTTP,为什么大厂还非要死磕 RPC?真相全在这!
后端
回家路上绕了弯1 小时前
人和 Agent 怎么一起干活:把任务交出去,把判断留在关键处
后端
波力海苔夹心脆6752 小时前
C# 正则表达式零基础入门:从“看得懂“到“会用“
经验分享·后端·mysql·正则表达式·c#
ServBay2 小时前
DeepSeek 又出王炸,用 DeepInfer 搭建自己的服务是不是很容易?
后端·ai编程·deepseek
.Bryce.3 小时前
MCP+A2A+ANP,Agent通信协议的完整知识地图
java·人工智能·后端·agent·通信协议
JuiceFS3 小时前
面向 EB 级 AI 存储:JuiceFS 企业版架构演进
人工智能·后端
迷途小羔羊3 小时前
项目概述
java·javascript·后端
我命由我123453 小时前
5-1.Python 编码进阶 - 函数进阶(参数、返回值、递归、函数作为参数传递、lambda 匿名函数)
java·服务器·数据库·后端·python·java-ee·python3.11