RabbitMq

 

一、概念

  rabbitMQ 的核心就是  交换器,路由键,队列;

  消息来了之后,会发送到交换器,交换器将根据路由键将消息发送到相对应的队列里面去!

  消息不用关系到达了那个队列.只需要带着自己的路由键到了交换器就可以了,交换器会帮你把消息发送到指定的队列里面去!

 

  RabbitMQ 即一个消息队列,主要是用来实现应用程序的异步和解耦,同时也能起到消息缓冲,消息分发的作用。

  • 左侧 P 代表 生产者,也就是往 RabbitMQ 发消息的程序。
  • 中间即是 RabbitMQ,其中包括了 交换机 和 队列。在发消息者和队列之间, 加入了交换器 (Exchange)
  • 右侧 C 代表 消费者,也就是往 RabbitMQ 拿消息的程序。

     交换器 (Exchange):消息生产者并没有直接将消息发送给消息队列,而是通过建立与Exchange的Channel,将消息发送给Exchange。Exchange根据路由规则,将消息转发给指定的消息队列。消息队列储存消息,等待消费者取出消息。消费者通过建立与消息队列相连的Channel,从消息队列中获取消息。提供Producer到Queue之间的匹配,接收生产者发送的消息并将这些消息按照路由规则转发到消息队列。交换器用于转发消息,它不会存储消息 ,如果没有 Queue绑定到 Exchange 的话,它会直接丢弃掉 Producer 发送过来的消息。

Channel(信道):多路复用连接中的一条独立的双向数据流通道。信道是建立在真实的TCP连接内的虚拟连接,复用TCP连接的通道。

Routing Key(路由键):消息头的一个属性,用于标记消息的路由规则,决定了交换机的转发路径。最大长度255 字节。

Queue(消息队列):存储消息的一种数据结构,用来保存消息,直到消息发送给消费者。它是消息的容器,也是消息的终点。一个消息可投入一个或多个队列。消息一直在队列里面,等待消费者连接到这个队列将消息取走。

Binding(绑定):用于建立Exchange和Queue之间的关联。一个绑定就是基于Binding Key将Exchange和Queue连接起来的路由规则,所以可以将交换器理解成一个由Binding构成的路由表。

Binding Key(绑定键):Exchange与Queue的绑定关系,用于匹配Routing Key。最大长度255 字节。

 

二、安装

 RabbitMQ由Erlang语言开发,需要安装与RabbitMQ版本对应的Erlang语言环境.

1、window环境

 RabbitMQ官网下载址:http://www.rabbitmq.com/install-windows.html
 Erlang官网下载地址:http://www.erlang.org/downloads

 点击下载好的.exe文件进行傻瓜式安装(一直next)即可
 Erlang配置Erlang环境变量

       

打开命令窗口,输入erl验证环境是否配置成功。

RabbitMQ配置环境变量

  

输入指令激活插件:rabbitmq-plugins.bat enable rabbitmq_management

重启服务器:net stop RabbitMQ && net start RabbitMQ

最后输入http://localhost:15672(默认账号:guest,密码:guest)就能进入RabbitMQ管理界面

2、linux

