实现一个IM(1)
IM (即时通信,Instant Messaging)在如今应用相当广泛:
- 聊天软件、站内消息
- 弹幕、礼物互动
- 共享白板、屏幕、位置
天天吐槽某绿色聊天软件的消息同步是个垃圾,骂完之后我又想到,如果让我搞一个IM,我要如何保证多设备消息同步呢,为何它的群聊限制是500人,而企鹅是2000人,tg则是上万人呢 ?
撤回、已读是如何实现的呢,我如何保证消息精准无重复按顺序送达呢?
不谈什么业务考虑和风控,只是最基本的功能的话,技术上到底是如何搞呢,深感自己知识的匮乏,是时候来学习一波了。
整体架构
接入服务是消息收发的出入口,主要负责保持连接、解析协议和 维护Session,业务处理服务则用处理各种乱七八糟的业务逻辑,一般会把这俩分开,计算机行业有一句经典名言叫做没有什么是再加一层不能解决的
将接入服务和业务处理服务处理服务解耦,是因为业务逻辑 可能 一定经常变动,而接入服务做的事情相对单一和稳定,而且可以搞业务的专心业务,搞技术的专心技术,提高效率和.......编码愉悦感?
存储服务用于各种数据的持久化,某APP: 我们 不 保证没有存你们的 破 聊天记录呢!
外部接口服务主要用于消息提醒之类的。
从发到收
库表设计
不知道用NoSQL或者ES存会不会在某些方面更高效,对这俩和IM还没理解到那个程度,还是先从熟悉的关系型数据库开始吧。
我们来看一个场景,小明给小红发了一条消息:俺喜欢你。
那么我们得有一张消息表,存储消息内容和消息类型,再加上Id和时间
message_id | type | content | time |
---|---|---|---|
000001 | 文本 | 俺喜欢你 | 2020-04-09 00:07:41 |
000008 | 图片 | 爱你.jpg | 2020-04-09 00:07:55 |
000099 | 文本 | 么么哒 | 2020-04-09 00:08:59 |
000111 | 音频 | 月亮代表我的心.mp3 | 2020-04-09 00:09:59 |
然后还得有一张表用来记录接收者和发送者,即消息表增加一条数据的时候,索引表增加两条数据,这两条分别记录了小红和小明,并标记他们是收方还是发送方。
存两条的原因主要是考虑各自索引维护时的独立性:比如一方删除了消息不影响另一方查看类似的需求。
Uid | other_uid | send_or_Receive | message_id |
---|---|---|---|
小明 | 小红 | 0 | 000001 |
小红 | 小明 | 1 | 000001 |
其实还有一些数据:
Uid | other_uid | send_or_Receive | message_id |
---|---|---|---|
小明 | 小花 | 0 | 000008 |
小明 | 小芳 | 0 | 000099 |
小花 | 小明 | 1 | 000008 |
小芳 | 小明 | 1 | 000099 |
小红 | 小王 | 0 | 000111 |
小王 | 小红 | 1 | 000111 |
一般都还有个对话框的界面,这里存储着最近联系人和最新的一条消息,这就需要一个联系人表:
Uid | other_uid | message_id |
---|---|---|
小明 | 小红 | 000001 |
小明 | 小花 | 000008 |
小明 | 小芳 | 000099 |
小红 | 小明 | 000001 |
小花 | 小明 | 000008 |
小芳 | 小明 | 000099 |
小红 | 小王 | 000111 |
小王 | 小红 | 000111 |
可以看出我们的小明同学是个脚踏三只船的海王,而小红同学爱着隔壁小王。
其实最近联系人这个表的数据我们可以直接写SQL从索引表中拿到,但是存数据一时爽,取数据火葬场,徒增SQL的复杂度。而且独立建一个联系人表还有一个潜在好处是之后可以直接把未读数这个字段放到这个表里。不过未读数一般不需要存表,直接扔redis里就完事了。
消息发送通道和接收通道
消息发送很简单,客户端调用一下服务端的send接口即可,但是消息接收稍微麻烦一点,因为从服务器的视角来看,需要区分客户端是否在线。
如果客户端在线,那么服务端和客户端之间有一个长连接,服务端便可通过此连接将消息下发,如果客户端不在线(APP被进程杀掉了、没信号)呢?
此时服务器看看能不能抢救一下,如果通过尝试重连还是不行、那么就会将消息提醒发给第三方服务,比如苹果的APN,咕果的GCM,或者国内一些手机厂商的消息推送通道,或者其他整合好的第三方服务,这些服务会通过系统级别的接口把消息提醒直接推送到用户的通知栏(倘若这个app没被ban的话)。
注意,通过第三方服务发送的只是消息提醒,真正的消息还是等到客户端上线了,与服务端建立起了可靠的长连接之后,才进行数据的传输。这就是有时候一点进聊天界面,疯狂刷出消息的原因。
消息未读数
小红给小明发消息了,此时小明正在和别的妹子聊天,等小明从聊天界面返回到对话框界面的时候,会发现和小红的对话框有个小红点,或者出现+1,此时小明的消息总未读也会+1,OR,夺命连环call 99+
小明点开和小红的对话框之后,小红点消失了,未读数-1,好感度-999
总结一下,发一条消息需要操作三张表
- 消息表新增一条消息
- 消息索引表新增两条记录分别记录收发
- 最近联系人表无则新增,有则更新
参考资料