微信架构已经非常透明,稍做抓包便可窥探整个传输机制,怎么实现已经没没什么好说了,手头IM(即时通信)为基础的衍生项目也做了3年了,IM设计架构连同服务器方案也变了3版,这里想说的是当前版本的架构设计。
先聊聊目前的整体架构设计,现项目是基于IM推送为主的OA类产品,包含会议、日程、任务、申请(请假/报销)等等OA模块,一切皆通过即时消息分发,同时包含通讯录,可以进行文字、语音、图片、附件、小视频等聊天,套路基本和微信、QQ、钉钉这类的差不多,面向企业化更多一点。前端采用了web和手机端同步办公设计,后端采用了IM服务器和接口服务器架构,IM服务器专门负责消息的分发和推送,接口服务器负责所有业务的处理。
移动端的架构设计
发送大概流程:
用户创建消息,图片/语音/附件之类的先写入沙盒——>写入本地数据库并标记待上传——>上传到HTTP服务器并得到服务器寄存文件的地址(图片还有缩略图尺寸)等相关参数——>更新数据库——>生成协议报文——>写入本地数据库后——>webSocket通道发送——>收到回执后——>更新数据库——>更新UI。
接收大概流程:
webSocket收到消息回调——>解包解码——>写入数据库——>更新UI——>下载相关资源
以上牵扯到太多的分线程操作,画起来相当考验耐心,不比敲代码复杂。但是核心就是3个队列:数据库操作队列、webSocket传输队列、http上传下载队列。
三个队列全部采用单线程也就是FIFO
HTTP队列:负责发送附件、图片、语音等,并发数为1,FIFO模式(实际上服务器网络带宽是制约上传速度的第一瓶颈)。
WS队列:负责发送webSocket消息,并发数为1,且收到回执之前堵塞队列里待发送任务,保证语序的正确性,不会出现12345实际收到未34512这类语义逆反情况(语义逆反是最不能接受的)。
队列之间语序:HTTP队列里的内容上传结束后加入WS队列时,按照用户生成时间来排优先级进入。具体参考下述场景。
重发:以上两个队列都有各自重发机制,任务超时或者失败会重发处理,除非超出重发边界(微信似乎是15分钟超时,我们可以短一些)会置为失败不再系统自动重发,确保先处理的任务先出去。
堵塞:HTTP和WS两个队列不互相堵塞,也就是图片发送不会堵塞消息发送。
————实际场景(因为图片、附件、语音都是同样的方式,所以只取图片来举例)————
用户:用户发送了三张图片ABC,然后发送了10条文本,再发DEF三张图片。
代码:ABC分别纳入HTTP队列单个按顺序处理——>10条文本纳入WS队列进行按序发送——>A图片收到HTTP回执并且已经处理好相关事务进入WS队列,假设此时WS正在发送第6条文本等待回执中,那么此时A图片的WS消息因为是用户最先创建,那么在WS消息队列中优先级最高,插队到7之前,只要6一收到回执就处理图片A,如此类推。(微信语序就是如此)
接收方:文本消息接受语序会和发送方保持一致性,不管中间是否穿插图片类消息,也就是发送方发1-100接收方一定是收到1-100(服务器消息丢失等因素不在此讨论),图片类的接收语序也会和发送方保持一致性,不管中间是否穿插文本消息;但是图片类和文本类的绝对时间收发双方不会绝对一致,如上面用户发送的场景,实际接收方收到的顺序是1-6的文本,然后图片A,然后7-9文本,图片B,文本10,图片CDEF;具体依图片类上传网络速度和用户发送各类消息的间隔等(微信也是如此),因为图片上传不能堵塞消息发送,不然会被吐槽卡死。
————好处————
产品层面:好处就是文本语序的正确性,不会产生因为没发出去导致“丢消息”、“跳消息”的情况,且用户的体验也更好,不会出现类似群聊里面某个用户发了一大堆图文消息后,起初的几条因为发送失败并且已经不在当前视窗中(列表滚动上去了)而没有发现自己发送失败的尴尬,也不会出现一个领导说开会了,然后说不开了,用户收到的是不开了,开会了这样的语序导致的类似灾难性后果。
在开发层面:可以大大降低维护成本和Bug率,因为所有的事件处理变得非常简单明了,健壮性和可维护性都提高不少。
总结:
即时通信类的项目开发到后面,侧重的就不是单机的处理速度,快个几毫秒不是所要追求的目的;重心慢慢偏向于使用可靠性,架构简单和健壮性。对于开发来讲,越简单的设计和架构,维护越容易,解耦也越容易,业务层和传输层逻辑一定要解耦,一定要避免高并发处理,采用队列FIFO设计极大简化了逻辑复杂度,降低了模块耦合度。
对于开发,开发者应尽量避免追求性能第一,多线程是毒品,不要滥用,能一条分线程做得好的事情,不要去开多个线程并发,业务架构一复杂,开发和维护成本指数级上升,而且项目最后期10%的Bug变得极为难排。能用队列解决的事情,不要去做自己处理线程。
PS:业务层很多指令在此就不说了,例如web和手机端要双向同步消息和指令,支持转发撤销之类的不在此赘述了。