源码分析:@SchedulerLock分布式锁

定时任务分布式锁SchedulerLock SchedulerLock 作用:确保任务在同一时刻最多执行一次。如果一个任务正在一个节点上执行,则它将获得一个锁,该锁将阻止从另一个节点(或线程)执行同一任务。如果一个任务已经在一个节点上执行,则在其他节点上的执行不会等待,只需跳过它即可。SchedulerLock 主要通过分布式锁实现,可以使用:数据库锁(基于数据库行锁或唯一约束)Redis 分布式锁(利用 SET NX EX)Zookeeper 分布式锁(基于临时节点)基于 Quartz/ShedLock 的框架实现。 阅读详情
AI摘要

本文围绕@EnableDiscoveryClient,对@SchedulerLock分布式锁进行源码分析。介绍了其两种方案(Mysql和Redis)的底层原理,分析了@EnableSchedulerLock修饰启动类及资源加载情况,还阐述了分布式定时任务原理,涉及定时锁配置类、方法代理锁配置类,以及RedisLockProvider实现类。

24e3093380daa1eb2f3be5a525f2cfca.jpeg

背景

@SchedulerLock源码分析思路总结:

一个中心(围绕@EnableDiscoveryClient开展)、两个基本点(两个方法级别的注解:@SchedulerLock和@Scheduled)。

我们都知道@SchedulerLock有两种分布式锁的方案:一个是Mysql,一个是Redis,分布式锁的底层原理不难:

  • 如果是采用Mysql,则通过磁盘记录的一张表,用于存储分布式锁信息

  • 如果是采用Redis,则通过内存记录的KV值,用于存储分布式锁信息

源码分析:@EnableSchedulerLock

@EnableSchedulerLock:修饰启动类

@EnableFeignClients
@SpringBootApplication
@EnableDiscoveryClient
@EnableScheduling
@EnableSchedulerLock(defaultLockAtLeastFor = "10s", defaultLockAtMostFor = "60s")
public class AuditServerApplication {
    public static void main(String[] args) {
        CodeSpringApplication.run(AuditServerApplication.class, args);
    }
}

@Import导入资源加载类

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(SchedulerLockConfigurationSelector.class)
public @interface EnableSchedulerLock {
    InterceptMode interceptMode() default InterceptMode.PROXY_METHOD;
    String defaultLockAtMostFor();
    String defaultLockAtLeastFor() default "PT0S";
    AdviceMode mode() default AdviceMode.PROXY;
    boolean proxyTargetClass() default false;
    int order() default Ordered.LOWEST_PRECEDENCE;
}

代码分析:

  • @Import注解(将某个类注入到IOC容器中,即将依赖到的资源导入到当前容器中)

SchedulerLockConfigurationSelector:资源加载类

public class SchedulerLockConfigurationSelector implements ImportSelector {


    @Override
    @NonNull
    public String[] selectImports(@NonNull AnnotationMetadata metadata) {
        AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(EnableSchedulerLock.class.getName(), false));
        InterceptMode mode = attributes.getEnum("interceptMode");
        if (mode == PROXY_METHOD) {
            return new String[]{AutoProxyRegistrar.class.getName(), 
                LockConfigurationExtractorConfiguration.class.getName(), 
                MethodProxyLockConfiguration.class.getName()};
        } else if (mode == PROXY_SCHEDULER) {
            return new String[]{AutoProxyRegistrar.class.getName(), 
                LockConfigurationExtractorConfiguration.class.getName(), 
                SchedulerProxyLockConfiguration.class.getName(), 
                RegisterDefaultTaskSchedulerPostProcessor.class.getName()};
        } else {
            throw new UnsupportedOperationException("Unknown mode " + mode);
        }
    }
}

关注点:

  • ImportSelector接口(ImportSelector接口是至spring中导入外部配置的核心接口,在SpringBoot的自动化配置和@EnableXXX(功能性注解)都有它的存在),对于实现了selectImports方法,会返回需要被引入的类名集合(selectImports()方法的返回值就是我们向Spring容器中导入的类的全类名)。

  • SchedulerLockConfigurationSelector 会进行以下资源类的导入工作

    • AutoProxyRegistrar

    • LockConfigurationExtractorConfiguration

    • MethodProxyLockConfiguration

    • SchedulerProxyLockConfiguration

    • RegisterDefaultTaskSchedulerPostProcessor

3、分布式定时任务原理

默认是分布式锁是对方法级别进行加锁,最典型的就是分布式定时任务了。

因此关注 :LockConfigurationExtractorConfiguration定时配置类

和 MethodProxyLockConfiguration方法代理锁配置类。

