在线聊天功能的总设计思路:
现在有两个浏览器在不同的两台电脑上面, 浏览器A登陆的是系统管理员,
浏览器B登陆的是总监, 现在系统管理员想给总监发送消息,而浏览器之间
是不可以相互之间直接发送消息的。因为一个浏览器是在A电脑上面,
一个浏览器是在B电脑上面。这两台电脑是不可以相互之间直接通讯的。
但是这两个浏览器都访问了CRM这个网站。我们就想一个办法,
系统管理员先把这个消息发给服务器,然后总监再向这个服务器要,
那么这样就涉及到一个问题。系统管理员把这个消息给服务器,服务器又怎么知道
这个消息是给总监的呢?所以必须给服务器一个实体,这个里面要包括哪几个东西呢?
首先谁发的必须要有,我们给这个实体取名为 ChatMsg , ChatMsg里面有这么几个属性
FormUserID(发送者ID),FormRealName(发送者名称),ToUserID(接收者ID),
ToRealName(接收者名称),MessageBody(内容),SendTime(发送时间)
系统管理员把这个消息实体发给这个网站以后,总监登录系统以后向这个服务器要,
总监的这个请求就必须带一个userid过来,服务器就会把userid等于这个值的消息给总监。
现在A电脑系统管理员和B电脑总监 都发送消息给这个网站,而这个网站还要做其他很多事情
这样就相当于它的职责不太清晰,压力也变大了。
那么想一个办法,CRM网站就只是负责处理登录,权限控制这些功能。那么这个聊天消息
的实时处理以及入库就交给另一个网站来处理。
就相当于一个应用服务器 ,那么在分布式结构里面,CRM就充当着
Web服务器。而这个应用服务器上面呢 说白了 也就相当于一个IIS,而在这个IIS上面发布了
一个站点,这个站点是用WCF构建的, 这个WCF服务 负责 聊天消息的实时转发和
历史消息的入库操作。那么这个时候 CRM的职责只需要 将浏览器发送过来的ChatMsg实体
转发给WCF聊天服务即可。无需理会这里面的逻辑了,这样CRM压力释放了,职责也明确了。
总监发送ID想从服务器拿到自己的消息,而CRM也只需要把ID转发给WCF聊天服务,由WCF处理
返回再由CRM转回给总监即可。这个时候CRM的职责就是一个转发的功能。所有的业务都不需要
处理,直接交给WCF服务处理,
那么再分析这些具体里面需要什么实际技术去实现呢?
发送消息需要什么就可以简单实现呢?在线聊天的界面,左边是一个用户列表,数据来源于
sysUserInfo这个表,右边上面用一个面板来做接收消息区域,放一个div就行了。
下面再放一个文本框,里面就是用来填要发送的消息内容。再有两个按钮,发送和查看历史消息
点击发送用Ajax的异步请求把文本框里的消息内容发送个CRM。
那么这个异步请求到底发送哪些消息过去?
FormUserID(发送者ID),FormRealName(发送者名称)表示发送者系统管理员,在服务器
已经登录就可以在Session中拿到,在这里没必要浏览器发送到CRM。就只需要把
ToUserID(接收者ID) ToRealName(接收者名称),MessageBody(内容),发送给服务器。
SendTime(发送时间)也不需要,就直接取服务器时间。
所以Ajax请求只需要把
ToUserID(接收者ID),ToRealName(接收者名称),MessageBody(内容),发送给CRM网站即可。
然后在CRM网站内部实例化ChatMsg对象就行了。
那么WCF这边级需要2个方法,发送消息需要一个方法。
SendMessage(ChatMsg msg)方法,这个方法
将来被CRM网站调用的。CRM实例化ChatMsg通过参数传给SendMessage方法。
因为发送消息以后级行了,不需要等待服务器处理完,所以这个方法是个数据报方法即可。
第二个方法:B电脑用户要从服务器拿到自己的消息,发送userid到CRM,然后转到WCF
将userid 传给方法 GetMessage(int userid),就把当前传过来的用户ID对应的聊天消息返回
,而返回的格式就直接返回ChatMsg实体的集合List<ChatMsg>,这个方法就必须是请求响应方法
,为什么?因为必须等服务器响应完才能返回,否则拿回去的数据会是空的。
那么这SendMessage方法接收到这个实体所做的逻辑步骤有哪些?
1,将msg消息实体先入Redis队列,
2,入库。加入到历史表中,方便将来查询。
GetMessage()方法呢?
获取消息逻辑有:
1,根据用户id从Redis中将消息出队 就直接返回
这两个方法的逻辑确定下来后,就要考虑第3件事。考虑性能问题
当用户频繁使用聊天功能进行发送消息的时候,会出现频繁的操作数据库
这样会导致数据库的压力非常大。
那该如何解决:
1,使用一个线程每隔1分钟将一批聊天记录进行一次性入库操作。
这样就能解决频繁操作数据库的问题。提升了db的性能,
但是引发了另一个问题,在进行一批数据的入库操作时,如果这批数据过大,
如何保证快速插入到db中。
解决方案:使用 system,data.SqlBluckCopy类来进行高效的批量插入处理,
这个时候注意:利用历史消息队列来统一存放要入库的消息实体。不然数据
从Redis中出队就没有了。
注意问题:
1,每个用户在Redis中都要有一个专属的消息队列(不然获取不到消息):
allmsg+msg.ToUserID,
这样就引发出一个设计问题:
1,在Redis中开启一个实时队列,此实时队列是每个用户都有一个的,
用来存储别人给这个用户发送的消息数据。
2,在Redis中开启一个历史消息队列,这个队列中存储的是每个用户的聊天消息,将来
每隔固定时间就将此消息队列中的数据入库(考虑到分表来存储)
3,使用单例类来管理上面两个队列利用Lock()锁防止多线程的并发问题。