SpringCloud 2020 Alibaba微服务实战六 sentinel实战之服务限流及限流规则持久化

前面介绍了服务的注册与发现,服务配置中心,服务的调用及负载均衡,这一回介绍服务的限流及熔断!

 Alibaba 技术栈为Sentinel,

在复杂的生产环境下可能部署着成千上万的服务实例,当流量持续不断地涌入,服务之间相互调用频率陡增时,会产生系统负载过高、网络延迟等一系列问题,从而导致某些服务不可用。如果不进行相应的流量控制,可能会导致级联故障,并影响到服务的可用性,因此如何对高流量进行合理控制,成为保障服务稳定性的关键。

Sentinel是面向分布式服务架构的轻量级限流降级框架,以流量为切入点,从流量控制、熔断降级和系统负载保护等多个维度来帮助用户保障服务的稳定性。

Sentinel 意为哨兵,这个命名形象的诠释了Sentinel在分布式系统中的工作角色和重要性。 以 Sentinel 在Dubbo 生态系统中的作用为例,Dubbo服务框架的核心模块包括注册中心、服务提供方、服务消费方(服务调用方)和监控4个模块。Sentinel通过对服务提供方和服务消费方的限流来进一步提升服务的可用性。

Sentinel 是什么

随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 是面向分布式服务架构的轻量级流量控制产品,主要以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度来帮助您保护服务的稳定性。在SpringCloud体系中,sentinel主要是为了替换原Hystrix的功能,与Hystrix相比,sentinel的隔离级别更加精细,提供的Dashboard可以在线更改限流熔断规则,而且使用也越加方便。

然后来看看它的特性:

  • 丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。
    完备的实时监控:Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。
    广泛的开源生态:Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。
    完善的 SPI 扩展点:Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。

Sentinel 基本概念

资源

资源是 Sentinel 的关键概念。它可以是 Java 应用程序中的任何内容,例如,由应用程序提供的服务,或由应用程序调用的其它应用提供的服务,甚至可以是一段代码。在接下来的文档中,我们都会用资源来描述代码块。

只要通过 Sentinel API 定义的代码,就是资源,能够被 Sentinel 保护起来。大部分情况下,可以使用方法签名,URL,甚至服务名称作为资源名来标示资源。

规则

围绕资源的实时状态设定的规则,可以包括流量控制规则、熔断降级规则以及系统保护规则。所有规则可以动态实时调整。

Sentinel 功能和设计理念

流量控制

什么是流量控制

流量控制在网络传输中是一个常用的概念,它用于调整网络包的发送数据。然而,从系统稳定性角度考虑,在处理请求的速度上,也有非常多的讲究。任意时间到来的请求往往是随机不可控的,而系统的处理能力是有限的。我们需要根据系统的处理能力对流量进行控制。Sentinel 作为一个调配器,可以根据需要把随机的请求调整成合适的形状,如下图所示:
在这里插入图片描述

流量控制设计理念

流量控制有以下几个角度:

  • 资源的调用关系,例如资源的调用链路,资源和资源之间的关系;
  • 运行指标,例如 QPS、线程池、系统负载等;
  • 控制的效果,例如直接限流、冷启动、排队等。

Sentinel 的设计理念是让您自由选择控制的角度,并进行灵活组合,从而达到想要的效果。

 

服务保护的基本概念
服务限流/熔断
服务限流目的是为了更好的保护我们的服务,在高并发的情况下,如果客户端请求的数量达到一定极限(后台可以配置阈值),请求的数量超出了设置的阈值,开启自我的保护,直接调用我们的服务降级的方法,不会执行业务逻辑操作,直接走本地falback的方法,返回一个友好的提示。

服务降级
在高并发的情况下, 防止用户一直等待,采用限流/熔断方法,使用服务降级的方式返回一个友好的提示给客户端,不会执行业务逻辑请求,直接走本地的falback的方法。
提示语:当前排队人数过多,稍后重试~

服务的雪崩效应

服务雪崩效应是一种因“服务提供者服务的不可用”(原因)导致“服务调用者服务不可用”(结果),并将不可用逐渐放大的现象。如下图所示

在这里插入图片描述

1. 形成原因

服务雪崩的过程可以分为三个阶段:

  • 服务提供者不可用;

  • 重试加大请求流量;

  • 服务调用者不可用;
    服务雪崩的每个阶段都可能由不同的原因造成,总结如下:

  • 在这里插入图片描述

2. 应对策略

