RabbitMQ 延迟队列实现订单支付结果异步阶梯性通知
在第三方支付中,例如支付宝、或者微信,对于订单请求,第三方支付系统采用的是消息同步返回、异步通知+主动补偿查询的补偿机制。
例如一个支付结果的通知,一方面会在支付页面跳转时候返回支付结果(一般只用作前端展示使用,非最终状态),同时会采用后台异步通知机制(有前台、后台通知的,以后台异步通知结果为准),但由于前台跳转、后台结果通知都可能失效,因此还以定时补单+请求方主动查询接口作为辅助手段。
常见的补单操作,任务调度策略一般设定30秒、60秒、3分钟、6分钟、10分钟调度多次(以自己业务需要),如果调度接收到响应确认报文,补单成功,则中止对应订单的调度任务;如果超过补单上限次数,则停止补单,避免无谓的资源浪费。请求端随时可以发起请求报文查询对应订单的状态。
所以最终打算使用rabbitmq的消息延迟+死信队列来实现。消息模型如下:
producer发布消息,通过exchangeA的消息会被分发到QueueA,Consumer监听queueA,一旦有消息到来就被消费,这边的消费业务就是通知前端,如果通知失败,就创建一个延迟队列declareQueue,设置每个消息的ttl然后通过declare_exchange将消息分发到declare_queue,因为declare_queue没有consumer并且declare_queue中的消息设置了ttl,当ttl到期后,将通过DEX路由到queueA,被重新消费。
-
package org.delayQueue;
-
-
import com.rabbitmq.client.BuiltinExchangeType;
-
import com.rabbitmq.client.Channel;
-
import com.rabbitmq.client.Connection;
-
import com.rabbitmq.client.ConnectionFactory;
-
-
public class DeclareQueue {
-
public static String EXCHANGE_NAME = "notifyExchange";
-
-
public static void init() {
-
ConnectionFactory factory = new ConnectionFactory();
-
factory.setHost("localhost");
-
factory.setPort(5672);
-
-
Connection connection = null;
-
try {
-
connection = factory.newConnection();
-
Channel channel = connection.createChannel();
-
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
-
String routingKey = "AliPaynotify";
-
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";
-
channel.basicPublish(EXCHANGE_NAME, routingKey, null, message.getBytes());
-
System.out.println(" [x] Sent :" + message);
-
} catch (Exception e) {
-
// TODO Auto-generated catch block
-
e.printStackTrace();
-
} finally {
-
if (connection != null) {
-
try {
-
connection.close();
-
} catch (Exception ignore) {
-
}
-
}
-
}
-
}
-
-
public static void main(String args[]) {
-
init();
-
}
-
-
}
DeclareConsumer.java
-
package org.delayQueue;
-
-
import java.io.BufferedReader;
-
import java.io.IOException;
-
import java.io.InputStreamReader;
-
import java.util.ArrayList;
-
import java.util.HashMap;
-
import java.util.List;
-
import java.util.Map;
-
import java.util.Map.Entry;
-
-
import org.apache.http.HttpResponse;
-
import org.apache.http.client.ClientProtocolException;
-
import org.apache.http.client.HttpClient;
-
import org.apache.http.client.methods.HttpPost;
-
import org.apache.http.impl.client.DefaultHttpClient;
-
-
import com.rabbitmq.client.AMQP;
-
import com.rabbitmq.client.Channel;
-
import com.rabbitmq.client.Connection;
-
import com.rabbitmq.client.ConnectionFactory;
-
import com.rabbitmq.client.Consumer;
-
import com.rabbitmq.client.DefaultConsumer;
-
import com.rabbitmq.client.Envelope;
-
-
public class DeclareConsumer {
-
public static String EXCHANGE_NAME = "notifyExchange";
-
public static String QU_declare_15S = "Qu_declare_15s";
-
public static String EX_declare_15S = "EX_declare_15s";
-
public static String ROUTINGKEY = "AliPaynotify";
-
public static Connection connection = null;
-
public static Channel channel = null;
-
public static Channel DECLARE_15S_CHANNEL = null;
-
public static String declare_queue = "init";
-
public static String originalExpiration = "0";
-
public static void init() throws Exception {
-
ConnectionFactory factory = new ConnectionFactory();
-
factory.setHost("localhost");
-
factory.setPort(5672);
-
connection = factory.newConnection();
-
channel = connection.createChannel();
-
DECLARE_15S_CHANNEL = connection.createChannel();
-
}
-
-
public static void consume() {
-
try {
-
channel.exchangeDeclare(EXCHANGE_NAME, "topic");
-
final String queueName = channel.queueDeclare().getQueue();
-
-
channel.queueBind(queueName, EXCHANGE_NAME, ROUTINGKEY);
-
System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
-
final Consumer consumer = new DefaultConsumer(channel) {
-
@Override
-
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
-
String message = new String(body, "UTF-8");
-
Map<String, Object> headers = properties.getHeaders();
-
if (headers != null) {
-
List<Map<String, Object>> xDeath = (List<Map<String, Object>>) headers.get("x-death");
-
System.out.println("xDeath--- > " + xDeath);
-
if (xDeath != null && !xDeath.isEmpty()) {
-
Map<String, Object> entrys = xDeath.get(0);
-
// for(Entry<String, Object>
-
// entry:entrys.entrySet()){
-
// System.out.println(entry.getKey()+":"+entry.getValue());
-
// }
-
originalExpiration = entrys.get("original-expiration").toString();
-
}
-
}
-
System.out.println(" [x] Received '" + envelope.getRoutingKey() + "':'" + message + "'" + "time" + System.currentTimeMillis());
-
HttpClient httpClient = new DefaultHttpClient();
-
HttpPost post = new HttpPost(message);
-
HttpResponse response = httpClient.execute(post);
-
BufferedReader inreader = null;
-
if (response.getStatusLine().getStatusCode() == 200) {
-
inreader = new BufferedReader(new InputStreamReader(response.getEntity().getContent(), "UTF-8"));
-
StringBuffer responseBody = new StringBuffer();
-
String line = null;
-
while ((line = inreader.readLine()) != null) {
-
responseBody.append(line);
-
}
-
if (!responseBody.equals("success")) {
-
// putDeclre15s(message);
-
if (originalExpiration.equals("0")) {
-
putDeclreQueue(message, 3000, QU_declare_15S);
-
}
-
if (originalExpiration.equals("3000")) {
-
putDeclreQueue(message, 30000, QU_declare_15S);
-
}
-
if (originalExpiration.equals("30000")) {
-
putDeclreQueue(message, 60000, QU_declare_15S);
-
}
-
if (originalExpiration.equals("60000")) {
-
putDeclreQueue(message, 120000, QU_declare_15S);
-
}
-
if (originalExpiration.equals("120000")) {
-
putDeclreQueue(message, 180000, QU_declare_15S);
-
}
-
if (originalExpiration.equals("180000")) {
-
putDeclreQueue(message, 300000, QU_declare_15S);
-
}
-
if (originalExpiration.equals("300000")) {
-
// channel.basicConsume(QU_declare_300S,true, this);
-
System.out.println("finish notify");
-
}
-
}
-
} else {
-
System.out.println(response.getStatusLine().getStatusCode());
-
}
-
}
-
};
-
-
channel.basicConsume(queueName, true, consumer);
-
} catch (Exception e) {
-
e.printStackTrace();
-
} finally {
-
}
-
}
-
-
-
-
static Map<String, Object> xdeathMap = new HashMap<String, Object>();
-
static List<Map<String, Object>> xDeath = new ArrayList<Map<String, Object>>();
-
static Map<String, Object> xdeathParam = new HashMap<String, Object>();
-
-
public static void putDeclre15s(String message) throws IOException {
-
channel.exchangeDeclare(EX_declare_15S, "topic");
-
Map<String, Object> args = new HashMap<String, Object>();
-
args.put("x-dead-letter-exchange", EXCHANGE_NAME);// 死信exchange
-
AMQP.BasicProperties.Builder builder = new AMQP.BasicProperties.Builder();
-
builder.expiration("3000").deliveryMode(2);// 设置消息TTL
-
AMQP.BasicProperties properties = builder.build();
-
channel.queueDeclare(QU_declare_15S, false, false, false, args);
-
channel.queueBind(QU_declare_15S, EX_declare_15S, ROUTINGKEY);
-
channel.basicPublish(EX_declare_15S, ROUTINGKEY, properties, message.getBytes());
-
System.out.println("send message in QA_DEFERRED_15S" + message + "time" + System.currentTimeMillis());
-
}
-
-
public static void putDeclreQueue(String message, int mis, String queue) throws IOException {
-
channel.exchangeDeclare(EX_declare_15S, "topic");
-
Map<String, Object> args = new HashMap<String, Object>();
-
args.put("x-dead-letter-exchange", EXCHANGE_NAME);// 死信exchange
-
AMQP.BasicProperties.Builder builder = new AMQP.BasicProperties.Builder();
-
builder.expiration(String.valueOf(mis)).deliveryMode(2);// 设置消息TTL
-
AMQP.BasicProperties properties = builder.build();
-
channel.queueDeclare(queue, false, false, false, args);
-
channel.queueBind(queue, EX_declare_15S, ROUTINGKEY);
-
channel.basicPublish(EX_declare_15S, ROUTINGKEY, properties, message.getBytes());
-
System.out.println("send message in " + queue + message + "time============" + System.currentTimeMillis());
-
}
-
-
public static void main(String args[]) throws Exception {
-
init();
-
consume();
-
}
-
}
消息通过dlx转发的情况下,header头部会带有x-death的一个数组,里面包含消息的各项属性,比如说消息成为死信的原因reason,original-expiration这个字段表示消息在原来队列中的过期时间,根据这个值来确定下一次通知的延迟时间应该是多少秒。
RabbitMQ 延迟队列实现订单支付结果异步阶梯性通知的更多相关文章
- C# RabbitMQ延迟队列功能实战项目演练
一.需求背景 当用户在商城上进行下单支付,我们假设如果8小时没有进行支付,那么就后台自动对该笔交易的状态修改为订单关闭取消,同时给用户发送一份邮件提醒.那么我们应用程序如何实现这样的需求场景呢?在之前 ...
- RabbitMQ延迟队列
rabbitmq延迟队列 rabbitmq实现延迟队列用了rabbitmq-delayed-message-exchange插件,需要提前安装,并启用. 原理 其原理是通过Exchange来实现延迟功 ...
- RabbitMQ延迟队列插件安装
RabbitMQ延迟队列插件安装 一.下载插件 下载地址:https://www.rabbitmq.com/community-plugins.html 二.把下载的插件放到指定位置 下载的文件为zi ...
- C#实现rabbitmq 延迟队列功能
最近在研究rabbitmq,项目中有这样一个场景:在用户要支付订单的时候,如果超过30分钟未支付,会把订单关掉.当然我们可以做一个定时任务,每个一段时间来扫描未支付的订单,如果该订单超过支付时间就关闭 ...
- Spring Boot(十四)RabbitMQ延迟队列
一.前言 延迟队列的使用场景:1.未按时支付的订单,30分钟过期之后取消订单:2.给活跃度比较低的用户间隔N天之后推送消息,提高活跃度:3.过1分钟给新注册会员的用户,发送注册邮件等. 实现延迟队列的 ...
- RabbitMQ 延迟队列,消息延迟推送
目录 应用场景 消息延迟推送的实现 测试结果 应用场景 目前常见的应用软件都有消息的延迟推送的影子,应用也极为广泛,例如: 淘宝七天自动确认收货.在我们签收商品后,物流系统会在七天后延时发送一个消息给 ...
- 【RabbitMQ】一文带你搞定RabbitMQ延迟队列
本文口味:鱼香肉丝 预计阅读:10分钟 一.说明 在上一篇中,介绍了RabbitMQ中的死信队列是什么,何时使用以及如何使用RabbitMQ的死信队列.相信通过上一篇的学习,对于死信队列已经有了更 ...
- SpringBoot RabbitMQ 延迟队列代码实现
场景 用户下单后,如果30min未支付,则删除该订单,这时候就要可以用延迟队列 准备 利用rabbitmq_delayed_message_exchange插件: 首先下载该插件:https://ww ...
- rabbitmq延迟队列demo
1. demo详解 1.1 工程结构: 1.2 pom 定义jar包依赖的版本.版本很重要,rabbit依赖spring,两者必须相一致,否则报错: <properties> <sp ...
随机推荐
- 限制tomcat仅响应本机请求(转)
http://blog.bbzhh.com/index.php/archives/135.html 在VPS上搭建了nginx和tomcat应用,想通过nginx来反向代理127.0.0.1:8080 ...
- 【BZOJ 1146】【CTSC 2008】网络管理network
一句话题意,树链上带改动区间第k大 感觉能够dfs+主席树O(nlog2n)过掉,但我不会写= = 于是写的线段树套平衡树+链剖+二分(改动O(nlog3n),查询O(nlog4n)慢了好多啊QAQ) ...
- Android Material风格的应用(四)--FloatActionButton
添加 FloatActionButton和SnackBar Android Material风格的应用(一)--AppBar TabLayoutAndroid Material风格的应用(二)--Re ...
- php课程 11-37 类和对象的关系是什么
php课程 11-37 类和对象的关系是什么 一.总结 一句话总结:类生成对象,对象是类的实例化,一定是先有类,后有对象,一定是先有标准,再有个体. 1.oop的三大优势是什么? 重用性,灵活性.扩展 ...
- postman和fiddler的基本使用
本文转自:https://www.cnblogs.com/qq909283/p/6826578.html 写在前面:本文主要的章节规划: 1.什么是接口测试 另外,有的时候会直接调用别的公司的接口,比 ...
- SQL判断空值、nvl处理与JOIN的使用
LIKE子句会影响查询性能,所以在明确知道字符个数时,应该使用'_',而不使用'%'. 判断空值/非空值 SELECT select_list FROM table_list/view_list WH ...
- 使用BeautifulSoup爬取“0daydown”站点的信息(2)——字符编码问题解决
上篇中的程序实现了抓取0daydown最新的10页信息.输出是直接输出到控制台里面.再次改进代码时我准备把它们写入到一个TXT文档中.这是问题就出来了. 最初我的代码例如以下: #-*- coding ...
- 如何在vue项目中使用百度编辑器ueditor
百度编辑器官方并没有提供vue项目使用说明,目前网上也有不少人实现了相关功能,这里就不再重复,只是针对自身项目碰到的情况做个记录,就当是熟悉了一遍富文本编辑器的代码结构. 按照网上的做法,基本可以实现 ...
- C#实现人脸识别
C#实现基于ffmpeg加虹软的人脸识别 关于人脸识别 目前的人脸识别已经相对成熟,有各种收费免费的商业方案和开源方案,其中OpenCV很早就支持了人脸识别,在我选择人脸识别开发库时,也横向对比了三种 ...
- [TypeStyle] Use fallback values in TypeStyle for better browser support
You can increase the browser support of your CSS using fallback values and vendor prefixes. This les ...