消息中间件作用:异步解耦
rabbitMq的组成:
producer:生产者,消息的发送方
connection:生产者或消费者和mq的连接,是一种TCP长连接
broker:mq服务器或者叫代理
virtual host:虚拟主机,用来隔离不同环境,比如测试环境和生产环境,或者用户之间的隔离
exchange:交换机,处理消息分配到相应的队列,有四种类型:direct(直接交换机,路由键完全一致,点对点路由)、fanout(广播,路由到所有该交换机绑定的队列)、topic(根据路由键路由,可以模糊匹配)、header(性能不好,很少用)
queue:存放消息,消费者从队列中取消息
binding:交换机和队列的绑定关系,一个交换机可以绑定多个队列,一个队列也可以被多个交换机绑定,交换机还可以绑定交换机
consumer:消费者,消费者主动拉取消息
channel:信道,在connection上建立的虚拟链接,每个线程通过自己的信道连接,就减少了TCP连接销毁的开销。
安装rabbitMq
通过docker安装命令:
docker run -d —name rabbitmq -p 5671:5671 -p 5672:5672 -p 4369:4369 -p 25672:25672 -p 15671:15671 -p 15672:15672 rabbitmq:management
4369,25672(Elang发现和集群端口)
5672,5671(AMQP端口)
15672(web管理后台端口)
61613,61614(STOMP协议端口)
1883,8883(MQTT协议端口)
rabbitmq:management(management版本带可视化界面)
安装完成后 网页输入ip:15672则可打开rabbitmq界面(用户名和密码:guest):
springboot整合rabbitmq
导入依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
<version>2.2.6.RELEASE</version>
</dependency>
以下方法在测试类中
创建交换机:
@Test
void createExchange() {
// DirectExchange 、 FanoutExchange、 TopicExchange。。。
//DirectExchange(String name, boolean durable, boolean autoDelete, Map<String, Object> arguments)
/**
* name:交换机名称
* durable:是否持久化(默认为true,设为false则重启后销毁)
* autoDelete:是否自动删除(默认false)
* arguments:
*/
DirectExchange directExchange = new DirectExchange("hello-exchange");
amqpAdmin.declareExchange(directExchange);
log.info("交换机{}创建成功", "hello-exchange");
}
执行结果:
下面的是直接在页面新建交换机,参数和代码里的参数一致。
创建队列:
@Test
void createQueue() {
//Queue(String name, boolean durable, boolean exclusive, boolean autoDelete, @Nullable Map<String, Object> arguments)
/**
* name:队列名称
* durable:是否持久化(默认为true,设为false则重启后销毁)
* exclusive:是否排他,只对首次连接可见(默认false)
* autoDelete:是否自动删除(默认false)
* arguments:
*/
Queue queue = new Queue("hello-queue");
amqpAdmin.declareQueue(queue);
log.info("队列{}创建成功", "hello-queue");
}
执行结果:
创建绑定关系:
@Test
void createBinding() {
//Binding(String destination, Binding.DestinationType destinationType, String exchange, String routingKey, @Nullable Map<String, Object> arguments)
/**
* 参数:
* destination:目标队列/交换机
* Binding.DestinationType:目标类型:queue/exchange
* exchange:交换机
* routingKey:路由键
* arguments:
*/
Binding binding = new Binding("hello-queue", Binding.DestinationType.QUEUE, "hello-exchange", "hello.route", null);
amqpAdmin.declareBinding(binding);
log.info("绑定关系{}和{}创建成功", "hello-queue", "hello-exchange");
}
执行结果:
发送消息:
/**
* 发送消息为对象时需要序列化:
* import lombok.Data;
* import java.io.Serializable;
*
* // 消息传递的对象必须继承Serializable,实现序列化
* @Data
* public class Person implements Serializable {
* private String name;
*
* private int id;
*
* private int age;
* }
*
* 收到的消息也是序列化之后的数据
*
* 可以在容器中注入MessageConverter,则可以使用我们定义的MessageConverter
*
* import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
* import org.springframework.amqp.support.converter.MessageConverter;
* import org.springframework.context.annotation.Bean;
* import org.springframework.context.annotation.Configuration;
*
* @Configuration
* public class MyRabbitConfig {
*
* @Bean
* public MessageConverter messageConverter() {
* // 转换成json
* return new Jackson2JsonMessageConverter();
* }
* }
*/
@Test
void setMessage(){
Person person = new Person();
person.setAge(19);
person.setId(1);
person.setName("haha");
rabbitTemplate.convertAndSend("hello-exchange", "hello-queue", person);
}
执行结果:
监听消息:
@EnableRabbit:开启基于注解的rabbit
@RabbitListener:指定监听的队列,可以标注在方法上也可以标注在类上(标注在类上和@RabbitHander一起使用,用于接收多种返回体的消息)
调用发送消息接口:
@GetMapping("/sendMessage")
@ApiOperation(value = "发送消息", notes = "发送i条消息")
public String sendMessage(@RequestParam(defaultValue = "10") Integer i) {
return producer.senMessage(i);
}
发送消息:
import com.example.gpy.provider.vo.Person;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Slf4j
@Service
public class Producer {
@Autowired
RabbitTemplate rabbitTemplate;
public String senMessage(Integer num) {
log.info("发送消息开始:" + num);
for(int i = 0; i < num; i++) {
Person person = new Person();
person.setAge(19);
person.setId(i);
person.setName("haha" + i);
rabbitTemplate.convertAndSend("hello-exchange", "hello-queue", person);
}
return "OK";
}
}
监听消息:
import com.example.gpy.provider.vo.Person;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Slf4j
// 监听消息的组件需要在容器中
@Component
public class customer {
@RabbitListener(queues = "hello-queue")
public void getMessageHello(Message message, Person person) throws InterruptedException {
log.info("接收消息:{}", person);
Thread.sleep(1000);
log.info("消息处理完成,用户id::{}", person.getId());
}
}
结果:
后台起了两个服务,看的出来一条消息只能被处理一次,并且消息是依次处理的。
使用@RabbitHander接收消息
@Slf4j
@Service
public class Producer {
@Autowired
RabbitTemplate rabbitTemplate;
public String senMessage(Integer num) {
log.info("发送消息开始:" + num);
for(int i = 0; i < num; i++) {
if(i%2==0) {
Person person = new Person();
person.setAge(19);
person.setId(i);
person.setName("person" + i);
rabbitTemplate.convertAndSend("hello-exchange", "hello-queue", person);
} else {
Student student = new Student();
student.setBanJi(2);
student.setId(i);
student.setName("xuesheng" + i);
rabbitTemplate.convertAndSend("hello-exchange", "hello-queue", student);
}
}
return "OK";
}
}
@Slf4j
// 监听消息的组件需要在容器中
@Component
@RabbitListener(queues = "hello-queue")
public class customer {
@RabbitHandler
public void getMessageHello(Message message, Person person) throws InterruptedException {
log.info("接收消息Person:{}", person);
Thread.sleep(1000);
log.info("消息处理完成,用户id::{}", person.getName());
}
@RabbitHandler
public void getMessageHello(Message message, Student student) throws InterruptedException {
log.info("接收消息Student:{}", student);
Thread.sleep(1000);
log.info("消息处理完成,用户id::{}", student.getName());
}
}