3.1 LockConfigurationExtractorConfiguration:定时锁配置类

@Configuration
class LockConfigurationExtractorConfiguration extends AbstractLockConfiguration implements EmbeddedValueResolverAware {
    private final StringToDurationConverter durationConverter = StringToDurationConverter.INSTANCE;


    private StringValueResolver resolver;


    @Bean
    ExtendedLockConfigurationExtractor lockConfigurationExtractor() {
        // 使用了
        return new SpringLockConfigurationExtractor(
        defaultLockAtMostForDuration(),
        defaultLockAtLeastForDuration(), 
        resolver, 
        durationConverter);
    }
    // 。。。getter/setter
}

代码分析:

  • 初始化了一个bean:ExtendedLockConfigurationExtractor。后面的方法代理锁配置类,会依赖这个bean。

  • 具体是初始化了 SpringLockConfigurationExtractor 是锁配置提取器,传入了两个属性,是的,这就是我们修饰了启动类时,全局配置注解@EnableSchedulerLock写入的属性。

    • defaultLockAtLeastFor:最短锁时间

    • defaultLockAtMostFor:最长锁时间

3.2  MethodProxyLockConfiguration:方法代理锁配置类

MethodProxyLockConfiguration的源码如下:

@Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
class MethodProxyLockConfiguration extends AbstractLockConfiguration {


    @Bean
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    MethodProxyScheduledLockAdvisor proxyScheduledLockAopBeanPostProcessor(
        @Lazy LockProvider lockProvider,
        @Lazy ExtendedLockConfigurationExtractor lockConfigurationExtractor
    ) {
        MethodProxyScheduledLockAdvisor advisor = 
        new MethodProxyScheduledLockAdvisor(
            lockConfigurationExtractor,
            new DefaultLockingTaskExecutor(lockProvider)
        );
        advisor.setOrder(getOrder());
        return advisor;


    }


}

关注点是 MethodProxyScheduledLockAdvisor  的装配初始化。

  • 使用到LockProvider lockProvider:是懒加载的bean,但它不可以为空,因为 new DefaultLockingTaskExecutor(lockProvider) 代码片段里头,是会抛异常的。

  • 使用到ExtendedLockConfigurationExtractor:这个就是上一步已经初始化好的SpringLockConfigurationExtractor实例。

那么问题来了,这里使用了@Lazy懒加载,那么LockProvider是什么时候,什么地点被加载到Spring容器的呢?答案是,要我们使用方自定义了。

3.3 RedisLockProvider:分布式锁实现类

这里我们使用了Redis方案,那么需要初始化一个RedisLockProvider bean,如下:

@Configuration
public class ApplicationConfig {
    @Bean
    public LockProvider lockProvider(
        @Autowired LettuceConnectionFactory connectionFactory) {
        return new RedisLockProvider(connectionFactory, applicationName);
    }
}

代码分析:

  • RedisLockProvider的初始化:注入了LettuceConnectionFactory,它是连接工厂,它怎么来的呢?

    • LettuceConnectionFactory实现了InitializingBean接口,将在容器正常加载。

    • new了一个bean RedisLockProvider,并注入connectionFactory 和 名称(环境标识,自定义)

 RedisLockProvider构造器
public RedisLockProvider(@NonNull RedisConnectionFactory redisConn, @NonNull String environment) {
    this(redisConn, environment, "job-lock");
}
public RedisLockProvider(@NonNull RedisConnectionFactory redisConn, @NonNull String environment, @NonNull String keyPrefix) {
    this(new StringRedisTemplate(redisConn), environment, keyPrefix);
}
public RedisLockProvider(@NonNull StringRedisTemplate redisTemplate, @NonNull String environment, @NonNull String keyPrefix) {
    this.redisTemplate = redisTemplate;
    this.environment = environment;
    this.keyPrefix = keyPrefix;
}

代码分析:StringRedisTemplate的初始化。

  • new了一个bean StringRedisTemplate,并注入了名称 和keyPrefix

RedisLockProvider#buildKey(String lockName);
String buildKey(String lockName) {
    return String.format("%s:%s:%s", this.keyPrefix, this.environment, lockName);
}
  • keyPrefix:默认是job-lock

  • environment:则是new RedisLockProvider时,传入的applicationName

  • lockName:则是由 @SchedulerLock 注解定义的name属性决定

那么 @SchedulerLock注解什么时候用到了呢?那便是我们期望分布式锁生效的地方,某个类的某个方法了。

@SchedulerLock和@Scheduled:修饰定时任务的方法

回想一下,还记得业务侧使用分布式锁时,是标注在方法上面,如下示例所示:

