目的:  

消息如何保证100%的投递

幂等性概念

Confirm确认消息

Return返回消息

自定义消费者

前言:

  想必知道消息中间件RabbitMQ的小伙伴,对于引入中间件的好处可以起到抗高并发,削峰,业务解耦的作用并不陌生。

  康康简单流程图了解一下。详情了解RabbitMQ可移步:https://www.cnblogs.com/huangting/p/11989597.html

 注意:一般MQ中间件为了提高系统的吞吐量会把消息保存在内存中,如果不作其他处理,MQ服务器一旦宕机,消息将全部丢失;

  这样会造成巨大影响,在业务流程上是不可以的。

以下就是我们去解决所使用的方法


消息如何保证100%的投递

  什么是生产端的可靠性投递?

    • 保障消息的成功发出
    • 保障MQ节点的成功接收
    • 发送端收到MQ节点(Broker)确认应答
    • 完善的消息进行补偿机制(如网络问题没有返回确认应答

  BAT/TMD互联网大厂的解决方案:

    • 消息落库,对消息状态进行打标
    • 消息的延迟投递,做二次确认,回调检查


幂等性概念

  首先,我们了解一下什么叫幂等:

      在分布式应用中,幂等是非常重要的,也就是相同条件下对一个业务的操作,不管操作多少次,结果都是一样。

   其次,什么要有幂等这种场景?

      基本上在逻辑涉及范围比较大系统里边,都有部署分布式,就如同下面其中订单和库存可以说是两种独立的个体。

    正因为是分布式,那么很有可能订单服务在调用库存时因为网络差等外因导致调用失败,但是库存服务其实是调用成功的只是在返回结果集时发生了异常,其实按照一般思维就会在调用一次库存不就可以了吗?恰恰是这样问题就出现了,订单没有接收到返回结果,但库存已经减去一个对应物品数量,如果在调用一次又减去一次,那么很明显业务上是错误的。

  

所以这个时候就要用到幂等性,无论库存服务在相同条件下调用几次,处理结果都一样。这样才能保证业务的紧密。

 业界主流的幂等性操作

  • 乐观锁方案

  比如我们执行一条更新库存的SQL语句

 Update t_repository set count = count -,version = version +  where version = 

  根据version版本,也就是在操作库存前先获取当前商品的version版本号,然后操作的时候带上此version号。

 我们梳理下,我们第一次操作库存时,得到version为1,调用库存服务version变成了;

 但返回给订单服务出现了问题,订单服务又一次发起调用库存服务,当订单服务传如的version还是1,再执行上面的sql语句时,就不会执行;

 因为version已经变为2了,where条件就不成立。这样就保证了不管调用几次,只会真正的处理一次

  • 唯一ID+指纹码机制,利用数据库主键去重

    原理就是利用数据库主键去重,业务完成后插入主键标识

Select count() from T_order where ID=唯一ID+指纹码
  1. 唯一ID就是业务表的唯一的主键,如商品ID
  2. 指纹码就是为了区别每次正常操作的码,每次操作时生成指纹码;可以用时间戳+业务编号的方式。

好处:实现简单

坏处:高并发下数据库瓶颈

解决方案:根据ID进行分库分表进行算法路由

  • 利用Redis的原子性去实现

    • 我们是否需要把业务结果进行数据落库,如果落库,关键解决的问题时数据库和redis操作如何做到原子性?

        意思就是库存减1了,但redis进行操作完成标记时,失败了怎么办?也就是一定要保证落库和redis 要么一起成功,要么一起失败  

    • 如果不进行落库,那么都存储到缓存中,如何设置定时同步策略?

      意思就是库存减1,不落库,直接先操作redis操作完成标记,然后由另外的同步服务进行库存落库,这个就是增加了系统复杂性,而且同步策略如何设置


Confirm确认消息

    Confirm消息确认机制含义:

消息的确认,是指订单服务投递消息后,如果物流服务收到消息,则会给我们订单服务 一个应答。

中间件进行接收应答,用来确定这条消息是否正常的发送到物流服务,这种方式也是消息的可靠性投递的核心保障

如何实现Confirm确认消息?

  1. 在Channel上开启确认模式:channel.confirmSelect()
  2. 在channel上添加监听:addConfirmListener,监听成功和失败的返回结果,根据具体的结果对消息进行重新发送、或记录日志等后续处理!

  消费端代码

package com.javaxh.rabbitmqapi.confirm;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.QueueingConsumer;
public class Consumer {
public static void main(String[] args) throws Exception {
//1 创建ConnectionFactory
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.239.131");
connectionFactory.setPort();
connectionFactory.setVirtualHost("/"); //2 获取C onnection
Connection connection = connectionFactory.newConnection(); //3 通过Connection创建一个新的Channel
Channel channel = connection.createChannel(); String exchangeName = "test_confirm_exchange";
String routingKey = "confirm.#";
String queueName = "test_confirm_queue"; //4 声明交换机和队列 然后进行绑定设置, 最后制定路由Key
channel.exchangeDeclare(exchangeName, "topic", true);
channel.queueDeclare(queueName, true, false, false, null);
channel.queueBind(queueName, exchangeName, routingKey); //5 创建消费者
QueueingConsumer queueingConsumer = new QueueingConsumer(channel);
channel.basicConsume(queueName, true, queueingConsumer); while(true){
QueueingConsumer.Delivery delivery = queueingConsumer.nextDelivery();
String msg = new String(delivery.getBody()); System.err.println("消费端: " + msg);
} }
}

服务提供方代码

package com.javaxh.rabbitmqapi.confirm;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.ConfirmListener;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException; public class Producer {
public static void main(String[] args) throws Exception {
//1 创建ConnectionFactory
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.239.131");
connectionFactory.setPort();
connectionFactory.setVirtualHost("/"); //2 获取C onnection
Connection connection = connectionFactory.newConnection(); //3 通过Connection创建一个新的Channel
Channel channel = connection.createChannel(); //4 指定我们的消息投递模式: 消息的确认模式
channel.confirmSelect();
String exchangeName = "test_confirm_exchange";
String routingKey = "confirm.save"; //5 发送一条消息
String msg = "Hello RabbitMQ Send confirm message!";
channel.basicPublish(exchangeName, routingKey, null, msg.getBytes()); //6 添加一个确认监听
channel.addConfirmListener(new ConfirmListener() {
@Override
public void handleNack(long deliveryTag, boolean multiple) throws IOException {
System.err.println("-------no ack!-----------");
} @Override
public void handleAck(long deliveryTag, boolean multiple) throws IOException {
System.err.println("-------ack!-----------");
}
});
}
}

运行结果:

消费端:

服务提供方:


Return返回消息

  Return Listener用于处理一些不可路由的消息。

正常情况:我们的消息生产者,通过指定一个Exchange和RoutingKey,把消息送达到某一个队列中去,然后我们的消费者监听队列,进行消费处理操作!

异常情况:在某些情况下,如果我们在发送消息的时候,当前的Exchange不存在或者指定的路由key路由不到,这个时候如果我们需要监听这种不可达的消息,就需要使用Return Listener!

 

在基础API中有一个关键的配置项

Mandatory:如果为true,则监听器会接收到路由不可达的消息,然后进行后续处理,如果为false,那么Broker端自动删除该消息!

消费端代码

package com.javaxh.rabbitmqapi.returnlistener;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.QueueingConsumer;
public class Consumer {
public static void main(String[] args) throws Exception {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.239.131");
connectionFactory.setPort();
connectionFactory.setVirtualHost("/"); Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel(); String exchangeName = "test_return_exchange";
String routingKey = "return.#";
String queueName = "test_return_queue"; channel.exchangeDeclare(exchangeName, "topic", true, false, null);
channel.queueDeclare(queueName, true, false, false, null);
channel.queueBind(queueName, exchangeName, routingKey); QueueingConsumer queueingConsumer = new QueueingConsumer(channel); channel.basicConsume(queueName, true, queueingConsumer); while(true){
QueueingConsumer.Delivery delivery = queueingConsumer.nextDelivery();
String msg = new String(delivery.getBody());
System.err.println("消费者: " + msg);
} }
}

生产端代码:

package com.javaxh.rabbitmqapi.returnlistener;
import com.rabbitmq.client.*; import java.io.IOException; public class Producer {
public static void main(String[] args) throws Exception {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.239.131");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/"); Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel(); String exchange = "test_return_exchange";
String routingKey = "return.save";
String routingKeyError = "abc.save"; String msg = "Hello RabbitMQ Return Message"; channel.addReturnListener(new ReturnListener() {
@Override
public void handleReturn(int replyCode, String replyText, String exchange,
String routingKey, AMQP.BasicProperties properties, byte[] body) throws IOException { System.err.println("---------handle return----------");
System.err.println("replyCode: " + replyCode);
System.err.println("replyText: " + replyText);
System.err.println("exchange: " + exchange);
System.err.println("routingKey: " + routingKey);
System.err.println("properties: " + properties);
System.err.println("body: " + new String(body));
}
}); //消息投递成功,会被消费者所消费
// channel.basicPublish(exchange, routingKey, true, null, msg.getBytes());
//消息不可达,将触发ReturnListener
channel.basicPublish(exchange, routingKeyError, true, null, msg.getBytes());
}
}

