(13)Reactor的backpressure策略——响应式Spring的道法术器

【webFlux】理解onBackpressureBuffer将数据缓存到一个缓冲区中,避免数据积压 上表中,DROP_LATEST 和 DROP_OLDEST 是用于缓冲区已满时的数据处理方式,前者会丢弃最新的数据,后者会丢弃最早的数据;在 Reactive 编程模型中,由于数据流传递的速度可能远远快于数据处理的速度,因此可能会出现数据积压的情况,这就是所谓的背压问题。例如,可以使用 onBackpressureBuffer 操作符,将内部缓存区的大小限制为一个固定的值,当缓存区满时,会自动丢弃最早的数据。例如,如果数据量很大,但是处理能力较弱,可以适当增大缓冲区的大小,以便缓存更多的数据。 阅读详情

本系列其他文章见:《响应式Spring的道法术器》。
前情提要:响应式流 | Reactor3快速上手 | 深入理解响应式流规范 | Reactor3自定义数据流

2.3 不同的回压策略

许多地方也叫做“背压”、“负压”,我在《Reactor参考文档》中是翻译为“背压”的,后来在看到有“回压”的翻译,忽然感觉从文字上似乎更加符合。

这一节讨论回压的问题,有两个前提:

  1. 发布者与订阅者不在同一个线程中,因为在同一个线程中的话,通常使用传统的逻辑就可以,不需要进行回压处理;
  2. 发布者发出数据的速度高于订阅者处理数据的速度,也就是处于“PUSH”状态下,如果相反,那就是“PUll”状态,不需要处理回压。

本节测试源码

2.3.1 回压策略

回压的处理有以下几种策略:

  1. ERROR: 当下游跟不上节奏的时候发出一个错误信号。
  2. DROP:当下游没有准备好接收新的元素的时候抛弃这个元素。
  3. LATEST:让下游只得到上游最新的元素。
  4. BUFFER:缓存下游没有来得及处理的元素(如果缓存不限大小的可能导致OutOfMemoryError)。

这几种策略定义在枚举类型OverflowStrategy中,不过还有一个IGNORE类型,即完全忽略下游背压请求,这可能会在下游队列积满的时候导致 IllegalStateException。

2.3.2 使用create声明回压策略

上一节中,用于生成数据流的方法create和push可以用于异步的场景,而且它们也支持回压,我们可以通过提供一个 OverflowStrategy 来定义背压行为。方法签名:

    public static <T> Flux<T> create(Consumer<? super FluxSink<T>> emitter, OverflowStrategy backpressure) 

默认(没有第二个参数的方法)是缓存策略的,我们来试一下别的策略,比如DROP的策略。

我们继续使用2.2节的那个测试例子,下边是用create创建的“快的发布者”,不过方便起见拆放到两个私有方法里供调用:

    public class Test_2_3 {
        /**
         * 使用create方法生成“快的发布者”。
         * @param strategy 回压策略
         * @return  Flux
         */
        private Flux<MyEventSource.MyEvent> createFlux(FluxSink.OverflowStrategy strategy) {
            return Flux.create(sink -> eventSource.register(new MyEventListener() {
                @Override
                public void onNewEvent(MyEventSource.MyEvent event) {
                    System.out.println("publish >>> " + event.getMessage());
                    sink.next(event);
                }
    
                @Override
                public void onEventStopped() {
                    sink.complete();
                }
            }), strategy);  // 1
        }
        /**
         * 生成MyEvent。
         * @param count 生成MyEvent的个数。
         * @param millis 每个MyEvent之间的时间间隔。
         */
        private void generateEvent(int times, int millis) {
            // 循环生成MyEvent,每个MyEvent间隔millis毫秒
            for (int i = 0; i < times; i++) {
                try {
                    TimeUnit.MILLISECONDS.sleep(millis);
                } catch (InterruptedException e) {
                }
                eventSource.newEvent(new MyEventSource.MyEvent(new Date(), "Event-" + i));
            }
            eventSource.eventStopped();
        }
    }

