什么是MQ,SpringBoot

本文深入介绍了RabbitMQ中的四种消息模型:发布/订阅、路由、主题和远程过程调用(RPC)。通过实例代码展示了如何在Java中使用这些模型,包括生产者和消费者的实现。发布/订阅模型类似微信公众号推送,路由允许过滤特定消息,主题提供了更灵活的消息匹配,而RPC则演示了如何构建一个简单的RPC服务。
摘要由CSDN通过智能技术生成

45 }
46 }
47 }
48 }
49 }


## 4.3 Publish/Subscribe(发布/订阅)

官方描述:

  RabbitMQ消息传递模型中的核心思想是生产者从不将任何消息直接发送到队列。实际上,生产者经常甚至根本不知道是否将消息传递到任何队列。相反,生产者只能将消息发送到交换机。交流是一件非常简单的事情。一方面,它接收来自生产者的消息,另一方面,将它们推入队列。交易所必须确切知道如何处理收到的消息。是否应将其附加到特定队列?是否应该将其附加到许多队列中?还是应该丢弃它。规则由交换类型定义 。

简而言之:

  相当于我们关注了一个微信公众号,公众号每次推文我们都能及时的收到。我们就相当于消费者,公众号相当于消息中转站,文章作者相当于生产者。

代码示例:

生产者:

