go 消息队列 涉及channel、ring buffer

前言

文章主要是针对消息队列的思考,消息队列核心实现使用了channel、ring buffer和map结构。

文章开篇介绍了channel的实现原理,然后是对消息队列的分析与实现,其中消息缓冲使用ring buffer存储,所以在文章第三部分介绍了ring buffer。

channel原理

chansend

chanrecv

消息队列

消息队列是一种应用间异步通讯方式,解耦了消息发送者和接收者,提供一定的缓冲。

消息订阅实现思考

  1. 没有消费者订阅消息时,是否提供消息缓冲区?
  2. 缓冲区信息是否需要群发所有消费者,还是只需要新订阅的消费者取走?
  3. 缓冲区未发送数据保留还是丢弃?

我的设计选择:整体上没取走的就丢弃;但对于没有消费者时,提供一定的缓冲。

没有消费者订阅消息时,是否提供消息缓冲区?

提供。不提供的话,缓冲区会导致没有消费者时消息丢失或者阻塞生产者

缓冲区信息是否需要群发所有消费者,还是只需要新订阅的消费者取走?

群发。保证消息的公平性和一致性

缓冲区未发送数据保留还是丢弃?

不保留。消费者能取走的就取,取不了的就丢弃

接口设计

type MessageQueue struct {
	chanList          map[string][]chan Message   // 切片的数量就是消费者的数量
	buffers           map[string]ring_buffer.Ring // 使用ring Buffer存储缓存
	bufferSize        int                         // 缓冲容量
	mutex             sync.RWMutex
	ringBufferCreator ring_buffer.RingBufferCreator // 选择基于不同的覆盖策略的ringBuffer:丢弃、覆盖等
}

func (m *MessageQueue) Send(ctx context.Context, topic string, msg Message) error // 发送消息
func (m *MessageQueue) Subscribe(topic string, cap int) <-chan Message // 订阅主题
func (m *MessageQueue) distributeBufferMsg(topic string, ringBuffer ring_buffer.Ring) // 后台goroutine发送缓冲数据

设计模式

消息队列使用生产者-消费者模型、发布订阅模式

在这里插入图片描述

生产者-消费者模型和发布订阅模式关系和区别
相同点

都常用于异步通信

不同点
  • 生产者-消费者模型共享缓冲区,低耦合,消费者需要知道缓冲区的存在;常用于点对点的通信,适用场景:任务调度、I/O操作,主要解决生产者、消费者数据发送速度不匹配的问题
  • 发布订阅模式解耦消息发送者和接收者,可以进行主题订阅,使用中间件进行通信,消息发送者和接收者无需知道对方的存在(完全解耦);常用于多对多的通讯,比如广播、多播,适用场景:事件通知、微服务通讯

支持能力

  • 主题订阅
  • 无消费者订阅主题时,暂存发送消息
    • 暂存使用ring buffer,可自行配置ring buffer覆盖策略:丢弃、覆盖等
    • 有消费者订阅消息后,后台启动goroutine发送缓冲消息给所有订阅的消费者

源码

gitee:https://gitee.com/luyue_zhang/channel/tree/master/message_queue

测试

在这里插入图片描述

常用的消息队列方案

  • 使用了redis,可以使用redis streams 特点:轻便
  • 分布式、高可用:kafka
  • 复杂路由、功能丰富:RabbitMQ
  • 轻量、高性能:NATS

Ring buffer

ring buffer是什么

ring buffer是容量固定的线性数据结构,通过内部两个指针循环移动进行数据的写入和读取,也叫circle buffer。

核心特性
  • 容量固定:避免内存动态分配,资源利用率高
  • 高效读写:读写操作时间复杂度O(1),适合实时系统
  • 循环利用:通过指针移动避免数据迁移和内存分配,适合高吞吐量场景

写覆盖策略

写覆盖策略:丢弃、覆盖、阻塞写入

策略使用维度分析
业务需求
  • 对于完整性强的场景可以考虑阻塞写入,同时提供灵活性告诉写入者队列是否已满,让写入者调整写入速度。比如:任务调度、金融

  • 对于实时性强的场景可以考虑覆盖。比如:日志、传感器

系统负载

系统负载高时丢弃、覆盖,系统负载低时阻塞

数据价值

确认是新数据重要还是旧数据重要

源码

gitee: https://gitee.com/luyue_zhang/channel/tree/master/ring_buffer

注意
// 读完数据后:
r.queue[r.readIdx%r.size] = nil // 重置,断开对值的引用,保证GC可以回收掉该位置的数据

测试

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值