单位即时聊天改版了.
正好趁着这次机会踢了MongoDB.
大家都拍手叫好..可见MongoDB这玩意多么的不得人心...都觉得不好用..
场景描述:
我们的IM软件有PC端和手机端.
同时在线的用户,通过长连接转发,并且存储消息.
接收方不在线,存储消息.
用户打开电脑端软件或者手机端网络掉线重新连接,都需要获取未读消息数量.当用户点击未读消息的时候,提供消息正文.
经过抽象,JAVA这块需要提供两个接口
1.获取用户的未读消息列表
2.给定发送方ID和接收方ID,返回消息内容.
发送方用户ID srcid
接收方用户ID destid
每个会话的当前消息ID mid(针对每个发送方和接收方,自增)
1.发送方用户通过电脑将消息发送至Web服务器.消息主要内容(srcid,destid,msg)
2.Web服务器根据srcid,destid获取会话的mid(会话状态的自增ID)
3.将消息放入持久化队列,并且更新redis未读消息列表
首先将(srcid,destid,mid,msg)放入持久化队列.
然后更新redis的用户未读消息列表.
未读消息列表在Redis的存储结构如下.
在接收方ID的前面增加一个前缀,表明是手机未读消息还是PC未读消息.这个作为key
value是一个HashMap.这个HashMap的key是发送方ID,value是未读消息数量.
一旦用户登录,直接拉取该用户的HashMap内容展示.
这部分内容是有过期时间的,假如用户长时间未使用.这个PC-destid和Mobile-destid的条目将被删除.
如果程序发现这个条目不存在.则去数据库中查询未读消息列表.
假如我的ID是1000,老板的ID是1001,老板给我发送了三条消息,但是我一直没有在线.
程序将做如下操作.
127.0.0.1:6379> HINCRBY pc-1000 1001 1
(integer) 1
127.0.0.1:6379> HINCRBY pc-1000 1001 1
(integer) 2
127.0.0.1:6379> HINCRBY pc-1000 1001 1
(integer) 3
127.0.0.1:6379> HINCRBY mobile-1000 1001 1
(integer) 1
127.0.0.1:6379> HINCRBY mobile-1000 1001 1
(integer) 2
127.0.0.1:6379> HINCRBY mobile-1000 1001 1
(integer) 3
如果我登录手机客户端,点击未读消息.则对Redis操作如下,删除对应条目
127.0.0.1:6379> hdel mobile-1000 1001
(integer) 1
127.0.0.1:6379>
并且回写数据库一个状态标识(已读的最大mid).这个回写数据库的过程,也是异步的,每隔30s回写一次数据库状态.
这个时候,我登录PC端,还是可以查看未读消息数量的.这个业务要求有点奇怪.但是要求就是这么做.
4.如果发送方的其他设备在线,或者接收方的设备在线,则转发消息.
5.JAVA从队列中异步获取消息,然后批量Insert到数据库.
数据库存储设计
初始设计使用4台MySQL分库.使用(发送方用户ID+接收方用户ID) mod 64
这样的好处是,A用户发送B用户和B用户发送A用户的消息,都会落在同一个底层数据库.
这样获取A,B之间的聊天内容,使用一个SQL查询即可.
聊天消息表,本身也是按照时间进行分区
正好趁着这次机会踢了MongoDB.
大家都拍手叫好..可见MongoDB这玩意多么的不得人心...都觉得不好用..
场景描述:
我们的IM软件有PC端和手机端.
同时在线的用户,通过长连接转发,并且存储消息.
接收方不在线,存储消息.
用户打开电脑端软件或者手机端网络掉线重新连接,都需要获取未读消息数量.当用户点击未读消息的时候,提供消息正文.
经过抽象,JAVA这块需要提供两个接口
1.获取用户的未读消息列表
2.给定发送方ID和接收方ID,返回消息内容.
发送方用户ID srcid
接收方用户ID destid
每个会话的当前消息ID mid(针对每个发送方和接收方,自增)
1.发送方用户通过电脑将消息发送至Web服务器.消息主要内容(srcid,destid,msg)
2.Web服务器根据srcid,destid获取会话的mid(会话状态的自增ID)
3.将消息放入持久化队列,并且更新redis未读消息列表
首先将(srcid,destid,mid,msg)放入持久化队列.
然后更新redis的用户未读消息列表.
未读消息列表在Redis的存储结构如下.
在接收方ID的前面增加一个前缀,表明是手机未读消息还是PC未读消息.这个作为key
value是一个HashMap.这个HashMap的key是发送方ID,value是未读消息数量.
一旦用户登录,直接拉取该用户的HashMap内容展示.
这部分内容是有过期时间的,假如用户长时间未使用.这个PC-destid和Mobile-destid的条目将被删除.
如果程序发现这个条目不存在.则去数据库中查询未读消息列表.
假如我的ID是1000,老板的ID是1001,老板给我发送了三条消息,但是我一直没有在线.
程序将做如下操作.
127.0.0.1:6379> HINCRBY pc-1000 1001 1
(integer) 1
127.0.0.1:6379> HINCRBY pc-1000 1001 1
(integer) 2
127.0.0.1:6379> HINCRBY pc-1000 1001 1
(integer) 3
127.0.0.1:6379> HINCRBY mobile-1000 1001 1
(integer) 1
127.0.0.1:6379> HINCRBY mobile-1000 1001 1
(integer) 2
127.0.0.1:6379> HINCRBY mobile-1000 1001 1
(integer) 3
如果我登录手机客户端,点击未读消息.则对Redis操作如下,删除对应条目
127.0.0.1:6379> hdel mobile-1000 1001
(integer) 1
127.0.0.1:6379>
并且回写数据库一个状态标识(已读的最大mid).这个回写数据库的过程,也是异步的,每隔30s回写一次数据库状态.
这个时候,我登录PC端,还是可以查看未读消息数量的.这个业务要求有点奇怪.但是要求就是这么做.
4.如果发送方的其他设备在线,或者接收方的设备在线,则转发消息.
5.JAVA从队列中异步获取消息,然后批量Insert到数据库.
数据库存储设计
初始设计使用4台MySQL分库.使用(发送方用户ID+接收方用户ID) mod 64
这样的好处是,A用户发送B用户和B用户发送A用户的消息,都会落在同一个底层数据库.
这样获取A,B之间的聊天内容,使用一个SQL查询即可.
聊天消息表,本身也是按照时间进行分区
- create table chat_msg(
- id bigint auto_increment,