消息队列总结(1)- 应用避坑指南

什么是消息队列?

消息队列是一种进程间通信或同一进程的不同线程间的通信方式,软件的贮列用来处理一系列的输入,通常是来自使用者。消息队列提供了异步的通信协议,每一个贮列中的纪录包含详细说明的资料,包含发生的时间,输入装置的种类,以及特定的输入参数,也就是说:消息的发送者和接收者不需要同时与消息队列互交。消息会保存在队列中,直到接收者取回它。--引用自“维基百科”

简单来说,消息队列就是保存消息的容器,目的是实现消息生产者与消息消费者的解耦。

消息队列中存在哪些重要概念?

  • 消息:是指在系统中传递的数据单元,可以是任何格式的数据,如文本、二进制数据等。
  • 主题:是指一类相关的消息,消费者可以通过订阅主题来接收该类消息。
  • 队列:是指消息在系统中存储的地方,消息按照先进先出(FIFO)的原则进行排队。
  • 生产者:是指向消息队列中发送消息的应用程序或服务。
  • 消费者:是指从消息队列中接收消息的应用程序或服务。
  • 订阅:是指消费者向消息队列注册自己对某个主题或者某些消息的关注,以便在有新消息到达时能够及时接收。

消息队列有哪些应用场景?

注:以下划分方法并不是一种MECE的划分方式,是从不同视角/业务场景看待消息队列的应用,关注在不同应用上可能遇到的问题,因此在案例上可能会存在一些重复。

  • 异步处理:把耗时、可接受延迟的任务放入队列中,通过异步处理,提升系统并发能力及减少响应时间。
    • 典型案例:用户下单后,可以以异步并行的方式处理减库存、用户积分等操作,以实现减少用户下单的系统响应时间。
    • 遇到的典型问题:包括消息积压、消息丢失、消息重复、消费顺序、消息处理失败等。对于消息处理,任何场景都需要考虑消息积压、消息丢失、消息重复、消费顺序等问题,把这个问题交给下一部分统一总结,但是相比于同步场景可以快速感知处理结果,异步处理需要“额外注意”消息处理失败场景。针对异步处理常见的解决方案是消息重试+死信队列,并配合以相应的告警+降级处理
  • 应用解耦:不同应用之间使用消息队列交互,降低应用之间的依赖。
    • 典型案例:用户下单后,通过异步的方式进行积分,可以避免因为积分系统的故障导致用户无法下单。
    • 遇到的典型问题:包括消息积压、消息丢失、消息重复、消费顺序、消息格式变更等。针对应用解耦场景我们需要“额外关注”消息格式变更问题,消息格式就是多个应用之间的交互协议,所以保障交互协议的稳定性非常重要。我们常见的解决手段是预留足够的扩展性(例如:预留扩展字段、灵活的数据格式等),并且在变更时做好向前兼容+版本管理
  • 流量削峰:在流量过大时,把请求放入消息队列中匀速处理,避免系统宕机。
    • 典型案例:秒杀系统,通过把请求放入消息队列,避免短时间内流量太大,导致系统宕机。
    • 遇到的典型问题:包括消息积压、消息丢失、消费者系统压力等。针对流量削峰场景需要“额外关注”消费者系统压力,包括由于消费者网络带宽不足、硬件资源不足、并发处理能力不足、消费能力不足等导致的集群压力大,也包括由于负载均衡策略配置不合理导致的消费者流量倾斜,最终导致局部机器压力大。针对这类问题我们常见的解决手段包括:事前做好容量评估、提高硬件资源、优化消费者代码(避免不必要的计算、网络等操作),事中支持水平扩容,消费者处理逻辑上支持重试,事后能够通过监控等手段快速定位到异常消息,并进行重试
  • 消息通讯:典型的消息通讯包括点对点、发布订阅模式。一个生产者发出消息后,可以由一个或多个消费者进行消费。
    • 典型案例:用户下单后,商品、积分等系统监听用户下单的消息,进行各自系统操作。(发布订阅模式的典型案例)
    • 遇到的典型问题:包括消息积压、消息丢失、消费顺序、消费者组消费一致性等问题。尤其在发布订阅模式下,消费者组消费一致性问题需要“额外关注”,也即部分消费者消费成功、部分消费者消费失败,否则可能在消息处理失败补偿时产生意料之外的风险。针对这类问题我们常见的解决手段包括基于单个消费者建立重试能力、消息去重等。

消息队列如何选型?

我们在做选型时,通常要考虑消息队列的高性能(例如:吞吐量、消息延迟等指标等)、高可用(例如:消息备份、故障恢复等)、其他特性(包括:堆积能力、多语言、跨平台能力),各个消息队列选型对比如下:

特性KafkaRocketMQRabbitMQActiveMQ
单机吞吐量10万级10万级万级10万级
消息延迟ms级ms级us级ms级
开发语言ScalaJavaErlangJava
客户端支持语言Java、ScalaJava、C++、GoJava、Python、ruby、PHP、C++Java、.NET、C++
高可用分布式分布式主从分布式
消费模式拉模式拉模式推模式+拉模式推模式 
持久化文件文件内存,文件内存,文件,数据库
支持协议TCPTCP,JMS,

