【学相伴飞哥】RabbitMQ 2021-03-14 笔记4:集群搭建,分布式事务 2PC和TCC,异步对账,Mq死信队列,可靠生产和可靠问题

MQ的默认交换器

NameTypeFeatures
(AMQP default)direct 默认的路由模式D
amq.directdirectD
amq.fanoutfanout 发布订阅者D
amq.headersheaders 参数模式D
amq.matchheadersD
amq.rabbitmq.tracetopic 主题模式D I
amq.topictopicD
internal
adj.
内部的,体内的;内政的,国内的;本身的,本质的;内心的;<英>(大学生)本校生的
n.
内部部件,内部特征;内脏

RabbitMQ-高级-集群

记录

  • 上传erlang 和 rabbitmq-server
rpm -Uvh erlang-solutions-2.0-1.noarch. rpm
yum install -y erlang
erl -v

yum install -y socat

rpm -Uvh rabbitmq-server-3.8.13-1.el8.noarch.rpm
systemctl start rabbitmq-server

ps aux|grep rabbitmq
systemctl stop rabbitmq-server -- 停止掉,在配置

-- 启动第一个节点。
sudo 
RABBITMQ_NODE_PORT=5672 
RABBITMQ_NODENAME=rabbit-1 
rabbitmq-server start &

-- 在启动第二个节点,
sudo 
RABBITMQ_NODE_PORT=5673 
RABBITMQ_SERVER_START_ARGS="-rabbitmq_management listener [{port,15673}]" 
RABBITMQ_NODENAME=rabbit-2 
rabbitmq-server start &

ps aux|grep rabbitmq --查看下
  • 后面命令参考下面:rabbit-1操作作为主节点

01、RabbitMQ 集群

RabbitMQ这款消息队列中间件产品本身是基于Erlang编写,Erlang语言天生具备分布式特性(通过同步Erlang集群各节点的magic cookie来实现)。因此,RabbitMQ天然支持Clustering。这使得RabbitMQ本身不需要像ActiveMQ、Kafka那样通过ZooKeeper分别来实现HA方案和保存集群的元数据。集群是保证可靠性的一种方式,同时可以通过水平扩展以达到增加消息吞吐量能力的目的。
在实际使用过程中多采取多机多实例部署方式,为了便于同学们练习搭建,有时候你不得不在一台机器上去搭建一个rabbitmq集群,本章主要针对单机多实例这种方式来进行开展。

主要参考官方文档:https://www.rabbitmq.com/clustering.html

02、集群搭建

配置的前提是你的rabbitmq可以运行起来,比如”ps aux|grep rabbitmq”你能看到相关进程,又比如运行“rabbitmqctl status”你可以看到类似如下信息,而不报错:

执行下面命令进行查看:

ps aux|grep rabbitmq

img

或者

systemctl status rabbitmq-server

注意:确保RabbitMQ可以运行的,确保完成之后,把单机版的RabbitMQ服务停止,后台看不到RabbitMQ的进程为止

03、单机多实例搭建

**场景:**假设有两个rabbitmq节点,分别为rabbit-1, rabbit-2,rabbit-1作为主节点,rabbit-2作为从节点。
启动命令:RABBITMQ_NODE_PORT=5672 RABBITMQ_NODENAME=rabbit-1 rabbitmq-server -detached
结束命令:rabbitmqctl -n rabbit-1 stop

03-1、第一步:启动第一个节点rabbit-1

> sudo RABBITMQ_NODE_PORT=5672 RABBITMQ_NODENAME=rabbit-1 rabbitmq-server start &
...............省略...................
  ##########  Logs: /var/log/rabbitmq/rabbit-1.log
  ######  ##        /var/log/rabbitmq/rabbit-1-sasl.log
  ##########
              Starting broker...
 completed with 7 plugins.

至此节点rabbit-1启动完成。

03-2、启动第二个节点rabbit-2

注意:web管理插件端口占用,所以还要指定其web插件占用的端口号
RABBITMQ_SERVER_START_ARGS=”-rabbitmq_management listener [{port,15673}]”

