rocketmq保证消息的顺序性

背景

公司实际业务有两个动作,一个插入数据库的操作,一个修改数据库状态的操作,直接操作数据库,会导致数据库的压力太大,引发系统故障;所以需要在保存中间增加mq进行削峰,但是要求修改动作必须在插入之后才能执行,所以需要mq顺序消费
流程很简单:业务方发送消息到生产者—》发送到MQ----》消费者消费,保存到数据库

rocket mq 基础常识

在开始做这个事情前,需要先了解一些mq的基本常识
建议读一下这个文档来熟悉Topic、Tag、GroupName和queue的概念、设计初衷以及使用方法。

rocket mq 的消息类型

1、普通消息
2、事务消息
3、定时和延时消息
4、顺序消息:分区顺序消息和全局顺序消息
本次只介绍顺序消息:

全局顺序消息:

在这里插入图片描述
当发送和消费参与的Queue只有一个时所保证的有序是整个Topic中消息的顺序, 称为全局有序。
全局顺序消息实际上是一种特殊的分区顺序消息,即Topic中只有一个分区,因此全局顺序和分区顺序的实现原理相同。因为分区顺序消息有多个分区,所以分区顺序消息比全局顺序消息的并发度和性能更高。

适用场景
适用于性能要求不高,所有的消息严格按照FIFO原则来发布和消费的场景。

分区顺序消息:

在这里插入图片描述
当一个Topic存在多个Queue参与的时候,所有消息根据Sharding Key选择Queue,同一个Queue的消息按照严格的先进先出(FIFO)原则进行发布和消费。同一Queue的消息保证顺序,不同分区之间的消息顺序不做要求。

这种情况试用场景:适用于性能要求高,以Sharding Key作为队列选择字段,在同一个区块中严格地按照先进先出(FIFO)原则进行消息发布和消费的场景。
比如:电商的订单创建,以订单ID作为Sharding Key,那么同一个订单相关的创建订单消息、订单支付消息、订单退款消息、订单物流消息都会进入同一个Queue,按照发布的先后顺序来消费。

注意事项

对于全局顺序消息,建议消息不要有阻塞。无论运行多少实例,实际工作的只会有一个实例。所以对性能要求高的业务,建议还是走分区顺序消息,这个地方作者踩过坑,起了4个实例,但是消费能力一直上不去,才发现全局消息,只有一个消费者会工作

实现Queue的选择

无论是什么开发语言,都是在定于Producer时候,指定消息队列选择器,java是实现MessageQueueSelector接口定义的

在定义选择器的选择算法时,一般需要使用Sharding key。这个Sharding key可以是消息key也可以是其它数据。但无论谁做Sharding key,都不能重复,都是唯一的。比如订单号,或者消息的唯一ID

一般性的选择算法是,让Sharding key(或其hash值)与该Topic所包含的Queue的数量取模,其结果即为选择出的Queue的QueueId

java的实现阿里云给了sdk,网上也有很多,但是GO语言的sdk阿里云没有给,需要自己实现,下面给出简单的代码实现

package main

import (
	"context"
	"encoding/json"
	"fmt"
	"github.com/apache/rocketmq-client-go/v2"
	"github.com/apache/rocketmq-client-go/v2/primitive"
	"github.com/apache/rocketmq-client-go/v2/producer"
	"github.com/google/uuid"
	"go.chanjet.com/chanjet/common/db"
	"log"
	"time"
)

var dbm db.DbWrap

func main() {
	p, err := rocketmq.NewProducer(
		producer.WithGroupName(uuid.New().String()),
		producer.WithNameServer([]string{"rocketMqAddress"}),
		producer.WithCredentials(primitive.Credentials{
			AccessKey: "accessKey",
			SecretKey: "accessSecret",
		}),
		producer.WithSendMsgTimeout(time.Second*60),
		//指定队列选择器,表明使用要发送的队列就是msg中定义的queue
		//hashQueueSelector如果消息具有分片密钥,则按hash选择队列,否则按随机选择队列
		producer.WithQueueSelector(producer.NewHashQueueSelector()),
	)

	if err != nil {
		fmt.Println("--生成 isv message producer 失败:", err)
		panic("生成isv message  producer 失败")
	}

	if err = p.Start(); err != nil {
		fmt.Println("--启动 isv message  producer 失败:", err)
		panic("启动isv message  producer 失败")
	}

	body, _ := json.Marshal("{\"status\":\"SEND_SUCCESS\",\"requestI\nd\":\"8d7d7bd0-7414-4f20-bd16-17c5ac71b8ce\",\"traceId\":\"315518776ef8a801\",\"appKey\":\"*******\",\"updatedAt\":\"0001-01-01T00:00:00Z\"}")

	msg := primitive.NewMessage("TOPIC", body)
	msg.WithTag("tag")
	//指定分片 key,保证相同key的消息进入同一个queue
	msg.WithShardingKey("*******" + "8d7d7bd0-7414-4f20-bd16-17c5ac71b8ce")

	//设置
	_, err = p.SendSync(context.Background(), msg) // 不能用单向
	if err != nil {
		log.Printf("send message err: %s", err)
		return
	}
}

这个就是Go发送分区顺序消息的方式,go这块资料比较少,记录一下,避免后续遗忘

核心就在于创建producer时候,就需要指定queue选择器,否则WithShardingKey设置了也不生效

这个QueueSelector队列选择器,其实就是hashQueueSelector,如果消息具有分片密钥,则按hash选择队列,否则按随机选择队列。

  • 12
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值