从前,我是个phper,每天被困在反反复复的增删改查的工作中,后来接触了python后,便对php嗤之以鼻,并且在python中拓宽了眼界,发现了后端世界,于是换了份python游戏服务器的工作,更加后台了。
python开发久了后,我有些腻了,因为python在我心里不再完美了,它的黑魔法实在太多了,导致python入门简单,精通实在太难,这么多语法糖、黑魔法,他们再以各种组合、各种花样实现各种神奇的功能时,你发现看懂这些功能如何实现好难。
另外python的动态类型特点,也让经常让我愁眉不展,当你调用一个三方库或者在模块间互相调用时,这之间的数据传递充满这很多不确定性,类型变来变去,这种不确定性会导致什么异常,你都无从所知。如果你没花大量时间写好完备的单元测试,你能做的就是祈祷线上别出问题。
慢慢地当我发现满世界都在讨论golang的时候,我也注意到了golang这门语言,看大家吹golang吹得快上天了,golang在我心里好像完美的解决了python在我心中的痛,因为它:
1、简单,golang特性很少,也没有oop那套复杂的东西,会很大程度上释放人的心智压力,确实看那些golang代码的时候代码逻辑以外的复杂设计会少很多,所以分析golang标准库和golang本身源码才会那么多。
2、静态类型,这点真的很爽,静态类型会让你有种稳稳的掌控全局的感觉。
花了些时间学习后,感觉还可以,虽然对于我这种脚本小子(因为之前写php和python都是脚本语言,哈哈)来说,学习golang还是有那么一点难度,但是还是入门了。golang真的很不错,不过我就不吹golang了,网上一搜一大把,再说我一个新手也吹不出效果来。
我学习golang是看的《the-way-to-go》,自己整理了一份在线html文档,大家感兴趣的可以去看。
后面我在公司有了用golang的机会了,公司以前的聊天服有些不稳定,领导有用golang重写的想法, 我也听闻golang的牛逼,很想学学golang,于是我就把这个任务揽下来了。
之前有人问过我rpc请求处理流程,下面就介绍公司rpc服务器框架结构图:
rpc使用protobuf作为序列化工具,网络库是我们基于gevent自己写的。
途中那三个长方形的queue是队列,三个椭圆是协程,每个协程都在一个loop中做自己的工作
req_packet_queue是protobuf包队列,当收到请求的包时,就放在这个队列里面。
协程A在loop中不断将req_packet_queue中的二进制包内容解析成request,然后放入
request_queue中。
协程B在自己的loop中不断将request_queue中的request取出来,然后进行业务逻辑处理,请求完后将结果放入resp_packet_queue中。
协程C在自己的loop中不断将response编码为二进制数据流,然后返回给客户端。
这样做的目的我自己感觉好处挺多的。
1、削峰,如果请求太多,可能会卡在处理业务逻辑这个环节,如果解析包、处理请求、封装包这三步是同步的,那么就会阻塞整个请求,导致后续请求过不来,而分开的话,则请求可以堆在req_packet_queue、request_queue这两个队列中,而协程B再慢慢处理,对用户来说只会请求有延迟,而不是不可用了。
2、解析包和处理请求解耦,我们后来除了tcp还加入了websocket两种网络请求,这种结构下,我们只需要更改协程A的逻辑就行了,tcp和websocket两种协议数据直接解析成request后直接放在request_queue中就行了,都不用动其他逻辑。
用golang重写我前前后后花了两个多礼拜的时间,每天花3-6个小时的样子,我是按照python的结构抄过来的,并做了很多简化工作,原有的代码太多绕过来绕过去的逻辑,很容易让人头晕。在大体功能完成以前都没运行过,是等功能写完了一次性运行的,在改完编译器提醒的100多个错误后,我的心里其实是完全没底的,之前的代码研究了那么久,都还没理解到很多东西,这次重写完全是按照我自以为的方式写的。可是在调试了一个多小时后,特瞄的居然从此之后就没问题了,跑了很多次性能测试都没蹦过或者异常过。golang真的很牛逼,编译器果然帮忙省去了大量程序员找bug的时间。
但是整个过程真的让人很心累,因为我对golang不熟悉,再加上golang特性少,公司又没人搞golang,很多实现都让我挠头抓耳的。我经常在网上搜索golang如何实现一个功能半天,最后发现实现不了。。。这时就会感慨golang特性少过头了。但是当我慢慢对golang更了解后,这种感觉慢慢的变少了,希望以后更懂它把。
今天基本rpc框架终于初步完成了,于是在我的垃圾电脑上(每天至少要卡死一次)来了一波粗略的性能测试对比,因为我主要是和python做对比,所以就不关心硬件配置了,结果如下:
测试对比,每次测试都会运行多次,python波动较golang大些,取的平均值的结果。
之前用python客户端,发现2w条请求,无论是两种服务器都耗时13秒左右,所以怀疑是客户端处理达到瓶颈,所以
换了golang客户端,所以现在不知道请求golang服务器的时候有没有达到瓶颈。
测试1:
客户端依次建立2w条请求,每条连接进行一次echo请求,
时间消耗如下:
gevent_chat:
real 0m8.578s
user 0m1.464s
sys 0m1.452s
golang_chat:
real 0m3.135s
user 0m1.180s
sys 0m1.020s
测试2:
客户端同时建立2w条请求(异步建立请求),每条连接发起依次echo请求,
时间消耗如下:
gevent_chat:
real 0m56.524s
user 0m1.256s
sys 0m0.804s
golang_chat:
real 0m1.941s
user 0m1.368s
sys 0m0.824s
测试3:
客户端建立一条连接, 依次(同步)发起2w次echo请求,异步没测试,电脑会卡死:
gevent_chat:
real 0m3.698s
user 0m0.824s
sys 0m0.388s
golang_chat:
real 0m1.321s user 0m0.772s sys 0m0.272s
测试4:
客户端异步发起1000条连接并发请求,每个连接发起500条echo请求,请求结束不断开连接,也就是整个期间都是1000条连接,测试结果如下:
gevent_chat:
会丢连接,有时候跑很久(8分钟)都还没结束
real 3m6.706s
user 0m17.904s
sys 0m17.896s
golang_chat:
real 0m35.140s
user 0m16.344s
sys 0m17.016s
另外推荐一篇golang新手踩坑总结文章:《Golang 新手可能会踩的 50 个坑
》,我反正是中枪不少,哈哈。
另外这篇文章是随手写的,我都没看通不通顺,逻辑有木有问题,但是现在多余精力很少,再加上懒花不了那么多时间在这上面,希望大家将就看。