@Scheduled(cron = "0 0 10 * * ?", zone = "GMT+08") // 线上用,每天早上10点执行
@SchedulerLock(name = TASK_NAME, lockAtLeastFor = "120s", lockAtMostFor = "360s")
public void execute() {}

@SchedulerLock注解配置不当导致数据重复问题解决方案 在使用Spring Boot的分布式定时任务框架(如ShedLock)时,你是否遇到过明明加了@SchedulerLock注解,却依然出现任务重复执行、数据重复生成的诡异问题?本文将从真实案例出发,深入剖析lockAtLeastFor与lockAtMostFor的配置陷阱,并提供一整套解决方案,彻底告别数据重复! 阅读详情

相关推荐

SqlSugar避坑指南:C#操作MySQL时你可能会忽略的5个细节

本文分享了使用C#和SqlSugar框架操作MySQL数据库时的实战避坑指南。针对连接配置、实体映射、查询性能、事务处理等关键环节,深入剖析了5个容易被忽略但至关重要的细节,帮助开发者提升项目稳定性和执行效率,避免常见陷阱。

delta的博客 276

@SchedulerLock详解

SpringBoot之数据库锁-@SchedulerLock 分布式锁ShedLock确保计划的任务最多同时执行一次。如果一个任务正在一个节点上执行,它会获得一个锁,该锁将阻止从另一个节点(或线程)执行同一任务。请注意,如果一个任务已经在一个节点上执行,则在其他节点上的执行不会等待,只会跳过它。就是当第一个微服务执行定时任务的时候,会定时任务进行锁操作,然后其他的定时任务就不会再执行,锁操作有一定的时长,超过这个时长以后,再一次,所有的定时任务进行争抢下一个定时任务的执行权限,如此循环。保证了即使是其中的一

ヾ凉秋的博客 2万+

数据分析(二):学生成绩预测分析报告

表1 特征变量描述变量描述school学生的学校sex学生的性别age学生的年龄address学生的家庭住址类型famsize家庭规模Pstatus父母同居状况Medu母亲的教育Fedu父亲的教育Mjob母亲的工作Fjob父亲的工作reason选择学校的理由guardian学生监护人traveltime从家到学校的时间studytime每周学习时间failures过去的类失败次数schoolsup额外的教育支持famsup家庭教育支持。

m0_73963149的博客 4417

关于@SchedulerLock的属性含义

参考博客:https://blog.youkuaiyun.com/qq_37779271/article/details/84581918 1.注解属性 @SchedulerLock所含属性name ,lockAtMostFor ,lockAtLeastFor name :定时器名称,每次只会执行一个,name类似分组 lockAtMostFor:该属性指定在执行节点死亡时应保留锁的时间。这只是一个后备,...

luo_yu_1106的博客 6429

手把手教你搭建一个属于自己的网站-适合零基础小白,文末附网站模板

手把手教你搭建一个属于自己的网站-适合零基础小白写在前面搭建需求功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注脚注释也是必不可少的KaTeX数学公式新的甘特图功能,丰富你的文章UML 图表FLowchart流程图导出与导入导出导入 写在前面 前段时间休息想着搭一个属于自己的网站,几经尝试终于成功了,网上其实有很多教程,但很多,希望这篇文章能够帮助到

qq_46788925的博客 1万+

Fluent 2.5D动网格重构实战:涡旋压缩机复杂运动模拟指南

本文详细解析了Fluent 2.5D动网格重构技术在涡旋压缩机复杂运动模拟中的应用。通过对比传统方法,2.5D重构显著提升了计算效率和稳定性,特别适用于柱体类几何的网格处理。文章提供了从基础配置到参数优化的实战指南,包括关键设置步骤、常见问题排查及性能优化策略,帮助工程师有效解决动网格重构中的负体积和网格畸变问题。

weixin_28455801的博客 549

通过@SchedulerLock使集群环境下同一定时任务只执行一次的简单示例

在一些业务场景下,我们只希望他执行一次,此时可通过@SchedulerLock来对该定时任务进行加锁从而控制定时任务执行次数(也可通过Redis实现分布式锁,本文不做拓展)。,若属性lockAtLeastFor时间大于CRON表达式所表示的任务执行时间间隔,则任务实际执行时间间隔是lockAtLeastFor的值(对应此处的定时任务,会20s输出一次而不是5s)。ShedLock使用外部存储,如Mongo,JDBC,Redis,Hazelcast,ZooKeeper等,本文采用JDBC。

ergo_9527的博客 4266

affymatrix探针转换 | GPL5175探针对应的基因转换

