分布式事务入门

分布式事务基础
事务的基本概念
事务一般指的是逻辑上的一组操作,或者作为单个逻辑单元执行的一系列操作。同属于一个事务的操作会作为一个整体提交给系统,这些操作要么全部执行成功,要么全部执行失败

事务的特性 ACID
原子性(Atomic)
事务的原子性指的是构成事务的所有操作要么全部执行成功,要么全部执行失败,不可能出现部分执行成功,部分执行失败的情况。例如,在转账业务中,张三向李四转账100元,于是张三的账户余额减少100元,李四的账户余额增加100元。在开启事务的情况下,这两个操作要么全部执行成功,要么全部执行失败,不可能出现只将张三的账户余额减少100元的操作,也不可能出现只将李四的账户余额增加100元的操作。

一致性(Consistency)
事务的一致性指的是在事务执行之前和执行之后,数据始终处于一致的状态。例如,同样是转账业务,张三向李四转账100元,且转账前和转账后的数据是正确的,那么,转账后张三的账户余额会减少100元,李四的账户余额会增加100元,这就是数据处于一致的状态。如果张三的账户余额减少了100元,而李四的账户余额没有增加100元,这就是数据处于不一致状态。

隔离性(Isolation)
事务的隔离性指的是并发执行的两个事务之间互不干扰。也就是说,一个事务在执行过程中不能看到其他事务运行过程的中间状态。例如,在张三向李四转账的业务场景中,存在两个并发执行的事务A和事务B,事务A执行扣减张三账户余额的操作和增加李四账户余额的操作,事务B执行查询张三账户余额的操作。在事务A完成之前,事务B读取的张三的账户余额仍然为扣减之前的账户余额,不会读取到扣减后的账户余额。

持久性(Durability)
事务的持久性指的是事务提交完成后,此事务对数据的更改操作会被持久化到数据库中,并且不会被回滚。例如,在张三向李四转账的业务场景中,在同一事务中执行扣减张三账户余额和增加李四账户余额的操作,事务提交完成后,这种对数据的修改操作就会被持久化到数据库中,且不会被回滚。

本地事务
本地事务使用常见的执行模式,可以使用如下伪代码来表示。

transaction begin
insert …
update …
delete …
transaction commit/rollback
本地事务的优点总结如下。
1)支持严格的ACID特性,这也是本地事务得以实现的基础。
2)事务可靠,一般不会出现异常情况。
3)本地事务的执行效率比较高。
4)事务的状态可以只在数据库中进行维护,上层的应用不必理会事务的具体状态。
5)应用的编程模型比较简单,不会涉及复杂的网络通信。
本地事务的缺点总结如下。
1)不具备分布式事务的处理能力。
2)一次事务过程中只能连接一个支持事务的数据库,即不能用于多个事务性数据库。

MySQL事务的实现原理
MySQL作为互联网行业使用最多的关系型数据库之一,其InnoDB存储引擎本身就支持事务。MySQL的事务实现离不开Redo Log和Undo Log。从某种程度上说,事务的隔离性是由锁和MVCC机制实现的,原子性和持久性是由Redo Log实现的,一致性是由Undo Log实现的。

Engine Support Comment Transactions XA Savepoints
FEDERATED NO Federated MySQL storage engine NULL NULL NULL
BLACKHOLE YES /dev/null storage engine (anything you write to it disappears) NO NO NO
XENGINE NO X-Engine storage engine NULL NULL NULL
MEMORY YES Hash based, stored in memory, useful for temporary tables NO NO NO
InnoDB DEFAULT Supports transactions, row-level locking, and foreign keys YES YES YES
PERFORMANCE_SCHEMA YES Performance Schema NO NO NO
Sequence YES Sequence Storage Engine Helper NO NO NO
MyISAM YES MyISAM storage engine NO NO NO
MRG_MYISAM YES Collection of identical MyISAM tables NO NO NO
CSV YES CSV storage engine NO NO NO
并发事务带来的问题
更新丢失(脏写)
脏读
不可重复读
幻读
MySQL中各种事务隔离级别的区别
enter image description here
1)读未提交允许脏读,即在读未提交的事务隔离级别下,可能读取到其他会话未提交事务修改的数据。这种事务隔离级别下存在脏读、不可重复读和幻读的问题。
2)读已提交只能读取到已经提交的数据。Oracle等数据库使用的默认事务隔离级别就是读已提交。这种事务隔离级别存在不可重复读和幻读的问题。
3)可重复读就是在同一个事务内,无论何时查询到的数据都与开始查询到的数据一致,这是MySQL中InnoDB存储引擎默认的事务隔离级别。这种事务隔离级别下存在幻读的问题。
4)串行化是指完全串行地读,每次读取数据库中的数据时,都需要获得表级别的共享锁,读和写都会阻塞。这种事务隔离级别解决了并发事务带来的问题,但完全的串行化操作使得数据库失去了并发特性,所以这种隔离级别往往在互联网行业中不太常用。

