让你最快的了解 Alibaba Druid 数据库连接池

1. Alibaba Druid

Alibaba Druid 是一个高性能的数据库连接池和监控工具,广泛应用于 Java 项目中。它提供了强大的数据库连接管理、SQL 解析、防火墙以及监控功能。Druid 能有效提升数据库连接的稳定性和性能,并通过丰富的监控功能帮助开发者管理和优化数据库连接池。

阿里巴巴 Druid 是由阿里巴巴集团开发的开源项目,主要由阿里巴巴的工程师主导开发。

1.1 Alibaba Druid 背景

在阿里巴巴大规模的业务场景中,传统的数据库连接池无法满足高并发和高可用性的需求,尤其是对实时监控和 SQL 解析有着更高的要求。因此,阿里巴巴开发了 Druid 来应对这些挑战,旨在提供一个高性能、易于使用、可扩展的数据库连接管理工具。

作用和解决的问题

  • 数据库连接管理: 提供高效、稳定的连接池管理,减少数据库连接的开销。
  • SQL 解析与优化: 支持对 SQL 语句进行解析和优化,提升数据库查询性能。
  • 监控与诊断: 实时监控数据库连接池的状态,并提供详细的 SQL 执行分析,帮助快速诊断问题。
  • 安全性增强: 提供 SQL 防火墙功能,有效防止 SQL 注入攻击。

应用场景

  • 高并发的互联网应用: 例如电子商务、社交媒体、金融系统等需要处理大量数据库连接和查询的应用。
  • 数据密集型应用: 需要对大量数据进行实时查询和分析的场景,例如日志分析、实时数据处理等。
  • 企业级应用: 在大型企业中,Druid 可以用于监控和管理复杂的数据库连接环境,确保数据库的稳定性和安全性。

2. Alibaba Druid 学习

2.1 快速实践

主要以Spring项目事例,进行的快速实践

1. 添加 Maven 依赖

 

xml

代码解读

复制代码

<dependency>     <groupId>com.alibaba</groupId>     <artifactId>druid</artifactId>     <version>1.2.16</version> <!-- 请使用最新版本 --> </dependency>

2. 配置 Druid 数据源

在 Spring Boot 项目中,你可以在 application.properties 或 application.yml 文件中配置 Druid 数据源。

官方配置说明: github.com/alibaba/dru…

 

bash

代码解读

复制代码

spring:   datasource:     url: jdbc:mysql://localhost:3306/yourdb     username: root     password: root     type: com.alibaba.druid.pool.DruidDataSource     druid:       initialSize: 5       minIdle: 5       maxActive: 20       maxWait: 60000       timeBetweenEvictionRunsMillis: 60000       minEvictableIdleTimeMillis: 300000       validationQuery: SELECT 1 FROM DUAL       testWhileIdle: true       testOnBorrow: false       testOnReturn: false

Java 代码中直接配置数据源对象

 

scss

代码解读

复制代码

