golang如何使用rocketmq 附加闭坑指南 建议收藏!!!

RocketMQ是一款低延迟、高并发的消息中间件,常用于业务解耦和流量削峰。文中介绍了RocketMQ的核心概念,如Topic、Message、Producer和Consumer,并详细阐述了各种消息类型、消费模式和特性,如集群消费、广播消费、顺序消息和事务消息。此外,还讲解了如何在Go中使用RocketMQ进行消息发送和消费。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


前言

当我们的业务达到一定规模,很多业务需要解耦,以及需要流量削峰的时候,我们需要使用MQ来让我们系统能够正常运转。

一、rocketmq是什么?

RocketMQ是由阿里捐赠给Apache的一款低延迟、高并发、高可用、高可靠的分布式消息中间件。经历了淘宝双十一的洗礼。RocketMQ既可为分布式应用系统提供异步解耦和削峰填谷的能力,同时也具备互联网应用所需的海量消息堆积、高吞吐、可靠重试等特性。

二、rocketmq核心概念

  • Topic:消息主题,一级消息类型,生产者向其发送消息。
  • Message:生产者向Topic发送并最终传送给消费者的数据消息的载体。
  • 消息属性:生产者可以为消息定义的属性,包含Message Key和Tag。
  • Message Key:消息的业务标识,由消息生产者(Producer)设置,唯一标识某个业务逻辑。
  • Message ID:消息的全局唯一标识,由消息队列RocketMQ系统自动生成,唯一标识某条消息。
  • Tag:消息标签,二级消息类型,用来进一步区分某个Topic下的消息分类
  • Producer:也称为消息发布者,负责生产并发送消息至Topic。
  • Consumer:也称为消息订阅者,负责从Topic接收并消费消息。
  • 分区:即Topic Partition,物理上的概念。每个Topic包含一个或多个分区。
  • 消费位点:每个Topic会有多个分区,每个分区会统计当前消息的总条数,这个称为最大位点MaxOffset;分区的起始位置对应的位置叫做起始位点MinOffset。
  • Group:一类生产者或消费者,这类生产者或消费者通常生产或消费同一类消息,且消息发布或订阅的逻辑一致。
  • Group ID:Group的标识。
  • 队列:个Topic下会由一到多个队列来存储消息。
  • Exactly-Once投递语义:Exactly-Once投递语义是指发送到消息系统的消息只能被Consumer处理且仅处理一次,即使Producer重试消息发送导致某消息重复投递,该消息在Consumer也只被消费一次。
    集群消费:一个Group ID所标识的所有Consumer平均分摊消费消息。例如某个Topic有9条消息,一个* Group ID有3个Consumer实例,那么在集群消费模式下每个实例平均分摊,只消费其中的3条消息。
  • 广播消费:一个Group ID所标识的所有Consumer都会各自消费某条消息一次。例如某个Topic有9条消息,一个Group ID有3个Consumer实例,那么在广播消费模式下每个实例都会各自消费9条消息。
  • 定时消息:Producer将消息发送到消息队列RocketMQ服务端,但并不期望这条消息立马投递,而是推迟到在当前时间点之后的某一个时间投递到Consumer进行消费,该消息即定时消息。
  • 延时消息:Producer将消息发送到消息队列RocketMQ服务端,但并不期望这条消息立马投递,而是延迟一定时间后才投递到Consumer进行消费,该消息即延时消息。
  • 事务消息:RocketMQ提供类似X/Open XA的分布事务功能,通过消息队列RocketMQ的事务消息能达到分布式事务的最终一致。
  • 顺序消息:RocketMQ提供的一种按照顺序进行发布和消费的消息类型,分为全局顺序消息和分区顺序消息。
  • 全局顺序消息:对于指定的一个Topic,所有消息按照严格的先入先出(FIFO)的顺序进行发布和消费。
  • 分区顺序消息:对于指定的一个Topic,所有消息根据Sharding Key进行区块分区。同一个分区内的消息按照严格的FIFO顺序进行发布和消费。Sharding Key是顺序消息中用来区分不同分区的关键字段,和普通消息的Message Key是完全不同的概念。
  • 消息堆积:Producer已经将消息发送到消息队列RocketMQ的服务端,但由于Consumer消费能力有限,未能在短时间内将所有消息正确消费掉,此时在消息队列RocketMQ的服务端保存着未被消费的消息,该状态即消息堆积。
  • 消息过滤:Consumer可以根据消息标签(Tag)对消息进行过滤,确保Consumer最终只接收被过滤后的消息类型。消息过滤在消息队列RocketMQ的服务端完成。
  • 消息轨迹:在一条消息从Producer发出到Consumer消费处理过程中,由各个相关节点的时间、地点等数据汇聚而成的完整链路信息。通过消息轨迹,您能清晰定位消息从Producer发出,经由消息队列RocketMQ服务端,投递给Consumer的完整链路,方便定位排查问题。
  • 重置消费位点:以时间轴为坐标,在消息持久化存储的时间范围内(默认3天),重新设置Consumer对已订阅的Topic的消费进度,设置完成后Consumer将接收设定时间点之后由Producer发送到消息队列RocketMQ服务端的消息。
  • 死信队列:死信队列用于处理无法被正常消费的消息。当一条消息初次消费失败,消息队列RocketMQ会自动进行消息重试;达到最大重试次数后,若消费依然失败,则表明Consumer在正常情况下无法正确地消费该消息。此时,消息队列RocketMQ不会立刻将消息丢弃,而是将这条消息发送到该Consumer对应的特殊队列中。 消息队列RocketMQ将这种正常情况下无法被消费的消息称为死信消息(Dead-Letter Message),将存储死信消息的特殊队列称为死信队列(Dead-Letter Queue)。

