java练习生 - 使用Rabbit MQ

方案1:spring-cloud-starter-stream-rabbit

一、添加依赖

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
        </dependency>

二、添加配置(application.yml)

spring:
  application:
    name: demo1
  rabbitmq:
    host: 10.10.10.10
    port: 5672
    username: guest
    password: guest
    virtualHost: /dev-test

三、创建操作类

生产者:

import lombok.RequiredArgsConstructor;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class MqSender {
    final AmqpTemplate template;

    /**
     * 发送消息的方法
     *
     * @param msg
     */
    public void send(String msg) {
        //向消息队列发送消息
        //参数一:交换器名称。
        //参数二:路由键
        //参数三:消息
        this.template.convertAndSend("test", "hzq", msg);
    }
}
View Code

消费者:

import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.stereotype.Component;

@Slf4j
@Component
@RabbitListener(bindings = @QueueBinding(
        value = @Queue(value = "test", autoDelete = "false"),
        exchange = @Exchange(value = "test", type = ExchangeTypes.DIRECT),
        key = "test"
))
public class MqListener {
    @RabbitHandler
    public void process(String msg) {
        log.info("mq接收到信息:{}", msg);
    }

    @RabbitHandler
    public void process(byte[] content) {
        try {
            log.info("mq接收到信息:{}", new String(content,"UTF-8"));
        }
        catch (Exception ex){
            log.error("mq接收到信息异常");
        }
    }
}
View Code

四、使用

@Slf4j
@RestController
@RequestMapping("/test")
public class TestController {

    @Resource
    MqSender mqSender;

    // rabbit测试
    @GetMapping("/rabbit/set")
    public String rabbitSet(){
        log.info("添加rabbit开始。");
        String msg = String.format("发送信息,当前时间戳:{%s}", System.currentTimeMillis());
        mqSender.send(msg);
        log.info("添加rabbit结束。");
        return "添加rabbit结束。";
    }
}
View Code

 

方案2:spring-boot-starter-amqp

一、添加依赖

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

二、添加配置(application.yml)

2.1 添加连接配置信息(application.yml) 允许连接多个mq服务

rabbitmq:
  default:
    host: 10.10.10.10
    port: 5672
    username: guest
    password: guest
    vhost: /dev-test
  other:
    host: 10.10.10.11
    port: 5672
    username: guest
    password: guest
    vhost: /sit-test

2.2 创建配置类

import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.amqp.SimpleRabbitListenerContainerFactoryConfigurer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.retry.backoff.ExponentialBackOffPolicy;
import org.springframework.retry.policy.SimpleRetryPolicy;
import org.springframework.retry.support.RetryTemplate;

/**
 * rabbitMq 配置
 */
@Configuration
@Slf4j
public class RabbitMqConfig {

    // 默认系统的MQ连接配置信息
    @Value("${rabbitmq.default.host}")
    private String host;

    @Value("${rabbitmq.default.port}")
    private int port;

    @Value("${rabbitmq.default.username}")
    private String username;

    @Value("${rabbitmq.default.password}")
    private String password;

    @Value("${rabbitmq.default.vhost}")
    private  String vhost;

    // other系统的MQ连接配置信息
    @Value("${rabbitmq.other.host}")
    private String otherHost;

    @Value("${rabbitmq.other.port}")
    private int otherPort;

    @Value("${rabbitmq.other.username}")
    private String otherUserName;

    @Value("${rabbitmq.other.password}")
    private String otherPassWord;

    @Value("${rabbitmq.other.vhost}")
    private String otherVhost;

    /**
     * 重试配置-初始间隔
     */
    @Value("${spring.rabbitmq.listener.simple.retry.initial-interval:6000}")
    private Long initialInterval;
    /**
     * 重试配置-最大尝试次数
     */
    @Value("${spring.rabbitmq.listener.simple.retry.max-attempts:3}")
    private Integer maxAttempts;

    // 默认系统的MQ工厂

    /**
     * 默认系统的MQ工厂
     * @param configurer
     * @param connectionFactory
     * @return
     */
    @Bean(name = "defaultFactory")
    public SimpleRabbitListenerContainerFactory defaultFactory(SimpleRabbitListenerContainerFactoryConfigurer configurer,
                                                               @Qualifier("defaultConnectionFactory") ConnectionFactory connectionFactory) {
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        configurer.configure(factory, connectionFactory);
//        factory.setMessageConverter(new Jackson2JsonMessageConverter());
        return factory;
    }