效果:


自定义消费者

我们一般就是在代码中编写while循环,进行consumer.nextDelivery方法进行获取下一条消息,然后进行消费处理!

但是我们使用自定义的Consumer更加的方便,解耦性更加的强,也是实际工作中最常用的使用方式!

自定义消费端代码

package com.javaxh.rabbitmqapi.consumer;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope; import java.io.IOException; public class MyConsumer extends DefaultConsumer { public MyConsumer(Channel channel) {
super(channel);
} @Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.err.println("-----------consume message----------");
System.err.println("consumerTag: " + consumerTag);
System.err.println("envelope: " + envelope);
System.err.println("properties: " + properties);
System.err.println("body: " + new String(body));
}
}

消费端调用:

package com.javaxh.rabbitmqapi.consumer;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory; public class Consumer {
public static void main(String[] args) throws Exception {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.239.131");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/"); Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel(); String exchangeName = "test_consumer_exchange";
String routingKey = "consumer.#";
String queueName = "test_consumer_queue"; channel.exchangeDeclare(exchangeName, "topic", true, false, null);
channel.queueDeclare(queueName, true, false, false, null);
channel.queueBind(queueName, exchangeName, routingKey); channel.basicConsume(queueName, true, new MyConsumer(channel)); }
}

生产端调用:

