从游击队到正规军:马蜂窝旅游网的IM系统架构演进之路

本文引用自马蜂窝公众号,由马蜂窝技术团队原创分享。php

1、引言

今天,愈来愈多的用户被马蜂窝持续积累的笔记、攻略、嗡嗡等优质的分享内容所吸引,在这里激发了去旅行的热情,同时也拉动了马蜂窝交易的增加。在帮助用户作出旅行决策、完成交易的过程当中,IM 系统起到了重要的做用。html

IM 系统为用户与商家创建了直接沟通的渠道,帮助用户解答购买旅行产品中的问题,既促成了订单交易,也帮用户打消了疑虑,促成用户旅行愿望的实现。伴随着业务的快速发展,几年间,马蜂窝 IM 系统也经历了几回比较重要的架构演化和转型。算法

本文将分享马蜂窝旅游网的IM系统架构从零演进的整个过程,但愿能给你的IM技术选型和方案肯定带来启发。数据库

关于马蜂窝旅游网:后端

马蜂窝旅游网是中国领先的自由行服务平台,由陈罡和吕刚创立于2006年,从2010年正式开始公司化运营。马蜂窝的景点、餐饮、酒店等点评信息均来自上亿用户的真实分享,每一年帮助过亿的旅行者制定自由行方案。浏览器

学习交流:缓存

- 即时通信/推送技术开发交流4群:101279154[推荐]服务器

- 移动端IM开发入门文章:《新手入门一篇就够:从零开发移动端IM微信

(本文同步发布于:http://www.52im.net/thread-2675-1-1.html网络

2、相关文章

3、IM 1.0:初期阶段

初期为了支持业务快速上线,且当时版本流量较低,对并发要求不高,IM 系统的技术架构主要以简单和可用为目的,实现的功能也很基础。

IM 1.0 使用 PHP 开发,实现了 IM 基本的用户/客服接入、消息收发、咨询列表管理功能。用户咨询时,会经过平均分配的策略分配给客服,记录用户和客服的关联关系。用户/客服发送消息时,经过调用消息转发模块,将消息投递到对方的 Redis 阻塞队列里。收消息则经过 HTTP 长链接调用消息轮询模块,有消息时即刻返回,没有消息则阻塞一段时间返回,这里阻塞的目的是下降轮询的间隔。

消息收发模型以下图所示:

 

上图模型中消息轮询模块的长链接请求是经过 php-fpm 挂载在阻塞队列上,当该请求变多时,若是不能及时释放 php-fpm 进程,会对服务器性能消耗较大,负载很高。

为了解决这个问题,咱们对消息轮询模块进行了优化,选用基于 OpenResty 框架,利用 Lua 协程的方式来优化 php-fmp 长时间挂载的问题。Lua 协程会经过对 Nginx 转发的请求标记判断是否拦截网络请求,若是拦截,则会将阻塞操做交给 Lua 协程来处理,及时释放 php-fmp,缓解对服务器性能的消耗。

优化的处理流程见下图:

 

4、IM 2.0:需求定制阶段

伴随着业务的快速增加,IM 系统在短时间内面临着大量定制需求的增长,开发了许多新的业务模块。面对大量的用户咨询,客服的服务能力已经招架不住。

所以,IM 2.0 将重心放在提高业务功能体验上,好比:

1)在处理用户的咨询时,将从前单一的分配方式演变为采用平均、权重、排队等多种方式;

2)为了提高客服的效率,客服的咨询回复也增长了可选配置,例如自动回复、FAQ 等。

以一个典型的用户咨询场景为例,当用户打开 App 或者网页时,会经过链接层创建长链接,以后在咨询入口发起咨询时,会携带着消息线索初始化消息链路,创建一条可复用、可检索的消息线;发送消息时,经过消息服务将消息存储到 DB 中,同时会根据消息线检索当前咨询是否被分配到客服,调用分配服务的目的是为当前咨询完善客服信息;最后将客服信息更新到链路关系中。

这样,一条完整的消息链路就创建完毕,以后用户/客服发出的消息经过转发服务传输给对方。

以上处理流程以下图所示:

 

5、IM 3.0:服务拆分阶段

5.一、概述

业务量在不断积累,随着模块增长,IM 系统的代码膨胀得很快。因为代码规范没有统1、接口职责不够单1、模块间耦合较多等种缘由,改动一个需求极可能会影响到其它模块,使新需求的开发和维护成本都很高。

