为什么要使用消息队列及Kafak、RocketMQ、ZeroMq的对比

你为啥用消息队列?噗此,这也叫问题?别人用了我能不用么?别人用了我就用了呗,我就是为了用而用。

参考资料

敖丙—消息队列

是什么——初步介绍

消息队列不知道大家看到这个词的时候,会不会觉得它是一个比较高端的技术,反正我是觉得它好像是挺牛逼的。

消息队列,一般我们会简称它为MQ(Message Queue),嗯,就是很直白的简写。

我们先不管消息(Message)这个词,来看看队列(Queue)。这一看,队列大家应该都就悉吧。

队列是一种先进先出的数据结构

在这里插入图片描述

为什么要使用中间件呢——MQ的优势点

那为什么还需要消息队列(MO)这种中间件呢? ? ? 其实这个问题,跟之前我学Redis的时候很像Redis是一个以 key-value 形式存储的内存数据库,明明我们可以使用类似HashMap这种实现类就可以达到类似的效果了,那还为什么要Redis?《Redis合集》
到这里,大家可以先猜猜为什么要用消息队列(MQ)这种中间件,下面会继续补充。

消息队列可以简单理解为:把要传输的数据放在队列中

"明明JDK已经有不少的队列实现了,我们还需要消息队列中间件呢?"其实很简单,JDK实现的队列种类虽然有很多种,但是都是简单的内存队列。为什么我说JDK是简单的内存队列呢?下面我们来看看要实现消息队列(中间件)可能要考虑什么问题。

一、首先是三个应用场景:

  • 异步
  • 解耦
  • 削峰

二、其次 消息队列还能解决:

  • 高可用(集群分布,消息队列肯定不能是单机的。试着想一下,如果是单机的消息队列,万一这台机器挂了,那我们整个系统几乎就是不可用了)
  • 数据丢失(redis能做持久化到硬盘)等问题

三、解决消息获取问题——消费者怎么得到消息队列的数据?

科普:
把数据放到消息队列叫做生产者
从消息队列里边取数据叫做消费者

消费者怎么从消息队列里边得到数据? 有两种办法:
主动叫消费者去拿(俗称push),生产者将数据放到消息队列中,消息队列有数据了,。消费者不断去轮训消息队列,看看有没有新的数据,如果有就消费(俗称pull)

四、其他
除了这些,我们在使用的时候还得考虑各种的问题:
消息重复消费了怎么办啊?
我想保证消息是绝对有顺序的怎么做?
虽然消息队列给我们带来了那么多的好处,但同时我们发现引入消息队列也会提高系统的复杂性.市面上现在已经有不少消息队列轮子了,每种消息队列都有自己的特点,选取哪种MQ还得好好斟酌。

应用场景

你为啥用消息队列?
噗此,这也叫问题?别人用了我能不用么?别人用了我就用了呗,我就是为了用而用。

异步

我们之前的场景里面有很多步骤都是在一个流程里面需要做完的,就比如说我的下单系统吧,本来我们业务简单,下单了付了钱就好了,流程就走完了。但是后面来了个产品经理,搞了个优惠券系统,OK问题不大,流程里面多100ms去扣减优惠券。后来产品经理灵光一闪说我们可以搞个积分系统啊,也行吧,流程里面多了200ms去增减积分。再后来后来隔壁的产品老王说:下单成功后我们要给用户发短信,也将就吧,100ms去发个短信。再后来。。。(敖丙你有完没完!!!)

在这里插入图片描述

你们可以看到这才加了三个,我可以斩钉截铁的告诉你真正的下单流程涉及的系统绝对在10个以上(主流电商),越大的越多。这个链路这样下去,时间长得一批,用户发现我买个东西你特么要花几十秒,垃圾电商我不在你这里买了,不过要是都像并夕夕这么便宜,真香!但是我们公司没有夕夕的那个经济实力啊,那只能优化系统了

那链路长了就慢了,但是我们发现上面的流程其实可以同时做的呀,你支付成功后,我去校验优惠券的同时我可以去增减积分啊,还可以同时发个短信啊。那正常的流程我们是没办法实现的呀,怎么办,异步。你对比一下是不是发现,这样子最多只用100毫秒用户知道下单成功了,至于短信你迟几秒发给他他根本不在意是吧。