三、rocketmq核心应用

  • 削峰填谷:诸如秒杀、抢红包、企业开门红等大型活动时皆会带来较高的流量脉冲,或因没做相应的保护而导致系统超负荷甚至崩溃,或因限制太过导致请求大量失败而影响用户体验,消息队列RocketMQ可提供削峰填谷的服务来解决该问题。
  • 异步解耦:交易系统作为淘宝和天猫主站最核心的系统,每笔交易订单数据的产生会引起几百个下游业务系统的关注,包括物流、购物车、积分、流计算分析等等,整体业务系统庞大而且复杂,消息队列RocketMQ可实现异步通信和应用解耦,确保主站业务的连续性。
  • 顺序收发:细数日常中需要保证顺序的应用场景非常多,例如证券交易过程时间优先原则,交易系统中的订单创建、支付、退款等流程,航班中的旅客登机消息处理等等。与先进先出FIFO(First In First Out)原理类似,消息队列RocketMQ提供的顺序消息即保证消息FIFO。
    分布式事务一致性:交易系统、支付红包等场景需要确保数据的最终一致性,大量引入消息队列
  • RocketMQ的分布式事务,既可以实现系统之间的解耦,又可以保证最终的数据一致性。
  • 大数据分析:数据在“流动”中产生价值,传统数据分析大多是基于批量计算模型,而无法做到实时的数据分析,利用阿里云消息队列RocketMQ与流式计算引擎相结合,可以很方便的实现业务数据的实时分析。
  • 分布式缓存同步:天猫双11大促,各个分会场琳琅满目的商品需要实时感知价格变化,大量并发访问数据库导致会场页面响应时间长,集中式缓存因带宽瓶颈,限制了商品变更的访问流量,通过消息队列RocketMQ构建分布式缓存,实时通知商品数据的变化。

四、go如何使用rocketmq

  1. 导入rocketmq包
    go get github.com/apache/rocketmq-client-go/v2
  2. 初始化消费者
package rocketmq

import (
	"context"
	"fmt"
	"gin/common/alarm"
	"gin/common/function"
	consumer2 "gin/config/extra/consumer"
	"gin/lib"
	"github.com/apache/rocketmq-client-go/v2"
	"github.com/apache/rocketmq-client-go/v2/consumer"
	"github.com/apache/rocketmq-client-go/v2/primitive"
	"github.com/apache/rocketmq-client-go/v2/producer"
	"github.com/apache/rocketmq-client-go/v2/rlog"
	"github.com/tidwall/gjson"
	"reflect"
	"sync"
)

var p rocketmq.Producer

func InitProducer() {
	p, _ = rocketmq.NewProducer(
        producer.WithNsResolver(primitive.NewPassthroughResolver([]string{"127.0.0.1:9876"})),
		producer.WithRetry(2),
		producer.WithGroupName("gin-hong-api"),
	)

	err := p.Start()

	if err != nil {
		fmt.Printf("start producer error: %s", err.Error())
	}
}