    @Bean(name="defaultRabbitTemplate")
    @Primary
    public RabbitTemplate defaultRabbitTemplate() {
        RabbitTemplate template = new RabbitTemplate(defaultConnectionFactory());
        template.setRetryTemplate(retryTemplate());
//        template.setMessageConverter(new Jackson2JsonMessageConverter());
        return template;
    }

    /**
     * 设置成手动模式的原因, 不在使用rabbit的默认分发方式(轮询的方式), 而使用公平的方式。 使用公平的方式可以实现负载均衡。
     * 具体实现:basicQos( prefetchCount=1)方法,来限制RabbitMQ只发不超过1条的消息给同一个消费者。当消息处理完毕后,有了反馈,才会进行第二次发送。
     *
     * @return ConnectionFactory
     */
    @Bean(name="defaultConnectionFactory")
    @Primary
    public ConnectionFactory defaultConnectionFactory() {
        CachingConnectionFactory connectionFactory = new CachingConnectionFactory(host, port);
        connectionFactory.setUsername(username);
        connectionFactory.setPassword(password);
        connectionFactory.setVirtualHost(vhost);
        //设置手动确认模式
        connectionFactory.setPublisherConfirms(true);
        return connectionFactory;
    }

    // vbs系统的MQ工厂

    /**
     * vbs系统的MQ工厂
     * @param configurer
     * @param connectionFactory
     * @return
     */
    @Bean(name = "otherFactory")
    public SimpleRabbitListenerContainerFactory vbsFactory(SimpleRabbitListenerContainerFactoryConfigurer configurer,
                                                           @Qualifier("otherConnectionFactory") ConnectionFactory connectionFactory) {
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        configurer.configure(factory, connectionFactory);
        return factory;
    }

    @Bean(name="otherRabbitTemplate")
    public RabbitTemplate vbsrabbitTemplate() {
        RabbitTemplate template = new RabbitTemplate(otherConnectionFactory());
        template.setRetryTemplate(retryTemplate());
        return template;
    }

    @Bean(name="otherConnectionFactory")
    public ConnectionFactory otherConnectionFactory() {
        CachingConnectionFactory connectionFactory = new CachingConnectionFactory(otherHost, otherPort);
        connectionFactory.setUsername(otherUserName);
        connectionFactory.setPassword(otherPassWord);
        connectionFactory.setVirtualHost(otherVhost);
        //设置手动确认模式
        connectionFactory.setPublisherConfirms(true);
        return connectionFactory;
    }

    /**
     * 构建rabbitMQ重试模板
     * @return RetryTemplate
     */
    private RetryTemplate retryTemplate(){
        RetryTemplate retryTemplate = new RetryTemplate();
        ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy();
        backOffPolicy.setInitialInterval(initialInterval);
        retryTemplate.setBackOffPolicy(backOffPolicy);
        retryTemplate.setRetryPolicy(new SimpleRetryPolicy(maxAttempts));
        return retryTemplate;
    }
//
//    /**
//     * 初始化默认系统MQ路由配置
//     */
//    @Bean
//    public RabbitAdmin rabbitAdmin() {
//        RabbitAdmin rabbitAdmin = new RabbitAdmin(defaultConnectionFactory());
//        rabbitAdmin.setAutoStartup(true);
//        //创建队列和交换机,并绑定
//        creactQueueAndChange(rabbitAdmin);
//        return rabbitAdmin;
//    }
//
//    /**
//     * 默认系统中所有的MQ路由配置信息
//     */
//    @Value("${rabbitInfo:{}}")
//    private String rabbitInfo;
//
//    /**
//     * 默认交换机名称
//     */
//    private static final String defualtExchange = "test";

//    /**
//     * 创建队列和交换机,并绑定
//     * @param rabbitAdmin
//     */
//    private void creactQueueAndChange(RabbitAdmin rabbitAdmin){
//        rabbitAdmin.declareExchange(new DirectExchange(defualtExchange,true,false));
//        Map<String, Object> arguments = new HashMap<>();
//        arguments.put("x-max-priority",10); // 设置队列优先级级别
//        List<RabbitObject> rabbitObjectDTOList = JSONObject.parseArray(rabbitInfo,RabbitObject.class);
//        for(RabbitObject rabbitObjectDTO:rabbitObjectDTOList){
//            rabbitAdmin.declareQueue(new Queue(rabbitObjectDTO.queueName,true,false,false,arguments));
//            rabbitAdmin.declareBinding(new Binding(rabbitObjectDTO.queueName, Binding.DestinationType.QUEUE, rabbitObjectDTO.exchangeName, rabbitObjectDTO.routingKey, null));
//        }
//    }
}
View Code

 

