前言

  上篇介绍了AMQP的基本概念,组成及其与rabbitmq的关系。了解了这些东西后,下面我们开始学习rabbitmq(消息队列)的作用以及用java代码和rabbitmq通讯进行消息发布和接收。因为消息的的接收以及路由都是通过交换机实现的,所以接下来我们要学习如何利用不同的交换机进行消息的发布。最后会再学习如何利用rabbitmq进行rpc的调用。

一、rabbitmq(消息队列)的作用

1.异步处理消息

  假设用户在网站注册成功后,需要向用户发送邮件和信息提示其注册成功。正常的做法是,后台将注册信息写入数据库,然后再给用户发邮件发短信。

  

  该流程的问题在于,我们其实只需要将注册信息写入数据库之后就可以告知用户注册成功,并不需要等待发送邮件和发送短信成功后再去告知。这样会延长请求处理的时间。利用消息队列我们可以异步的解决这个问题。

  

  我们可以在将注册信息写入数据库之后,把要发送注册邮件和发送短信的消息写入消息队列,然后就告知用户注册成功。发送邮件和短信将由订阅了消息的应用异步的去执行。这样将节省请求处理的时间。

2.系统解耦

  购物网站通常会将订单系统和库存系统分成两个不同的应用。正常情况下用户下单后订单系统会调用库存系统,然后返回给用户信息。

  

  这里有两个问题:1.如果库存系统挂了,那么下单就会失败。

          2.订单系统和库存系统耦合度太高

  为了解决这个问题就可以引入消息队列

  

  订单系统在处理完业务逻辑后,将订单消息写入消息队列,库存系统订阅订单消息,消息队列就会将订单消息推送给库存系统。库存系统再去处理。这样就解决了上述两个问题。即使库存系统挂了了,消息队列也会将订单消息持久化,保证库存系统正常后,可以正确的处理库存。

3.流量削峰

  流量削峰在秒杀活动中应用广泛

  场景:秒杀活动,一般会因为流量过大,导致应用挂掉,为了解决这个问题,一般在应用前段加入消息队列。此时起到两个作用:

    a.可以控制活动人数,超过一定阈值的订单直接丢弃

    b.可以缓解短时间的高流量压垮应用(应用程序按自己的最大处理能力获取订单)

  

  用户发来请求,服务器收到之后,先写入消息队列,加入消息超过队列的最大长度,则直接丢掉用户请求,或跳转到错误页面。秒杀业务再根据队列中的消息,做后续处理。

二、准备

在写代码之前,还需要我们先安装rabbitmq,因为rabbitmq是用erlang开发的,所以还需要我们下载erlang。具体下载的教程,这里不再讲解,网站有很多教程,可以学习一下。这里说一下比较关键的几个点。

  1.rabbitmq有个管理后台,访问地址为localhost:15672,默认的用户名:guest,默认的密码:guest

  2.client端通信口5672

  3.管理口15672

  4.server间内部通信口25672

  5.erlang发现口:4369