func SendMessage(topic string, message string) {

	msg := &primitive.Message{
		Topic: topic,
		Body:  []byte(message),
	}

	res, err := p.SendSync(context.Background(), msg)

	if err != nil {
		fmt.Printf("send message error: %s\n", err)
	} else {
		fmt.Printf("send message success: result=%s\n", res.String())
	}
}

3 初始化消费者

func InitConsumer() {
    c, err := rocketmq2.NewPushConsumer(
		consumer.WithGroupName("gin-hong-api"),
		consumer.WithNsResolver(primitive.NewPassthroughResolver([]string{"127.0.0.1:9876"})),
	)
	if err != nil {
		rlog.Fatal(fmt.Sprintf("fail to new pullConsumer: %s", err), nil)
	}
	
	wg := new(sync.WaitGroup)
	wg.Add(1)

	defer func() {
		if err := recover(); err != nil {
			errMsg := function.ErrorToString(err)
			trace := function.PrintStackTrace()
			alarm.DingDing(errMsg + trace)
		}
	}()
	
		c.Subscribe(s, consumer.MessageSelector{}, func(ctx context.Context,
			msgs ...*primitive.MessageExt) (consumer.ConsumeResult, error) {
			for _, message := range msgs {
				msg := string(message.Body)
				fmt.Println(msg)
			}

			return consumer.ConsumeSuccess, nil
		})
	

	err := c.Start()

	if err != nil {
		rlog.Fatal(fmt.Sprintf("fail to new pullConsumer: %s", err), nil)
	}

	wg.Wait()
}
  1. 需要注意的点
  • 一个消费者组中的功能应该一致,防止遗漏消费
  • 一个Topic中的所有队列和一个消费者组中的消费者是多对一的关系,防止重复消费
  • 一个消费者组对应消费者集群,一个消费者对应一个进程。同组中的消费者可以是一个机器中的多个进程;也可以是多个机器中的进程(建立集群,防止宕机导致服务不可用)
  • 在一个进程中,可以并发运行不同消费者组的消费者实例,但不能并发运行同组的消费者线程。Go SDK内部也没有提供单例线程数的设置。
  • 一个消费者组可以订阅多个Topic
  • 所有消费者组由nameserve统一管理

总结

由于rocketmq go 包使用pullConsumer还没实现。所以目前只能使用Subscribe来消费。

