幂等指的就是执行多次和执行一次的效果相同,主要是为了防止数据重复消费。MQ中为了保证消息的可靠性,生产者发送消息失败(例如网络超时)会触发 "重试机制",它不是生产者重试而是MQ自动触发的重试机制, 而这种情况下消费者就会收到两条消息,比如明明只需要扣一次款, 可是消费者却执行了2次。为了解决幂等问题,每一个消息应该有一个全局的唯一的标识,当处理过这条消息后,就把这个标识保存到数据库或者redis中,在处理消息前前判断这个标识记录为空就好了。像activemq中msgId就是唯一的,我们可以直接拿这个id来判断,但是rocketmq重试机制不一样,它重发会产生一个新的id,但是它提供了setKeys()这个api,我们可以给key设置一个唯一的流水编号来加以判断。(重试机制是不存在并发问题的,它是间隔一段时间自动促发的)。

1. 导入依赖( 生产者和消费者的依赖都一样)

  1. <parent>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-parent</artifactId>
  4. <version>1.5.7.RELEASE</version>
  5. <relativePath/>
  6. </parent>
  7. <!-- springcloud
  8. <dependencyManagement>
  9. <dependencies>
  10. <dependency>
  11. <groupId>org.springframework.cloud</groupId>
  12. <artifactId>spring-cloud-dependencies</artifactId>
  13. <version>Camden.SR6</version>
  14. <type>pom</type>
  15. <scope>import</scope>
  16. </dependency>
  17. </dependencies>
  18. </dependencyManagement>
  19. -->
  20. <properties>
  21. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  22. <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
  23. <java.version>1.8</java.version>
  24. </properties>
  25. <dependencies>
  26. <!-- webmvc -->
  27. <dependency>
  28. <groupId>org.springframework.boot</groupId>
  29. <artifactId>spring-boot-starter-web</artifactId>
  30. </dependency>
  31. <!-- 集成lombok 框架(get/set) -->
  32. <dependency>
  33. <groupId>org.projectlombok</groupId>
  34. <artifactId>lombok</artifactId>
  35. </dependency>
  36. <!-- RocketMq -->
  37. <dependency>
  38. <groupId>com.alibaba.rocketmq</groupId>
  39. <artifactId>rocketmq-client</artifactId>
  40. <version>3.2.6</version>
  41. </dependency>
  42. <!-- 热加载 -->
  43. <dependency>
  44. <groupId>org.springframework.boot</groupId>
  45. <artifactId>spring-boot-devtools</artifactId>
  46. <optional>true</optional>
  47. </dependency>
  48. <!-- jackson -->
  49. <dependency>
  50. <groupId>com.alibaba</groupId>
  51. <artifactId>fastjson</artifactId>
  52. <version>1.2.30</version>
  53. </dependency>
  54. </dependencies>
  55.  
  56. <build>
  57. <plugins>
  58. <plugin>
  59. <groupId>org.springframework.boot</groupId>
  60. <artifactId>spring-boot-maven-plugin</artifactId>
  61. </plugin>
  62. </plugins>
  63. </build>

pom.xml

2. 生产者配置参数和配置文件

  1. #该应用是否启用生产者
  2. #rocketmq.producer.isOnOff=on
  3. #发送同一类消息的设置为同一个group,保证唯一,默认不需要设置,rocketmq会使用ip@pid(pid代表jvm名字)作为唯一标示
  4. rocketmq.producer.groupName=mqtest
  5. #mq的nameserver地址
  6. rocketmq.producer.namesrvAddr=192.168.5.7:9876
  7. #消息最大长度 默认1024*4(4M)
  8. rocketmq.producer.maxMessageSize=4096
  9. #发送消息超时时间,默认3000
  10. rocketmq.producer.sendMsgTimeout=3000
  11. #发送消息失败重试次数,默认2
  12. rocketmq.producer.retryTimesWhenSendFailed=3

