在第三方支付中,例如支付宝、或者微信,对于订单请求,第三方支付系统采用的是消息同步返回、异步通知+主动补偿查询的补偿机制。

由于互联网通信的不可靠性,例如双方网络、服务器、应用等因素的影响,不管是同步返回、异步通知、主动查询报文都可能出现超时无响应、报文丢失等情况,所以像支付业务,对结果的通知一般采用几种方案结合的补偿机制,不能完全依赖某一种机制。

例如一个支付结果的通知,一方面会在支付页面跳转时候返回支付结果(一般只用作前端展示使用,非最终状态),同时会采用后台异步通知机制(有前台、后台通知的,以后台异步通知结果为准),但由于前台跳转、后台结果通知都可能失效,因此还以定时补单+请求方主动查询接口作为辅助手段。
常见的补单操作,任务调度策略一般设定30秒、60秒、3分钟、6分钟、10分钟调度多次(以自己业务需要),如果调度接收到响应确认报文,补单成功,则中止对应订单的调度任务;如果超过补单上限次数,则停止补单,避免无谓的资源浪费。请求端随时可以发起请求报文查询对应订单的状态。
在日常开发中,对于网站前端来说,支付计费中心对于订单请求信息的处理也是通过消息同步返回、异步通知+主动补偿查询相结合的机制,其中对于订单的异步通知,目前的通知策略为3s、30s、60s、120s、180、300s的阶梯性通知。返回成功情况下就不继续通知了,本来打算使用将失败的消息写到数据库等待发送,然后每秒查询数据库获取消息通知前端。但觉得这样的处理方式太粗暴。存在以下缺点:
1 、每秒请求有点儿浪费资源; 2 、通知方式不稳定; 3 、无法承受大数据量等等

所以最终打算使用rabbitmq的消息延迟+死信队列来实现。消息模型如下:


producer发布消息,通过exchangeA的消息会被分发到QueueA,Consumer监听queueA,一旦有消息到来就被消费,这边的消费业务就是通知前端,如果通知失败,就创建一个延迟队列declareQueue,设置每个消息的ttl然后通过declare_exchange将消息分发到declare_queue,因为declare_queue没有consumer并且declare_queue中的消息设置了ttl,当ttl到期后,将通过DEX路由到queueA,被重新消费。
代码如下:DeclareQueue.java

  1. package org.delayQueue;
  2. import com.rabbitmq.client.BuiltinExchangeType;
  3. import com.rabbitmq.client.Channel;
  4. import com.rabbitmq.client.Connection;
  5. import com.rabbitmq.client.ConnectionFactory;
  6. public class DeclareQueue {
  7. public static String EXCHANGE_NAME = "notifyExchange";
  8. public static void init() {
  9. ConnectionFactory factory = new ConnectionFactory();
  10. factory.setHost("localhost");
  11. factory.setPort(5672);
  12. Connection connection = null;
  13. try {
  14. connection = factory.newConnection();
  15. Channel channel = connection.createChannel();
  16. channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
  17. String routingKey = "AliPaynotify";
  18. String message = "http://localhost:8080/BossCenter/payGateway/notifyRecv.jsp?is_success=T¬ify_id=4ab9bed148d043d0bf75460706f7774a¬ify_time=2014-08-29+16%3A22%3A02¬ify_type=trade_status_sync&out_trade_no=1421712120109862&total_fee=424.42&trade_no=14217121201098611&trade_status=TRADE_SUCCESS";
  19. channel.basicPublish(EXCHANGE_NAME, routingKey, null, message.getBytes());
  20. System.out.println(" [x] Sent :" + message);
  21. } catch (Exception e) {
  22. // TODO Auto-generated catch block
  23. e.printStackTrace();
  24. } finally {
  25. if (connection != null) {
  26. try {
  27. connection.close();
  28. } catch (Exception ignore) {
  29. }
  30. }
  31. }
  32. }
  33. public static void main(String args[]) {
  34. init();
  35. }
  36. }

