一、springboot整合rabbitmq

  1. 我们需要新建两个工程,一个作为生产者,另一个作为消费者。在pom.xml中添加amqp依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
  1. 在application.yml文件中添加rabbitmq的相关信息:
spring:
rabbitmq:
# 连接地址
host: 127.0.0.1
# 端口
port: 5672
# 登录账号
username: guest
# 登录密码
password: guest
# 虚拟主机
virtual-host: /
  1. 在生产者工程中新建配置项rabbitmqConfig.java,申明名称为”byte-zb“直连交换机和队列,使用”byte-zb“的routing-key将队列和交换机绑定,代码如下:
@Configuration
public class RabbitConfig { public static final String QUEUE_NAME = "byte-zb"; public static final String EXCHANGE_NAME = "byte-zb"; public static final String ROUTING_KEY = "byte-zb"; // 队列申明
@Bean
public Queue queue(){
return new Queue(QUEUE_NAME);
} // 申明交换机
@Bean
public DirectExchange directExchange(){ return new DirectExchange(EXCHANGE_NAME);
} // 数据绑定申明
@Bean
public Binding directBinding(){ return BindingBuilder.bind(queue()).to(directExchange()).with(ROUTING_KEY);
}
}
  1. 创建生产者发送一条消息,代码如下:
@RestController
public class Producer {
public static final String QUEUE_NAME = "byte-zb"; public static final String EXCHANGE_NAME = "byte-zb"; @Autowired
private AmqpTemplate amqpTemplate; @RequestMapping("/send")
public void sendMessage(){ JSONObject jsonObject = new JSONObject();
jsonObject.put("email","11111111111");
jsonObject.put("timestamp",System.currentTimeMillis());
String json = jsonObject.toJSONString();
System.out.println(json);
amqpTemplate.convertAndSend(EXCHANGE_NAME,QUEUE_NAME,json);
} }
  1. 在消费者工程里创建消费者消费消息,代码如下:
@Component
public class Consumer throws Exception{ public static final String QUEUE_NAME = "byte-zb"; @RabbitListener(queues = QUEUE_NAME)
public void receiveMessage(String message){ System.out.println("接收到的消息为"+message);
}
}

我们启动生产者,然后请求send接口,然后打开rabbitmq控制台发现多了一个名为”byte-zb“的交换机和队列,并且队列中出现了一个未消费的消息,然后启动消费者,我们会在控制台上发现打印了一条消息,同时rabbitmq控制台中”byte-zb“的队列中消息没有了。

二、自动补偿机制

如果消费者消息消费不成功的话,会出现什么情况呢?我们修改一下消费者代码,然后看看。

@Component
public class Consumer { public static final String QUEUE_NAME = "byte-zb"; @RabbitListener(queues = QUEUE_NAME)
public void receiveMessage(String message) throws Exception { System.out.println("接收到的消息为"+message);
int i = 1 / 0;
}
}

我们会看到消费者工程控制台一直在刷新报错,当消费者配出异常,也就是说当消息消费不成功的话,该消息会存放在rabbitmq的服务端,一直进行重试,直到不抛出异常为止。

如果一直抛异常,我们的服务很容易挂掉,那有没有办法控制重试几次不成功就不再重试了呢?答案是有的。我们在消费者application.yml中增加一段配置。

spring:
rabbitmq:
# 连接地址
host: 127.0.0.1
# 端口
port: 5672
# 登录账号
username: guest
# 登录密码
password: guest
# 虚拟主机
virtual-host: /
listener:
simple:
retry:
enabled: true # 开启消费者进行重试
max-attempts: 5 # 最大重试次数
initial-interval: 3000 # 重试时间间隔

上面配置的意思是消费异常后,重试五次,每次隔3s。继续启动消费者看看效果,我们发现重试五次以后,就不再重试了。

三、结合实际案例来使用消息补偿机制

像上面那种情况出现的异常其实不管怎么重试都不会成功,实际上用到消息补偿的就是调用第三方接口的这种。

案例:生者往队列中扔一条消息,包含邮箱和发送内容。消费者拿到消息后将调用邮件接口发送邮件。有时候可能邮件接口由于网络等原因不通,这时候就需要去重试了。