rocketmq.properties

  1. import org.slf4j.Logger;
  2. import org.slf4j.LoggerFactory;
  3. import org.springframework.beans.factory.annotation.Value;
  4. import org.springframework.context.annotation.Bean;
  5. import org.springframework.context.annotation.Configuration;
  6. import org.springframework.context.annotation.PropertySource;
  7. import com.alibaba.rocketmq.client.exception.MQClientException;
  8. import com.alibaba.rocketmq.client.producer.DefaultMQProducer;
  9. /**
  10. * 生产者配置
  11. */
  12. @PropertySource("classpath:rocketmq.properties")
  13. @Configuration
  14. public class MQProducerConfiguration {
  15. public static final Logger LOGGER = LoggerFactory.getLogger(MQProducerConfiguration.class);
  16. /**
  17. * 发送同一类消息的设置为同一个group,保证唯一,默认不需要设置,rocketmq会使用ip@pid(pid代表jvm名字)作为唯一标示
  18. */
  19. @Value("${rocketmq.producer.groupName}")
  20. private String groupName;
  21. /** 服务器地址 */
  22. @Value("${rocketmq.producer.namesrvAddr}")
  23. private String namesrvAddr;
  24. /**
  25. * 消息最大大小,默认4M
  26. */
  27. @Value("${rocketmq.producer.maxMessageSize}")
  28. private Integer maxMessageSize ;
  29. /**
  30. * 消息发送超时时间,默认3秒
  31. */
  32. @Value("${rocketmq.producer.sendMsgTimeout}")
  33. private Integer sendMsgTimeout;
  34. /**
  35. * 消息发送失败重试次数,默认2次
  36. */
  37. @Value("${rocketmq.producer.retryTimesWhenSendFailed}")
  38. private Integer retryTimesWhenSendFailed;
  39.  
  40. @Bean
  41. public DefaultMQProducer getRocketMQProducer() {
  42. DefaultMQProducer producer;
  43. producer = new DefaultMQProducer(this.groupName);
  44. producer.setNamesrvAddr(this.namesrvAddr);
  45. //如果需要同一个jvm中不同的producer往不同的mq集群发送消息,需要设置不同的instanceName
  46. //producer.setInstanceName(instanceName);
  47. producer.setMaxMessageSize(this.maxMessageSize);
  48. producer.setSendMsgTimeout(this.sendMsgTimeout);
  49. //如果发送消息失败,设置重试次数,默认为2次
  50. producer.setRetryTimesWhenSendFailed(this.retryTimesWhenSendFailed);
  51. try {
  52. producer.start();
  53. LOGGER.info(String.format("rocketmq producer start "));
  54. } catch (MQClientException e) {
  55. LOGGER.error(String.format("producer is error {}", e.getMessage(),e));
  56. }
  57. return producer;
  58. }
  59. }

MQProducerConfiguration

3. 生产者发送消息

  1. import java.util.UUID;
  2. import org.springframework.beans.factory.annotation.Autowired;
  3. import org.springframework.web.bind.annotation.RequestMapping;
  4. import org.springframework.web.bind.annotation.RestController;
  5. import com.alibaba.rocketmq.client.producer.DefaultMQProducer;
  6. import com.alibaba.rocketmq.client.producer.SendResult;
  7. import com.alibaba.rocketmq.common.message.Message;
  8. import lombok.extern.slf4j.Slf4j;
  9. @RestController
  10. @Slf4j
  11. public class TestController {
  12.  
  13. /**使用RocketMq的生产者*/
  14. @Autowired
  15. private DefaultMQProducer defaultMQProducer;
  16.  
  17. @RequestMapping("/send")
  18. public void send(){
  19. String msg = "幂等";
  20. log.info("开始发送消息:"+msg);
  21.  
  22. try {
  23. // arg0主题名称 arg1分组 arg2内容
  24. Message sendMsg = new Message("DemoTopic","wulei",(msg).getBytes());
  25. // 注意: activemq的msgId是唯一的,但是rocketmq的不是,所以幂等不能用id来判断,我们可以通过setKeys来解决,一般都是业务id,这里用随机数代替。
  26. sendMsg.setKeys(UUID.randomUUID().toString());
  27. SendResult sendResult = defaultMQProducer.send(sendMsg);
  28. //默认3秒超时
  29. log.info("消息发送响应信息:"+sendResult.toString());
  30. } catch (Exception e) {
  31. e.printStackTrace();
  32. }
  33. }
  34. }

TestController

