一。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下载安装配置参考
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消息从哪个项目发出