有了“快的发布者”,下面是“慢的订阅者”,以及一些测试准备工作:

    public class Test_2_3 {
        private final int EVENT_DURATION   = 10;    // 生成的事件间隔时间,单位毫秒
        private final int EVENT_COUNT      = 20;    // 生成的事件个数
        private final int PROCESS_DURATION = 30;    // 订阅者处理每个元素的时间,单位毫秒
    
        private Flux<MyEventSource.MyEvent> fastPublisher;
        private SlowSubscriber slowSubscriber;
        private MyEventSource eventSource;
        private CountDownLatch countDownLatch;
    
        /**
         * 准备工作。
         */
        @Before
        public void setup() {
            countDownLatch = new CountDownLatch(1);
            slowSubscriber = new SlowSubscriber();
            eventSource = new MyEventSource();
        }
    
        /**
         * 触发订阅,使用CountDownLatch等待订阅者处理完成。
         */
        @After
        public void subscribe() throws InterruptedException {
            fastPublisher.subscribe(slowSubscriber);
            generateEvent(EVENT_COUNT, EVENT_DURATION);
            countDownLatch.await(1, TimeUnit.MINUTES);
        }
        
        /**
         * 内部类,“慢的订阅者”。
         */
        class SlowSubscriber extends BaseSubscriber<MyEventSource.MyEvent> {
    
            @Override
            protected void hookOnSubscribe(Subscription subscription) {
                request(1);     // 订阅时请求1个数据
            }
    
            @Override
            protected void hookOnNext(MyEventSource.MyEvent event) {
                System.out.println("                      receive <<< " + event.getMessage());
                try {
                    TimeUnit.MILLISECONDS.sleep(PROCESS_DURATION);
                } catch (InterruptedException e) {
                }
                request(1);     // 每处理完1个数据,就再请求1个
            }
    
            @Override
            protected void hookOnError(Throwable throwable) {
                System.err.println("                      receive <<< " + throwable);
            }
    
            @Override
            protected void hookOnComplete() {
                countDownLatch.countDown();
            }
        }
    }

下面是测试方法:

    /**
     * 测试create方法的不同OverflowStrategy的效果。
     */
    @Test
    public void testCreateBackPressureStratety() {
        fastPublisher =
                createFlux(FluxSink.OverflowStrategy.BUFFER)    // 1
                        .doOnRequest(n -> System.out.println("         ===  request: " + n + " ==="))    // 2
                        .publishOn(Schedulers.newSingle("newSingle"), 1);   // 3
    }
  1. 调整不同的策略(BUFFER/DROP/LATEST/ERROR/IGNORE)观察效果,create方法默认为BUFFER;
  2. 打印出每次的请求(也就是后边.publishOn的请求);
  3. 使用publishOn让后续的操作符和订阅者运行在一个单独的名为newSingle的线程上,第二个参数1是预取个数,也就是.publishOn作为订阅者每次向上游request的个数,默认为256,所以一定程度上也起到了缓存的效果,为了测试,设置为1。

通常情况下,发布者于订阅者并不在同一个线程上,这里使用publishOn来模拟这种情况。

BUFFER策略的输出如下(来不及处理的数据会缓存下来,这是通常情况下的默认策略):

         ===  request: 1 ===
publish >>> Event-0
                      receive <<< Event-0
publish >>> Event-1
publish >>> Event-2
         ===  request: 1 ===
publish >>> Event-3
                      receive <<< Event-1
publish >>> Event-4
publish >>> Event-5
publish >>> Event-6
         ===  request: 1 ===
                      receive <<< Event-2
publish >>> Event-7
publish >>> Event-8
...

DROP策略的输出如下(有新数据就绪的时候,看是否有request,有的话就发出,没有就丢弃):

         ===  request: 1 ===
publish >>> Event-0
                      receive <<< Event-0
publish >>> Event-1
publish >>> Event-2
publish >>> Event-3
         ===  request: 1 ===
publish >>> Event-4
                      receive <<< Event-4
publish >>> Event-5
publish >>> Event-6
publish >>> Event-7
         ===  request: 1 ===
publish >>> Event-8
                      receive <<< Event-8
...

