高并发读与高并发写的项目总结和mongodb使用中遇到的坑

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Acceptedxukai/article/details/54016342

最近开发了一个既要高并发写又要高并发读的项目,写的QPS比读还要高,这个需求几乎是变态的,任何缓存的工具都没法使用,数据在一秒内可能变化的几十遍,只能每次请求都实时从数据库读取。下面分如下几点介绍我们是如何技术选型的。


一、选择通信协议


之前写的项目并发量很小,http+keepalive完全可以搞定了,此项目http完全架不住,因为使用http协议传递数据,那么数据最友好的格式就是json,但是亲测过json的序列化和反序列化的耗时不可以忍受,并且随着数据量的增大耗时也将成倍增长,项目的实际应用场景在写操作时每条数据最大在10KB,因此http+json的组合被kill掉。

只能选择常见的RPC方式,thrift、protobuf,protobuf需要自己实现tcp的通信方式,我们项目使用的是golang开发,这两种方式之前都没有在生产环境使用过,thrift仅仅自己玩过,因此就直接选择了thrift,编码的量级也相比protobuf降低好多,不过在代码写差不多的时候发现golang有基于protobuf开源的gRpc包。。。

通信协议选择了thrift,基本确定了C+S的架构模式,Client负责业务端的访问请求(还是走的http,貌似业务那帮人只会写业务逻辑,完全不懂架构的东西,如果懂Client都可以干掉,自己连Server就搞定了),Server负责Client通过thrift协议发来的请求,包括查询DB和计算。

二、选择数据库


数据库貌似没得选啊,MySQL肯定是可以的,但是需要对一条完整的业务数据进行拆分到多个表中,这对高并发写可能会有影响,一次要锁住多个表的多条数据,对代码的要求肯定很高,具体没有尝试过,只是猜测。MySQL的存储方案一直都没有被考虑过。

我们选择的是mongodb,版本是3.2.11,NOSQL数据库,支持嵌套文档,所有的写操作都可以通过upsert搞定,将并发写的代码要求交给mongo去完成,这样大大降低了编码的难度,mongo也属于内存型数据库吧,数据的读写速度还算可以(但是读的速度还是达不到我们项目的要求。。)。

三、架构部署


架构图就不给了吧,通过上面的叙述,有经验的一眼就能想象出来。

我们使用了24台物理机,每台物理机一虚21个docker,1Client+10Server+10Mongo,每三个Server+Mongo作为一个服务节点,因此有24个Client,前面使用Nginx做反向代理处理业务的请求,80个服务节点用于处理数据查询和计算,最主要的是打平业务的数据,每个服务节点的三个mongo作为一个副本集(数据完全是一直的),每个client会随机选择服务节点里的某个Server来处理一次请求。

代码发布是个老大难问题!!!!

240个Server、240个mongo、24个Client,每个Server都需要通过环境变量来知道自己该连哪个mongo(docker采用的host模式,通过不同端口来控制同一台物理机的mongo)。运维的同事看到这个项目的发布需求,直接就懵逼了,因为之前从来没有这么大批量的docker发布需求。整个项目调动运维+DBA+我们自己+业务等各个部门的人员,而且我们还进行了内核参数优化,机器都重启了很多遍。

不过运维的同事的某个发布系统还是帮我们扛起了发布的重担,虽然发布的时间需要很长,但是咱要求的已经不能再多了,毕竟基础设施还是不够完善,发布的大事搞定了,也能在页面上监控发布过程中是否报错,机器各项参数指标的监控等。

四、说说坑吧

变态的项目肯定很多坑,踩了很多,都不知道从哪里说起,有些是沟通的不彻底,有的是完全没有遇到过,有的真的没法彻底解决。

坑1:
上面说过,Server是通过环境变量来确定该连哪个mongo,这不,运维在发布时,配错了,顿时就傻逼了,环境变量都人肉检测了几遍。

坑2:
业务需求沟通不彻底,业务的计算算法没说清楚,导致计算结果不对,每次请求必须实时读数据库,数据库内嵌文档结构的改变,为此,单机测试了很多遍,代码也改了很多遍,因为之前的功能测试,完全不能模拟高并发的场景,一旦高并发,很多问题就暴露出来了。每次修改后发布都是个漫长的等待过程。。。

坑3:
mongo会自动发现副本集的所有机器地址,之前我们是不知道的,在发现某台mongo被手动关闭了,居然连接的Server没有报错,卧槽,在这里也折腾了比较长的时间,mgo的文档读了一遍又一遍,最后发现确实没有问题,这样反而比较好,因为一旦改成断掉就报错,那么性能反而有下降。