/**  * 初始化数据源  */ public DataSource initDataSource() throws SQLException {   // 创建Druid数据源对象     DruidDataSource datasource = new DruidDataSource();     // 基础连接信息     datasource.setDriverClassName("驱动类");     datasource.setUrl("url");     datasource.setUsername("username");     datasource.setPassword("password");     // 连接池连接信息     datasource.setInitialSize(5);     datasource.setMinIdle(5);     datasource.setMaxActive(20);     datasource.setMaxWait(60000);      datasource.setKeepAlive(true);     // 是否移除泄露的连接/超过时间限制是否回收。     datasource.setRemoveAbandoned(true);     // 泄露连接的定义时间(要超过最大事务的处理时间);单位为秒。这里配置为1小时     datasource.setRemoveAbandonedTimeout(3600);     return datasource; }

3.启用 Druid 监控

 

typescript

代码解读

复制代码

import com.alibaba.druid.support.http.StatViewServlet; import com.alibaba.druid.support.http.WebStatFilter; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.boot.web.servlet.ServletRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class DruidConfiguration {     @Bean     public ServletRegistrationBean<StatViewServlet> druidStatViewServlet() {         ServletRegistrationBean<StatViewServlet> servletRegistrationBean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");         // 添加初始化参数:loginUsername、loginPassword         servletRegistrationBean.addInitParameter("loginUsername", "admin");         servletRegistrationBean.addInitParameter("loginPassword", "admin");         return servletRegistrationBean;     }     @Bean     public FilterRegistrationBean<WebStatFilter> druidWebStatFilter() {         FilterRegistrationBean<WebStatFilter> filterRegistrationBean = new FilterRegistrationBean<>(new WebStatFilter());         // 添加过滤规则         filterRegistrationBean.addUrlPatterns("/*");         // 忽略资源         filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");         return filterRegistrationBean;     } }

4. 访问 Druid 监控页面

启动应用后,你可以通过访问 http://localhost:8080/druid 来查看 Druid 提供的监控页面。你将能够查看数据库连接池的实时状态、SQL 执行统计信息、慢查询等。

5. 测试 Druid 数据源

创建一个简单的 Spring 服务类,验证 Druid 数据源是否工作正常:

这边整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题

 需要全套面试笔记的【点击此处即可】免费获取

java

代码解读

复制代码

import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Service; @Service public class TestService {     @Autowired     private JdbcTemplate jdbcTemplate;     public void testQuery() {         String sql = "SELECT COUNT(*) FROM your_table";         Integer count = jdbcTemplate.queryForObject(sql, Integer.class);         System.out.println("Record count: " + count);     } }

通过以上步骤,你已经成功将 Alibaba Druid 集成到你的 Java 应用中,并启用了监控功能。Druid 连接池将帮助你更好地管理数据库连接,提供更高的性能和稳定性。监控页面则可以帮助你实时监控应用的数据库访问情况。

2.2. Alibaba Druid 详解

架构图

 

sql

代码解读

复制代码

+-------------------------------------------------+ | 应用层 | | Spring Boot, Java EE, Spring Framework | +-------------------------------------------------+ | | v v +-----------------+ +-----------------------+ | Druid 连接池 | | Druid SQL 解析器 | | - 连接管理 | | - SQL 解析 | | - 连接池优化 | | - SQL 防火墙 | +-----------------+ +-----------------------+ | | v v +-------------------------------------------------+ | Druid 监控模块 | | - Web 控制台 - 监控统计 | | - 连接池状态 - SQL 执行分析 | +-------------------------------------------------+

源码核心部分包结果图

 

arduino

代码解读

复制代码

com.alibaba.druid ├── pool // 数据库连接池管理 ├── proxy // 主要用于 Druid 数据库连接池的代理功能,特别是提供对数据库操作的拦截、监控、统计和日志记录的能力。 ├── sql // SQL 解析器与执行 ├── stat // 监控模块 ├── wall // SQL 防火墙 └── support // 支持类和实用工具

包说明

  • com.alibaba.druid.pool: 负责 Druid 的核心数据库连接池管理功能,包括连接池的创建、维护、以及连接的分配和回收等。
  • com.alibaba.druid.proxy: 主要用于 Druid 数据库连接池的代理功能,特别是提供对数据库操作的拦截、监控、统计和日志记录的能力。
  • com.alibaba.druid.sql: 包含 SQL 解析器和相关的 SQL 执行管理,提供了对 SQL 语句的解析、分析、优化等功能。
  • com.alibaba.druid.stat: 监控相关的包,负责收集和统计数据库连接池的状态和 SQL 执行情况,提供监控数据的收集与展示。
  • com.alibaba.druid.wall: 主要用于 SQL 防火墙功能,提供 SQL 语句的安全性检查,防止 SQL 注入等攻击。
  • com.alibaba.druid.support: 一些支持类和实用工具类,用于提供各种辅助功能,如日志记录、工具类等。

核心类

 

diff

代码解读

复制代码

+------------------------+ +------------------------+ | DruidDataSource | | DruidDriver | +------------------------+ +------------------------+ | | v v +------------------------+ +------------------------+ | DruidConnectionHolder | | DataSourceProxy | +------------------------+ +------------------------+ | v +------------------------+ | DruidPooledConnection | +------------------------+

image.png

image.png

DruidDataSource

DruidDataSource 主要用于通过线程池管理数据库连接池中连接的创建、维护和配置。同时支持监控和统计还有扩展过滤器这些。下面是该类的核心方法:

 

scss

代码解读

复制代码

// 数据源类 // 这类的不是完整的代码,完整的请参考源码,这里我们只是关注核心的方法 // 当 new DruidDataSource() 的时候并没有进行连接的初始化,真正开始初始化是在 getConnection 的时候 public class DruidDataSource extends DruidAbstractDataSource implements DruidDataSourceMBean, ManagedDataSource, Referenceable, Closeable, Cloneable, ConnectionPoolDataSource, MBeanRegistration {                 public DruidPooledConnection getConnection(long maxWaitMillis) throws SQLException {         // 调用来初始化数据源         init();     // 检查是否有注册的过滤器 (filters)     // 这些过滤器通常用于在连接创建过程中执行额外的逻辑,比如记录日志、监控、SQL注入防护等。         if (filters.size() > 0) {             // FilterChainImpl 是 Druid 的内部类,负责处理连接请求时应用过滤器链。              FilterChainImpl filterChain = new FilterChainImpl(this);             return filterChain.dataSource_connect(this, maxWaitMillis);         } else {             // 如果没有配置过滤器,则直接获取连接             return getConnectionDirect(maxWaitMillis);         }     }          // 初始化数据源     // 它负责对数据源进行全面的初始化配置。这个方法的核心功能包括数据源的基本配置、校验、连接池初始化、线程管理等     public void init() throws SQLException {     // ... 省略的部分基本上都是进行数据验证和初始化参数判断          // 初始化驱动     DruidDriver.getInstance();              boolean init = false;         try {       // 这里是初始化过滤器             initFromSPIServiceLoader();             // 解析驱动就是Class.forName()获取数据库驱动             resolveDriver();       // 数据校验             initCheck();       // 创建和启动用于管理连接池的线程和任务调度器             this.netTimeoutExecutor = new SynchronousExecutor();             // 连接池初始化             connections = new DruidConnectionHolder[maxActive];             evictConnections = new DruidConnectionHolder[maxActive];             keepAliveConnections = new DruidConnectionHolder[maxActive];             SQLException connectError = null;             if (createScheduler != null && asyncInit) {                 for (int i = 0; i < initialSize; ++i) {                     submitCreateTask(true);                 }             } else if (!asyncInit) {                 // 连接池初始化                 while (poolingCount < initialSize) {                     try {                         PhysicalConnectionInfo pyConnectInfo = createPhysicalConnection();                         DruidConnectionHolder holder = new DruidConnectionHolder(this, pyConnectInfo);                         connections[poolingCount++] = holder;                     } catch (SQLException ex) {                         LOG.error("init datasource error, url: " + this.getUrl(), ex);                         if (initExceptionThrow) {                             connectError = ex;                             break;                         } else {                             Thread.sleep(3000);                         }                     }                 }                 if (poolingCount > 0) {                     poolingPeak = poolingCount;                     poolingPeakTime = System.currentTimeMillis();                 }             }       // 启动记录日志信息的现场             createAndLogThread();             createAndStartCreatorThread();             createAndStartDestroyThread();             initedLatch.await();             init = true;             initedTime = new Date();             registerMbean();             if (connectError != null && poolingCount == 0) {                 throw connectError;             }             if (keepAlive) {                 // async fill to minIdle                 if (createScheduler != null) {                     for (int i = 0; i < minIdle; ++i) {                       // 创建线程实例                       // 此方法通常用于管理和调度需要异步执行的任务,例如连接池中的连接创建任务。通过动态扩展数组和调度任务,可以高效地管理连接创建过程。                       // 该方法会创建一个 CreateConnectionTask 实例,可以在一个线程中异步执行。它负责创建数据库连接并将其放入连接池中。                         submitCreateTask(true);                     }                 } else {                     this.emptySignal();                 }             }         } catch (Exception e) {          ...          } finally {           // 标记初始化完成             inited = true;             lock.unlock();         }     }      }

所以通过对以上 DruidDataSource 类的核心方法分析,我们可以知道,DruidDataSource 主要用于通过线程池管理数据库连接池中连接的创建、维护和配置。同时支持监控和统计还有扩展过滤器这些。

DruidConnectionHolder

DruidConnectionHolder 该类的基本上都是get和set方法。保存的属性则是数据库连接的基础属性。它的主要作用是封装并管理一个物理数据库连接 (java.sql.Connection) 以及与之关联的状态信息。在连接池的上下文中,DruidConnectionHolder 主要负责维护连接池中的连接,并提供对该连接的管理和使用的支持。

DruidPooledConnection

仔细观察会发现 DruidDataSource 中获取连接的方法 getConnection 返回的值就是 DruidPooledConnection ,DruidPooledConnection 是 Druid 数据库连接池中的一个重要类,其主要作用是封装一个从连接池中获取的数据库连接,并管理该连接的生命周期,提供安全、可靠的数据库连接操作。这个其实就是真正执行sql语句的类。

 

scss

代码解读

复制代码

//  public class DruidPooledConnection extends PoolableWrapper implements javax.sql.PooledConnection, Connection {          // 获取数据库连接     public Connection getConnection() {         if (!holder.underlyingAutoCommit) {           // 构建事务             createTransactionInfo();         }         return conn;     }          // 准备 PreparedStatement     // prepareStatement 还有很多重载的方法,基本逻辑都差不多     public PreparedStatement prepareStatement(String sql) throws SQLException {         // 检查状态         checkState();         // 使用预编译语句池 (PreparedStatement Pool), 这里采用了设计模式中的享元设计模式         // 尝试从池中获取对应 SQL 的 PreparedStatement 对象。如果池中已经存在一个与给定 SQL 语句相匹配的 PreparedStatement,那么将复用这个对象。         // 这种做法可以避免每次执行相同 SQL 时都重新创建 PreparedStatement 对象,从而提高性能。         PreparedStatementHolder stmtHolder = null;         PreparedStatementKey key = new PreparedStatementKey(sql, getCatalog(), MethodType.M1);         boolean poolPreparedStatements = holder.isPoolPreparedStatements();         if (poolPreparedStatements) {             stmtHolder = holder.getStatementPool().get(key);         }         // 初始化 PreparedStatement 封装为 DruidPooledPreparedStatement         if (stmtHolder == null) {             try {                 stmtHolder = new PreparedStatementHolder(key, conn.prepareStatement(sql));                 holder.getDataSource().incrementPreparedStatementCount();             } catch (SQLException ex) {                 handleException(ex, sql);             }         }         initStatement(stmtHolder);         DruidPooledPreparedStatement rtnVal = new DruidPooledPreparedStatement(this, stmtHolder);         // 添加到池中         holder.addTrace(rtnVal);         return rtnVal;     }          // 关闭数据库连接     public void close() throws SQLException {         if (this.disable) {             return;         }         DruidConnectionHolder holder = this.holder;         if (holder == null) {             if (dupCloseLogEnable) {                 LOG.error("dup close");             }             return;         }         DruidAbstractDataSource dataSource = holder.getDataSource();         boolean isSameThread = this.getOwnerThread() == Thread.currentThread();         if (!isSameThread) {             dataSource.setAsyncCloseConnectionEnable(true);         }         if (dataSource.isAsyncCloseConnectionEnable()) {             syncClose();             return;         }         if (!CLOSING_UPDATER.compareAndSet(this, 0, 1)) {             return;         }         try {           // 执行实践监听事件             for (ConnectionEventListener listener : holder.getConnectionEventListeners()) {                 listener.connectionClosed(new ConnectionEvent(this));             }       // 过滤器             List<Filter> filters = dataSource.getProxyFilters();             if (filters.size() > 0) {                 FilterChainImpl filterChain = new FilterChainImpl(dataSource);                 filterChain.dataSource_recycle(this);             } else {                 recycle();             }         } finally {             CLOSING_UPDATER.set(this, 0);         }         this.disable = true;     }    // 事务提交     public void commit() throws SQLException {       // 检查状态         checkState();         DruidAbstractDataSource dataSource = holder.getDataSource();         dataSource.incrementCommitCount();         try {             // 事务提交             conn.commit();         } catch (SQLException ex) {             handleException(ex, null);         } finally {             handleEndTransaction(dataSource, null);         }     }   // 事务回滚     public void rollback() throws SQLException {         if (transactionInfo == null) {             return;         }         if (holder == null) {             return;         }         DruidAbstractDataSource dataSource = holder.getDataSource();         dataSource.incrementRollbackCount();         try {             conn.rollback();         } catch (SQLException ex) {             handleException(ex, null);         } finally {             handleEndTransaction(dataSource, null);         }     } }

DruidDriver

DruidDriver 是 Druid 数据库连接池的一个核心类,内容也基本上是get和set,保存了连接驱动的一些信息,负责实现 JDBC Driver 的相关功能,并为 DruidDataSource 提供一些底层支持。它继承自 java.sql.Driver 接口,提供了连接池驱动的实现。

如果你仔细查看会发现 DruidDataSource 源码中获取连接的 init() 方法中,执行了 DruidDriver.getInstance(); 来进行初始化。

DataSourceProxy

在 Druid 的设计中,DataSourceProxy 是为 Druid 的监控、统计、扩展等功能提供的一个代理接口。DataSourceProxyImpl 它实现了 DataSourceProxy 接口,通过封装实际的 Connection 对象,使得 Druid 可以在 JDBC 操作的各个环节注入自定义的逻辑,例如 SQL 监控、执行统计、事务管理等。

以上小编觉得就是 Druid 最核心的部分,其他的部分 sql 解析,过滤器模块,监控和扩展,都是在以上的基础上,通过 proxy 包下的代理类来实现的。这里就不进行过多的讲解了。

Druid 中涉及的一些设计模式:

  1. 代理模式 (Proxy Pattern)

    • 使用场景:Druid 使用代理模式来实现数据库连接的增强功能,如 SQL 解析、监控、过滤等。
    • 示例:DataSourceProxy ,ConnectionProxy 和 ConnectionProxyImpl 类实现了对原始数据库连接的代理,允许在连接方法调用前后插入额外的逻辑(例如,执行 SQL 监控或过滤操作)。
  2. 工厂模式 (Factory Pattern)

    • 使用场景:Druid 使用工厂模式来创建和管理数据库连接池中的连接对象。
    • 示例:DruidDataSource 类中的连接创建方法,如 getConnection(),可能涉及工厂模式来生产新的数据库连接实例。
  3. 单例模式 (Singleton Pattern)

    • 使用场景:Druid 的配置类和某些管理类可能会使用单例模式,确保在整个应用程序中只存在一个实例。
    • 示例:DruidDriver 类通常是一个单例,用于管理数据库驱动的注册和管理。
  4. 装饰者模式 (Decorator Pattern)

    • 使用场景:Druid 使用装饰者模式来扩展连接池和 SQL 处理的功能,而无需修改现有的连接类。
    • 示例:Filter 和 FilterChain 类在连接对象上添加了额外的功能,如日志记录、统计和安全检查。
  5. 观察者模式 (Observer Pattern)

    • 使用场景:Druid 可能使用观察者模式来监控和报告数据库连接池的状态和性能。
    • 示例:在一些监控和统计功能中,Druid 可能会注册观察者来接收和处理事件,如连接池的状态变化和 SQL 执行情况。
  6. 策略模式 (Strategy Pattern)

    • 使用场景:Druid 使用策略模式来动态选择 SQL 处理和连接管理的策略。
    • 示例:不同的 Filter 实现可以视情况选择应用于 SQL 执行的不同策略,如 StatFilter 用于统计,WallFilter 用于安全检查。

补充:

HighAvailableDataSource

HighAvailableDataSource 是 Druid 数据库连接池中提供高可用性的组件,它的主要作用是通过多数据源配置,实现数据库的高可用性和自动故障转移(failover)。在分布式系统或者需要高可用性的应用场景中,单一数据源可能会成为系统的单点故障(SPOF)。HighAvailableDataSource 通过集成多个数据源来避免这一问题。

简单事例

 

java

代码解读

复制代码

import com.alibaba.druid.pool.DruidDataSource; import com.alibaba.druid.pool.DruidDataSourceStatManager; import com.alibaba.druid.pool.ha.HighAvailableDataSource; import java.sql.Connection; import java.sql.SQLException; public class DataSourceConfig {     public static void main(String[] args) throws SQLException {         // 创建 DruidDataSource 实例         DruidDataSource druidDataSource1 = new DruidDataSource();         druidDataSource1.setUrl("jdbc:mysql://localhost:3306/db1");         druidDataSource1.setUsername("user");         druidDataSource1.setPassword("password");                  DruidDataSource druidDataSource2 = new DruidDataSource();         druidDataSource2.setUrl("jdbc:mysql://localhost:3306/db2");         druidDataSource2.setUsername("user");         druidDataSource2.setPassword("password");                  // 创建 HighAvailableDataSource 实例         HighAvailableDataSource highAvailableDataSource = new HighAvailableDataSource();         highAvailableDataSource.addDataSource(druidDataSource1);         highAvailableDataSource.addDataSource(druidDataSource2);                  // 获取连接         Connection connection = highAvailableDataSource.getConnection();                  // 业务逻辑         System.out.println("Connection obtained: " + connection);                  // 关闭连接         connection.close();     } }

高可用性和故障转移

故障转移(Failover) : HighAvailableDataSource 能够在连接池中的数据源出现故障时自动切换到其他可用的数据源。这意味着,如果某个数据库实例不可用,连接池会自动使用其他可用的数据源来处理请求。

负载均衡(Load Balancing) : HighAvailableDataSource 可以在多个数据源之间进行负载均衡,以平衡请求负载。然而,这通常是基于简单的轮询或随机选择,并不涉及复杂的主从数据同步和切换策略。

2.3 总结

Druid 在数据库连接池和数据源管理领域有广泛的使用,特别是在 Java 生态系统中。其高性能和丰富的功能使其成为企业级应用的热门选择。

特性DruidHikariCPApache Commons DBCP
性能高性能,适合高并发场景极高性能,最低延迟性能一般,适合中低并发场景
配置配置选项多,功能丰富配置简单,易于上手配置选项多,成熟稳定
监控功能丰富的监控功能,包括 SQL 执行统计和慢查询分析监控功能有限监控功能较少
过滤器支持多种过滤器,功能灵活过滤器支持有限过滤器支持有限
社区支持活跃的社区,支持较好广泛的社区支持过滤器支持有限
兼容性支持多种数据库和 JDBC 驱动支持主流数据库和 JDBC 驱动支持多种 JDBC 驱动
功能扩展支持插件系统,功能扩展性强功能相对简单,扩展性有限功能扩展性一般
优点- 丰富的监控功能- 支持多种过滤器- 强大的配置选项- 插件系统支持- 活跃的社区- 极高性能- 配置简单- 设计上关注性能- 轻量级- 成熟且稳定- 配置选项多- 广泛的社区支持- 适合与老旧系统兼容
缺点- 配置和功能复杂- 低并发场景下显得庞大- 性能不如 HikariCP- 监控功能有限- 扩展性和插件支持较少- 功能简单- 性能一般- 监控和过滤器功能有限- 可能不如 Druid 和 HikariCP
适用场景需要丰富监控功能和灵活扩展的企业级应用,如 SQL 性能分析和慢查询监控对性能有极高要求且对功能需求较少的应用,如高并发的 web 应用对连接池有基本需求且需要稳定性的应用,如老旧系统的维护和兼容性需求
 

ruby

代码解读

复制代码

// 源码 https://github.com/alibaba/druid/ https://gitee.com/wenshao/druid // 文档 https://github.com/alibaba/druid/wiki // 配置参考说明 https://github.com/alibaba/druid/wiki/DruidDataSource%E9%85%8D%E7%BD%AE

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值