MQ 消息队列之RabbitMQ

参考地址

一。MQ概念与作用

message queue消息队列,有1.RabbitMQ    2.RocketMQ     3.ActiveMQ  4.Redis 5.kafka
AMQP(高级消息队列协议):Advaned Message Queue Protocol

作用:异步、解耦、削峰
1.异步处理 用户注册:   注册信息填入数据库 —— 写入消息 —— 发送邮件和发短信
2.应用解耦
   下单减库存   订单系统 —— 消息中间件 —— 库存系统    降低了耦合性,让订单系统和库存系统分开,通过中间件联系
3.流量削峰  
    秒杀       1000个客户抢购 ———— 写入10条进入消息中间件 ———— 秒杀业务处理

二。MQ组成

1.生产者()
2.消费者
3.服务器(server)
4.队列
5.主题,发布订阅模式下的消息统一汇集地,不同生产者向topic发送消息,由MQ服务器分发到不同的订阅者,实现消息的       广播
6.消息体

三。消息模型

点对点 Point to Point              使用queue作为通信载体
发布/订阅  Publish to Subscriber   使用topic作为通信载体

四。RabbitMQ下载与安装

1。Erlang 下载与安装

ErLang下载网址
RabbitMQ 是由 Erlang 语言编写的 也正因如此 在安装 bb itM 之前需要安装 rl 。(选择次新版22.2,最新版不知道为什么下载不完全)
安装后配置下环境变量 D:\Erlang\installPath\bin ,CMD运行 erl -version 查看是否安装成功
在这里插入图片描述

2。RabbitMQ 下载与安装

Rabbit下载网址

Rabbit下载安装配置参考
1.下载压缩包rabbitmq-server-windows-3.7.7.zip
2.配置环境变量 D:\Erlang\RabbitMQ\rabbitmq_server-3.7.7\sbin ,切换到 \sbin目录下,输入rabbitmqctl status
3.安装插件,命令:rabbitmq-plugins.bat enable rabbitmq_management
4.启动服务,rabbitmq-server.bat
测试:http://localhost:15672/, 账号密码guest guest进入后台管理

//后台查看内容
Exchange模块
   a.可以看到自己定义的交换机(原本七个),以及交换机的发送模式
   b.点击交换机名称可以看到此交换机绑定的队列以及routingKey
     
Queue模块
 查看所有队列

五。RabbitMQ命令汇总

erl -version                                                 //查看二郎版本
rabbitmqctl status                                          //查看rabbit状态(查看是否安装成功)
rabbitmq-plugins.bat enable rabbitmq_management             //安装管理界面插件
rabbitmq-server.bat                                         //启动服务

//重启和清除所有队列数据先进入安装目录下的sbin
//重启
rabbitmq-service stop
rabbitmq-service start

//清除所有队列数据
rabbitmqctl stop_app
rabbitmqctl reset
rabbitmqctl start_app

六。干货:2020录制RabbitMQ整合SpringBoot综合应用

交换器分类:
direct:一个交换机绑定一个队列,一个routingKey
topic:一个交换机绑定多个队列,多个routingKey
fanout :不需要绑定routingKey
headers

有几个队列就需要绑定几次(队列和交换机的绑定)
direct控制层发送消息时指定特定routingKey,订阅者config绑定时也用这个特定的routingKey (收到一个队列的消息)一个队列
topic控制层发送消息时 动态routingKey,订阅者config绑定多个routingKey (收到routingKey满足条件的队列消息 ) 多个队列
扇形发送到每个队列不指定routingKey,(所有队列都接收到消息) 多个队列
根据传入的参数发送到不同的队列 (收到参数满足条件的队列信息) 多个队列

1。direct 模式

1.新建工程,新建子工程publish和subscriber
2.配置父工程pom springboot加上如下amqp依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

3.发布者和订阅者的yml文件

  spring:
  application:
    name: biz-publisher
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest
server:
  port: 8071

spring:
  application:
    name: biz-subscriber
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest
server:
  port: 8072

1.发布者配置交换机

//发布者定义交换机
@Configuration
public class EmailConfig {
    @Bean
    DirectExchange EmailExchange(){
        return  new DirectExchange("emailExchange");
    }
}

2.发送消息

@Controller
public class AmqpController {
    @Autowired
    RabbitTemplate rabbitTemplate;
    
    @GetMapping("/direct")
    @ResponseBody
    public String sendEmail(@RequestParam Map<String,Object> params){
        String msg = params.get("msg").toString();
        rabbitTemplate.convertAndSend("emailExchange","emailRoutingKey",msg);
        System.out.println("发送了消息:" +msg);
        return "OK";
    }
}