三、直连交换机代码实现

  1.生产者

  

  1. /**
  2. * 消息发送者
  3. */
  4. public class LogProducer {
  5. //交换机名字
  6. private static final String EXCHANGE_NAME = "direct_logs";
  7. // 路由关键字
  8. private static final String[] routingKeys = new String[]{"info" ,"warning", "error"};
  9.  
  10. public static void main(String[] args) {
  11. //创建连接工厂并设置连接信息,这些连接信息都是默认的,所以这里注释掉了,打开也是可以的。
  12. ConnectionFactory connectionFactory = new ConnectionFactory();
  13. // connectionFactory.setHost("localhost");
  14. // connectionFactory.setUsername("guest");
  15. // connectionFactory.setPassword("guest");
  16. // connectionFactory.setPort(5672);
  17. Connection connection = null;
  18. Channel channel = null;
  19. try {
  20. //获取连接
  21. connection = connectionFactory.newConnection();
  22. //连接中打开通道
  23. channel = connection.createChannel();
  24. //声明交换机
  25. //参数1:交换机名字,参数2:交换机类型
  26. channel.exchangeDeclare(EXCHANGE_NAME, "direct");
  27.  
  28. for (String routingKey : routingKeys){
  29. //将消息发送给交换机,我们这里发送的消息就是routingKey
  30. //参数1:交换机名字,参数2:消息路由键,参数3:消息属性,参数4:消息体
  31. channel.basicPublish(EXCHANGE_NAME, routingKey,null, routingKey.getBytes());
  32. System.out.println("RoutingSendDirect -> routingkey: " + routingKey + ", send message " + routingKey);
  33. }
  34. } catch (IOException e) {
  35. e.printStackTrace();
  36. } finally {
  37. try {
  38. channel.close();
  39. connection.close();
  40. } catch (IOException e) {
  41. e.printStackTrace();
  42. }
  43.  
  44. }
  45. }
  46. }

  2.消费者

  消费者1

  

  1. /**
  2. * 消息消费者1
  3. */
  4. public class Consumer1 {
  5. private static final String EXCHANGE_NAME = "direct_logs";
  6. // 路由关键字
  7. private static final String[] routingKeys = new String[]{"info", "warning"};
  8.  
  9. public static void main(String[] args) {
  10. //创建连接工厂并设置连接信息,这些连接信息都是默认的,所以这里注释掉了,打开也是可以的。
  11. ConnectionFactory connectionFactory = new ConnectionFactory();
  12. // connectionFactory.setHost("localhost");
  13. // connectionFactory.setUsername("guest");
  14. // connectionFactory.setPassword("guest");
  15. // connectionFactory.setPort(5672);
  16. Connection connection = null;
  17. Channel channel = null;
  18. try {
  19. connection = connectionFactory.newConnection();
  20. channel = connection.createChannel();
  21. //声明队列,这里队列的名字由代理自动生成
  22. String queueName = channel.queueDeclare().getQueue();
  23. //声明交换机
  24. //参数1:交换机名字,参数2:交换机类型
  25. channel.exchangeDeclare(EXCHANGE_NAME, "direct");
  26.  
  27. for (String routingKey : routingKeys) {
  28. //将交换机和队列用routing key绑定起来
  29. //参数1:队列名,参数2:交换机名,参数3:队列和交换机之间绑定的路由键
  30. channel.queueBind(queueName, EXCHANGE_NAME, routingKey);
  31. System.out.println("ReceveLogDirect1 -> queue: " + queueName + ", exchange_name: " + EXCHANGE_NAME + ", routingKey: " + routingKey);
  32. }
  33.  
  34. //声明消费者
  35. QueueingConsumer consumer = new QueueingConsumer(channel);
  36. //指定从哪个消费者从哪个通道获取消息,并指明自动确认的机制
  37. //参数1:队列名,参数2:确认机制,true表示自动确认,false代表手动确认,参数3:消费者
  38. channel.basicConsume(queueName, true, consumer);
  39. System.out.println("ReceveLogDirect1 waitting for message");
  40.  
  41. while (true){
  42. //获取消息,这一步会一直阻塞,直到收到消息
  43. QueueingConsumer.Delivery delivery = consumer.nextDelivery();
  44. //获取消息
  45. String message = new String(delivery.getBody(), "UTF-8");
  46. System.out.println("ReceveLogDirect1 receive message " + message);
  47. }
  48.  
  49. } catch (IOException e) {
  50. e.printStackTrace();
  51. } catch (InterruptedException e) {
  52. e.printStackTrace();
  53. }
  54. }
  55. }

  消费者2

  

  1. /**
  2. * 消息消费者2
  3. */
  4. public class Consumer2 {
  5. private static final String EXCHANGE_NAME = "direct_logs";
  6. // 路由关键字
  7. private static final String[] routingKeys = new String[]{"error"};
  8.  
  9. public static void main(String[] args) {
  10. //创建连接工厂并设置连接信息,这些连接信息都是默认的,所以这里注释掉了,打开也是可以的。
  11. ConnectionFactory connectionFactory = new ConnectionFactory();
  12. // connectionFactory.setHost("localhost");
  13. // connectionFactory.setUsername("guest");
  14. // connectionFactory.setPassword("guest");
  15. // connectionFactory.setPort(5672);
  16. Connection connection = null;
  17. Channel channel = null;
  18. try {
  19. connection = connectionFactory.newConnection();
  20. channel = connection.createChannel();
  21. //声明队列,这里队列的名字由代理自动生成
  22. String queueName = channel.queueDeclare().getQueue();
  23. //声明交换机
  24. //参数1:交换机名字,参数2:交换机类型
  25. channel.exchangeDeclare(EXCHANGE_NAME, "direct");
  26.  
  27. for (String routingKey : routingKeys) {
  28. //将交换机和队列用routing key绑定起来
  29. //参数1:队列名,参数2:交换机名,参数3:队列和交换机之间绑定的路由键
  30. channel.queueBind(queueName, EXCHANGE_NAME, routingKey);
  31. System.out.println("ReceveLogDirect2 -> queue: " + queueName + ", exchange_name: " + EXCHANGE_NAME + ", routingKey: " + routingKey);
  32. }
  33.  
  34. //声明消费者
  35. QueueingConsumer consumer = new QueueingConsumer(channel);
  36. //指定从哪个消费者从哪个通道获取消息,并指明自动确认的机制
  37. //参数1:队列名,参数2:确认机制,true表示自动确认,false代表手动确认,参数3:消费者
  38. channel.basicConsume(queueName, true, consumer);
  39. System.out.println("ReceveLogDirect2 waitting for message");
  40.  
  41. while (true){
  42. //获取消息,这一步会一直阻塞,直到收到消息
  43. QueueingConsumer.Delivery delivery = consumer.nextDelivery();
  44. //获取消息
  45. String message = new String(delivery.getBody(), "UTF-8");
  46. System.out.println("ReceveLogDirect2 receive message " + message);
  47. }
  48.  
  49. } catch (IOException e) {
  50. e.printStackTrace();
  51. } catch (InterruptedException e) {
  52. e.printStackTrace();
  53. }
  54. }
  55. }

  先运行Consumer1,Consumer2。再运行LogProducer。运行结果如下:

  LogProducer:

  

  Consumer1:

  

  Consumer2:

  

  从运行结果中可以看出,消息是根据路由键被交换机路由到对应的队列上的。