sudo RABBITMQ_NODE_PORT=5673 RABBITMQ_SERVER_START_ARGS="-rabbitmq_management listener [{port,15673}]" RABBITMQ_NODENAME=rabbit-2 rabbitmq-server start &
..............省略..................
  ##########  Logs: /var/log/rabbitmq/rabbit-2.log
  ######  ##        /var/log/rabbitmq/rabbit-2-sasl.log
  ##########
              Starting broker...
 completed with 7 plugins.

至此节点rabbit-2启动完成

03-3、验证启动 “ps aux|grep rabbitmq”

rabbitmq  2022  2.7  0.4 5349380 77020 ?       Sl   11:03   0:06 /usr/lib/erlang/erts-9.2/bin/beam.smp -W w -A 128 -P 1048576 -t 5000000 -stbt db -


rabbitmq  2402  4.2  0.4 5352196 77196 ?       Sl   11:05   0:05 /usr/lib/erlang/erts-9.2/bin/beam.smp -W w -A 128 -P 1048576 -t 5000000 -stbt db -

03-4、rabbit-1操作作为主节点

#停止应用。 -n 节点名。重置一次。
> sudo rabbitmqctl -n rabbit-1 stop_app

#目的是清除节点上的历史数据(如果不清除,无法将节点加入到集群)
> sudo rabbitmqctl -n rabbit-1 reset

#启动应用
> sudo rabbitmqctl -n rabbit-1 start_app

03-5、rabbit2操作为从节点

# 停止应用
> sudo rabbitmqctl -n rabbit-2 stop_app

# 目的是清除节点上的历史数据(如果不清除,无法将节点加入到集群)
> sudo rabbitmqctl -n rabbit-2 reset

#记得改主机名。未来装rabbit3,也绑定到1上。最好是3个节点。
# 一主多从,主节点挂了 从节点没法数据写入。
# 将rabbit2节点加入到rabbit1(主节点)集群当中【Server-node服务器的主机名】
> sudo rabbitmqctl -n rabbit-2 join_cluster rabbit-1@'Server-node'

# 启动应用
> sudo rabbitmqctl -n rabbit-2 start_app

03-6、验证集群状态

> sudo rabbitmqctl cluster_status -n rabbit-1

//集群有两个节点:rabbit-1@Server-node、rabbit-2@Server-node
[{nodes,[{disc,['rabbit-1@Server-node','rabbit-2@Server-node']}]},
 {running_nodes,['rabbit-2@Server-node','rabbit-1@Server-node']},
 {cluster_name,<<"rabbit-1@Server-node.localdomain">>},
 {partitions,[]},
 {alarms,[{'rabbit-2@Server-node',[]},{'rabbit-1@Server-node',[]}]}]

03-7、Web监控

rabbitmq-plugins enable rabbitmq_management

img

注意在访问的时候:web结面的管理需要给15672 node-1 和15673的node-2 设置用户名和密码。如下:

rabbitmqctl -n rabbit-1 add_user admin admin
rabbitmqctl -n rabbit-1 set_user_tags admin administrator
rabbitmqctl -n rabbit-1 set_permissions -p / admin ".*" ".*" ".*"

-- 子节点提示用户已经存在,无需再次设置了。
rabbitmqctl -n rabbit-2 add_user admin admin
rabbitmqctl -n rabbit-2 set_user_tags admin administrator
rabbitmqctl -n rabbit-2 set_permissions -p / admin ".*" ".*" ".*"

03-8、小结

Tips:
如果采用多机部署方式,需读取其中一个节点的cookie, 并复制到其他节点(节点之间通过cookie确定相互是否可通信)。cookie存放在/var/lib/rabbitmq/.erlang.cookie。
例如:主机名分别为rabbit-1、rabbit-2
1、逐个启动各节点
2、配置各节点的hosts文件( vim /etc/hosts)
​ ip1:rabbit-1
​ ip2:rabbit-2
其它步骤雷同单机部署方式

  • 创建交换器
    • cluster.fanout.ex
  • 创建队列:queue1 和 queue2,并且绑定。会自动同步。
    • 才用的是 源数据共享。