OpenMessaging

AMQP,XMPP, SMTP,STOMPAMQP,MQTT,OpenWire,STOMP,JMS
延迟消息不支持支持不支持支持
优先级消息不支持不支持支持支持
管理界面 无web console一般
综合评价优点:拥有强大的性能及吞吐量,兼容性很好。
缺点:由于支持消息堆积,导致延迟比较高。
优点:性能好,稳定可靠,有活跃的中文社区,特点响应快。
缺点:兼容性较差,但随着影响力的扩大,该问题会有改善。
优点:产品成熟,容易部署和使用,拥有灵活的路由配置。
缺点:性能和吞吐量较差,不易进行二次开发。
优点:产品成熟,支持协议多,支持多种语言的客户端。
缺点:社区不活跃,存在消息丢失的可能。

使用消息队列常见的坑及解决思路有哪些?

  • 消息积压
    • 问题原因:导致消息积压的可能性很多,包括单机/集群消费能力差、消息负载均衡策略不合理、消费网络耗时高、单机消息卡死等。
    • 解决思路:解决此类问题如果是集群消费能力不足可以提升并行消费能力,例如:增加消费者、引入线程池提升处理效率。如果是单机消费能力不足可以调整消息的负载均衡策略/分片策略。此外在消息处理过程中。需要额外关注消息处理时间的设计、消息的异常捕获等,避免异常情况消息卡死的问题。
  • 消息丢失
    • 问题原因:导致消息丢失的问题包括生产者异步发送、网络丢包、Broker消息积压超限、消费者消费异常等。
    • 解决思路:避免消息丢失主要是要做好消息的ACK以及重试,并且需要基于业务场景选择合适的持久化策略。
  • 重复消费
    • 问题原因:导致消息重复消费的原因包括生产者重复发送、消息投递过程中由于网络抖动等原因导致的重复、消息消费失败导致的业务侧主动重试、消息中间件由于消息未被ACK导致的主动重试。
    • 解决思路:此类问题需要在消息设计时增加消息的唯一标识,并且在消费者进行去重设计/幂等设计。
  • 消费顺序
    • 问题原因:导致消费顺序错误的原因包括多个消息并发消费(部分消息消费快,部分消息消费慢)、消息分区(部分消息队列支持消息分区,不同分区消费速度不同可能导致消费顺序错误)。
    • 解决思路:解决顺序消费的最好手段就是串行消费,但是考虑到串行消费有可能会带来消息积压等问题,因此串行消费需要配合进行消息分区,保障同一个分区内串行消费。
  • 消息限流
    • 问题原因:由于CPU、内存、磁盘、网络等资源的限制,消息限流是为了维护系统的稳定性,保持系统资源利用率的一种主动保护手段。
    • 解决思路:消息限流需包括生产者限流、消费者限流。从系统稳定性的角度考虑,在消费者需要进行限流,常见的限流算法包括令牌桶、滑动窗口等,实现方案包括借助Google的guava包实现单机限流,也可以借助redis实现集群限流等。此外若仅进行消费者限流,生产者不做控制可能会导致严重的消息积压,甚至影响生产者写入。

参考资料

秒懂消息队列MQ,万字总结带你全面了解消息队列MQ-阿里云开发者社区

消息队列详解:ActiveMQ、RocketMQ、RabbitMQ、Kafka-梅竹生辉