Spring事务的实现原理
从本质上讲,Spring事务是对数据库事务的进一步封装。也就是说,如果数据库不支持事务,Spring也无法实现事务操作。

spring事务的两种方式
编程式事务
通过TransactionTemplate或TransactionManager手动管理事务,因为是手动,所以实际开发中很少使用。

@Service
public class AccountService01 {
@Autowired
private AccountDao accountDao;
@Autowired
private PlatformTransactionManager platformTransactionManager;

public void TransferMoney(String from, String to, Double money) {
    DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
    //开启事务
    TransactionStatus status = platformTransactionManager.getTransaction(definition);
    try {
        accountDao.minusMoney(from, money);
        accountDao.addMoney(to, money);
        platformTransactionManager.commit(status);
    } catch (Exception e) {
        e.printStackTrace();
        platformTransactionManager.rollback(status);
    }
}

}
声明式事务
applicationContext.xml 配置文件主要分为五个步骤:配置数据源、配置JdbcTemplate、配置事务管理器、配置事务属性和配置AOP。

@Configuration
@EnableTransactionManagement
public class CommonConfig {

@Bean(destroyMethod = "close", initMethod = "init", name = "paasDataSource")
public DruidDataSource writeDataSource() {
    System.out.println("注入druid!!!");
    return createDataSource();
}
@Bean
public JdbcTemplate jdbcTemplate(@Qualifier("paasDataSource") DataSource dataSource) {
    return new JdbcTemplate(dataSource);
}
@Bean(name = "transactionManager")
public PlatformTransactionManager transactionManager(@Qualifier("paasDataSource") DataSource dataSource) {
    return new DataSourceTransactionManager(dataSource);
}

}
@Transactional
@Override
public MetaCreateResultVO insertMeta(String entCode, String empCode, PMeta meta) {
//创建存储映射
PMetaStoreMapping metaStoreMapping = metaStoreMappingService.allocMetaStore(entCode, empCode, id, name);
//创建主标题字段
Long fieldId = createPrimaryFields(entCode, empCode, id, primaryField, metaStoreMapping.getTableName());
//创建默认字段及其布局
Long layoutId = createDefaultFieldsAndLayout(entCode, empCode, meta, fieldId, result, metaStoreMapping.getTableName());
//创建字段分配记录
metaFieldAllocService.initFieldAlloc(entCode, id);
//创建默认业务类型
Long bizTypeId = createDefaultBiztype(entCode, empCode, meta, layoutId);
// 发送es消息
MessageEntity idxMsgEntity = new MessageEntity(entCode, id, name, MsgQConstant.MSG_ACTION_CREATE, null);
messageProducer.send(MsgQConstant.PAAS_EXCHANGE, MsgQConstant.PAAS_INDEX_CREATE, JSONObject.toJSONString(idxMsgEntity));
return result;
}
spring事务回滚原则
使用Spring管理事务,可以指定在方法抛出异常时,哪些异常能够回滚事务,哪些异常不回滚事务。默认情况下,在方法抛出RuntimeException时回滚事务,也可以手动指定回滚事务的异常类型,代码如下。
@Transactional(rollbackFor = Exception.class)