rabbitmqctl -n rabbit-2 stop_app  --停止节点2
-- 如果主节点挂了,从节点 无法启动。

-- 先挂主节点,所有的消息 变成 down,消息无法投递。
  • 1主多从,1主多从,有多个。如果 把这些集群联系在一起。
    • 如果,在不同机器上。erlang.cooke 要复制到不同机器人上,保持一致。
    • 即:上面小结处说明的。
  • HA:LVS + Keeplive
    • LVS是Linux virtual server

高可用和高可靠 复习

消息队列高可用和高可靠 页面说了:

集群模式1 - Master-slave主从共享数据

  • 共享这块数据区域。Master节点负责写入,一旦Master挂掉,slave节点继续服务。

集群模式2 - Master- slave主从同步部署方式

  • 主节点会同步数据到slave节点形成副本

集群模式3 - 多主集群同步部署模式

  • 写入可以往任意节点去写入。

集群模式4 - 多主集群转发部署模式 (Mq集群,是元数据共享)

  • 元数据 共享

  • 黄牛没有但是他会去联系其他的黄牛询问

集群模式5 Master-slave与Breoker-cluster组合的方案

  • 多组 主从模式

1:要么消息共享,
2:要么消息同步
3:要么元数据共享

broker
英
/ˈbrəʊkə(r)/
n.
经纪人,中间人
v.
协调,安排

RabbitMQ-高级-分布式事务

简述

分布式事务指事务的操作位于不同的节点上,需要保证事务的 AICD 特性。
例如在下单场景下,库存和订单如果不在同一个节点上,就涉及分布式事务。

01、分布式事务的方式

在分布式系统中,要实现分布式事务,无外乎那几种解决方案。

一、两阶段提交(2PC)需要数据库产商的支持,java组件有atomikos等。

两阶段提交(Two-phase Commit,2PC),通过引入协调者(Coordinator)来协调参与者的行为,并最终决定这些参与者是否要真正执行事务。

准备阶段

协调者询问参与者事务是否执行成功,参与者发回事务执行结果。

img

1.2 提交阶段
如果事务在每个参与者上都执行成功,事务协调者发送通知让参与者提交事务;否则,协调者发送通知让参与者回滚事务。
需要注意的是,在准备阶段,参与者执行了事务,但是还未提交。只有在提交阶段接收到协调者发来的通知后,才进行提交或者回滚。

img

存在的问题
  • 2.1 同步阻塞 所有事务参与者在等待其它参与者响应的时候都处于同步阻塞状态,无法进行其它操作。
  • 2.2 单点问题 协调者在 2PC 中起到非常大的作用,发生故障将会造成很大影响。特别是在阶段二发生故障,所有参与者会一直等待状态,无法完成其它操作。
  • 2.3 数据不一致 在阶段二,如果协调者只发送了部分 Commit 消息,此时网络发生异常,那么只有部分参与者接收到 Commit 消息,也就是说只有部分参与者提交了事务,使得系统数据不一致。
  • 2.4 太过保守 任意一个节点失败就会导致整个事务失败,没有完善的容错机制。

二、补偿事务(TCC) 严选,阿里,蚂蚁金服。

  • try Confirm Cancel

TCC 其实就是采用的补偿机制,其核心思想是:针对每个操作,都要注册一个与其对应的确认和补偿(撤销)操作。它分为三个阶段:

  • Try 阶段主要是对业务系统做检测及资源预留
  • Confirm 阶段主要是对业务系统做确认提交,Try阶段执行成功并开始执行 Confirm阶段时,默认 - - - Confirm阶段是不会出错的。即:只要Try成功,Confirm一定成功。
  • Cancel 阶段主要是在业务执行错误,需要回滚的状态下执行的业务取消,预留资源释放。