一般情况下,有一些比较成熟的对应平台的注释数据集的R包。但是这个注释平台,我在Bioconductor上找了一圈都没有找到。只能通过最原始也最可靠的方法,从GEO数据集上去下载这部分的注释文件。以下展示全过程。 我要检查的数据平台是:GPL5157。 数据集的注释集链接为:https://www.ncbi.nlm.nih.gov/geo/query/acc.cgi?acc=GPL5175 这个注释数据集比较难弄的地方在于,它的基因集注释的不太好。没有比较直接的可供提取的genesample的信息;在ge.

weixin_40640700的博客 7124

2024年Java开发者必备技能与最佳实践

Java作为企业级开发的主流语言,其技术生态持续演进。云原生与中间件深度整合成为趋势,Spring Boot与Redis、Kafka等组件的组合方案广泛应用。性能优化需求显著增长,涉及JVM调优、MySQL索引优化等关键技术。工作流引擎如Flowable/Activiti在审批场景快速普及,AI工程化落地也推动Spring AI等框架兴起。开发者需要掌握虚拟线程、结构化并发等Java新特性,同时理解分布式事务、接口幂等等微服务设计模式。通过防御性编程、日志规范等工程实践,可有效提升系统稳定性。从初级到专家,

weixin_33738982的博客 339

告别配置噩梦:用Vcpkg三行命令为Easy3D自动安装Boost、CGAL和Qt(VS2022实测)

本文详细介绍了如何使用Vcpkg工具在VS2022环境下为Easy3D项目一键安装Boost、CGAL和Qt等依赖库,解决传统手动配置的复杂性问题。通过三行简单命令即可自动完成下载、编译和路径配置,大幅提升开发效率,特别适合C++开发者快速搭建3D图形开发环境。

weixin_42525582的博客 210

①电子产品拆解分析-暖手宝

电子产品拆解分析-暖手宝 ①按键三档调温暖手; ②长按进入氛围呼吸灯; ③安卓接口充电以及支持USB输出;

a6662580的博客 5295

SchedulerLock 分布式锁 原理

背景 使用@SchedulerLock可以实现比较轻量级的简单的定时任务,也可以实现分布式锁,那具体的原理是啥? 一探究竟 不熟悉的同学可以先看一下用法: https://www.baeldung.com/shedlock-spring 大概就是定义一个数据表 注意这里的name是primary key,是不可重复的。利用了primary key的这个特性,实现了锁的抢占。细节下面会介绍 然后加注解实现: 看源码 直接从注解出发,看源码 在注解所在的包内发现一个DefaultLockManager 一看

stevewong的专栏 6396

ParaView: 从 2 或 3 个标量创建向量

ParaView: 从 2 或 3 个标量创建向量

希望我的博客,能帮上你解决学习中工作中所遇到的问题 585

常用的注解

常用的注解

qq_31217383的博客 1270

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

@SchedulerLock注解使用

name任务唯一标识。最重要的参数。同一个name的任务,同一个时刻,多个线程只会有一个线程获取到锁。其他没有获取到锁的线程会跳过,不会阻塞等待。持有锁的最短时间。这个主要是防止不同节点时间存在误差,比如有个任务是0点执行,节点1的时间是准的,在0点执行花了30秒执行完成。节点2的时间比节点1慢了1分钟,那么节点2到0点的时候,又执行了一次任务。这个参数可以设置10秒、30秒等。保证不同节点的时间戳不会出现。持有锁的最长时间。主要是为了防止死锁。

theflowerofac^MQLYES的专栏 1395

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

实现分布式锁SchedulerLock

Spring 提供了一种简单的方法来实现用于调度作业的 API。在我们部署应用程序的多个实例之前,它运行良好。默认情况下,Spring 无法处理多个实例上的调度程序同步。它在每个节点上同时执行作业。

wang20010104的博客 2435

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

SchedulerLock使用

如果一个任务正在一个节点上执行,它会获得一个锁,该锁将阻止从另一个节点(或线程)执行同一任务。请注意,如果一个任务已经在一个节点上执行,则在其他节点上的执行不会等待,只会跳过它。就是当第一个微服务执行定时任务的时候,会定时任务进行锁操作,然后其他的定时任务就不会再执行,锁操作有一定的时长,超过这个时长以后,再一次,所有的定时任务进行争抢下一个定时任务的执行权限,如此循环。保证了即使是其中的一个定时任务挂掉了,到一定的时间以后,锁也会释放,其他的定时任务依旧会进行执行权的争夺,执行定时任务。

lhosdp的博客 1425

SchedulerLock LockProvider参数配置表,列,大小写等 分布式锁 定时任务锁 学习总结