1 public class EmitLog {
2
3 private static final String ExCHANGE_NAME = “logs”;
4
5 public static void main(String[] args) throws Exception{
6
7 ConnectionFactory factory = new ConnectionFactory();
8
9 // 设置IP
10 factory.setHost(“127.0.0.1”);
11
12 // 设置端口号
13 factory.setPort(5672);
14
15 try (Connection connection = factory.newConnection();
16 Channel channel = connection.createChannel()){
17 channel.exchangeDeclare(ExCHANGE_NAME, “fanout”);
18
19 String message = args.length < 1 ? “info: Hello World!” : String.join(" “, args);
20
21 channel.basicPublish(ExCHANGE_NAME, “”, null, message.getBytes(StandardCharsets.UTF_8));
22
23 System.out.println(” [x] Sent ‘" + message + "’");
24
25 }
26
27 }
28
29 }


消费者:

1 public class ReceiveLogs {
2
3 private static final String ExCHANGE_NAME = “logs”;
4
5 public static void main(String[] args) throws Exception{
6 ConnectionFactory factory = new ConnectionFactory();
7
8 // 设置IP
9 factory.setHost(“127.0.0.1”);
10
11 // 设置端口号
12 factory.setPort(5672);
13
14 Connection connection = factory.newConnection();
15 Channel channel = connection.createChannel();
16
17 channel.exchangeDeclare(ExCHANGE_NAME, “fanout”);
18 String queueName = channel.queueDeclare().getQueue();
19 channel.queueBind(queueName, ExCHANGE_NAME, “”);
20
21 System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
22
23 DeliverCallback deliverCallback = (sonsumerTag, delivery) -> {
24 String message = new String(delivery.getBody(), StandardCharsets.UTF_8);
25 System.out.println(" [x] Received ‘" + message + "’");
26 };
27 channel.basicConsume(queueName, true, deliverCallback, consumerTag -> {});
28 }
29 }


## 4.4 Routing(路由)

官方描述:

  接上例,我们可能希望将日志消息写入磁盘的程序仅接收严重错误,而不会在警告或信息日志消息上浪费磁盘空间。

简而言之:

  如果我们只想接收某些信息,比如日志级别有INFO、ERROR、DEBUG等,我们只愿接收INFO日志。可以使用Routing进行过滤。

代码示例:

生产者:

1 public class EmitLogDirect {
2
3 private static final String EXCHANGE_NAME = “direct_logs”;
4
5 public static void main(String[] args) throws Exception{
6
7 ConnectionFactory factory = new ConnectionFactory();
8
9 // 设置IP
10 factory.setHost(“127.0.0.1”);
11
12 // 设置端口号
13 factory.setPort(5672);
14
15 try (Connection connection = factory.newConnection();
16 Channel channel = connection.createChannel()){
17 channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
18
19 String severity = getServerity(args);
20 String message = getMessage(args);
21
22 channel.basicPublish(EXCHANGE_NAME, severity, null, message.getBytes(StandardCharsets.UTF_8));
23 System.out.println(" [x] Sent ‘" + severity + "’:’" + message + “’”);
24
25 }
26
27 }
28
29 private static String getServerity(String[] strings){
30 if (strings.length < 1){
31 return “info”;
32 }
33 return strings[0];
34
35 }
36
37 private static String getMessage(String[] strings){
38 if (strings.length < 2) {
39 return “Hello World!”;
40 }
41 return joinStrings(strings, " ", 1);
42 }
43
44 private static String joinStrings(String[] strings, String delimiter, int startIndex){
45 int length = strings.length;
46 if(length == 0){
47 return “”;
48 }
49 if(length <= startIndex){
50 return “”;
51 }
52 StringBuilder words = new StringBuilder(strings[startIndex]);
53 for (int i = startIndex + 1; i < length; i++){
54 words.append(delimiter).append(strings[i]);
55 }
56 return words.toString();
57
58 }
59 }


消费者:

1 public class ReceiveLogsDirect {
2
3 private static final String EXCHANGE_NAME = “direct_logs”;
4
5 public static void main(String[] args) throws Exception{
6
7 ConnectionFactory factory = new ConnectionFactory();
8
9 // 设置IP
10 factory.setHost(“127.0.0.1”);
11
12 // 设置端口号
13 factory.setPort(5672);
14
15 Connection connection = factory.newConnection();
16
17 Channel channel = connection.createChannel();
18
19 channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
20
21 String queueName = channel.queueDeclare().getQueue();
22
23 if(args.length < 1){
24 System.err.println(“Usage: ReceiveLogsDirect [info] [warning] [error]”);
25 System.exit(1);
26 }
27
28 for (String severity : args){
29 channel.queueBind(queueName, EXCHANGE_NAME, severity);
30 }
31 System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
32
33 DeliverCallback deliverCallback = (consumerTag, delivery)->{
34 String message = new String(delivery.getBody(), StandardCharsets.UTF_8);
35 System.out.println(" [x] Received ‘" + delivery.getEnvelope().getRoutingKey() + "’:’" + message + “’”);
36 };
37
38 channel.basicConsume(queueName, true, deliverCallback, comsumerTag ->{});
39 }
40 }


## 4.5 Topics(主题)

官方描述:

  发送到主题交换机的消息不能具有任意的 routing_key-它必须是单词列表,以点分隔。这些词可以是任何东西,但通常它们指定与消息相关的某些功能。一些有效的路由关键示例:“ stock.usd.nyse ”,“ nyse.vmw ”,“ quick.orange.rabbit ”。路由关键字中可以包含任意多个单词,最多255个字节。

  绑定密钥也必须采用相同的形式。主题交换背后的逻辑 类似于直接交换-用特定路由键发送的消息将传递到所有用匹配绑定键绑定的队列。但是,绑定键有两个重要的特殊情况:

*   *(星)只能代替一个单词。#(散列)可以代替零个或多个单词。

简而言之:

Topic会根据消息自身所携带的路由键(Routing Key)在所有的绑定关系中寻找,与消息相匹配的队列推送该消息。

注意:

  当在绑定中不使用特殊字符“ * ”(星号)和“ # ”(哈希)时,主题交换的行为就像直接的一样。

代码示例:

生产者:

1 public class EmitLogTopic {
2
3 private static final String EXCHANGE_NAME = “topic_logs”;
4
5 public static void main(String[] args) throws Exception{
6
7 ConnectionFactory factory = new ConnectionFactory();
8 // 设置IP
9 factory.setHost(“127.0.0.1”);
10
11 // 设置端口号
12 factory.setPort(5672);
13
14 try(Connection connection = factory.newConnection();
15 Channel channel = connection.createChannel()){
16
17 channel.exchangeDeclare(EXCHANGE_NAME, “topic”);
18
19 String routingKey = getRouting(args);
20 String message = getMessage(args);
21
22 channel.basicPublish(EXCHANGE_NAME, routingKey, null, message.getBytes(StandardCharsets.UTF_8));
23 System.out.println(" [x] Sent ‘" + routingKey + "’:’" + message + “’”);
24 }
25 }
26
27 private static String getRouting(String[] strings){
28 if (strings.length < 1){
29 return “anonymous.info”;
30 }
31 return strings[0];
32 }
33
34 private static String getMessage(String[] strings){
35 if (strings.length < 2){
36 return “hello world”;
37 }
38 return joinStrings(strings, " ", 1);
39 }
40
41 private static String joinStrings(String[] strings, String delimiter, int startIndex){
42 int length = strings.length;
43 if(length == 0){
44 return “”;
45 }
46 if(length < startIndex){
47 return “”;
48 }
49 StringBuilder words = new StringBuilder(strings[startIndex]);
50 for (int i = startIndex + 1; i < length; i++){
51 words.append(delimiter).append(strings[i]);
52 }
53 return words.toString();
54 }
55 }


消费者:

1 public class ReceiveLogTopic {
2
3 private static final String EXCHANGE_NAME = “topic_logs”;
4
5 public static void main(String[] args) throws Exception{
6
7 ConnectionFactory factory = new ConnectionFactory();
8 // 设置IP
9 factory.setHost(“127.0.0.1”);
10
11 // 设置端口号
12 factory.setPort(5672);
13
14 Connection connection = factory.newConnection();
15 Channel channel = connection.createChannel();
16
17 channel.exchangeDeclare(EXCHANGE_NAME, “topic”);
18
19 String queueName = channel.queueDeclare().getQueue();
20
21 if(args.length < 1){
22 System.err.println(“Usage: ReceiveLogsTopic [binding_key]…”);
23 System.exit(1);
24 }
25
26 for (String bindingKey : args){
27 channel.queueBind(queueName, EXCHANGE_NAME, bindingKey);
28 }
29
30 System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
31
32 DeliverCallback deliverCallback = (consumerTag, delivery) -> {
33 String message = new String(delivery.getBody(), StandardCharsets.UTF_8);
34 System.out.println(" [x] Received ‘" + delivery.getEnvelope().getRoutingKey() + "’:’" + message + “’”);
35 };
36 channel.basicConsume(queueName, true, deliverCallback, consumerTag -> {});
37 }
38 }


## 4.6 RPC(远程过程调用)

官方描述:

  尽管RPC是计算中非常普遍的模式,但它经常受到批评。当程序员不知道函数调用是本地的还是缓慢的RPC时,就会出现问题。这样的混乱会导致系统变幻莫测,并给调试增加了不必要的复杂性。滥用RPC可能会导致无法维护的意大利面条代码,而不是简化软件。

代码示例:

生产者

1 public class RPCServer {
2
3 private static final String RPC_QUEUE_NAME = “rpc_queue”;
4
5 private static int fib(int n){
6 if(n == 0){
7 return 0;
8 }
9 if(n == 1){
10 return 1;
11 }
12 return fib(n - 1) + fib(n - 2);
13 }
14
15 public static void main(String[] args) throws Exception{
16
17 // 创建服务器的连接
18 ConnectionFactory factory = new ConnectionFactory();
19
20 // 设置IP
21 factory.setHost(“127.0.0.1”);
22
23 // 设置端口号
24 factory.setPort(5672);
25
26 try (Connection connection = factory.newConnection();
27 Channel channel = connection.createChannel()) {
28 channel.queueDeclare(RPC_QUEUE_NAME, false, false, false, null);
29 channel.queuePurge(RPC_QUEUE_NAME);
30
31 channel.basicQos(1);
32
33 System.out.println(" [x] Awaiting RPC requests");
34
35 Object monitor = new Object();
36 DeliverCallback deliverCallback = (consumerTag, delivery) ->{
37 AMQP.BasicProperties replyProps = new AMQP.BasicProperties
38 .Builder()
39 .correlationId(delivery.getProperties().getCorrelationId())
40 .build();
41
42 String response = “”;
43
44 try{
45 String message = new String(delivery.getBody(), StandardCharsets.UTF_8);
46 int n = Integer.parseInt(message);
47
48 System.out.println(" [.] fib(" + message + “)”);
49 response += fib(n);
50 }catch (RuntimeException e){
51 System.out.println(" [.] " + e.toString());
52 }finally {
53 channel.basicPublish("", delivery.getProperties().getReplyTo(), replyProps, response.getBytes(StandardCharsets.UTF_8));
54 channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
55
56 // RabbitMq consumer worker thread notifies the RPC server owner thread
57 // RabbitMq使用者工作线程通知RPC服务器所有者线程
58 synchronized (monitor){
59 monitor.notify();
60 }
61 }
62 };
63 channel.basicConsume(RPC_QUEUE_NAME, false, deliverCallback, (consumerTag -> {}));
64 // Wait and be prepared to consume the message from RPC client.
65 // 等待并准备使用来自RPC客户端的消息。
66 while(true){
67 synchronized (monitor){
68 try {
69 monitor.wait();
70 }catch (InterruptedException e){
71 e.printStackTrace();
72 }
73 }
74 }
75 }
76 }
77 }


消费者:

1 public class RPCClient {
2
3 private Connection connection;
4 private Channel channel;
5 private String requestQueueName = “rpc_queue”;
6
7 public RPCClient() throws IOException, TimeoutException {
8 // 创建服务器的连接
9 ConnectionFactory factory = new ConnectionFactory();
10
11 // 设置IP
12 factory.setHost(“127.0.0.1”);
13
14 // 设置端口号
15 factory.setPort(5672);
16
17 connection = factory.newConnection();
18 channel = connection.createChannel();
19 }
20
21 public static void main(String[] args) throws Exception{
22 RPCClient fibonacciRpc = new RPCClient();
23 for (int i = 0; i < 32; i++) {
24 String i_str = Integer.toString(i);
25 System.out.println(" [x] Requesting fib(" + i_str + “)”);
26 String response = fibonacciRpc.call(i_str);
27 System.out.println(" [.] Got ‘" + response + "’");
28 }
29
30 }
31
32 public String call(String message) throws IOException, InterruptedException {
33 final String corrId = UUID.randomUUID().toString();
34
35 String replyQueueName = channel.queueDeclare().getQueue();
36 AMQP.BasicProperties props = new AMQP.BasicProperties
37 .Builder()
38 .correlationId(corrId)
39 .replyTo(replyQueueName)
40 .build();
41
42 channel.basicPublish("", requestQueueName, props, message.getBytes(“UTF-8”));
43
44 final BlockingQueue response = new ArrayBlockingQueue<>(1);
45
46 String ctag = channel.basicConsume(replyQueueName, true, (consumerTag, delivery) -> {
47 if (delivery.getProperties().getCorrelationId().equals(corrId)) {
48 response.offer(new String(delivery.getBody(), “UTF-8”));
49 }
50 }, consumerTag -> {
51 });
52
53 String result = response.take();
54 channel.basicCancel(ctag);
55 return result;
56 }
57
58 public void close() throws IOException {
59 connection.close();
60 }
61 }