<think>我们参考引用[3]和引用[4]来实现GolangRocketMQ的顺序消息发送。 根据引用[3],顺序消息需要将同一业务标识的消息发送到同一个队列中(在RocketMQ中称为MessageQueue)。在Golang客户端中,我们可以通过设置`producer.WithQueueSelector`来指定队列选择器,将同一业务标识(如订单ID)的消息发送到同一个队列。 同时,引用[4]中提到,可以通过设置消息组(`SetMessageGroup`)来实现顺序消息。实际上,在RocketMQ 5.0中,消息组(MessageGroup)用于标识同一组消息,确保它们被发送到同一个队列。在Golang客户端中,我们可以使用`SetMessageGroup`方法。 因此,有两种方式可以实现顺序消息发送: 1. 使用自定义的队列选择器(QueueSelector),根据业务标识(如订单ID)选择队列。 2. 使用消息组(MessageGroup),将同一组消息设置相同的消息组名。 下面分别给出两种方式的示例代码: 方式一:使用自定义队列选择器(参考引用[3]) 我们可以实现一个`producer.QueueSelector`,根据业务标识(如订单ID)选择队列。这里我们使用一个简单的哈希算法来选择队列。 方式二:使用SetMessageGroup(参考引用[4]) 在消息上设置消息组(MessageGroup),这样RocketMQ会根据消息组名进行哈希,将同一消息组的消息发送到同一个队列。 注意:根据RocketMQ的文档,使用消息组(MessageGroup)是推荐的方式,因为它更简单,并且RocketMQ服务端会保证同一消息组的消息顺序。 下面我们分别给出两种方式的代码示例。 首先,确保已经安装RocketMQ的Go客户端: go get github.com/apache/rocketmq-client-go/v2 方式一:自定义队列选择器 ```go package main import ( "context" "fmt" "hash/fnv" "strconv" "github.com/apache/rocketmq-client-go/v2" "github.com/apache/rocketmq-client-go/v2/primitive" "github.com/apache/rocketmq-client-go/v2/producer" ) // 自定义队列选择器,根据shardingKey进行哈希选择队列 type mySelector struct{} func (s *mySelector) Select(shardingKey string) producer.QueueSelector { return func(mqs []*primitive.MessageQueue, msg *primitive.Message) (int, error) { // 使用shardingKey(即业务标识)来选择队列 // 这里使用fnv哈希算法 h := fnv.New32a() h.Write([]byte(shardingKey)) hashCode := h.Sum32() index := int(hashCode) % len(mqs) return index, nil } } func main() { p, err := rocketmq.NewProducer( producer.WithNameServer([]string{"127.0.0.1:9876"}), // 替换为你的NameServer地址 producer.WithRetry(2), // 设置自定义选择器,这里我们使用一个固定的shardingKey,实际中应该根据业务变化,比如使用订单ID // 注意:这里我们通过WithQueueSelector设置一个队列选择器,并且我们将在发送消息时传入shardingKey producer.WithQueueSelector(&mySelector{}), ) if err != nil { panic("生成producer失败: " + err.Error()) } err = p.Start() if err != nil { panic("启动producer失败: " + err.Error()) } defer p.Shutdown() // 发送顺序消息,同一个订单的消息使用相同的订单ID作为shardingKey for i := 0; i < 10; i++ { // 模拟两个订单,订单ID分别为"order001"和"order002" orderID := "order001" if i%2 == 0 { orderID = "order002" } msg := primitive.NewMessage("YourTopic", []byte("Hello RocketMQ! This is a order message. ID: "+orderID+", index: "+strconv.Itoa(i))) // 发送消息时,传入shardingKey(即订单ID) res, err := p.SendSync(context.Background(), msg, producer.WithShardingKey(orderID)) if err != nil { fmt.Printf("发送失败: %s\n", err) } else { fmt.Printf("发送成功: result=%s\n", res.String()) } } } ``` 方式二:使用消息组(MessageGroup)(推荐) ```go package main import ( "context" "fmt" "strconv" "github.com/apache/rocketmq-client-go/v2" "github.com/apache/rocketmq-client-go/v2/primitive" "github.com/apache/rocketmq-client-go/v2/producer" ) func main() { p, err := rocketmq.NewProducer( producer.WithNameServer([]string{"127.0.0.1:9876"}), // 替换为你的NameServer地址 producer.WithRetry(2), ) if err != nil { panic("生成producer失败: " + err.Error()) } err = p.Start() if err != nil { panic("启动producer失败: " + err.Error()) } defer p.Shutdown() // 发送顺序消息,同一个订单的消息使用相同的消息组 for i := 0; i < 10; i++ { // 模拟两个订单,订单ID分别为"order001"和"order002" orderID := "order001" if i%2 == 0 { orderID = "order002" } msg := primitive.NewMessage("YourTopic", []byte("Hello RocketMQ! This is a order message. ID: "+orderID+", index: "+strconv.Itoa(i))) // 设置消息组,使用订单ID作为消息组名 msg.SetMessageGroup(orderID) res, err := p.SendSync(context.Background(), msg) if err != nil { fmt.Printf("发送失败: %s\n", err) } else { fmt.Printf("发送成功: result=%s\n", res.String()) } } } ``` 注意: 1. 请将NameServer地址替换为实际的地址。 2. 主题(Topic)需要提前在RocketMQ中创建,或者由RocketMQ自动创建(如果配置允许)。 3. 顺序消息不仅需要发送顺序,还需要消费顺序。消费者需要设置为顺序消费模式(在消费者端使用`consumer.WithConsumerOrder`选项),并且同一个消息组(或同一业务标识)的消息会被同一个消费者顺序消费。 相关问题: 1. 在Golang中如何实现RocketMQ的顺序消费? 2. RocketMQ顺序消息的注意事项有哪些? 3. 如何确保RocketMQ顺序消息在发送和消费过程中的可靠性?</think>### Golang RocketMQ 顺序消息发送实现方法 在 Golang 中实现 RocketMQ 的顺序消息发送,核心是**确保同一业务标识的消息发送到同一队列**。以下是两种实现方式(推荐方式二): --- ### **方式一:自定义队列选择器(`MessageQueueSelector`)** 参考引用[3],通过实现 `producer.QueueSelector` 接口,将同一业务标识(如订单ID)的消息路由到固定队列。 ```go package main import ( "context" "fmt" "hash/fnv" "strconv" "github.com/apache/rocketmq-client-go/v2" "github.com/apache/rocketmq-client-go/v2/primitive" "github.com/apache/rocketmq-client-go/v2/producer" ) // 自定义队列选择器(按订单ID哈希) type OrderHashSelector struct{} func (s *OrderHashSelector) Select(mqs []*primitive.MessageQueue, msg *primitive.Message, arg interface{}) (int, error) { orderID := arg.(string) // 业务标识(如订单ID) h := fnv.New32a() h.Write([]byte(orderID)) index := int(h.Sum32()) % len(mqs) return index, nil } func main() { p, _ := rocketmq.NewProducer( producer.WithNameServer([]string{"127.0.0.1:9876"}), producer.WithQueueSelector(&OrderHashSelector{}), // 注册选择器 ) _ = p.Start() defer p.Shutdown() // 发送顺序消息(同一订单ID发到同一队列) for i := 0; i < 10; i++ { orderID := "order_1001" // 业务标识(实际需动态生成) msg := &primitive.Message{ Topic: "OrderTopic", Body: []byte("订单消息:" + strconv.Itoa(i)), } // 关键:通过SendOption传递业务标识 res, err := p.SendSync(context.Background(), msg, producer.WithShardingKey(orderID)) if err != nil { fmt.Printf("发送失败: %v\n", err) } else { fmt.Printf("发送成功: %s\n", res.String()) } } } ``` #### **关键点说明** 1. **队列选择器**:实现 `producer.QueueSelector` 接口,根据业务标识(如订单ID)计算队列索引。 2. **业务标识传递**:通过 `producer.WithShardingKey(orderID)` 将标识传递给选择器。 3. **队列映射**:同一标识始终映射到同一队列(例如 `order_1001` 的所有消息发到队列2)。 --- ### **方式二:消息组(`MessageGroup`)方式(推荐)** 参考引用[4],直接为消息设置 `MessageGroup` 属性(RocketMQ 5.0+ 支持),服务端自动路由到同一队列。 ```go // (省略相同初始化代码) func main() { p, _ := rocketmq.NewProducer(producer.WithNameServer([]string{"127.0.0.1:9876"})) _ = p.Start() defer p.Shutdown() for i := 0; i < 10; i++ { msg := primitive.NewMessage("OrderTopic", []byte("订单消息:"+strconv.Itoa(i))) // 设置消息组(同一组的消息发到同一队列) msg.SetMessageGroup("order_1001") // 业务标识作为组名 res, err := p.SendSync(context.Background(), msg) if err != nil { fmt.Printf("发送失败: %v\n", err) } else { fmt.Printf("发送成功: %s\n", res.String()) } } } ``` #### **优势** - **更简洁**:无需实现选择器逻辑。 - **服务端保障**:RocketMQ 自动处理队列路由[^4]。 - **兼容性**:适用于 RocketMQ 5.0 及以上版本。 --- ### **注意事项** 1. **消费者需顺序消费**:顺序消息要求消费者使用 `consumer.WithConsumerOrder(true)` 启动顺序消费模式[^3]。 2. **队列数量限制**:一个消息组只能映射到一个队列,吞吐量受队列数量限制。 3. **业务标识设计**:标识需保证同一业务链消息使用相同值(如订单ID、用户ID)。 4. **错误处理**:生产环境需添加重试机制(通过 `producer.WithRetry(2)` 配置)。 > **引用说明**:顺序消息的核心原理是将同一业务标识的消息映射到同一队列[^3][^4],消费者按队列顺序处理消息。 --- ### 相关问题 1. **如何保证RocketMQ顺序消息的消费顺序性?** (需配置消费者顺序消费模式及单队列单线程处理) 2. **顺序消息在集群故障时如何保证可靠性?** (依赖RocketMQ的主从复制和故障转移机制) 3. **顺序消息与普通消息的性能差异有哪些?** (顺序消息吞吐量较低,但保证业务顺序) 4. **如何处理顺序消息中的消费失败?** (需跳过失败消息或暂停消费,避免破坏顺序)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员若风+

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值