Rabbitmq消息投递确认有四种方案
- 通过RabbitMQ事务
@Test
public void test02() throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost(HOST);
connectionFactory.setPort(PORT);
connectionFactory.setUsername(USERNAME);
connectionFactory.setPassword(PASSWORD);
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
try {
//开启事务
channel.txSelect();
channel.basicPublish(EXCHANGE_NAME, ROUNTING_KEY, MessageProperties.PERSISTENT_TEXT_PLAIN,
("hello world ").getBytes());
System.out.println(1 / 0);
//提交
channel.txCommit();
} catch (Exception e) {
e.printStackTrace();
//回滚
channel.txRollback();
} finally {
channel.close();
}
if (connection != null) {
connection.close();
}
}
开 启 R a b b i t m q 的 事 务 , 然 后 推 送 消 息 , 后 续 如 果 异 常 , 则 回 滚 。 该 种 方 式 对 r a b b i t m q 的 性 能 会 产 生 较 大 影 响 。 \color{#FF0000}{开启Rabbitmq的事务,然后推送消息,后续如果异常,则回滚。该种方式对rabbitmq的性能会产生较大影响。} 开启Rabbitmq的事务,然后推送消息,后续如果异常,则回滚。该种方式对rabbitmq的性能会产生较大影响。
- 同步Confirm模式
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost(HOST);
connectionFactory.setPort(PORT);
connectionFactory.setUsername(USERNAME);
connectionFactory.setPassword(PASSWORD);
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
try {
//设置confirm模式
channel.confirmSelect();
channel.basicPublish(EXCHANGE_NAME, ROUNTING_KEY, MessageProperties.PERSISTENT_TEXT_PLAIN,
("hello world ").getBytes());
if (!channel.waitForConfirms()) {
System.out.println("发送消息失败");
//重试
}else{
System.out.println("投递成功");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
channel.close();
connection.close();
}
该 种 方 式 能 较 事 务 那 种 方 式 性 能 能 提 升 一 点 , 但 是 也 是 一 种 串 行 方 式 , 即 推 送 一 条 信 息 , 然 后 确 认 。 \color{#FF0000}{该种方式能较事务那种方式性能能提升一点,但是也是一种串行方式,即推送一条信息,然后确认。} 该种方式能较事务那种方式性能能提升一点,但是也是一种串行方式,即推送一条信息,然后确认。
- 同步批量confirm
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost(HOST);
connectionFactory.setPort(PORT);
connectionFactory.setUsername(USERNAME);
connectionFactory.setPassword(PASSWORD);
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
channel.confirmSelect();
int count = 0;
int batchPublishNum = 10;
List<Integer> failList = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
channel.basicPublish(EXCHANGE_NAME, ROUNTING_KEY, MessageProperties.PERSISTENT_TEXT_PLAIN,
("hello world " + i).getBytes());
failList.add(i);
if (++count >= batchPublishNum) {
//开始检查一批是否提交成功
count = 0;
if (channel.waitForConfirms()) {
//投递成功
failList.clear();
} else {
//投递失败--重新投递
for (int j = 0; j < failList.size(); j++) {
channel.basicPublish(EXCHANGE_NAME, ROUNTING_KEY, MessageProperties.PERSISTENT_TEXT_PLAIN,
("hello world " + j).getBytes());
}
}
}
}
channel.close();
connection.close();
批 量 c o n f i r m 较 单 条 性 能 能 提 升 不 少 , 减 少 了 交 互 , \color{#FF0000}{批量confirm较单条性能能提升不少,减少了交互,} 批量confirm较单条性能能提升不少,减少了交互,
- 异步回调
//推送消息
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost(HOST);
connectionFactory.setPort(PORT);
connectionFactory.setUsername(USERNAME);
connectionFactory.setPassword(PASSWORD);
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
//开启confirm确认
channel.confirmSelect();
channel.exchangeDeclare(EXCHANGE_NAME, "direct", true, false, null);
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, ROUNTING_KEY);
//推送消息
for (int i = 0; i < 100; i++) {
long seqNo = channel.getNextPublishSeqNo();
channel.basicPublish(EXCHANGE_NAME, ROUNTING_KEY, MessageProperties.PERSISTENT_TEXT_PLAIN,
("hello world " + i).getBytes());
seqNoSet.add(seqNo);
}
System.out.println("推送完成");
//监控推送回调
channel.addConfirmListener(new ConfirmListener() {
@Override
public void handleAck(long deliveryTag, boolean multiple) throws IOException {
System.out.println("ack:" + deliveryTag + " multiple:" + multiple);
if (multiple) {
seqNoSet.headSet(deliveryTag + 1).clear();
} else {
seqNoSet.remove(deliveryTag);
}
}
@Override
public void handleNack(long deliveryTag, boolean multiple) throws IOException {
System.out.println("nack:" + deliveryTag + " multiple:" + multiple);
if (multiple) {
seqNoSet.headSet(deliveryTag + 1).clear();
} else {
seqNoSet.remove(deliveryTag);
}
//处理投递失败的场景
}
});
Thread.sleep(200000);
channel.close();
connection.close();
while (Thread.activeCount() > 2) {
System.out.println(seqNoSet.size());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("run over");
异 步 回 调 较 批 量 确 认 性 能 提 升 了 一 点 。 \color{#FF0000}{异步回调较批量确认性能提升了一点。} 异步回调较批量确认性能提升了一点。
100%消息投递成功方案
-
消息落库打标
该方案的核心的是通过表记录投递成功与否,失败的通过定时任务进行重新推送。 -
消息延迟投递,做二次确认。
说明: -
推送消息会推送两次,第一次是真正的业务消息,如果消费失败,那么监听到消费失败后会修改msg表的标识,第二次只是检查一下是否推送成功,检查msg表如果为失败,则发送消息重新推送业务消息。
图片摘自某课网。
幂等性
重复提交是在第一次请求已经成功的情况下,人为的进行多次操作,导致不满足幂等要求的服务多次改变状态。而幂等更多使用的情况是第一次请求不知道结果(比如超时)或者失败的异常情况下,发起多次请求,目的是多次确认第一次请求成功,却不会因多次请求而出现多次的状态变化
针对RabbitMQ,消费端收到多条也只消费一次
- 利用redis的原子性去实现。
需要解决的问题
1 实现写redis和写数据库原子性操作。
可以采用均写redis,定时同步到缓存中。