3.订阅者配置交换机

@Configuration
public class EmailConfig {
    @Bean
    DirectExchange EmailExchange(){
        return  new DirectExchange("emailExchange");
    }

    @Bean
    Queue EmailQueue(){
        return new Queue("emailQueue");
    }

    @Bean
    Binding BindEamil(){
        return BindingBuilder.bind(EmailQueue()).to(EmailExchange()).with("emailRoutingKey");
    }
}

4.订阅者监听队列

@Component
public class EmailReceiver {
    @RabbitListener(queues = "emailQueue")    //大写试下
    public void receiver(String msg){
        System.out.println("收到的消息是:" + msg );
    }
}

http://localhost:8071/direct?msg=1111 ——只收到一个队列的消息

2。topic模式

1.发布者配置交换机

@Configuration
public class BlogConfig {
    @Bean
    TopicExchange EmailExchange(){
        return  new TopicExchange("BlogExchange");
    }
}

2.发布者发送消息

@GetMapping("/topic")
@ResponseBody
public String sendBlog(@RequestParam Map<String,Object> params){
    String msg = params.get("msg").toString();
    String routingKey = params.get("key").toString();   //blog.java 和订阅者config文件里面的绑定匹配
    rabbitTemplate.convertAndSend("BlogExchange",routingKey,msg);
    System.out.println("topic模式发送了消息:" +msg);
    return "topic模式发送OK";
}



3.订阅者配置交换机 队列 以及绑定

@Configuration
public class BlogConfig {
    /*交换机*/
    @Bean
    TopicExchange TopicExchange(){
        return  new TopicExchange("BlogExchange");
    }

    /*java队列*/
    @Bean
    Queue BlogJavaQueue(){
        return new Queue("BlogJavaQueue",true);  //第二个参数表示是否持久化
    }
    /*.net队列*/
    @Bean
    Queue BlogDotnetQueue(){
        return new Queue("BlogDotnetQueue",true);  //第二个参数表示是否持久化
    }
    /*所有队列*/
    @Bean
    Queue BlogAllQueue(){
        return new Queue("BlogAllQueue",true);  //第二个参数表示是否持久化
    }


    /*绑定java*/
    @Bean
    Binding BindToJava(){
        return BindingBuilder.bind(BlogJavaQueue()).to(TopicExchange()).with("blog.java");
    }
    /*绑定.net*/
    @Bean
    Binding BindToDotnet(){
        return BindingBuilder.bind(BlogDotnetQueue()).to(TopicExchange()).with("blog.net");
    }
    /*绑定所有*/
    @Bean
    Binding BindToALL(){
        return BindingBuilder.bind(BlogAllQueue()).to(TopicExchange()).with("blog.all");
    }
}

4.监听队列

@Component
public class BlogReceiver {
    @RabbitListener(queues = "BlogJavaQueue")    //和config文件那里对应
    public void javaReceiver(String msg){
        System.out.println("收到的Java消息是:" + msg);
    }
    @RabbitListener(queues = "BlogDotnetQueue")    //和config文件那里对应
    public void dotnetReceiver(String msg){
        System.out.println("收到的Dotnet消息是:" + msg);
    }
    @RabbitListener(queues = "BlogAllQueue")    //和config文件那里对应
    public void allReceiver(String msg){
        System.out.println("收到的All消息是:" + msg);
    }
}

http://localhost:8071/topic?msg=1111&routingKey=blog.net ——收到满足条件的队列消息 ( 精确匹配,模糊匹配)
http://localhost:8071/topic?msg=1111&routingKey=blog.java

3。fanout模式

扇形出去,每个队列都连接,没有routingKey
1.发送者配置交换机

@Configuration
public class FanoutConfig {
    @Bean
    FanoutExchange fanoutExchange(){
        return new FanoutExchange("FanoutExchange");
    }
}

2.发送消息

@GetMapping("/fanout")
@ResponseBody
public String fanout(@RequestParam Map<String,Object> params){
    String msg = params.get("msg").toString();
    rabbitTemplate.convertAndSend("BlogExchange",null,msg);
    return "fanout模式发送OK";
}

3.订阅者的交换机 队列 绑定
多个队列多个绑定

@Configuration
public class FanoutConfig {
    @Bean
    FanoutExchange FanoutExchange(){
        return  new FanoutExchange("FanoutExchange");
    }

    @Bean
    Queue FanoutQueue1(){
        return new Queue("FanoutQueue1");
    }
    @Bean
    Queue FanoutQueue2(){
        return new Queue("FanoutQueue2");
    }