三、创建操作类

生产者:

import com.alibaba.fastjson.JSONObject;
import lombok.extern.log4j.Log4j2;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

import java.util.UUID;

/**
 * rabbit 发送实例
 */
@Service
@Log4j2
public class RabbitSendServiceImpl implements RabbitSendService, RabbitTemplate.ConfirmCallback {

    private RabbitTemplate rabbitTemplate;

    /**
     * 构造方法注入rabbitTemplate
     */
    @Autowired
    public RabbitSendServiceImpl(@Qualifier("defaultRabbitTemplate") RabbitTemplate rabbitTemplate) {
        this.rabbitTemplate = rabbitTemplate;
        rabbitTemplate.setConfirmCallback(this);
    }

    @Override
    public void sendMsg(String exchangeName, String routeName, Object message, String messageId) {
        CorrelationData correlationId = new CorrelationData(messageId);
        rabbitTemplate.convertAndSend(exchangeName, routeName, JSONObject.toJSONString(message), correlationId);
    }

//    @Override
//    public void sendMsg(MqInfoEnum mqInfoEnum, Object message, String messageId) {
//        CorrelationData correlationId = new CorrelationData(messageId);
//        rabbitTemplate.convertAndSend(mqInfoEnum.getExchangeName(), mqInfoEnum.getRouteName(), JSONObject.toJSONString(message),
//                correlationId);
//    }

    @Override
    public void sendMsg(String exchangeName, String routeName, Object message) {
        CorrelationData correlationId = new CorrelationData(UUID.randomUUID().toString());
        rabbitTemplate.convertAndSend(exchangeName, routeName, JSONObject.toJSONString(message), correlationId);
    }

//    @Override
//    public void sendMsg(MqInfoEnum mqInfoEnum, Object message) {
//        CorrelationData correlationId = new CorrelationData(UUID.randomUUID().toString());
//        rabbitTemplate.convertAndSend(mqInfoEnum.getExchangeName(), mqInfoEnum.getRouteName(), JSONObject.toJSONString(message),
//                correlationId);
//    }


//    @Override
//    public void sendDefaultMsg(Object message, String messageId) {
//        CorrelationData correlationId = new CorrelationData(messageId);
//        rabbitTemplate.convertAndSend(MqInfoEnum.VTS_DEFAULT.getExchangeName(), MqInfoEnum.VTS_DEFAULT.getRouteName(),
//                JSONObject.toJSONString(message),
//                correlationId);
//    }

    @Override
    public void confirm(CorrelationData correlationData, boolean b, String s) {
        if (b) {
            log.info("回调[{}]结果:消息成功消费", correlationData);
        } else {
            log.info("回调[{}]结果:消息消费失败:{}", correlationData, s);
        }
    }
}
View Code

消费者:

公共类

import hzq.maven.demo.service.rabbit.RabbitListenService;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.stereotype.Service;

import java.io.UnsupportedEncodingException;

@Service
public abstract class RabbitListenServiceImpl implements RabbitListenService {

    /**
     * 监听
     *
     * @param content mq消息
     */
    @RabbitHandler
    public void process(String content) {
        try {
            execute(content);
        } catch (Exception e) {
            // 保存异常消息到消息补偿表中,根据具体要求实现
            // 必须将异常抛出去,不然是不会触发rabbit unack
            throw e;
        }
    }

    @RabbitHandler
    public void process(byte[] content) {
        try {
            String result = new String(content,"UTF-8");
            execute(result);
        } catch (Exception e) {
            // 保存异常消息到消息补偿表中,根据具体要求实现
            // 必须将异常抛出去,不然是不会触发rabbit unack
            try {
                throw e;
            } catch (UnsupportedEncodingException e1) {
                e1.printStackTrace();
            }
        }
    }

    /**
     * 接受消息
     * @param message 消息体
     */
    public abstract void execute(String message);

}
View Code
子类
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import hzq.maven.demo.model.dto.TestMqDataDTO;
import hzq.maven.demo.service.business.TestService;
import hzq.maven.demo.service.rabbit.impl.RabbitListenServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

/**
 * 测试消息队列
 */
@Slf4j
@Component
@RabbitListener(queues = "test",containerFactory="defaultFactory")
public class TestListen extends RabbitListenServiceImpl {
    @Resource
    private TestService testService;