package com.javaxh.rabbitmqapi.consumer;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory; public class Producer {
public static void main(String[] args) throws Exception {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.239.131");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/"); Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel(); String exchange = "test_consumer_exchange";
String routingKey = "consumer.save"; String msg = "Hello RabbitMQ Consumer Message"; for(int i =0; i<5; i ++){
channel.basicPublish(exchange, routingKey, true, null, msg.getBytes());
} }
}

效果:

谢谢观看!!!

RabbitMQ之消息模式的更多相关文章

  1. RabbitMQ之消息模式(下)

    目的: RabbitMQ之消息模式(上):https://www.cnblogs.com/huangting/p/11994539.html 消费端限流 消息的ACK与重回队列 TTL消息 死信队列 ...

  2. RabbitMQ之消息模式简单易懂,超详细分享~~~

    前言 上一篇对RabbitMQ的流程和相关的理论进行初步的概述,如果小伙伴之前对消息队列不是很了解,那么在看理论时会有些困惑,这里以消息模式为切入点,结合理论细节和代码实践的方式一起来学习. 正文 常 ...

  3. RabbitMQ之消息模式2

    消费端限流 什么是消费端的限流? 假设一个场景,首先,我们RabbitMQ服务器有上万条未处理的消息,我们随便打开一个消费者客户端,会出现下面情况: 巨量的消息瞬间全部推送过来,但是我们单个客户端无法 ...

  4. RabbitMQ之消息模式1

    消息100%的投递 消息如何保障100%的投递成功? 什么是生产端的可靠性投递? 保障消息的成功发出 保障MQ节点的成功接收 发送端收到MQ节点(Broker)确认应答 完善的消息进行补偿机制 BAT ...

  5. RabbitMQ入门-消息订阅模式

    消息派发 上篇<RabbitMQ入门-消息派发那些事儿>发布之后,收了不少反馈,其中问的最多的还是有关消息确认以及超时等场景的处理. 楼主,有遇到消费者后台进程不在,但consumer连接 ...

  6. RabbitMQ 消息模式

    消息模式实例 视频教程:https://ke.qq.com/course/304104 编写代码前,最好先添加好用户并设置virtual hosts 一.简单模式 1.导入jar包 <depen ...

  7. twsited(5)--不同模块用rabbitmq传递消息

    上一章,我们讲到,用redis共享数据,以及用redis中的队列来实现一个简单的消息传递.其实在真实的过程中,不应该用redis来传递,最好用专业的消息队列,我们python中,用到最广泛的就是rab ...

  8. RabbitMQ入门-消息派发那些事儿

    在上篇<RabbitMQ-高效的Work模式>中,我们了解了Work模型,该模型包括一个生产者,一个消息队列和多个消费者. 我们已经通过实例看出消息队列中的消息是如何被一个或者多个消费者消 ...

  9. rabbitmq(中间消息代理)在python中的使用

    在之前的有关线程,进程的博客中,我们介绍了它们各自在同一个程序中的通信方法.但是不同程序,甚至不同编程语言所写的应用软件之间的通信,以前所介绍的线程.进程队列便不再适用了:此种情况便只能使用socke ...

