RocketMq的理解和记录

文章目录

RocketMQ

rocketMq 事务消息:

2pc 两阶段提交 rocketMq使用的是2pc;最典型的方案就是数据库的事物(特点);需要b的一方实现xa协议
1、尝试提交(不会真的提交会维护一个状态,不可用) 2、确认ok (确认之后状态可用)
在这里插入图片描述

tcc (try confirm cancel)三阶段提交分为3步
1、发送数据就是try b开始lock数据 2、a执行本地事物当conmit的时候,confirm数据,释放b这个锁 3、如果失败就cancal方法(try confirm cancal都有b实现)[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-abIGH2TI-1672195623166)(./images/221103-170326.png)]

rocketMq中使用消息事物的流程

1、首先发送方要开启事务
2、发送消息这一端要发送消息(这个时候并不是真的要发送消息 HFMessage(一般消息发出去)或者叫做preparaMessage)
3、发送消息给broker,broker接到hfMessage后(其实主要标识一下这个produce发送事物了)
4、broker接到这个消息后会写到专门的消息队列 hf
5、写进去之后,同步或者异步保存到磁盘(那么这条消息就算是收到了)
6、把确认信号给producer(保证发送消息也是可靠的)
7、这个时候就可以真正的可以执行本地事务了
之前在send方法的时候,这个broker会返回信号
如果rollback就把HFM队列的消息撤销掉,commit的话就把Hf队列消息撤销掉,把真正的消息发送出去
8、为了保证执行本地任务时间过长的问题,broker会开启一个定时任务(跑的就是Hf队列里的半消息)
9、取到事务相关信息,检查7有没有完成
10、produce会发起一个回调方法,按照本地逻辑回馈 成功或者失败 (如果连接不上回调方法重试几次后就会剔除这个事务,producer就会重新发这个消息)
11、consumer直接订阅消息

执行过的消息会存在系统磁盘上,一定时间后会清除消息
tcc一般是框架,需要中间件支持
2pc一般是基于数据库或者第三方的中间件比如说redis,一些mq或者说数据库这些

RocketMQ角色

在这里插入图片描述

broker

  • Broker面向producer和consumer接收和发送消息
  • 向nameserver提交自己的信息
  • 是消息中间件的消息存储、转发服务器
  • 每个broker节点,在启动时,都会遍历NameServer列表,与每个NameServer建立长连接,注册自己的信息,之后上报(broker中的master和slave,master只支持写发,slave只支持发,p只能连接master,c可以连接master和slave)
    (producer想要发送到某一个broker上,会先去找nameserver的topic,发送到某一个topic上,topic知道是哪个broker,consumer同理;broker可以动态的上下线,namespace是无状态的)
broker集群
  • Broker高可用,可以配成Master/Slave结构,Master可写可读,Slave只可以读,Master将写入的数据同步给Slave
    • 一个Master可以对应多个slave,但是一个Slave只能对应一个Master
    • Master与Slave的对应关系通过指定相同的brokerName,不同的BrokerId来定义BrokerId为0表示Master,非0表示Slave
  • Master多机负载,可以部署多个broker
    • 每个broker与namespace集群中的所有节点建立长连接,定时注册Topic信息到所有的namesever

producer

  • 消息的生产者
  • 通过集群中的其中一个节点(随机选择)建立长连接,获取Topic的路由信息,包括Topic下面有哪些Queue,这些Queue分布在哪些Broker上等
  • 接下来向提供Topic服务的Master建立长连接,且定时向Master发送心跳

consumer

消息的消费者,通过namespace集群获取Topic的路由信息,连接到对应的Broker上消费消息
注意,由于Master和salve都可以读取消息,因此Consumer会与Master和Slave都建立连接

namespace

底层由netty实现,提供了路由管理、服务注册、服务发现的功能,是一个无状态节点
namespace是服务发现者,集群中各个角色(producer、broker、consumer等)都需要定时向namespace上报自己的状态,以便互相发现彼此,超时不上报的话,nameserver会把它从列表剔除
**nameserver可以部署多个,**当多个namespace存在的时候,其他角色同时向他们上报消息,以保证高可用
**namespace集群间互不通信,**没有主备概念
为什么不用zookeeper?:rockerMq希望提高性能,CAP定理,客户端负载均衡