代码gitbu地址:https://github.com/wutianqi/rabbitmq-learn.git

参考资料:https://www.cnblogs.com/LipeiNet/p/5978276.html

  

rabbitmq学习(二):rabbitmq(消息队列)的作用以及rabbitmq之直连交换机的更多相关文章

  1. 【RabbitMQ学习记录】- 消息队列存储机制源码分析

    本文来自 网易云社区 . RabbitMQ在金融系统,OpenStack内部组件通信和通信领域应用广泛,它部署简单,管理界面内容丰富使用十分方便.笔者最近在研究RabbitMQ部署运维和代码架构,本篇 ...

  2. 二、消息队列之如何在C#中使用RabbitMQ(转载)

    二.消息队列之如何在C#中使用RabbitMQ 1.什么是RabbitMQ.详见 http://www.rabbitmq.com/. 作用就是提高系统的并发性,将一些不需要及时响应客户端且占用较多资源 ...

  3. Linux进程间通信IPC学习笔记之消息队列(SVR4)

    Linux进程间通信IPC学习笔记之消息队列(SVR4)

  4. 二、消息队列之如何在C#中使用RabbitMQ

    1.什么是RabbitMQ.详见 http://www.rabbitmq.com/. 作用就是提高系统的并发性,将一些不需要及时响应客户端且占用较多资源的操作,放入队列,再由另外一个线程,去异步处理这 ...

  5. Spring学习笔记3——消息队列(rabbitmq), 发送邮件

    本节的内容是用户注册时,将邮箱地址先存入rabbitmq队列,之后返回给用户注册成功:之后消息队列的接收者从队列中获取消息,发送邮件给用户. 一.RabbitMQ介绍     如果之前对rabbitm ...

  6. 消息队列面试题、RabbitMQ面试题、Kafka面试题、RocketMQ面试题 (史上最全、持续更新、吐血推荐)

    文章很长,建议收藏起来,慢慢读! 疯狂创客圈为小伙伴奉上以下珍贵的学习资源: 疯狂创客圈 经典图书 : <Netty Zookeeper Redis 高并发实战> 面试必备 + 大厂必备 ...

  7. RabbitMQ消息队列(一)-RabbitMQ的优劣势及产生背景

    本篇并没有直接讲到技术,例如没有先写个Helloword.我想在选择了解或者学习一门技术之前先要明白为什么要现在这个技术而不是其他的,以免到最后发现自己学错了.同时如果已经确定就是他,最好先要了解下技 ...

  8. 消息队列 (1) mac安装RabbitMQ

    什么是RabbitMQ? RabbitMQ是由Erlang语言编写的实现了高级消息队列协议(AMQP)的开源消息代理软件(也称为面向消息的中间件).支持WIndows.Linux.MAC OS 操作系 ...

  9. 消息队列 - mac上安装RabbitMq (转)

    什么是RabbitMQ? RabbitMQ是由Erlang语言编写的实现了高级消息队列协议(AMQP)的开源消息代理软件(也称为面向消息的中间件).支持WIndows.Linux.MAC OS 操作系 ...

  10. rabbitmq和redis用作消息队列的区别

    将redis发布订阅模式用做消息队列和rabbitmq的区别: 可靠性redis :没有相应的机制保证消息的可靠消费,如果发布者发布一条消息,而没有对应的订阅者的话,这条消息将丢失,不会存在内存中:r ...

