skynet_fly热更新存在的问题

前言

最近有空在用skynet_fly写中国象棋的游戏,发现了room_game房间类游戏基础架构有些许不够好的地方,就对room_game房间类游戏基础架构做了优化,具体优化有空会另外写一篇blog,这里主要记录一个我在优化的时候,突然想到的一个热更问题。

问题描述

触发场景分析

假如有A、B 两个服务,代码如下:

  • A服务
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
local contriner_client = require "contriner_client"
local timer = require "timer"
local log = require "log"
local CMD = {}
function CMD.start()
timer:new(timer.second,0,CMD.send_msg_to_b)
return true
end

function CMD.exit()

end

function CMD.send_msg_to_b()
local b_client = contriner_client:new("B_m") --用于访问B服务
local ret = b_client:mod_call("hello")
log.info("send_msg_to_b:",ret)
end

return CMD
  • B服务
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
local CMD = {}

function CMD.start()
return true
end

function CMD.exit()

end

function CMD.hello()
return "HEELO A"
end

return CMD

很简单的一个示例,A服务间隔1秒给B服务发送hello消息。
然后我们修改代码如下:

  • A服务
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
local contriner_client = require "contriner_client"
local timer = require "timer"
local log = require "log"
local CMD = {}
function CMD.start()
timer:new(timer.second,0,CMD.send_msg_to_b)
return true
end

function CMD.exit()

end

function CMD.send_msg_to_b()
-- local b_client = contriner_client:new("B_m") --用于访问B服务
-- local ret = b_client:mod_call("hello")
-- log.info("send_msg_to_b:",ret)
end

return CMD
  • B服务
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
local CMD = {}

function CMD.start()
return true
end

function CMD.exit()

end

-- function CMD.hello()
-- return "HEELO A"
-- end

return CMD

再执行热更,我们修改了A,B服务,我们不想让AB服务继续发送消息了,所有直接B服务直接去掉了hello命令,A服务也去掉了向B服务发送消息的代码。此时看起来没什么问题。
这时就会有一个问题,旧的A服务监听到了B服务更新了,此时它的消息会发送到新的B服务,那么问题就出现了。此时新的B服务hello函数并不存在。

解决方案的思考

  • 方案一
    目前能想到的是,当一个服务是旧版本时,不能去切换访问新的服务,这种方案要保证旧服务需要联系的其他旧服务暂时都不下线。还有就是必须所有服务热更之前就要关闭掉该服务的新服务切换。

深入思考出现的问题

热更过程中,假设A服务先热更,A服务有可能访问到旧的B服务。也是有问题的。
  • 解决方案
    要解决这个问题就必须保证所有服务热更完之前,对服务id的查询暂时先阻塞。
    解决方案是contriner_mgr的所有cmd命令用queue队列包裹起来,这样load启动完所有需要加载的热更模块之前所有query都得先排队,不过这样需要去避免环队列问题,load和start过程中不能有服务去调用query。所以在start完之前,需要把contriner_client:new命令暂时禁用掉。
    如果是非可热更模块服务就没有这个限制。

  • 环队列问题
    假设队列里面有A,B两个函数,队列执行顺序为 B>A>,也就是先执行A,再执行B。
    前提是B的函数执行是因为A函数的call调用发起的。
    此时就会出现A在等B的回应,B在等A先跑完。

做了上诉解决方案的修改后,解决了新旧版本串调的问题,此时又多了一个问题。

旧服务何时销毁退出

之前旧服务被新服务顶替之后,就可以确定自己不再会新的访问者了,现在是可能存在访问者,不能再像之前一样只需要判断自己还有没有需要处理的数据。

  • 解决方案
    记录来访者地址,检查是否可以退出时,询问来访者是否不需要访问自己了或者已经退出了,当所有来访者都退出了,说明此服务可以退出了。这种处理方式有环问题

  • 边界问题

    1. 当旧服务可以退出时,会不会出现全新的来访者。
      除非手动指定id去调用,不然不会出现全新的访问者。新的query查询都会查到新服务,想到这里又想到一个要退出的旧服务可能访问新服务的问题,迟到访问问题

迟到访问问题

热更之前A服务没有访问过B服务,就是还没有查询过B服务的地址,热更结束之后,旧A服务想访问B了,此时又会查询到新服务。
  • 解决方案
    只要用到contriner_client的任何服务,都需要先先注册好需要访问的服务,没有注册的不能访问。

  • 环问题
    A访问B,B访问A,都退出不了。
    引入弱访问者,AB互相联系,应该有一个占主导地位的发起方。主导发起方不用管被访问者是否退出。

总结

此次发现并解决了热更可能存在的诸多问题。

skynet_fly热更新存在的问题
https://huahua132.github.io/2023/10/06/think/reload_error/
作者
huahua132
发布于
2023年10月6日
许可协议