举个例子,假入 Bob 要向 Smith 转账,思路大概是: 我们有一个本地方法,里面依次调用
1:首先在 Try 阶段,要先调用远程接口把 Smith 和 Bob 的钱给冻结起来。
2:在 Confirm 阶段,执行远程调用的转账的操作,转账成功进行解冻。
3:如果第2步执行成功,那么转账成功,如果第二步执行失败,则调用远程冻结接口对应的解冻方法 (Cancel)。

优点: 跟2PC比起来,实现以及流程相对简单了一些,但数据的一致性比2PC也要差一些
缺点: 缺点还是比较明显的,在2,3步中都有可能失败。TCC属于应用层的一种补偿方式,所以需要程序员在实现的时候多写很多补偿的代码,在一些场景中,一些业务流程可能用TCC不太好定义及处理。

三、本地消息表(异步确保)比如:支付宝、微信支付主动查询支付状态,对账单的形式

本地消息表与业务数据表处于同一个数据库中,这样就能利用本地事务来保证在对这两个表的操作满足事务特性,并且使用了消息队列来保证最终一致性。

  • 在分布式事务操作的一方完成写业务数据的操作之后向本地消息表发送一个消息,本地事务能保证这个消息一定会被写入本地消息表中。
  • 之后将本地消息表中的消息转发到 Kafka 等消息队列中,如果转发成功则将消息从本地消息表中删除,否则继续重新转发。
  • 在分布式事务操作的另一方从消息队列中读取一个消息,并执行消息中的操作。

img

优点: 一种非常经典的实现,避免了分布式事务,实现了最终一致性。
缺点: 消息表会耦合到业务系统中,如果没有封装好的解决方案,会有很多杂活需要处理。

  • 如果写入的时候 出现异常,就会同时进行广播。广播后发现没有问题,才进行提交。
  • 但凡出现一点问题,就会失败。

四、MQ 事务消息 异步场景,通用性较强,拓展性较高。

有一些第三方的MQ是支持事务消息的,比如RocketMQ,他们支持事务消息的方式也是类似于采用的二阶段提交,但是市面上一些主流的MQ都是不支持事务消息的,比如 Kafka 不支持。
以阿里的 RabbitMQ 中间件为例,其思路大致为:

  • 第一阶段Prepared消息,会拿到消息的地址。 第二阶段执行本地事务,第三阶段通过第一阶段拿到的地址去访问消息,并修改状态。

  • 也就是说在业务方法内要想消息队列提交两次请求,一次发送消息和一次确认消息。如果确认消息发送失败了RabbitMQ会定期扫描消息集群中的事务消息,这时候发现了Prepared消息,它会向消息发送者确认,所以生产方需要实现一个check接口,RabbitMQ会根据发送端设置的策略来决定是回滚还是继续发送确认消息。这样就保证了消息发送与本地事务同时成功或同时失败。

img

优点: 实现了最终一致性,不需要依赖本地数据库事务。
缺点: 实现难度大,主流MQ不支持,RocketMQ事务消息部分代码也未开源。

五、总结

通过本文我们总结并对比了几种分布式分解方案的优缺点,分布式事务本身是一个技术难题,是没有一种完美的方案应对所有场景的,具体还是要根据业务场景去抉择吧。阿里RocketMQ去实现的分布式事务,现在也有除了很多分布式事务的协调器,比如LCN等,大家可以多去尝试。

02、具体实现

  • 就是项目A 开启事务,http 调用项目B (执行超时了,比如睡眠3秒)
    • 项目A 因为有事务,不会插入数据。项目B 睡眠后,插入数据,数据不一致。

MQ高可用配置

** 我的实现

