中间件

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

                                       

 

 生产者: 

 

 1 package com.learn.rabbitmq.all;
 2 
 3 import com.rabbitmq.client.Channel;
 4 import com.rabbitmq.client.Connection;
 5 import com.rabbitmq.client.ConnectionFactory;
 6 
 7 public class Producer {
 8     public static void main(String[] args) {
 9         // 1: 创建连接工厂
10         ConnectionFactory connectionFactory = new ConnectionFactory();
11         //2: 设置连接属性
12         connectionFactory.setHost("127.0.0.1");
13         connectionFactory.setPort(5672);
14         connectionFactory.setVirtualHost("/");
15         connectionFactory.setUsername("guest");
16         connectionFactory.setPassword("guest");
17         Connection connection = null;
18         Channel channel = null;
19         try {
20             // 3: 从连接工厂中获取连接
21             connection = connectionFactory.newConnection("生产者");
22             // 4: 从连接中获取通道channel
23             channel = connection.createChannel();
24             // 5: 申明队列queue存储消息
25             /*
26              *  如果队列不存在,则会创建
27              *  Rabbitmq不允许创建两个相同的队列名称,否则会报错。
28              *
29              *  @params1: queue 队列的名称
30              *  @params2: durable 队列是否持久化
31              *  @params3: exclusive 是否排他,即是否私有的,如果为true,会对当前队列加锁,其他的通道不能访问,并且连接自动关闭
32              *  @params4: autoDelete 是否自动删除,当最后一个消费者断开连接之后是否自动删除消息。
33              *  @params5: arguments 可以设置队列附加参数,设置队列的有效期,消息的最大长度,队列的消息生命周期等等。
34              * */
35 //            channel.queueDeclare("queueTest",false,false,false,null);
36             // 6: 准备发送消息的内容
37             String msg = "hello world 222!";
38             String exchangeName = "direct-exchange-code";
39             // direct/topic/fanout/headers
40             String exchangeType = "direct";
41             //声明交换机
42             channel.exchangeDeclare(exchangeName,exchangeType,true,false,false,null);
43             //声明队列
44             channel.queueDeclare("queue5",true,false,false,null);
45             channel.queueDeclare("queue6",true,false,false,null);
46             channel.queueDeclare("queue7",true,false,false,null);
47             //绑定
48             channel.queueBind("queue5",exchangeName,"order");
49             channel.queueBind("queue6",exchangeName,"order");
50             channel.queueBind("queue7",exchangeName,"course");
51             String routingKey = "email";
52             // 7: 发送消息给中间件rabbitmq-server
53             // @params1: 交换机exchange
54             // @params2: 队列名称/routing
55             // @params3: 属性配置
56             // @params4: 发送消息的内容
57             channel.basicPublish(exchangeName,"course",null,msg.getBytes());
58             System.out.println("消息发送成功!");
59         } catch (Exception e) {
60             e.printStackTrace();
61             System.out.println("发送消息出现异常!");
62         }finally {
63             if (channel != null && channel.isOpen()){
64                 try {
65                     channel.close();
66                 } catch (Exception e) {
67                     e.printStackTrace();
68                 }
69             }
70             if (connection != null){
71                 try {
72                     connection.close();
73                 }catch (Exception e){
74                     e.printStackTrace();
75                 }
76             }
77         }
78     }
79 }

 

 

 

消费者:

 1 package com.learn.rabbitmq.all;
 2 
 3 import com.rabbitmq.client.*;
 4 
 5 import java.io.IOException;
 6 import java.nio.channels.Channels;
 7 
 8 public class Customer {
 9     public static Runnable runnable = () -> {
10         // 1: 创建连接工厂
11         ConnectionFactory connectionFactory = new ConnectionFactory();
12         //2: 设置连接属性
13         connectionFactory.setHost("127.0.0.1");
14         connectionFactory.setPort(5672);
15         connectionFactory.setVirtualHost("/");
16         connectionFactory.setUsername("guest");
17         connectionFactory.setPassword("guest");
18         Connection connection = null;
19         Channel channel = null;
20 
21         final String queueName = Thread.currentThread().getName();
22         try {
23             // 3: 从连接工厂中获取连接
24             connection = connectionFactory.newConnection("消费者");
25             // 4: 从连接中获取通道channel
26             channel = connection.createChannel();
27             // 5: 创建交换机,声明队列,绑定关系,路由key,发视信息,接收信息
28             Channel finalChannel = channel;
29             finalChannel.basicConsume(queueName, true, new DeliverCallback() {
30                 public void handle(String consumerTag, Delivery delivery) throws IOException {
31                     System.out.println(queueName + "收到的信息是:" + new String(delivery.getBody(), "UTF-8"));
32                 }
33             }, new CancelCallback() {
34                 public void handle(String s) throws IOException {
35                     System.out.println("接收失败了!");
36                 }
37             });
38             System.out.println(queueName+"开始接收信息!");
39             //阻断作用,不往下继续执行
40             System.in.read();
41         } catch (Exception e) {
42             e.printStackTrace();
43             System.out.println("发送消息出现异常!");
44         }finally {
45             if (channel != null && channel.isOpen()){
46                 try {
47                     channel.close();
48                 } catch (Exception e) {
49                     e.printStackTrace();
50                 }
51             }
52             if (connection != null){
53                 try {
54                     connection.close();
55                 }catch (Exception e){
56                     e.printStackTrace();
57                 }
58             }
59         }
60     };
61 
62     public static void main(String[] args) {
63         new Thread(runnable,"queue5").start();
64         new Thread(runnable,"queue6").start();
65         new Thread(runnable,"queue7").start();
66     }
67 }

 