随机推荐

  1. [golang][gui]Hands On GUI Application Development in Go【在Go中动手进行GUI应用程序开发】读书笔记03-拒交“智商税”,解密“GUI”运行之道

    和老外的原文好像没多大联系了,哈哈哈,反正是读书笔记,下面的内容也是我读此书中的历程,也写进来吧.不过说实话,这框架的作者还挺对我脾气的,哈哈哈. 拒交“智商税”,解密“GUI”运行之道 我很忙 项目 ...

  2. 内核过DSE驱动签名验证.

    一丶简介 现在的驱动,必须都有签名才能加载.那么如何加载无签名的驱动模块那. 下面可以说下方法.但是挺尴尬的是,代码必须在驱动中编写.所以就形成了 你必须一个驱动带有一个签名加载进去.执行你的代码.p ...

  3. mybatis之批量查询

    关于MyBatis批量更新和添加,参考我的如下文章即可:MyBatis的批量更新实例 MyBatis的批量添加实例 另外不管是批量的新增.删除.修改.查询也好,还是单个新增.删除.修改查询也罢.都会用 ...

  4. 2018-2019-2 20175211 实验四《Android程序设计》实验报告

    目录 一.实验内容及步骤 1.Android Studio的安装测试 2.Activity测试 3.UI测试 4.布局测试 5.事件处理测试 二.问题及解决方法 三.代码托管 四.实验心得体会 一.实 ...

  5. [技术博客]使用PanResponder实现响应左右滑动手势

    在实现用户左右滑动完成不同操作时,使用react-native的官方API--PanResponder响应用户手势操作. PanResponder介绍 PanResponder中文文档 PanResp ...

  6. portaudio使用笔记《转》

    原文链接:https://blog.csdn.net/gg_simida/article/details/77185755 介绍 PortAudio是一个免费.跨平台.开源的音频I/O库.看到I/O可 ...

  7. 使用Sabaki和Leela Zero配置AI围棋对弈环境

    求 李昌镐儿童围棋课堂 的pdf. 一.下载Sabaki和Leela Zero最新版本 二.安装Sabaki 三.安装leela zero 四.Sabaki配置leela zero引擎 五.Sabak ...

  8. 【Python】解析Python中类的使用

    目录结构: contents structure [-] 类的基本使用 专有方法 继承 单重继承 多重继承 砖石继承 1.类的基本使用 下面是类使用的一个简单案例, class person: &qu ...

  9. 在 Windows 服务中托管 ASP.NET Core

    众所周知,ASP.NET Core采用了和传统ASP.NET不同的托管和HTTP处理方式,即把服务器和托管环境完全解耦.ASP.NET Core内置了两个HTTP服务器实现,一个是基于libuv实现的 ...

  10. libFFM 与 python-libffm 安装遇到的一系列问题-解决方案

    mac os x 环境下 libFFM 与 python-libffm的安装问题 Gcc 版本问题 : brew Clang 版本问题 : brew upgrade llvm 编译参数: 多核运行 o ...