为了解决这种局面,IM 系统必需要进行架构升级,首要任务就是服务的拆分。目前,通过拆分后的 IM 系统总体分为 4 块大的服务,包括客服服务、用户服务、IM 服务、数据服务。

以下图所示:

 

如上图,咱们来进行一下解读:

1)客服服务:围绕提高客服效率和用户体验提供多种方式,如提供群组管理、成员管理、质检服务等来提高客服团队的运营和管理水平;经过分配服务、转接服务来使用户的接待效率更灵活高效;支持自动回复、FAQ、知识库服务等来提高客服咨询的回复效率等;

2)用户服务:分析用户行为,为用户作兴趣推荐及用户画像,以及统计用户对马蜂窝商家客服的满意度;

3)IM 服务:支持单聊和群聊模式,提供实时消息通知、离线消息推送、历史消息漫游、联系人、文件上传与存储、消息内容风控检测等;

4)数据服务:经过采集用户咨询的来源入口、是否咨询下单、是否有客服接待、用户咨询以及客服回复的时间信息等,定义数据指标,经过数据分析进行离线数据运算,最终对外提供数据统计信息。主要的指标信息有 30 秒、1 分钟回复率、咨询人数、无应答次数、平均应答时间、咨询销售额、咨询转化率、推荐转化率、分时接待压力、值班状况、服务评分等。

5.二、用户状态流转

现有的 IM 系统 中,用户咨询时一个完整的用户状态流转以下图所示:

 

如上图所示:

1)用户点击咨询按钮触发事件,此时用户状态进入初始态;

2)发送消息时,系统更改用户状态为待分配,经过调用分配服务分配了对应的客服后,用户状态更改成已分配、未解决;

3)当客服解决了用户或者客服回复后用户长时间未说话,触发系统自动解决的操做,此时用户状态更改成已解决,一个咨询流程结束。

5.三、IM 服务的重构

在服务拆分的过程当中,咱们须要考虑特定服务的通用性、可用性和降级策略,同时须要尽量地下降服务间的依赖,避免因为单一服务不可用致使总体服务瘫痪的风险。

在这期间,公司其它业务线对 IM 服务的使用需求也愈来愈多,使用频次和量级也开始加大。初期阶段的 IM 服务当链接量大时,只能经过修改代码实现水平扩容;新业务接入时,还须要在业务服务器上配置 Openresty 环境及 Lua 协程代码,业务接入很是不便,IM 服务的通用性也不好。

考虑到以上问题,咱们对 IM 服务进行了全面重构,目标是将 IM 服务抽取成独立的模块,不依赖其它业务,对外提供统一的集成和调用方式。考虑到 IM 服务对并发处理高和损耗低的要求,选择了 Go 语言来开发此模块。

新的 IM 服务设计以下图:

 

其中,比较重要的 Proxy 层和 Exchange 层提供了如下服务:

1)路由规则:例如 ip-hash、轮询、最小链接数等,经过规则将客户端散列到不一样的 ChannelManager 实例上;

2)对客户端接入的管理:接入后的链接信息会同步到 DispatchTable 模块,方便 Dispatcher 进行检索;

3)ChannelManager 与客户端间的通讯协议:包括客户端请求创建链接、断线重连、主动断开、心跳、通知、收发消息、消息的 QoS 等;

4)对外提供单发、群发消息的 REST 接口:这里须要根据场景来决定是否使用,例如用户咨询客服的场景就须要经过这个接口下发消息。

针对上述第“4)”点,主要缘由在如下 3 点:

1)发消息时会有建立消息线、分配管家等逻辑,这些逻辑目前是 PHP 实现,IM 服务须要知道 PHP 的执行结果,一种方式是使用 Go 从新实现,另一种方式是经过 REST 接口调用 PHP 返回,这样会带来 IM 服务和 PHP 业务过多的网络交互,影响性能;

2)转发消息时,ChannelManager 多个实例间须要互相通讯,例如 ChannelManager1 上的用户 A 给 ChannelManager2 上的客服 B 发消息,若是实例间无通讯机制,消息没法转发。当要再扩展 ChannelManager 实例时,新增实例须要和其它已存在实例分别创建通讯,增长了系统扩展的复杂度;

3)若是客户端不支持 WebSocket 协议,做为降级方案的 HTTP 长链接轮循只能用来收消息,发消息须要经过短链接来处理。其它场景不须要消息转发,只用来给 ChannelManager 传输消息的场景,可经过 WebSocket 直接发送。

5.四、改造后的 IM 服务调用流程

