分布式事务(四)Spring Cloud 微服务系统基于 RocketMQ 可靠消息最终一致性实现分布式事务(完结)

本文详细介绍了如何在Spring Cloud微服务系统中,利用RocketMQ的事务消息实现分布式事务的最终一致性。包括RocketMQ的安装、Topic原理、原生API收发消息示例,特别是事务消息的生产者和消费者实现,以及在订单业务中的应用,确保在分布式环境中数据的一致性。
摘要由CSDN通过智能技术生成

分布式事务(四)Spring Cloud 微服务系统基于 RocketMQ 可靠消息最终一致性实现分布式事务

搭建 RocketMQ 服务器

RocketMQ 安装

  1. 克隆 centos-8-2105(centos-7-1908):rocketmq

  2. 设置 ip

    ./ip-static
    ip: 192.168.64.141
    
    ifconfig
    

    centos-8 设置若有问题:

    可以开启 VMware 托管

    nmcli n on
    
    systemctl restart NetworkManager
    
  3. 上传文件到 /root/

    • 课前资料/分布式事务/rocketmq/ 文件夹的三个文件
  4. 按照 rocketmq 笔记安装:

    RocketMQ (一) 安装

  5. 启动 rocketmq

    先启动 name server

    # 进入 rocketmq 目录
    cd /usr/local/rocketmq/
    
    # 启动 name server
    nohup sh bin/mqnamesrv &
    
    # 查看运行日志, 看到"The Name Server boot success."表示启动成功
    tail -f ~/logs/rocketmqlogs/namesrv.log
    

    再启动 broker

    # 启动 broker, 连接name server: localhost:9876
    nohup sh bin/mqbroker -n localhost:9876 &
    
    # 查看运行日志, 看到"The broker[......:10911] boot success."表示启动成功
    tail -f ~/logs/rocketmqlogs/broker.log 
    
  6. 启动管理界面

    # 切换到主目录
    cd ~/
    
    # 运行启动管理界面(控制台)
    nohup java -jar rocketmq-console-ng-1.0.1.jar --server.port=8080 --rocketmq.config.namesrvAddr=localhost:9876 &
    

    访问管理界面:http://192.168.64.141:8080

    查看 Cluster 选项卡,下面应有注册信息:

    • IP 地址应为 192.168.64.141

    • 若 ip 地址为 172.xx.xx.xxx 则是虚拟机克隆错误或者网络配置问题。

  7. 收发消息出现超时问题

    直接复制代码解决问题

    cd /usr/local/rocketmq/
    
    vim conf/broker.conf
    
    末尾添加
    brokerIP1=192.168.64.141
    
    关闭 broker 服务
    mqshutdown broker
    
    重新使用 broker.conf 配置启动 broker
    nohup sh bin/mqbroker -n localhost:9876 -c conf/broker.conf &
    

RocketMQ 双主双从同步复制集群方案

broker 集群是为了实现高可用,防止服务器宕机,每一个主 broker 都带有一个从 broker。

参考笔记:

RocketMQ (二) 双主双从同步复制集群方案

RocketMQ 基本原理

Topic 基本原理

在管理界面,点击 Topic 选项卡,点击 ADD/UPDATE 创建一个新的 Topic。

clusterName 选择默认 DefaultCluster,命名 Topic1,读写队列为 3,权限为 6。

使用 RocketMQ 原生 API 收发消息代码样例

创建工程

新建空工程 rocketmq-dtx

创建 Maven 模块 rocketmq-api,注意项目目录在空工程下面。

添加依赖(直接复制):

<dependencies>
    <dependency>
        <groupId>org.apache.rocketmq</groupId>
        <artifactId>rocketmq-client</artifactId>
        <version>4.7.1</version>
    </dependency>

    <dependency>
        <groupId>org.apache.rocketmq</groupId>
        <artifactId>rocketmq-store</artifactId>
        <version>4.7.1</version>
    </dependency>

</dependencies>
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.8.0</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
            </configuration>
        </plugin>
    </plugins>
</build>

同步消息

同步消息发送要保证强一致性,发到master的消息向slave复制后,才会向生产者发送反馈信息。

这种可靠性同步地发送方式使用的比较广泛,比如:重要的消息通知,短信通知。

生产者

新建 m1 包。创建 Producer 生产者类,添加 main 方法。

创建生产者对象

添加组名 pro-group1

DefaultMQProducer p = new DefaultMQProducer("pro-group1");

设置 name server,启动连接

p.setNamesrvAddr("192.168.64.141:9876");
p.start();

创建消息封装对象 Message(Topic,Tag,消息)并发送

while (true){
   
    System.out.println("输入消息: ");
    String s = new Scanner(System.in).nextLine();
    // Topic 相当于是一级分类
    // Tag   相当于是二级分类
    Message msg = new Message("Topic1", "TagA", s.getBytes());
    SendResult r = p.send(msg);
    System.out.println(r);
}

若有异常直接抛出。

启动测试

测试输入语句,查看控制台输出信息。

前面的状态,ID数据可不看,看后面的 topic 应为之前创建的 Topic1,队列ID(queueId)应为 0-2,队列下标(queueOffset)都应为 0

