前言

本篇随笔将汇总一些我对消息队列 RabbitMQ 的认识,顺便谈谈其在高并发和秒杀系统中的具体应用。

1. 预备示例

想了下,还是先抛出一个简单示例,随后再根据其具体应用场景进行扩展,我觉得这样表述条理更清晰些。

RabbitConfig:

@Configuration
public class RabbitConfig { @Bean
public Queue callQueue() {
return new Queue(MQConstant.CALL);
}
}

Client:

@Component
public class Client { @Autowired
private RabbitTemplate rabbitTemplate; public void sendCall(String content) {
for (int i = 0; i < 10000; i++) {
String message = i + "-" + content;
System.out.println(String.format("Sender: %s", message));
rabbitTemplate.convertAndSend(MQConstant.CALL, message);
}
}
}

Server:

@Component
public class Server { @RabbitHandler
@RabbitListener(queues = MQConstant.CALL)
public void callProcess(String message) throws InterruptedException {
Thread.sleep(1000);
System.out.println(String.format("Receiver: reply(\"%s\") Yes, I just saw your message!", message));
} }

Result:

Sender: Hello, are you there!
Receiver: reply("Hello, are you there!") Yes, I just saw your message!

以上示例会在 rabbitmq 中创建一条队列 CALL, 消息在其中等待消费:

在此基础上的简单扩展我就不再写案例了,比如领域模块完成了其核心业务规则之后可能需要更新缓存、写个邮件、记个复杂日志、做个统计报表等等,这些不需要及时反馈或者耗时的附属业务都可以通过异步队列分发,以此来提升核心业务的响应速度,同时如此处理能让领域边界更加清晰,代码的可维护性和持续拓展的能力也会有所提升。

2. 削峰

上个示例中我提到的应用场景是解耦和通知,再接着扩展,因其具备良好的缓冲性质,所以还有一个非常适合的应用场景那就是削峰。对于突如其来的极高并发请求,我们可以先瞬速地将其加入队列并回复用户一个友好提示,然后服务器可在其能承受的范围内慢慢处理,以此来防止突发的 CPU 和内存 “爆表”。

改造之后对于发送方来说当然是比较爽的,他只是将请求加入消息队列而已,处理压力都归到了消费端。接着思考,这样处理有没有副作用?如果这个请求刚好是线程阻塞的,那还要加入队列慢慢排队处理,那不是完蛋了,用户要猴年马月才能得到反馈?所以针对此,我觉得应该将消费端的方法改为异步调用(即多线程)以提升吞吐量,在 Spring Boot 中的写法也非常简单:

@Component
public class Server { @Async
@RabbitHandler
@RabbitListener(queues = MQConstant.CALL)
public void callProcess(String message) throws InterruptedException {
Thread.sleep(100);
System.out.println(String.format("Receiver: reply(\"%s\") Yes, I just saw your message!", message));
} }

参照示例一的方法,我发布了 10000 条消息加入队列,且消费端的调用每次阻塞一秒,那可有意思了,什么时候能处理完?但如果开几百个线程同时处理的话,那几十秒就够了,当然具体多少合适还应根据具体的业务场景和服务器配置酌情考虑。另外,别忘了配线程池:

@Configuration
public class AsyncConfig { @Bean
public Executor asyncExecutor(){
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(500);
executor.setQueueCapacity(10); executor.setThreadNamePrefix("MyExecutor-"); executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
}

3. Exchange

RabbitMQ 可能为 N 个应用同时提供服务,要是你和你的蓝颜知己突然心有灵犀,在不同的业务上使用了同一个 routingKey,想想就刺激。因此,队列多了自然要进行分组管理,限定好 Exchange 的规则,接下来就可以独自玩耍了。

MQConstant:

public class MQConstant {

    public static final String EXCHANGE = "YOUCLK-MESSAGE-EXCHANGE";

    public static final String CALL = MQConstant.EXCHANGE + ".CALL";