对比JMS中的Topic和Queue

Topic是一个逻辑上的概念,实际上Message是每个Broker上以及Queue的形式记录
在这里插入图片描述
(和activeMq做对比,activeMq中遵循JMS标准,有两种消息类型一种是Topic一种是Queue,topic相当于广播,可以被多个人消费,queue只能被单个消费者消费;在RocketMq中没有topic的概念,只有一种Queue,Queue在rocketmq中可以广播,广播在客户端设置,通过客户端设置这条消息的属性,消费者来设置; 一个topic中可以包含多个queue,topic中queue太多的话,可以存在多个broker上,这时候就是对于一个broker进行分片处理;topic在使用的时候只是逻辑上存在,在使用的时候其实就是消息队列,队列的名字是什么,往这个队列里面写数据,topic和queue相当于包含关系)

发送方式

批量发送消息

可以多条消息打包一起发送,减少网络传输次数提高效率

  • 批量消息要求必要具有同一topic,相同消息配置
  • 不支持延时消息
  • 建议一个批量消息最好不要超过1MB大小
  • 如果不确定是否超过限制,可以手动计算大小分批发送

消费消息模式

集群消息

在这里插入图片描述
集群消息是指集群化部署消费者
当使用集群消费模式时,MQ认为任意一条消息只需要被集群内的任意一个消费者处理即可
特点

  • 每条消息只需要被处理一次,broker只会把消息发送给消费集群中的一个消费者
  • 在消息重投时,不能保证路由到同一台机器上
  • 消费状态由broker维护
广播消息

在这里插入图片描述
当使用广播消费模式时,MQ会将每条消息推送给集群内所有注册过的客户端,保证消息至少被每台机器消费一次
特点

  • 消费进度由consumer维护
  • 保证每个消费者消费一次消息
  • 消费失败的消息不回重投
    消息过滤,根据Tag来过滤,根据key来做模糊查找(tag selector在一个group中的消费者,都不能随便变,要保持统一;某一个组刚刚关注了tag-a 又换了关注了tag-b就会混乱接收不到消息;但是可以事同一个topic)
    组:在这里插入图片描述
    设置tag
    在这里插入图片描述
    在这里插入图片描述

RocketMq源码一级高级Api

Offset

每个broker中的queue在收到消息时会记录offset,初始值为0,每记录一条消息offset会递增+1

minOffset

最小值

maxOffset

最大值

consumerOffset

消费者消费进度/位置

diffTotal

消费积压/未被消费的消息数值

消费者
DefaultMQPushConsumer与DefaultMQPullConsumer

