一、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. 深入理解String、StringBuffer、StringBuilder(转)

    文章系转载,非原创,原地址: http://www.cnblogs.com/dolphin0520/p/3778589.html 相信String这个类是Java中使用得最频繁的类之一,并且又是各大公 ...

  2. fatal: Not a git repository (or any of the parent directories)

    当从github.com上面下载下了Firmware后,无意中删除了Firmware目录下的.git文件夹,再去编译就会出现:   fatal: Not a git repository (or an ...

  3. P1094 百钱白鸡

    题目描述 公鸡5文钱一只,母鸡3文钱一只,小鸡3只一文钱,用100文钱买100只鸡,其中公鸡,母鸡,小鸡都必须要有,问公鸡,母鸡,小鸡要买多少只刚好凑足100文钱. 输入格式 无. 输出格式 输出所有 ...

  4. P1002 Hello,World!

    题目描述 输出"Hello Wolrd!". 输入格式 无. 输出格式 输出一行"Hello World!". 样例输入 无. 样例输出 Hello World ...

  5. H3C 路由优先级

  6. H3C 端口隔离基本配置

  7. H3C配置console口密码

    方法一: [H3C]user-interface console 0 [H3C-ui-console0]authentication-mode password [H3C-ui-console0]se ...

  8. C# 线程参数

    . class ThreadSample { private readonly int _iterations; public ThreadSample(int iterations) { _iter ...

  9. python 练习题2

    # 习题1:# 设定一个用户名和密码,用户输入正确的用户名和密码,# 则显示登录成功,否则提示登录失败,用户最多失败3次,# 否则退出程序.username="test"passw ...

  10. struts2生成随机验证码图片

    之前想做一个随机验证码的功能,自己也搜索了一下别人写的代码,然后自己重新用struts2实现了一下,现在将我自己实现代码贴出来!大家有什么意见都可以指出来! 首先是生成随机验证码图片的action: ...