如果未指定此选项,则将使用来自应用程序服务器的时钟(应用程序服务器上的时钟可能不同步,从而导致各种锁定问题)。如果是分布式任务,即多个相同的应用执行定时任务,那么为了防止重复执行可以使用其他分布式锁做内部判断或其他形式的锁机制来防止重复执行。JdbcTemplate 方式,即通过配置一张数据库表 为此提供锁的服务,官方提供的表建表语句,如果需要其他的可以额外加入。lockprovider 依赖,下面会说到,但根据选择不同的锁方式,依赖其实也不同,如果是其他方式请改为其他依赖。如果任务花费的时间超过。

weixin_44131922的博客 1634

@SchedulerLock处理Spring Task在分布式环境下的重复执行问题

@SchedulerLock处理Spring Task在分布式环境下的重复执行问题

llg___的博客 1477

@SchedulerLock基于 redis 配置

SchedulerLock的作用,确保任务在同一时刻最多执行一次。如果一个任务正在一个节点上执行,则它将获得一个锁,该锁将阻止从另一个节点(或线程)执行同一任务。如果一个任务已经在一个节点上执行,则在其他节点上的执行不会等待,只需跳过它即可。ShedLock使用Mongo,JDBC数据库,Redis,Hazelcast,ZooKeeper或其他外部存储进行协调,即通过外部存储来实现锁机制;ScheduledLockConfig 配置。redisson 单例配置。设置定时任务(注解)

qq_36428598的博客 1248

springboot 极简微服务之分布式定时任务 Scheduled 与 SchedulerLock, 配置中心动态cron定时任务

最近有个小项目, 生产是要双活的, 硬件资源不足,整套微服务满足不了,因此阉割了只用 nacos 和 springboot ,在定时任务上要满足双活, 不能并发, 因此技术选型上就用了 Scheduled 与 SchedulerLock;前提条件, SchedulerLock 的依赖jar版本号。分布式定时任务数据库锁及多线程配置。

zeng_0407的博客 2836

SpringBoot 集成 ShedLock 分布式锁

SpringBoot 集成 ShedLock 分布式锁 一:ShedLock 简介 ShedLock是一个在分布式环境中使用的定时任务框架,用于解决在分布式环境中的多个实例的相同定时任务在同一时间点重复执行的问题。ShedLock确保计划的任务最多同时执行一次。如果一个任务正在一个节点上执行,它会获得一个锁,该锁将阻止从另一个节点(或线程)执行同一任务。请注意,如果一个任务已经在一个节点上执行,则在其他节点上的执行不会等待,只是将其跳过。。简单来说,ShedLock本身只做一件事情:保证一个任务最多同时执行

鱼的博客 1078

Spring Cloud Alibaba -- 分布式定时任务解决方案(轻量级、快速构建)(ShedLock 、@SchedulerLock )

Spring Cloud Alibaba -- 分布式定时任务解决方案(轻量级、快速构建)(ShedLock 、@SchedulerLock )

weixin_44111939的博客 3294

@SchedulerLock 防止分布式环境下定时任务并发执行

在一个有多个服务实例的分布式系统中,如果你用@Scheduled来定义定时任务,所有实例都会执行这个任务。ShedLock的目标是只让一个实例在某一时刻执行这个定时任务。

一千个读者就有一千个哈姆雷特 1130

定时任务锁ShedLock的原理

目录前言总结 前言 本文以jdbc数据库锁为例,分析ShedLock的执行原理。 1、@EnableSchedulerLock 开启定时任务锁,添加了该注解,才能使用ShedLock锁的功能,该类通过@Import导入了SchedulerLockConfigurationSelector @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Import(SchedulerLockConfigurationSelector.c..

码易的博客 4192

Java中@Scheduled定时任务详解

Cron表达式是一个字符串,字符串以5或6个空格隔开,分为6或7个域,每一个域代表一个含义,Cron有如下两种语法格式: (1) Seconds Minutes Hours DayofMonth Month DayofWeek Year (2)Seconds Minutes Hours DayofMonth Month DayofWeek 一、结构 corn从左到右(用空格隔开): 秒 分 小时 月份中的日期 月份 星期中的日期 年份 二、各字段的含义 注意事项: 每一个域都使用数字,但还可以出现如下特殊

筱筱攻程师的博客 1万+
上一篇: SpringCloud源码:服务端分析(一)- 对Client事件的处理
下一篇: 深刻理解Redis集群(下):Redis 哨兵(Sentinel)模式
后台技术汇
博客等级 码龄11年 670粉丝 531原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

后台技术汇

对你的帮助,是对我的最好鼓励。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值