通过yum等软件仓库都可以直接安装RabbitMQ,但版本一般都较为保守。
RabbitMQ官网提供了新版的rpm包(http://www.rabbitmq.com/download.html),但是安装的时候会提示需要erlang版本>=19.3,然而默认yum仓库中的版本较低。
其实RabbitMQ在github上有提供新的erlang包(https://github.com/rabbitmq/erlang-rpm)
也可以直接加到yum源中

  1. #vim /etc/yum.repos.d/rabbitmq-erlang.repo
  2. [rabbitmq-erlang]
  3. name=rabbitmq-erlang
  4. baseurl=https://dl.bintray.com/rabbitmq/rpm/erlang/20/el/7
  5. gpgcheck=1
  6. gpgkey=https://dl.bintray.com/rabbitmq/Keys/rabbitmq-release-signing-key.asc
  7. repo_gpgcheck=0
  8. enabled=1
  9. #yum clean all
  10. #yum makecache

然后下载RabbitMQ的RPM包(http://www.rabbitmq.com/download.html)

  1. 这里是centos7的版本
  2. #wget https://dl.bintray.com/rabbitmq/all/rabbitmq-server/3.7.4/rabbitmq-server-3.7.4-1.el7.noarch.rpm
  3. #yum install rabbitmq-server-3.7.4-1.el7.noarch.rpm
  4. yum会自动去源里安装依赖包

 安装到这里就完成了,下面进行简单的配置

  1. 启动RabbitMQ服务
  2. #service rabbitmq-server start
  3. 状态查看
  4. #rabbitmqctl status
  5. 启用插件
  6. #rabbitmq-plugins enable rabbitmq_management
  7. 重启服务
  8. #service rabbitmq-server restart
  9. 添加帐号:name 密码:passwd
  10. #rabbitmqctl add_user name passwd
  11. 赋予其administrator角色
  12. #rabbitmqctl set_user_tags name administrator
  13. 设置权限
  14. #rabbitmqctl set_permissions -p / name ".*" ".*" ".*"

 然后就能够访问http://ip:15672进入web管理页面了(外部访问别忘记修改防火墙)。

三、工具类RabbitMQUtil

  1、往指定路由发送数据  sendMsg(Object event,String exchangePara,String routingkeyPara){}

  2、往指定队列发送数据 sendMsg(String queueName,Object event){}

  3、发送通知 sendJsonMessage(String dataType,Integer id,String empNo,String oldEmpNo,Integer type) {}  //调用1

  4、发送消息 send(MessageParam param){}   //调用1

@Component
@Slf4j
public class RabbitMQUtil {
    @Autowired
    private  RabbitTemplate rabbitTemplate;

    @Autowired
    private  ObjectMapper objectMapper;

    /**
     * 往指定路由发送数据
     * @param event 消息结构体
     * @param exchangePara
     * @param routingkeyPara
     */
    public void sendMsg(Object event,String exchangePara,String routingkeyPara){
        try {
            rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter());
            rabbitTemplate.setExchange(exchangePara);
            rabbitTemplate.setRoutingKey(routingkeyPara);
            rabbitTemplate.convertAndSend(MessageBuilder.withBody(objectMapper.writeValueAsBytes(event)).build());
        }catch (Exception e){
            log.error("RabbitMQUtil:sendMsg error:"+e.getMessage());
            e.printStackTrace();
        }
    }

    /**
     * 往指定队列发送消息
     * @param event 消息结构体
     * @param queueName 队列名称
     */
    public void sendMsg(String queueName,Object event){
        try {
            rabbitTemplate.convertAndSend(queueName,MessageBuilder.withBody(objectMapper.writeValueAsBytes(event)).build());
        }catch (Exception e){
            log.error("RabbitMQUtil_sendMsg error:"+e.getMessage());
            e.printStackTrace();
        }
    }

    @Value("${spring.rabbitmq.exchange}")
    public String exchange;
    @Value("${spring.rabbitmq.routingkey}")
    public String routingkey;

    @Value("${spring.rabbitmq.subscribe.exchange}")
    public String subscribeexchange;
    @Value("${spring.rabbitmq.subscribe.routingkey}")
    public String subscriberoutingkey;

    /**
     * 发送通知 imine-main-data 服务
     * @param dataType
     * @param id
     * @param empNo
     * @param oldEmpNo
     * @param type
     */
    public void sendJsonMessage(String dataType,Integer id,String empNo,String oldEmpNo,Integer type) {
        //通知
        JsonMessage message = new JsonMessage();
        message.setDataType(dataType);
        message.setType(type);
        MessageValue messageValue = new MessageValue();
        messageValue.setId(id);
        messageValue.setEmpNo(empNo);
        messageValue.setOldEmpNo(oldEmpNo);
        message.setValue(messageValue);
        this.sendMsg(message,exchange,routingkey);
    }

    /**
     * 发送通知
     * @param param
     */
    public void send(MessageParam param){
        this.sendMsg(param,subscribeexchange,subscriberoutingkey);
    }
}
View Code

四、发送消息

1、基本模型:直接往消息队列发送消息

2、work消息模型

两个消费端共同消费同一个队列中的消息,但是一个消息只能被一个消费者获取。

   

3、Publish/subscribe(交换机类型:Fanout,也称为广播 )

  • 1)声明Exchange,不再声明Queue

  • 2) 发送消息到Exchange,不再发送到Queue

4、Routing 路由模型(交换机类型:direct)

Exchange(交换机),接收生产者的消息,然后把消息递交给 与routing key完全匹配的队列,C1:消费者,其所在队列指定了需要routing key 为 error 的消息,C2:消费者,其所在队列指定了需要routing key 为 info、error、warning 的消息。

 5、Topics 通配符模式(交换机类型:topics)

每个消费者监听自己的队列,并且设置带统配符的routingkey,生产者将消息发给broker,由交换机根据routingkey来转发消息到指定的队列。

Routingkey一般都是有一个或者多个单词组成,多个单词之间以“.”分割,例如:inform.sms

6、RPC

     

 

业务:在增删改数据时往中间件发送消息,让别的系统消费消息可进行数据同步

