消息队列与分布式事务

分布式事务的最终解决方案:消息队列

 

消息服务中的重要概念:消息代理和目的地

当消息发送者发送消息以后,将由消息代理接管,消息代理保证消息传递到指定目的地

消息队列的两种形式的目的地:a:队列(点对点通信) b:主题(发布/订阅消息通信)

 

消息队列的好处:

1)异步处理

2)应用解耦

3)流量削峰

 

发布订阅式:  发送者发送消息到主题,多个接收者(订阅者)监听(订阅)这个主题,那么就会在消息到达时同时接收到消息

JMS:      不能跨语言,不能跨平台

AMQP:高级消息队列协议,兼容JMS         网络线级协议    跨平台跨语言       

 

RabbitMQ的核心概念

 

接下来进行rabbitMQ的安装,依然是docker(不得不说docker爽爆了哇)

1、下载镜像文件

 

docker pull rabbitmq:management

 

2、创建实例并启动

 

docker run -d --name rabbitmq --publish 5671:5671 \

--publish 5672:5672 --publish 4369:4369 --publish 25672:25672 --publish 15671:15671 --publish 15672:15672 \

rabbitmq:management

 

注:

4369 -- erlang发现口

5672 --client端通信口

 

15672 -- 管理界面ui端口

25672 -- server间内部通信口

3、测试

在web浏览器中输入地址:http://虚拟机ip:15672/

输入默认账号: guest 密码: guest

 

安装好界面就是这个样子的

接下来写个rabbitMQ的demo尝试用一下

首先是引入依赖:

然后编写配置文件

 

最后开启该功能

 

测试一下消息队列是否成功

注意,只要是想通过网络通信,那么对象都要实现序列化,converAndSend的第一个参数是交换机的名字,第二个参数是路由键,第三个是你要发送的信息

去消息队列中看到了

但是因为该消息是用java序列化的,不能跨平台,也就是加入是php进来拿该消息,是拿不到的,所以我们得到消息转成json,

rabbitTemplate有自带的方法能够传送的时候给你自己转成json

然后发现消息队列中就是json格式的

当然,为了不去每次发送消息都这样写,可以直接在注入一个bean。

 

后台想要接收消息

只要写一个Service,声明监听的交换机就行,注意这里传过来的是Message,如果你明确传过来的对象是声明,可以声明第二个参数,那么接收者接收到到消息后就会自动转成你要的第二个参数类型的对象,并且还可以声明第三个参数Channel,即通道,一个服务和一个交换机建立起一条连接(connection),一条连接里有很多通道(channel),channel还能够拒绝消息,让其他接收者接受

Message,对象类型,Channel三个可以任意选择参数,不限制数量,接收到消息后就可以拿去使用了

 

AmqpAdmin可以创建队列,交换机等

首先是AmqpAdmin创建队列:

可以看到已经有了该队列了

 

交换机的创建也类似

 

主要就是用了amqpAdmin来完成原来在可视化界面里完成过工作

 

其实可以自己写一个配置类,然后在配置里面创建好队列,交换机,绑定等等也可以

比如这样就是创建了一个heolloQueue的队列(如果没有才会自动创建,有的话不会覆盖)

 

rabbitMQ的手动Ack(也就是消息队列的确认机制)

每个消息都要被ack。我们可以显示的在程序中去ack,也可以自动的ack。如果数据没有被ack,那么RabbitMQ Server会把这个信息发送到下一个消费者,如果app有bug,忘记了ack,那么RabbitMQServer不会再发送消息给它,因为Server认为这个消费者正在处理该消息

ack的机制可以起到限流的作用:在消费者处理完成数据后发送ack,甚至在额外的延时后发送ack,将有效的实现消费者的负载均衡

 

接下来试一下ack

通过模仿订单的提交来试,首先是创建该订单,由于此时交换机,队列,绑定都没有,我们去配置类中创建好

接下来就可以写service方法了

最后通过Controller调用该service

 接收到该消息

 

消息确认机制:

1)如果消息收到了,在处理期间出现运行时异常,默认消息没有被正确处理。

                                   消息状态unack:队列中感知有一个unack的消息

                                   unack的消息队列会再次尝试把这个消息发送给其他消费者

 

2)不要让它认为是ack还是unack,我们应该手动ack,手动确认机制。

                                  否则,可能会出现收到消息,库存扣了,但是出现未知异常导致消息又重新入队,这个消息不断重复地发送过来。解决方案:手动ack       接口幂等性(在本地维护一个日志表,记录哪些已经减过库存,再来同样的消息就不减了)

 

以后都采用手动ack方式解决

1)开启手动ack

2)处理消息

消息处理成功就是channel.basicAck(),处理失败的话就是channel.basicNack()或channel.reject()

及时没回复消息也不会阻塞,在basicNack中可以设置requeue,也就是是否重新入队

 

消息的TTL

消息的TTL就是消息的存货时间。RabbitMQ可以对队列和消息分别设置TTL。对队列设置就是队列没有消费者连着的保留时间,也可以队每一个单独的消息做单独是设置。超过了这个时间,我们认为这个消息就死了,称之为死。如果队列设置了,消息也设置了,那么就会去小的。所以一个消息如果被路由到不同的队列中,这个消息的死亡时间有可能不一样。这里单讲单个消息的TTL,因为它才是实现延迟任务的关键。可以通过设置消息的expiration字段或者x-message-ttl属性来设置时间,两者是一样的效果

 

死信路由

一个消息在满足如下条件下,会进入死信路由(不是队列)

1)一个消息被consumer拒收了,并且reject方法的参数里requeue是false,也就是说不会被再次放进队列里面

2)上面的TTL时间到了,消息过期

3)队列的长度限制满了。排在前面的消息会被丢弃或者扔到死信路由上

 

 

 

我们既可以控制消息在一段时间后变成死信,又可以控制变成死信的消息被路由到某个指定的交换机,结合二者,可以实现延时队列

 

接下来尝试一下延时队列

先配置好延迟队列的基本信息

 

首先将消息发送给延时队列,还有对应的路由键也发过来

上面我们创建好了延迟消息队列的交换机,队列,绑定,特别要注意的是队列的相关属性的设定,必须是这几个

前面的参数名,后面是参数值

然后消息过期了(也就是死了后)会发送到另外一个交换机,接下来就是到达另一个交换机里面开始进行处理

这里处理的就是user.order.queue的消息,我们用死信交换机完成了定时关闭订单的操作

 

 

这就是死信交换机的过程

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值