spring:
  rabbitmq:
    #    host: 192.168.44.146
    #    port: 5672
    virtual-host: /
    username: admin
    password: admin
    addresses: 192.168.44.146:5672,192.168.44.146:5673 #集群使用这种配置。
    listener:
      simple: #简单模式
        acknowledge-mode: manual #手动ack
        retry:
          enabled: true #开启重试
          max-attempts: 10 #最大重试次数
          initial-interval: 2000ms #重试间隔的时间
    publisher-returns: true
    publisher-confirm-type: correlated #有相互关系的。投递消息的 确认机制,一定要配置
    @Transactional(rollbackFor = Exception.class)
    public void createOrder() throws Exception {

        SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
        factory.setConnectTimeout(3000);//连接 > 3秒
        factory.setReadTimeout(2000);//处理 > 2秒

        String url = "http://localhost:8000/dispatch/order?orderId=" + "123123";

        RestTemplate r = new RestTemplate(factory);
        String result = r.getForObject(url, String.class);

        System.out.println(result);
        //如果成功
        if ("success".equals(result)) {
        } else {
            throw new Exception("运单接口调用失败");
        }
    
    }
可靠生产的问题
spring:
  rabbitmq:
# 	publisher-returns: true 这个参数不配置也行。
    publisher-confirm-type: correlated #有相互关系的。投递消息的 确认机制,一定要配置
  • 为 rabbitMq增加 回调的方法。注意 回执方法,需要项目在运行。
    • 使用 junit 可以发送后,在睡几秒,等回执了 项目才会关闭。
    @Resource
    private RabbitTemplate rabbitTemplate;

	//Java中该注解的说明:e PostConstruct该注解被用来修饰一个非静态的void ()方法。
    // 被@PostConstruct修饰的方法会在服务器加载Servlet的时候运行,
    // 并且只会被服务器执行一次。PostConstruct在构造函数之后执行,init ()方法之前执行。
    @PostConstruct //其实是java自己的注解
    public void regCallback() {
        //其实就是给 rabbitTemplate 扩展了方法。
        //消息发送成功以后,给子生产者的消息回执,来确保生产者的可靠性
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
            @Override
            public void confirm(CorrelationData correlationData, boolean ack, String cause) {
                System.out.println("cause" + cause);

                String orderId = correlationData.getId();
                System.out.println("数据的ID" + orderId);

                //如果没有ack成功
                if (!ack) {
                    //应答失败,也可以存到 其他地方
                    System.out.println("mq应答失败");
                    return;
                }

                System.out.println("应答成功了");

                //应答成功,更新数据库,状态改为1 (已发送到Mq)
            }
        });
    }
  • 投递时增加一个参数
        //这里要插入 冗余表,状态为0,未投递成功。上面回调的方法 会改为1。
		//另一个线程 定时任务,定时投递 为0的消息。
		
		//教程使用的是:fanout模式,不需要指定 routing key。 order.queue order_fanout_exchange
        rabbitTemplate.convertAndSend("direct_order_exchange"
                , "sms", "order的json数据" + content,
                new CorrelationData(content));


@EnableScheduling
public class TaskService {

    @Scheduled(cron = "秒 分 时 日 月 年 周")
    public void sendMessage() {
        //消息为0的状态的消息,重新发送到Mq
    }
}
  • 测试,投递成功后,就会调用回调的方法(只要投递到交换器成功就会回调,不管 交换机有没有投递到队列)
可靠消费的问题
  • 使用 手动Ack的确认机制
  • 默认消费者出错,会触发重试,在错,在重试。引发死循环。
    • 解决消息重试的集中方案:
      • 控制重发的次数 + 死信队列
        • 如果不加死信队列,重试次数到了,会扔了。
      • try+catch+手动ack
      • try+catch+手动ack +死信队列处理+人工干预
spring:
  rabbitmq:
    #    host: 192.168.44.146
    #    port: 5672
    virtual-host: /
    username: admin
    password: admin
    addresses: 192.168.44.146:5672,192.168.44.146:5673
    listener:
      simple:
        #简单模式。老师是 fanout,用了 simple。我这里测试用direct 不行的。
        #手动ack。应答模式一定要改。让程序去控制Q的消息的重发和删除和转移。默认为null,是自动ack
        acknowledge-mode: manual
        retry:
          enabled: true #开启重试
          max-attempts: 3 #最大重试次数。默认为3
          initial-interval: 2000ms #重试间隔的时间
    #    publisher-returns: true
    publisher-confirm-type: correlated #有相互关系的
  • 注意配置了 手动ack,

    •   channel.basicNack(tag, false, true); 
      //最后一个参数true,会一直重发。重试次数机制,会失效。
      //作者也是:如果 重试次数 > 3(配置的),就设置为false,不重试了(扔死信队列)
      
      //想要重试次数生效,就不要 加try catch。也别加 ack nack 就让他报错。
      
      //如果 开启了手动ack,方法执行正常了,而没有 ack或nack,消息不会真实消费,mq服务还有(但这个消息消费者 会消费一次)。
      
      //使用 CorrelationData correlationData,这个对象也可以。
      //有个属性,第一次为 false,出错以后,以后都是 true
      
  • 手动ack

