一文看懂Rabbitmq,从安装到实战演练

640

640

今天分享下我的朋友「胖琪的升级之路」中关于Rabbitmq的使用。说来叶巧,项目组之前刚好用MQ来完成了一个功能,而且前段时间在捣鼓微服务,也知道了这个MQ的重要性。胖琪真的是及时雨呀。让我们开始好好学习吧。


Rabbitmq的初步使用

随着微服务概念发展,大应用逐步拆分为小应用,提高开发效率,专门的人做专门的事情,逐渐的流行起来。

在微服务上实现通信的方式大部分是采用rpc方式,也有升级版本的grpc。

还有另外一种实现就是使用mq来进行解耦。

今天初识mq,快速入门先,准备一个环境实现案例,该文涉及以下内容:

  • 安装rabbitmq

  • mq能解决的问题

  • 实战演练

安装

rabbitmq的安装我们采用docker的方式,docker方便我们快速的实现rabbitmq的安装,不需要再对安装mq进行头疼。

docker 的两种方式

docker方式

//拉取mq镜像	
docker pull rabbitmq	
//启动mq	
docker run -d --name rabbitmq3.7.7 -p 5672:5672 -p 15672:15672 -v `pwd`/data:/var/lib/rabbitmq --hostname myRabbit -e RABBITMQ_DEFAULT_VHOST=my_vhost  -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=admin df80af9ca0c9

说明:

  1. -d 后台运行容器;

  2. --name 指定容器名;

  3. -p 指定服务运行的端口(5672:应用访问端口;15672:控制台Web端口号);

  4. -v 映射目录或文件;

  5. --hostname 主机名(RabbitMQ的一个重要注意事项是它根据所谓的 “节点名称” 存储数据,默认为主机名);

  6. -e 指定环境变量;(RABBITMQDEFAULTVHOST:默认虚拟机名;RABBITMQDEFAULTUSER:默认的用户名;RABBITMQDEFAULTPASS:默认用户名的密码)

docker-compose 方式

 
 
  1. version: "3"

  2. services:

  3. rabbit:

  4. image: docker.infervision.com/library/rabbitmq:3-management

  5. ports:

  6. - "4369:4369"

  7. - "5671:5671"

  8. - "5672:5672"

  9. - "15671:15671"

  10. - "15672:15672"

  11. restart: always

  12. environment:

  13. - RABBITMQ_DEFAULT_USER=test

  14. - RABBITMQ_DEFAULT_PASS=test

  15. volumes:

  16. - /home/ruiqi/Desktop/disk/rabbitmq:/var/lib/rabbitmq

  17. container_name: rabbitmq


  18. 在该文件目录下执行:docker-compose up -d

下载的rabbitmq内置管理界面,ip:15672 用户名与密码是我们在启动是写入的。640?wx_fmt=png

mq能解决什么?

通俗的来说,主要使用MQ来解决以下三个问题。

异步消息

在业务中,经常会遇到同时发送邮件,短信或者其他通知内容服务。业务初期,采用同步或者异步处理方式都需要等发送完毕后再返回给客户端。中间有一定的延迟

640?wx_fmt=png

业务增长后,此方式系统性能就会造成很大的浪费。采用消息队列,将这几个服务进行解耦,只需将消息内容发送到消息队列中,降低用户的等待时间,体验效果比原先好很多。

640?wx_fmt=png

应用间解耦

同一个服务中可能需要其他服务的配合才能完成一项业务操作.还是拿常见的购物案例来说明。

在京东下单支付后,消息要通知到商家,邮件通知用户已经购买某商品。

如果这两种操作都采用同步执行,用户等待时间会变长。

采用mq方式之后,订单系统将消息持久化到mq上,返回给用户下单成功。

  • 商家接收到用户的下单信息,进行处理,如果有库存管理那么需要进行库存处理。

  • 邮件通知用户,告知用户下单成功。

mq保证消息的可靠投递,不会导致消息丢失,保证消息的高可靠性。如果库存出现失败也不会导致用户下单失败的情况,可以重新进行投递。

流量削峰

流量削峰,一般是同一时间涌进来很多请求,后台处理不过来。那么需要采用削峰方式来处理。

简单来说是通过一个队列承接瞬时过来流量洪峰,在消费端平滑的将消息推送出去,如果消费者消费不及时可以将消息内容持久化在队列中,消息不存在丢失。

  1. 消费端不及时进行消费,还可以动态的扩增消费者数量,提高消费速度。

  2. 设定相关的阀值,多余的消息直接丢弃,告知用户秒杀失败等业务消息内容。

640?wx_fmt=png

实战案例

本文是按照Java语言进行,使用Spring boot搭建,包管理工具Gradle。

导入rabbitmq jar包

 
 
  1. compile("org.springframework.boot:spring-boot-starter-amqp:1.5.10.RELEASE")

配置mq

yaml 文件配置

 
 
  1. spring:

  2. rabbitmq:

  3. host: 192.168.110.5

  4. port: 5672

  5. username: tuixiang

  6. password: tuixiang