初始化消息线及分配客服过程由 PHP 业务完成。须要消息转发时,PHP 业务调用 Dispatcher 服务的发消息接口,Dispatcher 服务经过共享的 Dispatcher Table 数据,检索出接收者所在的 ChannelManager 实例,将消息经过 RPC 的方式发送到实例上,ChannelManager 经过 WebSocket 将消息推送给客户端。

IM 服务调用流程以下图所示:

 

当链接数超过当前 ChannelManager 集群承载的上限时,只需扩展 ChannelManager 实例,由 ETCD 动态的通知到监听侧,从而作到平滑扩容。目前浏览器版本的 JS-SDK 已经开发完毕,其它业务线经过接入文档,就能方便的集成 IM 服务。

在 Exchange 层的设计中,有 3 个问题须要考。

1)多端消息同步:

如今客户端有 PC 浏览器、Windows 客户端、H五、iOS/Android,若是一个用户登陆了多端,当有消息过来时,须要查找出这个用户的全部链接,当用户的某个端断线后,须要定位到这一个链接。

上面提到过,链接信息都是存储在 DispatcherTable 模块中,所以 DispatcherTable 模块要能根据用户信息快速检索出链接信息。DispatcherTable 模块的设计用到了 Redis 的 Hash 存储,当客户端与 ChannelManager 创建链接后,须要同步的元数据有 uid(用户信息)、uniquefield(惟一值,一个链接对应的惟一值)、wsid(链接标示符)、clientip(客户端 ip)、serverip(服务端 ip)、channel(渠道),对应的结构大体以下:

 

这样经过 key(uid) 能找到一个用户多个端的链接,经过 key+field 能定位到一条链接。链接信息的默认过时时间为 2 小时,目的是避免因客户端链接异常中断致使服务端没有捕获到,从而在 DispatcherTable 中存储了一些过时数据。

2)用户在线状态同步:

好比一个用户前后和 4 个客服咨询过,那么这个用户会出如今 4 个客服的咨询列表里。当用户上线时,要保证 4 个客服看到用户都是在线状态。

要作到这一点有两种方案:

一种是客服经过轮询获取用户的状态,但这样当用户在线状态没有变化时,会发起不少无效的请求;

另一种是用户上线时,给客服推送上线通知,这样会形成消息扩散,每个咨询过的客服都须要扩散通知。

咱们最终采起的是第二种方式,在推送的过程当中,只给在线的客服推送用户状态。

 

3)消息的不丢失,不重复:

为了不消息丢失,对于采用长链接轮询方式的咱们会在发起请求时,带上客户端已读消息的 ID,由服务端计算出差值消息而后返回;使用 WebSocket 方式的,服务端会在推送给客户端消息后,等待客户端的 ACK,若是客户端没有 ACK,服务端会尝试屡次推送。

这时就须要客户端根据消息 ID 作消息重复的处理,避免客户端可能已收到消息,可是因为其它缘由致使 ACK 确认失败,触发重试,致使消息重复。

5.五、IM 服务的消息流

上文提到过 IM 服务须要支持多终端,同时在角色上又分为用户端和商家端,为了能让通知、消息在输出时根据域名、终端、角色动态输出差别化的内容,引入了 DDD (领域驱动设计)的建模方法来对消息进行处理。

处理过程以下图所示:

 

6、小结和展望

伴随着马蜂窝「内容+交易」模式的不断深化,IM 系统架构也经历着演化和升级的不一样阶段,从初期粗旷无序的模式走向统一管理,逐渐规范、造成规模。 

咱们取得了一些进步,固然,还有更长的路要走。将来,结合公司业务的发展脚步和团队的技术能力,咱们将不断进行 IM 系统的优化。

目前咱们正在计划将消息轮询模块中的服务端代码用 Go 替换,使其再也不依赖 PHP 及 OpenResty 环境,实现更好地解耦;另外,咱们将基于 TensorFlow 实现向智慧客服的探索,经过训练数据模型、分析数据,进一步提高人工客服的解决效率,提高用户体验,更好地为业务赋能。

附录:更多架构设计文章