@Component
public class Consumer {

    private int count = 1;

    //@RabbitHandler
    @RabbitListener(queues = {"sms.direct.queue"}) //直接这样写,也可以
    public void receiveMessage(String message, Channel channel,
                               @Header(AmqpHeaders.DELIVERY_TAG) long tag) throws IOException {
        System.out.println("email direct模式收到了消息" + message + "===" + count++);
        try {
            int i = 1 / 0;
            System.out.println(i);
            //手动ack已经正常消费
            channel.basicAck(tag, false);
        } catch (Exception e) {
            //如果出现异常的情况下,根据实际的情况去进行重发
            //重发一次后.丢失.还是日记.存库根据自己的业务场景去决定
            //参数1:消息的tag参数 2: false多条处理参数 3: requeue重发
            // false不会重发,会把消息打入到死信队列
            // true 的会会死循环的重发,建议如果使用true的话,不加try/catch否则就会造成死循环
            channel.basicNack(tag, false, false);//单条 不重发。直接扔掉(进入死信队列)
        }
    }

    /*ordermsg 应该是老师的 Order类的一个字段,转为了json了
    @RabbitListener(queues = {"sms.direct.queue"})
    public void receiveMessage(String ordermsg,
                               CorrelationData correlationData) throws Exception {
        System.out.println("相互关系ID为:" + correlationData.getId());
        System.out.println("email direct模式收到了消息" + ordermsg);
    }*/

}
绑定死信队列
@Configuration
public class DeadRabbitConfig {

    @Bean
    public Queue deadQueue() {
        Map<String, Object> args = new HashMap<>();
        return new Queue("dead.direct.queue", true, false, false, null);
    }

    @Bean
    public DirectExchange deadDirectExchange() {
        return new DirectExchange("dead_direct_exchange", true, false);
    }

    @Bean
    public Binding deadBinding() {
        return BindingBuilder.bind(deadQueue()).to(deadDirectExchange()).with("dead");
    }
}


    @Bean
    public Queue smsQueue() {
        Map<String, Object> args = new HashMap<>();

        args.put("x-dead-letter-exchange", "dead_direct_exchange");
        args.put("x-dead-letter-routing-key", "dead");
        return new Queue("sms.direct.queue", true, false, false, args);
    }
  • 测试,进入 Nack ,最后一个参数为 false ,当前消息 被转移到 死信队列。
  channel.basicNack(tag, false, false);//单条 不重发。直接扔掉(死信队列)
  • 死信队列,逻辑 和 队列处理的一样。保存运单等
    • (注意 幂等性,因为这是 死信队列,可能前面已经处理过了,比如 oderId作为主键)。
      • 幂等性 也可以用 saveUpdate,如果有就更新。
        • 使用 分布式锁,先查询一下,如果 >0,就不插入。
  • 如果死信队列报错,那就人工干预,发短信预警。把消息转移到数据库。
    • 同样是 Nack 移除这个消息(就是直接扔了,因为已经保存过了)

分布式事务的完整架构图

img

注释说明
  • 生产者,消费者,或 消息队列出错。

  • 消息队列出错:

    • 消息冗余表,如果未投递成功,有状态标识(一直没有投递)
    • 使用定时器重新发送Mq,如果重试次数>2,可能是消息有问题??人共排查。
    • 如果投递成功,Mq会有 确认机制(Confirm Listerner 确认监听)

