聊天室开发心得

聊天室开发心得

之前在新浪微博工作期间,开发了一个用于在线直播互动的聊天室项目,虽然最后由于高层决策的原因,微博最后没有采用我们的服务,但觉得还是可以将这个项目开发过程分享一下,有一些点可以参考的。

这是最开始的一个架构图:

客户端维护两个链接,一个是与网关的长链,用于接收下推的消息和事件;一个是与聊天室服务器的短链(其实是经过了 lvs 的负载均衡 DR 代理),用于发言、点赞、加入和退出聊天室。

chatroom server 在收到用户加入的请求后,将用户信息保存在 redis 中。redis 中还保存有房间信息、房间的成员列表。在收到用户退出的房间请求后,从 redis 中删除该信息。

chatroom server 收到用户发言后,会从 redis 中读出房间成员列表,然后根据用户信息找到对应的网关服务,向网关发送消息,由网关推给相应的客户端。

这只是原始模型,只能说可用。用户量大了后,因为每一次发言都要从 redis 中获取整个房间的成员列表(hgetall 操作),给所有成员下推,数据量太大了,给 cpu 和内存带来很大的压力。同时,由于下推服务很重,拖累上行服务,造成并发量上不去。

另外,我们最开始使用的 redis,有4组实例,每组实例只有一个 master 节点,没有从节点,这在数据保障和并发性上都不及格。hgetall 是一个慢操作,一个房间人数增长到一定程度,这个操作会严重影响房间所在的 redis 的性能。因此,我们采取了两个措施,一个每组实例添加从节点,主节点写,从节点读,redis 客户端实现读写分离;二,数据分片,当房间人数增加到一定程度,在 redis 客户端对房间成员进行分片,平均分配到4组实例上,采用轮询的方式。

我们对架构进行了一次调整:

我们把下推服务单独分离出来。chatroom server 收到用户的发言请求后,会将内容写入到队列中. broadcast 服务从队列中读取消息,从 redis 中读取房间成员列表,然后推给网关。这样实现 I/O 密集型操作与CPU密集型操作分离。经过这次修改,我们的并发量有了很大提升。

举个例子,一个房间有 1w 用户,每个用户发一条消息,一条消息是 1k,那么我要推 1w * 1w * 1k 的数据下去,这个流量太大了。所以,必须采取主动丢弃的策略。因为在聊天室中用户的消息,并不需要像 IM 那样实时准确,过于频繁时丢弃一些,用户体验反而会更好。

我们在 broadcast 前加了一个漏桶,用户的所有发言、点赞等下推消息都放在这个漏桶中,broadcast 广播完一条消息,就去读下一条消息。但漏桶满了,就丢弃新的消息(或者覆盖旧的消息)。这里需要注意的是,漏桶的容积不要设置过大。如果用户发言比较频繁,broadcast 处理速度又相对较慢,会造成大量的消息堆积,造成的一个现象就是直播结束了,有的用户的发言才被推送到客户端。

另外,在 chatroom server 内部,以单个房间为维度,我们设置了一个频率监控器,当房间内用户发言行为频率超过阈值后,主动丢弃,并给客户端返回成功。

之后我们又做了一些改善。用户发送的消息是不同的,普通用户的发言、点赞的优先级低一些,可以多丢弃。用户的打赏、送礼以及明星用户的发言优先级高一些,尽量不丢弃。针对不同的优先级,设置不同容量的队列,由不同的进程处理。

该项目是用 Erlang 语言来实现的,队列使用的是 Pobox 。这个工具针对 Erlang 进程消息箱做了优化,可以限定消息箱大小。

posted @ 2017-12-22 12:50  我的娃会叫爸爸啦  阅读(1023)  评论(1编辑  收藏  举报