面试题

为什么rabbitmq是基于通道处理而不是连接呢?   

因为连接需要结果三次握手,四次挥手,费时间,关闭,连接性能消耗大。所以通过长连接---------有很多个信道 channel  处理  所以性能高

 

 

 

 可靠消费机制

手动应答,代码和NACK确定,可靠消费,ACK机制 AutoAck  为true 正常应答,服务端就会将消息队列的信息移除,整个过程正常,为false,不应答,就会不停重试,直到消费为止,如果出现异常,就会可能无限重试,服务器资源消耗殆尽。消息队列被挂起,就会屏蔽生产者的消息接收。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

消息队列mq应用场景

01、解耦、削峰、异步

01-1、同步异步的问题(串行)

串行方式:将订单信息写入数据库成功后,发送注册邮件,再发送注册短信。以上三个任务全部完成后,返回给客户端

代码

public void makeOrder(){
// 1 :保存订单
orderService.saveOrder();
// 2: 发送短信服务
messageService.sendSMS("order");//1-2 s
// 3: 发送email服务
emailService.sendEmail("order");//1-2 s
// 4: 发送APP服务
appService.sendApp("order");
}

 

01-2、并行方式 异步线程池

并行方式:将订单信息写入数据库成功后,发送注册邮件的同时,发送注册短信。以上三个任务完成后,返回给客户端。与串行的差别是,并行的方式可以提高处理的时间

代码

 1 public void makeOrder(){
 2 // 1 :保存订单
 3 orderService.saveOrder();
 4 // 相关发送
 5 relationMessage();
 6 }
 7 public void relationMessage(){
 8 // 异步
 9 theadpool.submit(new Callable<Object>{
10 public Object call(){
11 // 2: 发送短信服务
12 messageService.sendSMS("order");
13 }
14 })
15 // 异步
16 theadpool.submit(new Callable<Object>{
17 public Object call(){
18 // 3: 发送email服务
19 emailService.sendEmail("order");
20 }
21 })
22 // 异步
23 theadpool.submit(new Callable<Object>{
24 public Object call(){
25 // 4: 发送短信服务
26 appService.sendApp("order");
27 }
28 })
29 // 异步
30 theadpool.submit(new Callable<Object>{
31 public Object call(){
32 // 4: 发送短信服务
33 appService.sendApp("order");
34 }
35 })
36 }

 

存在问题:
1:耦合度高
2:需要自己写线程池自己维护成本太高
3:出现了消息可能会丢失,需要你自己做消息补偿
4:如何保证消息的可靠性你自己写
5:如果服务器承载不了,你需要自己去写高可用

01-2、异步消息队列的方式

好处
1:完全解耦,用MQ建立桥接
2:有独立的线程池和运行模型
3:出现了消息可能会丢失,MQ有持久化功能
4:如何保证消息的可靠性,死信队列和消息转移的等
5:如果服务器承载不了,你需要自己去写高可用,HA镜像模型高可用。
按照以上约定,用户的响应时间相当于是订单信息写入数据库的时间,也就是50毫秒。注册邮件,发送短信写入消息队列后,直接返回,因此写入消息队列的速度很快,基本可以忽略,因此用户的响应时间可能是50毫秒。因此架构改变后,系统的吞吐量提高到每秒20 QPS。比串行提高了3倍,比并行提高了两倍

代码

1 public void makeOrder(){
2 // 1 :保存订单
3 orderService.saveOrder();
4 rabbitTemplate.convertSend("ex","2","消息内容");
5 }

 

02、高内聚,低耦合

03、流量的削峰

04、分布式事务的可靠消费和可靠生产
05、索引、缓存、静态化处理的数据同步
06、流量监控
07、日志监控(ELK)
08、下单、订单分发、抢票

posted @ 2022-08-20 00:13  Hu_note  阅读(23)  评论(0)    收藏  举报