消息总线和配置中心是微服务架构的两个重要组件
Spring Cloud Bus目前仅支持两款中间件产品:RabbitMQ和Kafka
RabbitMQ及Kafka安装请看消息队列相关笔记
整合RabbitMQ
1、引入AMQP依赖
pom.xml文件中加入:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
可看到多了一个jar包amqp-client-4.0.3.jar
2、在application.properties中配置RabbitMQ的连接和用户信息
#RabbitMQ配置
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=user1
spring.rabbitmq.password=123456
spring.rabbitmq.host指定RabbitMQ host
spring.rabbitmq.port指定RabbitMQ的端口
addresses与host+port的区别:
spring.rabbitmq.addresses指定client连接到的server的地址,可以配置多个服务地址,以逗号分隔,如host1:9999,host2:1111。host+port的方式只能配置一个服务地址。
3、创建消息生产者
通过AmqpTemplate接口发送消息,Spring Boot会根据配置来注入其具体实现,我们采用默认配置即可,
@Component
public class MqSender {
@Autowired
private AmqpTemplate amqpTemplate;
public void sendUserPojo(UserPojo userPojo) {
amqpTemplate.convertAndSend("queue1", userPojo);
}
public void sendStr(UserPojo userPojo) {
String str = JSON.toJSONString(userPojo);
amqpTemplate.convertAndSend("queue2", str);
}
}
convertAndSend有多个重载方法,如:
- convertAndSend(String routingKey, Object message)
- convertAndSend(String exchange, String routingKey, Object message)
为了方便测试可以在controller加上一个http接口:
@Autowired
private MqSender mqSender;
@RequestMapping(value = "/sendUserPojoToMQ", method = RequestMethod.POST)
public String sendUserPojoToMQ(@RequestBody UserPojo userPojo) {
mqSender.sendUserPojo(userPojo);
return "success";
}
@RequestMapping(value = "/sendStringToMQ", method = RequestMethod.POST)
public String sendStringToMQ(@RequestBody UserPojo userPojo) {
mqSender.sendStr(userPojo);
return "success";
}
可以通过http://localhost:15672的queue Tab查看队列详细信息,如果没有队列,可以手动创建一个,即使不创建,消息也能存放到队列中,只是在页面上看不到该队列的详细信息。
4、创建消息消费者
通过@RabbitListener修饰方法,对queue1队列进行监听,并用@RabbitHandler注解来指定对消息的处理方法,注意这两个注解都是用于方法,书上说@RabbitListener用于类,测试这样是会报错的。
@Component
public class MqReceiver {
@RabbitListener(queues="queue1")
@RabbitHandler
public void process1(@Payload UserPojo userPojo) {
System.out.println("收到消息:name="+userPojo.getName()+",age="+userPojo.getAge());
}
@RabbitListener(queues="queue2",exclusive = true)
@RabbitHandler
public void process2(String string) {
System.out.println("收到消息:"+string);
}
}
@RabbitListener有多个属性可以配置的,比较常用的是queues和exclusive,queues用于指定监听的队列。exclusive如果为true,则消费者排他式地使用队列,若多个消费者监听同一个队列,其中一个消费者取出了某条消息,则这条消息就不存在队列中了,其它消费者只能取下一条消息,默认为false,即每个消费者是独立的,队列中的每条消息都能被所有消费者一起使用。可以理解为,当为true时,是取出消息,当为false时,消费者仅仅把自己在队列中的下标移动而已。
消费者常见启动报错情况:
1、无限循环抛出以下异常:
Caused by: org.springframework.amqp.AmqpException: No method found for class [B
这种情况是@RabbitListener修饰的对象搞错了,书上说用于修饰类,实际上却是用于修饰方法的。
2、报错:Payload value must not be empty
1)方法的参数中加上注解@Payload
2)UserPojo要序列化(生产者、消费者的UserPojo都要序列化)
3)报错说找不到UserPojo类
从错误信息可看出,消费者的UserPojo所在的位置要和生产者一样,真是绑得好死,所以还要改一下消费者的UserPojo位置。所以,尽量还是不要用自定义的对象,最好是用json字符串。
4)报错说找不到队列,消费者要能启动的前提是队列已存在,如果队列还没创建,消费者启动是会报错的。
5、配置队列、交换器、路由等信息
以上代码都是默认配置,我们可以根据需要自定义配置,如下是一个最小化的配置:
/**
* 定义队列
*/
@Bean
public Queue queue1Queue() {
return new Queue("queue1");
}
/**
* 定义交换机
*/
@Bean
TopicExchange exchange() {
return new TopicExchange("exchange1");
}
/**
* 定义绑定
* @param queue
* @param exchange
*/
@Bean
Binding binding(Queue queue, TopicExchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with("bind-key-1");
}
注解方式如何灵活访问不同队列?
非注解方式
RabbitMQ配置项
整合Kafka