spring事务传播机制
enter image description here
@Transactional(propagation = Propagation.REQUIRED)
REQUIRED事务传播类型表示如果当前没有事务,就创建一个事务,如果已经存在一个事务,就加入这个事务。这是最常见的事务传播类型,也是Spring当中默认的事务传播类型。外部不存在事务时,开启新的事务,外部存在事务时,将其加入外部事务中。如果调用端发生异常,则调用端和被调用端的事务都将回滚。
适用于大部分场景。
@Transactional(propagation = Propagation.NOT_SUPPORTED)
NOT_SUPPORTED事务传播类型表示以非事务方式执行,如果当前操作在一个事务中,则把当前事务挂起,直到当前操作完成再恢复事务的执行。如果当前操作存在事务,则把事务挂起,以非事务的方式运行。
适用于发送提示消息、站内信、短信、邮件等,这类场景要求不影响系统的主体业务逻辑,即使操作失败也不应该对主体逻辑产生影响,不能使主体逻辑的事务回滚。
@Transactional(propagation = Propagation.REQUIRES_NEW)
REQUIRES_NEW事务传播类型表示如果当前存在事务,则把当前事务挂起,并重新创建新的事务并执行,直到新的事务提交或者回滚,才会恢复执行原来的事务。这种事务传播类型具备隔离性,将原有事务和新创建的事务隔离,原有事务和新创建的事务的提交和回滚互不影响。新创建的事务和被挂起的事务没有任何关系,它们是两个不相干的独立事务。外部事务执行失败后回滚,不会回滚内部事务的执行结果。内部事务执行失败抛出异常,被外部事务捕获到时,外部事务可以不处理内部事务的回滚操作。
适用于不受外层方法事务影响的场景,例如记录操作日志,不管操作是否完成,日志都要记录下来。

分布式事务基本概念
分布式事务指的是事务的参与者、事务所在的服务器、涉及的资源服务器以及事务管理器等分别位于不同分布式系统的不同服务或数据库节点上。简单来说,分布式事务就是一个在不同环境(比如不同的数据库、不同的服务器)下运行的整体事务。这个整体事务包含一个或者多个分支事务,并且整体事务中的所有分支事务要么全部提交成功,要么全部提交失败。例如,在电商系统的下单减库存业务中,订单业务所在的数据库为事务A的节点,库存业务所在的数据库为事务B的节点。事务A和事务B组成了一个具备ACID特性的分布式事务,要么全部提交成功,要么全部提交失败。

分布式事务理论
什么是CAP原则?
CAP原则又叫CAP定理,同时又被称作布鲁尔定理(Brewer’s theorem),指的是在一个分布式系统中,不可能同时满足以下三点。
enter image description here

一致性(Consistency)
指强一致性,在写操作完成后开始的任何读操作都必须返回该值,或者后续写操作的结果。

也就是说,在一致性系统中,一旦客户端将值写入任何一台服务器并获得响应,那么之后client从其他任何服务器读取的都是刚写入的数据
一致性保证了不管向哪台服务器写入数据,其他的服务器能实时同步数据
可用性(Availability)
可用性(高可用)是指:每次向未崩溃的节点发送请求,总能保证收到响应数据(允许不是最新数据)

分区容忍性(Partition tolerance)
分布式系统在遇到任何网络分区故障的时候,仍然能够对外提供满足一致性和可用性的服务,也就是说,服务器A和B发送给对方的任何消息都是可以放弃的,也就是说A和B可能因为各种意外情况,导致无法成功进行同步,分布式系统要能容忍这种情况。除非整个网络环境都发生了故障。

为什么只能在A和C之间做出取舍?
分布式系统中,必须满足 CAP 中的 P,此时只能在 C/A 之间作出取舍。
如果选择了CA,舍弃了P,说白了就是一个单体架构。

一致性有几种分类?
CAP理论告诉我们只能在C、A之间选择,在分布式事务的最终解决方案中一般选择牺牲一致性来获取可用性和分区容错性。

这里的 “牺牲一致性” 并不是完全放弃数据的一致性,而是放弃强一致性而换取弱一致性。
一致性可以分为以下三种:

强一致性

弱一致性

最终一致性