准备好模板类,供后面直接使用

 
 
  1. package com.infervision.config;


  2. import org.slf4j.Logger;

  3. import org.slf4j.LoggerFactory;

  4. import org.springframework.amqp.core.AcknowledgeMode;

  5. import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;

  6. import org.springframework.amqp.rabbit.connection.ConnectionFactory;

  7. import org.springframework.amqp.rabbit.core.ChannelAwareMessageListener;

  8. import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;

  9. import org.springframework.beans.factory.annotation.Value;

  10. import org.springframework.context.annotation.Bean;

  11. import org.springframework.context.annotation.Configuration;


  12. /**

  13. * @author: fruiqi

  14. * @date: 19-2-18 下午2:42

  15. * @version:1.0 rabbit配置

  16. **/

  17. @Configuration

  18. public class RabbitConfig {


  19. /**

  20. * 日志

  21. **/

  22. private static final Logger logger = LoggerFactory.getLogger(RabbitConfig.class);



  23. @Value("${spring.rabbitmq.username}")

  24. String userName;


  25. @Value("${spring.rabbitmq.password}")

  26. String userPassword;


  27. @Value("${spring.rabbitmq.host}")

  28. String host;


  29. @Value("${spring.rabbitmq.port}")

  30. Integer port;


  31. /**

  32. * 注入

  33. *

  34. * @param

  35. * @return com.rabbitmq.client.Connection

  36. * @author fruiqi

  37. * @date 19-1-22 下午5:41

  38. **/

  39. @Bean

  40. public ConnectionFactory getConnection() throws Exception {

  41. CachingConnectionFactory factory = new CachingConnectionFactory();

  42. factory.setUsername(userName);

  43. factory.setPassword(userPassword);

  44. factory.setHost(host);

  45. factory.setPort(port);

  46. return factory;

  47. }



  48. /**

  49. * 创建制定的 监听容器

  50. *

  51. * @param queueName 监听的队列名字

  52. * @param listenerChannel 设置是否将监听的频道 公开给已注册的

  53. * @param PrefetchCount 告诉代理一次请求多少条消息过来

  54. * @param ConcurrentConsumers 制定创建多少个并发的消费者数量

  55. * @param acknowledgeMode 消息确认模式

  56. * @param listener 监听器

  57. * @return org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer

  58. **/

  59. public SimpleMessageListenerContainer setSimpleMessageListenerContainer(String queueName, boolean listenerChannel,

  60. int PrefetchCount, int ConcurrentConsumers,

  61. AcknowledgeMode acknowledgeMode,

  62. ChannelAwareMessageListener listener) throws Exception {

  63. SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(getConnection());

  64. container.setQueueNames(queueName);

  65. container.setExposeListenerChannel(listenerChannel);

  66. container.setPrefetchCount(PrefetchCount);

  67. container.setConcurrentConsumers(ConcurrentConsumers);

  68. container.setAcknowledgeMode(acknowledgeMode);

  69. container.setMessageListener(listener);

  70. return container;

  71. }

  72. }



  73. package com.infervision.config;


  74. import org.slf4j.Logger;

  75. import org.slf4j.LoggerFactory;

  76. import org.springframework.amqp.rabbit.core.RabbitTemplate;

  77. import org.springframework.beans.factory.annotation.Autowired;

  78. import org.springframework.stereotype.Component;


  79. /**

  80. * @author: fruiqi

  81. * @date: 19-2-18 下午2:51

  82. * @version:1.0

  83. **/

  84. @Component

  85. public class MsgSender {



  86. private static final Logger logger = LoggerFactory.getLogger(MsgSender.class);


  87. @Autowired

  88. private RabbitTemplate rabbitTemplate;


  89. /**

  90. * @param exchange 交换机名称

  91. * @param routingKey 路由名称

  92. * @param message 消息内容

  93. * @return void

  94. * @description //TODO 发送消息到消息队列中

  95. **/

  96. public void sendMsg(String exchange, String routingKey, Object message) {

  97. try {

  98. rabbitTemplate.convertAndSend(exchange,routingKey,message);

  99. }catch (Exception e){

  100. logger.error("[ERROR] send statistic message error ",e);

  101. }

  102. }


  103. }

实例链接mq

在使用rabbitmq 有的时候需要自己客户端创建queue,但有的时候并不是自己创建,在rabbitmq页面上进行创建queue,其他消费者直接引用。