坑4:
这是个巨坑,一直没有填完,而且好像也不能彻底填完,花的时间也最多,做的各种尝试也最多。
swap,swap,swap,mongo在内存的available空间还很多的情况下,居然他妈的去使用swap空间,坑啊,此种项目的场景最好全部使用内存才最好,修改了内核参数也没能彻底解决,最终还是会使用几十M的swap,而且还会增长。这个后面可能是个隐患。。。


就说这么多吧,近三个月的时间,第一次写高并发读写的项目,收获还是很多的,对代码的要求也很高,挺锻炼人的,并且全程的所有代码都是我来把控的,整个完整的架构也是我来设计的,基本上还算可以吧,虽然才刚刚接入数据还没有完全上线。

安利一个自己实现的thrift客户端连接池golang的开源代码:https://github.com/xkeyideal/ThriftPool,欢迎使用,在该项目中实践了没有任何问题,用的时候希望加上,如果从pool里拿到的client连接在某次请求出错时,希望不要再放回池子里,直接关闭该连接丢弃的代码,还是很简单的。


后记


今天终于把坑4给填了,消灭了一个巨大的定时炸弹,目前mongo的swap用量都是0。 
前天发现服务器上某个程序突然崩掉,使用dmesg命令查看,发现文件系统不兼容,宿主机是xfs,docker里使用的是ext4,文件落盘肯定会出现问题。发现问题后,在保障服务不中断的前提下,花了两天时间重装全部的物理机OS,重装后已经跑了一天多,尚未发现任何程序使用swap空间的痕迹。这次可谓解决了一个巨大的bug。此前一直怀疑这批机器有问题,果不其然。


====================================2017-09-11 更新======================================

mongodb深入运用

运维的发布系统只能串行发布,而我们线上运行了几百台docker,每次发布简直是令人发指,全程需要2个小时,同时docker容器里运行mongodb副本集 dba也不给维护,不过好像mongodb也没有死过,想看监控信息只能手动登录到docker里面敲命令行。

一周之前业务需要扩容机器,机器数量直接翻一倍,如果还照之前那样部署和运维,那只要发布一次,一天就基本上别干事情了,而且之前我们的mongodb是裸跑的,没有任何账号密码,也就是说,只要任何一个人有线上机器的权限就能登录我们的数据库,虽然是公司内部系统,但还是比较危险。

基于上面的各项需求矛盾点,特此花了两个月时间,开发了一套自动化并行发布系统,并且只需要点点鼠标就能看到mongodb的一些监控信息,同时mongodb副本集的发布也告别了运维的脚本,全部实现自动化,发布480个mongodb --> 3个mongodb组成副本集 --> 为每个副本集添加admin账号和普通账号 --> 各项mongodb监控命令的可视化结果呈现, 可以说是非常的完备,并且可以完全避免之前的文件系统出错,和numa没有强行关闭的问题,这些都是可以通过mongodb的指令查询的。


该系统的mongodb自动化发布和运维,翻阅了mongodb的文档;
mongodb的命令行工具参照了[mongo-tools](https://github.com/mongodb/mongo-tools)代码,仅仅是参考,真正的实现完全重写了,并且`mongotop`和`mongostat`两个命令还使用websocket实现实时的输出。

server和client的发布,还是直接调用了运维发布系统的api,只不过按照我们的并行发布策略,一次性发送多个发布任务,来达到快速发布的目的。

虽然属于特供项目,但是收益还是十分巨大的:
1、扩容后总计48台物理机 48clients+480servers+480mongodb 从0开始到整个业务部署上线,仅仅耗时1小时不到,如果按照之前的速度,一天时间都搞不定,不需要运维人肉操作一些命令和脚本,所有全部自动化一键触发,实在太爽。
2、对mongodb的理解也进一步深入,该项目有一半的时间,在搞定mongodb从0到所有的自动化发布和监控上,基本上对mongo副本集的知识有了重新的理解
3、有了可视化命令输出就是好,为此发现了go-mgo/mgo驱动的一个小问题


go-mgo/mgo的问题

由于我们的server和mongo一一对应部署在同一台物理机上,mongodb的读连接的模式采用了Nearest,但是无意中发现golang的mgo驱动Nearest的实现并没有按照mongodb文档(3.2.11版本,我们线上使用的该版本数据库)选用延迟最低的。导致在网络十分好的情况下,副本集中的第三台机器没有查询的量。

具体的issue详见[mgo nearest issue](https://github.com/go-mgo/mgo/issues/487)。

通过查阅mgo的源码最终发现了问题,具体的分析已经写在上面的issue里了,有兴趣的可以看看。

解决方案是有的,只需要改掉相应代码,但个人能力有限,改完之后不能进行完整的测试,我们业务的查询量比较小,所以就此放过。



猜你喜欢

转载自blog.csdn.net/Acceptedxukai/article/details/54016342
今日推荐