springboot与rabbitmq(一)

初始RabbitMq

应用场景

邮箱发送:用户注册后投递消息到rabbitmq中,由消息的消费方异步的发送邮件,提升系统响应速度
流量削峰:一般在秒杀活动中应用广泛,秒杀会因为流量过大,导致应用挂掉,为了解决这个问题,一般在应用前端加入消息队列。用于控制活动人数,将超过此一定阀值的订单直接丢弃。缓解短时间的高流量压垮应用。
订单超时:利用rabbitmq的延迟队列,可以很简单的实现订单超时的功能,比如用户在下单后30分钟未支付取消订单。.

各中间件比较

.各种消息中间件性能的比较:
TPS比较 一ZeroMq 最好,RabbitMq 次之, ActiveMq 最差。
持久化消息比较—zeroMq不支持,activeMq和rabbitMq都支持。持久化消息主要是指:MQ down或者MQ所在的服务器down了,消息不会丢失的机制。
可靠性、灵活的路由、集群、事务、高可用的队列、消息排序、问题追踪、可视化管理工具、插件系统、社区—RabbitMq最好,ActiveMq次之,ZeroMq最差。
高并发—从实现语言来看,RabbitMQ最高,原因是它的实现语言是天生具备高并发高可用的erlang语言。
综上所述:RabbitMQ的性能相对来说更好更全面,是消息中间件的首选。

主要组件说明:

Broker:它提供一种传输服务,它的角色就是维护一条从生产者到消费者的路线,保证数据能按照指定的方式进行传输,
Exchange:消息交换机,它指定消息按什么规则,路由到哪个队列。
Queue:消息的载体,每个消息都会被投到一个或多个队列。
Binding:绑定,它的作用就是把exchange和queue按照路由规则绑定起来.
Routing Key:路由关键字,exchange根据这个关键字进行消息投递。
vhost:虚拟主机,一个broker里可以有多个vhost,用作不同用户的权限分离。
Producer:消息生产者,就是投递消息的程序.
Consumer:消息消费者,就是接受消息的程序.
Channel:消息通道,在客户端的每个连接里,可建立多个channel.

windows 安装

安装Erlang

所以在安装rabbitMQ之前,需要先安装Erlang 。
配置好环境变量
springboot与rabbitmq(一)_第1张图片
命令行输入命令:erl ,验证是否安装成功

下载rabbitMq

下载运行rabbitmq-server-3.6.5 ,需要其他版本或者32位系统的,可以去官网下载。

依旧可以不改变默认进行安装。

需要注意:默认安装的RabbitMQ 监听端口是5672
然后是配置环境变量。

激活 RabbitMQ’s Management Plugin

使用RabbitMQ 管理插件,可以更好的可视化方式查看Rabbit MQ 服务器实例的状态。

打开命令窗口:

输入命令:

“D:\JavaApp\RabbitMq\RabbitMQ Server\rabbitmq_server-3.7.13\sbin\rabbitmq-plugins.bat” enable rabbitmq_management

启动命名:

net stop RabbitMQ;
net start RabbitMQ

常用命令

rabbitmqctl.bat list_users 查看用户和权限
add_user xudong 123456 增加用户
rabbitmqctl.bat set_user_tags xudong administrator 设置用户权限

代码实现

引入pom


            org.springframework.boot
            spring-boot-starter-amqp
            1.5.2.RELEASE
        

配置rabbitConfig类

package com.example.config;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * Created by Administrator on 2019/3/22.
 * 定义队列queue
 */
@Configuration
public class RabbitConfig {


    public static final String QUEUE_A = "QueueA";
    public static final String QUEUE_B = "QueueB";
    public static final String QUEUE_C = "QueueC";

    public static final String EXCHANGE_A = "exchangeA";
    public static final String EXCHANGE_B = "exchangeB";
    public static final String EXCHANGE_C = "exchangeC";

    public static final String ROUTINGKEY_A = "Routingkey_A";
    public static final String ROUTINGKEY_B = "Routingkey_B";
    public static final String ROUTINGKEY_C = "Routingkey_C";

    /**
     * 获取交换机: EXCHANGE_A
     * 针对消费者配置交换机
     * 1. 设置交换机类型
     * 2. 将队列绑定到交换机
     *  FanoutExchange: 将消息分发到所有的绑定队列,无routingkey的概念
     *  HeadersExchange :通过添加属性key-value匹配
     *  DirectExchange:按照routingkey分发到指定队列
     *  TopicExchange:多关键字匹配
     */
    @Bean
    public DirectExchange getExchangeA() {
        return new DirectExchange(EXCHANGE_A);
    }
    /**
     * 获取队列 :QUEUE_A
     * @return
     */
    @Bean
    public Queue queueA() {
        return new Queue(QUEUE_A, true); //第一个参数队列名称,第二个队列持久化,遇到宕机不会丢失数据
    }

