以harbor作为节点间通信的方式,解决的问题及思路回顾

      前面记录了一次skynet框架中cluster和harbor两种节点间的通信方式,总结下来是harbor方式在使用上更方便,其实漏掉了一处非常方便的应用场景:在不同节点间做状态同步,及通过multicast模块实现不同节点间的服务收到同一频道上的消息,这里对于社交类的应用,比如群聊,非常的方便,下面会做进一步的分析和总结。

     harbor通信方式,支持不同节点间,通过服务名发送消息,这里的服务名需要做成全局唯一的,如果采用下面这种方式来确保服务名唯一,代价太大。这种方式是这样的:在一个服务的入口处,向cslave服务申请绑定服务名和服务id;如果这个节点上存在这个服务名,返回失败,重新生成一个服务名,再次尝试;如果这个节点上不存在这个服务名,向cmaster服务所在的节点申请绑定服务名和服务id,如果cmaster上存在这个服务名,向cslave返回失败,cslave再向申请的服务返回失败,流程上复杂,而且必要性不大。可以提供一个接口,通过harborid、服务的类型、服务的特定标记来组合成一个唯一的服务名,就能解决服务名唯一的问题。现在有这样的问题,在1节点的A服务向2节点的B服务发送消息时,会先通过查询获得B服务的handle,这个信息会记录在1节点的harbor服务、1节点的cslave服务、cmaster服务所在节点的cmaster服务,但是当B服务退出的时候,并没有把这些记录了B服务的handle信息删除,我在服务通用退出接口里向cslave服务发送消息,告知服务已退出,cslave通过handle查找是否有这个服务,如果有,cslave通知cmaster服务,cmaster删除,然后通知所有的cslave删除这个handle和服务名,cslave服务再通知harbor删除handle和服务名,这样就不会出现系统里缓存的handle分配给新的服务时,产生的异常。

      multicast模块支持不同节点上的服务,订阅同一个频道。我在开始的时候没看明白,删减和调整了部分代码,做了些测试,能实现不同节点间订阅,但是某节点的服务发布消息的时候,只有一个节点能收到,另一个节点订阅了这个频道的服务收不到消息,期间出现了一处令我困惑不解的代码,我做了很多测试,最终部分理解了。过程是这样的:我发现在一个节点的某个服务发布数据的时候,会用通用打包模块打包数据,返回打包后数据在内存的地址和数据打包后的长度,然后再申请一个结构的内存A(地址),存放这些信息,然后又申请一块指针长度的内存B(地址),存放A的地址,返回的时候返回B和B的长度(就是指针占的内存大小);数据到了multicastd服务后,先将B的地址和长度打包(其实就是一个内存地址)成C(字符串,这里也要注意),再将A里的引用计数设置为在本节点订阅了这个频道的服务数量,然后将B释放,然而在multicastd向本地订阅了这个频道的所有服务发送数据C,在各服务收到数据C后,通过B拿到A,再取到真实数据的地址和长度,再解包就拿到了结构化的数据,处理逻辑之后,然后将A的引用计数减一,当计数为0时,删除A和A中数据所占用的内存。其中令我疑惑不解的地方是为何前面删除了B,后面各服务,还能通过B拿到真实的数据。我写了简单的测试用例,发现和我的理解是一致的,被释放的内存是不能再被使用的。我对skynet_free接口进行了调试,经过反复修改和调试,知道这个调用肯定是在某个线程上,而同一时间,一个服务只会被一个线程调度,并且会记下当前调度的是哪个服务,因此,我输出了释放的内存地址和服务id,反复测试后,发现这里输出的内存地址和B的值是一样的,并且服务id有两个,这两个服务和multicastd服务是完全不相关的,我对很多调用skynet_free的地方进行调试,想确定是哪出代码调用了skynet_free,结果发现就是消息打包后数据在内存中的地址,这里我进行了猜想,是释放这处内存的服务重新申请了这块内存。为了验证我的猜想,我在skynet_malloc进行了输出,很不幸,运行skynet时,直接挂了,但是不知道具体是什么原因导致的,系统没有产生core文件,经过我的一番设置,终于可以产生了core文件了,然后进行了测试,发现是skynet_malloc作为申请内存的一个钩子,不光skynet里可见的使用skynet_malloc来分配内存的地方,而且其他使用malloc分配内存的地方也使用了skynet_malloc来分配内存,而且printf也调用了malloc,也就是自己会调用自己,产生了死循环。我必须换一种方式来证明,前面释放的地址,在这里再次申请了,我直接比较新申请的内存的地址和前面释放的地址是否相等,相等的时候,设置一个值,然后在skynet_free里输出这个值,最终确认,这个释放了的地址B的确被其他服务再次申请内存时使用了,着实折腾了一番。这里要注意的是,skynet_free调用了jemalloc的api来释放内存,并没有像系统库里的free一样,直接释放,所以在其他服务,还是可以使用这个地址B的。还有一个点,是上面multicastd生成的字符串C,是为了高效和符合skynet消息使用习惯:数据由发送方分配内存,由接收方释放内存,如果是数据到另一个节点的multicastd,会把数据的实际内容通过节点间通信发送到harbor服务,再转到multicastd服务,再做一次打包后的数据到A、B的转化,由multicastd服务发到节点上的其他服务和上面的过程一样。

      我们项目的定时器实现是仿照了skynet_timer.c的设计思路,用lua来实现的,一个服务只有一个定时器模块,在一个协程里控制所有的定时逻辑,线上服务器出现过几次,由定时逻辑产生的异常导致所有的定时逻辑失效。弊端是很明显了,我这周使用skynet.timeout接口实现了一种新的定时器方案,在5分钟内的定时,会产生一个协程,待定时时间到时,唤醒协程执行对应的逻辑,多于5分钟的定时操作,先做缓存,由固定定时逻辑来处理,当前时间点离定时结束的时间点少于5分钟,才会创建一个协程。每个定时器是独立的协程,协程的申请和释放由skynet框架保证,而且有协程池,5分钟的时间也用变量来控制,如果5分钟内产生的协程数量太多,可以适当的改小一点。提供了几个简单api,添加一个定时器(执行结束,定时器取消),添加一个间隔固定时间的定时器(执行结束,再创建一个定时器),取消一个定时器,热更定时器执行的函数。创建随机20-900秒内的定时器300个,根据日志分析对比,还是比较满意的。

      之前由于频繁的创建和销毁服务,skynet在handle和名字的管理上有bug,作者对ATOM_CAS做过修改,我仔细分析过代码,认为不是ATOM_CAS的bug,我写了简单了测试代码,测试了10秒钟之内的线程在抢占锁和释放锁的情况,发现atomic_compare_exchange_weak接口的第二个参数会返回设置后的值,这个接口提供的是指针,说明这个指针指向的内容是有可能被修改的,ATOM_CAS的第二参数不是一个指针类型,所以不会影响到实际的值,__sync_bool_compare_and_swap的第二个参数也不是指针类型,也不会影响到实际的参数。由此判定,ATOM_CAS修改前和修改后执行的最终结果是一样的。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值