【自研项目之分布式IM】08. 核心篇-消息ID生成策略与消息有序性的关系

523 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 6 天,点击查看活动详情

在定义IM消息的结构体中,通常都有一个全局唯一的msgId即消息ID,可以是无序或递增,主要看业务逻辑怎么处理。消息ID的作用:

  • 防重
    • 发送端超时重试同一条消息发送多次,服务端可以利用msgId去重。
    • 服务端同一条消息发送多次也快利用msgId去重。
  • 可以实现消息有序性。如果是无序ID则需要其他方式保证有序性,比如时间戳。

ID生成器根据实际业务做选型,下面推荐几种:

  • UUID
    基于UUID生成的id是无序的,为了保证消息的有序性还需要额外的字段,如时间戳。使用UUID的好处是客户端可以直接生成消息ID而无需去ID发布器申请消息ID,速度上占优。虽然UUID会有一定几率重复,但是中小型项目完全足够的,不用担心。
  • 基于Redis自增方式
    基于Redis自增方式容易出现并发问题,实现上可以做一些优化优化。因为群消息id只需要保证在同一个群是有序的,这样每个群一个key,整体的压力就会分散开,加上Redis的强悍性能,并发生成上万QPS很轻松。
  • 雪花算法(SnowFlake)
    雪花算法最大的问题就是时钟回拨,解决时钟回拨可以在内存中保存最近生成的id,生成新id时和旧id做对比,如果小于旧值,则在旧值的基础上+1即可。
    雪花算法也可以用Redis的优化方式,只需要保证每个群是单调递增即可。
  • 美团(Leaf)、百度(uid-generator)等开源分布式ID方案
    美团Leaf、百度uid-generator开源产品,都是经过考验的,可以拿来即用。

下面思考个问题:客户端发送消息时消息ID怎么获取?
如果是无序ID可以在客户端生产,比如UUID这种方式。如果是由服务端生成,那一般是会有一个ID发号器的服务专门提供给客户端申请id。一般申请id速度是很快的,基本在毫秒级别。

如何保证消息有序性?

在实际场景中,保证消息有序性是很有必要的,否则会造成聊天信息错乱。怎么保证有序性其实就是和数据库倒序排序一样。
基于消息ID排序
如果使用的全局递增的消息ID,那么就可以根据消息ID做排序条件。还有一点是在分布式部署中,比如A节点消息ID=1,B节点消息ID=2,但B节点的消息比A节点的消息先到达Socket长连接服务或先到达客户端,这时候其实就需要2端都实现短暂的等待整流。说白就是服务端/客户端拿到消息后不是立即展示,而是先短暂等待做一下排序然后在展示。等待的时间可以根据实际测试设置。

基于时间戳 如果消息ID是无序的,就需要另外字段做排序。时间戳是用的比较多的一种方式。只需要在发送消息时给消息添加一个时间戳即可。后续处理和上面是一样的。