    public void execute(String message) {
        TestMqDataDTO rabbitMqDataDTO;
        if(JSON.isValid(message)){
            JSONObject jsonObject = JSON.parseObject(message);
            rabbitMqDataDTO = jsonObject.toJavaObject(TestMqDataDTO.class);
        }
        else{
            rabbitMqDataDTO = new TestMqDataDTO();
            rabbitMqDataDTO.setName(message);
        }

        testService.mqListen(rabbitMqDataDTO);
    }
}
View Code

四、使用

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

@Slf4j
@Service
@RequiredArgsConstructor
public class TestServiceImpl implements TestService {
    @Resource
    private RabbitSendService rabbitSendService;

    @Override
    public void mqSend(TestMqDataDTO testMqDataDTO){
        //推送信息到MQ
        rabbitSendService.sendMsg("test","hzq",testMqDataDTO);
        log.info("mq发送数据成功:" + testMqDataDTO.toString());
    }

    @Override
    public void mqListen(TestMqDataDTO testMqDataDTO){
        //从MQ接收信息
        log.info("mq消费数据成功:" + testMqDataDTO.toString());
        // do...
    }
}
View Code

 

方案3:spring-cloud-starter-stream-rabbit

一、添加依赖

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
        </dependency>

二、添加配置(application.yml)

spring:
cloud:
stream:
default-binder: rabbit_server_1 # 设置默认的binder
binders: #需要绑定的rabbitmq的服务信息
rabbit_server_1: #定义的名称,用于bidding整合
type: rabbit #消息组件类型,目前只支持rabbit和kafka
environment: #配置rabbitmq连接环境
spring:
rabbitmq:
host: 10.138.60.219 #rabbitmq 服务器的地址
port: 5672 #rabbitmq 服务器端口
username: guest #rabbitmq 用户名
password: guest #rabbitmq 密码
virtual-host: /creditTrade #虚拟路径
rabbit_server_2: #定义的名称,用于bidding整合
type: rabbit #消息组件类型,目前只支持rabbit和kafka
environment: #配置rabbitmq连接环境
spring:
rabbitmq:
host: 10.138.60.219 #rabbitmq 服务器的地址
port: 5672 #rabbitmq 服务器端口
username: guest #rabbitmq 用户名
password: guest #rabbitmq 密码
virtual-host: /creditTrade/dev #虚拟路径
bindings: #服务的整合处理
input-from-server1: #这个是消息通道的名称
destination: test_hzq_exchange_01 #exchange名称,交换模式默认是topic;把SpringCloud stream的消息输出通道绑定到RabbitMQ的exchange-saveOrder交换器。
content-type: application/json #设置消息的类型,本次为json
binder: rabbit_server_1 #指定使用的绑定器,如果只有一个绑定器可以不配置
group: t1 #分组 用以生成【destination.group】的队列,如:test_hzq_exchange_01.t1
consumer.concurrency: 5 # 配置开启多个消费线程
output-to-server1: #这个是消息通道的名称
destination: test_hzq_exchange_01 #exchange名称,交换模式默认是topic;把SpringCloud stream的消息输出通道绑定到RabbitMQ的exchange-saveOrder交换器。
content-type: application/json #设置消息的类型,本次为json
binder: rabbit_server_1 #指定使用的绑定器,如果只有一个绑定器或者设置了默认绑定器可以不配置
group: t1 #分组 因为服务很可能不止一个实例,如果启动多个实例,默认每个实例都会消费一遍同一个消息,只要把功能相同的实例的 group 设置为同一个,那么就会只有一个实例来消费消息,避免重复消费的情况。如果设置了 group,那么 【destination.group】 名称就会作为 queue 的名称,如果没有设置 group ,那么 queue 就会根据 destination + 随机字符串的方式命名。
input-from-server2: #这个是消息通道的名称
destination: test_hzq_exchange_02 #exchange名称,交换模式默认是topic;把SpringCloud stream的消息输出通道绑定到RabbitMQ的exchange-saveOrder交换器。
content-type: application/json #设置消息的类型,本次为json
binder: rabbit_server_2 #指定使用的绑定器,如果只有一个绑定器可以不配置
group: t2 #分组 用以生成【destination.group】的队列,如:test_hzq_exchange_01.t1
output-to-server2: #这个是消息通道的名称
destination: test_hzq_exchange_02 #exchange名称,交换模式默认是topic;把SpringCloud stream的消息输出通道绑定到RabbitMQ的exchange-saveOrder交换器。
content-type: application/json #设置消息的类型,本次为json
binder: rabbit_server_2 #指定使用的绑定器,如果只有一个绑定器或者设置了默认绑定器可以不配置
group: t2 #分组 因为服务很可能不止一个实例,如果启动多个实例,默认每个实例都会消费一遍同一个消息,只要把功能相同的实例的 group 设置为同一个,那么就会只有一个实例来消费消息,避免重复消费的情况。如果设置了 group,那么 【destination.group】 名称就会作为 queue 的名称,如果没有设置 group ,那么 queue 就会根据 destination + 随机字符串的方式命名。