    @Bean
    Binding BindFanout1(){
        return BindingBuilder.bind(FanoutQueue1()).to(FanoutExchange());
    }

    @Bean
    Binding BindFanout2(){
        return BindingBuilder.bind(FanoutQueue2()).to(FanoutExchange());
    }
}

4.监听队列

@Component
public class FanoutReceiver {
    @RabbitListener(queues = "FanoutQueue1")    //大写试下
    public void javaReceiver(String msg){
        System.out.println("收到第一个队列的消息:" + msg);
    }
    
    @RabbitListener(queues = "FanoutQueue2")    //大写试下
    public void dotnetReceiver(String msg){
        System.out.println("收到第二个队列的消息:" + msg);
    }
}

http://localhost:8071/fanout?msg=啊啊啊 —— 收到所有队列的消息

4。Headers模式

功能:根据传入的参数发送到不同的队列
1.发布者config

@Configuration
public class HeadersConfig {
    @Bean
    HeadersExchange headersExchange(){
        return new HeadersExchange("HeadersExchange");
    }
}

2.发送消息

@GetMapping("/headers")
@ResponseBody
public String headers(@RequestParam Map<String,Object> params){
    String msg = params.get("msg").toString();
    MessageProperties messageProperties = new MessageProperties();
    if (params.get("token")!= null){
        messageProperties.setHeader("token",params.get("token").toString());
    }
    if (params.get("id")!= null){
        messageProperties.setHeader("id",params.get("id").toString());
    }
    Message message = new Message(msg.getBytes(),messageProperties);
    rabbitTemplate.convertAndSend("HeadersExchange",null,message);
    return "headers模式发送OK";
}

3.订阅者config

@Configuration
public class HeadersConfig {
    /*交换机*/
    @Bean
    HeadersExchange HeadersExchange(){
        return  new HeadersExchange("HeadersExchange");
    }


    @Bean
    Queue HeadersQueue1(){
        return new Queue("HeadersQueue1",true);  //第二个参数表示是否持久化
    }
    @Bean
    Queue HeadersQueue2(){
        return new Queue("HeadersQueue2",true);  //第二个参数表示是否持久化
    }
    @Bean
    Queue HeadersQueue3(){
        return new Queue("HeadersQueue3",true);  //第二个参数表示是否持久化
    }
    @Bean
    Queue HeadersQueue4(){
        return new Queue("HeadersQueue4",true);  //第二个参数表示是否持久化
    }


    /*key和id两个参数都要存在  不管值多少*/
    @Bean
    Binding BindHeadersQueue1(){
        return BindingBuilder.bind(HeadersQueue1()).to(HeadersExchange()).whereAll("token","id").exist();
    }

    /* key和id两个参数存在一个就ok  不管值多少      最不严格*/
    @Bean
    Binding BindHeadersQueue2(){
        return BindingBuilder.bind(HeadersQueue2()).to(HeadersExchange()).whereAny("token","id").exist();
    }

    /*key都要存在,并且value都要匹配        最严格 */
    @Bean
    Binding BindHeadersQueue3(){
        Map map = new HashMap();
        map.put("token","123");
        map.put("id","123");
        return BindingBuilder.bind(HeadersQueue3()).to(HeadersExchange()).whereAll(map).match();
    }

    /*Key存在一个并且value匹配就行*/
    @Bean
    Binding BindHeadersQueue4(){
        Map map = new HashMap();
        map.put("token","123");
        map.put("id","123");
        return BindingBuilder.bind(HeadersQueue4()).to(HeadersExchange()).whereAny(map).match();
    }

}

4.监听队列

@Component
public class HeadersReceiver {
    @RabbitListener(queues = "HeadersQueue1")
    public void HeadersaQueue1(String msg){
        System.out.println("收到队列一消息:" + msg);
    }

    @RabbitListener(queues = "HeadersQueue2")
    public void HeadersaQueue2(String msg){
        System.out.println("收到队列列二消息:" + msg);
    }

    @RabbitListener(queues = "HeadersQueue3")
    public void HeadersaQueue3(String msg){
        System.out.println("收到队列三消息:" + msg);
    }

    @RabbitListener(queues = "HeadersQueue4")
    public void HeadersQueue4(String msg){
        System.out.println("收到队列四消息:" + msg);
    }
}

七、星云天气消息队列

消息队列处理
 发送消息  1.application   2.defaultProcess  3.MessageProvider   4.srvice调用messageProvider的方法
 接收消息  1.application   2.defaultProcess  3.MessageListener

1.定时任务获取站点天气

/**
 * 定时获取站点天气
 */