    /**
     * binding:将交换机和队列通过路由关键字绑定,不绑定也可以,用默认的交换机
     * @return
     */
    @Bean
    public Binding binding() {
        return BindingBuilder.bind(queueC()).to(getExchangeC()).with(RabbitConfig.ROUTINGKEY_C);
    }
    /**
     * 获取队列 :QUEUE_B
     * @return
     */
    @Bean
    public Queue queueB() {
        return new Queue(QUEUE_B, true); //第一个参数队列名称,第二个队列持久化,遇到宕机不会丢失数据
    }
    /**
     * 获取队列 :QUEUE_C
     * @return
     */
    @Bean
    public Queue queueC() {
        return new Queue(QUEUE_C, true); //第一个参数队列名称,第二个队列持久化,遇到宕机不会丢失数据
    }
    @Bean
    public DirectExchange getExchangeC() {
        return new DirectExchange(EXCHANGE_C);
    }

}

application.properties


##rabbitMq
spring.rabbitmq.username=xudong
spring.rabbitmq.password=123456
spring.rabbitmq.host=127.0.0.1
spring.rabbitmq.port=5672
spring.rabbitmq.virtual-host=/

# 手动ACK 不开启自动ACK模式,目的是防止报错后未正确处理消息丢失 默认 为 none
spring.rabbitmq.listener.simple.acknowledge-mode=manual

消费者

package com.example.listener;

import com.example.config.RabbitConfig;
import com.example.model.Student;
import com.rabbitmq.client.Channel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

import java.io.IOException;

/**
 * Created by Administrator on 2019/3/22.
 */
@Component
public class BookHandler {
    private static final Logger log = LoggerFactory.getLogger(BookHandler.class);

    /**
     * 

TODO 该方案是 spring-boot-data-amqp 默认的方式,不太推荐。具体推荐使用 listenerManualAck()

* 默认情况下,如果没有配置手动ACK, 那么Spring Data AMQP 会在消息消费完毕后自动帮我们去ACK * 存在问题:如果报错了,消息不会丢失,但是会无限循环消费,一直报错,如果开启了错误日志很容易就吧磁盘空间耗完 * 解决方案:手动ACK,或者try-catch 然后在 catch 里面讲错误的消息转移到其它的系列中去 * spring.rabbitmq.listener.simple.acknowledge-mode=manual *

* * @param stu 监听的内容 */ @RabbitListener(queues = {RabbitConfig.QUEUE_B}) public void listenerAutoAck(Student stu, Message message, Channel channel) { // TODO 如果手动ACK,消息会被监听消费,但是消息在队列中依旧存在,如果 未配置 acknowledge-mode 默认是会在消费完毕后自动ACK掉 final long deliveryTag = message.getMessageProperties().getDeliveryTag(); try { log.info("[listenerAutoAck 监听的消息] - [{}]", stu.toString()); // TODO 通知 MQ 消息已被成功消费,可以ACK了 channel.basicAck(deliveryTag, false); } catch (IOException e) { try { // TODO 处理失败,重新压入MQ channel.basicRecover(); } catch (IOException e1) { e1.printStackTrace(); } } } @RabbitListener(queues = {RabbitConfig.QUEUE_C}) public void listenerManualAck(Student stu, Message message, Channel channel) { log.info("[listenerManualAck 监听的消息] - [{}]", stu.toString()); try { // TODO 通知 MQ 消息已被成功消费,可以ACK了 channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); } catch (IOException e) { // TODO 如果报错了,那么我们可以进行容错处理,比如转移当前消息进入其它队列 try { // TODO 处理失败,重新压入MQ channel.basicRecover(); } catch (IOException e1) { e1.printStackTrace(); } } } }

controller端添加生产消息

package com.example.controller;

import com.example.config.RabbitConfig;
import com.example.listener.BookHandler;
import com.example.model.Student;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;



/**
 * Created by Administrator on 2019/3/22.
 */
@Controller
@RequestMapping("/rabbigMq")
@Api(value="StudentRabbitMqController",description = "rabbitMq测试学生")
public class StudentRabbitMqController {
    private final RabbitTemplate rabbitTemplate;
    private static final Logger log = LoggerFactory.getLogger(StudentRabbitMqController.class);
    @Autowired
    public StudentRabbitMqController(RabbitTemplate rabbitTemplate) {
        this.rabbitTemplate = rabbitTemplate;
    }

    /**
     * this.rabbitTemplate.convertAndSend(RabbitConfig.DEFAULT_BOOK_QUEUE, book); 对应 {@link BookHandler#listenerAutoAck}
     * this.rabbitTemplate.convertAndSend(RabbitConfig.MANUAL_BOOK_QUEUE, book); 对应 {@link BookHandler#listenerManualAck}
     */
    @ApiOperation(value = "rabbitmq消息队列")
    @RequestMapping(value = "/stu",method = {RequestMethod.GET})
    @ResponseBody
    public String proMsg() {
        Student stu = new Student();
        stu.setId((short)1);
        stu.setName("学习mq的旭东");
        log.info("生产者生产消息:"+stu);
        this.rabbitTemplate.convertAndSend(RabbitConfig.QUEUE_B, stu);
        this.rabbitTemplate.convertAndSend(RabbitConfig.QUEUE_C, stu);
        return "success";
    }
}

启动springboot,用swagger调用

你可能感兴趣的:(RabbitMq)