4. 消费者配置参数和配置文件

  1. ##该应用是否启用消费者
  2. #rocketmq.consumer.isOnOff=on
  3. #发送同一类消息的设置为同一个group,保证唯一,默认不需要设置,rocketmq会使用ip@pid(pid代表jvm名字)作为唯一标示
  4. rocketmq.consumer.groupName=mqtest
  5. #mq的nameserver地址
  6. rocketmq.consumer.namesrvAddr=192.168.5.7:9876
  7. #该消费者订阅的主题和tags("*"号表示订阅该主题下所有的tags),格式:topic~tag1||tag2||tag3;topic2~*;
  8. rocketmq.consumer.topics=DemoTopic~*;
  9. rocketmq.consumer.consumeThreadMin=20
  10. rocketmq.consumer.consumeThreadMax=64
  11. #设置一次消费消息的条数,默认为1条
  12. rocketmq.consumer.consumeMessageBatchMaxSize=1

rocketmq.properties

  1. import org.slf4j.Logger;
  2. import org.slf4j.LoggerFactory;
  3. import org.springframework.beans.factory.annotation.Autowired;
  4. import org.springframework.beans.factory.annotation.Value;
  5. import org.springframework.context.annotation.Bean;
  6. import org.springframework.context.annotation.Configuration;
  7. import org.springframework.context.annotation.PropertySource;
  8. import com.alibaba.rocketmq.client.consumer.DefaultMQPushConsumer;
  9. import com.alibaba.rocketmq.client.exception.MQClientException;
  10. import com.alibaba.rocketmq.common.consumer.ConsumeFromWhere;
  11. import com.wulei.listener.MQConsumeMsgListenerProcessor;
  12. /**
  13. * 消费者配置
  14. */
  15. @PropertySource("classpath:rocketmq.properties")
  16. @Configuration
  17. public class MQConsumerConfiguration {
  18. public static final Logger LOGGER = LoggerFactory.getLogger(MQConsumerConfiguration.class);
  19. // 地址
  20. @Value("${rocketmq.consumer.namesrvAddr}")
  21. private String namesrvAddr;
  22. // 发送同一类消息的设置为同一个group,保证唯一,默认不需要设置,rocketmq会使用ip@pid(pid代表jvm名字)作为唯一标示
  23. @Value("${rocketmq.consumer.groupName}")
  24. private String groupName;
  25. // 该消费者订阅的主题和tags("*"号表示订阅该主题下所有的tags)
  26. @Value("${rocketmq.consumer.topics}")
  27. private String topics;
  28.  
  29. @Value("${rocketmq.consumer.consumeThreadMin}")
  30. private int consumeThreadMin;
  31. @Value("${rocketmq.consumer.consumeThreadMax}")
  32. private int consumeThreadMax;
  33. @Value("${rocketmq.consumer.consumeMessageBatchMaxSize}")
  34. private int consumeMessageBatchMaxSize;
  35.  
  36. @Autowired
  37. private MQConsumeMsgListenerProcessor mqMessageListenerProcessor;
  38.  
  39. @Bean
  40. public DefaultMQPushConsumer getRocketMQConsumer(){
  41. DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(groupName);
  42. consumer.setNamesrvAddr(namesrvAddr);
  43. consumer.setConsumeThreadMin(consumeThreadMin);
  44. consumer.setConsumeThreadMax(consumeThreadMax);
  45. consumer.registerMessageListener(mqMessageListenerProcessor);
  46. /**
  47. * 设置Consumer第一次启动是从队列头部开始消费还是队列尾部开始消费
  48. * 如果非第一次启动,那么按照上次消费的位置继续消费
  49. */
  50. consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET);
  51. /**
  52. * 设置消费模型,集群还是广播,默认为集群
  53. */
  54. //consumer.setMessageModel(MessageModel.CLUSTERING);
  55. /**
  56. * 设置一次消费消息的条数,默认为1条
  57. */
  58. consumer.setConsumeMessageBatchMaxSize(consumeMessageBatchMaxSize);
  59. try {
  60. /**
  61. * 设置该消费者订阅的主题和tag,如果是订阅该主题下的所有tag,则tag使用*;如果需要指定订阅该主题下的某些tag,则使用||分割,例如tag1||tag2||tag3
  62. */
  63. String[] topicTagsArr = topics.split(";");
  64. for (String topicTags : topicTagsArr) {
  65. String[] topicTag = topicTags.split("~");
  66. consumer.subscribe(topicTag[0],topicTag[1]);
  67. }
  68. consumer.start();
  69. LOGGER.info("consumer is start !!! groupName:{},topics:{},namesrvAddr:{}",groupName,topics,namesrvAddr);
  70. }catch (MQClientException e){
  71. LOGGER.error("consumer is start !!! groupName:{},topics:{},namesrvAddr:{}",groupName,topics,namesrvAddr,e);
  72. }
  73. return consumer;
  74. }
  75. }

