从游击队到正规军(二):马蜂窝旅游 的IM客户端架构演进和实践总结

一、引言

移动互联 技术改变了旅游的世界,这个领域过去沉重的信息分销成本被大大降低。用户与服务供应商之间、用户与用户之间的沟通路径逐渐打通,沟通的场景也在不断扩展。这促使所有的移动应用开发者都要从用户视角出发,更好地满足用户需求。

论坛时代的马蜂窝,用户之间的沟通形式比较单一,主要为单纯的回帖回复等。为了以较小的成本快速满足用户需求,当时采用的是非实时性消息的方案来实现用户之间的消息传递。

随着行业和公司的发展,马蜂窝确立了「内容+交易」的独特商业模式。在用户规模不断增长及业务形态发生变化的背景下,为用户和商家提供稳定可靠的售前和售后技术支持,成为电商移动业务线的当务之急。

系列文章:

  • 《从游击队到正规军(一):马蜂窝旅游 的IM系统架构演进之路》

关于马蜂窝旅游 :

 

为了实现马蜂窝旅游 App 及商家 IM 业务逻辑、公共资源的整合复用及 UI 个性化定制,将问题拆解为以下部分来解决:

1)IM 数据通道与异常重连机制:解决不同业务实时消息下发以及稳定性保障;

2)IM 实时消息订阅分发机制:解决消息定向发送、业务订阅消费,避免不必要的请求资源浪费;

3)IM 会话列表 UI 绘制通用解决方案:解决不同消息类型的快速迭代开发和管理复杂问题。

整体实现结构分为 4 个部分进行封装,分别为下图中的数据管理、消息注册分发管理、通用 UI 封装及业务管理。

 

常见的客户端与服务端数据交互依赖于 HTTP 请求响应过程,只有客户端主动发起请求才可以得到响应结果。结合马蜂窝的具体业务场景,我们希望建立一种可靠的消息通道来保障服务端主动通知客户端,实现业务数据的传递。目前采用的是 HTTP 长链接轮询的形式实现,各业务数据消息类型只需遵循约定的通用数据结构,即可实现通过数据通道下发给客户端。数据通道不必关心数据的具体内容,只需要关注接收与发送。

3.1.2 客户端数据通道实现原理

客户端数据通道管理的核心是维护一个业务场景请求栈,在不同业务场景切换过程中入栈不同的业务场景参数数据。每次 HTTP 长链接请求使用栈顶请求数据,可以模拟在特定业务场景 (如与不同的用户私信) 的不同处理。数据相关处理都集中封装在数据通道管理中,业务层只需在数据通道管理中注册对应的接收处理即可得到需要的业务消息数据。

 

业务层订阅需要处理的业务消息类型,在注册后会自动监控当前页面的生命周期,并在页面销毁后删除对应的消息订阅,从而避免手动编写成对的订阅和取消订阅,降低业务层的耦合,简化调用逻辑。订阅分发管理会根据各业务类型维护订阅者队列用于消息接收的分发操作。

3.2.2 消息分发

数据通道的核心在于维护多消息类型各自对应的订阅者集合,并将解析的消息分发到业务层。

 

3.3.2 消息类型与展示布局管理原理

对于不同消息类型及展示,问题的核心在于建立消息类型、消息数据结构、消息展示布局管理的映射关系。以上三者在实现过程中通过建立映射管理表来维护,各自建立列表存储消息类型/消息体封装结构/消息展示布局管理,设置对应关系关联 3 个列表来完成查找。 

 

拆分的目的在于使各类型消息 UI 处理只需要关注特有数据。而如通用消息如头像、名称、消息时间、是否可举 、已读未读状态、发送失败/重试状态等都可以统一处理,降低修改维护的成本,同时使各消息 UI 处理逻辑更少、更清晰,更利于新类型的扩展管理。

收发到消息后,根据消息类型判断是「发送接收类型」还是「居中展示类型」,找到外层的布局样式,再根据具体消息类型找到特有的 UI 样式,拼接在外层布局中,得到完整的消息卡片,然后设置对应的数据渲染到列表中,完成整个消息的绘制。

四、细节优化 & 踩坑经验

在实现上述 IM 系统的过程中,我们遇到了很多问题,也做了很多细节优化。在这里总结实现时需要考虑的几点,以供大家借鉴。

4.1、消息去重

在前面的架构中,我们使用 msg_id 来标记消息列表中的每一条消息,msg_id 是根据客户端上传的数据,进行存储后生成的。

 

当客户端 A 因为 络出现问题,无法接受对应发送消息的请求返回的时候,会触发重发机制。此时虽然 IM 服务器已经接受过一次客户端 A 的消息发送请求,但是因为无法确定两个请求是否来自同一条原始消息,只能再次接受,这就导致了重复消息的产生。解决的方法是引入客户端消息标识 id。因为我们已经依附旧有的 msg_id 做了很多工作,不打算让客户端的消息 id 代替 msg_id 的职能,因此重新定义一个 random_id。

 

通过这种数据通道+本地通知展示的机制,可以在应用处于运行状态的时间内提高消息抵达率,减少对于远程推送的依赖,降低推送系统的压力,并提升用户体验。

4.3、数据通道异常重连机制

当前数据通道通过 HTTP 长链接轮询 (Polling) 实现。

不同业务场景下对 Polling 的影响如下图所示:

 

在实践中发现以下问题:

1)当服务端突然异常并持续超过 1 分钟后,客户端启动执行重试机制,并每隔 1 分钟重发一次重连请求。这对服务器而言就相当于遭受一次短暂集中的「攻击」,甚至有可能拖垮服务器;

2)当客户端断 后立刻进行重试也并不合理,因为用户恢复 络也需要一定时间,这期间的重连请求是无意义的。

基于以上问题分析改进,我们设计了第二版重试机制。此次将 5 次以下请求错误的延迟时间修改为 5 – 20 秒随机重试,将客户端重试请求分散在多个时间点避免同时请求形成对服务器对瞬时压力。同时在客户端断 情况下也进行延迟重试。

 

4.4、唯一会话标识

4.4.1 为何引入消息线 ID

消息线就是用来表示会话的聊天关系,不同消息线代表不同对象的会话,从 DB 层面来看需要一个张表来存储这种关系 uid + object_id + busi_type = 消息线 ID。

 

近期将对 HTTP 轮询实现方案进行替换,进一步优化数据通道的效率。

5.2、业务功能的扩展

计划将 IM 移动端功能模块打造成通用的即时通讯组件,能够更容易地赋予各业务 IM 能力,使各业务快速在自有产品线上添加聊天功能,降低研发 IM 的成本和难度。目前的 IM 功能实现主要有两个组成,分别是公用的数据通道与 UI 组件。

随着马蜂窝业务发展,在现有 IM 系统上还有很多可以建设和升级的方向。比如消息类型的支撑上,扩展对短视频、语音消息、快捷消息回复等支撑,提高 交的便捷性和趣味性;对于多人场景希望增加群组,兴趣频道,多人音视频通信等场景的支撑等。

相信未来通过对更多业务功能的扩展及应用场景的探索,马蜂窝移动端 IM 将更好地提升用户体验,并持续为商家赋能。

附录:更多IM架构设计方面的文章

 

声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

上一篇 2019年9月15日
下一篇 2019年9月15日

相关推荐