@Scheduled(cron = "0 0 */1 * * ?")
public void getStationSetWeather() {
    try {
        stationSetService.getStationWeather();
    } catch (Exception e) {
        log.error("获取站点天气失败");
    }
}

2.获取站点天气后封装成对象weatherDTO
从3开始使用消息队列
3.发送队列 messageProvider.sendStationWeather(weatherDTO); 写在service层 和其他业务写在一起

    public void sendStationWeather(StationWeatherDTO weatherDTO){
        weatherOutputChannel.send(MessageBuilder.withPayload(weatherDTO).build()); 
    }

4.接收队列,往数据库添加天气数据

@StreamListener(DefaultProcess.STATION_WEATHER_INPUT)
public void weatherInput(Message<StationWeatherDTO> message) {
    StationWeatherDTO weatherDTO = message.getPayload();
    StationWeather weather = new StationWeather();
    weather.setId(UUIDUtil.getUUID());
    weather.setTodayCond(weatherDTO.getTodayCond());
    weather.setStationId(weatherDTO.getStationId());
  stationWeatherRepository.insert(weather);
}

5.DefaultProcess 定义发送和接收通道
6.发送者和监听者的key绑定在配置文件中,spring.cloud.stream.rabbit.bindings (发送通道和接收通道绑定同一个key)

cloud:
  stream:
    rabbit:
      bindings:
        station_weather_output:
          producer:
            routing-key-expression: '''station-weather-key'''
        station_weather_input:
          consumer:
            bindingRoutingKey: station-weather-key

八、Autel OCPI RabbitMQ

1、配置exchange、Queue、bind

@Configuration
public class RabbitConfig {
    @Bean
    public DirectExchange ocpiTestExchange() {
        return new DirectExchange(Constant.OCPI_TEST_EXCHANGE);
    }

    @Bean
    public Queue ocpiTestQueue() {
        return new Queue(Constant.OCPI_TEST_QUEUE, true);
    }

    @Bean
    public Binding ocpTestBinding() {
        return BindingBuilder.bind(ocpiTestQueue())
                .to(ocpiTestExchange())
                .with(Constant.OCPI_TEST_ROUTE);
    }  
}

//常量
public class Constant {
    public static final String OCPI_TEST_EXCHANGE = "energy.ocpi.TEST.exchange";
    public static final String OCPI_TEST_ROUTE = "energy.ocpi.TEST.route";
    public static final String OCPI_TEST_QUEUE = "energy.ocpi.TEST.queue";
}

2、发送mq消息

mqProductUtil.sendPushMessage(
   Constant.OCPI_TEST_EXCHANGE, 
   Constant.OCPI_TEST_ROUTE, 
   JSONObject.toJSONString(tokensDTO));


mqProductUtil.sendDelayMessage(
   Constant.OCPI_TEST_EXCHANGE, 
   Constant.OCPI_TEST_ROUTE, 
   30000,     //延迟30s发送
   JSONObject.toJSONString(tokensDTO));

3、监听mq消息

import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpStatus;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.autel.cloud.app.ocpi.common.constant.Constant;
import com.autel.cloud.app.ocpi.common.util.HttpClientUtil;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;


@Slf4j
@RefreshScope
@Component
public class PushTokenToCPOListener {
    @Resource
    private MqProductUtil mqProductUtil;
    @Value("${ocpi.push.retryEnabled:false}")
    public Boolean retryEnabled;

    @RabbitHandler
    @RabbitListener(queues = Constant.OCPI_PUSH_TOKEN_TOCPO_QUEUE)
    public void consumeMsg(Message message, Channel channel) throws Exception {
        log.info("推送token给CPO消息监听: {}", new String(message.getBody()));
        long deliveryTag = message.getMessageProperties().getDeliveryTag();

        try {
            HttpRequestDto httpRequestDto = JSON.parseObject(new String(message.getBody()), HttpRequestDto.class);
            log.info("=====推送token给cpo httpRequestDto:{}", JSONObject.toJSONString(httpRequestDto));
            HttpResponse httpResponse = HttpClientUtil.execute(httpRequestDto.getHttpUrl(), httpRequestDto.getMethod(), httpRequestDto.getHeaderMap(), httpRequestDto.getBodyObject());
            log.info("推送token给cpo响应:{} 结果ocpiCloudResponse:{}", httpRequestDto.getHttpUrl(), JSONObject.toJSONString(httpResponse));
            //如果配置为允许重试,并且http响应状态是500,那么重试
            if (retryEnabled && (httpResponse == null || HttpStatus.HTTP_INTERNAL_ERROR == httpResponse.getStatus())) {
                //不成功,重新添加消息消费
                mqProductUtil.sendDelayMessage(Constant.OCPI_PUSH_TOKEN_TOCPO_EXCHANGE, Constant.OCPI_PUSH_TOKEN_TOCPO_ROUTE, 30 * 1000, JSONObject.toJSONString(httpRequestDto));
            } else {
                //告知 RabbitMQ 消息已经成功处理,RabbitMQ 可以将该消息从队列中移除,避免重复消费
                channel.basicAck(deliveryTag, Boolean.FALSE);
            }
        } catch (Exception ex) {
            //服务异常,重新添加消息消费
            if (retryEnabled) {
                mqProductUtil.sendDelayMessage(Constant.OCPI_PUSH_TOKEN_TOCPO_EXCHANGE, Constant.OCPI_PUSH_TOKEN_TOCPO_ROUTE, 30 * 1000, new String(message.getBody()));
            }
        }
    }
}

