组队大项目--Pulsar | 青训营笔记

98 阅读3分钟

这是我参与「第五届青训营」笔记创作活动的第9天

需求分析

针对的user、video、comment、message模块中的高频写入操作场景,会先写入redis,然后生产消息到消息队列Pulsar,通过服务消费Pulsar topic消息,再写入mysql,来进行削峰以平缓流量,异步操作,降低如点赞等操作响应耗时,改善用户体验,提升系统稳定性,并且实现了redis和mysql数据的最终一致性。

user服务

  • 关注/取消关注

video服务

  • 点赞/取消点赞

comment服务

  • 评论/删评

message服务

  • 消息存储

实现

在每个服务下都建立了Pulsar客户端,并且定义了每个场景的Schema和Topic

const (
   LikeVideoTopic     = "persistent://public/douyin_prod/like_video"
   FollowUserTopic    = "persistent://public/douyin_prod/follow_user"
   CreateMessageTopic = "persistent://public/douyin_prod/create_message"
   CommentTopic       = "persistent://public/douyin_prod/comment"
)

具体解释如下:

Pulsar消息被存储为非结构化的字节数组,数据结构(即所谓的模式)只有在读取时才会应用于这些数据。因此,生产者和消费者都需要就消息的数据结构达成一致,包括字段及其相关类型。

Pulsar Schema(模式)是定义如何将原始消息字节转化为更正式的结构类型的元数据,作为生成消息的应用程序和消费消息的应用程序之间的协议。它在将数据发布到主题之前将其序列化为原始字节,并在将原始字节交付给消费者之前将其反序列化。

Pulsar使用模式注册表作为中央存储库来存储已注册的模式信息,这使得生产者/消费者能够通过经纪人协调主题的消息模式。

在任何围绕信息传递和流媒体系统建立的应用中,类型安全都是极其重要的。原始字节对于数据传输来说很灵活,但这种灵活性和中立性是有代价的:你必须覆盖数据类型检查和序列化/反序列化,以确保输入系统的字节可以被读取并成功消费。换句话说,你需要确保数据对应用程序来说是可理解和可用的。

并且在服务初始化的时候,启动了producer生产者,通过一个协程启动了consumer消费者,以Shared的方式来订阅Topic,以支持弹性扩容,然后消费消息来将相关操作记录到mysql中。

高并发下的性能最大瓶颈是mysql的读写,通过消费队列削峰能减少mysql的写入压力。

关注/取消关注设计

不引入pulsar的话,在处理关注/取消关注的请求中,需要等待写入mysql操作完成后才能响应请求,这样在大量请求时,mysql的io压力会剧增,响应时间会拉长甚至是打挂mysql数据库,威胁较大。

从产品设计的角度出发,用户对自己关注列表的实时性要求不高,所以可以引入消息队列组件。

  • 关注操作中pulsar消息体的定义如下

    • type FollowUserMessageJSON struct {
         SrcUserID     int64 `json:"srcUserId"`
         FollowID      int64 `json:"followId"`
         ActionType    int   `json:"actionType"`
      }
      

service将相关字段数据打包成上述消息体,push到消息队列

  • 如果推送成功,直接返回操作成功
  • 如果推送失败,如发出push操作超时后仍未收到响应则也会返回失败,然后返回操作失败

另外一个协程准备一个messageChannel,不断消费消息,消费成功则调用ACK向pulsar表示成功,然后根据定义的schema将消息反序列化为指定消息体变量,然后写入关注信息到mysql中。

Tradeoff: 上述方案虽然对于不合法操作也会返回成功响应,但考虑到实际使用中用户更关心响应耗时,且不合法操作最终也不会影响真实的数据,所以进行权衡之后使用了此方案。

其实操作的设计方案类似,就不一一列举了。