消息队列:基础概念篇_消息队列基本概念_Hello,Mr.S的博客-CSDN博客

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 在Linux环境下,可以使用消息队列实现客户端-服务器应用程序,完成文件传输功能。程序示例如下:1.首先,在服务器端建立消息队列,用来接收客户端发送的请求信息。2.客户端发送请求信息到服务端的消息队列,请求服务器端传输文件。3.服务端接收到客户端发送的请求信息,开始读取文件。4.服务端将读取的文件内容发送到另一条消息队列,用于接收客户端的请求。5.客户端从消息队列中接收文件内容,并将其保存到本地。 ### 回答2: 在Linux环境下,可以使用消息队列实现客户端-服务器应用程序,完成文件传输功能。下面是一个详细的程序示例: 1. 定义消息队列数据结构和消息类型。 ```c // 服务器端定义的消息队列数据结构 struct msg_buffer { long msg_type; char msg_text[1024]; }; // 定义服务器和客户端之间的消息类型 #define SERVER_MSG_TYPE 1 #define CLIENT_MSG_TYPE 2 ``` 2. 服务器端主程序。 ```c #include <stdio.h> #include <stdlib.h> #include <sys/msg.h> int main() { key_t key; int msg_id; struct msg_buffer message; // 生成唯一的消息队列键值 key = ftok("server", 65); // 创建消息队列 msg_id = msgget(key, 0666 | IPC_CREAT); // 从客户端接收消息 msgrcv(msg_id, &message, sizeof(message), CLIENT_MSG_TYPE, 0); // 打开要传输的文件 FILE *file = fopen(message.msg_text, "rb"); if (!file) { printf("无法打开文件"); return 1; } // 读取文件内容,并将内容发送给客户端 while (!feof(file)) { fread(message.msg_text, sizeof(char), sizeof(message.msg_text), file); message.msg_type = SERVER_MSG_TYPE; msgsnd(msg_id, &message, sizeof(message), 0); } // 发送结束标志 message.msg_type = SERVER_MSG_TYPE; sprintf(message.msg_text, "EOF"); msgsnd(msg_id, &message, sizeof(message), 0); // 关闭文件和消息队列 fclose(file); msgctl(msg_id, IPC_RMID, NULL); return 0; } ``` 3. 客户端主程序。 ```c #include <stdio.h> #include <stdlib.h> #include <sys/msg.h> int main() { key_t key; int msg_id; struct msg_buffer message; // 生成唯一的消息队列键值 key = ftok("server", 65); // 创建消息队列 msg_id = msgget(key, 0666 | IPC_CREAT); // 发送文件名给服务器 message.msg_type = CLIENT_MSG_TYPE; printf("请输入要传输的文件名:"); scanf("%s", message.msg_text); msgsnd(msg_id, &message, sizeof(message), 0); // 创建目标文件 FILE *file = fopen("received_file", "wb"); if (!file) { printf("无法创建文件"); return 1; } // 从服务器接收文件内容并写入目标文件 while (1) { msgrcv(msg_id, &message, sizeof(message), SERVER_MSG_TYPE, 0); if (strcmp(message.msg_text, "EOF") == 0) { break; } fwrite(message.msg_text, sizeof(char), sizeof(message.msg_text), file); } // 关闭文件和消息队列 fclose(file); msgctl(msg_id, IPC_RMID, NULL); return 0; } ``` 以上是一个使用消息队列在Linux环境下实现客户端-服务器文件传输功能的程序示例。服务器端程序接收客户端发送的文件名,然后逐块读取文件内容,并发送给客户端。客户端接收服务器发送的文件内容,并将其写入目标文件中。 ### 回答3: 在Linux环境下,可以使用消息队列来实现客户端-服务器应用程序完成文件传输功能。下面以C语言为例,详细编写程序。 步骤1:首先,我们需要创建一个消息队列,并定义消息结构体。消息结构体可以包含文件名、文件大小、操作类型等信息。 ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/ipc.h> #include <sys/msg.h> #include <sys/stat.h> #define MAX_SIZE 1024 // 定义消息结构体 struct message { long mtype; char mtext[MAX_SIZE]; }; ``` 步骤2:服务器端代码,包含消息队列的创建、发送和接收消息等功能。 ```c int main() { key_t key; int msgid; struct message msg; // 创建消息队列 key = ftok(".", 'a'); msgid = msgget(key, IPC_CREAT | 0666); if (msgid == -1) { perror("msgget"); exit(1); } // 接收客户端发送的文件名 msgrcv(msgid, &msg, sizeof(msg.mtext), 1, 0); // 根据文件名打开文件 FILE *fp = fopen(msg.mtext, "rb"); if (fp == NULL) { strcpy(msg.mtext, "文件不存在"); msg.mtype = 2; msgsnd(msgid, &msg, sizeof(msg.mtext), 0); exit(1); } // 发送文件内容 while (!feof(fp)) { size_t size = fread(msg.mtext, 1, MAX_SIZE, fp); msg.mtype = 2; msgsnd(msgid, &msg, size, 0); } // 发送结束标志 strcpy(msg.mtext, "文件传输完成"); msg.mtype = 2; msgsnd(msgid, &msg, sizeof(msg.mtext), 0); // 关闭文件和消息队列 fclose(fp); msgctl(msgid, IPC_RMID, NULL); return 0; } ``` 步骤3:客户端代码,包含消息队列的连接,并发送和接收消息等功能。 ```c int main() { key_t key; int msgid; struct message msg; // 连接消息队列 key = ftok(".", 'a'); msgid = msgget(key, 0); if (msgid == -1) { perror("msgget"); exit(1); } // 输入要传输的文件名 printf("请输入要传输的文件名:"); scanf("%s", msg.mtext); // 发送文件名 msg.mtype = 1; msgsnd(msgid, &msg, sizeof(msg.mtext), 0); // 接收服务器端的回复 msgrcv(msgid, &msg, sizeof(msg.mtext), 2, 0); if (strcmp(msg.mtext, "文件不存在") == 0) { printf("文件不存在\n"); exit(1); } // 打开文件,准备接收文件内容 FILE *fp = fopen(msg.mtext, "wb"); if (fp == NULL) { perror("fopen"); exit(1); } // 接收文件内容 while (1) { msgrcv(msgid, &msg, MAX_SIZE, 2, 0); if (strcmp(msg.mtext, "文件传输完成") == 0) break; fwrite(msg.mtext, 1, MAX_SIZE, fp); } // 关闭文件和消息队列 fclose(fp); return 0; } ``` 以上就是使用消息队列在Linux环境下实现客户端-服务器文件传输功能的详细代码。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值