    public static final String ALL = MQConstant.EXCHANGE + ".#";
}

RabbitConfig:

@Configuration
public class RabbitConfig { @Bean
public Queue callQueue() {
return new Queue(MQConstant.CALL);
} @Bean
TopicExchange exchange() {
return new TopicExchange(MQConstant.EXCHANGE);
} @Bean
Binding bindingExchangeMessage(Queue queueMessage, TopicExchange exchange) {
return BindingBuilder.bind(queueMessage).to(exchange).with(MQConstant.ALL);
}
}

此时我们再去查队列 CALL,可以看到已经绑定了Exchange:

当然 Exchange 的作用远不止如此,以上示例为 Topic 模式,除此之外还有 Direct、Headers 和 Fanout 模式,写法都差不多,感兴趣的童鞋可以去查看 “官方文档” 进行更深入了解。

4. 延时队列

延时任务的场景相信小伙伴们都接触过,特别是抢购的时候,在规定时间内未付款订单就被回收了。微信支付的 API 里面也有一个支付完成后的延时再确认消息推送,实现原理应该都差不多。

利用 RabbitMQ 实现该功能首先要了解他的两个特性,分别是 Time-To-Live Extensions 和 Dead Letter Exchanges,字面意思上就能理解个大概,一个是生存时间,一个是死信。整个过程也很容易理解,TTL 相当于一个缓冲队列,等待其过期之后消息会由 DLX 转发到实际消费队列,如此便实现了他的延时过程。

MQConstant:

public class MQConstant {

    public static final String PER_DELAY_EXCHANGE = "PER_DELAY_EXCHANGE";

    public static final String DELAY_EXCHANGE = "DELAY_EXCHANGE";

    public static final String DELAY_CALL_TTL = "DELAY_CALL_TTL";

    public static final String CALL = "CALL";

}

ExpirationMessagePostProcessor:

public class ExpirationMessagePostProcessor implements MessagePostProcessor {
private final Long ttl; public ExpirationMessagePostProcessor(Long ttl) {
this.ttl = ttl;
} @Override
public Message postProcessMessage(Message message) throws AmqpException {
message.getMessageProperties()
.setExpiration(ttl.toString());
return message;
}
}

Client:

@Component
public class Client { @Autowired
private RabbitTemplate rabbitTemplate; public void sendCall(String content) {
for (int i = 1; i <= 3; i++) {
long expiration = i * 5000;
String message = i + "-" + content;
System.out.println(String.format("Sender: %s", message));
rabbitTemplate.convertAndSend(MQConstant.DELAY_CALL_TTL, (Object) message, new ExpirationMessagePostProcessor(expiration)); }
}
}

Server:

@Component
public class Server { @Async
@RabbitHandler
@RabbitListener(queues = MQConstant.CALL)
public void callProcess(String message) throws InterruptedException {
String date = (new SimpleDateFormat("HH:mm:ss")).format(new Date());
System.out.println(String.format("Receiver: reply(\"%s\") Yes, I just saw your message!- %s", message, date));
} }

Result:

Sender: 1-Hello, are you there!
Sender: 2-Hello, are you there!
Sender: 3-Hello, are you there!
Receiver: reply("1-Hello, are you there!") Yes, I just saw your message!- 23:04:12
Receiver: reply("2-Hello, are you there!") Yes, I just saw your message!- 23:04:17
Receiver: reply("3-Hello, are you there!") Yes, I just saw your message!- 23:04:22

结果一目了然,分别在队列中延迟了 5秒,10秒,15秒,当然,以上只是我的简单示例,童鞋们可翻阅官方文档(“ ttl ” && “ dlx ”)进一步深入学习。

结语

本篇随笔不该就这么结束,但晚上心情不好,百感交集,无法继续写作,无奈至此。近期正在寻觅新的工作机会,我的微信:youclk,无论有没有推荐的,给我点鼓励,谢谢!


我的公众号《有刻》,我们共同成长!

Java 小记 — RabbitMQ 的实践与思考的更多相关文章

  1. 《Java 程序设计》课堂实践项目 课后学习总结

    <Java 程序设计>课堂实践项目 课后学习总结 String类的使用(sort) 目录 Linux命令(sort) 课堂实践 课后思考 学习老师的代码之后的思考:int与Integer ...

  2. 《Java 程序设计》课堂实践项目-数据库

    <Java 程序设计>课堂实践项目数据库 课后学习总结 目录 数据库实验要求 课堂实践成果 课后思考 由于担心做的不好,找同学询问了数据库的问题,学习了数据库的连通,补写的这篇博客.这是补 ...

  3. 《Java 程序设计》课堂实践项目-类定义

    <Java 程序设计>课堂实践项目类定义 课后学习总结 目录 改变 类定义实验要求 课堂实践成果 课后思考 改变 修改了博客整体布局,过去就贴个代码贴个图很草率,这次布局和内容都有修改. ...

  4. 《Java 程序设计》课堂实践项目-简易计算器

