初识MQ
同步调用存在的问题
异步调用常见实现就是事件驱动模式
事件驱动模式优势:
优势1:服务解耦
一旦有新业务只需要订阅或者减少事件就行了
优势2:性能提升,吞吐量提高
优势3:服务没有强依赖,不用担心级联失败问题
优势4:流量削峰
异步通信的缺点:
1.依赖于Broker的可靠性,安全性,吞吐能力
2.架构复杂了,业务没有明显的流程线,不好追踪管理
什么是MQ
MQ(MessageQueue),中文是消息队列,字面来看就是存放消息的队列。也就是事件驱动架构中的Broker。
我们选择使用RabbitMQ
RabbitMQ快速入门
RabbitMO是基于Erlang语言开发的开源消息通信中间件,官网地址: https://www.rabbitmq.com/
我们将在centos7虚拟机中用docker来部署
1.下载镜像
在课前资料已经提供了镜像包,上传到虚拟机中后,使用命令加载镜像即可:
docker load -i mq.tar
1.2.安装MQ
执行下面的命令来运行MQ容器:
docker run \
-e RABBITMQ_DEFAULT_USER=itcast \
-e RABBITMQ_DEFAULT_PASS=123321 \
-v mq-plugins:/plugins \
--name mq \
--hostname mq \
-p 15672:15672 \
-p 5672:5672 \
-d \
rabbitmq:3-management
运行完成我们输入地址,就能检查了:
输入我们刚才设置的账号密码
常见消息模型
消费者:
public class ConsumerTest { public static void main(String[] args) throws IOException, TimeoutException { // 1.建立连接 ConnectionFactory factory = new ConnectionFactory(); // 1.1.设置连接参数,分别是:主机名、端口号、vhost、用户名、密码 factory.setHost("192.168.150.101"); factory.setPort(5672); factory.setVirtualHost("/"); factory.setUsername("itcast"); factory.setPassword("123321"); // 1.2.建立连接 Connection connection = factory.newConnection(); // 2.创建通道Channel Channel channel = connection.createChannel(); // 3.创建队列 String queueName = "simple.queue"; channel.queueDeclare(queueName, false, false, false, null); // 4.订阅消息 channel.basicConsume(queueName, true, new DefaultConsumer(channel){ @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { // 5.处理消息 String message = new String(body); System.out.println("接收到消息:【" + message + "】"); } }); System.out.println("等待接收消息。。。。"); } }
生产者:
public class PublisherTest { @Test public void testSendMessage() throws IOException, TimeoutException { // 1.建立连接 ConnectionFactory factory = new ConnectionFactory(); // 1.1.设置连接参数,分别是:主机名、端口号、vhost、用户名、密码 factory.setHost("192.168.150.101"); factory.setPort(5672); factory.setVirtualHost("/"); factory.setUsername("itcast"); factory.setPassword("123321"); // 1.2.建立连接 Connection connection = factory.newConnection(); // 2.创建通道Channel Channel channel = connection.createChannel(); // 3.创建队列 String queueName = "simple.queue"; channel.queueDeclare(queueName, false, false, false, null); // 4.发送消息 String message = "hello, rabbitmq!"; channel.basicPublish("", queueName, null, message.getBytes()); System.out.println("发送消息成功:【" + message + "】"); // 5.关闭通道和连接 channel.close(); connection.close(); } }
SpringAMQP
什么是SpringAMQP
spring-amqp依赖
<!--AMQP依赖,包含RabbitMQ--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency>
这里要注意,rabbit不会主动创建队列,我们要自己先创建一个队列。这样才能接收到消息
接收消息:
@Component public class SpringRabbitListener { @RabbitListener(queues = "simple.queue") public void listenSimpleQueue(String msg){ System.out.println("消费者接收到simple.queue的消息是:"+msg); } }w
我们需要运行消费者的入口程序,然后接收消息
SpringAmqp work queue工作队列
@Test public void testSendMsg2WorkQueue(){ String queueName="simple.queue"; String message ="hello,msg--"; for (int i = 1; i <= 50; i++) { rabbitTemplate.convertAndSend(queueName,message+i); } }】
修改原来的
public class SpringRabbitListener
两个消费者:
@RabbitListener(queues = "simple.queue") public void listenWorkQueue1(String msg) throws InterruptedException { System.out.println("消费者1接收到simple.queue的消息是:"+msg+ LocalTime.now()); Thread.sleep(20); } @RabbitListener(queues = "simple.queue") public void listenWorkQueue2(String msg) throws InterruptedException { System.err.println("消费者2.......接收到simple.queue的消息是:"+msg+ LocalTime.now()); Thread.sleep(200); }
虽然两个消费者速度不一样,但是由于消息预取机制,他们分配的量是一样的
发布(publish),订阅(subscribe)
发布订阅:Fanout Exchange
配置: @Configuration public class FanoutConfig { //itcast.fanout @Bean public FanoutExchange fanoutExchange() { return new FanoutExchange("itcast.fanout"); } //fanout.queue1 @Bean public Queue fanoutQueue1() { return new Queue("fanout.queue1"); } //绑定队列1到交换机 @Bean public Binding fanoutBinding1(Queue fanoutQueue1, FanoutExchange fanoutExchange) { return BindingBuilder.bind(fanoutQueue1).to(fanoutExchange); } //fanout.queue2 @Bean public Queue fanoutQueue2() { return new Queue("fanout.queue2"); } @Bean public Binding fanoutBinding2(Queue fanoutQueue2, FanoutExchange fanoutExchange) { return BindingBuilder.bind(fanoutQueue2).to(fanoutExchange); } }
消费者:
生产者:
@Test public void testSendFanoutExchange(){ //交换机名称 String exchangeName ="itcast.fanout"; //消息 String message = "hello,every one!"; //发送消息 rabbitTemplate.convertAndSend(exchangeName,"",message); } }
发布订阅-DirectExchange
exchange的 type默认是direct
生产者测试代码:
@Test public void testSendDirectExchange(){ //交换机名称 String exchangeName ="itcast.direct"; //消息 String message = "hello,blue!"; //发送消息 rabbitTemplate.convertAndSend(exchangeName,"blue",message); }
结果:消费者接收到direct.queue1的消息是:hello,blue!
发布订阅-TopicExchange
1.
@RabbitListener(bindings = @QueueBinding( value = @Queue("topic.queue1"), exchange = @Exchange(value = "itcast.topic",type = ExchangeTypes.TOPIC), key = "china.#" )) public void listenTopicQueue1(String msg){ System.out.println("消费者接收到topic.queue1的消息是:"+msg); } @RabbitListener(bindings = @QueueBinding( value = @Queue("topic.queue2"), exchange = @Exchange(value = "itcast.topic",type = ExchangeTypes.TOPIC), key = "#.news" )) public void listenTopicQueue2(String msg){ System.out.println("消费者接收到topic.queue2的消息是:"+msg); }
生产者测试方法:
@Test public void testSendTopicExchange(){ //交换机名称 String exchangeName ="itcast.topic"; //消息 String message = "今天天气真不错"; //发送消息 rabbitTemplate.convertAndSend(exchangeName,"china.news",message); }
结果是两个都能收到
SpringAMQP -消息转换器
我们的申明直接在PublisherApplication里面做就可以