随机推荐

  1. tp5.0 composer命令插件

    1.单元测试composer require topthink/think-testing 1.* (5.0) composer require topthink/think-testing 5.1官 ...

  2. php将中文符号全部替换为英文符号

    php将中文符号全部替换为英文符号 一.总结 一句话总结:可以用简单替换和规律替换 简单替换 str_replace() 规律替换 均相差 65248 方法一:简单替换(php代码) $val1=st ...

  3. WPF中的Style(风格,样式)

    作者: 周银辉  来源: 博客园  发布时间: 2009-02-27 15:04  阅读: 6698 次  推荐: 0   原文链接   [收藏]   在WPF中我们可以使用Style来设置控件的某些 ...

  4. English trip -- VC(情景课)1 D

    Read 阅读 Welcome! Meet our new student. His first name is Ernesto.  欧内斯托 His last name is Delgado. 德尔 ...

  5. Vue.js 渲染函数, JSX(未掌握,未学完)

    渲染函数 , JSX(没完成学习) 基础: 实例属性:vm.$slots default 属性包括了所有没有被包含在具名插槽中的节点. 渲染函数: render: function(createEle ...

  6. 让CLOVER默认引导WINDOWS

    解决问题帖子: http://www.insanelymac.com/forum/topic/296000-force-clover-to-always-choose-win-81-efi-as-de ...

  7. js 刷新页面

    Javascript刷新页面的几种方法:1 history.go(0)2 window.location.reload() window.location.reload(true) 3 locatio ...

  8. Queue 实现生产者消费者模型

    Python中,队列是线程间最常用的交换数据的形式. Python Queue模块有三种队列及构造函数: 1.Python Queue模块的FIFO队列先进先出. class Queue.Queue( ...

  9. java.lang.Exception: Socket bind failed: [730048]

    严重: Error initializing endpoint java.lang.Exception: Socket bind failed: [730048] ?????????×???(Э?é/ ...

  10. 组数排序非sort

    <!doctype html> <html> <head> <meta charset="utf-8"> <meta name ...