一、背景
客服一站式工作台蕴含了在线、电话、工单和工具类四大功能模块。其中很多通用的模块,比方工单详情、订单详情都是通过 iframe 的模式嵌套的,在零碎加载过程中会比拟耗时,再加上在线音讯通信模块强依赖 tinode 第三方 SDK,很多办法都是间接调用 tinode 提供的 api,同时也继承了 tinode 很多不合理的形式,从应用 tinode 到目前为止,因迭代资源的投入,始终没有对 tinode 源码做一些优化和改良,当音讯通信的模式改成播送之后,会话卡顿问题就裸露进去了。通过对 tinode 源码音讯链路模块的浏览,发现了有不少的优化空间,本文则是针对音讯链路这块论述的具体优化实现。
二、发现问题
1、音讯数据处理流程存在缺点
通过对 tinode 第三方 sdk 源码的浏览,发现其中客服在“接管”和“发送”音讯的链路上有很大的优化空间,在原有的逻辑中,从发送音讯到疾速渲染页面再到 tinode 响应返回后果再去刷新渲染页面,以及客服承受到音讯的时候,会对整个音讯进行刷新,反序列化、排序、去重、状态解决等等 , 都须要屡次的循环,再加上通信模式改为播送模式,大数据量循环工作,对于性能来说是个严厉的挑战。
客服“接管”和“发送”音讯链路概图(一)
客服“接管”和“发送”音讯链路概图(二)
图中 红色区域有较多的 for 循环是耗时最多的场景 ,起因是要获取用户与客服沟通记录( 原有 tinode 中提供的形式,topic.message() 会执行 n 次),反序列化、会话状态解决、排序、去重都会将所有聊天音讯进行遍历,其中 反序列化为耗时最高的场景,如果客服跟用户之前的聊天音讯越多,遍历次数就越多,耗时就越久,再加上 JavaScript 是单线程,遍历次数多了就会造成阻塞,导致客服在疾速切换会话的时候,循环还未完结,页面未渲染实现,就呈现卡顿景象。
三、优化思路
每一位用户从客户端进线到坐席客服工作台的时候,会生成的一个会话 id(sessionId),每一个会话 id 上面的每一条人工音讯中都会有一个音讯 id(msgid)
客服在跟用户之间来回沟通的音讯回合比拟多,为了缩小“老代码”中屡次循环升高性能的操作,想到 最外围的工作 就是尽量避免去遍历聊天的音讯数据(因为音讯太多了),遵循能不遍历聊天音讯就不遍历的准则,对于原逻辑中的“去重”和“排序”逻辑做了重写,这个时候,下面提到的会话 id 和音讯 id 就起到了十分重要的作用。
1、去重
本次优化计划中采纳全局保护一个 msgidCacheMaps Map 数据结构,这个数据结构有两个维度,sessionId 和 msgid,用来保留以后会话 (sessionId) 中每条音讯的 msgid,音讯对话中,人工客服发送的音讯会经验从虚构音讯到实在音讯两个阶段(这里的的虚构音讯指的是在人工会话中,客服向网关发送音讯后,为了疾速让音讯展现在聊天区域,通过前一个音讯 seq + 0.002 生成虚构的 seq 即:virtualSeq,等到网关返回实在的 seq 后,再将 virtualSeq 替换成实在的 seq),虚构音讯阶段会保留 msgid 到 Map 中,对于零碎推送的音讯,没有 msgid,不须要经验这个过程,间接放进会话池,实在音讯 (tinode 返回 seq) 阶段,依据 msgid 到 msgidCacheMaps Map 数据结构中进行查问,存在此 msgid,阐明是反复数据,配合 seq 进行替换即可。
2、排序
本次的优化计划是采纳 二分查找插入排序 * 的办法,全局保护一个 seqCacheMaps Map 数据结构,这个数据结构跟下面去重有些相似,也有两个维度,sessionId 和 seq,二分查找插入排序的办法,用 seq(实在 seq)和 virtualSeq(虚构 seq)作为查找的根据,每次音讯进来,依据二分法疾速找到以后 seq 可插入地位,虚构音讯阶段,直接插入,实在音讯阶段 (msgidCacheMaps 存在此 msgid),间接替换,然而这个时候遇到一个问题,因为在人工会话过程中客服向用户发的每一条音讯都会在网关进行敏感词校验,没有触发到敏感词就会将音讯发送到客服端展现给用户,如果触发到敏感词,含有敏感词的音讯就会被网关拦挡,音讯也不会达到用户侧,此时网关也不会返回 seq,那么没有返回 seq,又该如何解决呢?那就是 在 tinode 返回阶段,会把后面 virtualSeq 替换为前一个音讯 seq + 0.002,确保其地位有序不会错乱的展现在在聊天区域 *。
“去重”和“排序”概图
3、缓存回收(完结会话销毁)
在下面 去重 和 排序 中提到,为了缩小遍历次数,全局保护两个数据仓库(msgidCacheMaps Map 数据结构、seqCacheMaps Map 数据结构),然而每位客服每天的会话量在 100+,再加上每条会话中客服和用户的来回音讯数约 40+,如果客服再去查看历史音讯,一页 20 条,如果只存不删,存储的数据量还比拟宏大的,容易导致内存溢出,那么什么时候删除比拟适合呢?依据业务状况,最初抉择在完结会话、会话转接、推送离线的状况下会对挂载在全局的 hash map 进行销毁,开释内存。
数据“存储”和“删除”概图
4、音讯状态
这里的音讯状态指:已读、未读、已接管、发送中、发送失败……等
在客服和用户沟通过程中,客服侧和用户侧所展现的音讯状态都是实时更新的,客服发送音讯给用户,当用户读了这条音讯后会返回 info 协定(推送曾经音讯告诉)通知 h5 侧该条音讯已读,而后 h5 侧对该条音讯进行状态更新
- 原解决形式: 当客服给用户发送音讯后,对以后会话中这个用户的所有历史音讯进行遍历,进行全副重置操作,这个时候如果遇到用户与客服沟通的音讯很多的状况,就会导致遍历次数多,产生重大耗费性能等问题。
- 优化计划: 先过滤掉历史音讯和非客服发送的音讯,通过二分法的形式去找到该音讯,而后间接扭转状态。在收到用户发送的音讯后,对 messagePools(以后用户所有会话)中的客服发送的音讯倒序进行状态更新为已读,因为既然用户都发消息过去了,阐明客服发送的音讯曾经被浏览过了,就不须要依照之前老的逻辑再去给每个音讯都遍历去设置状态了,节约性能,除发送中和发送失败的音讯外,全副渲染为已读
- 具体实现:客户端推送长链 note 事件通知 H5,H5 侧记录已读的这条音讯的 seq,对于小于等于 seq 的客服发送的音讯数据进行状态更新,即:recv(已接管) => read(已读)
- 发送音讯:目前发送音讯只会执行 2 次,第一次会疾速将音讯展现到沟通页面,而后再进行音讯的发送(wss),当收到 ack 后,会进行二次音讯状态更新,只通过 msgid 会找到须要更新的音讯进行更新,不再须要利用 tinode 提供的 topic.message 办法进行全量遍历了
- 接管音讯:客服接管用户音讯只会触发一次音讯更新,不须要再对以后用户的全量数据进行遍历更新新状态了,同时也会回 ack
5、敏感词拦挡解决
IM 聊天页面在用户进线后,对于用户和坐席客服之间发送的音讯会进线敏感词监控(仅监控 和 禁止发送)。
- 原计划: 坐席客服在编辑音讯后,点击发送,调用后端敏感词接口,没有触发到敏感词校验通过后能力收回去,如果呈现网络稳定接口返回慢的时候,就会让客服感觉发消息卡一下能力进来的状况。
- 优化计划: 通过网关拦挡,客服发送音讯的时候,间接渲染到聊天区域,网关去测验发送的音讯是否触发到敏感词,如果有触发到敏感词,那么网关会返回一个状态通知 h5,h5 再依据返回的后果更改状态去提醒客服。
敏感词逻辑概图
四、优化前后数据比照
优化链路技术计划实现整体在 2 月 28 号公布上线,所以 以 2 月 28 号为工夫截点,拉取了优化前后的数据比照,具体如下所示。
1、优化前
如上图所示,统计了 2022 年 2 月 1 日 ~ 2022 年 2 月 28 日总进线的两个数据指标:
- 均匀首次响应时长: 8.40 秒
- 均匀响应时长: 19.9 秒
2、优化后
如上图所示,统计了 2022 年 3 月 1 日 ~ 2022 年 3 月 9 日总进线的两个数据指标:
- 均匀首次响应时长: 6.82 秒,比优化前缩小了 1.58 秒
- 均匀响应时长: 18.22 秒,比优化前缩小了 1.68 秒
五、总结
一般来说 IM 产品的用户量和活跃度通常都很大,在一些非凡的工夫点常常容易造成流量的波峰,因而技术上须要可能应答突发的量级,同时 IM 个别次要蕴含这 4 个特点:实时性、可靠性、一致性、安全性,对于 IM 的优化还有很长的路要走,在保障业务稳固状况下,后续咱们也会围绕着四个特点持续致力打磨,让合乎得物本人的 IM SDK 越来越欠缺,造成行业音讯通信的标杆。
文 /YU BO
关注得物技术,做最潮技术人!