DeclareConsumer.java


  1. package org.delayQueue;
  2. import java.io.BufferedReader;
  3. import java.io.IOException;
  4. import java.io.InputStreamReader;
  5. import java.util.ArrayList;
  6. import java.util.HashMap;
  7. import java.util.List;
  8. import java.util.Map;
  9. import java.util.Map.Entry;
  10. import org.apache.http.HttpResponse;
  11. import org.apache.http.client.ClientProtocolException;
  12. import org.apache.http.client.HttpClient;
  13. import org.apache.http.client.methods.HttpPost;
  14. import org.apache.http.impl.client.DefaultHttpClient;
  15. import com.rabbitmq.client.AMQP;
  16. import com.rabbitmq.client.Channel;
  17. import com.rabbitmq.client.Connection;
  18. import com.rabbitmq.client.ConnectionFactory;
  19. import com.rabbitmq.client.Consumer;
  20. import com.rabbitmq.client.DefaultConsumer;
  21. import com.rabbitmq.client.Envelope;
  22. public class DeclareConsumer {
  23. public static String EXCHANGE_NAME = "notifyExchange";
  24. public static String QU_declare_15S = "Qu_declare_15s";
  25. public static String EX_declare_15S = "EX_declare_15s";
  26. public static String ROUTINGKEY = "AliPaynotify";
  27. public static Connection connection = null;
  28. public static Channel channel = null;
  29. public static Channel DECLARE_15S_CHANNEL = null;
  30. public static String declare_queue = "init";
  31. public static String originalExpiration = "0";
  32. public static void init() throws Exception {
  33. ConnectionFactory factory = new ConnectionFactory();
  34. factory.setHost("localhost");
  35. factory.setPort(5672);
  36. connection = factory.newConnection();
  37. channel = connection.createChannel();
  38. DECLARE_15S_CHANNEL = connection.createChannel();
  39. }
  40. public static void consume() {
  41. try {
  42. channel.exchangeDeclare(EXCHANGE_NAME, "topic");
  43. final String queueName = channel.queueDeclare().getQueue();
  44. channel.queueBind(queueName, EXCHANGE_NAME, ROUTINGKEY);
  45. System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
  46. final Consumer consumer = new DefaultConsumer(channel) {
  47. @Override
  48. public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
  49. String message = new String(body, "UTF-8");
  50. Map<String, Object> headers = properties.getHeaders();
  51. if (headers != null) {
  52. List<Map<String, Object>> xDeath = (List<Map<String, Object>>) headers.get("x-death");
  53. System.out.println("xDeath--- > " + xDeath);
  54. if (xDeath != null && !xDeath.isEmpty()) {
  55. Map<String, Object> entrys = xDeath.get(0);
  56. // for(Entry<String, Object>
  57. // entry:entrys.entrySet()){
  58. // System.out.println(entry.getKey()+":"+entry.getValue());
  59. // }
  60. originalExpiration = entrys.get("original-expiration").toString();
  61. }
  62. }
  63. System.out.println(" [x] Received '" + envelope.getRoutingKey() + "':'" + message + "'" + "time" + System.currentTimeMillis());
  64. HttpClient httpClient = new DefaultHttpClient();
  65. HttpPost post = new HttpPost(message);
  66. HttpResponse response = httpClient.execute(post);
  67. BufferedReader inreader = null;
  68. if (response.getStatusLine().getStatusCode() == 200) {
  69. inreader = new BufferedReader(new InputStreamReader(response.getEntity().getContent(), "UTF-8"));
  70. StringBuffer responseBody = new StringBuffer();
  71. String line = null;
  72. while ((line = inreader.readLine()) != null) {
  73. responseBody.append(line);
  74. }
  75. if (!responseBody.equals("success")) {
  76. // putDeclre15s(message);
  77. if (originalExpiration.equals("0")) {
  78. putDeclreQueue(message, 3000, QU_declare_15S);
  79. }
  80. if (originalExpiration.equals("3000")) {
  81. putDeclreQueue(message, 30000, QU_declare_15S);
  82. }
  83. if (originalExpiration.equals("30000")) {
  84. putDeclreQueue(message, 60000, QU_declare_15S);
  85. }
  86. if (originalExpiration.equals("60000")) {
  87. putDeclreQueue(message, 120000, QU_declare_15S);
  88. }
  89. if (originalExpiration.equals("120000")) {
  90. putDeclreQueue(message, 180000, QU_declare_15S);
  91. }
  92. if (originalExpiration.equals("180000")) {
  93. putDeclreQueue(message, 300000, QU_declare_15S);
  94. }
  95. if (originalExpiration.equals("300000")) {
  96. // channel.basicConsume(QU_declare_300S,true, this);
  97. System.out.println("finish notify");
  98. }
  99. }
  100. } else {
  101. System.out.println(response.getStatusLine().getStatusCode());
  102. }
  103. }
  104. };
  105. channel.basicConsume(queueName, true, consumer);
  106. } catch (Exception e) {
  107. e.printStackTrace();
  108. } finally {
  109. }
  110. }
  111. static Map<String, Object> xdeathMap = new HashMap<String, Object>();
  112. static List<Map<String, Object>> xDeath = new ArrayList<Map<String, Object>>();
  113. static Map<String, Object> xdeathParam = new HashMap<String, Object>();
  114. public static void putDeclre15s(String message) throws IOException {
  115. channel.exchangeDeclare(EX_declare_15S, "topic");
  116. Map<String, Object> args = new HashMap<String, Object>();
  117. args.put("x-dead-letter-exchange", EXCHANGE_NAME);// 死信exchange
  118. AMQP.BasicProperties.Builder builder = new AMQP.BasicProperties.Builder();
  119. builder.expiration("3000").deliveryMode(2);// 设置消息TTL
  120. AMQP.BasicProperties properties = builder.build();
  121. channel.queueDeclare(QU_declare_15S, false, false, false, args);
  122. channel.queueBind(QU_declare_15S, EX_declare_15S, ROUTINGKEY);
  123. channel.basicPublish(EX_declare_15S, ROUTINGKEY, properties, message.getBytes());
  124. System.out.println("send message in QA_DEFERRED_15S" + message + "time" + System.currentTimeMillis());
  125. }
  126. public static void putDeclreQueue(String message, int mis, String queue) throws IOException {
  127. channel.exchangeDeclare(EX_declare_15S, "topic");
  128. Map<String, Object> args = new HashMap<String, Object>();
  129. args.put("x-dead-letter-exchange", EXCHANGE_NAME);// 死信exchange
  130. AMQP.BasicProperties.Builder builder = new AMQP.BasicProperties.Builder();
  131. builder.expiration(String.valueOf(mis)).deliveryMode(2);// 设置消息TTL
  132. AMQP.BasicProperties properties = builder.build();
  133. channel.queueDeclare(queue, false, false, false, args);
  134. channel.queueBind(queue, EX_declare_15S, ROUTINGKEY);
  135. channel.basicPublish(EX_declare_15S, ROUTINGKEY, properties, message.getBytes());
  136. System.out.println("send message in " + queue + message + "time============" + System.currentTimeMillis());
  137. }
  138. public static void main(String args[]) throws Exception {
  139. init();
  140. consume();
  141. }
  142. }