在这里插入图片描述

解耦

既然面试官这么问了,我就说一下为啥我们不能用线程去做,因为用线程去做,你是不是要写代码?你一个订单流程,你扣积分,扣优惠券,发短信,扣库存。。。等等这么多业务要调用这么多的接口,每次加一个你要调用一个接口然后还要重新发布系统,写一次两次还好,写多了你就说:老子不干了!而且真的全部都写在一起的话,不单单是耦合这一个问题,你出问题排查也麻烦,流程里面随便一个地方出问题搞不好会影响到其他的点,小伙伴说我每个流程都try catch不就行了,相信我别这么做,这样的代码就像个定时炸弹 ,你不知道什么时候爆炸,平时不炸偏偏在你做活动的时候炸,你就领个P0故障收拾书包提前回家过年吧。

Tip:P0—PN 是互联网大厂经常用来判定事故等级的机制,P0是最高等级了。

例子1

但是你用了消息队列,耦合这个问题就迎刃而解了呀。哦,帅丙怎么说? 且听我娓娓道来:你下单了,你就把你支付成功的消息告诉别的系统,他们收到了去处理就好了,你只用走完自己的流程,把自己的消息发出去,那后面要接入什么系统简单,直接订阅你发送的支付成功消息,你支付成功了我监听就好了。

在这里插入图片描述
那你的流程走完了,你不用管别人是否成功么?比如你下单了积分没加,优惠券没扣怎么办?
问题是个好问题,但是没必要考虑,业务系统本身就是自己的开发人员维护的,你积分扣失败关我下单的什么事情?你管好自己下单系统的就好了。

Tip:话是这么说,但是这其实是用了消息队列的一个缺点,涉及到分布式事务的知识点,我下面会提到。

例子2

把下面的userid换成订单号,就好理解了!

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

削峰

就拿我上一期写的秒杀来说(暗示新同学看我上一期),你平时流量很低,但是你要做秒杀活动00 :00的时候流量疯狂怼进来,你的服务器,Redis,MySQL各自的承受能力都不一样,你直接全部流量照单全收肯定有问题啊,直接就打挂了。那怎么办? 简单,把请求放到队列里面,然后至于每秒消费多少请求,就看自己的服务器处理能力,你能处理5000QPS你就消费这么多,可能会比正常的慢一点,但是不至于打挂服务器,等流量高峰下去了,你的服务也就没压力了。你看阿里双十一12:00的时候这么多流量瞬间涌进去,他有时候是不是会慢一点,但是人家没挂啊,或者降级给你个友好的提示页面,等高峰过去了又是一条好汉了。

MQ的问题

系统复杂性

系统复杂性本来蛮简单的一个系统,我代码随便写都没事,现在你凭空接入一个中间件在那,我是不是要考虑去维护他,而且使用的过程中是不是要考虑各种问题,比如消息重复消费、消息丢失、消息的顺序消费等等,反正用了之后就是贼烦。我插一句嘴,上面的问题(重复消费、消息丢失、顺序消费)你能分别介绍一下,并且说一下分别是怎么解决的么? 不要!我都说了敖丙下一章写啥?其实不是暖男我不想在这里写,这三个问题我想了下,统统都是MQ的重点问题,单独拿一个出来就是一篇文章了,篇幅实在太长了,我会在下一章挨个介绍一遍的。

重复消费

消息重复消费是使用消息队列之后,必须考虑的一个问题,也是比较严重和常见的问题,帅丙我在开发过程中,但凡用到了消息队列,我第一时间考虑的就是重复消费的问题。

就比如有这样的一个场景,用户下单成功后我需要去一个活动页面给他加GMV(销售总额),最后根据他的GMV去给他发奖励,这是电商活动很常见的玩法。

类似累计下单金额到哪个梯度给你返回什么梯度的奖励这样。
在这里插入图片描述
我只能告诉你这样的活动页面10000%是用异步去加的(别问我为什么,因为这个活动的后端是敖丙我做的😂),不然你想,你一个用户下一单就给他加一下,那就意味着对那张表就要操作一下,你考虑下双十一当天多少次对这个表的操作?这数据库或者缓存都顶不住吧。