强一致性
系统中的某个数据被成功更新后,后续任何对该数据的读取操作都将得到更新后的值。
也称为:原子一致性(Atomic Consistency)、线性一致性(Linearizable Consistency)
简言之,在任意时刻,所有节点中的数据是一样的。例如,对于关系型数据库,要求更新过的数据能被后续的访问都能看到,这是强一致性。
总结:

一个集群需要对外部提供强一致性,所以只要集群内部某一台服务器的数据发生了改变,那么就需要等待集群内其他服务器的数据同步完成后,才能正常的对外提供服务。

保证了强一致性,务必会损耗可用性。

弱一致性
系统中的某个数据被更新后,后续对该数据的读取操作可能得到更新后的值,也可能是更改前的值。
但即使过了不一致时间窗口这段时间后,后续对该数据的读取也不一定是最新值。
所以说,可以理解为数据更新后,如果能容忍后续的访问只能访问到部分或者全部访问不到,则是弱一致性。
例如12306买火车票,虽然最后看到还剩下几张余票,但是只要选择购买就会提示没票了,这就是弱一致性。

最终一致性
是弱一致性的特殊形式,存储系统保证在没有新的更新的条件下,最终所有的访问都是最后更新的值。
不保证在任意时刻任意节点上的同一份数据都是相同的,但是随着时间的迁移,不同节点上的同一份数据总是在向趋同的方向变化。
简单说,就是在一段时间后,节点间的数据会最终达到一致状态。

总结
弱一致性即使过了不一致时间窗口,后续的读取也不一定能保证一致,而最终一致过了不一致窗口后,后续的读取一定一致。

BASE理论
BASE理论是对CAP中的一致性和可用性进行一个权衡的结果,理论的核心思想就是:我们无法做到强一致,但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性。

BA(Basic Available)基本可用
整个系统在某些不可抗力的情况下,仍然能够保证“可用性”,即一定时间内仍然能够返回一个明确的结果。这里是属于基本可用。
基本可用和高可用的区别:

“一定时间”可以适当延长 当举行大促(比如秒杀)时,响应时间可以适当延长

给部分用户返回一个降级页面 给部分用户直接返回一个降级页面,从而缓解服务器压力。但要注意,返回降级页面仍然是返回明确结果。

S(Soft State)柔性状态
称为柔性状态,是指允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性,即允许系统不同节点的数据副本之间进行数据同步的过程存在延时。

E(Eventual Consisstency)最终一致性
同一数据的不同副本的状态,可以不需要实时一致,但一定要保证经过一定时间后仍然是一致的。

分布式事务解决方案
强一致性方案
2PC(Two-phase commit protocol)
2PC 引入一个事务协调者的角色来协调管理各参与者(也可称之为各本地资源)的提交和回滚,二阶段分别指的是准备(投票)和提交两个阶段。
enter image description here
两阶段提交协议原理简单、易于实现,但是缺点也是显而易见的,包含如下:

单点问题
协调者在整个两阶段提交过程中扮演着举足轻重的作用,一旦协调者所在服务器宕机,就会影响整个数据库集群的正常运行。比如在第二阶段中,如果协调者因为故障不能正常发送事务提交或回滚通知,那么参与者们将一直处于阻塞状态,整个数据库集群将无法提供服务。

同步阻塞
两阶段提交执行过程中,所有的参与者都需要听从协调者的统一调度,期间处于阻塞状态而不能从事其他操作,这样效率极其低下。

数据不一致性
两阶段提交协议虽然是分布式数据强一致性所设计,但仍然存在数据不一致性的可能性。比如在第二阶段中,假设协调者发出了事务 commit 通知,但是因为网络问题该通知仅被一部分参与者所收到并执行了commit 操作,其余的参与者则因为没有收到通知一直处于阻塞状态,这时候就产生了数据的不一致性。
针对上述问题可以引入 超时机制 和 互询机制在很大程度上予以解决。
超时机制
对于协调者来说如果在指定时间内没有收到所有参与者的应答,则可以自动退出 WAIT 状态,并向所有参与者发送 rollback 通知。对于参与者来说如果位于 READY 状态,但是在指定时间内没有收到协调者的第二阶段通知,则不能武断地执行 rollback 操作,因为协调者可能发送的是 commit 通知,这个时候执行 rollback 就会导致数据不一致。
互询机制
此时,我们可以介入互询机制,让参与者 A 去询问其他参与者 B 的执行情况。如果 B 执行了 rollback 或 commit 操作,则 A 可以大胆的与 B 执行相同的操作;如果 B 此时还没有到达 READY 状态,则可以推断出协调者发出的肯定是 rollback 通知;如果 B 同样位于 READY 状态,则 A 可以继续询问另外的参与者。只有当所有的参与者都位于 READY 状态时,此时两阶段提交协议无法处理,将陷入长时间的阻塞状态。