常见容错方案:

  1. 超时

  2. 限流

  3. 舱壁模式(如每个controller都有自己独立的线程池,之间互不干扰)

  4. 断路器模式

在这里插入图片描述

3. 全面应对策略:

对于高并发的系统,有三大利器来保证系统的稳定可用:缓存、降级、限流

缓存即是提升系统访问速度和增大系统能处理的容量,可谓是抗高并发流量的银弹

降级即是把服务暂时屏蔽,直接越过,待高峰后再恢复

限流即流量限制,限流的目的是在遇到流量高峰期或者流量突增(流量尖刺)时,把流量速率限制在系统所能接受的合理范围之内,不至于让系统被高流量击垮

(熔断即是把服务直接短路掉,返回一个mock的值)
 

img

 

默认的情况下,Tomcat或者是Jetty服务器只有一个线程池去处理客户端的请求,
这样的话就是在高并发的情况下,如果客户端所有的请求都堆积到同一个服务接口上,
那么就会产生tomcat服务器所有的线程都在处理该接口,可能会导致其他的接口无法访问。

假设我们的tomcat线程最大的线程数量是为20,这时候客户端如果同时发送100个请求会导致有80个请求暂时无法访问,就会转圈。

服务的隔离的机制
服务的隔离机制分为:信号量和线程池隔离模式
服务的线程池隔离机制:每个服务接口都有自己独立的线程池,互不影响,缺点就是占用cpu资源非常大。
服务的信号量隔离机制:最多只有一定的阈值线程数处理我们的请求,超过该阈值会拒绝请求。

Sentinel 与hytrix区别
前哨以流量为切入点,从流量控制,熔断降级,系统负载保护等多个维度保护服务的稳定性。

前哨具有以下特征:

  1. 丰富的应用场景:前哨兵承接了阿里巴巴近10年的双十一大促流的核心场景,例如秒杀(即突然流量控制在系统容量可以承受的范围),消息削峰填谷,传递流量控制,实时熔断下游不可用应用等。
  2. 完备的实时监控:Sentinel同时提供实时的监控功能。您可以在控制台中看到接收应用的单台机器秒级数据,甚至500台以下规模的整合的汇总运行情况。
  3. 广泛的开源生态:Sentinel提供开箱即用的与其他开源框架/库的集成模块,例如与Spring Cloud,Dubbo,gRPC的整合。您只需要另外的依赖并进行简单的配置即可快速地接入Sentinel。
  4. 完善的SPI扩展点:Sentinel提供简单易用,完善的SPI扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理,适应动态数据源等。

在这里插入图片描述

熔断降级

什么是熔断降级

除了流量控制以外,降低调用链路中的不稳定资源也是 Sentinel 的使命之一。由于调用关系的复杂性,如果调用链路中的某个资源出现了不稳定,最终会导致请求发生堆积。这个问题和 Hystrix 里面描述的问题是一样的。

Sentinel 和 Hystrix 的原则是一致的: 当调用链路中某个资源出现不稳定,例如,表现为 timeout,异常比例升高的时候,则对这个资源的调用进行限制,并让请求快速失败,避免影响到其它的资源,最终产生雪崩的效果。

熔断降级设计理念

在限制的手段上,Sentinel 和 Hystrix 采取了完全不一样的方法。

Hystrix 通过线程池的方式,来对依赖(在我们的概念中对应资源)进行了隔离。这样做的好处是资源和资源之间做到了最彻底的隔离。缺点是除了增加了线程切换的成本,还需要预先给各个资源做线程池大小的分配。

Sentinel 对这个问题采取了两种手段:

  • 通过并发线程数进行限制
    和资源池隔离的方法不同,Sentinel 通过限制资源并发线程的数量,来减少不稳定资源对其它资源的影响。这样不但没有线程切换的损耗,也不需要您预先分配线程池的大小。当某个资源出现不稳定的情况下,例如响应时间变长,对资源的直接影响就是会造成线程数的逐步堆积。当线程数在特定资源上堆积到一定的数量之后,对该资源的新请求就会被拒绝。堆积的线程完成任务后才开始继续接收请求。
  • 通过响应时间对资源进行降级
    除了对并发线程数进行控制以外,Sentinel 还可以通过响应时间来快速降级不稳定的资源。当依赖的资源出现响应时间过长后,所有对该资源的访问都会被直接拒绝,直到过了指定的时间窗口之后才重新恢复。

系统负载保护