查看管理界面,点击 Topic1 的 status 会发现有请求的痕迹,只不过时间戳有问题(无所谓)。

查看 Message 的选项卡,查询 Topic1,注意后面时间范围要注意调整。

消费者

创建 Consumer 生产者类,添加 main 方法。

创建消费者对象

DefaultMQPushConsumer c = new DefaultMQPushConsumer("con-group1");

设置 name,server

c.setNamesrvAddr("192.168.64.141:9876");

设置从哪里订阅消息

c.subscribe("Topic1","TagA || TagB || TagC");

设置消息监听器

// Concurrently -- 并发的,会启动多个线程处理消息
c.setMessageListener(new MessageListenerConcurrently() {
   
    @Override
    public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
   
        for (MessageExt ext : msgs) {
   
            String s = new String(ext.getBody());
            System.out.println("收到: "+s);
        }
        return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
        //return ConsumeConcurrentlyStatus.RECONSUME_LATER;//稍后重新消费
    }
});

启动连接

c.start();

启动测试

启动 Consumer 类,会接收到先前 Producer 发送的几条消息,只不过顺序不一样。

延时消息

消息发送到 Rocketmq 服务器后, 延迟一定时间再向消费者进行投递。

生产者

在创建完 msg 后面添加一句延迟。

msg.setDelayTimeLevel(3);

重启测试:Consumer 会根据延迟等级延迟发送消息。

消费者

将之前注释的 RECONSUME_LATER 打开,CONSUME_SUCCESS 注释掉。

重启 Consumer 测试是否有重新发送的消息。

顺序消息

Rocketmq 顺序消息的基本原理:

  • 同一组有序的消息序列,会被发送到同一个队列,按照 FIFO 的方式进行处理。
  • 一个队列只允许一个消费者线程接收消息,这样就保证消息按顺序被接收

下面以订单为例:

一个订单的顺序流程是:创建、付款、推送、完成。订单号相同的消息会被先后发送到同一个队列中。消费时,从同一个队列接收同一个订单的消息。

生产者

生产者发送消息时要把消息发到同一个队列。

方式:用第一条消息的 ID 去取余队列数来分组。

添加发送语句

从笔记复制即可。

static String[] msgs = {
   
        "15103111039,创建",
        "15103111065,创建",
        "15103111039,付款",
        "15103117235,创建",
        "15103111065,付款",
        "15103117235,付款",
        "15103111065,完成",
        "15103111039,推送",
        "15103117235,完成",
        "15103111039,完成"
};

基本配置

// 创建生产者对象
DefaultMQProducer p = new DefaultMQProducer("pro-group2");
// 设置 name server
p.setNamesrvAddr("192.168.64.141:9876");
// 启动,连接
p.start();

遍历数组发送消息

//遍历数组发送消息
        for (String s: msgs) {
   
            // s -- " 15103111039,完成"
            Long orderId = Long.valueOf(s.split(",")[0]);
            Message msg = new Message("Topic2", s.getBytes());
            //p.send(消息,队列选择器,选择依据)
            SendResult r = p.send(msg, new MessageQueueSelector() {
   
                @Override
                public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
   
                    Long orderId = (Long) arg;
                    int index = (int) (orderId % mqs.size());
                    return mqs.get(index);
                }
            }, orderId);
            System.out.println(r);
        }

启动测试

会有很多数据显示被发送了。

消费者

基本配置

复制之前的 Consumer 类就行。

消费者只能启动同一个线程来处理消息

更改消费者对象分组为 “con-group2”

更改订阅和标签为 “Topic2” 和 “*”

消息监听器要重写

// orderly -- 单个线程
c.setMessageListener(new MessageListenerOrderly() {
   
    @Override
    public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs, ConsumeOrderlyContext context) {
   
        for (MessageExt ext: msgs){
   
            String s = new String(ext.getBody());
            System.out.println("收到: "+s);
        }
        return ConsumeOrderlyStatus.SUCCESS;
    }
});

启动测试

收到先前发送的字符即可。

事务消息

生产者

创建事务消息生产者,设置 name server

TransactionMQProducer p = new TransactionMQProducer("pro-group3");
p.setNamesrvAddr("192.168.64.141:9876");

设置事务消息监听器

  1. 执行本地事务(service.业务方法())

  2. 处理 rocketmq 的事务回查

分别测试成功和失败两种情况:

p.setTransactionListener(new TransactionListener() {
   
    @Override
    public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
   
        System.out.println("执行本地业务,参数: "+arg);
        if (Math.random() < 0.5){
   
            System.out.println("本地事务执行成功~");
            return LocalTransactionState.COMMIT_MESSAGE; // 提交消息,通知服务器消息可以投递
        }else{
   
            System.out.println("本地事务执行失败~");
            return LocalTransactionState.ROLLBACK_MESSAGE; // 回滚消息,撤回消息
        }
        // return LocalTransactionState.UNKNOW; // 未知,当前方法中,一般不会出现这种状态
    }

    @Override
    public LocalTransactionState checkLocalTransaction(MessageExt msg) {
   
        return null;
    }
});

启动,发送事务消息

p.start();

while(true){
   
    System.out.println("输入消息: ");
    String s = new Scanner(System.in).nextLine();
    Message msg = 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值