最近上了全节点热更配置表。起因是有一次热更配置表时没有覆盖全节点,导致数据不一致而 traceback,中断了一个重要流程。由于读表服务和目标服务的相关性比较隐蔽,热更 review 时没能发现这个问题,于是决定后续所有配置表都采用全节点热更。

全节点热更上线后,发现内存会持续增长,多次热更后尤为明显。同事排查后发现是 sharedata 服务的内存一直在涨,应该是旧配置表没有释放导致的。这件事比较复杂,搞清楚后很容易又忘了,决定写篇博客记录一下。

为什么要有 sharedata

各服务的配置内容都是相同的,如果每个 lua 虚拟机都各自加载一份,既浪费内存,热更处理也不够优雅。

sharedata 是怎么做的

为了实现配置表共用,sharedata 在 C 层维护一份公共配置数据,对 lua 层返回一个 userdata。这个 userdata 包含指向配置数据的指针,以及用于减少引用计数的 gc 方法。当引用计数降为 0 时,就可以释放底层资源了。

热更时需要通知各服务进行更新。为什么需要通知呢?原因有二:一是底层没有持有所有指针的引用;二是需要提供一个保证——在处理一条消息的过程中,访问到的配置表版本是一致的。如果不通知就直接更换指针,可能导致数据异常。

因此 skynet 提供了 sharedatad 服务来负责通知各服务热更。但这会引入一个问题:如果服务退出时恰好发生热更,通知就会失效。此时服务不会再发送确认消息说自己已更新、可以释放旧资源,最终导致旧资源内存泄漏。

为了解决这个问题,我们在 sharedatad 之上又包装了一层 sharedata_extend 服务来处理服务退出的情况。sharedatad 永不退出,其他服务需要配置时向 sharedata_extend 申请,后者再向 sharedatad 申请。服务退出时,通过 __gc 方法通知 sharedata_extend,然后由 sharedata_extendsharedatad 提出释放请求。

现在的难点在于:当服务退出时恰好遇到热更,可能出现几种情况:

  • 通知不到服务
  • 通知到服务后,服务不再回包确认已更新
  • 服务地址发生回绕,同一个 address id 已经对应到其他服务了,自然也不会回包
  • 用 sharedata 的服务,__gc不一定是服务退出,也有可能只是暂时不用导致被gc。申请也有可能随着资源使用而申请多次

sharedata_extend 需要解决上述问题,才算比较完整。只是感觉这个方案比较复杂,有点像为了一碟醋,包了一碟饺子,又杀了一只鸡的感觉……


本文由 Glm 4.7 润色