刚开始来到公司的时候,运气比较好,正好公司的聊天室要改版,所以有幸参与了此项目。
聊天室大家都知道,是个互联网早期的产物了,由于它特有的聊天属性,加上用户发言频繁,有新的发言时,所在房间的用户需要能够及时看到,如果用户量大了,对于技术上还是有一定的含量的
我来的时候,带我的组长就敲定了架构:openresty,一个通过扩展nginx实现的强大应用服务器;有兴趣的可以参考起官网:http://openresty.org/;还有用户获取最新聊天记录的方式采用轮询的方式,eg:每隔5S访问下接口
大概的架构组合就是nginx + lua + redis;nginx作为应用服务器,lua实现逻辑部分,redis作为数据存储(openresty封装了redis的使用)
整个开发过程,大概经历了3个版本
1.基于以前的数据,单机的聊天室由于能够承受的人数太少,所以一开始的设计就是奔着集群去的
1.1 先看一下第一版的设计吧:
第一层是公司统一的域名跳转服务器,有可能直接是dns,也有可能是nginx的upstream,具体的问了下前辈,他们也不清楚
第二层呢,openresty就发挥了它的作用了,通过content_by_lua_file,使用lua来做一些逻辑处理,然后把数据存入到redis中,redis是个key-value的数据,由于redis也是一个集群,所以在这层呢,通过对key做一致性hash,这样对同一数据的存入和获取都会到同一台redis了
第三层就是主要负责数据的存储了,由于考虑到聊天数据不是那么重要,所以呢,redis的save功能都给注释掉了,只是把数据保存在redis内存中,并且设置了2天的过期时间,也就是说,对于一个聊天室,数据只保留2天的时间,这样会大大减少服务器的内存压力
1.2这一版的设计呢,有一个很大的缺陷,呆会再说,先说说具体的数据结构设计,
对于聊天室,重要的操作无非是分房、发言、以及获取最新发言这三个接口,当然还有其他一些辅助的接口,如禁言、举报等等
首先来说说分房操作,对于聊天室,分房的步骤大概是:①首先设定房间的人数,房间的人数即不能太多也不能太少,太少了,就会显的聊天室活跃度不够,太多了,就有可能造成刷屏,当然也有可能会对服务器造成很大的压力,原因呆会说 ②来了一个新的用户,检查第一个房间是否满员,如果没满,则把用户分到这个房间,如果满了,则要重新开一个房间
这里边,每一个房间的人数会经常变化,因为用户有可能离开嘛,有离开的就会有加入的,所以这个人数经常变化的,把这个变量放入到redis中就不合适了,大家都知道,虽然redis比较高效,但是如果聊天室的人数特别多,对redis的访问太频繁了,redis也受不了了,毕竟有网络开销嘛,肯定会出现timeout的现象,这个问题虽然一开始就规避了,但后来上线测试的时候,还是出现了timeout,当然不是这个导致的,这个后面再说;所以每个房间的人数就放到了共享内存中(openresty 的lua_shared_dict);还有聊天室的房间号也放在了内存中(房间号是从1开始递增的,所以只保存了当前最大的房间号)
再说说发言,用户的聊天记录在聊天室里面是最重要的,所以第一版设计聊天记录都被放入到了redis中,使用的是list结构,这样的设计在人数少的时候没有问题,但是人数多了,redis访问频繁了,就出现了上面提到的timeout问题,这个在第三版设计中得到了很好的解决
第一版的获取最新发言是通过用户传过来的start字段(start字段分房的时候会传给客户,客户拿到后,每获取一次记录,就会在客户端更新一下,因为这个设计,后来改版的时候费了好大劲才得以兼容,是一个教训,这个字段最好的设计应该服是务器给返回,而不是客户端自己计算),直接从redis取得