客户端创建mq
 
 
  1. //初始化队列,如果队列已存在,则不作任何处理 如果有权限控制如下操作并不能实现

  2. @Bean

  3. public Queue dicomQueue() {

  4. return new Queue(getMacPreStr(DICOM_QUEUE_NAME));

  5. }


  6. //初始化交换机

  7. @Bean

  8. public Exchange topicExchange() {

  9. return ExchangeBuilder.topicExchange((DEFAULT_TOPIC_EXCHANGE).durable(true).build();

  10. }


  11. // 将队列与交换机按照路由规则进行绑定

  12. @Bean

  13. Binding bindingExchangeDicomQueue(Queue dicomQueue, TopicExchange topicExchange) {

  14. return BindingBuilder.bind(dicomQueue).to(topicExchange).with(DICOM_QUEUE_ROUTING_KEY);

  15. }

使用

队列的使用:一个是发送,属于生产者;一个是监听,属于消费者.

生产者实现

在mq配置模板类中,专门实现了一个发送类,发送文件内容,直接调用发送接口即可。

 
 
  1. @Autowired

  2. RabbitService rabbitService;


  3. /**

  4. * 练习 发送数据到 mq中

  5. * 1. 发送的数据会到 mq中

  6. * 2. 我们配置的 listener 是用来消费消息的

  7. * 3. 客户端配置 可以参考 RabbitClientConfig

  8. * @param name 名字编号

  9. * @param vo 实体内容

  10. * @return: com.infervision.model.NameVo

  11. */

  12. @ApiOperation(value = "增加name信息", notes = "实体信息")

  13. @PostMapping(value = "/{name}")

  14. @ApiImplicitParam(paramType = "query", name = "name", value = "用户名字", required = true, dataType = "string")

  15. public NameVo addNameVo(@RequestParam String name, @RequestBody NameVo vo) {

  16. rabbitService.sendMessage(DEFAULT_TOPIC_TEST_EXCHANGE, LABEL_FIEL_XML_QUEUE_ROUTING_KEY, JSON.toJSONString(vo));

  17. return vo;

  18. }



  19. @Service

  20. public class RabbitServiceImpl implements RabbitService {


  21. @Autowired

  22. MsgSender msgSender;


  23. /**

  24. * 尝试发送 message 到mq中

  25. * @param message

  26. * @return: void

  27. */

  28. @Override

  29. public void sendMessage(String exchange, String routingKey,String message) {

  30. msgSender.sendMsg(exchange, routingKey, message);

  31. }

  32. }

消费者实现

消费者实现有两种方式,一种通过注解的方式监听,一种是实现ChannelAwareMessageListener类来实现消费。

注解实现监听
 
 
  1. //在方法上进行注入。配置工厂帮助提高单个消费者一次性消费的消息数量,设置多少个消费者,用来提高程序的性能

  2. @RabbitListener(queues = "dicom.queue",containerFactory = "multipleConsumerContainerFactory")

  3. public void processDicomMessage(Message message, Channel channel) {

  4. logger.info(message);

  5. }


  6. // 工厂可以在配置模板类中中配置好。

  7. @Bean("multipleConsumerContainerFactory")

  8. public SimpleRabbitListenerContainerFactory multipleConsumerContainerFactory(SimpleRabbitListenerContainerFactoryConfigurer configurer, ConnectionFactory connectionFactory) {

  9. SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();

  10. factory.setPrefetchCount(50);

  11. factory.setConcurrentConsumers(10);

  12. factory.setAcknowledgeMode(AcknowledgeMode.MANUAL);

  13. configurer.configure(factory, connectionFactory);

  14. return factory;

  15. }

实现接口方式
 
 
  1. /**

  2. * 创建监听器。

  3. * @author fruiqi

  4. * @date 19-2-11 下午4:18

  5. * @param labelStatisticsListener 监听器

  6. * 调用我们公用的方法

  7. **/

  8. @Bean

  9. public SimpleMessageListenerContainer mqMessageContainer(LabelStatisticsListener labelStatisticsListener) throws Exception {

  10. SimpleMessageListenerContainer container = rabbitConfig.setSimpleMessageListenerContainer(“queue_name”,

  11. true, rabbitProperties.getMaximumDelivery(),

  12. rabbitProperties.getConsumer(), AcknowledgeMode.MANUAL, labelStatisticsListener);

  13. return container;

  14. }



  15. @Component

  16. public class LabelStatisticsListener implements ChannelAwareMessageListener {



  17. private static final Logger logger = LoggerFactory.getLogger(LabelStatisticsListener.class);


  18. /**

  19. * 处理传输过来的数据

  20. * @param message 传送的消息内容

  21. * @param channel 实现通道

  22. * @return: void

  23. */

  24. @Override

  25. public void onMessage(Message message, Channel channel) throws Exception {

  26. String mes = new String(message.getBody());

  27. logger.info("[INFO] message is {}",mes);


  28. // 手动应答 消息已消费

  29. channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);


  30. }

  31. }

总结

以上内容就完成了rabbitmq 从搭建到使用全部的流程。当然里面还有更多的可以让我们去探讨,比如mq的队列模式,一个系统配置多个mq等等内容。敬请期待我们下一篇mq系列内容。

大家在系统中使用过mq吗?你们使用的mq是什么样的?可以在留言区我们一起探讨哦。

源代码存放在:https://github.com/menhuan/notes/tree/master/code/codebase-master

·END·

路虽远,行则必至

往期文章一览

1、Sharding-JDBC:查询量大如何优化?

2、如果我是个商品

3、我的前六年程序生涯

4、没那么简单的线程池

640?wx_fmt=png你点的每个在看,我都认真当成了喜欢
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值