生产者:向交换机和路由发送消息 在rabbit管理页面配置交换机下路由键和队列的关系,在队列下配置交换机和路由键的绑定
//区分新增还是编辑 if(position.getPositionId() == null){//新增 count = positionMapper.selectCount(queryWrapper); if(count >0){ return R.failed(false, CodeConstants.FAILURE_CODE,"职务编号/名称不可重复,请修改"); }else{ baseMapper.insert(position); rabbitMQUtil.sendJsonMessage("position",position.getPositionId(),position.getPositionNo(),position.getPositionNo(),1); MessageParam param = new MessageParam(); param.setObjId(position.getPositionId()); param.setObjNo(position.getPositionNo()); param.setDataType(RabbitDataType.POSITION_DATA_TYPE); param.setAction(1); rabbitMQUtil.send(param); //继续调用sendMsg(Object event,String exchangePara,String routingkeyPara){},向交换机发送消息,属订阅模式 return R.ok(true,"新增成功"); } }

消费者:监听队列消息
@RabbitListener(queues = "${spring.rabbitmq.queuename}")
public void receiveUserInfo(String message) {
try {
Gson gson = new Gson();
ResUtil command = gson.fromJson(message, ResUtil.class);
if (command != null) {
MessageParam param = command.getParam();
String dataType = param.getDataType();
switch (dataType) { //根据消息的类型区分是哪个基础信息
case RabbitmqDataType.USER:
threadDispatch(message, RabbitmqDataType.USER);
break;
case RabbitmqDataType.VEHICLE:
threadDispatch(message, RabbitmqDataType.VEHICLE);
break;
case RabbitmqDataType.DEPT:
threadDispatch(message, RabbitmqDataType.DEPT);
break;
default:
break;
}
}
} catch (Exception e) {
log.info("接收基础数据解析失败:" + e);
}
}

 

路由与队列的对应

五、类

  1 @Component
  2 @Slf4j
  3 public class RabbitMqListener {
  4     @Autowired
  5     private AlarmSaveService alarmSaveService;
  6 
  7     /**
  8      * 监听基础数据 vehicle_queue
  9      *
 10      * @param message
 11      */
 12     private AtomicInteger dispatchKeyGenerator = new AtomicInteger();
 13 
 14     @RabbitListener(queues = "${spring.rabbitmq.queuename}")
 15     public void receiveUserInfo(String message) {
 16         try {
 17             Gson gson = new Gson();
 18             ResUtil command = gson.fromJson(message, ResUtil.class);
 19             if (command != null) {
 20                 MessageParam param = command.getParam();
 21                 String dataType = param.getDataType();
 22                 switch (dataType) {
 23                     case RabbitmqDataType.USER:
 24                         threadDispatch(message, RabbitmqDataType.USER);
 25                         break;
 26                     case RabbitmqDataType.VEHICLE:
 27                         threadDispatch(message, RabbitmqDataType.VEHICLE);
 28                         break;
 29                     case RabbitmqDataType.DEPT:
 30                         threadDispatch(message, RabbitmqDataType.DEPT);
 31                         break;
 32                     case RabbitmqDataType.VEHICLETYPE:
 33                         threadDispatch(message, RabbitmqDataType.VEHICLETYPE);
 34                         break;
 35                     case RabbitmqDataType.DRIVER:
 36                         threadDispatch(message, RabbitmqDataType.DRIVER);
 37                         break;
 38                     case RabbitmqDataType.SHIFT:
 39                         threadDispatch(message, RabbitmqDataType.SHIFT);
 40                         break;
 41                     case RabbitmqDataType.WAITROOM:
 42                         threadDispatch(message, RabbitmqDataType.WAITROOM);
 43                         break;
 44                     case RabbitmqDataType.DISPATCH:
 45                         threadDispatch(message, RabbitmqDataType.DISPATCH);
 46                         break;
 47                     case RabbitmqDataType.DRIVERLICENSE:
 48                         threadDispatch(message, RabbitmqDataType.DRIVERLICENSE);
 49                         break;
 50                     case RabbitmqDataType.TRAFFICLIGHTMANAGE:
 51                         threadDispatch(message,RabbitmqDataType.TRAFFICLIGHTMANAGE);
 52                         break;
 53                     case RabbitmqDataType.TRAFFICLIGHT:
 54                         threadDispatch(message,RabbitmqDataType.TRAFFICLIGHT);
 55                         break;
 56                     case RabbitmqDataType.CARD:
 57                         threadDispatch(message,RabbitmqDataType.CARD);
 58                         break;
 59                     default:
 60                         break;
 61                 }
 62             }
 63         } catch (Exception e) {
 64             log.info("接收基础数据解析失败:" + e);
 65         }
 66     }
 67 
 68     /**
 69      * 监听告警信息 vehicle_alarm_queue
 70      *
 71      * @param message
 72      */
 73     @RabbitListener(queues = "${spring.rabbitmq.alarm.queuename}")
 74     public void receiveAlarm(String message) {
 75         try {
 76             Gson gson = new Gson();
 77             ResUtil command = gson.fromJson(message, ResUtil.class);
 78             AlarmDto alarmDto = command.getMsg();
 79             if (alarmDto != null) {
 80                 String alarmType = alarmDto.getAlarmTypeCode();
 81                 if (alarmDto.getDataType().equals(2)) {
 82                     log.info("收到告警数据:" + alarmDto);
 83                     List<AlarmVehicle> vehiclelist = JSON.parseArray(command.getData().toString(), AlarmVehicle.class);
 84                     alarmDto.setVehiclelist(vehiclelist);
 85                 } else{
 86                     return;
 87                 }
 88                 switch (alarmType) {
 89                     case RabbitmqAlarmType.AREA_SPEED: // 区间超速告警
 90                         alarmSaveService.saveAndUpdateAreaSpeedAlarm(alarmDto);
 91                         break;
 92                     case RabbitmqAlarmType.VEHICLE_OVERWEIGHT:
 93                         alarmSaveService.saveAndUpdateVehicleOverweightAlarm(alarmDto);
 94                         break;
 95                     case RabbitmqAlarmType.VEHICLE_STAY:
 96                         alarmSaveService.saveAndUpdateVehicleStayAlarm(alarmDto);
 97                         break;
 98                     case RabbitmqAlarmType.AREA_ATRESIA: // 闭锁区间告警
 99                         alarmSaveService.saveAndUpdateAreaAtresiaAlarm(alarmDto);
100                         break;
101                     case RabbitmqAlarmType.AREA_CARD: // 卡电量不足告警
102                         alarmSaveService.saveAndUpdateCardAlarm(alarmDto);
103                         break;
104                     default:
105                         break;
106                 }
107             }
108         } catch (Exception e) {
109             log.info("告警数据解析失败:" + e);
110         }
111     }
112 
113 
114     public void threadDispatch(Object data, String type) {
115         // 耗时操作,扔到业务线程池处理
116         MessageTask cmdTask = MessageTask.valueOf(dispatchKeyGenerator.getAndIncrement(), type, data);
117         MessageDispatcher dispatcher = new MessageDispatcher();
118         dispatcher.init();
119         dispatcher.addMessageTask(cmdTask);
120     }
121 }
View Code

