IM实时通信系统
背景
IM实时通信是基于刚才提到的Websocekt实时消息推送系统的子系统,和消息推送系统的区别主要是业务上,实时通信是前后端交互推送消息,而并非消息推送只会从服务端进行push。
具体实现
当用户在平台点击咨询按钮时,会激活聊天窗口,此时消息体内由json数据构成,存储发送人uid(当前登录用户uid),和接收人的uid,后台通过onmessage方法获取到前端推送的websocket数据,从json中解析出接收人uid,随后通过接收人的uid在websocket链接容器散列表中进行寻址,获取接收人websocket链接对象,然后将消息推送到接收人的聊天窗口中。
时序算法
其实这里面有一个算法问题,就是客户发起在线咨询请求的时候,如何对应分配客服的问题,一般情况下,客服数量是有限的,怎么才能在有限的客服资源条件下,最大化的提高咨询效率?
我自己设计了一套基于时序的算法
首先声明一个全局时序变量,这个变量默认时序是0
随后定义一个基于散列表(哈希)的容器,key是就客服所在时序,假设我们有三个客服,每个客服最多同时和三个客户聊天,那么并发聊天的阈值就是9个客户,如此,我们的哈希时序就分为四个状态,分别是 0、1、2、3 分别代表同时在和几个客户聊天:
# 时序字典
s_dict = {0:[c],1:[a,b],2:[],3:[]}
客服分配
当客户发起咨询请求时,首先通过时序变量获取客服所在的线性结构容器,此时可以获取到当前的客服列表,随后采用hash取模算法,通过对客户uid进行哈希操作,并且对客服列表长度n取余操作,获取到分配客服的下标,接着通过下标累加操作,将该客服转移到下一个时序列表中,同时根据下标将当前分配后时序客服删除。
当然,这里可能会有一些问题
比如如果当前时序已经是3了,说明客服系统已经满载了,此时需要进行排队操作,客户uid会进入队列,遵循先进先出原则,当某一个客服被释放之后,再触发出队逻辑。
另外一个问题是,当前时序客服队列的长度如果是1的话,说明当前时序只剩下一个客服,那么客服增序操作后,当前时序变量也必须增序,因为当前时序已经不存在客服,时序变量不增量操作的话,就无法获取到客服列表了。
# 分配客服方法
def send_user(uid):
global s
if s == 3:
print("客服正忙,请稍后")
return
if len(s_dict[s]) == 1:
user = s_dict[s].pop()
s_dict[s+1].append(user)
s += 1
return s_dict[s][0]
else:
user_index = hash(uid) % len(s_dict[s])
user = s_dict[s].pop(user_index)
s_dict[s+1].append(user)
return user
客服释放
客服不仅仅有分配逻辑,当客户关闭聊天窗口后,会触发绑定事件,将该客服uid发送至后端,该客服会进行减序操作,即在时序散列表中进行寻址,找到客服uid所在的序列,然后将该客服序列做递减操作。
这里可能出现的问题是,时序变量的递减时机问题,只有在客服所在序列和当前时序吻合的情况,才会针对时序变量递减,否则时序变量不变,因为如果每一次都递减,就会出现时序错乱,无法获取到客服时序列表的问题。
举例子:时序1中两位客服,时序3中一位客服,时序3客服递减,总时序不应该递减
举例子:时序2中三位客服,其他时序没有客服,时序2中客服递减,总时序也应该递减
# 释放客服
def release_user(user):
global s
# 查找客服所在时序
for key in s_dict.keys():
if user in s_dict[key]:
s_dict[key].remove(user)
s_dict[key-1].append(user)
print(len(s_dict[key-1]))
if key == s:
s = s - 1
break
聊天记录存储
历史聊天记录我会保存在redis中,生命周期是一个月,使用hash进行存储,主要目的是为了量化客服的工作绩效,以及对聊天记录进行自然语言分析(nlp),这块聊天记录往往一式两份,但真实记录没必要存两份,所有通过客户uid和客服uid做正序排序操作后,组成唯一的聊天记录key,只存储一份聊天记录,节约系统内存资源。