问题:skynet中节点间通信,一节点应用层无法立即感知另一节点已停止运行
背景:我们游戏服务端使用的是skynet,在A节点的x服务会根据B节点的运行状态设置不同的数值,B节点退出时,A节点的gate服务会向clusterd服务发送"close"协议,带一个能标记是哪一个clusteragent的参数,然后由clusterd服务向对应的clusteragent服务发送"exit",但是没有标记是哪一个节点退出,我刚开始开发相关功能的时候,是间隔一段时间,B节点的y服务向A节点的x服务发送一次数据,证明自己存在,A节点的x服务间隔一段时间检测B节点是否还存在,这里的做法和心跳类似,但是这种做法存在因为B节点的实际状态和A节点记录的B节点的状态不一致产生的问题,为了更好的解决这个问题,我分析了skynet节点间的通信流程,下面记录一下我解决这个问题的思路和操作。
正文:我使用skynet接近5个月,看了云风的博客近一半的内容,当时看的时候,很多内容没理解。之前一直在做业务功能,也没有太多的时间去更好的解决上面的问题,最近有一点时间,对这个问题进行了分析并找到了较好的解决方案。
上面的问题是和两个节点相关,我有了第一个问题:节点B是如何和节点A通信的?因为skynet提供了跨节点通信的解决方案,业务层有使用,我找到一个具体的点:B节点的y服务会间隔一段时间向A节点的x服务推送数据。经分析,y服务远程调用的地方,会根据是否已和被调用的节点A建立连接,来向具体的clustersender服务发送数据;判断是否已和被调用的节点A建立连接,是根据节点A的名字来查找对应的clustersender服务是否存在,当不存在时,会请求clusterd服务创建一个对A节点连接的服务clustersender(w服务),这里在等待clusterd服务的返回,此刻产生了一个问题:如果此处阻塞了,肯定会影响整体性能,skynet肯定有解决的方法;为了理清楚这个问题点p1,我想到这一步是在同一个节点里的服务间调用,于是想到了debug_console服务,这个服务提供了控制台命令来操作这个节点,在前面的工作过程中,我已经熟悉了debug_console服务,这个服务帮我解决了不少问题,我list看了一下,没有列出bootstrap服务,因为这是第二个启动的服务,第一个服务是logger,这两个服务都是在c代码里启动的,没有加入launcher服务的列表里,所以list在debug_console服务里查不到;我想到bootstrap是一个snlua类型的服务,c调用lua的入口在service_snlua.c/init_cb函数,第一个lua脚本是loader.lua,刚开始的时候我看过这里,不理解的地方有:正则表达式、loadfile返回一个chunk作为lua函数调用、c传递参数到lua、table的用法,之前没有深度使用lua来开发项目,通过一个又一个的猜想,一个又一个的不断被证实,直至上面这些不懂的点,都理解了;可以看到bootstrap服务的入口是skynet.start,这是每一个snlua类型服务的入口,参数是一个lua函数f,在skynet.start里会通过c接口设置一个消息处理函数dm,这里有个问题:dm是如何被调用的?在skynet.start里会调用定时器接口,定时器接口有一个函数f1作为参数,定时器接口会向当前服务的消息队列投递第一个消息m1,同时返回返回一个session(s1),然后以f作为参数创建一个协程co1,s1为key,co1为值,放入table中,dm会收到m1,然后唤醒co1,f1开始执行,然后调用skynet.init_service,这个函数会先调用skynet_require.init_all函数,这里会执行在主线程t1(建立这个服务时的lua_State)中调用skynet.init时传递的函数,这里有个问题,不在t1中调用skynet.init时传入的函数,何时执行?然后再执行f;bootstrap同步调用创建launcher服务,然后通过调用launcher服务来创建cdummy服务,这里和前面的p1的应用场景是一样的,同一节点里服务间调用,仔细分析后知道,在call调用时,需要等待返回值,为了能高效的处理其他业务,必须让出处理器,在调用call的时候,是协程被唤醒里的逻辑,在让出处理器时,绑定好session和当前协程,然后在dm中收到消息后,根据消息类型和session找到对应的协程,将其唤醒,并且把收到的数据传递过去,协程开始接着前面的中断往下执行,这次通过launcher建立datacenterd服务时,把"SUSPEND"返回,suspend函数会让出处理器,主线程继续高效运转,这就是同一节点服务间的”同步“调用p1;不断分析,发现bootstrap运行到最后会传递“QUIT”退出,协程最后关闭,服务也最后退出;理解了服务间的“同步”调用,再接着看y服务向clustersender服务投递数据,这里y服务生成的session是流程正常运转的关键,session会投递到clustersender服务,clustersender会将数据二次打包,这里打包的数据是会发到A节点的,在这里我稍微着急(没有达到对整个流程有清晰的了解)了一点,找到打包的代码,把本节点的信息塞了进去,同时解包的地方进行了修改,这里改动了lua-cluster.c、clustersender.lua、clusteragent.lua,之前通过分析结合证实,知道一个节点如何启动一个clusteragent服务:gate服务会收到"socket"类型的消息"open",gate通知clusterd服务建立一个clusteragent服务;修改代码之后,clusnteragent收到的B节点信息不对,然后查了修改的代码,发现了些问题,又进行了修改,再进行了测试,还是有问题,后来我测试了修改前的代码,并在关键点写了日志,弄清了节点间的调用流程,然后稍微修改了前面改过的代码,终于通过了;节点间的调用流程:B节点的y服务向对应的clustersender服务c1投递消息,y服务会建立一个session和co(协程)的关系,c1向A节点投递消息msg1的时候,c1服务会建立一个session和co(协程)的关系,clusteragent收到(经gate服务转)msg1时,经解包能获得B节点的信息,这里记下B节点的信息和x服务的名字(等clusteragent服务收到"exit"消息时,同时向前面记录了的x服务发消息,并带上B节点的信息),在向x服务投递消息,clusteragent服务会建立一个session和co(协程)的关系,业务层的服务x处理完,会向clusteragent服务回一个响应包,clusteragent服务根据session唤醒协程,然后打响应包,回到clustersender,clustersender服务根据session,唤醒协程,继续流程,回包给B节点的y服务,y收到包,根据session,唤醒协程,流程继续;修改的代码不多,但是找到关键点比较费时,需要一步一步的证实。
刚开始从业时不认可lua,主要原因有:1、到处都是宏,不便于阅读 2、脚本语言运行时性能差 3、观念上排斥,觉得这么语言很低端,不上层次,而现在我越来越喜欢lua这门精简的语言,和我的精简、极致的哲学观很吻合,lua能在它处于优势地位的领域独领风骚,系统不停机更新,是lua最大的优势,对语言特性的扩展极为克制保守,也是我非常推崇的,我也很喜欢c+lua这种组合来解决问题,语言简单,面向问题,解决问题。