0%

关于站内信功能设计的一些思考

前言

​ 站内信,相信对于绝大多数后端开发者来说,这是一个相对比较常见的功能,结合消息推送是触达用户带动日活的必要手段,区别就是或简单、或复杂。在此之前(2021-08),由于系统的用户量小,从未仔细思考过这个问题,由于本次负责的业务系统可能比以往的用户更多一些,不得以多了解一些,因此,以下内容仅作为个人的一些学习记录、分享。

一些问题(需求),抛砖引玉

  1. 10w用户或者更多,为什么是10w而不是50w、100w? 只能说有生之年! 100来个用户讨论这个没什么意义。
  2. 需要支持个人私信(点对点)、群发(点对面),并记录阅读状态(已读、未读)。
  3. 需要支持按单位、按角色、按职务群发消息。

初步解决问题(实现需求)

​ 10w用户群发消息,显然在发送消息的时候插入10w条数据是不太现实的,现在大厂的用户量动不动上亿,少的也有几百万,肯定不会这么做。10w的用户量,实际在线的不可能达到这个数字,我们只需要实现 在线push(推送),离线pull(拉取)就可以了,说起来似乎挺简单,那么数据表如何设计?

你使用搜索引擎查到的资料,一般会这么设计:

1.消息内容表(t_message_content)

列名 类型 备注
id bigint 主键
content text 消息内容

2.消息表(t_message)

列名 类型 备注
id bigint 主键
send_id bigint 发送人id
receive_group_id bigint 接收群组id
content_id bigint 消息内容id

3.用户消息表(t_message_user)

列名 类型 备注
id bigint 主键
message_id bigint 消息id(群组消息为群组 id,私信为0)
status int 阅读状态(0:未读,1:已读)
content_id bigint 消息内容id(冗余,方便直接查询内容)
send_id bigint 发送人id
receive_id bigint 接受人id(用户查询自己的消息使用此条件即可)

以上设计解释:

1.如果是私信消息直接插入到t_message_user即可,使用receive_id条件即可查询到自己的消息

2.群发消息插入到t_message即可,用户查询自己的消息sql语句如下:

1
2
3
select *from t_message where receive_group_id in(用户的用户组) and id not in(
select message_id from t_message_user where receive_id=当前登录用户Id
)

用户登录或者刷新消息列表时,将查询结果插入 到t_message_user即可。

但是显然,以上设计是无法满足以下需求的:

3.需要支持按单位、按角色、按职务群发消息。

初步修改

2.消息表v2(t_message)

列名 类型 备注
id bigint 主键
send_id bigint 发送人id
receive_group_id bigint 接收群组id
content_id bigint 消息内容id
type int 群组类型(0:角色,1:职务,2:单位)
org_id bigint 单位id

那么查询语句修改成以下这样:

用户查询自己的群组消息sql语句如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
select *from t_message 
where
(
(org_id is null and type=0 and receive_group_id in(用户的角色集合)) -- 表示发给某些角色的消息(不分单位)
or
(org_id=用户所在单位id and type=0 and receive_group_id in(用户的角色集合)) -- 表示发给某个单位下的某些角色的消息
or
(org_id is null and type=1 and receive_group_id in(用户的职务集合)) -- 表示发给某些职务的消息(不分单位)
or
(org_id=用户所在单位id and type=1 and receive_group_id in(用户的职务集合)) -- 表示发给某个单位下的某些职务的消息
or
(org_id=用户所在单位id and type=2) -- 表示发给某个单位所有人的消息
)
and id not in(
select message_id from t_message_user where receive_id=当前登录用户Id
)

用户登录或者刷新消息列表时,将查询结果插入 到t_message_user即可。

以上设计看似可以满足需求,但查询语句效率太低而且冗长,也不利于扩展,但当时我确实就是这么做的(时间太赶实现功能就行…此处省略数万字……),毕竟数据量小,确实能用,也用了一段时间。

优化,进一步解决问题

​ 其实也好解决,只要了解过消息队列 mq、mqtt之类的中间件、协议,基本上就知道怎么设计更好。我们只要把单位、职务、角色这几个群组条件拼凑成一个订阅字符串就行了。

举例:

  1. org/1 表示群组消息发送给单位id为1下的所有用户
  2. role/1 表示群组消息发送给所有拥有角色id为1的用户
  3. job/1 表示群组消息发送给所有拥有职务id为1的用户
  4. org/1/role/1 表示群组消息发送给所属单位id为1并且拥有角色id为1的所有用户

消息表v3(t_message)

列名 类型 备注
id bigint 主键
send_id bigint 发送人id
receive_group varchar(100) 群组字符串
content_id bigint 消息内容id

那么新的表结构下,用户查询自己的消息sql语句如下:

1
2
3
select *from t_message where receive_group in(用户的订阅数组) and id not in(
select message_id from t_message_user where receive_id=当前登录用户Id
)

以上就是最终的实现方式,这样不但简洁、效率更高,并且利于扩展,方便用户订阅各种消息类型。

结语

​ 以上只说了主要的东西,实际开发中,有些东西需要做一些取舍,比如用户2年从未登录过,积累的消息几w条,这个怎么办?因此我们要给消息分等级,重要消息永远不能丢失或者保存10年20年(比如存取款消息);至于一般消息,我们设定一个过期时间即可,用户登录是只拉去最近1个月、2个月的消息。

另外,拉取消息时,需要异步分页拉取。

至此,以上为全部内容。

坚持原创技术分享,您的支持将鼓励我继续创作!
YANG 微信支付

微信支付

YANG 支付宝

支付宝