V-IM-PRO 消息ACK机制说明

307 阅读4分钟

服务端 ACK 机制说明

1. 什么是 Ack 机制?

Ack(Acknowledgement,确认)机制是一种消息确认机制,常用于即时通讯系统中,确保消息能够被可靠地送达接收方。发送方在发送消息后,会等待接收方的确认(ack),只有收到 ack 后,才认为消息被成功送达。若未收到 ack,发送方可选择重发消息或做其他处理。

2. Ack 机制的作用

  • 保证消息可靠送达:防止消息丢失,提升系统可靠性。
  • 消息去重:防止重复消息的产生。
  • 消息状态追踪:可以追踪消息的已读/未读、已送达/未送达状态。

3. Ack 机制的实现流程

3.1 消息发送与未确认消息保存

  • 当用户 A 向用户 B 发送消息时,系统会将消息保存到 Redis 的未 ack 消息队列中(即未被确认的消息)。
  • 只有当消息类型为好友消息(ChatTypeEnum.FRIEND),且不是自己发给自己的消息时,才会保存到未 ack 队列。

代码片段:

java

Apply to AckServiceIm...


@Override

public void saveUnAckMessage(Message message) {

    // 如果是在线消息,则保存到redis的未ack消息队列里,并且不是自己发给自己的消息

    if (ChatTypeEnum.FRIEND.getCode().equals(message.getChatType()) && !message.getFromId().equals(message.getChatId())) {

        redisTemplate.opsForHash().put(ChatUtils.getUnAckKey(message.getChatId()), message.getId(), JSON.toJSONString(message));

    }

}

3.2 消息确认(Ack)

  • 当接收方收到消息后,会向服务端发送 ack 确认。
  • 服务端收到 ack 后,会将对应的消息从 Redis 的未 ack 队列中删除,表示该消息已被确认。

代码片段:

java

Apply to AckServiceIm...

@Override

public void ack(SendInfo sendInfo) {

    Ack ack = JSONUtil.toBean(sendInfo.getMessage(), Ack.class);

    List<AckItem> list = ack.getItems();

    // 把list按照chatKey进行分组,value 为 消息id的list

    Map<String, List<String>> resultMap = new HashMap<>();

    for (AckItem ackItem : list) {

        String messageId = ackItem.getMessageId();

        resultMap.computeIfAbsent(ackItem.getUserId(), k -> new ArrayList<>()).add(messageId);

    }

    resultMap.forEach((userId, messageIds) -> redisTemplate.opsForHash().delete(ChatUtils.getUnAckKey(userId), messageIds.toArray()));

}

3.3 未确认消息的加载

  • 用户上线或重连时,可以从 Redis 中加载所有未被确认的消息,进行补发,保证消息不丢失。

代码片段:

java

Apply to AckServiceIm...

public List<Message> loadUnAckMessage(String userId, TioConfig tioConfig) {

    List<Message> messageList = new ArrayList<>();

    Map<Object, Object> map = redisTemplate.opsForHash().entries(ChatUtils.getUnAckKey(userId));

    for (Map.Entry<Object, Object> entry : map.entrySet()) {

        Message message = JSONUtil.toBean((String) entry.getValue(), Message.class);

        messageList.add(message);

    }

    return messageList;

}

4. 相关数据结构说明

  • 未 ack 消息队列:以用户 ID 作为 key,消息 ID 作为 field,消息内容为 value,存储在 Redis 的 Hash 结构中。
  • AckItem:ack 确认项,包含 userId 和 messageId。
  • Ack:ack 消息,包含多个 AckItem。

5. 总结

Ack 机制通过“发送-确认-删除”的流程,确保了消息的可靠送达和状态可追踪。即使在网络波动、用户掉线等情况下,也能通过补发未确认消息,保证消息不丢失,极大提升了系统的健壮性和用户体验。

客户端 ACK 机制说明

ack机制说明

1. 设计目的

ack(Acknowledgement,消息回执)机制用于确保客户端收到的消息能够被服务端确认,防止消息丢失或重复,提高消息传递的可靠性。客户端在收到消息后不会立即单独发送ack,而是采用批量确认的方式,减少网络请求次数,提高性能。


2. 主要流程

2.1 消息接收与ack队列
  • 当客户端收到一条消息(如私聊消息),会调用 onmessage 方法。
  • 如果消息需要回执(如私聊消息且有 id),会调用 queueAckMessage(userId, messageId) 方法,将该消息的 userIdmessageId 添加到 pendingAckSet 集合中。
2.2 批量ack触发条件
  • 数量阈值触发:当 pendingAckSet 的数量达到 ACK_BATCH_SIZE(如10条)时,立即批量发送ack。
  • 定时器触发:如果未达到数量阈值,会启动一个定时器(如2秒,ACK_INTERVAL),定时批量发送ack。
2.3 批量ack发送
  • sendBatchAck() 方法实现,将 pendingAckSet 中的所有ack项(userIdmessageId)打包成一个ack消息,通过WebSocket发送给服务端。
  • 发送后,已发送的ack项会从 pendingAckSet 中移除。

3. 关键代码说明

private pendingAckSet: Set<AckItem> = new Set() // 待确认的消息ID集合
private ackTimer: NodeJS.Timeout | null = null // 批量 ACK 定时器
private readonly ACK_BATCH_SIZE = 10 // 批量确认的数量阈值
private readonly ACK_INTERVAL = 2000 // 批量确认的时间间隔(毫秒)
  • pendingAckSet:存储待ack的消息。
  • ACK_BATCH_SIZE:批量ack的数量阈值。
  • ACK_INTERVAL:批量ack的时间间隔。

添加ack队列
private queueAckMessage(userId: string, messageId: string): void {
  this.pendingAckSet.add({ userId, messageId })
  if (this.pendingAckSet.size >= this.ACK_BATCH_SIZE) {
    this.sendBatchAck()
    return
  }
  if (!this.ackTimer) {
    this.ackTimer = setTimeout(() => {
      this.sendBatchAck()
      this.ackTimer = null
    }, this.ACK_INTERVAL)
  }
}
  • 达到数量阈值立即发送,否则启动定时器。

批量发送ack
private sendBatchAck(): void {
  if (this.pendingAckSet.size === 0) return
  const itemsToSend = Array.from(this.pendingAckSet)
  const ackMessage: SendInfo<Ack> = {
    code: SendCode.ACK,
    message: { items: itemsToSend }
  }
  this.send(JSON.stringify(ackMessage))
  itemsToSend.forEach((item) => {
    this.pendingAckSet.delete(item)
  })
}
  • 将所有待ack消息打包发送,发送后清理集合。

4. 机制优点

  • 高效:批量发送减少了网络请求次数。
  • 可靠:确保每条消息都能被服务端确认,防止消息丢失。
  • 灵活:数量和时间阈值可调,适应不同业务场景。

5. 适用场景

  • 适用于需要高可靠性消息传递的IM、聊天等场景,尤其是高并发下的消息确认。