MQConsumerConfiguration

5. 消费者监听消息

  1. import java.util.HashMap;
  2. import java.util.List;
  3. import org.slf4j.Logger;
  4. import org.slf4j.LoggerFactory;
  5. import org.springframework.stereotype.Component;
  6. import com.alibaba.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
  7. import com.alibaba.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
  8. import com.alibaba.rocketmq.client.consumer.listener.MessageListenerConcurrently;
  9. import com.alibaba.rocketmq.common.message.MessageExt;
  10. @Component
  11. public class MQConsumeMsgListenerProcessor implements MessageListenerConcurrently{
  12. private static final Logger logger = LoggerFactory.getLogger(MQConsumeMsgListenerProcessor.class);
  13.  
  14. // 假装这是一个redis
  15. private HashMap<String, String> myredis = new HashMap<String, String>();
  16.  
  17. /**
  18. * 默认msgs里只有一条消息,可以通过设置consumeMessageBatchMaxSize参数来批量接收消息<br/>
  19. * 不要抛异常,如果没有return CONSUME_SUCCESS ,consumer会重新消费该消息,直到return CONSUME_SUCCESS
  20. */
  21. @Override
  22. public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
  23. // if(CollectionUtils.isEmpty(msgs)){
  24. // logger.info("接受到的消息为空,不处理,直接返回成功");
  25. // return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
  26. // }
  27. //
  28.  
  29. //for(MessageExt messageExt : msgs) { }
  30. MessageExt messageExt = msgs.get(0);
  31. String keys = messageExt.getKeys();// 自定义的唯一key
  32. String msgId = null; // 消息id(不是唯一的)
  33. String msgContext = null; // 消息内容
  34. int reconsume = 0; // 重试次数
  35.  
  36. if(messageExt.getTopic().equals("DemoTopic") && messageExt.getTags().equals("wulei")){
  37. if(myredis.get(keys)==null) {
  38. //logger.info("接受到的消息为:"+messageExt.toString());
  39. msgId = messageExt.getMsgId();
  40. msgContext = new String(messageExt.getBody());
  41. reconsume = messageExt.getReconsumeTimes();
  42. try {
  43. int i = 1/0;
  44. System.out.println("消费成功: id:"+msgId+" msg"+msgContext+" 次数"+reconsume);
  45. myredis.put(messageExt.getKeys(), msgContext);
  46. return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
  47. } catch (Exception e) {
  48. // 重试3次就不在重试了,直接返回消费成功状态,并触发人工补偿机制。
  49. if(reconsume==2) {
  50. myredis.put(messageExt.getKeys(), msgContext);
  51. return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
  52. }else {
  53. // 一般消费者这边尽量不要抛异常,它失败就会触发重试机制。如果非要抛异常可以在try{}catch{}里面return ConsumeConcurrentlyStatus.RECONSUME_LATER(表示失败让他重试)
  54. return ConsumeConcurrentlyStatus.RECONSUME_LATER;
  55. }
  56. }
  57. }else {
  58. // 已经消费过就不要再重试了,直接返回成功。
  59. return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
  60. }
  61. }else {
  62. // 不存在不要再重试了,直接返回成功。
  63. return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
  64. }
  65. }
  66. }

MQConsumeMsgListenerProcessor