而且大家应该也有这样的体会,你下单了马上去看一些活动页面,有时候马上就有了,有时候缺延迟有很久,为啥?这个速度取决于消息队列的消费速度,消费慢堵塞了就迟点看到呗。

你下个单支付成功你就发个消息出去,我们上面那个活动的开发人员就监听你的支付成功消息,我监听到你这个订单成功支付的消息,那我就去我活动GMV表里给你加上去,听到这里大家可能觉得顺理成章。

在这里插入图片描述
但是我告诉大家一般消息队列的使用,我们都是有重试机制的,就是说我下游的业务发生异常了,我会抛出异常并且要求你重新发一次。

我这个活动这里发生错误,你要求重发肯定没问题。但是大家仔细想一下问题在哪里?

是的,不止你一个人监听这个消息啊,还有别的服务也在监听,他们也会失败啊,他一失败他也要求重发,但是你这里其实是成功的,重发了,你的钱不就加了两次了?

对不对???是不是这个道理???

还不理解?看下面 ↓
在这里插入图片描述
就好比上面的这样,我们的积分系统处理失败了,他这个系统肯定要求你重新发送一次这个消息对吧,积分的系统重新接收并且处理成功了,但是别人的活动,优惠券等等服务也监听了这个消息呀,那不就可能出现活动系统给他加GMV加两次,优惠券扣两次这种情况么?

真实的情况其实重试是很正常的,服务的网络抖动,开发人员代码Bug,还有数据问题等都可能处理失败要求重发的。

一般我们叫这样的处理叫接口幂等。

幂等(idempotent、idempotence)是一个数学与计算机学概念,常见于抽象代数中。

在编程中一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。

幂等函数,或幂等方法,是指可以使用相同参数重复执行,并能获得相同结果的函数。这些函数不会影响系统状态,也不用担心重复执行会对系统造成改变。

例如,“setTrue()”函数就是一个幂等函数,无论多次执行,其结果都是一样的.更复杂的操作幂等保证是利用唯一交易号(流水号)实现.

通俗了讲就是你同样的参数调用我这个接口,调用多少次结果都是一个,你加GMV同一个订单号你加一次是多少钱,你加N次都还是多少钱。

但是如果不做幂等,你一个订单调用多次钱不就加多次嘛,同理你退款调用多次钱也就减多次了。

大致处理流程如下:
在这里插入图片描述
怎么保证呢?
一般帅丙我是这么回答的:

帅气面试官您好,一般幂等,我会分场景去考虑,看是强校验还是弱校验,比如跟金钱相关的场景那就很关键呀,就做强校验,别不是很重要的场景做弱校验。

强校验:
比如你监听到用户支付成功的消息,你监听到了去加GMV是不是要调用加钱的接口,那加钱接口下面再调用一个加流水的接口,两个放在一个事务,成功一起成功失败一起失败。

每次消息过来都要拿着订单号+业务场景这样的唯一标识(比是天猫双十一活动)去流水表查,看看有没有这条流水,有就直接return不要走下面的流程了,没有就执行后面的逻辑。

之所以用流水表,是因为涉及到金钱这样的活动,有啥问题后面也可以去流水表对账,还有就是帮助开发人员定位问题。

弱校验:
这个简单,一些不重要的场景,比如给谁发短信啥的,我就把这个id+场景唯一标识作为Redis的key,放到缓存里面失效时间看你场景,一定时间内的这个消息就去Redis判断。

用KV就算消息丢了可能这样的场景也没关系,反正丢条无关痛痒的通知短信嘛(你敢说你没验证码短信丢失的情况?)。

还有很多公司的弱校验用token啊什么的,反正花样很多,但是重要的场景一定要强校验,真正查问题的时候没有在磁盘持久化的数据,心里还是空空的,就像你和女朋友分开的时候的心里状态一样。(我单身的怎么知道这种感觉?猜的)

顺序消费

Tip:但是说实话顺序消费这里很难介绍,我上周到这周问了很多身边的师兄开发过程中这样的场景不多,我跟三歪也讨论了几次,网上更多的都是介绍binlog的同步,好像更多的场景就没了。

一般都是同个业务场景下不同几个操作的消息同时过去,本身顺序是对的,但是你发出去的时候同时发出去了,消费的时候却乱掉了,这样就有问题了。

