MQ消息队列中间件介绍及IoT领域应用
为什么使用消息队列?消息队列有什么优点和缺点?Kafka、ActiveMQ、RabbitMQ、RocketMQ 都有什么区别,以及适合哪些场景?为什么使用消息队列其实就是问问你消息队列都有哪些使用场景,然后你项目里具体是什么场景,说说你在这个场景里用消息队列是什么?面试官问你这个问题,期望的一个回答是说,你们公司有个什么业务场景,这个业务场景有个什么技术挑...
-
什么是消息队列?
-
发布/订阅消息收发
-
为什么使用消息队列?
-
消息队列有什么优点和缺点?
-
消息队列中间件
-
Kafka、ActiveMQ、RabbitMQ、RocketMQ 都有什么区别,以及适合哪些场景?
-
IoT领域中的消息队列应用
什么是消息队列
什么是消息队列?
消息队列是一种异步的服务间通信方式,使用于无服务和微服务架构。消息在被处理和删除之前一直存储在队列上。每条消息仅可被一直为用户处理一次。消息队列可被用于分离重量级处理、缓存或批处理工作以及缓解高峰期工作负载。
在现代云架构中,应用程序被分解为多个规模较小且更易于开发、部署和维护的独立构建模块。消息队列可为这些分布式应用程序提供通信和协调。消息队列可以显著简化分离应用程序的编码,同时提高性能、可靠性和可扩展性。
借助消息队列,系统的不同部分可相互通信并异步执行处理操作。消息队列提供一个临时存储消息的轻量级缓存区,以及允许软件组件连接到队列以发送接受消息的终端节点。这些消息通常较小,可以是请求、恢复、错误消息或明文消息等。要发送消息时,一个名为“创建器”的组件将消息添加到队列。消息将存储在队列中,直至名为“处理器”的另一组件检索该消息并执行相关操作。
许多创建器和处理器都可以使用队列,但一条信息只能有一盒处理器处理一次。因此,这种消息收发模式通常称为一对一或点对点通信。如果消息需要由多个处理器进行处理,可采用扇出设计模式将消息队列与发布/订阅消息收发结合起来。
发布/订阅消息收发
发布/订阅消息收发是一种异步的服务间通信方式,适用于无服务和微服务架构。在发布/订阅模式下,发布到主题的任何的任何消息都会立即被主题的所有订阅者接受。发布/订阅消息收发可用于启用事件驱动架构,或分离应用程序,以提高性能、可靠性和可扩展性。
发布/订阅消息收发基础知识
在现代云架构中,应用程序被分解为多个规模较小且易于开发、部署和维护的独立构建块。发布/订阅消息收发可以为这些分布式应用程序提供及时事件通知。
发布/订阅模式让消息能够异步广播到系统中的不同部分。消息主题与消息队列类似,可以提供一个轻量型机制来广播异步事件通知,还可以提供能让软件组件连接主题以便发送和接受消息的终端节点。在广播消息时,一个叫做“发布者”的组件会将消息推送到主题。与在消息被检索前批量处理消息的消息队列不同的是,消息主题无需或使用极少消息队列即可传输消息,并将消息立即推送给所有订阅者。订阅该主题的所有组件都会收到广播的每一条消息,除非订阅着设置了消息筛选策略。
消息主题的订阅着通常执行不同的功能,并可以同时对消息执行不同的操作。发布者无需知道谁在使用广播的消息而订阅着也无需知道消息来自哪里。这种消息收发模式与消息队列稍有不同,在消息队列中,发送消息的组件通常知道发送目的地。
为什么使用消息队列
其实就是问问你消息队列都有哪些使用场景,然后你项目里具体是什么场景,说说你在这个场景里用消息队列是什么?
面试官问你这个问题,期望的一个回答是说,你们公司有个什么业务场景,这个业务场景有个什么技术挑战,如果不用 MQ 可能会很麻烦,但是你现在用了 MQ 之后带给了你很多的好处。
先说一下消息队列常见的使用场景吧,其实场景有很多,但是比较核心的有 3 个:解耦、异步、削峰。
解耦
看这么个场景。A 系统发送数据到 BCD 三个系统,通过接口调用发送。如果 E 系统也要这个数据呢?那如果 C 系统现在不需要了呢?A 系统负责人几乎崩溃......
在这个场景中,A 系统跟其它各种乱七八糟的系统严重耦合,A 系统产生一条比较关键的数据,很多系统都需要 A 系统将这个数据发送过来。A 系统要时时刻刻考虑 BCDE 四个系统如果挂了该咋办?要不要重发,要不要把消息存起来?头发都白了啊!
如果使用 MQ,A 系统产生一条数据,发送到 MQ 里面去,哪个系统需要数据自己去 MQ 里面消费。如果新系统需要数据,直接从 MQ 里消费即可;如果某个系统不需要这条数据了,就取消对 MQ 消息的消费即可。这样下来,A 系统压根儿不需要去考虑要给谁发送数据,不需要维护这个代码,也不需要考虑人家是否调用成功、失败超时等情况。
总结:通过一个 MQ,Pub/Sub 发布订阅消息这么一个模型,A 系统就跟其它系统彻底解耦了。
面试技巧:你需要去考虑一下你负责的系统中是否有类似的场景,就是一个系统或者一个模块,调用了多个系统或者模块,互相之间的调用很复杂,维护起来很麻烦。但是其实这个调用是不需要直接同步调用接口的,如果用 MQ 给它异步化解耦,也是可以的,你就需要去考虑在你的项目里,是不是可以运用这个 MQ 去进行系统的解耦。在简历中体现出来这块东西,用 MQ 作解耦。
异步
再来看一个场景,A 系统接收一个请求,需要在自己本地写库,还需要在 BCD 三个系统写库,自己本地写库要 3ms,BCD 三个系统分别写库要 300ms、450ms、200ms。最终请求总延时是 3 + 300 + 450 + 200 = 953ms,接近 1s,用户感觉搞个什么东西,慢死了慢死了。用户通过浏览器发起请求,等待个 1s,这几乎是不可接受的。
一般互联网类的企业,对于用户直接的操作,一般要求是每个请求都必须在 200 ms 以内完成,对用户几乎是无感知的。
如果使用 MQ,那么 A 系统连续发送 3 条消息到 MQ 队列中,假如耗时 5ms,A 系统从接受一个请求到返回响应给用户,总时长是 3 + 5 = 8ms,对于用户而言,其实感觉上就是点个按钮,8ms 以后就直接返回了,爽!网站做得真好,真快!
削峰
每天 0:00 到 12:00,A 系统风平浪静,每秒并发请求数量就 50 个。结果每次一到 12:00 ~ 13:00 ,每秒并发请求数量突然会暴增到 5k+ 条。但是系统是直接基于 MySQL 的,大量的请求涌入 MySQL,每秒钟对 MySQL 执行约 5k 条 SQL。
一般的 MySQL,扛到每秒 2k 个请求就差不多了,如果每秒请求到 5k 的话,可能就直接把 MySQL 给打死了,导致系统崩溃,用户也就没法再使用系统了。
但是高峰期一过,到了下午的时候,就成了低峰期,可能也就 1w 的用户同时在网站上操作,每秒中的请求数量可能也就 50 个请求,对整个系统几乎没有任何的压力。
如果使用 MQ,每秒 5k 个请求写入 MQ,A 系统每秒钟最多处理 2k 个请求,因为 MySQL 每秒钟最多处理 2k 个。A 系统从 MQ 中慢慢拉取请求,每秒钟就拉取 2k 个请求,不要超过自己每秒能处理的最大请求数量就 ok,这样下来,哪怕是高峰期的时候,A 系统也绝对不会挂掉。而 MQ 每秒钟 5k 个请求进来,就 2k 个请求出去,结果就导致在中午高峰期(1 个小时),可能有几十万甚至几百万的请求积压在 MQ 中。
这个短暂的高峰期积压是 ok 的,因为高峰期过了之后,每秒钟就 50 个请求进 MQ,但是 A 系统依然会按照每秒 2k 个请求的速度在处理。所以说,只要高峰期一过,A 系统就会快速将积压的消息给解决掉。
消息队列有什么优缺点
优点上面已经说了,就是在特殊场景下有其对应的好处,解耦、异步、削峰。
缺点有以下几个:
-
系统可用性降低
系统引入的外部依赖越多,越容易挂掉。本来你就是 A 系统调用 BCD 三个系统的接口就好了,人 ABCD 四个系统好好的,没啥问题,你偏加个 MQ 进来,万一 MQ 挂了咋整,MQ 一挂,整套系统崩溃的,你不就完了?如何保证消息队列的高可用? -
系统复杂度提高
硬生生加个 MQ 进来,你怎么保证消息没有重复消费?怎么处理消息丢失的情况?怎么保证消息传递的顺序性?头大头大,问题一大堆,痛苦不已。 -
一致性问题
A 系统处理完了直接返回成功了,人都以为你这个请求就成功了;但是问题是,要是 BCD 三个系统那里,BD 两个系统写库成功了,结果 C 系统写库失败了,咋整?你这数据就不一致了。
所以消息队列实际是一种非常复杂的架构,你引入它有很多好处,但是也得针对它带来的坏处做各种额外的技术方案和架构来规避掉,做好之后,你会发现,妈呀,系统复杂度提升了一个数量级,也许是复杂了 10 倍。但是关键时刻,用,还是得用的。
消息队列中间件
- RabbitMQ
- Kafka
- RocketMQ
- Qpid
- Artemis
- NSQ
- ZeroMQ
ActiveMQ
ActiveMQ是由Apache出品,ActiveMQ是一个完全支持JMS1.1和JMS1.4规范的JMS Provider实现。它非常快速,只是多种语言的客户端和协议,而且可以非常容易的嵌入到企业的应用环境中,并有许多高级功能。
主要特性
- 服从JMS规范:JMS规范提供了良好的标准和保证,包括:同步或异步的消息分发,一次或仅一次的消息分发,消息接收和订阅等等。遵从JMS规范的好处在于,不论使用什么JMS实现提供者,这些基础特性都是可用的;
- 连接灵活性:ActiveMQ提供了广泛的连接协议,支持的协议有:HTTP/S,IP多播,SSL,TCP,UDP等等。对众多协议的支持让ActiveMQ拥有了很好的灵活性
- 支持的协议种类多:OpenWrite、STOMP、REST、XMMP、AMQP;
- 持久化插件和安全插件:ActiveMQ提供了多种持久化选择。而且,ActiveMQ的安全性也可以完全一句用户需求进行自定义鉴权和授权;
- 支持的客户端语言种类多:除了Java之外,还有C/C++,.NET,Perl,PHP,Python,Ruby;
- 代理集群:多个ActiveMQ代理可以组成一个集群来提供服务;
- 异常简单的管理:ActiveMQ是以开发者思维被设计的。所以,它并不需要专门的管理员,因为它提供了简单有实用的管理特性。又很多方法可以监控ActiveMQ不同层面的数据,包括实用在JConsole或者在ActiveMQ的Web console中使用JMX。通过处理JMX的告警消息,通过使用命令行脚本,甚至可以通过监控各种类型的日志。
部署环境
ActiveMQ 可以运行在 Java 语言所支持的平台之上。使用 ActiveMQ 需要:
- Java JDK
- ActiveMQ 安装包
优点
- 跨平台 (JAVA 编写与平台无关,ActiveMQ 几乎可以运行在任何的 JVM 上);
- 可以用 JDBC:可以将 数据持久化 到数据库。虽然使用 JDBC 会降低 ActiveMQ 的性能,但是数据库一直都是开发人员最熟悉的存储介质;
- 支持 JMS 规范:支持 JMS 规范提供的 统一接口;
- 支持 自动重连 和 错误重试机制;
- 有安全机制:支持基于 shiro,jaas 等多种 安全配置机制,可以对 Queue/Topic 进行 认证和授权;
- 监控完善:拥有完善的 监控,包括 Web Console,JMX,Shell 命令行,Jolokia 的 RESTful API;
- 界面友善:提供的 Web Console 可以满足大部分情况,还有很多 第三方的组件 可以使用,比如 hawtio;
缺点
- 社区活跃度不及 RabbitMQ 高;
- 根据其他用户反馈,会出莫名其妙的问题,会 丢失消息;
- 目前重心放到 activemq 6.0 产品 Apollo,对 5.x 的维护较少;
- 不适合用于 上千个队列 的应用场景;
RabbitMQ
RabbitMQ 于 2007 年发布,是一个在 AMQP (高级消息队列协议)基础上完成的,可复用的企业消息系统,是当前最主流的消息中间件之一。
主要特性
- 可靠性:提供了多种技术可以让你在 性能 和 可靠性 之间进行 权衡。这些技术包括 持久性机制、投递确认、发布者证实 和 高可用性机制;
- 灵活的路由:消息在到达队列前是通过 交换机 进行 路由 的。RabbitMQ 为典型的路由逻辑提供了 多种内置交换机 类型。如果你有更复杂的路由需求,可以将这些交换机组合起来使用,你甚至可以实现自己的交换机类型,并且当做 RabbitMQ 的 插件 来使用;
- 消息集群:在相同局域网中的多个 RabbitMQ 服务器可以 聚合 在一起,作为一个独立的逻辑代理来使用;
- 队列高可用:队列可以在集群中的机器上 进行镜像,以确保在硬件问题下还保证 消息安全;
- 支持多种协议:支持 多种消息队列协议;
- 支持多种语言:用 Erlang 语言编写,支持只要是你能想到的 所有编程语言;
- 管理界面: RabbitMQ 有一个易用的 用户界面,使得用户可以 监控 和 管理 消息 Broker 的许多方面;
- 跟踪机制:如果 消息异常,RabbitMQ 提供消息跟踪机制,使用者可以找出发生了什么;
- 插件机制:提供了许多 插件,来从多方面进行扩展,也可以编写自己的插件。
部署环境
RabbitMQ 可以运行在 Erlang 语言所支持的平台之上,包括 Solaris,BSD,Linux,MacOSX,TRU64,Windows 等。使用 RabbitMQ 需要:
- ErLang 语言包
- RabbitMQ 安装包
优点
- 由于 Erlang 语言的特性,消息队列性能较好,支持 高并发;
- 健壮、稳定、易用、跨平台、支持 多种语言、文档齐全;
- 有消息 确认机制 和 持久化机制,可靠性高;
- 高度可定制的 路由;
- 管理界面 较丰富,在互联网公司也有较大规模的应用,社区活跃度高。
缺点
- 尽管结合 Erlang 语言本身的并发优势,性能较好,但是不利于做 二次开发和维护;
- 实现了 代理架构,意味着消息在发送到客户端之前可以在 中央节点 上排队。此特性使得 RabbitMQ 易于使用和部署,但是使得其 运行速度较慢,因为中央节点 增加了延迟,消息封装后 也比较大;
- 需要学习比较复杂的接口协议,学习和维护成本较高。
RocketMQ
RocketMQ出自阿里的开源产品,用Java语言实现,在设计时参考了Kafka,并做出了自己的一些改进,消息可靠性上比Kafka更好。RocketMQ在阿里内部被广泛应用在订单,交易,充值,流计算,消息推送,日志流式处理,binglog分发等场景。
主要特性
- 基于队列模型:具有高性能、高可靠、高实时、分布式等特点;
- Producer、Consumer、队列都支持分布式;
- Producer向一些队列轮流发送消息,队列集合称为Topic。Consumer如果做广播消费,则一个Consumer实例消费这个Topic对应的所有队列;如果做集群消费,则多个Consumer实例平均消费这个Topic对应的队列集合;
- 能够保证严格的消息顺序;
- 提供丰富的消息拉取模式;
- 高效的订阅着水平扩展能力;
- 实时的消息订阅机制;
- 亿级消息堆积能力;
- 较少的外部依赖;
部署环境
RocketMQ可以运行在Java语言所支持的平台之上。使用RocketMQ需要:
- Java JDK
- 安装git、Maven
- RocketMQ安装包
优点
- 单机支持一万以上持久化队列;
- RocketMQ的所有消息都是持久化的,先写入系统PageCache,然后刷盘,可以保证内存与磁盘都有一份数据,而访问时,直接从内存读取。
- 模型简单,接口易用(JMS的接口很多场合并不实用);
- 性能非常好,可以允许大量堆积消息在Broker中;
- 支持多种消费模式,包括集群消费、广播消费等;
- 各个环节分布式扩展设计,支持主从和高可用;
- 开发度比较活跃,版本更新更快;
缺点
- 支持的客户端语言不多,目前是Java及C++,其中C++还不成熟;
- RocketMQ社区关注度及成熟度也不及前两者;
- 没有Web管理界面,提供了一个CLI管理工具代理查询、管理和诊断各种问题;
- 没有在MQ核心里实现JMS等接口
Kafka
Apache Kafka 是一个 分布式消息发布订阅 系统。它最初由 LinkedIn 公司基于独特的设计实现为一个 分布式的日志提交系统 (a distributed commit log),之后成为 Apache 项目的一部分。Kafka 性能高效、可扩展良好 并且 可持久化。它的 分区特性,可复制 和 可容错 都是其不错的特性。
主要特性
- 快速持久化:可以在 O(1) 的系统开销下进行 消息持久化;
- 高吞吐:在一台普通的服务器上既可以达到 10W/s 的 吞吐速率;
- 完全的分布式系统:Broker、Producer 和 Consumer 都原生自动支持 分布式,自动实现 负载均衡;
- 支持 同步 和 异步 复制两种 高可用机制;
- 支持 数据批量发送 和 拉取;
- 零拷贝技术(zero-copy):减少 IO 操作步骤,提高 系统吞吐量;
- 数据迁移、扩容 对用户透明;
- 无需停机 即可扩展机器;
- 其他特性:丰富的 消息拉取模型、高效 订阅者水平扩展、实时的 消息订阅、亿级的 消息堆积能力、定期删除机制;
部署环境
使用 Kafka 需要:
- Java JDK
- Kafka 安装包
优点
- 客户端语言丰富:支持 Java、.Net、PHP、Ruby、Python、Go 等多种语言;
- 高性能:单机写入 TPS 约在 100 万条/秒,消息大小 10 个字节;
- 提供 完全分布式架构,并有 replica 机制,拥有较高的 可用性 和 可靠性,理论上支持 消息无限堆积;
- 支持批量操作;
- 消费者 采用 Pull 方式获取消息。消息有序,通过控制 能够保证所有消息被消费且仅被消费 一次;
- 有优秀的第三方 Kafka Web 管理界面 Kafka-Manager;
- 在 日志领域 比较成熟,被多家公司和多个开源项目使用。
缺点
- Kafka 单机超过 64 个 队列/分区 时,Load 时会发生明显的飙高现象。队列 越多,负载 越高,发送消息 响应时间变长;
- 使用 短轮询方式,实时性 取决于 轮询间隔时间;
- 消费失败 不支持重试;
- 支持 消息顺序,但是 一台代理宕机 后,就会产生 消息乱序;
- 社区更新较慢。
Kafka、ActiveMQ、RabbitMQ、RocketMQ 有什么优缺点?
特性 | ActiveMQ | RabbitMQ | RocketMQ | Kafka |
---|---|---|---|---|
单机吞吐量 | 万级,比 RocketMQ、Kafka 低一个数量级 | 同 ActiveMQ | 10 万级,支撑高吞吐 | 10 万级,高吞吐,一般配合大数据类的系统来进行实时数据计算、日志采集等场景 |
topic 数量对吞吐量的影响 | topic 可以达到几百/几千的级别,吞吐量会有较小幅度的下降,这是 RocketMQ 的一大优势,在同等机器下,可以支撑大量的 topic | topic 从几十到几百个时候,吞吐量会大幅度下降,在同等机器下,Kafka 尽量保证 topic 数量不要过多,如果要支撑大规模的 topic,需要增加更多的机器资源 | ||
时效性 | ms 级 | 微秒级,这是 RabbitMQ 的一大特点,延迟最低 | ms 级 | 延迟在 ms 级以内 |
可用性 | 高,基于主从架构实现高可用 | 同 ActiveMQ | 非常高,分布式架构 | 非常高,分布式,一个数据多个副本,少数机器宕机,不会丢失数据,不会导致不可用 |
消息可靠性 | 有较低的概率丢失数据 | 基本不丢 | 经过参数优化配置,可以做到 0 丢失 | 同 RocketMQ |
功能支持 | MQ 领域的功能极其完备 | 基于 erlang 开发,并发能力很强,性能极好,延时很低 | MQ 功能较为完善,还是分布式的,扩展性好 | 功能较为简单,主要支持简单的 MQ 功能,在大数据领域的实时计算以及日志采集被大规模使用 |
综上,各种对比之后,有如下建议:
一般的业务系统要引入 MQ,最早大家都用 ActiveMQ,但是现在确实大家用的不多了,没经过大规模吞吐量场景的验证,社区也不是很活跃,所以大家还是算了吧,我个人不推荐用这个了;
后来大家开始用 RabbitMQ,但是确实 erlang 语言阻止了大量的 Java 工程师去深入研究和掌控它,对公司而言,几乎处于不可控的状态,但是确实人家是开源的,比较稳定的支持,活跃度也高;
不过现在确实越来越多的公司会去用 RocketMQ,确实很不错,毕竟是阿里出品,但社区可能有突然黄掉的风险(目前 RocketMQ 已捐给 Apache,但 GitHub 上的活跃度其实不算高)对自己公司技术实力有绝对自信的,推荐用 RocketMQ,否则回去老老实实用 RabbitMQ 吧,人家有活跃的开源社区,绝对不会黄。
所以中小型公司,技术实力较为一般,技术挑战不是特别高,用 RabbitMQ 是不错的选择;大型公司,基础架构研发实力较强,用 RocketMQ 是很好的选择。
如果是大数据领域的实时计算、日志采集等场景,用 Kafka 是业内标准的,绝对没问题,社区活跃度很高,绝对不会黄,何况几乎是全世界这个领域的事实性规范。
IoT领域中的消息队列应用
为什么以及何时在您的物联网项目中使用消息队列?
考虑温室中的温度传感器,它测量温度。您的应用程序可以在15分钟的时间间隔内(每天月96次)将温度发送到消息队列。而不是处理温室中的数据并始终连接。
物联网的消息队列旨在提供轻量级的发布/订阅消息传输。您只需发送数据并在另一项服务中处理数据,而不是保留处理温室数据的应用程序。这需要较少的网络带宽,这可能会限制您的传感器,或者您的传感器通过卫星链路进行通信。
消息队列使您的应用程序具有低功耗,发送最小化的数据包,并有效地将信息分发给一个或多个接收器。
物联网项目中的消息队列
消息队列是一种服务到服务通信的方式。它允许应用程序通过相互发送消息进行通信。消息队列的基本体系结构很简单,有些客户端应用程序可以创建消息并将它们传递到消息队列。其他应用程序/服务从队列中检索消息并处理消息中包含的请求和信息
消息可以包含任何类型的信息。例如,它可以获得有关应该从另一个应用程序(可能位于其他位置)开始的进程/任务的消息,或者它可能只是需要处理的数据。
物联网和异步消息
因特网 应用程序被动反应和异步是一个“必须”。大多数IoT应用程序应该能够处理来自设备的许多连接以及从中过去的所有消息
异步消息传递在机器到机器通信中被广泛使用。这以为这发送方不会期望立即相应,并且发送方在等待相应是时不会“阻止”任何内容。
异步通信可以实现灵活性,应用程序可以发送消息,然后继续处理其他事情-在同步通信中,它必须等待实时响应。您可以将消息写入队列,然后让相同的业务员逻辑发生,而不是调用Web服务并等待它完成。
在您的应用程序需要完成某些操作但不需要立即完成,或者甚至不关心结果的情况下,队列可能很棒。
解耦
消息队列可用于实现解耦,并有助于保持结构的灵活性。它使得用不同语言编写的两个不同的应用程序连接在一起非常容易。
消息队列允许每个组件独立地执行其任务-它允许组件保持完全自治并且彼此不知道,一项服务的更改不应要求更改其他服务。它是分离服务的过程因此它们的功能更加独立。
冗余和弹性
应用程序有时会崩溃 - 它会发生。这可能是由于超时或您的代码中只有错误影响整个应用程序。消息队列强制可以使接收应用程序确认它已完成任务,并且可以安全地从队列中删除该任务。如果接收应用程序中的任何内容失败,该消息将保留在队列中。
当目标程序繁忙或未连接时,消息队列提供临时消息存储。
通过打破您的应用程序并按队列分隔不同的组件,您固有地创建了更多的弹性。即使部分后端处理延迟,您的应用程序仍然可以运行。
交通高峰
许多应用程序的流量激增。门铃应用程序,让您可以从任何地方回答您的门,万圣节期间可能会有交通高峰,而购物应用程序可能会在黑色星期五有交通高峰。通过对数据进行排队,您可以确保最终保留和处理您的数据; 即使这意味着由于高流量峰值,它需要比平常更长的时间。
------------------------------------------------------------------------------------------------------------------------------
如果您觉得这篇博客对您有帮助,希望您能略微打赏打赏。如果您有业务合作意向,请私信联系。
更多推荐
所有评论(0)