如今,越来越多的用户被马蜂窝不断积累的笔记、攻略、动态消息等优质分享内容所吸引,马蜂窝交易量的增长。IM系统在帮助用户做出出行决策和完成交易方面发挥着重要作用。IM系统为用户和商户建立了直接的沟通渠道,帮助用户解答购买旅游产品中的疑问,既方便了订单交易,又帮助用户打消了疑虑,促进了用户旅游愿望的实现。伴随着业务的快速发展,马蜂窝IM系统在过去几年也经历了数次重要的架构演进和变革。IM1.0——早期为了支持业务的快速上线,而当时版本流量低,对并发要求不高,IM系统的技术架构主要针对简单易用,实现的功能也非常基础。IM1.0采用PHP开发,实现了基本的IM用户/客服接入、消息收发、咨询列表管理等功能。用户咨询时,会通过均匀分配策略分配给客服,并记录用户与客服的关系。当用户/客服发送消息时,通过调用消息转发模块将消息投递到对方的Redis阻塞队列中。接收消息,通过HTTP长连接调用消息轮询模块。当有消息时,它会立即返回。如果没有消息,则阻塞一段时间后返回。这里阻塞的目的是减少轮询间隔。消息收发模型如下图所示:消息轮询模块优化上述模型中消息轮询模块的长连接请求是通过php-fpm挂载到阻塞队列上的。当请求数量增加时,如果不能及时释放php-fpm进程,对服务器性能消耗很大,负载很高。为了解决这个问题,我们优化了消息轮询模块,基于OpenResty框架,使用Lua协程优化了php-fmp的长期挂载。Lua协程会通过标记Nginx转发的请求来判断是否拦截网络请求。如果被拦截,阻塞操作将交给Lua协程处理,并及时释放php-fmp,减轻服务器性能的消耗。优化后的处理流程如下图所示:IM2.0——需求定制阶段随着业务的快速增长,IM系统在短期内面临着大量的定制化需求,开发了许多新的业务模块。面对大量的用户咨询,客服的服务能力已经不堪重负。因此,IM2.0着重提升业务功能的体验。例如,在处理用户查询时,从以前单一的分配方式演变为平均、加权、排队等多种方式;为了提高客服效率,客服咨询回复还增加了可选配置,比如自动回复、FAQ等。以一个典型的用户咨询场景为例,当用户打开一个App或者网页时,一个会通过连接层建立长连接,然后在咨询入口发起咨询时,会用消息线程初始化消息链接,建立可重用、可检索的消息线路;发送消息时,通过消息服务将消息存入DB,同时根据消息行获取当前咨询是否分配给客服。调用分配服务的目的是为了完善本次咨询的客服信息;信息更新为链接关系。这样就建立了一个完整的消息链接,然后将用户/客服发送的消息通过转发服务转发给对方。增加,IM系统的代码迅速膨胀。由于代码规范不统一、接口职责单一、模块间耦合较多等多种原因,改变一个需求很可能会影响到其他模块,使得新需求的开发和维护成本非常高。为了解决这种情况,必须对IM系统进行升级,而首要任务就是拆分服务。目前拆分的IM系统分为4大服务,包括客服、用户服务、IM服务、数据服务,如下图:客服:提供多种方式提升客服效率和用户体验,如提供群组管理、会员管理、质检服务等,提升客服团队的运营管理水平;通过服务的分配、转移服务使用户的接待效率更加灵活高效;支持自动回复、FAQ、知识库服务等,提高客服查询效率。用户服务:分析用户行为,为用户进行兴趣推荐和用户画像,统计用户对马蜂窝商家客服的满意度。IM服务:支持单聊和群聊模式,提供实时消息通知、离线消息推送、历史消息漫游、联系人列表、文件上传存储、消息内容风控检测等数据服务:通过采集源入口用户咨询、是否下单、是否有客服接待、用户咨询及客服回复时间信息等,定义数据指标,通过数据分析进行离线数据计算,最终对外提供数据统计.主要指标包括30秒内回复率、1分钟内回复率、咨询次数、无回复次数、平均回复时间、咨询销售额、咨询转化率、推荐转化率、分时接待压力、值班状态、服务评分、ETC。用户状态流程在现有的IM系统中,一个完整的用户咨询时的用户状态流程如下图所示:用户点击咨询按钮触发事件,此时用户状态进入初始状态。发送消息时,系统将用户状态更改为待分配。调用分配服务分配对应的客服后,用户状态变为已分配和未解决。当客服解决用户或客服回复后用户长时间不说话,系统自动解决操作。此时用户状态变为resolved,结束咨询流程。IM服务重构在服务拆分的过程中,需要考虑具体服务的通用性、可用性和降级策略,同时需要尽可能降低服务之间的依赖关系,规避风险由于单个服务不可用导致整体服务瘫痪。在此期间,公司其他业务条线对IM服务的使用需求也越来越大,使用频率和幅度也开始增加。当初期IM服务连接数较多时,只能通过修改代码实现水平扩展;接入新业务时,需要在业务服务器上配置Openresty环境和Lua协程代码,业务接入非常不方便。通用性也很差。考虑到以上问题,我们对IM服务进行了全面重构,目标是将IM服务抽取出一个独立的模块,独立于其他服务,对外提供统一的集成和调用方式。考虑到IM业务对高并发处理和低丢包的要求,选择了Go语言开发该模块。新的IM服务设计如下图所示:其中,比较重要的Proxy层和Exchange层提供以下服务:1.路由规则,如ip-hash、轮询、最小连接数等,通过规则将客户端散列到不同的ChannelManager实例。2、对于客户端访问的管理,访问后的连接信息会同步到DispatchTable模块,方便Dispatcher检索。3、ChannelManager与客户端的通信协议,包括客户端请求建立连接、断开重连、主动断开、心跳、通知、消息收发、消息QoS等。4、为单机提供对外REST接口-发送和群发消息。这里需要根据场景来决定是否使用。比如用户咨询客服的场景,需要通过该接口发送消息。主要原因有以下三点:发送消息时,会有创建消息行,分配管家等逻辑。这些逻辑目前都是PHP实现的,IM服务需要知道PHP的执行结果,一种方式是使用Go重新实现,另一种方式是调用PHP通过REST接口返回,这样会带来太IM服务与PHP业务之间的网络交互较多,影响性能。转发消息时,ChannelManager的多个实例需要相互通信。例如,ChannelManager1上的用户A向ChannelManager2上的客服B发送消息。如果实例之间没有通信机制,则无法转发消息。在扩展ChannelManager实例时,新的实例需要单独与现有的其他实例建立通信,增加了系统扩展的复杂度。如果客户端不支持WebSocket协议,作为降级方案的HTTP长连接轮询只能用于接收消息,需要通过短连接处理消息。其他场景不需要消息转发,只用于向ChannelManager传输消息的场景可以直接通过WebSocket发送。调用修改后的IM服务、初始化消息行、分配客服的过程由PHP业务完成。当需要消息转发时,PHP业务调用Dispatcher服务的消息发送接口。Dispatcher服务通过共享的DispatcherTable数据获取接收者所在的ChannelManager实例,通过RPC向实例发送消息,ChannelManager通过WebSocket将消息推送给客户端。IM服务调用流程如下图所示:当连接数超过当前ChannelManager集群的上限时,只需要扩展ChannelManager实例,ETCD会动态通知监听端,从而实现顺利扩张。目前已经开发出浏览器版JS-SDK,其他业务线可以通过访问文档轻松集成IM服务。在Exchange层的设计中,需要考虑三个问题:1.多端消息同步目前客户端包括PC浏览器、Windows客户端、H5、iOS/Android。如果一个用户登录了多个终端,当消息来的时候,需要找出这个用户的所有连接。当用户的某一端断开连接时,需要定位这个连接。如前所述,连接信息存储在DispatcherTable模块中,因此DispatcherTable模块必须能够根据用户信息快速获取连接信息。DispatcherTable模块的设计使用了Redis的Hash存储。当客户端与ChannelManager建立连接时,需要同步的元数据包括uid(用户信息)、uniquefield(唯一值,连接对应的唯一值)、wsid(连接标识)、clientip(客户端ip)、serverip(serverip),channel(通道),对应的结构大致如下:这样可以通过key(uid)找到一个用户多个终端的连接,通过key+fieldconnect定位到一个用户.连接信息的默认过期时间为2小时。目的是为了防止由于客户端连接异常中断导致服务端无法捕获,从而在DispatcherTable中存储一些过期的数据。2、用户在线状态同步。例如,用户先后咨询过4个客服,则该用户会出现在4个客服的咨询列表中。用户上线时,需要保证4个客服人员看到用户在线。有两种方法可以做到这一点。一是客服通过轮询获取用户状态,但是当用户在线状态没有变化时,会发起很多无效请求;另一种是当用户上线时,客服推送上线通知,这样会造成消息传播,每个咨询过的客服都需要传播通知。我们最终采用了第二种方法。在推送过程中,我们只将用户状态推送给在线客服。3.消息不会丢失或重复。为了避免消息丢失,当我们使用长连接轮询方式时,我们会在发起请求时带上客户端已经阅读过的消息的ID,由服务端计算差异消息并返回;使用WebSocket方式,服务端将消息推送给客户端后,会等待客户端的ACK。如果客户端没有ACK,服务器会尝试多次推送。此时客户端需要根据消息ID进行消息重复处理,避免客户端可能已经收到消息,但由于其他原因导致ACK确认失败,触发重试,导致消息重复。IM服务的消息流前面提到,IM服务需要支持多终端,同时从角色上分为用户端和商家端。为了让通知和消息能够根据域名、终端、角色动态输出,差异化Content,引入DDD(DomainDrivenDesign)建模方法来处理消息。处理流程如下图所示:总结与展望随着马蜂窝“内容+交易”模式的不断深入,IM系统架构也在不断演进。而升级的不同阶段,从最初粗放无序的模式,到统一管理,逐步规范,形成规模。我们取得了一些进展,当然,还有很长的路要走。未来,我们将结合公司业务发展步伐和团队技术能力,不断优化IM系统。目前,我们计划将消息轮询模块中的服务端代码替换为Go,使其不再依赖PHP和OpenResty环境,实现更好的解耦;此外,我们还将实现基于TensorFlow的智能客服探索,通过训练数据模型和数据分析,进一步提升人工客服的效率,提升用户体验,更好地为商家赋能。本文作者:马蜂窝电商平台IM研发团队。(马蜂窝科技原创内容,转载请注明出处并在文末保存二维码图片,谢谢合作。)