分布式事务——幂等设计(rocketmq案例)的更多相关文章

  1. 分布式事务之如何基于RocketMQ的事务消息特性实现分布式系统的最终一致性?

    导读 在之前的文章中我们介绍了如何基于RocketMQ搭建生产级消息集群,以及2PC.3PC和TCC等与分布式事务相关的基本概念(没有读过的读者详见

  2. 了解一下Mysql分布式事务及优缺点、使用案例(php+mysql)

    在开发中,为了降低单点压力,通常会根据业务情况进行分表分库,将表分布在不同的库中(库可能分布在不同的机器上),但是一个业务场景可能会同时处理两个表的操作.在这种场景下,事务的提交会变得相对复杂,因为多 ...

  3. springCloud分布式事务实战(一)案例需求及实现步骤

    本文不对分布式事务原理进行探索,而是通过一个案例来说明如何使用分布式事务 案例需求:创建2个基于springCloud的微服务,分别访问不同的数据库:然后创建一个整合服务,调用微服务实现数据的保存到2 ...

  4. 分布式之分布式事务、分布式锁、接口幂等性、分布式session

    一.分布式session session 是啥?浏览器有个 cookie,在一段时间内这个 cookie 都存在,然后每次发请求过来都带上一个特殊的 jsessionid cookie,就根据这个东西 ...

  5. texedo 分布式事务

    1.问题现象 但是实际情况,完全出乎笔者的想法.检查一般对象数据表锁定,只需要检查v$locked_object和v$transaction视图,就可以定位到具体人.但是检查之后的结果如下: SQL& ...

  6. 消息服务框架(MSF)应用实例之分布式事务三阶段提交协议的实现

    一,分布式事务简介 在当前互联网,大数据和人工智能的热潮中,传统企业也受到这一潮流的冲击,纷纷响应国家“互联网+”的战略号召,企业开始将越来越多的应用从公司内网迁移到云端和移动端,或者将之前孤立的IT ...

  7. 分布式事务之深入理解什么是2PC、3PC及TCC协议?

    导读 在上一篇文章<[分布式事务]基于RocketMQ搭建生产级消息集群?>中给大家介绍了基于RocketMQ如何搭建生产级消息集群.因为本系列文章最终的目的是介绍基于RocketMQ的事 ...

  8. 即时消息服务框架(iMSF)应用实例之分布式事务三阶段提交协议的实现

    一,分布式事务简介 在当前互联网,大数据和人工智能的热潮中,传统企业也受到这一潮流的冲击,纷纷响应国家“互联网+”的战略号召,企业开始将越来越多的应用从公司内网迁移到云端和移动端,或者将之前孤立的IT ...

  9. 微服务开发的最大痛点-分布式事务SEATA入门简介

    前言 在微服务开发中,存在诸多的开发痛点,例如分布式事务.全链路跟踪.限流降级和服务平滑上下线等.而在这其中,分布式事务是最让开发者头痛的.那分布式事务是什么呢? 分布式事务就是指事务的参与者.支持事 ...

随机推荐

  1. 拦截器中,request中getReader()和getInputStream()只能调用一次,构建可重复读取inputStream的request.

    由于 request中getReader()和getInputStream()只能调用一次 在项目中,可能会出现需要针对接口参数进行校验等问题. 因此,针对这问题,给出一下解决方案 实现方法:先将Re ...

  2. Spring Cloud Config教程(五)客户端使用

    要在应用程序中使用这些功能,只需将其构建为依赖于spring-cloud-config-client的Spring引导应用程序(例如,查看配置客户端或示例应用程序的测试用例).添加依赖关系的最方便的方 ...

  3. 20道JS原理题助你面试一臂之力!(转)

    20道JS原理题助你面试一臂之力! 前言 本文针对目前常见的面试题,仅提供了相应的核心原理及思路,部分边界细节未处理.后续会持续更新,希望对你有所帮助. 1. 实现一个call函数 // 思路:将要改 ...

  4. Java equals 和 hashCode 的这几个问题可以说明白吗?

    前言 上一篇文章 如何妙用 Spring 数据绑定? ,灵魂追问 环节留下了一个有关 equals 和 hashcode 问题 .基础面试经常会碰到与之相关的问题,这不是一个复杂的问题,但很多朋友都苦 ...

  5. spring cloud:feign-hystrix

    producer 1. File-->new spring starter project 2.add dependency       <dependency> <group ...

  6. Gridview中显示的值根据数据库中带出的值作更改

    前台页面对Gridview增加事件 OnRowDataBound="GridView1_RowDataBound"protected void GridView1_RowDataB ...

  7. modern php笔记---php (性状)

    modern php笔记---php (性状) 一.总结 一句话总结: trait是和继承一个层次的东西 一个类use MyTrait;后,trait中的方法覆盖父类方法,当前类中的方法覆盖trait ...

  8. LeetCode_1114.按顺序打印(多线程)

    LeetCode_1114 LeetCode-1114.按顺序打印 我们提供了一个类: public class Foo { public void one() { print("one&q ...

  9. spring boot 和 mybatis集成

    1.pom.xml 添加mybatis和mysql依赖 <!-- 添加 MyBatis --> <dependency> <groupId>org.mybatis. ...

  10. tomcat的work目录的作用

    最近发现,很多网友喜欢把tomcat的work目录里的东西叫做缓存,其实那不是很恰当,work目录只是tomcat的工作目录,也就是tomcat把jsp转换为class文件的工作目录,这也正是为什么它 ...