在调用接口的工具类中,如果出现异常我们直接返回null,工具类具体代码就不贴了,如果返回null之后怎么处理呢?我们只需要抛出异常,rabbitListener捕获到异常后就会自动重试。

我们改造一下消费者代码:

@Component
public class Consumer { public static final String QUEUE_NAME = "byte-zb"; @RabbitListener(queues = QUEUE_NAME)
public void receiveMessage(String message) throws Exception { System.out.println("接收到的消息为"+message);
JSONObject jsonObject = JSONObject.parseObject(message);
String email = jsonObject.getString("email");
String content = jsonObject.getString("timestamp"); String httpUrl = "http://127.0.0.1:8080/email?email"+email+"&content="+content;
// 如果发生异常则返回null
String body = HttpUtils.httpGet(httpUrl, "utf-8");
//
if(body == null){
throw new Exception();
}
}
}

当然我们可以自定义异常抛出。具体怎么试验呢,第一步启动生产者和消费者,这时候我们发现消费者在重试,第二步我们启动邮件服务,这时候我们会发现邮件发送成功了,消费者不再重试了

四、解决消息幂等性问题

一些刚接触java的同学可能对幂等性不太清楚。幂等性就是重复消费造成结果不一致。为了保证幂等性,因此消费者消费消息只能消费一次消息。我么可以是用全局的消息id来控制幂等性。当消息被消费了之后我们可以选择缓存保存这个消息id,然后当再次消费的时候,我们可以查询缓存,如果存在这个消息id,我们就不错处理直接return即可。先改造生产者代码,在消息中添加消息id:

@RequestMapping("/send")
public void sendMessage(){ JSONObject jsonObject = new JSONObject();
jsonObject.put("email","11111111111");
jsonObject.put("timestamp",System.currentTimeMillis());
String json = jsonObject.toJSONString();
System.out.println(json); Message message = MessageBuilder.withBody(json.getBytes()).setContentType(MessageProperties.CONTENT_TYPE_JSON)
.setContentEncoding("UTF-8").setMessageId(UUID.randomUUID()+"").build();
amqpTemplate.convertAndSend(EXCHANGE_NAME,QUEUE_NAME,message);
}

消费者代码改造:

@Component
public class Consumer { public static final String QUEUE_NAME = "byte-zb"; @RabbitListener(queues = QUEUE_NAME)
public void receiveMessage(Message message) throws Exception { Jedis jedis = new Jedis("localhost", 6379); String messageId = message.getMessageProperties().getMessageId();
String msg = new String(message.getBody(),"UTF-8");
System.out.println("接收导的消息为:"+msg+"==消息id为:"+messageId); String messageIdRedis = jedis.get("messageId"); if(messageId == messageIdRedis){
return;
}
JSONObject jsonObject = JSONObject.parseObject(msg);
String email = jsonObject.getString("email");
String content = jsonObject.getString("timestamp"); String httpUrl = "http://127.0.0.1:8080/email?email"+email+"&content="+content;
// 如果发生异常则返回null
String body = HttpUtils.httpGet(httpUrl, "utf-8");
//
if(body == null){
throw new Exception();
}
jedis.set("messageId",messageId);
}
}

我们在消费者端使用redis存储消息id,只做演示,具体项目请根据实际情况选择相应的工具进行存储。

如果文章对您有帮助,请记得点赞关注哟~

欢迎大家关注我的公众号:字节传说,每日推送技术文章供大家学习参考。

