什么?你没用过Sentinel?

java高级开发 专栏收录该内容
18 篇文章 1 订阅

安装下载

简介

一句话 牛逼 可以随时随地配置各种各样的限流规则到你的系统中 针对方法级别 接口级别等多维度的配置 最重要的是无侵入式的!!!我们做高并发秒杀等业务场景的利器,如果你没用过sentinel 那么对不起 我们觉得你们的系统流量一般般(调侃)

官方介绍

丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即
突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。
完备的实时监控:Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。

广泛的开源生态:Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。 完善的 SPI 扩展点:Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来
速地定制逻辑。例如定制规则管理、适配动态数据源等。

安装

Sentinel Dashboard 安装运行
基于springboot开发 直接运行 有一个web控制台可以操作 sentinel

https://github.com/alibaba/Sentinel/tree/v1.8.0下载

启动命令

java -Dserver.port=8888 -Dcsp.sentinel.dashboard.server=localhost:8888 -Dproject.name=sentinel-dashboard -Dsentinel.dashboard.auth.username=sentinel -Dsentinel.dashboard.auth.passoword=sentinel -jar sentinel-dashboard-1.8.0.jar

代表含义:

-Dserver.port=8888 设置控制台端口
-Dcsp.sentinel.dashboard.server=localhost:8888 设置控制台的地址
-Dproject.name=sentinel-dashboard 设置控制台名称
-Dsentinel.dashboard.auth.username=sentinel 设置服务账户
-Dsentinel.dashboard.auth.password=sentinel 设置服务密码

**查看页面 账号密码:sentinel **
在这里插入图片描述

Sentinel整合springboot工程

1.pom文件依赖 这里以服务提供者为例子