Sentinel 同时对系统的维度提供保护。防止雪崩,是系统防护中重要的一环。当系统负载较高的时候,如果还持续让请求进入,可能会导致系统崩溃,无法响应。在集群环境下,网络负载均衡会把本应这台机器承载的流量转发到其它的机器上去。如果这个时候其它的机器也处在一个边缘状态的时候,这个增加的流量就会导致这台机器也崩溃,最后导致整个集群不可用。

针对这个情况,Sentinel 提供了对应的保护机制,让系统的入口流量和系统的负载达到一个平衡,保证系统在能力范围之内处理最多的请求。

Sentinel 是如何工作的

Sentinel 的主要工作机制如下:

  • 对主流框架提供适配或者显示的 API,来定义需要保护的资源,并提供设施对资源进行实时统计和调用链路分析。
  • 根据预设的规则,结合对资源的实时统计信息,对流量进行控制。同时,Sentinel 提供开放的接口,方便您定义及改变规则。
  • Sentinel 提供实时的监控系统,方便您快速了解目前系统的状态。

 

实战:

 

sentinel 分为2部分:

1.控制台(Dashboard)基于 Spring Boot 开发,打包后可以直接运行,不需要额外的 Tomcat 等应用容器。也就是是控制台,能够实时监控各个资源的流量情况,同时提供各种规则的配置。服务下载及启动参考博客  https://blog.csdn.net/liuerchong/article/details/114392617
2.另一部分核心库(Java 客户端)不依赖任何框架/库,能够运行于所有 Java 运行时环境,同时对 Dubbo / Spring Cloud 等框架也有较好的支持。包括对规则的检查,各种统计指标都是在这个里面做的,后续的源码解析也就是在这里展开。

  1. 流量控制: 对某个资源的qps ,并发线程数进行控制,提供了快速失败,预热启动,匀速排队各种流控方式。
  2. 降级控制: 可以根据某个资源的rt,异常比例,异常数各种策略进行降级。
  3. 热点控制: 这个就是对某个资源的热点参数进行流控
  4. 系统控制:这个就是对整个服务进行控制,控制类型有:系统负载,rt,入口qps,并发线程数
  5. 权限控制:这个就是黑白名单的限制
  6. 集群控制:从集群的角度对资源进行控制

 

项目中有一个订单服务,一个支付服务,订单服务调用支付服务,支付模块为订单模块提供服务。

1、对服务提供方的限流

对服务提供方的限流可分为服务提供方的自我保护能力和服务提供方对服务消费方的请求分配能力这两个维度。

服务提供方用于向外界提供服务,处理各个消费方的调用请求。为了保护提供方不被激增的流量拖垮影响稳定性,可以给提供方配置QPS模式的限流,这样当每秒的请求量超过设定的阈值时会自动拒绝阈值外的请求。若希望整个服务接口的QPS不超过一定数值,则可以为对应服务接口资源(resourceName为接口全限定名)配置QPS阈值;若希望对某个服务函数的QPS不超过一定数值,则可以为对这个服务函数资源(resourceName为接口全限定名:方法签名)配置QPS阈值。

根据调用方的需求来分配服务提供方的处理能力也是常见的限流方式。比如有两个服务 A 和 B 都向 Service Provider 发起调用请求,我们希望只对来自服务 B 的请求进行限流,则可以设置限流规则的 limitApp为服务 B 的名称。Sentinel  会自动解析  消费方(调用方)的 application name 作为调用方名称(origin),在进行资源保护的时候都会带上调用方名称。若限流规则未配置调用方(default),则该限流规则对所有调用方生效。若限流规则配置了调用方则限流规则将仅对指定调用方生效。

2、对服务消费方的限流

对服务消费方的限流可分为对控制并发线程数,和服务降级两个维度。

服务消费方作为客户端去调用远程服务。每一个服务都可能会依赖几个下游服务,若某个服务A依赖的下游服务B出现了不稳定的情况,服务A请求服务B的响应时间变长,从而服务A调服务B的线程就会产生堆积,最终可能耗尽服务A的线程数。我们通过用并发线程数来控制对下游服务B的访问,来保证下游服务不可靠的时候,不会拖垮服务自身。采用基于线程数的限制模式后,我们不需要再去对线程池进行隔离,Sentinel 会控制资源的线程数,超出的请求直接拒绝,直到堆积的线程处理完成。限流粒度同样可以是服务接口和服务方法两种粒度。