rabbit.bindings:
input-from-server1.consumer:
bindingRoutingKey: t1 # 队列绑定交换机的routeKey
maxPriority: 10 # 声明优先级队列并设置最大优先级,之后发布者可以使用priority属性发布优先级消息,数字越大代表优先级越高
output-to-server1.producer:
routingKeyExpression: '''t1''' # 发送消息的routeKey
exchangeType: direct # 默认为topic,设置交换机类型
 

三、创建通道

消费通道:

import org.springframework.cloud.stream.annotation.Input;
import org.springframework.messaging.SubscribableChannel;

/**
 * 接收通道
 * 在同一类中添加输入和输出通道可能将产生不可预测的结果,所以这里将输入和输出分为两个channel类
 */
public interface InChannel {
    @Input("input-from-server1")
    SubscribableChannel inputFromServer1();
    @Input("input-from-server2")
    SubscribableChannel inputFromServer2();
}

 

生产通道:

import org.springframework.cloud.stream.annotation.Output;
import org.springframework.messaging.MessageChannel;

/**
 * 发送通道
 * 在同一类中添加输入和输出通道可能将产生不可预测的结果,所以这里将输入和输出分为两个channel类
 */
public interface OutChannel {
    @Output("output-to-server1")
    MessageChannel outputToServer1();
    @Output("output-to-server2")
    MessageChannel outputToServer2();
}

 

四、使用

发送消息

import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.messaging.support.MessageBuilder;

@Slf4j
@RestController
@RequestMapping("/api/test/")
@EnableBinding(OutChannel.class) // 表明启用 stream ,并指定定义的 Channel 定义接口类。
@RequiredArgsConstructor
public class TestController {
    private final OutChannel channel;
    private final TestConfig config;
    
    /**
     * 发送消息到【业务1】
     * @return
     */
    @GetMapping(value = "get1")
    public String get1() {
        channel.outputToServer1().send(MessageBuilder.withPayload("测试1").build());
        log.info("发送消息到【output-to-server1】");
        return "测试1";
    }

    /**
     * 发送消息到【业务2】
     * @return
     */
    @GetMapping(value = "get2")
    public String get2() {
        channel.outputToServer2().send(MessageBuilder.withPayload("测试2").build());
        log.info("发送消息到【output-to-server2】");
        return "测试2";
    }
}

消费消息:

import hzq.test.channel.InChannel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.integration.annotation.Transformer;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.messaging.handler.annotation.SendTo;

@Slf4j
@EnableBinding(InChannel.class)
public class Listener {
//    /**
//     * 【业务1】消费消息
//     * @param payload
//     */
//    @StreamListener(value = "input-from-server1")
//    public void onMessage1(@Payload String payload) {
//        log.info("onMessage1接收:input-from-server1" + payload);
//    }

    /**
     * 【业务2】消费消息
     * @param payload
     */
    @StreamListener(value = "input-from-server2")
    public void onMessage2(@Payload String payload) {
        log.info("onMessage2接收:input-from-server2" + payload);
    }

    /**
     * 【业务1】消费消息,并将结果发送到【业务2】
     * @param payload
     * @return
     */
    @Transformer(inputChannel = "input-from-server1", outputChannel = "output-to-server2")
    public String onMessage3(@Payload String payload) {
        log.info("onMessage3接收:input-from-server1" + payload);
        log.info("onMessage3发送:output-to-server2" + payload);
        return "消息来自onMessage3";
    }

    /**
     * 【业务1】消费消息,并将结果发送到【业务2】
     * @param payload
     * @return
     */
    @StreamListener(value = "input-from-server1")
    @SendTo("output-to-server2")
    public String onMessage4(@Payload String payload) {
        log.info("onMessage4接收:input-from-server1" + payload);
        log.info("onMessage4发送:output-to-server2" + payload);
        return "消息来自onMessage4";
    }
}

 

posted @ 2021-06-15 17:54  Ariter  阅读(133)  评论(0)    收藏  举报