3PC
3PC 包含了三个阶段,分别是准备阶段、预提交阶段和提交阶段,对应的英文就是: CanCommit、PreCommit 和 DoCommit
enter image description here
在第三阶段如果因为协调者或网络问题,导致参与者迟迟不能收到来自协调者的 commit 或 rollback 请求,那么参与者将不会如两阶段提交中那样陷入阻塞,而是等待超时后继续 commit,相对于两阶段提交虽然降低了同步阻塞,但仍然无法完全避免数据的不一致。

总结
两阶段提交协议中所存在的长时间阻塞状态发生的几率还是非常低的,所以虽然三阶段提交协议相对于两阶段提交协议对于数据强一致性更有保障,但是因为效率问题,两阶段提交协议在实际系统中反而更加受宠。
2PC和3PC在极端情况下无法100%保证数据一致性,单点问题问题其实就出在每个参与者自身的状态只有自己和协调者知道,虽然可以增加重新选举和工作日志恢复的机制,但是在参与者也挂了的情况下,还是无法完全恢复。

最终一致性方案
TCC
TCC是Try、Confirm 和 Cancel三个单词首字母缩写,它们分别的职责是:
Try:负责预留资源(比如新建一条状态=PENDING的订单);
做业务检查,简单来说就是不能预留已经被占用的资源;
隔离预留资源。
Confirm:负责落地所预留的资源
真正的执行业务使用try阶段预留的资源,幂等。
Cancel:负责撤销所预留的资源
enter image description here

异常处理
空回滚
在没有调用 TCC 资源 Try 方法的情况下,调用了二阶段的 Cancel 方法,Cancel 方法需要识别出这是一个空回滚,然后直接返回成功。
出现原因是当一个分支事务所在服务宕机或网络异常,分支事务调用记录为失败,这个时候其实是没有执行 Try 阶段,当故障恢复后,分布式事务进行回滚则会调用二阶段的 Cancel 方法,从而形成空回滚。
解决思路是关键就是要识别出这个空回滚。思路很简单就是需要知道一阶段是否执行,如果执行了,那就是正常回滚;如果没执行,那就是空回滚。前面已经说过 TM 在发起全局事务时生成全局事务记录,全局事务 ID 贯穿整个分布式事务调用链条。再额外增加一张分支事务记录表,其中有全局事务 ID 和分支事务 ID,第一阶段 Try 方法里会插入一条记录,表示一阶段执行了。Cancel 接口里读取该记录,如果该记录存在,则正常回滚;如果该记录不存在,则是空回滚。
幂等
通过前面介绍已经了解到,为了保证 TCC 二阶段提交重试机制不会引发数据不一致,要求 TCC 的二阶段 Try、Conrm 和 Cancel 接口保证幂等,这样不会重复使用或者释放资源。如果幂等控制没有做好,很有可能导致数据不一致等严重问题。
解决思路在上述"分支事务记录"中增加执行状态,每次执行前都查询该状态。
悬挂
悬挂就是对于一个分布式事务,其二阶段 Cancel 接口比 Try 接口先执行。
出现原因是在 RPC 调用分支事务 Try 时,先注册分支事务,再执行 RPC 调用,如果此时 RPC 调用的网络发生拥堵,通常 RPC 调用是有超时时间的,RPC 超时以后,TM 就会通知 RM 回滚该分布式事务,可能回滚完成后,RPC 请求才到达参与者真正执行,而一个 Try 方法预留的业务资源,只有该分布式事务才能使用,该分布式事务第一阶段预留的业务资源就再也没有人能够处理了,对于这种情况,我们就称为悬挂,即业务资源预留后没法继续处理。
解决思路是如果二阶段执行完成,那一阶段就不能再继续执行。在执行一阶段事务时判断在该全局事务下,"分支事务记录"表中是否已经有二阶段事务记录,如果有则不执行 Try。

