消息队列 | 青训营笔记

241 阅读3分钟

消息队列

这是我参加「第五届青训营」伴学笔记创作活动的第 10 天

四个问题

  • 系统崩溃
  • 服务处理能力有限
  • 链路耗时长尾
  • 日志如何处理

如何解决?

  • 解耦(生产消费
  • 削峰(每次限制请求数量
  • 异步(先反馈给用户,再处理后台
  • 消息队列记录

消息队列:保存消息的容器,本质是一个队列,但是要满足高吞吐、高并发、高可用

Kafka消息队列

Kafka是一种分布式消息队列,可以支持高吞吐量和低延迟。它主要用于在大型分布式系统中进行数据流处理和事件驱动微服务。

Kafka消息队列拥有以下特点:

  • 高吞吐量:可以处理每秒数千万条消息。
  • 分布式:支持在多台机器上运行,以提高可用性和可扩展性。
  • 持久性:消息可以永久保存,以保证不会丢失任何数据。
  • 可靠性:支持消息的可靠传递,以保证消息不会丢失。
  • 简单:支持简单的生产者/消费者API,使用起来非常方便。

在Go语言中,可以使用第三方包github.com/segmentio/kafka-go"来使用Kafka消息队列。以下是生产者的示例代码:

package main

import (
	"fmt"

	"github.com/segmentio/kafka-go"
)

func main() {
	// 创建一个生产者
	producer := kafka.NewWriter(kafka.WriterConfig{
		Brokers:  []string{"localhost:9092"},
		Topic:    "test-topic",
		Balancer: &kafka.LeastBytes{},
	})

	// 发送消息
	message := "hello, kafka"
	producer.WriteMessages(
		kafka.Message{
			Key:   []byte("key"),
			Value: []byte(message),
		},
	)

	// 关闭生产者
	producer.Close()

	fmt.Println("Message sent:", message)
}

以下是消费者的示例代码:

package main

import (
	"fmt"

	"github.com/segmentio/kafka-go"
)

func main() {
	// 创建一个消费者
	consumer := kafka.NewReader(kafka.ReaderConfig{
		Brokers:   []string{"localhost:9092"},
		Topic:     "test-topic",
		Partition: 0,
		MinBytes:  10e3, // 10KB
		MaxBytes:  10e6, // 10MB
	})

	// 读取消息
	for {
		message, err := consumer.ReadMessage(context.Background())
		if err != nil {
			break
		}

		fmt.Println("Message received:", string(message.Value))
	}

	// 关闭消费者
	consumer.Close()
}

高吞吐发挥出色

如何使用?

  • 创建Kafka集群(有些地方可以不做,例如字节)
  • 新增Topic
  • 生产者逻辑
  • 消费者逻辑

基本概念?

  • offset,消息在分区内的位置,可以理解为唯一id,在分区内部严格递增(说明了可以二分

如何高吞吐?

  • 批量发送(减少发送次数
  • 数据压缩(减少消息大小

BMQ

存算分离,云原生消息队列,逐步替换Kafka BMQ (Binary Message Queue) 是一种消息队列系统,具有高吞吐量、高并发能力、可靠性等特点。它主要通过二进制格式存储消息,使用简单、效率高,适用于各种分布式系统。

在 Go 语言中,可以使用第三方库 github.com/bm-metrics/bmq 来实现 BMQ 的生产和消费。

以下是一个生产者示例代码:

package main

import (
	"github.com/bm-metrics/bmq"
)

func main() {
	// 创建一个生产者
	producer := bmq.NewProducer("localhost:11300")
	defer producer.Close()

	// 发送消息
	producer.Put("test-tube", []byte("Hello, BMQ!"), 0, 0)
}

以下是一个消费者示例代码:

package main

import (
	"github.com/bm-metrics/bmq"
)

func main() {
	// 创建一个消费者
	consumer := bmq.NewConsumer("localhost:11300")
	defer consumer.Close()

	// 订阅消息
	consumer.Watch("test-tube")

	// 消费消息
	for {
		job, err := consumer.Reserve(0)
		if err != nil {
			break
		}

		fmt.Println("Message received:", string(job.Body))

		// 标记任务已完成
		consumer.Delete(job.ID)
	}
}

RocketMQ

低延迟,实施场景使用

RocketMQ 是阿里巴巴开源的分布式消息中间件,具有高性能、高可用、高可靠的特点。RocketMQ 提供了生产者、消费者、存储和管理等功能,可以很好的满足分布式系统的消息需求。

在 Go 语言中,可以使用第三方库 "github.com/apache/rocketmq-client-go" 来实现 RocketMQ 的生产和消费。

以下是一个生产者示例代码:

package main

import (
	"github.com/apache/rocketmq-client-go/v2"
	"github.com/apache/rocketmq-client-go/v2/producer"
)

func main() {
	// 创建生产者
	p, err := rocketmq.NewProducer(
		producer.WithNameServer([]string{"localhost:9876"}),
		producer.WithRetry(2),
	)
	if err != nil {
		fmt.Printf("create producer error: %s", err)
		return
	}
	defer p.Shutdown()

	// 发送消息
	res, err := p.SendSync(
		context.Background(),
		&producer.Message{
			Topic: "test-topic",
			Body:  []byte("Hello, RocketMQ!"),
		},
	)
	if err != nil {
		fmt.Printf("send message error: %s", err)
		return
	}
	fmt.Printf("send message success: result=%s", res)
}

以下是一个消费者示例代码:

package main

import (
	"context"
	"fmt"
	"github.com/apache/rocketmq-client-go/v2"
	"github.com/apache/rocketmq-client-go/v2/consumer"
)

func main() {
	// 创建消费者
	c, err := rocketmq.NewPushConsumer(
		consumer.WithNameServer([]string{"localhost:9876"}),
		consumer.WithConsumerGroup("test-group"),
		consumer.WithRetry(2),
	)
	if err != nil {
		fmt.Printf("create consumer error: %s", err)
		return
	}
	defer c.Shutdown()

	// 订阅消息
	err = c.Subscribe("test-topic", consumer.MessageSelector{}, func(ctx context.Context, msg *consumer.Message) (consumer.ConsumeResult, error) {
		fmt.Printf("receive message: %s\n", string(msg.Body))
		return consumer.ConsumeSuccess, nil
	})
	if err != nil {
		fmt.Printf("subscribe error: %s", err)
		return
	}

	// 启动消费者
	err = c.Start()
	if err != nil {
		fmt.Printf("start consumer error: %s", err)
		return
	}
}