rabbitmq系列(三)消息幂等性处理的更多相关文章

  1. RabbitMQ系列(四)--消息如何保证可靠性传输以及幂等性

    一.消息如何保证可靠性传输 1.1.可能出现消息丢失的情况 1.Producer在把Message发送Broker的过程中,因为网络问题等发生丢失,或者Message到了Broker,但是出了问题,没 ...

  2. RabbitMQ系列(三)--Java API

    基于java使用RabbitMQ 框架:SpringBoot1.5.14.RELEASE maven依赖: <dependency> <groupId>com.rabbitmq ...

  3. rabbitmq系列三 之发布/订阅

    1.发布/订阅 在上篇教程中,我们搭建了一个工作队列,每个任务只分发给一个工作者(worker).在本篇教程中,我们要做的跟之前完全不一样 —— 分发一个消息给多个消费者(consumers).这种模 ...

  4. 9. RabbitMQ系列之消息发布确认

    Publisher Confirms发布确认是用于实现可靠发布的RabbitMQ扩展. 我们将使用发布确认来确保已发布的消息已安全到达代理.我们将介绍几种使用publisher确认的策略,并解释其优缺 ...

  5. Redis系列(三)--消息队列、排行榜等

    Redis命令执行生命周期: 发送命令--->排队(单线程)--->执行命令--->返回结果 慢查询: 只是针对命令执行阶段 慢查询日志通过一个固定长度的FIFO queue,这个q ...

  6. RabbitMQ学习系列三-C#代码接收处理消息

    RabbitMQ学习系列三:.net 环境下 C#代码订阅 RabbitMQ 消息并处理 http://www.80iter.com/blog/1438251320680361 http://www. ...

  7. RabbitMQ学习系列三:.net 环境下 C#代码订阅 RabbitMQ 消息并处理

    上一篇已经讲了Rabbitmq如何在Windows平台安装 不懂请移步: RabbitMQ学习系列二:.net 环境下 C#代码使用 RabbitMQ 消息队列 一.理论 .net环境下,C#代码订阅 ...

  8. RabbitMQ系列(四)RabbitMQ事务和Confirm发送方消息确认——深入解读

    RabbitMQ事务和Confirm发送方消息确认--深入解读 RabbitMQ系列文章 RabbitMQ在Ubuntu上的环境搭建 深入了解RabbitMQ工作原理及简单使用 RabbitMQ交换器 ...

  9. RabbitMQ系列(三)RabbitMQ交换器Exchange介绍与实践

    RabbitMQ交换器Exchange介绍与实践 RabbitMQ系列文章 RabbitMQ在Ubuntu上的环境搭建 深入了解RabbitMQ工作原理及简单使用 RabbitMQ交换器Exchang ...

随机推荐

  1. js(三) ajax异步局部刷新技术底层代码实现

    ajax 异步 javaScript and xml 开发五步骤: 1. 创建对象 XMLHttpRequest(chrome,firefox) ie... jquery 2. 找到连接, http的 ...

  2. java io流与序列化反序列化

    java的io是实现输入和输出的基础,可以方便的实现数据的输入和输出操作. 序列化 (Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程.在序列化期间,对象将其当前状态写入 ...

  3. H3C 显示OSPF邻居信息

  4. C# 7.2 通过 in 和 readonly struct 减少方法值复制提高性能

    在 C# 7.2 提供了一系列的方法用于方法参数传输的时候减少对结构体的复制从而可以高效使用内存同时提高性能 在开始阅读之前,希望读者对 C# 的值类型.引用类型有比较深刻的认知. 在 C# 中,如果 ...

  5. tensorflow在文本处理中的使用——Doc2Vec情感分析

    代码来源于:tensorflow机器学习实战指南(曾益强 译,2017年9月)——第七章:自然语言处理 代码地址:https://github.com/nfmcclure/tensorflow-coo ...

  6. 1119 机器人走方格 V2 (组合数学)

    M * N的方格,一个机器人从左上走到右下,只能向右或向下走.有多少种不同的走法?由于方法数量可能很大,只需要输出Mod 10^9 + 7的结果.   Input 第1行,2个数M,N,中间用空格隔开 ...

  7. java打包上传服务器的一些命令

    Maven下package打包成jar包和war包,都在target目录下 其中War包扔在tomcat的webapps目录下.随tomcat启动自行启动 运行jar包命令. nohup java - ...

  8. CF1045G AI robots

    CF1045G AI robots 题目大意就不说了 这道题可以用CDQ分治做 但是,如何选择CDQ分治的维度一直是CDQ分治的难点所在 这道题我们有三种选择 1.让智商高的数智商低的 2.让看的近的 ...

  9. JQ表单选择器和CSS3表单选择器

    JQ表单选择器和CSS3表单选择器 JQ表单选择器 为了使用户能够更加灵活地操作表单,jQuery中加入了表单选择器,利用这个选择器能极其方便的获取到表单的某个或者某类型的元素.表单选择器的介绍如图: ...

  10. Team Foundation Server 2015使用教程【1】:团队项目创建