本地消息表
本地消息表其实就是利用了 各系统本地的事务来实现分布式事务。
本地消息表顾名思义就是会有一张存放本地消息的表,一般都是放在数据库中,然后在执行业务的时候 将业务的执行和将消息放入消息表中的操作放在同一个事务中,这样就能保证消息放入本地表中业务肯定是执行成功的。
然后再去调用下一个操作,如果下一个操作调用成功了好说,消息表的消息状态可以直接改成已成功。
如果调用失败也没事,会有 后台任务定时去读取本地消息表,筛选出还未成功的消息再调用对应的服务,服务更新成功了再变更消息的状态。
这时候有可能消息对应的操作不成功,因此也需要重试,重试就得保证对应服务的方法是幂等的,而且一般重试会有最大次数,超过最大次数可以记录下报警让人工处理。
可以看到本地消息表其实实现的是最终一致性,容忍了数据暂时不一致的情况。

基于可靠消息
可靠消息最终一致性方案是指当事务发起方执行完成本地事务后并发出一条消息,事务参与方(消息消费者)一定能够接收消息并处理事务成功,此方案强调的是只要消息发给事务参与方最终事务要达到一致。
enter image description here

注意事项
本地事务与消息发送的原子性问题本地事务与消息发送的原子性问题即:事务发起方在本地事务执行成功后消息必须发出去,否则就丢弃消息。即实现本地事务和消息发送的原子性,要么都成功,要么都失败。本地事务与消息发送的原子性问题是实现可靠消息最终一致性方案的关键问题。

事务参与方接收消息的可靠性 事务参与方必须能够从消息队列接收到消息,如果接收消息失败可以重复接收消息。 消息重复消费的问题 由于网络问题,若某一个消费节点超时但是消费成功,此时消息中间件会重复投递此消息,就导致了消息的重复消费。 要解决消息重复消费的问题就要实现事务参与方的方法幂等性。

最大努力通知
其实我觉得本地消息表也可以算最大努力,事务消息也可以算最大努力。
就本地消息表来说会有后台任务定时去查看未完成的消息,然后去调用对应的服务,当一个消息多次调用都失败的时候可以记录下然后引入人工,或者直接舍弃。这其实算是最大努力了。
事务消息也是一样,当半消息被commit了之后确实就是普通消息了,如果订阅者一直不消费或者消费不了则会一直重试,到最后进入死信队列。其实这也算最大努力。
所以最大努力通知其实只是表明了一种柔性事务的思想:我已经尽力我最大的努力想达成事务的最终一致了。
适用于对时间不敏感的业务,例如短信通知。

总结
如果拿 TCC 事务的处理流程与 2PC 两阶段提交做比较,2PC 通常都是在跨库的 DB 层面,而 TCC 则在应用层面的处理,需要通过业务逻辑来实现。这种分布式事务的实现方式的优势在于,可以让应用自己定义数据操作的粒度,使得降低锁冲突、提高吞吐量成为可能。而不足之处则在于对应用的侵入性非常强,业务逻辑的每个分支都需要实现 Try、Conrm、Cancel 三个操作。此外,其实现难度也比较大,需要按照网络状态、系统故障等不同的失败原因实现不同的回滚策略。

可靠消息最终一致性事务适合执行周期长且实时性要求不高的场景。引入消息机制后,同步的事务操作变为基于消息执行的异步操作, 避免了分布式事务中的同步阻塞操作的影响,并实现了两个服务的解耦。

可靠消息一致性,发起通知方需要保证将消息发出去,并且将消息发到接收通知方,消息的可靠性关键由发起通知方来保证。最大努力通知,发起通知方尽最大的努力将业务处理结果通知为接收通知方,但是可能消息接收不到,此时需要接 收通知方主动调用发起通知方的接口查询业务处理结果,通知的可靠性关键在接收通知方

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值