    <Java 程序设计>课堂实践项目简易计算器 课后学习总结 目录 改变 简易计算器实验要求 课堂实践成果 课后思考 改变 修改了博客整体布局,过去就贴个代码贴个图很草率,这次布局和内容都有 ...

  5. 《Java 程序设计》课堂实践项目-Arrays和String单元测试

    <Java 程序设计>课堂实践项目-Arrays和String单元测试 课后学习总结 目录 改变 Arrays和String单元测试实验要求 课堂实践成果 课后思考 改变 修改了博客整体布 ...

  6. 《Java程序设计》课堂实践内容总结

    <Java程序设计>课堂实践内容总结 实践一 要求 修改教材P98 Score2.java, 让执行结果数组填充是自己的学号: 提交在IDEA或命令行中运行结查截图,加上学号水印,没学号的 ...

  7. 《Java 程序设计》课堂实践项目-命令行参数

    <Java 程序设计>课堂实践项目 课后学习总结 目录 改变 命令行参数实验要求 课堂实践成果 课后思考 改变 修改了博客整体布局,过去就贴个代码贴个图很草率,这次布局和内容都有修改.加了 ...

  8. 《Java 程序设计》课堂实践项目-mini dc

    <Java 程序设计>课堂实践项目-后缀表达式 课后学习总结 目录 改变 mini dc实验要求 后缀表达式介绍 课堂实践成果 课后思考 改变 修改了博客整体布局,改变了之前贴个截图粘个代 ...

  9. Java 异步处理简单实践

    Java 异步处理简单实践 http://www.cnblogs.com/fangfan/p/4047932.html 同步与异步 通常同步意味着一个任务的某个处理过程会对多个线程在用串行化处理,而异 ...

随机推荐

  1. R语言︱ROC曲线——分类器的性能表现评价

    笔者寄语:分类器算法最后都会有一个预测精度,而预测精度都会写一个混淆矩阵,所有的训练数据都会落入这个矩阵中,而对角线上的数字代表了预测正确的数目,即True Positive+True Nagetiv ...

  2. 芝麻HTTP: Python爬虫利器之Requests库的用法

    前言 之前我们用了 urllib 库,这个作为入门的工具还是不错的,对了解一些爬虫的基本理念,掌握爬虫爬取的流程有所帮助.入门之后,我们就需要学习一些更加高级的内容和工具来方便我们的爬取.那么这一节来 ...

  3. MongoDB的安装和配置(Windows系统)及遇到的常见问题解答

    目前比较流行的数据库大致可以分为三种: 前两种是按照图论理论建立起来的,分别是: 层次式数据库(IMS(Information Management System)是其典型代表)和 网络式数据库(DB ...

  4. DELL XPS 13 9350 装Win7系统(坑爹)

    0.记一次悲惨的装机记录 1.为什么这么难装呢? 因为这个NB本身是为Win10设计的,所以官网没有Win7驱动,系统设置各种不兼容 2.希望你能看到本文最后 因为你看到最后,你就不会给这个逗比装Wi ...

  5. 初入前端框架bootstrap--Web前端

    Bootstraps是一种简洁.直观.强悍的前端开发框架,它让web开发更迅速.简单.对于初入Bootstrap的小白,高效进入主题很重要,能为我们节省很多时间,下面我将对使用Bootstrap开发前 ...

  6. myeclipse 打开jsp文件出错

    第一步:找到MyEclipse的安装路径:第二步:删除掉MyEclipse\configuration下名为:org.eclipse.update的文件夹:第三步:产出之后重启myeclipse,在打 ...

  7. UVA10692:Huge Mods

    题面 传送门 题意 输入正整数a1,a2,a3..an和模m,求a1^a2^...^an mod m Sol 首先有\[ a^b\equiv \begin{cases} a^{b\%\phi(p)}~ ...

  8. React-Native安装使用

    先附上React-Native官方文档中文版:http://wiki.jikexueyuan.com/project/react-native/getting-started.html 好,接下来我们 ...

  9. 解决PhpMyadmin1440秒未活动自动退出

    解决方法如下:#vim phpMyAdmin/libraries/config.default.php找到如下位置$cfg['LoginCookieValidity'] = ;    将1440修改成 ...

  10. 大数据Hadoop与Spark学习经验谈

    昨晚听了下Hulu大数据基础架构组负责人–董西成的关于大数据学习方法的直播,挺有收获的,下面截取一些PPT的关键内容,希望对正在学习大数据的人有帮助. 现状是目前存在的问题,比如找百度.查书这种学习方 ...