可以看到,第1/2/3/5/6/7/…的数据被丢弃了,当有request之后的数据会被发出。调整一下publishOn方法的第二个参数(预取个数)为2,输出如下:

         ===  request: 2 ===
publish >>> Event-0
                      receive <<< Event-0
publish >>> Event-1
publish >>> Event-2
publish >>> Event-3
                      receive <<< Event-1
publish >>> Event-4
publish >>> Event-5
publish >>> Event-6
         ===  request: 2 ===
publish >>> Event-7
                      receive <<< Event-7
publish >>> Event-8
publish >>> Event-9
publish >>> Event-10
                      receive <<< Event-8
publish >>> Event-11
publish >>> Event-12

可见,每次request(请求2个数据)之后的2个数据发出,更多就绪的数据由于没有request就丢弃了。

LATEST的输出如下(request到来的时候,将最新的数据发出):

         ===  request: 1 ===
publish >>> Event-0
                      receive <<< Event-0
publish >>> Event-1
publish >>> Event-2
publish >>> Event-3
         ===  request: 1 ===
                      receive <<< Event-3
publish >>> Event-4
publish >>> Event-5
         ===  request: 1 ===
                      receive <<< Event-5
publish >>> Event-6
publish >>> Event-7
publish >>> Event-8
         ===  request: 1 ===
                      receive <<< Event-8

ERROR的输出如下(当订阅者来不及处理时候发出一个错误信号):

         ===  request: 1 ===
publish >>> Event-0
                      receive <<< Event-0
publish >>> Event-1
publish >>> Event-2
         ===  request: 1 ===
                      receive <<< reactor.core.Exceptions$OverflowException: The receiver is overrun by more signals than expected (bounded queue...)

IGNORE的输出如下:

...
         ===  request: 2 ===
                      receive <<< Event-10
                      receive <<< Event-11
         ===  request: 2 ===
                      receive <<< Event-12
                      receive <<< reactor.core.Exceptions$OverflowException: Queue is full: Reactive Streams source doesn't respect backpressure

2.3.3 调整回压策略的操作符

Reactor提供了响应的onBackpressureXxx操作符,调整回压策略。测试方法如下:

    /**
     * 测试不同的onBackpressureXxx方法的效果。
     */
    @Test
    public void testOnBackPressureXxx() {
        fastPublisher = createFlux(FluxSink.OverflowStrategy.BUFFER)
                .onBackpressureBuffer()     // BUFFER
//                .onBackpressureDrop()     // DROP
//                .onBackpressureLatest()   // LATEST
//                .onBackpressureError()    // ERROR
                .doOnRequest(n -> System.out.println("         ===  request: " + n + " ==="))
                .publishOn(Schedulers.newSingle("newSingle"), 1);
    }

通过打开某一个操作符的注释可以观察输出。这里就不贴输出内容了,Reactor文档的示意图更加直观:

onBackpressureBuffer,对于来自其下游的request采取“缓存”策略。

onBackpressureDrop,元素就绪时,根据下游是否有未满足的request来判断是否发出当前元素。

onBackpressureLatest,当有新的request到来的时候,将最新的元素发出。

onBackpressureError,当有多余元素就绪时,发出错误信号。

真是一图胜千言啊,上边的这些图片都是来自Reactor官方文档。

当进行异步编程时,通常会面临相互协作的各个组件不在同一个线程的情况,比如一个生产者不断生成消息,而一个消费者不断处理这些产生的消息,二者通常不在一个线程甚至是两个不同的组件。当有人不小心采用了无界资源(比如无上限的弹性线程池、无界队列等),那么在高并发或任务繁重时就有可能造成线程数爆炸增长,或队列堆积,因此backpressure这种协调机制对于维持系统稳定具有重要作用。

手把手教你从入门到精通C# MES通信 本文是C#与MES通信通信的目录,使用C#与各种MES系统通信的方法我都会在专栏中讲到,比如WebService、Http、Ftp、共享文件夹、串口通信等,绝对是上位机软件工程师实现与MES系统通信的必备教程! 阅读详情

相关推荐

LabVIEW数据采集-数字I/O/计数器

labview 数据采集 数字I/O 计数器

weixin_43781045的博客 5617