我们看一下这种模式的效果。假设当前服务A依赖两个远程服务方法 sayHello(java.lang.String) 和 doAnother()。前者远程调用的响应时间为1s-1.5s之间,后者RT非常小(30 ms左右)。服务A端设两个远程方法线程数为5,然后每隔50 ms左右向线程池投入两个任务,作为调用者分别远程调用对应方法,持续10次。可以看到 sayHello 方法被限流5次,因为后面调用的时候前面的远程调用还未返回(RT高);而 doAnother() 调用则不受影响。线程数目超出时快速失败能够有效地防止自己被调用所影响。

此外,当调用链路中某个资源出现不稳定的情况,如平均 RT 增高、异常比例升高的时候,Sentinel 会使对此调用资源进行降级操作。

下面先讲限流,先以服务提供方为例讲解限流

引入Sentinel
在需要配置限流熔断服务payment_service的POM文件中引入Sentinel组件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>cloud_alibaba_learn</artifactId>
        <groupId>com.liu.learn</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud_payment</artifactId>

    <dependencies>
        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.liu.learn</groupId>
            <artifactId>cloud_common_db</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>5.3.6</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>


    </dependencies>


</project>

自定义资源@SentinelResource
我们只需要在相关方法上加上@SentinelResource注解,让其可以成为sentinel识别的资源即可。如:

  //通过id进行查询
    @GetMapping("/get/{id}")
    /**
     * 通过id进行查询
     */
	//通过id查询
    @SentinelResource(value = "getPaymentById")
    public CommonResult<com.liu.entitis.Payment> getPaymentById(@PathVariable("id") Long id ){
       Payment payment = paymentService.getById(id);
        log.info("****查询结果:"+payment);
        if (payment != null){
            return new CommonResult(200,"查询成功"+serverPort,payment);
        }else {
            return new CommonResult(444,"没有对应记录,查询ID:"+id,null);
        }
    }

 

在配置文件中添加sentinel的服务端地址

server:
  port: 8070
spring:
  main:
    allow-bean-definition-overriding: true
  profiles:
    active: dev
  application:
    name: payment-service
    cloud:
      nacos:
        discovery:
          server-addr: 127.0.0.1:8848
          #namespace: b561281a-fb95-411e-b75b-cf36bfc90854
        config:
          server-addr: ${spring.cloud.nacos.discovery.server-addr}
          file-extension: properties
          #namespace: b561281a-fb95-411e-b75b-cf36bfc90854
          #group: payment
      sentinel:
        transport:
          port: 8719
          dashboard: 127.0.0.1:10011
        eager: true


 

经过以上几步我们准备好了使用Sentinel的基础环境,接下来我们看看限流熔断的具体配置。

限流
概念说明
生产者payment-service是一个核心服务,比如我们通过压测得出服务的最大负载能力为1000。如果某个时间payment-service的请求数飙升达到了6000,那服务肯定就直接挂了。所以为了保护我们的payment-service,我们会给它配置一个限流规则,如果每秒钟有超过1000的请求那不好意思我直接丢掉不处理了,然后丢给消费者一个异常,免得拖垮服务!。


总而言之,限流是通过限制调用方对自己的调用,起到保护自己系统的效果。

限流配置
为了方便测试,我们将payment-service的QPS单机阈值设置成2,如果每秒QPS超过2,直接丢弃。

测试效果

后台报以下错误

2021-05-18 11:08:49.928 ERROR 48956 --- [nio-8072-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.reflect.UndeclaredThrowableException] with root cause

com.alibaba.csp.sentinel.slots.block.flow.FlowException: null

 目的达到了,限流成功!

此处只演示最简单的限流,其他限流规则参考以下:

流控规则参考

流控规则讲解及演示

 

下面过一下@SentinelResource注解

资源
资源是 Sentinel 的关键概念。它可以是 Java 应用程序中的任何内容,例如,由应用程序提供的服务,或由应用程序调用的其它应用提供的服务,甚至可以是一段代码。

只要通过 Sentinel API 定义的代码,就是资源,能够被 Sentinel 保护起来。大部分情况下,可以使用方法签名,URL,甚至服务名称作为资源名来标示资源。

@SentinelResource
Sentinel资源的定义

