在 UGC 评论回复系统中使用 Token 令牌机制预防重复请求

评论回复系统遍地开花,但是我们仍然会发现很多系统有重复评论的情况。

之前我们的 APP / WEB 端也存在这样的情况,下文介绍一种用令牌机制预防重复请求的方法。

令牌是指服务器生成一个独一无二的字符串 Token 下发给客户端,客户端发送内容到服务器必须带上从服务器获取的一个合法的 Token。

使用令牌后,可以获得很多好处:

  • 杜绝了重复消息的可能:当客户端发生网络等异常而重试时,使用同一个合法令牌的请求被合并为一次请求。这意味着服务器即可以将相同 Token 的消息覆盖为最后一次的内容,也可以在接受到相同 Token 消息时直接丢弃,我个人推荐并使用前者。
  • Token 的唯一性可以保证它是一个非常好的索引,避免其它方式的对数据库自增 ID 或 timestamp 之流的依赖。
  • Token 生成时可以包含用户的个人信息和时间信息,当服务器收到写请求时可以校验令牌的真实性和时效性。
  • Token 中保存的信息,可能用来判断用户的行为,如:用户从看到内容到评论经过了多少阅读时间等。
  • Token 比 ID / timestamp 之流的数字有更好的扩展性,可以在 Token 中加入很多信息。

我将会使用我最近做的一个评论回复系统中的协议和代码片段来介绍,该系统被设计为可以承载目前各种场景的评论回复点赞。

Token 包含用户信息和时间信息及其它,加密压缩后使用 base64 处理成字符串:

eg: "w_HA15Rs7ycAiUSVAngpp36nb7jUCa_wvuVhR18EX7gn9YTxjXrjMMXT6d-Ddho="

APP / WEB 使用两个接口来分别获取和发送评论/回复/点赞:

rpc GetContentReplyList(ContentReplyListRequest)
returns(ContentReplyListResponse);
rpc PostContentReply(PostContentReplyRequest)
returns(PostContentReplyResponse);

服务器在两种回复结构中都会下发新的 Token :

message ContentReplyListResponse {
......
optional string post_token = 3; // 发送回复的 TOKEN
}

message PostContentReplyResponse {
......
optional string new_token = 2; // 旧 TOKEN 已经被使用,后续用新 TOKEN
}

客户端发送回复或评论时结构中必须填写获取到的最后一个 Token :

message PostContentReplyRequest {
......
optional ReplyFrame reply = 3;
optional string token = 4;
}

在很容易出现重复内容的场景里使用 Token 都可以避免重复,比如网络卡用户多点了几次发送,而在几次发送之间,用户编辑了内容。如果使用 MD5 去重或各种超时机制,则受限于变量太多无法达到很好的效果,而超时机制也会成为性能障碍。但因为 Token 是用户无法改变的,系统和用户之间的一致性就可以得到保证。


2017-01-22 深圳

对做足设计的系统而言,出问题不靠猜,也不会有所谓的上线前的不安。