学习Golang之服务器框架编写 – 名字发现服务

名字发现服务(后文简称Naming)一般使用在分布式系统中,当新节点加入时,如何让其他节点知道?

原始的做法是使用配置文件,对每个节点可相连的节点进行配置。这种方法费时费力还容易出错。所以现代系统中一般使用配置中心或者Naming对新加入的节点进行管理,让其他节点感知到它的存在,从而进行通信。

剔除节点的情形也类似,使用Naming机制能更方便的屏蔽失效节点。所以Blue也内置了一套简单的Naming。

 

Blue中的Naming为了解决什么问题?

首先要说明是的,Blue不是针对分布式系统开发的,只是参考了分布式系统的思想,希望使用Blue搭建的服务能方便的平行扩缩容,让Blue内建的RPC更容易使用。所以在功能上来说Naming主要提供:

  • 存储所有节点的有效信息;
  • 感知节点的加入与失效;
  • 广播变化节点的信息至相关节点;

 

实现的方案选择

其实go语言对于Naming的实现有大名鼎鼎的etcd,一个分布式、高可用的k-v存储系统。它的一个特性是提供了gRPC naming and discovery,依托于gRPC提供Naming。其实我设计Blue Naming的思想有些受到它的影响 – RPC必须很好的和Naming配合起来。(自信点的说法是不谋而合,在最开始构思Blue的时候就选定了gRPC作为内置RPC实现方案,但是那个时候我并不知道有etcd这个大杀器)

最后之所以没有选择etcd的原因是:功能太大而全了!它的成名主要归功于k8s使用其做Naming,追根溯源的话,它并不是以Naming为目的开发。

要说有直接拿来用且功能符合预期的轮子还有一个consul,其生来就是为Naming。但是经过评估后,发现把它融合进Blue并且不破坏设计目标比较难,所以也放弃了。

所以最终还是手动写一个Blue专有的Naming,它不具有普适性,但是确是最适合Blue的定制版。

 

Blue Naming的实现

程序启动流程

在新的Goroutine中将节点内存数据落地到文件,这是一个定时的异步过程,对于一个节点存储的信息如下:

type ServerInfo struct {
    app           string
    set           string     
    name          string
    addr          string
    isActive      bool
    lastHeartBeat time.Time
    update        chan bool
}

需要注意的是update字段,这是一个更新通知器,当该节点需要更新数据时管道会被写入数据。(update管道缓存大小默认10)

节点上报更新

在gRPC使用的provider.proto文件里,一共有三个接口提供节点与Naming的交互:

  • Registry           –  节点注册,节点初始化时调用
  • HeartBeat        –  节点心跳,节点定期调用
  • UpdateInfo      –  节点更新,从Naming主动推送的stream

具体交互序列图:

一些设计细节思考

为什么不用HeartBeat带回UpdateInfo

最初的设计是想用HeartBeat带回UpdateInfo,一方面可以简化设计,一方面可以提高网络利用率,但是最终因为实时性的原因放弃了这个方案。还是增加了UpdateInfo推送流;

为什么Naming收到第一个HeartBeat才将节点置active

在Naming收到第一个HeartBeat才将节点置位active,这么做的原因是,Blue无法保证自定义的程序在Registry到程序正常运行这段时间是否会有异常。(比如这中间有一个自定义的预处理,它存在一个严重错误,需要整个程序退出,如果在Registry阶段将其设置成active广播到其他节点是不合理的,这样会导致在至少两个超时时间内节点处于假死状态)

所以在Registry10秒后才会发第一个HeartBeat告知Naming:我启动好了,可以接受服务。同时也要求使用Blue的自定义程序能在10秒内将初始化工作完成,让节点进入可服务状态。

这里比较完美做法是,留下一个通知接口让使用者调用,显式告诉Blue节点已经准备好了,可以进行服务了。但同时考虑到不应该对使用者的使用方式做过多的干预和要求,这里暂时还是使用约定时长的方式。

Naming的可用性问题

目前Blue的Naming是单点的,但即使Naming停止了服务也不影响现有节点之间的通讯与服务,可能的故障分三种情况:

  1. Naming进程跪了。不要紧,我们有落地的文件,Naming重启的时候会加载文件里内容,虽然文件可能会有脏数据,但是在HeartBeat时会把错误的状态纠正;
  2. Naming所在的机器宕机了。这个还好,只需要尽快回复机器,重启Naming进程;
  3. Naming所在的机器宕机无法回复并且硬盘挂了。这个有点麻烦,换机器会导致Naming服务地址变动,最终需要修改节点的Naming配置;