美团外卖架构:
img

2-01、系统与系统之间的分布式事务问题

img

2-02、系统间调用过程中事务回滚问题

package com.xuexiangban.rabbitmq.service;
import com.xuexiangban.rabbitmq.dao.OrderDataBaseService;
import com.xuexiangban.rabbitmq.pojo.Order;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.client.RestTemplate;
@Service
public class OrderService {
    @Autowired
    private OrderDataBaseService orderDataBaseService;
    // 创建订单
    @Transactional(rollbackFor = Exception.class) // 订单创建整个方法添加事务
    public void createOrder(Order orderInfo) throws Exception {
        // 1: 订单信息--插入丁订单系统,订单数据库事务
        orderDataBaseService.saveOrder(orderInfo);
        // 2:通過Http接口发送订单信息到运单系统
        String result = dispatchHttpApi(orderInfo.getOrderId());
        if(!"success".equals(result)) {
            throw new Exception("订单创建失败,原因是运单接口调用失败!");
        }
    }
    /**
     *  模拟http请求接口发送,运单系统,将订单号传过去 springcloud
     * @return
     */
    private String dispatchHttpApi(String orderId) {
        SimpleClientHttpRequestFactory factory  = new SimpleClientHttpRequestFactory();
        // 链接超时 > 3秒
        factory.setConnectTimeout(3000);
        // 处理超时 > 2秒
        factory.setReadTimeout(2000);
        // 发送http请求
        String url = "http://localhost:9000/dispatch/order?orderId="+orderId;
        RestTemplate restTemplate = new RestTemplate(factory);//异常
        String result = restTemplate.getForObject(url, String.class);
        return result;
    }
}

2-03、基于MQ的分布式事务整体设计思路

img

2-04、* 基于MQ的分布式事务消息的可靠生产问题

  • 就是交换机 有确认机制,会回调方法。
  • 如果没有回调,状态不对。定时器会重发。

img

如果这个时候MQ服务器出现了异常和故障,那么消息是无法获取到回执信息。怎么解决呢?r

2-04-01、基于MQ的分布式事务消息的可靠生产问题-定时重发

img

2-06、基于MQ的分布式事务消息的可靠消费

img

2-07、基于MQ的分布式事务消息的消息重发

img

2-08、基于MQ的分布式事务消息的死信队列消息转移 + 人工处理

img

如果死信队列报错就进行人工处理

img

2-09、基于MQ的分布式事务消息的死信队列消息重试注意事项

2-10、基于MQ的分布式事务消息的定式重发

03、总结

基于MQ的分布式事务解决方案优点:

1、通用性强
2、拓展方便
3、耦合度低,方案也比较成熟

基于MQ的分布式事务解决方案缺点:

1、基于消息中间件,只适合异步场景
2、消息会延迟处理,需要业务上能够容忍

建议

1、尽量去避免分布式事务
2、尽量将非核心业务做成异步

面试

面试题:1、Rabbitmq 为什么需要信道,为什么不是TCP直接通信

1、TCP的创建和销毁,开销大,创建要三次握手,销毁要4次分手。

2、如果不用信道,那应用程序就会TCP连接到Rabbit服务器,高峰时每秒成千上万连接就会造成资源的巨大浪费,而且==底层操作系统每秒处理tcp连接数也是有限制的,==必定造成性能瓶颈。

3、信道的原理是一条线程一条信道,多条线程多条信道同用一条TCP连接,一条TCP连接可以容纳无限的信道,即使每秒成千上万的请求也不会成为性能瓶颈。

2:queue队列到底在消费者创建还是生产者创建?

1: 一般建议是在rabbitmq操作面板创建。这是一种稳妥的做法。
2:按照常理来说,确实应该消费者这边创建是最好,消息的消费是在这边。这样你承受一个后果,可能我生产在生产消息可能会丢失消息。
3:在生产者创建队列也是可以,这样稳妥的方法,消息是不会出现丢失。
4:如果你生产者和消费都创建的队列,谁先启动谁先创建,后面启动就覆盖前面的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值