源码
 

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface SentinelResource {
	// 资源的名称
    String value() default "";
	// 流量的方向,默认为出站
    EntryType entryType() default EntryType.OUT;
	// 资源的分类(类型)
    int resourceType() default 0;
	// 异常函数的名称,默认为空
    String blockHandler() default "";
	// 处理异常所在的类
    Class<?>[] blockHandlerClass() default {};
	// 降级方法的名称,默认为空
    String fallback() default "";
	// 默认降级方法的名称,默认为空
    String defaultFallback() default "";
	// 降级方法所在的类(仅单个类)
    Class<?>[] fallbackClass() default {};
	// 要跟踪的异常类列表
    Class<? extends Throwable>[] exceptionsToTrace() default {Throwable.class};
	// 要忽略的异常类列表,默认为空
    Class<? extends Throwable>[] exceptionsToIgnore() default {};
}

 

  1. value    资源名称    是
  2. entryType    entry类型,标记流量的方向,取值IN/OUT,默认是OUT    否
  3. blockHandler    处理BlockException的函数名称。函数要求:1. 必须是 public 2.返回类型与原方法一致 3. 参数类型需要和原方法相匹配,并在最后加 BlockException 类型的参数。4. 默认需和原方法在同一个类中。若希望使用其他类的函数,可配置 blockHandlerClass ,并指定blockHandlerClass里面的方法。    否
  4. blockHandlerClass    存放blockHandler的类。对应的处理函数必须static修饰,否则无法解析,其他要求:同blockHandler。    否
  5. fallback    用于在抛出异常的时候提供fallback处理逻辑。fallback函数可以针对所有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。函数要求:1. 返回类型与原方法一致 2. 参数类型需要和原方法相匹配,Sentinel 1.6开始,也可在方法最后加 Throwable 类型的参数。3.默认需和原方法在同一个类中。若希望使用其他类的函数,可配置 fallbackClass ,并指定fallbackClass里面的方法。    否
  6. fallbackClass【1.6】    存放fallback的类。对应的处理函数必须static修饰,否则无法解析,其他要求:同fallback。    否
  7. defaultFallback【1.6】    用于通用的 fallback 逻辑。默认fallback函数可以针对所有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。若同时配置了 fallback 和defaultFallback,以fallback为准。函数要求:1. 返回类型与原方法一致 2. 方法参数列表为空,或者有一个 Throwable 类型的参数。3. 默认需要和原方法在同一个类中。若希望使用其他类的函数,可配置 fallbackClass ,并指定 fallbackClass 里面的方法。    否
  8. exceptionsToIgnore【1.6】    指定排除掉哪些异常。排除的异常不会计入异常统计,也不会进入fallback逻辑,而是原样抛出。    否
  9. exceptionsToTrace    需要trace的异常    Throwable
     

 

若访问数超过限流所示的最大值,超出部分会报错,下文讲解错误处理方法使显示更优雅!

 

Sentinel持久化

由于Sentinel的配置默认是放在内存中的,每当应用重启或者sentinel重启都会丢失数据,我们这里使用Nacos作为配置中心持久化限流配置。

解决方案:
Sentinel持久化机制支持四种持久化的机制。

  1. 本地文件
  2. 携程阿波罗
  3. Nacos
  4. Zookeeper

 

  • 修改pom文件,引入sentinel-datasource-nacos组件
<dependency>
	<groupId>com.alibaba.csp</groupId>
	<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
  • 修改application.yml,配置sentinel的数据源
spring:
  cloud:
    sentinel:
      datasource:
        ds:
          nacos:
            server-addr: 127.0.0.1:8848
            data-id: ${spring.application.name}-sentinel
            group-id: DEFAULT_GROUP
            rule-type: flow
  • 在nacos中建立限流配置account-service-sentinel(配置格式设置成json)
[
    {
        "resource": "hello",
        "limitApp": "default",
        "grade": 1,
        "count": 3,
        "strategy": 0,
        "controlBehavior": 0,
        "clusterMode": false
    }
]

可以看到上面配置规则是一个数组类型,数组中的每个对象是针对每一个保护资源的配置对象,每个对象中的属性解释如下:

  1. resource:资源名,即限流规则的作用对象
  2. limitApp:流控针对的调用来源,若为 default 则不区分调用来源
  3. grade:限流阈值类型(QPS 或并发线程数);0代表根据并发数量来限流,1代表根据QPS来进行流量控制
  4. count:限流阈值
  5. strategy:调用关系限流策略
  6. controlBehavior:流量控制效果(直接拒绝、Warm Up、匀速排队)
  7. clusterMode:是否为集群模式
     
  • 进入sentinel查看dashboard,发现sentinel自动获取nacos的配置
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值