<?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>springcloud-alibaba-sxw-parent</artifactId>
        <groupId>com.sxw</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <modelVersion>4.0.0</modelVersion>
    <artifactId>04-provider-nacos-sentinel</artifactId>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <spring-cloud.version>Hoxton.SR8</spring-cloud.version>
        <spring-cloud-alibaba.version>2.2.3.RELEASE</spring-cloud-alibaba.version>
    </properties>


    <dependencies>
        <!-- sentinel依赖 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>
        <!-- spring-cloud-nacos的注册中心依赖starter -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
        <!-- spring-cloud-nacos的服务注册与发现依赖starter -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!-- actuator依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.10</version>
        </dependency>
        <!--修改MySQL驱动版本-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <!-- spring-cloud的依赖-->
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!-- spring-cloud-alibaba的依赖 -->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>${spring-cloud-alibaba.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>


    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

2.配置连接sentinel的信息

spring:
  application:
    #你的服务名称
    name: sxw-provider-nacos-sentinel
  cloud:
    #配置中心地址
    nacos:
      config:
        server-addr: 127.0.0.1:8848
        #配置中心扩展文件名
        file-extension: yml
      #sentinel配置
    sentinel:
      transport:
        dashboard: 127.0.0.1:8888
        port: 8888
        eager: true

3.启动服务访问服务内的接口(provider-nacos-sentinel)

注意 这里需要访问接口 这样才能使sentinel发现这个资源
4.在sentinel dashboard控制台看到相应的服务
在这里插入图片描述

服务降级

含义:
服务降级是一种增强用户体验的方式。当用户的请求由于各种原因被拒后,系统返回一个事先设定好的、用户可以接受的,但又令用户并不满意的结果。这种请求处理方式称为服务降级。

sentinel方法级别降级使用

1.修改Controller类

注意点:
参数和返回值需要一模一样
fallback可以声明方法
这里服务的调用案例使用的是feign 如果没有使用过 也可以使用restTemplate来代替

//消费者Controller,对外提供接口
@RequestMapping("/consumer/feign/depart")
@RestController
public class DepartFeignController {
    @Autowired
    private DepartFeignService feignService;

    //根据id查询
    @GetMapping("/get/{id}")
    //定义一个sentinel资源 value是资源名称 fallback是降级方法的名字
    @SentinelResource(value = "sentinel-getHandlerById",fallback = "getHandlerFallback")
    public Depart getHandle(@PathVariable("id") int id) {
        return feignService.getDepartById(id);
    }

    public Depart getHandlerFallback(@PathVariable("id") int id) {
        Depart depart = Depart.builder().id(id).name("sentinel-fallback-method-1").build();
        return depart;
    }
}

2.启动测试 将服务提供者下线(模拟服务提供者不可用)然后访问消费者 进入降级方法

在这里插入图片描述
在这里插入图片描述

sentinel类级别降级使用

每个方法都要定义 很麻烦 不好维护

1.定义降级类
注意点:使用类的话 降级方法需要用静态修饰

public class DepartServiceSentinelFallback {
    public static Depart getHandleFallback(@PathVariable("id") int id) {
        Depart depart = Depart.builder().id(id).name("sentinel-fallback-method-2").build();
        return depart;
    }
}

2.修改控制器

    @GetMapping("/get/{id}")
    //fallback:降级方法名字 注意:必须是静态修饰
    //fallbackClass:DepartServiceSentinelFallback 降级处理类
    @SentinelResource(value = "sentinel-getHandlerById-class",fallback = "getHandleFallback",fallbackClass = DepartServiceSentinelFallback.class)
    public Depart getHandle(@PathVariable("id") int id) {
        return feignService.getDepartById(id);
    }

3启动测试 将服务提供者下线然后访问消费者 进入降级方法 http://127.0.0.1:8081/consumer/feign/depart/get/1
在这里插入图片描述

Feign式类级别降级

sentinel集成Feign 非常方便

1.feign依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

2.修改配置 开启feign对sentinel的支持

feign:
  client:
    config:
      default:
        #连接超时时间
        connectTimeout: 5000
        #数据读取超时时间
        readTimeout: 5000
  #开启feign对sentinel的支持
  sentinel:
    enabled: true

3.定义降级类

@Component
//注意:这里实现的是Feign接口
public class FeignSentinelFallbackImpl implements DepartFeignService {
    @Override
    public Depart getDepartById(int id) {
        Depart de = Depart.builder().id(id).name("Feign-sentinel-fallback-method-3").build();
        return de;
    }
}

4.修改Feign接口

@FeignClient(value = "sxw-provider-nacos-sentinel",configuration = FeignConfig.class,fallback = FeignSentinelFallbackImpl.class)
public interface DepartFeignService {

    @GetMapping("/provider/depart/get/{id}")
    Depart getDepartById(@PathVariable("id")int id);
}

5启动测试 将服务提供者下线然后访问消费者 进入降级方法 http://127.0.0.1:8081/consumer/feign/depart/get/1
在这里插入图片描述

服务熔断

以上的几个例子都是针对于服务提供者provider服务不可用或网络不通时采用的兜底方法。但是在分布式系统中这种情况是比较少的,而多数情况有可能是,服务提供者有可能是外部的系统,比如对接银行的第三方查询账户余额或者查询价格这样的接口,这样的接口是有可能造成线程阻塞的,由于提供方的系统比较缓慢导致我们的链路阻塞,如果线程在此堆积,最终可能耗尽程序的线程池,整个服务就会处于崩溃的边缘,这就是微服务的一个大的痛点,服务雪崩。因此我们需要对不稳定的服务弱依赖服务调用进行熔断降级,暂时切断不稳定调用,避免局部的不稳定因素导致我们的系统雪崩,这样的策略实现一般在客户端(调用者 也就是服务消费者)。这种策略的实现叫做熔断。 熔断,一句话理解,就是定义一个策略或者规则、阈值,如果达到了这个阈值,那么我就触发熔断。 这个熔断也被设计成熔断器,有开关的按钮

熔断器

熔断器的三个状态:

  • 1.关闭状态:所有请求正常访问
  • 2.全开状态:达到一定的错误阈值(可以针对单个api)就会触发 此时请求都会被拒绝
  • 3.半开:全开状态一定时间后会进入休眠时间(30s)然后进入半开状态。此时会判断下一次请求的状况,如果成功,熔断器会切回关闭状态。否则切回打开状态

在这里插入图片描述
服务雪崩:
A->B->C-D 如果某个或多个系统不可用或高延迟导致的系统崩溃导致

熔断策略

基于控制台设置 也可以使用代码来定义熔断策略

慢调用比例

含义:以慢调用作为熔断策略。

  • 最大RT:最大响应时间 单位是毫秒 如果请求的响应时间大于最大RT时间则认为这是一次慢调用(提供者处理需要500毫秒 sentinel设置300毫秒 这次请求就是一次慢调用 )
  • 最小请求数:是单位时间内(默认1s)请求数量是否超过最小请求数的比例阈值
  • 比例阈值:最小请求数的比例阈值
  • 熔断时长:熔断器打开后持续多长时间 单位秒

例子:
提供者接口:处理500ms
当1s内有5*0.5个(约等于3)请求访问这个接口并且这3次接口响应时间都超过了最大RT300ms,那么此时熔断器就会进入打开状态。之后10秒这个接口处于熔断状态,会拒绝访问,当10s后会进入半打开状态,如果还有请求响应时间超过300ms,会再次进入熔断状态,直到半打开状态后,有正常的请求没有超过300ms,此时熔断器会进入关闭状态。

在这里插入图片描述

接下来的举一反三

异常比

异常比例:当单位统计时长内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是[0.0,1.0] 代表 0% - 100%。

在这里插入图片描述

异常数

当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。
默认情况下,统计时间窗口大小为1s。发生熔断后,熔断时长为指定的时长。熔断期间再来的请求,将直接地降级响应。

在这里插入图片描述

服务流控

服务流控概念: 流量限流
一句话理解 我可以设置这个秒杀的接口 一秒钟只能通过1000个 那么这样就能防止突发的高并发流量压垮我的后台秒杀服务 为什么我们淘宝经常看到 来慢了 客官请稍后这样的页面 就是我们的请求被限流了!!!!!由我们来定义时间单位内可以通过多少请求这样的规定

Sentinel 实现流控的原理是监控应用流量的QPS 或并发线程数等指标,当达到指定的阈值时对再来的请求进行进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。
服务流控是为了在高并发场景下不至于将系统压垮。

基于qps的流控策略

基于控制台+@SentinelResource注解的方式 也可以使用代码 但是不建议 使用控制台的方式 可以动态变动流控规则

1.设置一个资源

    @GetMapping("/get/{id}")
    //fallback:降级方法
    //blockHandler:流控降级方法
    @SentinelResource(value = "qpsFlowRule",fallback = "getHandlerFallback",blockHandler = "getHandlerBlockHandler")
    public Depart getHandle(@PathVariable("id") int id) {
        return feignService.getDepartById(id);
    }

    public Depart getHandlerFallback(@PathVariable("id") int id) {
        Depart depart = Depart.builder().id(id).name("sentinel-fallback-method-1").build();
        return depart;
    }

    public Depart getHandlerBlockHandler(@PathVariable("id") int id, BlockException blockException) {
        Depart depart = Depart.builder().id(id).name("sentinel-fallback-method-getHandlerBlockHandler").build();
        return depart;
    }

2.在控制台配置流控规则 代表含义:针对这个资源的流控规则 1秒内只能通过2个请求
在这里插入图片描述
3.测试 快速刷新(F5)三次请求地址 进入流控降级方法
在这里插入图片描述
总结:
QPS,Queue Per Second,就是通常所说的每秒的访问量。该流控方案通过对每秒访问量的控制
来达到保护。当指定资源的每秒访问量达到了设置的阈值时,可以立即拒绝再新进入的请求。
一句话理解 很多人想进去银行办理业务 但是这个qps流控相当于门的作用 一秒钟 只能进去3个人 第四个人就会被限流很好理解

基于并发线程数流控

A、 线程池隔离

  • 系统为不同的提供者资源设置不同的线程池来隔离业务自身之间的资源争抢。该方案隔离性较好,但需要创建的线程池及线程数量太多,系统消耗较大。当请求线程到达后,会从线程池中获取到一个新的执行线程去完成提供者的调用。由请求线程到执行线程的上下文切换时间开销较大。特别是对低延时的调用有比较大的影响。

B、 信号量隔离

  • 系统为不同的提供者资源设置不同的计数器。每增加一个该资源的调用请求,计数器就变化一次。当达到该计数器阈值时,再来的请求将被限流。该方式的执行线程与请求线程是同一个线程,不存在线程上下文切换的问题。更不存在很多的线程池创建与线程创建问题。也正因为请求线程与执行线程没有分离,所以,其对于提供者的调用无法实现异步,执行效率降低,且对于依赖资源的执行超时控制不方便。

Sentinel 并发线程数控制:

  • 也属于隔离方案,但不同于以上两种隔离方式,是对以上两种方案的综合与改
    进,或者说更像是线程池隔离。 其也将请求线程与执行线程进行了分离,但不负责创建和管理线程池,
    而仅仅是简单统计该资源的请求占用的线程数量超出了阈值,则可以立即拒绝再新进入的请求。
    一句话理解 很多人进去银行办理业务 这个线程数流控相当于限制了办理业务的人员 全部人都进去 但是只有一个业务人员再办理业务 进去了也没用 用于对资源的线程数进行控制

1.提供者服务模拟耗时

//根据id查询
@GetMapping("/get/{id}")
public Depart getHandle(@PathVariable("id") int id) throws InterruptedException {
    TimeUnit.SECONDS.sleep(2);
    return service.getDepartById(id);
}

2.配置线程数流控
在这里插入图片描述

流控的三种模式

在这里插入图片描述

直接

直接进入流控方法 默认使用的都是这种

关联

当写请求和读请求一起来的时候 他们是有关联关系 我为了要确保写得请求优先执行 那么我对写请求进行关联流控 当读请求(被关联)到达阈值时 会对写得请求也进行限流 这样确保了我的写请求优先级较高被执行完 一句话理解:一个资源关联另一个资源 被关联的资源如果达到阈值 那么当前资源也会被限流

1.定义两个资源

//当前资源
@SentinelResource(value = "threadFlowRule",fallback = "getHandlerFallback",blockHandler = "getHandlerBlockHandler")
public Depart getHandle(@PathVariable("id") int id) throws InterruptedException {
    return feignService.getDepartById(id);
}

public Depart getHandlerFallback( int id) {
    Depart depart = Depart.builder().id(id).name("sentinel-fallback-method-1").build();
    return depart;
}

public Depart getHandlerBlockHandler( int id, BlockException blockException) {
    Depart depart = Depart.builder().id(id).name("sentinel-fallback-method-getHandlerBlockHandler").build();
    return depart;
}

//被关联资源
@GetMapping("/LinkResource")
@SentinelResource(value = "threadFlowRule_link")
public Map<String,String> LinkResource() {
    Map<String,String> map = new HashMap<>();
    map.put("code","link");
    map.put("msg","link");
    System.out.println(map.toString());
    return map;
}

2.设置规则
在这里插入图片描述
在这里插入图片描述
3.使用压测工具
在这里插入图片描述
在这里插入图片描述
看效果:100个线程循环20次访问被关联资源使其进入流控,此时访问当前资源:当前资源也会进入流控
在这里插入图片描述

链路

一句话理解 针对方法层面 dao层 比如一个dao层有两个端调用 可以针对某一端进行限流

流控效果分类

快速失败

进入降级方法

Warm up

当服务的qps长期处理冷状态,那么突然qps增多的时候,可以保证qps缓慢增加到设定的阈值,而不是让所有的请求快速失败。

排队等待

队等待,也称为匀速排队。该方式会严格控制请求通过的间隔时间,让请求以均匀的速度通过。其是
漏桶算法的改进。不同的是,当流量超过设定阈值时,漏桶算法会直接将再来的请求丢弃,而排队等待
算法则是将请求缓存起来,后面慢慢处理。不过,该算法目前暂不支持QPS 超过1000 的场景。

来源流控

一句话理解:来源流控是针对请求发出者的来源名称所进行的流控

1.增加Source配置类

@Component
public class MyRequestOriginParser implements RequestOriginParser {
    @Override
    public String parseOrigin(HttpServletRequest httpServletRequest) {
        //如果请求没有携带该参数 默认设置上Service-A
        String source = httpServletRequest.getParameter("source");
        if (StringUtils.isEmpty(source)){
            source = "Service-A";
        }
        //返回来源标识
        return source;
    }
}

2.定义一个资源

@SentinelResource(value = "SourceFlowRule",fallback = "getHandlerFallback",blockHandler = "getHandlerBlockHandler")
public Depart getHandle(@PathVariable("id") int id) throws InterruptedException {
    return feignService.getDepartById(id);
}

3.配置规则
含义:Service-A这种资源访问限制阈值是1 Service-B这种资源访问限制阈值是2
在这里插入图片描述
在这里插入图片描述
4.测试

测试1:http://127.0.0.1:8081/consumer/feign/depart/get/1 不带source 第二次被限流

在这里插入图片描述
测试2:http://127.0.0.1:8081/consumer/feign/depart/get/1?source=Service-A 带Serive-A 第二次被限流

在这里插入图片描述

测试3:http://127.0.0.1:8081/consumer/feign/depart/get/1?source=Service-B 带Service-B 第三次被限流
在这里插入图片描述

黑白名单流控

代表只有Service-A这类资源才能访问某个资源在这里插入图片描述

热点参数限流

顾名思义 秒杀商品适用于这个规则 比较简单

系统自适应限流

Sentinel 系统自适应限流对应用级别入口流量进行整体控制,结合应用的 Load、CPU 使用率、平均RT、入口 QPS 和入口并发线程数等几个维度的监控指标,通过自适应的流控策略,让系统的入口流量和系统的负载达到一个平衡,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。 由于该限流方式中阈值的设置需要很多系统软硬件相关的数据,而与代码关系不大,所以这种限流方式一般是由运维来设置的。

(1) 系统负载 Load
*该模式仅对Linux/Unix-like 系统生效。当系统 CPU 最近一分钟的负载量load1 超过了设置的阈值时会触发系统保护,即对再来的请求进行限流处理。这个阈值就是系统负载容量,系统容量可以由maxQps minRt 估算得出。不过,也可以通过 CPU cores * 2.5 计算出其参考数值

(2) CPU 使用率
当系统 CPU 使用率超过阈值即触发系统保护(取值范围 0.0-1.0),比较灵敏。

(3) 平均响应时间 RT
当对当前应用上所有入口流量的平均RT 达到阈值时触发系统保护,单位是毫秒。

(4) 并发线程数
当对当前应用的所有入口流量进行处理的所有线程数量达到阈值时触发系统保护。

(5) 入口 QPS
当对当前应用的所有入口流量的总 QPS 达到阈值时触发系统保护。

网关限流

用的比较少 下次需要单独写

动态规则扩展

将流控规则配置到配置中心 方便动态修改
1.新增依赖

    <!-- sentinel数据源 nacos的支持 依赖 -->
    <dependency>
        <groupId>com.alibaba.csp</groupId>
        <artifactId>sentinel-datasource-nacos</artifactId>
    </dependency>
    <!-- sentinel依赖 -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
    </dependency>

    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    </dependency>
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>

2.配置文件

server:
  port: 8081

spring:
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
    sentinel:
      transport:
        dashboard: 127.0.0.1:8888
        port: 8888
        eager: true
      #配置sentinel数据源
      datasource:
        my-ds-flow:
          nacos:
            #nacos地址
            serverAddr: localhost:8848
            #配置文件名字
            dataId: sxw-sentinel-nacos-rule
            #规则类型 flow-流控
            ruleType: flow
            #配置文件类型
            dataType: json
  application:
    name: sxw-consumer-nacos-sentinel

3.nacos建立配置文件
在这里插入图片描述
grade代表使用qps限流 count代表阈值 具体参考这个类 com.alibaba.csp.sentinel.slots.block.RuleConstant

[
    {
        "resource": "nacosConfigFlowRule",
        "limitApp": "default",
        "grade": 1,
        "count": 3
    }
]

4.可以试下再配置中心修改后 控制台也会同步更新规则

  • 1
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值