## 

4.7 Publisher Confirms(发布者确认)

官方描述:

  在某些应用程序中,确保将发布的消息发送到代理非常重要。发布者确认是RabbitMQ功能,可以帮助满足此要求。发布者确认本质上是异步的,但也可以同步处理它们。没有确定的方法可以实现发布者确认,这通常归结为应用程序和整个系统中的约束。典型的技术有:

*   单独发布消息,同步等待确认:简单,但吞吐量非常有限。

*   批量发布消息,同步等待批量确认:简单,合理的吞吐量,但是很难推断出什么时候出了问题。

*   异步处理:最佳性能和资源使用,在出现错误的情况下可以很好地控制,但是可以正确实施。

代码示例:

1 public class PublisherConfirms {
2
3 static final int MESSAGE_COUNT = 50_000;
4
5 static Connection createConnection() throws Exception{
6
7 ConnectionFactory cf = new ConnectionFactory();
8
9 // 设置IP
10 cf.setHost(“127.0.0.1”);
11
12 // 设置端口号
13 cf.setPort(5672);
14
15 // 设置用户名
16 cf.setUsername(“guest”);
17
18 // 设置密码
19 cf.setPassword(“guest”);
20
21 return cf.newConnection();
22 }

总目录展示

该笔记共八个节点(由浅入深),分为三大模块。

高性能。 秒杀涉及大量的并发读和并发写,因此支持高并发访问这点非常关键。该笔记将从设计数据的动静分离方案、热点的发现与隔离、请求的削峰与分层过滤、服务端的极致优化这4个方面重点介绍。

一致性。 秒杀中商品减库存的实现方式同样关键。可想而知,有限数量的商品在同一时刻被很多倍的请求同时来减库存,减库存又分为“拍下减库存”“付款减库存”以及预扣等几种,在大并发更新的过程中都要保证数据的准确性,其难度可想而知。因此,将用一个节点来专门讲解如何设计秒杀减库存方案。

高可用。 虽然介绍了很多极致的优化思路,但现实中总难免出现一些我们考虑不到的情况,所以要保证系统的高可用和正确性,还要设计一个PlanB来兜底,以便在最坏情况发生时仍然能够从容应对。笔记的最后,将带你思考可以从哪些环节来设计兜底方案。


篇幅有限,无法一个模块一个模块详细的展示(这些要点都收集在了这份《高并发秒杀顶级教程》里),觉得有需要的码友们,麻烦各位转发一下(可以帮助更多的人看到哟!)点这里,即可获得免费下载的方式!!

由于内容太多,这里只截取部分的内容。需要这份《高并发秒杀顶级教程》的小伙伴,麻烦各位帮忙点赞分享支持一下(可以帮助更多的人看到哟!)
秒杀中商品减库存的实现方式同样关键。可想而知,有限数量的商品在同一时刻被很多倍的请求同时来减库存,减库存又分为“拍下减库存”“付款减库存”以及预扣等几种,在大并发更新的过程中都要保证数据的准确性,其难度可想而知。因此,将用一个节点来专门讲解如何设计秒杀减库存方案。

高可用。 虽然介绍了很多极致的优化思路,但现实中总难免出现一些我们考虑不到的情况,所以要保证系统的高可用和正确性,还要设计一个PlanB来兜底,以便在最坏情况发生时仍然能够从容应对。笔记的最后,将带你思考可以从哪些环节来设计兜底方案。


篇幅有限,无法一个模块一个模块详细的展示(这些要点都收集在了这份《高并发秒杀顶级教程》里),觉得有需要的码友们,麻烦各位转发一下(可以帮助更多的人看到哟!)点这里,即可获得免费下载的方式!!

[外链图片转存中…(img-wbSGVRJi-1628433382003)]

[外链图片转存中…(img-xBvJYy7s-1628433382008)]

由于内容太多,这里只截取部分的内容。需要这份《高并发秒杀顶级教程》的小伙伴,麻烦各位帮忙点赞分享支持一下(可以帮助更多的人看到哟!)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值