我之前做电商活动也是有这样的例子,我们都知道数据量大的时候数据同步压力还是很大的,有时候数据量大的表需要同步几个亿的数据。(并不是主从同步,主从延迟大会有问题,可能是从数据库或者主数据库同步到备库)

这种情况我们都是怼到队列里面去,然后慢慢消费的,那问题就来了呀,我们在数据库同时对一个Id的数据进行了增、改、删三个操作,但是你消息发过去消费的时候变成了改,删、增,这样数据就不对了。

本来一条数据应该删掉了,结果在你那却还在,这不是出大问题!
在这里插入图片描述
那你怎么解决呢?
我简单的说一下我们使用的RocketMQ里面的一个简单实现吧。

Tip:为啥用RocketMQ举例呢,这玩意是阿里开源的,我问了下身边的朋友很多公司都有使用,所以读者大概率是这个的话我就用这个举例吧,具体的细节我后面会在RocketMQ和Kafka各自章节说到。

生产者消费者一般需要保证顺序消息的话,可能就是一个业务场景下的,比如订单的创建、支付、发货、收货。

那这些东西是不是一个订单号呢?一个订单的肯定是一个订单号的说,那简单了呀。

一个topic下有多个队列,为了保证发送有序,RocketMQ提供了MessageQueueSelector队列选择机制,他有三种实现:

  • SelectMessageQueueByHash
  • SelectMessageQueueByMachineRoom
  • SelectMessageQueueByRandom

我们可使用Hash取模法,让同一个订单发送到同一个队列中,再使用同步发送,只有同个订单的创建消息发送成功,再发送支付消息。这样,我们保证了发送有序。

RocketMQ的topic内的队列机制,可以保证存储满足FIFO(First Input First Output 简单说就是指先进先出),剩下的只需要消费者顺序消费即可。

RocketMQ仅保证顺序发送,顺序消费由消费者业务保证!!!

这里很好理解,一个订单你发送的时候放到一个队列里面去,你同意的订单号Hash一下是不是还是一样的结果,那肯定是一个消费者消费,那顺序是不是就保证了?

真正的顺序消费不同的中间件都有自己的不同实现我这里就举个例子,大家思路理解下。

Tip:我写到这点的时候人才群里也有人问我,一个队列有序出去,一个消费者消费不就好了,我想说的是消费者是多线程的,你消息是有序的给他的,你能保证他是有序的处理的?还是一个消费成功了再发下一个稳妥。

数据一致性——分布式事务

这个其实是分布式服务本身就存在的一个问题,不仅仅是消息队列的问题,但是放在这里说是因为用了消息队列这个问题会暴露得比较严重一点。就像我开头说的,你下单的服务自己保证自己的逻辑成功处理了,你成功发了消息,**但是优惠券系统,积分系统等等这么多系统,他们成功还是失败你就不管了?**我说了保证自己的业务数据对的就好了,其实还是比较不负责任的一种说法,这样就像个渣男,没有格局,这样呀你的路会越走越窄的。所有的服务都成功才能算这一次下单是成功的,**那怎么才能保证数据一致性呢?分布式事务:把下单,优惠券,积分。。。都放在一个事务里面一样,要成功一起成功,要失败一起失败。**Tip:分布式事务在互联网公司里面实在常见,我也不在这里大篇幅介绍了,后面都会专门说的。

分布式事务在现在遍地都是分布式部署的系统中几乎是必要的。

大家可以想一下,你下单流程可能涉及到10多个环节,你下单付钱都成功了,但是你优惠券扣减失败了,积分新增失败了,前者公司会被薅羊毛,后者用户会不开心,但是这些都在不同的服务怎么保证大家都成功呢?

聪明,分布式事务,你看你都会抢答了!

Tip:真实的应用场景可能比我介绍的场景复杂数倍,我只是为了举例方便一下大家理解所以用了很简单的例子。

分类

我接触和了解到的分布式事务大概分为:

  • 2pc(两段式提交)
  • 3pc(三段式提交)
  • TCC(Try、Confirm、Cancel)
  • 最大努力通知
  • XA
  • 本地消息表(ebay研发出的)
  • 半消息/最终一致性(RocketMQ)

这里我就介绍下最简单的2pc(两段式),以及大家以后可能比较常用的半消息事务也就是最终一致性,目的是让大家理解下分布式事务里面消息中间件的作用,别的事务都大同小异,都有很多优点。