[1] 有关IM架构设计的文章:
浅谈IM系统的架构设计
简述移动端IM开发的那些坑:架构设计、通讯协议和客户端
一套海量在线用户的移动端IM架构设计实践分享(含详细图文)
一套原创分布式即时通信(IM)系统理论架构方案
从零到卓越:京东客服即时通信系统的技术架构演进历程
蘑菇街即时通信/IM服务器开发之架构选择
腾讯QQ1.4亿在线用户的技术挑战和架构演进之路PPT
微信后台基于时间序的海量数据冷热分级架构设计实践
微信技术总监谈架构:微信之道——大道至简(演讲全文)
如何解读《微信技术总监谈架构:微信之道——大道至简》
快速裂变:见证微信强大后台架构从0到1的演进历程(一)
17年的实践:腾讯海量产品的技术方法论
移动端IM中大规模群消息的推送如何保证效率、实时性?
现代IM系统中聊天消息的同步和存储方案探讨
IM开发基础知识补课(二):如何设计大量图片文件的服务端存储架构?
IM开发基础知识补课(三):快速理解服务端数据库读写分离原理及实践建议
IM开发基础知识补课(四):正确理解HTTP短链接中的Cookie、Session和Token
WhatsApp技术实践分享:32人工程团队创造的技术神话
微信朋友圈千亿访问量背后的技术挑战和实践总结
王者荣耀2亿用户量的背后:产品定位、技术架构、网络方案等
IM系统的MQ消息中间件选型:Kafka仍是RabbitMQ?
腾讯资深架构师干货总结:一文读懂大型分布式系统设计的方方面面
以微博类应用场景为例,总结海量社交系统的架构设计步骤
快速理解高性能HTTP服务端的负载均衡技术原理
子弹短信光鲜的背后:网易云信首席架构师分享亿级IM平台的技术实践
知乎技术分享:从单机到2000万QPS并发的Redis高性能缓存实践之路
IM开发基础知识补课(五):通俗易懂,正确理解并用好MQ消息队列
微信技术分享:微信的海量IM聊天消息序列号生成实践(算法原理篇)
微信技术分享:微信的海量IM聊天消息序列号生成实践(容灾方案篇)
新手入门:零基础理解大型分布式架构的演进历史、技术原理、最佳实践
一套高可用、易伸缩、高并发的IM群聊、单聊架构方案设计实践
阿里技术分享:深度揭秘阿里数据库技术方案的10年变迁史
阿里技术分享:阿里自研金融级数据库OceanBase的艰辛成长之路
社交软件红包技术解密(一):全面解密QQ红包技术方案——架构、技术实现等
社交软件红包技术解密(二):解密微信摇一摇红包从0到1的技术演进
社交软件红包技术解密(三):微信摇一摇红包雨背后的技术细节
社交软件红包技术解密(四):微信红包系统是如何应对高并发的
社交软件红包技术解密(五):微信红包系统是如何实现高可用性的
社交软件红包技术解密(六):微信红包系统的存储层架构演进实践
社交软件红包技术解密(七):支付宝红包的海量高并发技术实践
社交软件红包技术解密(八):全面解密微博红包技术方案
社交软件红包技术解密(九):谈谈手Q红包的功能逻辑、容灾、运维、架构等
即时通信新手入门:一文读懂什么是Nginx?它可否实现IM的负载均衡?
即时通信新手入门:快速理解RPC技术——基本概念、原理和用途
多维度对比5款主流分布式MQ消息队列,妈妈不再担忧个人技术选型了
从游击队到正规军:马蜂窝旅游网的IM系统架构演进之路
>> 更多同类文章 ……

[2] 更多其它架构设计相关文章:
腾讯资深架构师干货总结:一文读懂大型分布式系统设计的方方面面
快速理解高性能HTTP服务端的负载均衡技术原理
子弹短信光鲜的背后:网易云信首席架构师分享亿级IM平台的技术实践
知乎技术分享:从单机到2000万QPS并发的Redis高性能缓存实践之路
新手入门:零基础理解大型分布式架构的演进历史、技术原理、最佳实践
阿里技术分享:深度揭秘阿里数据库技术方案的10年变迁史
阿里技术分享:阿里自研金融级数据库OceanBase的艰辛成长之路
达达O2O后台架构演进实践:从0到4000高并发请求背后的努力
优秀后端架构师必会知识:史上最全MySQL大表优化方案总结
小米技术分享:解密小米抢购系统千万高并发架构的演进和实践
一篇读懂分布式架构下的负载均衡技术:分类、原理、算法、常见方案等
通俗易懂:如何设计能支撑百万并发的数据库架构?
多维度对比5款主流分布式MQ消息队列,妈妈不再担忧个人技术选型了
重新手到架构师,一篇就够:从100到1000万高并发的架构演进之路
>> 更多同类文章 ……

(本文同步发布于:http://www.52im.net/thread-2675-1-1.html

相关文章
相关标签/搜索