Panda白话 Reactor -背压策略

上回书我们讲了元素采样和延迟响应操作符 - 传送门 五、背压策略 背压:下游消费速度跟不上上游发送数据速度,导致阻塞或异常,进而影响上游。 reactor提供了集中背压策略 onBackPressureBuffer - 顾名思义,来不及消费的数据先缓存在队列里 onBackPressureDrop - drop,丢掉,来不及消费的数据直接扔掉 onBackPressureLatest - 保留最新数据,一旦下游请求出现,立即推向下游 onBackPressureError - 下游消费速度跟不上,直接抛异

FightingITPanda的博客 1902

Temporal Anti-Aliasing(时域抗锯齿TAA)

首先说一下走样:一般分为时域走样(如旋转车轮)和空域走样(锯齿),但在 TAA 技术是采用时域相关叠加混合技术来解决空域走样的问题。 简单看一下空域抗锯齿 (Spatial Anti-Aliasing, SAA)相关技术,最普及的莫过于 MSAA,被各大渲染引擎采用,但是 MSAA 并不适用于延迟渲染 (Deferred Rendering),随之出现了很多基于形态学的后处理抗锯齿技术,如 MLAA,FXAA,SMAA 等(具体看参见图形_反走样技术总结相关介绍),虽然他们表现良好且支持延迟渲染,但是都会面

qq_35312463的博客 1万+

终极指南:Android RxJava背压处理与onBackpressureBuffer实战技巧