当然也都有种种弊端:

例如长时间锁定数据库资源,导致系统的响应不快,并发上不去。

网络抖动出现脑裂情况,导致事物参与者,不能很好地执行协调者的指令,导致数据不一致。

单点故障:例如事物协调者,在某一时刻宕机,虽然可以通过选举机制产生新的Leader,但是这过程中,必然出现问题,而TCC,只有强悍的技术团队,才能支持开发,成本太高。

不多BB了,我们开始介绍这个两个事物吧。

2pc(两段式提交) :

在这里插入图片描述
2pc(两段式提交)可以说是分布式事务的最开始的样子了,像极了媒婆,就是通过消息中间件协调多个系统,在两个系统操作事务的时候都锁定资源但是不提交事务,等两者都准备好了,告诉消息中间件,然后再分别提交事务。

但是我不知道大家看到问题所在没有?

是的你可能已经发现了,如果A系统事务提交成功了,但是B系统在提交的时候网络波动或者各种原因提交失败了,其实还是会失败的。

最终一致性:

在这里插入图片描述
整个流程中,我们能保证是:

  • 业务主动方本地事务提交失败,业务被动方不会收到消息的投递。
  • 只要业务主动方本地事务执行成功,那么消息服务一定会投递消息给下游的业务被动方,并最终保证业务被动方一定能成功消费该消息(消费成功或失败,即最终一定会有一个最终态)。

不过呢技术就是这样,各种极端的情况我们都需要考虑,也很难有完美的方案,所以才会有这么多的方案三段式、TCC、最大努力通知等等分布式事务方案,大家只需要知道为啥要做,做了有啥好处,有啥坏处,在实际开发的时候都注意下就好好了,系统都是根据业务场景设计出来的,离开业务的技术没有意义,离开技术的业务没有底气。

还是那句话:没有最完美的系统,只有最适合的系统。

可用性

你搞个系统本身没啥问题,**你现在突然接入一个中间件在那放着,万一挂了怎么办?**我下个单MQ挂了,优惠券不扣了,积分不减了,这不是杀一个程序员能搞定的吧,感觉得杀一片。至于怎么保证高可用,还是那句话也不在这里展开讨论了,我后面一样会写,像写Redis那样写出来的。

大吞吐量

比如现在突然爆发了一个超级热点新闻,你的APP注册用户高达亿数,你要想办法第一时间把突发全部推送到每个人手上,你没有大吞吐量的消息队列中间件用啥去推?

再说这些用户大量涌进来看了你的新闻产生了一系列的附带流量,你怎么应对这些数据,很多场景离开消息队列基本上难以为继。

其他需要需要注意的问题

1、屏蔽异构平台的细节:发送方、接收方系统之间不需要了解双方,只需认识消息。
2、异步:消息堆积能力;发送方接收方不需同时在线,发送方接收方不需同时扩容(削峰)。
3、解耦:防止引入过多的API给系统的稳定性带来风险;调用方使用不当会给被调用方系统造成压力,被调用方处理不当会降低调用方系统的响应能力。
4、复用:一次发送多次消费。
5、可靠:一次保证消息的传递。如果发送消息时接收者不可用,消息队列会保留消息,直到成功地传递它。
6、提供路由:发送者无需与接收者建立连接,双方通过消息队列保证消息能够从发送者路由到接收者,甚至对于本来网络不易互通的两个服务,也可以提供消息路由。

消息队列对比

在这里插入图片描述

  • RocketMQ 和 Kafka 是目前最热门的两种消息中间件,互联网公司应用最为广泛
  • 从 MQ 的发展历程来看,Kafka 先于 RocketMQ 诞生,并且阿里团队在实现 RocketMQ 时,充分借鉴了 Kafka 的设计思想。掌握了 Kafka 的设计原理,后面再去理解 RocketMQ 会容易很多。
  • Kafka 其实是一个轻量级的 MQ,它具备 MQ 最基础的能力,但是在延迟队列、重试机制等高级特性上并未做支持,因此降低了实现复杂度。从 Kafka 入手,有利于大家快速掌握 MQ 最核心的东西。