消息通过dlx转发的情况下,header头部会带有x-death的一个数组,里面包含消息的各项属性,比如说消息成为死信的原因reason,original-expiration这个字段表示消息在原来队列中的过期时间,根据这个值来确定下一次通知的延迟时间应该是多少秒。

运行结果如下:

RabbitMQ 延迟队列实现订单支付结果异步阶梯性通知的更多相关文章

  1. C# RabbitMQ延迟队列功能实战项目演练

    一.需求背景 当用户在商城上进行下单支付,我们假设如果8小时没有进行支付,那么就后台自动对该笔交易的状态修改为订单关闭取消,同时给用户发送一份邮件提醒.那么我们应用程序如何实现这样的需求场景呢?在之前 ...

  2. RabbitMQ延迟队列

    rabbitmq延迟队列 rabbitmq实现延迟队列用了rabbitmq-delayed-message-exchange插件,需要提前安装,并启用. 原理 其原理是通过Exchange来实现延迟功 ...

  3. RabbitMQ延迟队列插件安装

    RabbitMQ延迟队列插件安装 一.下载插件 下载地址:https://www.rabbitmq.com/community-plugins.html 二.把下载的插件放到指定位置 下载的文件为zi ...

  4. C#实现rabbitmq 延迟队列功能

    最近在研究rabbitmq,项目中有这样一个场景:在用户要支付订单的时候,如果超过30分钟未支付,会把订单关掉.当然我们可以做一个定时任务,每个一段时间来扫描未支付的订单,如果该订单超过支付时间就关闭 ...

  5. Spring Boot(十四)RabbitMQ延迟队列

    一.前言 延迟队列的使用场景:1.未按时支付的订单,30分钟过期之后取消订单:2.给活跃度比较低的用户间隔N天之后推送消息,提高活跃度:3.过1分钟给新注册会员的用户,发送注册邮件等. 实现延迟队列的 ...

  6. RabbitMQ 延迟队列,消息延迟推送

    目录 应用场景 消息延迟推送的实现 测试结果 应用场景 目前常见的应用软件都有消息的延迟推送的影子,应用也极为广泛,例如: 淘宝七天自动确认收货.在我们签收商品后,物流系统会在七天后延时发送一个消息给 ...

  7. 【RabbitMQ】一文带你搞定RabbitMQ延迟队列

    本文口味:鱼香肉丝   预计阅读:10分钟 一.说明 在上一篇中,介绍了RabbitMQ中的死信队列是什么,何时使用以及如何使用RabbitMQ的死信队列.相信通过上一篇的学习,对于死信队列已经有了更 ...

  8. SpringBoot RabbitMQ 延迟队列代码实现

    场景 用户下单后,如果30min未支付,则删除该订单,这时候就要可以用延迟队列 准备 利用rabbitmq_delayed_message_exchange插件: 首先下载该插件:https://ww ...

  9. rabbitmq延迟队列demo

    1. demo详解 1.1 工程结构: 1.2 pom 定义jar包依赖的版本.版本很重要,rabbit依赖spring,两者必须相一致,否则报错: <properties> <sp ...