4、MqProductUtil

package com.autel.cloud.app.ocpi.listener;


import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageBuilder;
import org.springframework.amqp.core.MessageDeliveryMode;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

@Slf4j
@Component
public class MqProductUtil implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnCallback {

    @Resource
    RabbitTemplate rabbitTemplate;

    /**
     * 发送消息
     */
    public void sendPushMessage(String exchange, String routingKey, String content) {
        //消息回调参数
        MessageCallBack callbackDto = new MessageCallBack();
        callbackDto.setMessageId(String.valueOf(IdWorker.getId()));
        callbackDto.setExchange(exchange);
        callbackDto.setRoutingKey(routingKey);
        callbackDto.setRetryCount(0);
        callbackDto.setContent(content);
        sendPushMessage(callbackDto);
    }

    private void sendPushMessage(MessageCallBack callbackDto) {
        Message msg = MessageBuilder.withBody(callbackDto.getContent().getBytes()).setContentType(MessageProperties.CONTENT_TYPE_JSON)
                .setDeliveryMode(MessageDeliveryMode.PERSISTENT)
                .setContentEncoding("utf-8").setMessageId(callbackDto.getMessageId()).build();
        CorrelationData correlationData = new CorrelationData(callbackDto.toString());

        rabbitTemplate.setMandatory(true);
        rabbitTemplate.setConfirmCallback(this);
        rabbitTemplate.setReturnCallback(this);
        rabbitTemplate.convertAndSend(callbackDto.getExchange(), callbackDto.getRoutingKey(), msg, correlationData);
    }


    public void sendDelayMessage(String exchange, String routingKey, Integer delayTime, String content) {
        Message msg = MessageBuilder.withBody(content.getBytes()).setContentType(MessageProperties.CONTENT_TYPE_JSON)
                .setDeliveryMode(MessageDeliveryMode.PERSISTENT)
                .setContentEncoding("utf-8").build();
        rabbitTemplate.setMandatory(true);
        rabbitTemplate.convertAndSend(exchange, routingKey, msg, message -> {
            message.getMessageProperties().setDelay(delayTime);
            return message;
        });
    }


    /**
     * 发送消息确认机制
     */
    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        log.info("broker ack! mq send " + (ack ? "success" : "failed") + JSON.toJSONString(correlationData));
    }

    @Override
    public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
        log.error("Queue fail: exchange: {}, route: {}, replyCode: {}, replyText: {}, message: {}", exchange, routingKey, replyCode, replyText, message);
    }
}

九、查询mq消息在哪个项目消费

1、先看发送的地方,根据exchange和routeKey查看对应的queue
 rabbitTemplate.convertAndSend(ProtocolConstant.MONITOR_STATUSNOTIFICATION_EXCHANGE + RabbitBean.RABBITMQ_VERSION_SUFFIX,
                    ProtocolConstant.MONITOR_STATUSNOTIFICATION_ROUTE_KEY, JSON.toJSONString(opEvseStatusUploadDTO));


   public static final String MONITOR_STATUSNOTIFICATION_EXCHANGE = "energy.protocol.monitor.statusNotification.exchange";
    public static final String MONITOR_STATUSNOTIFICATION_QUEUE = "energy.protocol.monitor.statusNotification.queue";
    public static final String MONITOR_STATUSNOTIFICATION_ROUTE_KEY = "energy.protocol.monitor.statusNotification.route";```


2、拿着队列去rabbitMq管理页面(reader/reader)搜索,点击进去,有个ip+端口号的,复制ip

在这里插入图片描述
在这里插入图片描述

3、拿着ip去k8s查找

在这里插入图片描述

十、查看mq消息从哪个项目发出

在这里插入图片描述
在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

飘然生

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值