目前在市面上比较主流的消息队列中间件主要有,Kafka、ActiveMQ、RabbitMQ、RocketMQ 等这几种。不过敖丙我想说的是,ActiveMQ和RabbitMQ这两着因为吞吐量还有GitHub的社区活跃度的原因,在各大互联网公司都已经基本上绝迹了,业务体量一般的公司会是有在用的,但是越来越多的公司更青睐RocketMQ这样的消息中间件了。Kafka和RocketMQ一直在各自擅长的领域发光发亮,不过写这篇文章的时候我问了蚂蚁金服,字节跳动和美团的朋友,好像大家用的都有点不一样,应该都是各自的中间件,可能做过修改,也可能是自研的,大多没有开源。就像我们公司就是是基于Kafka和RocketMQ两者的优点自研的消息队列中间件,吞吐量、可靠性、时效性等都很可观。我们回归正题,我这里用网上找的对比图让大家看看差距到底在哪里:

在这里插入图片描述
作者:敖丙
链接:https://www.zhihu.com/question/54152397/answer/923992679
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

大家其实一下子就能看到差距了,就拿吞吐量来说,早期比较活跃的ActiveMQ 和RabbitMQ基本上不是后两者的对手了,在现在这样大数据的年代吞吐量是真的很重要。比如现在突然爆发了一个超级热点新闻,你的APP注册用户高达亿数,你要想办法第一时间把突发全部推送到每个人手上,你没有大吞吐量的消息队列中间件用啥去推?再说这些用户大量涌进来看了你的新闻产生了一系列的附带流量,你怎么应对这些数据,很多场景离开消息队列基本上难以为继。就部署方式而言前两者也是大不如后面两个天然分布式架构的哥哥,都是高可用的分布式架构,而且数据多个副本的数据也能做到0丢失。我们再聊一下RabbitMQ这个中间件其实还行,但是这玩意开发语言居然是erlang,我敢说绝大部分工程师肯定不会为了一个中间件去刻意学习一门语言的,开发维护成本你想都想不到,出个问题查都查半天。至于RocketMQ(阿里开源的),git活跃度还可以。基本上你push了自己的bug确认了有问题都有阿里大佬跟你试试解答并修复的,我个人推荐的也是这个,他的架构设计部分跟同样是阿里开源的一个RPC框架是真的很像(Dubbo)可能是因为师出同门的原因吧。Tip:Dubbo等我写到RPC我会详细介绍的。Kafka我放到最后说,你们也应该知道了,压轴的这是个大哥,大数据领域,公司的日志采集,实时计算等场景,都离不开他的身影,他基本上算得上是世界范围级别的消息队列标杆了。

在这里插入图片描述

zeromq

zeromq是系统库的形式出现的
在这里插入图片描述
在这里插入图片描述

wget https://github.com/zeromq/libzmq/releases/download/v4.2.2/zeromq-4.2.2.tar.gz #此处一定要装4.2.2 因为使用的go包 目前cgo版本导入的库,最高限制为4.2.2

# Unpack tarball package
tar xvzf zeromq-4.2.2.tar.gz

# Install dependency
sudo yum update

sudo yum install -y libtool pkg-config build-essential autoconf automake uuid-dev


# Create make file
cd zeromq-4.2.2
./configure                                                 //如果提示gcc-c 或者gcc-c++找不到 进行yum安装

# Build and install(root permission only)
sudo make install

# Install zeromq driver on linux
sudo ldconfig

# Check installed
ldconfig -p | grep zmq                           #如果没出现期望,请按照此链接执行 https://blog.csdn.net/Sunny_Future/article/details/105410625

# Expected
############################################################
# libzmq.so.5 (libc6,x86-64) => /usr/local/lib/libzmq.so.5
# libzmq.so (libc6,x86-64) => /usr/local/lib/libzmq.so
############################################################


编译完成后:
需要在/etc/profile中添加:

export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig
然后,运行source /etc/profile

此外,还需要将在/etc/ld.so.conf.d 中新建一个zeromq.conf文件,在此文件中写入:

/usr/local/lib
保存此次文件写入。

至此,zeromq安装完毕,可通过ldconfig -p | grep zeromq 验证是否已经安装完成。 这步没有也无所谓

在这里插入图片描述
在这里插入图片描述
总结:适用于消息接受没这么关键的场景

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值