RocketMQ 是一种快速、低延迟、可靠、可扩展和分布式的消息中间件,特别为大规模分布式系统设计。它是一种消息解决方案,为系统架构中各种应用、微服务和其他组件提供高效可靠的通信。
RocketMQ 提供了消息持久性、事务消息、发布/订阅消息、消息过滤和负载平衡等功能。它支持同步和异步消息,并能处理高消息吞吐量和低延迟的消息。
RocketMQ 还提供了灵活可扩展的架构,可轻松集成到各种应用中,包括电子商务、金融、物联网和游戏应用。它的可扩展性和高性能使其成为需要可靠消息功能的大规模分布式系统中的一个流行选择。
总之,RocketMQ 是一个强大的消息中间件,为大规模分布式系统提供高效、可靠和可扩展的消息。
MQ (Message Queue) 消息队列是一种软件体系结构模式,它允许不同的应用程序或系统之间通过一个中间代理来进行消息传递。这种代理叫做消息队列服务器,通常简称为 MQ 服务器。
消息队列中的消息可以在生产者和消费者之间传递,生产者向队列中投递消息,消费者从队列中读取消息。这种模式在分布式系统、异步任务处理、解耦任务之间的依赖关系等方面都有很好的应用。
其应用场景主要包含以下3个方面
系统的耦合性越高,容错性就越低。以电商应用为例,用户创建订单后,如果耦合调用库存系统、物流系统、支付系统,任何一个子系统出了故障或者因为升级等原因暂时不可用,都会造成下单操作异常,影响用户使用体验。
使用消息队列解耦合,系统的耦合性就会提高了。比如物流系统发生故障,需要几分钟才能来修复,在这段时间内,物流系统要处理的数据被缓存到消息队列中用户的下单操作正常完成,当物流系统回复后
应用系统如果遇到系统请求流量的瞬间猛增,有可能会将系统压垮。
有了消息队列可以将大量请求缓存起来,分散到很长一段时间处理,这样可以大大提到系统的稳定性和用户体验。
一般情况,为了保证系统的稳定性,如果系统负载超过阈值,就会阻止用户请求,这会影响用户体验,而如果使用消息队列将请求缓存起来,等待系统处理完毕后通知用户下单完毕,这样总不能下单体验要好。
处于经济考量目的:
业务系统正常时段的QPS如果是1000,流量最高峰是10000,为了应对流量高峰配置高性能的服务器显然不划算,这时可以使用消息队列对峰值流量削峰
通过消息队列可以让数据在多个系统更加之间进行流通。
数据的产生方不需要关心谁来使用数据,将数据发送到消息队列,数据使用方直接在消息队列中直接获取数据即可。
这里我选RocketMQ 原因有两点:
insatll.zip
下载解压后
进入conf 编辑配置文件 把127.0.0.1换成自己服务器(虚拟机)的IP
docker-compose up
在查看的时候起始日期尽量提前 因为服务器(虚拟机)的日期可能和当前时间不匹配
completes
阻塞结束3秒
内进行重试,最多重试2次completes
不代表投递成功,要check SendResult.sendStatus
来判断是否投递成功SendResult
里面有发送状态的枚举:SendStatus,同步的消息投递有一个状态返回值的public enum SendStatus {
SEND_OK,
FLUSH_DISK_TIMEOUT,
FLUSH_SLAVE_TIMEOUT,
SLAVE_NOT_AVAILABLE,
}
ack
的SendStatus=SEND_OK
才会停止retry
注意事项:
发送同步消息且Ack
为SEND_OK
,只代表该消息成功的写入了MQ
当中,并不代表该消息成功的被Consumer
消费了
produce
r啊,因为是异步的,不会阻塞,提前关闭producer
会导致未回调链接就断开了retry
,投递失败回调onException()
方法,只有同步消息才会retry
,源码参考DefaultMQProducerlmpl.class
下表概括了三者的特点和主要区别。
普通消息是我们在业务开发中用到的最多的消息类型,生产者需要关注消息发送成功即可,消费者消费到消息即可,不需要保证消息的顺序,所以消息可以大规模并发地发送和消费,吞吐量很高,适合大部分场景。
顺序消息分为分区顺序消息和全局顺序消息,全局顺序消息比较容易理解,也就是哪条消息先进入,哪条消息就会先被消费,符合我们的FIFO,很多时候全局消息的实现代价很大,所以就出现了分区顺序消息。分区顺序消息的概念可以如下图所示:
我们通过对消息的key
,进行hash
,相同hash
的消息会被分配到同一个分区里面,当然如果要做全局顺序消息,我们的分区只需要一个即可,所以全局顺序消息的代价是比较大的。
延迟的机制是在服务端实现的,也就是Broker
收到了消息,但是经过一段时间以后才发送
服务器按照1-N定义了如下级别:“1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h2h”;若要发送定时消息,在应用层初始化Message消息对象之后,调用Message.setDelayTimeLeve(intlevel)
方法来设置延迟级别,按照序列取相应的延迟级别,例如level=2
,则延迟为5s
msg.setDelayTimeLevel(2);
SendResult sendResult = producer.send(msg );
实现原理:
DelayTimeLevel
,那么该消息会被丢到ScheduleMessageService.SCHEDULE_TOPIC
这个Topic
里面DelayTimeLevel
选择对应的queue
topic
和queue
信息封装起来,set
到msg
里面SCHEDULE_TOPIC_XXXX
的每个DelayTimeLevelQueue
,有定时任务去刷新,是否有待投递的消息10s
定时持久化发送进度正是因为有完善的事务消息才会用到rocketMQ(重点)
上一篇文章也详细分析过事务消息 这里就不详细分析了
官方详解:https://help.aliyun.com/document_detail/43348.html?spm=a2c4g.11186623.2.16.78ee6192siK1qV#concept-2047067
消息队列RocketMQ版提供的分布式事务消息适用于所有对数据最终一致性有强需求的场景。本文介绍消息队列RocketMQ版事务消息的概念、优势、典型场景、交互流程以及使用过程中的注意事项。
消息队列RocketMQ版分布式事务消息不仅可以实现应用之间的解耦,又能保证数据的最终一致性。同时,传统的大事务可以被拆分为小事务,不仅能提升效率,还不会因为某一个关联应用的不可用导致整体回滚,从而最大限度保证核心系统的可用性。在极端情况下,如果关联的某一个应用始终无法处理成功,也只需对当前应用进行补偿或数据订正处理,而无需对整体业务进行回滚。
github开源库:https://github.com/apache/rocketmq-client-go/blob/d6e66a2d648d6529eca833f9bf912915d1749a5c/docs/Introduction.md
需要关闭防火墙
发送普通消息
package main
import (
"context"
"fmt"
"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() {
//连接recketmq
p, err := rocketmq.NewProducer(producer.WithNameServer([]string{"192.168.10.130:9876"}))
if err != nil {
fmt.Println("生成producer失败:", err)
}
//启动
err = p.Start()
if err != nil {
fmt.Println("启动producer错误:", err)
}
//实例化消息
msg := &primitive.Message{
Topic: "jzin",
Body: []byte("this is jzin"),
}
//同步发送
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())
}
//关闭连接
err = p.Shutdown()
if err != nil {
fmt.Printf("shutdown producer error: %s", err.Error())
}
}
消费者
package main
import (
"context"
"fmt"
"github.com/apache/rocketmq-client-go/v2"
"github.com/apache/rocketmq-client-go/v2/consumer"
"github.com/apache/rocketmq-client-go/v2/primitive"
"os"
"time"
)
func main() {
//启动recketmq并设置负载均衡的Group
c, _ := rocketmq.NewPushConsumer(
consumer.WithNameServer([]string{"192.168.10.130:9876"}),
consumer.WithGroupName("jzins"),
)
//订阅消息
if err := c.Subscribe("jzin", consumer.MessageSelector{}, func(ctx context.Context, msgs ...*primitive.MessageExt) (consumer.ConsumeResult, error) {
for i := range msgs {
fmt.Printf("subscribe callback: %v \n", msgs[i])
}
return consumer.ConsumeSuccess, nil
}); err != nil {
fmt.Println(err.Error())
}
//启动
err := c.Start()
if err != nil {
fmt.Println(err.Error())
os.Exit(-1)
}
//阻塞主线程
time.Sleep(time.Hour)
//关闭连接
err = c.Shutdown()
if err != nil {
fmt.Printf("shutdown Consumer error: %s", err.Error())
}
}
延时消息
为什么要用延时消息而不是自己写:
支付的时候,淘宝,12306 ,购票,超时归还–定时执行逻辑
我可以去写一个轮询,轮询的问题:1.多久执行一次轮询30分钟
在12:00执行过一次,下一次执行就是在12:30的时候但是12:01的时候下了单,12:31就应该超时13:00时候才能超时
那我1分钟执行一次啊,比如我的订单量没有这么大,1分钟执行一次,其中29次查询都是无用,而且你还还会轮询mysql
rocketmq的延迟消息,1.时间一到就执行,2.消息中包含了订单编号,你只查询这种订单编号
只需要在msg
里设置WithDelayTimeLevel
就行了:
msg.WithDelayTimeLevel(3)
事务消息
package main
import (
"context"
"fmt"
"github.com/apache/rocketmq-client-go/v2"
"github.com/apache/rocketmq-client-go/v2/primitive"
"github.com/apache/rocketmq-client-go/v2/producer"
"go.uber.org/zap"
"time"
)
type OrderListener struct {
ID int32
Detail string
}
func (o *OrderListener) ExecuteLocalTransaction(msg *primitive.Message) primitive.LocalTransactionState {
//执行逻辑并返回状态-自己决定
return primitive.CommitMessageState
}
func (o *OrderListener) CheckLocalTransaction(msg *primitive.MessageExt) primitive.LocalTransactionState {
//执行回查逻辑并返回状态-自己决定
return primitive.RollbackMessageState
}
func main() {
p, err := rocketmq.NewTransactionProducer(
&OrderListener{},
producer.WithNameServer([]string{"192.168.10.130:9876"}),
)
if err != nil {
zap.S().Error("生成producer失败:%s", err.Error())
}
//启动
if err = p.Start(); err != nil {
zap.S().Error("启动producer失败:%s", err.Error())
}
//发送半消息
res, err := p.SendMessageInTransaction(context.Background(), primitive.NewMessage("order_info", []byte("this is tranJzin")))
if err != nil {
fmt.Printf("发送失败: %s\n", err)
} else {
fmt.Printf("发送成功: %s\n", res.String())
}
//if res.State == primitive.CommitMessageState {
// fmt.Printf("发送失败: %s\n", err)
//}
//阻塞主线程
time.Sleep(time.Hour)
if err = p.Shutdown(); err != nil {
fmt.Println("关闭producer失败")
}
}