背景
视频直播间作为直播系统对外的表现形式,在整个系统中处于核心地位。通常除了视频直播窗口外,直播间还包含在线用户,礼物,评论,点赞,排行榜等信息。直播间消息,时效性高,互动性强,对系统时延有着非常高的要求,非常适合使用Redis等缓存服务来处理。
直播信息
实时排行信息
实时排行信息包含直播间在线用户列表,各种礼物排行榜,弹幕消息(可以理解为按消息维度的消息排行榜)等信息,适合使用Redis中的SortedSet
结构进行存储。
Redis集合使用空值散列表(hash table)实现,因此对集合的增删改查操作的时间复杂度都是O(1)。有序集合中的每个成员都关联一个分数(score),可以方便地实现排序等操作。下面以增加和返回弹幕消息为例对有序集合在直播间信息系统中的实际运用进行说明。
代码语言:javascript复制以
unix timestamp
毫秒数
为分值,记录user55
的直播间增加的5
条弹幕
redis> ZADD user55:_danmu 1523959031601166 message111111111111
(integer) 1
11.160.24.14:3003> ZADD user55:_danmu 1523959031601266 message222222222222
(integer) 1
11.160.24.14:3003> ZADD user55:_danmu 1523959088894232 message33333
(integer) 1
11.160.24.14:3003> ZADD user55:_danmu 1523959090390160 message444444
(integer) 1
11.160.24.14:3003> ZADD user55:_danmu 1523959092951218 message5555
(integer) 1
代码语言:javascript复制返回最新的3条弹幕信息:
redis> ZREVRANGEBYSCORE user55:_danmu inf -inf LIMIT 0 3
1) "message5555"
2) "message444444"
3) "message33333"
代码语言:javascript复制返回指定时间段内的3条弹幕信息:
redis> ZREVRANGEBYSCORE user55:_danmu 1523959088894232 -inf LIMIT 0 3
1) "message33333"
2) "message222222222222"
3) "message111111111111"
计数类信息
计数类信息以用户相关数据为例,有未读消息数、关注数、粉丝数、经验值等等。这类消息适合以Redis中的散列(hash)结构进行存储。
比如关注数可以用如下的方法处理:
代码语言:javascript复制redis> HSET user:55 follower 5
(integer) 1
redis> HINCRBY user:55 follower 1 //关注数 1
(integer) 6
redis> HGETALL user:55
1) "follow"
2) "6"
时间线信息
时间线信息是以时间为维度的信息列表,典型的比如主播动态,新帖。这类信息排序方式是固定的时间顺序,可以考虑使用List
或者SortedSet
来存储。
示例如下:
代码语言:javascript复制redis> LPUSH user:55_recent_activitiy '{datetime:201804112010,type:publish,title:开播啦,content:加油}'
(integer) 1
redis> LPUSH user:55_recent_activitiy '{datetime:201804131910,type:publish,title:请假,content:抱歉,今天有事鸽一天}'
(integer) 2
redis> LRANGE user:55_recent_activitiy 0 10
1) "{datetime:201804131910,type:publish,title:xe8xafxb7xe5x81x87",content:xe6x8axb1xe6xadx89xefxbcx8cxe4xbbx8axe5xa4xa9xe6x9cx89xe4xbax8bxe9xb8xbdxe4xb8x80xe5xa4xa9}"
2) "{datetime:201804112010,type:publish,title:xe5xbcx80xe6x92xadxe5x95xa6,content:xe5x8axa0xe6xb2xb9}"
直播间IM
消息同步
多端同步的核心问题在于多端数据的一致性,IM系统需要记录消息的顺序和每个端的同步点,从而实现消息的最终一致性。
既然采用写扩散的方式来记录消息,系统需要:
- 为每个用户创建一个
message_inbox
,用于储存该用户的消息。 - 为每一条消息创建一个自增的
sync_id
,用于记录消息的顺序。 - 记录用户在每个客户端上的同步
ID
。
通过对用户在各客户端上的数据进行对比和同步,就可以实现多端数据同步,详细的实现方式如下。
提炼数据结构
从IM系统中的各类事件中提炼出统一的消息数据结构,这些事件包括新消息、已读消息、增删会话信息等。
消息数据结构示例如下:
代码语言:javascript复制struct message {
int type; // 业务类型
string data; // 业务数据
}
进行存储产品选型
选型依据主要有以下两点:
- 系统需要为
message_inbox
中的每条消息分配一个自增的sync_id
,所以用于存储消息数据的产品需要能实现原子递增队列。 - 完整的消息需要在IM系统的消息管理模块中保存到持久化存储(例如MySQL)中,而
message_inbox
数据则无需持久化存储,只需存储一段时间(例如一周)即可,所以对存储的容量要求并不高
数据存储
通过Redis数据库的Hash结构来存储每个用户在客户端上的同步ID
场景案例
多端同步的详细实现方式
新消息入库以后,推送消息逻辑被触发,系统根据用户名获取到所有客户端设备的当前点位,然后从消息队列中获取历史点位到最新点位间的所有消息,再将其推送到客户端设备。推送完成后,更新设备的当前点位信息。
关键步骤的示例代码如下
1、新消息入库
代码语言:javascript复制sync_id = INCR bob
ZADD bob $sync_id message:{type:new_message, data:"{msgid:991,cid:123,text:"hello"}"}
2、获取消息范围
代码语言:javascript复制ZRangeByScore bob 100103 100310
3、获取客户端设备的点位
代码语言:javascript复制HGETALL bob
4、加入或更新客户端设备信息
代码语言:javascript复制HSET bob dev_1001 100103
HSET bob dev_1002 100202
IM通信已经成为互联网环境中最常见通信方式之一,借助Redis丰富的数据结构,您可以构建出高可用的IM系统。
不仅是本文提到的消息同步模块,IM系统的消息存储模块也可以使用Redis进行加速,最终构建出支持大规模访问的可靠IM系统。