message_server 是一个 websocket 服务,最初的设计是为实现聊天功能。
聊天功能大概如下:
- 每个用户(商家或客户)端可创建一个与 server 的 socket 连接
- socket 连接是被一个 room 空间所包含的
- 客户与商家能互相聊天,说明他们一定是在同一个 room 空间下
当前,message_server 只能让不同用户端在一起互相通信实现聊天,但还缺失以下功能或数据:
- 一个用户最近与哪些人聊过天(最近的时长最大设置为 7天)
- 没把一个用户用参数标识好 --- 从而能准确查询某个用户是否在线,在哪个房间(room)
- 现有 room 的概念,如果我想知道当前有多少用户在线,可以说把 rooms 中所有 room 遍历就可以实现。试问,需要搞个优化方案吗,从而更高效快速查询某个用户是否在线,总共有多少用户在线?
按以上列出的缺失功能,设计出实现的技术方案与细节:
如果可以归纳在一起实现的就直接按整体实现来。
一个用户最近与哪些人聊过天
- 如果用户是门店(Client.userInfo.Type == 'shop'),那么与他关联的聊天个体是客户(customer)。请用 redis 数据结构中有序字典为门店用户创建出“聊天用户群体”,其中排序权重(score)使用是时间戳,时间越是最近,就排序在越前边。
- 如果用户是门店(Client.userInfo.Type == 'customer'),那么与他关联的聊天个体是店家(shop)。请用 redis 数据结构中有序字典为门店用户创建出“聊天用户群体”,其中排序权重(score)使用是时间戳,时间越是最近,就排序在越前边。
- 有了聊天用户数据的同时,还要有与每个聊天体的未读消息数。为了不额外添加数据结构,要对保存“聊天用户群体”的有序字典的 score 做个特殊设计,从而实现在记录未读消息数。由于 score 是个浮点数,那么就充分利用它的特点:整数部分是 时间戳,小数部分其实是未读消息数
统计:有多少在线用户
用 Client.name 就能查询该用户是否在线,及在哪个房间。
AI 优化的需求
为了高效实现“最近联系人列表”和“未读消息数”,我们将为每个用户创建一个 Redis 有序集合(Sorted Set)。
设计细节:
- 数据结构: 为不同类别用户创建有序集合:门店(shop)以
shop_chats:{ShopId} 为键名,客户(customer)以 customer_chats:{UserId}
- 成员(Member): 集合的成员是与该用户聊天的另一个用户的 ID(
{contact_id})。
- 分数(Score): 这是设计的核心。我们将分数(
score)设计为一个浮点数,利用它的特性同时存储两个信息:
- 整数部分: 存储 Unix 时间戳(
timestamp)。这样,当有新的聊天发生时,只需更新这个时间戳,Redis 会自动根据时间戳对联系人进行排序,越新的聊天越靠前。
- 小数部分: 存储未读消息数(
unread_count)。例如,我们可以将小数部分乘以一个很大的数(如 1000)来表示未读消息数,以确保其在浮点数精度范围内。或者更简单地,直接使用一个相对较小的数作为未读数的乘数。
- 举例:
score = timestamp + (unread_count / 1000.0)。每收到一条新消息,只需递增 unread_count,并更新 score。
优势:
- 高效排序: Redis 有序集合天然支持按分数排序,无需额外操作即可获取最近联系人列表。
- 一举两得: 利用
score 的浮点数特性,在一个数据结构中同时存储了时间戳和未读消息数,减少了对 Redis 内存的占用和数据同步的复杂性。
- TTL 支持: 可以为这个键设置一个 过期时间(TTL),比如 7 天,来自动清理过期的聊天记录,符合你的需求。
疑问:
- 同一个用户是否可以用不同设备实现进入同一个房间 ?