在Android开发中,RxJava作为强大的响应式编程框架,能够有效处理异步数据流。然而,当上游数据产生速度远超下游消费能力时,就会出现**数据流过载**问题。本文将深入解析RxJava的**背压处理**机制,重点介绍**onBackpressureBuffer**操作符的实际应用,帮助开发者构建更稳定的Android应用。🚀 [![Android开发背压处理](https://raw.gi

gitblog_00655的博客 483

国产麒麟系统下用Rider开发C#桌面应用:从环境配置到Avalonia UI实战

本文详细介绍了在国产麒麟系统上使用JetBrains Rider开发C#桌面应用的完整流程,重点讲解了Avalonia UI框架的实战应用。从环境配置、Rider安装到Avalonia项目创建和UI开发技巧,全面覆盖了在非Windows环境下进行C#开发的关键步骤和优化建议,助力开发者高效构建跨平台桌面应用。

weixin_27791839的博客 387

Windows下用MSYS2一键搞定Verilator安装(附国内镜像加速)

本文详细介绍了在Windows系统下,如何利用MSYS2工具和国内镜像源,快速、高效地完成Verilator的安装与配置。通过替换清华镜像源加速下载,并配合Pacman包管理器,实现近乎一键式的安装流程,有效解决了传统方法步骤繁琐、网络依赖下载慢的问题,为数字电路仿真提供了便捷的Windows环境解决方案。

weixin_29208327的博客 592

RxJava 2.0中backpressure(背压)概念的理解

英文原文:https://github.com/ReactiveX/RxJava/wiki/Backpressure Backpressure(背压、反压力) 在rxjava中会经常遇到一种情况就是被观察者发送消息太快以至于它的操作符或者订阅者不能及时处理相关的消息。那么随之而来的就是如何处理这些未处理的消息。 举个例子,使用zip操作符将两个无限大的Observable压缩在一起,其...

dawuafang 708

SolidWorks与Python二次开发实战:从宏录制到自动化脚本

本文详细介绍了利用Python对SolidWorks进行二次开发的实战方法。通过宏录制获取操作指令,再将其转换为Python脚本,工程师可以高效实现建模、出图等重复任务的自动化,显著提升设计效率。文章涵盖了环境搭建、代码转换、调试技巧及完整实战案例,为工程师提供了从入门到精通的完整路径。

weixin_29082143的博客 945

关于RxJava最友好的文章——背压(Backpressure)

前言 背压(Backpressure)可能是所有想要深入运用RxJava的朋友必须理解的一个概念。 关于它的介绍,我本意是想写在RxJava2.0更新介绍的文章里的,可是写着写着发现,要完整介绍这个概念需要花费的篇幅太长,恰好目前对于背压的介绍文章比较少,所以决定单独拿出来,自成一篇。而关于RxJava2.0的文章修改之后就会发出来和大家探讨。 如果对于RxJava不是很熟悉,那么在这篇文章之前,...

weixin_33739627的博客 361

YOLOv5改进 | 多尺度特征提取 | 结合多样分支块及融合的高级设计(CVPR2021)

YOLOv5改进 | 多尺度特征提取 | 结合多样分支块及融合的高级设计(CVPR2021)

qq_44224801的博客 2068

构建具有预测维护能力的AI Agent

随着工业4.0的推进,工业设备的复杂性和自动化程度不断提高,设备的稳定运行对于企业的生产效率和经济效益至关重要。预测维护作为一种先进的设备维护策略,能够通过对设备状态的实时监测和分析,提前预测设备故障,从而避免设备意外停机,降低维护成本。本文的目的是详细阐述如何构建具有预测维护能力的AI Agent,涵盖从数据采集、处理、模型训练到实际应用的整个流程,为企业和开发者提供全面的技术指导。本文将按照以下结构进行组织:首先介绍核心概念与联系,包括预测维护和AI Agent的基本原理和架构;

2501_92132293的博客 943

Spring cloud gateway 详解和配置使用(文章较长)

目录 spring cloud gateway 介绍 1. 网关是怎么演化来的 2. 网关的基本功能 3.关于Spring Cloud Gateway 4.Spring Cloud Gateway 功能特征 5.predicate简介 6.GatewayFilter 工厂介绍 7.集成Hystrix的断路器功能 8.请求限流 pom.xml application.ym...

荡漾 21万+

低功耗设计-Power Switch

power gating与power switch cell

m0_61544122的博客 1万+

Java核心技术解析:从JVM到Spring Cloud实战指南

Java作为企业级开发的主流语言,其核心技术体系涵盖JVM内存管理、并发编程机制及Spring生态框架。JVM通过分代垃圾回收机制管理堆内存,GC日志分析配合jvisualvm工具可实现性能调优。多线程编程需解决可见性、有序性和原子性问题,BlockingQueue等并发容器比传统同步方式性能提升5-8倍。Spring IoC容器通过Bean定义解析和依赖注入实现控制反转,MyBatis则通过动态代理机制优化SQL执行流程。掌握这些核心原理能有效应对CPU飙升、内存泄漏等生产环境问题,并为云原生和模块化开发

weixin_34112181的博客 348

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

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

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

Java注解原理与高级应用全解析

Java注解(Annotation)作为元数据编程的核心技术,通过编译器检查和运行时反射实现声明式开发。其底层原理基于接口动态代理,配合@Target、@Retention等元注解控制作用范围与生命周期。在Spring、JPA等主流框架中,注解大幅简化了配置工作,典型应用包括依赖注入(@Autowired)、ORM映射(@Entity)和AOP切面(@Aspect)。开发自定义注解时需注意反射性能开销,可通过注解处理器(AbstractProcessor)实现编译期代码生成。随着Java生态发展,注解在微服

weixin_34391445的博客 319

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

Java观察者模式详解:原理、实现与应用场景

观察者模式是软件设计中常用的行为型模式,它定义了对象间的一对多依赖关系,当被观察对象状态变化时自动通知所有依赖对象。该模式通过解耦观察者和被观察者,实现了松耦合设计,广泛应用于事件处理、消息通知等场景。Java标准库提供了Observable/Observer基础实现,但在实际开发中需要注意线程安全和扩展性问题。现代Java开发中,观察者模式常结合Spring事件机制、响应式编程等技术,在GUI开发、微服务架构、CQRS模式等场景发挥重要作用。掌握观察者模式的核心原理和最佳实践,能够帮助开发者构建更灵活、可

weixin_30892763的博客 359

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

编程高级特性:闭包、元编程与并发模型实战

在软件开发中,高级编程特性如闭包和元编程能显著提升代码质量与开发效率。闭包允许函数记住其词法作用域,实现状态封装,而元编程则使程序能够动态修改自身结构,减少样板代码。这些技术特别适用于Web开发中的中间件系统、自动化路由注册等场景。理解并发编程模型如协程和goroutine,则对构建高性能系统至关重要。通过合理运用这些高级特性,开发者可以写出更简洁、健壮且易于维护的代码,有效应对复杂业务需求。

395

flux中的缓存

cache可以将hot流的数据缓存起来。onBackpressureBuffer也是缓存,但是当下游消费者的处理速度比上游生产者慢时,上游生产的数据会被暂时存储在缓冲区中,防止丢失。Flux.interval 默认下游的consume速率不能低于produce的速率,否则直接报错。4. 注意默认带有无界缓冲区时,如果设置了onBackpressureBuffer(size)。有界会导致如果缓冲区不足以缓冲数据时,会报错。3. Flux.fromIterable 经过测试,应该也是属于无界缓冲区。

silver9886的专栏 943

RxJava onBackpressureBuffer() 背压

当订阅者Observer消费完一个事情之后再生产下一个事件。

qiaoyd的博客 1304

背压(Backpressure)机制

作者:张铁蕾链接:https://www.zhihu.com/question/49618581/answer/117107570来源:知乎著作权归作者所有,转载请联系作者获得授权。 首先,从大的方面说,这篇文档的名字,虽然叫“Backpressure”(背压),但却是在讲述一个更大的话题,“Flow Control”(流控)。Backpressure只是解决Flow Control的...

aizongmao2539的博客 1809

RxJava 并发之数据流发射太快如何办(背压(Backpressure))

RxJava系列教程:1. RxJava使用介绍 【视频教程】 2. RxJava操作符   • Creating Observables(Observable的创建操作符) 【视频教程】   • Transforming Observables(Observable的转换操作符) 【视频教程】   • Filtering Observables(Observable的过滤操

Dream It Possible 1万+

51、基于注解方式开发Spring WebFlux,实现生成背压数据,就是实现一直向客户端发送消息

基于注解方式开发Spring WebFlux,实现生成背压数据,就是实现一直向客户端发送消息

Java 领域技术分享 1333

Spring WebFlux中的响应式编程系列(七):复杂场景下的响应式编程

在本篇文章中,我们探讨了在复杂场景下应用Spring WebFlux的一些策略和方法。通过合理处理背压、集成外部服务以及流式处理大型文件,我们可以构建更加健壮和高效的响应式应用。在接下来的系列文章中,我们将继续探索更多高级主题,如安全性、测试和部署等。在前几篇文章中,我们讨论了Spring WebFlux的基础概念、Reactor、错误处理、数据流转换、响应式数据库访问以及性能优化。背压(Backpressure)是指在处理大量数据时,下游消费者无法跟上上游生产者的速度,导致数据堆积和资源耗尽。

阿里渣渣java研发组-群主 937

Rxjava2中的Backpressure背压策略

rxjava2中的背压Backpressure策略:     在rxjava常见的使用场景我们一般的操作步骤如下:         1.创建被观察者Observable对象在上游生产事件(Event)         2.通过一系列的操作符operators以及subscribeOn()来对上游Observable生产的数据进行加工处理以及          Observable 生产数据...

qq_15327675的博客 2410

RxJava2.0 (七) Flowable之 BackpressureStrategy

在上一节中, 我们学习了FLowable的一些基本知识, 同时也挖了许多坑, 这一节就让我们来填坑吧.

m0_49508485的博客 614

Spring WebFlux 背压处理策略及响应式编程错误处理详解

在响应式编程中,背压是消费者(Subscriber)向生产者(Publisher)反馈自身处理能力的机制,用于解决数据生产与消费速度不匹配的问题。通过合理选择背压策略和分层错误处理机制,可以确保Spring WebFlux应用在高负载和异常场景下保持稳定性和可维护性。响应式编程的异步和非阻塞特性使得错误处理需要特殊设计。Spring WebFlux通过。

csdn_tom_168的博客 1222
上一篇: 附1:多线程并发方案的不足——响应式Spring的道法术器
下一篇: (14)Reactor调度器与线程模型——响应式Spring的道法术器
享学IT
博客等级 码龄12年 1516粉丝 91原创
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值