随机推荐

  1. 洛谷 P1510 精卫填海

    洛谷 P1510 精卫填海 题目描述 [版权说明] 本题为改编题. [问题描述] 发鸠之山,其上多柘木.有鸟焉,其状如乌,文首,白喙,赤足,名曰精卫,其名自詨.是炎帝之少女,名曰女娃.女娃游于东海,溺 ...

  2. Notepad++使用心得和特色功能介绍 -> notepad/ultraedit的最好的替代品

    [详细]Notepad++使用心得和特色功能介绍 -> notepad/ultraedit的最好的替代品 最近在用Notepad++,发现的确是很不错的工具,具体特色,看了下面介绍就知道了. [ ...

  3. 洛谷 P1691 有重复元素的排列问题

    P1691 有重复元素的排列问题 题目描述 设R={r1,r2,……,rn}是要进行排列的n个元素.其中元素r1,r2,……,rn可能相同.使设计一个算法,列出R的所有不同排列. 给定n以及待排列的n ...

  4. jquery如何实现表单post方式提交

    jquery如何实现表单post方式提交 一.总结 一句话总结:即使js给form对象提供了submit()方法,那也不意为表单中可以不写提交按钮这个元素,即form表单依然需要五脏俱全才可以使用js ...

  5. POJ 1064 Cable master 浮点数二分

    http://poj.org/problem?id=1064 题目大意: 有N条绳子,他们的长度分别为Li,如果从它们中切割出k条长度相同的绳子的话,这K条绳子每条能有多长? 思路: 二分,设答案为m ...

  6. CSDN学院 免费技术答疑公开课,本周三场即将开播~~~

    为了酬谢广大学员.CSDN学院特推出免费技术答疑公开课,让您开启一段充实的学习之旅~ 本周三场即将开播! ----------------------------------------------- ...

  7. [Angular] Angular Advanced Features - ng-template , ng-container, ngTemplateOutlet

    Previously we have tab-panel template defined like this: <ul class="tab-panel-buttons" ...

  8. 将OpenCV捕获的摄像头加载到picture控件中

    CRect rect; CStatic* pStc; CDC* pDC; HDC hDC; pStc = (CStatic*)GetDlgItem(IDC_CAM);//IDC_CAM是Picture ...

  9. 【44.10%】【codeforces 723B】Text Document Analysis

    time limit per test1 second memory limit per test256 megabytes inputstandard input outputstandard ou ...

  10. Java链接Redis时出现 “ERR Client sent AUTH, but no password is set” 异常的原因及解决办法

    Java链接Redis时出现 "ERR Client sent AUTH, but no password is set" 异常的原因及解决办法 [错误提示] redis.clie ...