高可用的解决方案很多,从主备到主主,甚至分布式,我个人更倾向于主主,可用性理论最高可以达到99.9%,后面有必要的话会尝试扩展。

(全文结束)

 

学习Golang之服务器框架编写 – CS网络通信

概况

blue的整个CS网络通信设计参考了TarsGo(https://github.com/TarsCloud/TarsGo),在其基础上做了一些优化与微创新。

目前只提供了基于TCP通信协议的相关接口,后续会加入UDP或者RUDP通信。

代码包里提供了一个简单的TCPClient封装,里面有连接服务器、收发包基本功能。一般来说用不到,因为使用go做客户端的比较少。目前仅仅在blue的样例中使用。

 

设计细节

Server框架

利用go的语言特性,我们很方便的将Server需要的接口抽象出来,类似于C++的纯虚函数:

type ServerHandler interface {
    //设置监听地址端口
    Listen() error
    //接受连接的主循环,根据不同协议处理不同
    Run() error
}

如果你需要自己加入相关的协议监听,上述interface的内容是必须要实现的。

Read More →

学习Golang之服务器框架编写 – 开篇

写在前面的废话

最近几年一直关注Golang(后面简称Go)的发展,最开始翻看了很多关于Go独特优点以及如何解决传统C/C++程序员痛点的文章,这几年也断断续续的使用Go写一些工具来辅助开发或者服务器事务处理,一方面是在实践中学习Go,另一方面是体验一下Go的“妙处”,看看是不是真说的那么神。但从目前来看收效并不明显,或者说自己没有明显感觉到Go的魅力,归结原因还是对Go使用程度不够。

纸上得来终觉浅,绝知此事要躬行。所以我决定使用Go来造一个服务器框架的轮子。在此之前见过太多的服务器框架,凡是和“框架”两字沾边,必然有一些个人偏好或者选择倾向(从模块结构,代码组织,使用方式等几个角度),而且一般都是针对某种特定场景的框架,比如Http服务、游戏服务、微服务等等。

所以这个轮子也逃不出“凡是”:它会带有一些个人偏好,并且倾向于游戏服务,甚至因为个人经验水平有限会有一些不成熟的设计。不管怎么说,欢迎一起讨论学习。

目前来说初步的功能已经实现,包括一些基本的功能测试用例,后续除了未知BUG的修改,还会出一版完整的使用样例,扩容一下CS通信相关,以及接口易用性的调试修改。

Github地址:https://github.com/lanyutc/blue

Read More →

Tars批量发布的轮子

Tars的开源Web管理没有提供SET区域层级批量发布的功能(内部版本里面我记得有),提了Issue(https://github.com/TarsCloud/TarsWeb/issues/13)也没回馈。想想自己也算对Tars熟悉,于是便挽起袖子造轮子。

需求比较明确,在上面的Issue里面也说的比较清楚:对一个app众多set里的同一server服务进行发布更新。

最终实现:https://github.com/lanyutc/TarsBatchPatch

Read More →

努力与选择

(全文谨代表个人观点)

毕业后进入了一家几百人的游戏公司做开发工作,当时满心欢喜,原因有三:钱够,游戏行业,离女朋友近。进入公司后,并不是965或者996工作时长,而是周一、二10:00~19:00(开发功能),周三10:00~24:00(开发+测试),周四11:00~周五1:00-13:00(改BUG+发版本),通宵是常事,周六周日很少加班

开始的1年我成长很快,一方面是有高人指点,一方面是自己的努力,有时能搞定别人搞定不了的事情,或者提出更加合理的实施方案。那会比较年轻,对于“每周通宵一次”丝毫没在意,更何况当时的游戏DAU高峰有50W,月流水高峰有400W,一片欣欣向荣(现在回过头来看已经是我职业生涯所处项目的巅峰了!)。过了一年半慢慢意识到“通宵”的伤身,但是公司的制度无法改变(周四发版本),最终选择和组员商量轮班:第一周周四A、B晚11点回去,C、D留守发版本,周五A、B早些过来处理线上问题,第二周轮换

轮班对个人要求更高,引发一个显著的问题是:因为你需要熟悉别人的代码,可能还需要替别人改BUG,耗时更长导致延长发版本的时间;但这样做明显的好处是从“每周通宵一次”变成了“二周通宵一次”,身体上更吃得消,同时每个组员都对系统更加熟悉,减少后续BUG产生的几率。但是“通宵”肯定不是长久之计,于是我在毕业2年半之际跳到了大厂,也与我的职业规划相符。

到了大厂之后,工作时长变成了10106,如果遇到版本节点会更晚一些,基本没有通宵。大厂内部论坛活跃,有各种技术心得共享,工作空闲之余逛论坛会发现:这个问题还可以用这种方法;这个方案好,以后做XX的时候应该可以用到的感悟。在大厂,身边的大牛更多,遇见棘手问题/大型模块他们会给出建议然后进行组内讨论,从讨论中学到了很多方法论与技巧。总之,入职前3个月应该是我在大厂成长最快的时候。

虽然大家都很努力,保持10106的工作强度,但是项目最终还是没有成功上线,团队的努力付之东流。从个人努力成长来看与大牛交流和论坛分享给予了我很大的帮助,在一些问题处理的方法论上有了极大的提高。

Read More →

网游自动开服方案

网游自动开服不是什么有难度的技术,严格来说只能算是一个业务需求。这篇文章会介绍一种自动开服的方案,梳理清楚实现逻辑以及相关的注意点。

写在前面

虽然把方案形容成“自动”,但并非是我们想象中的流程:服务器自动购买/部署->新服运行->玩家进入,而是在服务器自动购买/部署这一步做了一些妥协。

为什么?

Read More →

2018年小游戏开发总结

这其实是一篇个人的2018年度总结。

整个2018年我基本都投身到小游戏的开发制作当中,游戏先后上线了微信小游戏QQ空间小游戏,同时预开发了厘米游戏版本。所以这篇总结我会站在开发的角度谈一谈在这三个平台开发的经验,以及趟过的坑。

上述的三个平台分别对应微信平台,QQ空间平台,手Q平台,对于详细介绍这里就不累述了,相信网上的很多文章肯定比我介绍的好。

Read More →

libcurl内存泄漏排查

由于一些第三方平台/服务的API都升级成了Https,导致原来Tars自带的Http库无法直接使用了,于是引进了libcurlgithub地址)。

但是发现在对应模块长时间运行后,其占用内存一直稳定上升,存在内存泄漏。首先排除掉一些不太可能的泄漏点:Tars、std::string、std::map、std::vector常规使用,无显式的new/malloc。

那么只剩下模块引入的动态库libcurl了,而程序实现严格按照libcurl按照官方文档的样例做法:

  1. 在主线程curl_global_init;
  2. 业务线程每次进行业务处理时调用curl_easy_init获得新的CURL,设置好选项后调用curl_easy_perform;
  3. 业务处理完毕后调用curl_easy_cleanup,curl_slist_free_all(如果有用到curl_slist)释放资源;
  4. 如果主线程退出调用curl_global_cleanup释放资源;

开始一度以为是libcurl用法有问题,后来用valgrind跑程序发现是libcurl真有泄漏,github上1W+Star的开源库有泄漏,这是什么操作?

Read More →

游戏上线前服务器应该如何测试

又一款游戏项目上线测试了,深刻体会到服务端在上线前的这段日子是比较“难熬”的。因为除了原有的开发任务,还有大量的第三方接口支持、统计支持、日志分析需求等,以及服务器各种测试和后续测试BUG的排除。其中测试关系到后续服务器能否满足要求稳定运行,马虎不得。

写这篇文章的目的主要是记录一下上线前服务器测试的个人经验,如果觉得内容有不妥或者遗漏的地方欢迎交流。

测试目的

测试不是盲目的,不要为了应付而测试。简单来说测试有以下目的:

  • 检测是否满足运营/商务需求;怎么理解?比如游戏上线第一天通过平台/渠道买10W的量,可以假设这10W的量在1个小时之内进来,那么登录/创角大概在1666次/秒,系统能否正常承载?
  • 高压力下发现系统瓶颈;
  • 高压力下发现功能BUG;
  • 得到系统承载数据,为后面可能存在的重构做数据支持;
  • 需要发报告,为了应付;

Read More →

鄂公网安备42018502003990号