在消费端,我们可以视情况来控制消费过程
DefaultMQPushConsumer由系统自动控制过程

  1. 消息的消费者new出来一个group DefaultMQPushConsumer consumer = new DefaultMQPushConsumer (''consumer01")
  2. 然后set地址连接 consumer.setNamesrvAddr(“ip:端口”);
  3. 然后订阅 consumer.subscribe(“myTopic01”)
  4. 然后registerMessageListener(new Listener(){回调})
  5. 启动 consumer.start
    start后客户端启动成功了,消息会放在回调里,messageListener会逐步的消费消息
    rocketMq伪代码整体启动流程
    在这里插入图片描述

DefaultMQPullConsumer大部分功能需要手动控制 去掉了换成了DefaultMQLiteConsumer

集群消息的消息负载均衡

在集群消费模式下(clustering)
相同的group中的每个消费者只消费topic中的一部分内容
group中所有消费者都参与消费过程,每个消费者消费的内容不重复,从而达到负载均衡的效果
使用DefaultMQPushConsumer,新启动的消费者自动参与负载均衡

ProcessQueue

消息处理类拉取回来的消息,也可认为是消息缓冲区

长轮询

Consumer->Broker RocketMq才用的长轮询建立连接

  • consumer的处理能力Broker不知道
  • 直接推送消息broker端压力较大,需要维护consumer的状态
  • 采用长链接有可能consumer不能及时处理推送过来的数据
  • pull主动权在consumer手里
    (RabbitMQ无论是pull还是push底层用的都是pull这说明了一个问题,RabbitMQ的网络模型是长轮询-介于长连接和短轮询之间.)
短轮询

client不断发送请求到server,每次都需要重新连接
每隔2秒请求一次
比较常见的事 http协议

长轮询

client不断发送请求到server,server有数据返回,没有数据请求挂起不断开连接
在这里插入图片描述
好处:client掌握了主动权,不会导致消息重投

长连接

连接一旦建立,永远不断开,push方式推送
在这里插入图片描述
好处:没有延迟
坏处:server这一端要维护client的状态,对于服务器来说性能开销要高于轮询;还有一个问题服务器不知道client的消费速度,盲目的消息推过去导致消息堆积,消息堆积导致的后果就是c1堆积消息了,分配给c1的消息c2消费不到,服务器拿不到该消息的状态就会还往c1推送消息,导致消息很久得不到消费消息会退还给server造成消息重投
相当于websocket协议
源码解析processQueue
在这里插入图片描述
真实拉取数据的转换和协议
在这里插入图片描述
客户端向服务器连接发心跳包
在这里插入图片描述
客户端分发排序配置
在这里插入图片描述
pullMessageService拉取消息
发心跳包,然后设置间隔时间;然后client启动流程,开启schedule定时器(获取nameserver地址,定时更新路由信息,清除无效broker,向broker发送心跳包保证连接状态,持久化消息);消息拉取;消息填充
rebalanceServer负载消息

消息存储

在这里插入图片描述

磁盘存储速度问题
省去DB层提高性能

RocketMQ使用文件系统持久化消息,性能要比使用DB产品要高
在这里插入图片描述

M.2 NVME协议磁盘存储

文件写入速度顺序读写:3G左右 随机读写:2G

数据零拷贝技术

很多实用文件系统存储的高性能中间件都是使用了零拷贝技术来发送文件数据,比如Nginx
在这里插入图片描述
rocketMQ想要拿一份数据

  1. 先把请求发送给内核
  2. 内核到磁盘找到数据
  3. 找到的数据复制一份加载到内核
  4. 再复制一份发给Mq
    想要发出去
  5. 再复制一份交给网卡驱动,由内核提交
    启动了数据0拷贝,只需要一个信号给内核,内核拿到数据直接推到网卡上
    复制在内存里,数据量比较大占用数据总线时间很长
内存映射MapperdByteBuffer API
  1. MappedByteBuffer使用虚拟内存,因此分配(map)的内存大小不受JVM的-Xmx参数限制,但是也是有大小限制的
  2. 如果当文件超出1.5G限制时,可以通过position参数重新map文件后面的内容
  3. MappedByteBuffer在处理大文件时的性能的确很高,但也存在一些问题,如内存占用、文件关闭不确定,被其打开的文件只有在垃圾回收的时候才会被关闭,而且这个时间点是不确定的
    javadoc中也提到:A mapped byte and the file mapping that it represents remain* valid until the buffer itself is garbage-collectes
    所以为了使用零拷贝技术,RocketMQ的文件存储大小默认每个1g,超过1g会重新建立一个新文件
存储结构
CommitLog

存储消息的详细内容,按照消息收到的顺序,所有消息都存储在一起.每个消息存储后都会有一个offset,代表在commitLog中的偏移量
默认配置 MessageStoreConfig
核心方法

  • putMessage写入消息
CommitLog内部结构
  • MappedFileQueue->MappedFile
    MappedFile 默认1G
ConsumerQueue

通过消息偏移量建立的消息索引
针对每个Topic创建,消费逻辑队列,存储位置信息,用来快速定位CommitLog中的数据位置
启动后会被加载到内存中,加快查找消息速度

indexFile
存储路径配置
config
consumerFilter.json
consumerOffset.json
delayOffset.json
subscriptionGroup.json
topics.json
刷盘机制
写入是消息会不会分割到两个MapedFile中?
同步刷盘

消息被broker写入磁盘后再给producer响应

异步刷盘

消息被broker写入内存后立即给producer响应,当内存中消息堆积到一定程度的时候写入磁盘持久化

配置选项

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

消息存储流程

在这里插入图片描述

broker启动流程

消息持久化流程

在这里插入图片描述

NameServer特点

1、没状态 2、高可用 3、高性能 4、数据会有不一致
在这里插入图片描述
NameServer采用的是broker定时向所有的NameServer发送心跳包,容错信息放在客户端来做(注册中心简化了,客户端就复杂了)
producer发送消息正常,broker之间发送信息异常,nameserver就要规避这样的问题,就会把这个broker踢掉,来维护本地的topic信息,那么producer取到的topic信息就是准确的。
对于已订阅的consumer来说剔除掉了一个broker,那么nameserver里的信息没有了,那么就得不到正确的broker地址,怎么解决?
一次订阅会定时拉取

NameServer启动流程

1和broker建立连接 2和producer和consumer建立连接在这里插入图片描述
在这里插入图片描述
一个topic可以在很多个broker中,一个topic有很多个queue
TopicRouteData返回BrokerData和QueueData;BrokerData标记了当前这个topic他所在服务器的ip地址,在那几台服务器上 (一个brokerName有多个ip地址是因为在主从复制的情况下,brokerName相同会返回不同的brokerId,区分出来那个是主节点注解便brokerId=0 );QueueData返回Ququq的信息
RocketMQ的同步双写
保证强一致性
在这里插入图片描述

集群方式

单Master模式

只有一个master节点
优点:配置简单方便部署
缺点:风险大,一旦broker重启或者宕机,会出现整个服务不可用

多Master模式

一个集群无Slave,全是Master,例如2个Master或者3个Master
优点:配置简单,单个Master宕机或重启维护对应用无影响,在磁盘配置RAID10时,即便机器宕机不可恢复的情况下,由于RAID10磁盘十分可靠,消息也不会丢失(异步刷盘丢失少量消息,同步刷盘一条不丢),性能最高,多Master多Slave模式,异步复制
缺点:单台机器宕机期间,这台机器上未被消费的消息在机器恢复之前不决订阅,消息实时性受到影响

多Master多Slave模式(异步复制)

每个Master配置一个Slave,有多对Master-Slave,HA,采用异步复制方式,主备有短暂消息延迟,毫秒级
优点:即使磁盘损坏,消息丢失的很少,且消息实时性不会受到影响,因为Master宕机后,消费者仍然可以从Slave消费,此过程对外透明.不需要人工干预.性能同多Master模式几乎一样
缺点:Master宕机,磁盘损坏情况下,会丢失少量消息

多Master多Slave模式(同步双写)

每个Master配置一个Slave,有多对Master-Slave,HA采用同步双写方式,主备都写成功,向应用返回成功
优点:数据与服务都无单点,Master宕机情况下,消息无延迟,服务可用性与数据可用性都非常高
缺点:性能比异步复制模式略低,大约低10%左右,发送单个消息的RT会略高,目前主宕机后,备机不能自动切换为主机,后续会支持主动切换功能
在这里插入图片描述

基于raft协议的commitlog存储库DLeger

之前的数据同步流程:
发送一条消息进入到master的commitLog当中,master的commitLog执行append方法,将数据添加,这个时候数据是未提交数据;同步到slave的commitLog后返回一个ack到index是完成了当ack个数超过半数节点之后;这个未提交数据被换成提交状态;producer才能收到sendResult
在这里插入图片描述
采用Dleger方式同步时
producer进来之后等待同步到一半之后返回的ack的个数超过就置为提交;主备之间广播方式通过心跳来实现.Dleger之间完成心跳;Dleger可以当做一个内置的服务器,单独起了线程有自己端口,两个Dleger之间可以互相通讯
包装了原来的commitLog;拥有选举功能(选举策略类zk)
在这里插入图片描述

RocketMQ由哪些角色组成,每个角色的作用是什么?

nameserver 无状态不存储数据,每个nameserver启动起来互相不通讯;作用:路由,用来注册producer broker consumer ;无状态 动态列表
如何保证数据一致性?每起一个服务,都会随机的向nameserver发动长连接,把数据上报上去
producer
broker 这个角色向所有的nameserver建立长连接 上报topic信息queue的信息
consumer
producer和consumer不需要担心nameserver数据的一致性,内部没有完成数据的交换与通信,但是有很多的broker连向nameserver,由broker来完成与nameserver之间通讯一致性

RocketMQ中的Topic和ActiveMQ有什么区别?
ActiveMQ

有destination的概念,即消息目的地
destination分为两类:

  • topic
  • 广播消息
  • queue
    • 队列消息
RcoketMQ

RocketMQ的Topic是一组Message Queue的集合
一条消息是广播消息还是队列消息是由客户端消费决定

RcoketMQ Broker中的消息被消费后会立即删除吗?

不会,每条消息都会持久化到CommitLog中,每个consumer连接到broker后会维持消费季度信息,当有消息消费后只是当前consumer的消费进度(CommitLog中的offset)更新了

那么消息会堆积吗?什么时候清理过期消息?

4.6版本默认48小时会删除不再使用的commitLog文件

  • 判断这个文件最后访问时间
  • 判断是否大于过期时间
  • 指定时间删除,默认凌晨4点
RockerMQ消费模式有几种?

消费模型由consumer决定,消费维度为Topic

集群消费

一组consumer同时消费一个Topic,可以分配消费负载均衡策略分配consumer对应消费topic下的那些queue
多个group同时消费一个topic时,每个group都会消费到数据
一条消息只会被一个group中的consumer消费

广播消费

消息将对一个Consumer Group下的各个consumer实例都消费一遍,即便这些consumer属于同一个consumer group,消息也会被consumer group中的每个consumer都消费一次

消费消息时使用的是push还是pull?

在刚开始的时候就要决定适用哪种方式消费
两种:
DefaultLitePullConsumerImpl 拉
DefaultMQPushConsumerImpl 推
两个实现DefaultLitePullConsumerImpl DefaultMQPushConsumerImpl 都实现了MQConsumerInner接口
名称上看起来是一个推,一个拉,但实际底层是实现都是采用长轮询机制,即拉取方式
broker端属性longPullingEnable标记是否开启长轮询.默认开启

为什么要主动拉取消息而不使用事件监听方式`?

事件驱动方式是建立好长连接,由事件(发送数据)的方式来实时推送
如果broker主动推送消息的话有可能push速度快,消费速度慢的情况,那么就会造成消息在consumer端堆积过多,同时又不能被其他consumer消费的情况

说一说常见的消息同步机制

push:
如果broker主动推送消息的话有可能push速度快,消费速度慢的情况,那么就会造成消息在consumer端堆积过多,同事不能被其他consumer消费的情况
pull:
轮询时间间隔,固定值的话会造成资源浪费
长轮询:

broker如何处理拉取请求的?

consumer首次请求broker

  • broker中是否有符合条件的消息
  • 有->
    • 响应consumer
    • 等待下次consumer的请求
  • 没有
    • 挂起consumer的请求,即不断开连接也不返回数据
    • 使用consumer的offset
      • DefaultMessageStore#ReputMessageService#run方法
        • 每间隔1ms检查commitLog中是否有新消息,有的话写入到pullRequestTable
        • 当有新消息的时候返回请求
      • PullRequestHoldService来Hold连接,每隔5s执行一次检查pullRequestTable有没有消息,有的话立即推送
RocketMQ如何做负载均衡?

通过Topic在多broker中分布式存储实现
默认写法:producer发送数据到topic,topic采用随机选举策略把数据存放在queue上(brokerName+queueId是唯一标识)
console里不能为单个broker指定创建几个queue
在这里插入图片描述
消息的消费是基于group的,一个group多个consumer会将消费进度用offset进行标记进度;新上一个group那么这些消息对于这个group来说就是没有消费过的

RocketMQ的消息堆积如何处理

下游消费系统如果宕机了,导致几百万条消息在消息中间件里积压,如何处理?
你们线上是否遇到消息积压的生产故障?如果没有遇到过,你考虑一下怎么应对?
具体表现为ui中转圈圈
对于大规模消息发送接收可以使用pull模式,手动处理消息拉取速度,消费的时候统计消费事件以供参考
保证消息消费速度固定,即可通过上线更多的consumer临时解决消息堆积问题
什么情况可能产生消费堆积?

  1. producer业务量激增 2 .consumer故障或者重启
如果consumer和queue不对等,上线了多台也在短时间内无法消费完堆积的消息怎么办?
  • 准备一个临时的topic

  • 新上线一个group设置fromwhere定位到最后边,老的consumer继续按照顺序执行,判断消息的offset防止重复消费(临时解决方案)

  • queue的数量是堆积的几倍

  • queue分不到多broker种

  • 上线一台consumer做消息的搬运工,把原来topic中的消息挪到新的topic里,不做业务逻辑处理,只是挪过去

  • 在这里插入图片描述

  • 上线N台consumer同时消费临时topic中的数据

  • 改bug

  • 恢复原来的consumer,继续消费之前的topic

堆积时间过长消息超时了?

RockeMQ中的消息只会在commitLog被删除的时候才会消失,不会超时(RcoketMQ没有针对单条消息过期,只有commitLog过期)commitLog根据最后访问的时间超过48h自动删除

堆积的消息会不会进死信队列

不会,消息在消费失败后进入重试队列(%RETRY%+consumergroup),多次(默认16)才会进入死信队列(%DLQ+consumergroup)

零拷贝等技术是如何运用的?

使用nio的MappedByteBuffer调用数据输出

你们使用的是RcoketMQ?RocketMQ很大的一个特点是对分布式事物的支持,说一下他在分布式事务的底层原理

分布式系统中的事务可以使用TCC(Try,Confirm,Cancel),2pc来解决分布式系统中的消息原则性,RocketMQ4.3+替工分布式事务功能,通过RcoketMQ事务消息能达到分布式事务的最终一致性

RcoketMQ实现方式

**Half Message:**预处理消息,当broker收到此类消息后,会存储到RMQ_SYS_TRANS_HALF_TOPIC的消息消费队列中
检查事物状态: Broker会开启一个定时任务,消费RMQ_SYS_TRANS_HALF_TOPIC队列中的消息,每次执行任务会向消息发送者确认事物执行状态(提交、回滚、未知),如果是未知,等待下一次回调
超时: 如果超过回查次数,默认回滚消息

如何让RocketMQ保证消息的消费顺序
  • 同一个topic
  • 同一个queue
  • 发送消息的时候一个线程去发送消息
  • 消费的时候一个线程消费一个queue里的消息或者使用MessageListenerOrderly
  • 多个queue只能保证单个queue里的顺序
RocketMQ如何保证消息不丢失

1.生产端如何保证投递出去的消息不丢失;消息在半路丢失,或者在MQ内存中宕机导致丢失,此时你如何基于MQ的功能保证消息不要丢失?
2.MQ自身如何保证消息不丢失?
3.消费端如何保证消费到的消息不丢失;如果你处理到一半消费端宕机,导致消息丢失,此时怎么办?

解耦的思路

发送方
发送消息时做消息备份(记日志或同步到数据库),判断sendResult是否正常返回
broker
节点保证

  • master接收到消息后同步刷盘,保证了数据持久化到本机磁盘中
  • 同步写入到slave
  • 写入完成后返回sendResult
    consumer
  • 记日志
  • 同步执行业务逻辑,最后返回ack
  • 异常控制
    磁盘保证
    使用Raid磁盘阵列保证数据磁盘安全
    网络数据篡改
    内置TLS可以开启,默认使用crc32校验数据
高吞吐下如何优化生产者和消费者的性能?

消费

  • 同一group下,多机部署,并行消费
  • 单个consumer提高消费线程消费个数
  • 批量消费
    • 消息批量拉取
    • 业务逻辑批量处理
      运维
    • 网卡调优
    • jvm调优
      RocketMq是如何保证数据的高容错性的?
  • 在不开启容错的情况下,轮询队列进行发送,如果失败了,重试的时候过滤失败的broker
  • 如果开启了容错策略,会通过RocketMQ的预测机制来预测一个broker是否可用
  • 如果上次失败的broker可用那么还是会选择broker队列
  • 如果上述情况失败,则随机选择一个进行发送
  • 在发送消息的时候记录一下调用的时间是否报错,根据该时间去预测broker的可用时间
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值