六、项目实例

1、springboot配置类

@Configuration
public class RabbitMQConfig {

    @Value("${spring.rabbitmq.queuename}")
    private String queueName;

    @Value("${spring.rabbitmq.exchange}")
    public String exchange;

    @Value("${spring.rabbitmq.routingkey}")
    public String routingkey;
    
    @Bean(name ="basicInfo") //队列
    public Queue basicInfo() {
        return new Queue(queueName,true);
    }

    @Bean  //交换机
    public DirectExchange directExchange(){
        // durable:是否持久化;auto_delete:当所有消费客户端连接断开后,是否自动删除队列;
        return new DirectExchange(exchange, true, false);
    }

    @Bean  //绑定
    public Binding binding(){
        return BindingBuilder.bind(basicInfo()).to(directExchange()).with(routingkey);
    }
}

  

2、直接监听队列

@RabbitListener注解指定目标方法来作为消费消息的方法,通过注解参数指定所监听的队列或者Binding

@RabbitListener(queues = "${spring.rabbitmq.alarm.queuename}")
    public void receiveAlarm(String message) {
        try{
            log.info("收到告警数据:"+message);
            }catch (Exception e){
            log.error("收到告警数据:"+message);
            log.error("告警数据解析失败:"+e);
        }
    }

  指定向多个队列发送消息,逗号隔开

   @RabbitListener(queues ={"zhihao.miao.order","zhihao.info"})

3、@RabbitListener进行声明binding

@Component
public class MessageHandler {
 
    //支持自动声明绑定,声明之后自动监听队列的队列,此时@RabbitListener注解的queue和bindings不能同时指定,否则报错
    @RabbitListener(bindings ={@QueueBinding(value = @Queue(value = "q5",durable = "true"),
            exchange =@Exchange(value = "zhihao.miao.exchange",durable = "true"),key = "welcome")})
    public void handleMessage(Message message){
        System.out.println("====消费消息"+message.getMessageProperties().getConsumerQueue()+"===handleMessage");
        System.out.println(message.getMessageProperties());
        System.out.println(new String(message.getBody()));
    }
 }
 七、优秀博文
https://blog.csdn.net/qq_35387940/article/details/100514134

posted @ 2021-06-16 17:22  zhangtianhong511  阅读(114)  评论(0)    收藏  举报