分布式系统并发请求限流平台Sentinel功能特性调研-集成测试(中篇)

摘 要
随着近些年系统稳定性要求越来越高,而系统限流则是其中提高系统稳定性的手段之一,而在众多限流平台中Sentinel凭着丰富功能特性和多次阿里双十一的线上实践,成为最热门限流平台之一,本文就Sentinel相关特性进行分析并测试并对底层原理进行分析,为后续相关系统建设积累经验。

关 键 词:
限流;熔断;自适应保护;Sentinel;流量控制

3. 应用集成测试
因Sentinel其他功能特性都是围绕着流量控制、熔断降级、自适应保护这三个点扩展而来,所以本次主要验证此3个功能点

3.1 测试环境准备
3.1.1 软件测试环境说明
硬件环境

中央处理器:Intel Core i7 2.5 GHz

内存:16GB

硬盘:512GB

显示器:NVIDIA GeForce GT 750M 2 GB 图形卡

网卡:100Mbit/s

软件环境

操作系统: mac os 10.14.6、Ubuntu Linux 14.04

Zookeeper:3.4.6

开发软件:IDEA 2020.3

JDK版本:1.8.0_151

Sentinel版本:1.8.1

3.1.2 测试架构概述
为了使调研结果能覆盖更多真实场景,因此采用单节点和多节点集成Sentinel进行测试,以下是单节点和多节点测试架构。

3.1.2.1 单节点测试架构
见图4单节点测试架构图,主要分为4部分,分别为

应用系统:
此系统集成Sentinel相关jar包,并提供流量控制、熔断降级、自适应保护测试接口

测试脚本:
用于模拟用户请求,调用应用系统提供的测试接口,三个文件分别对应三个功能接口

Sentinel控制台服务端:
提供了机器发现以及健康管理、流量监控、限流规则管理与下发的功能,应用系统启动时会指定此服务的地址,会把相关信息进行上报,上报之后控制台也有了应用的地址信息,那么在规则下发时就能定位到具体服务实例

Sentinel控制台WEB端
Sentinel控制台对应的WEB端,提供了图形化的界面,对应用系统服务进行规则配置、以及监控应用系统运行情况

​ 图4 单节点测试架构,sentinel-springbot-demo为应用系统名称

3.1.2.2 多节点测试架构
见图5,多节点只是扩展应用系统部署数量以支撑更多的请求,其他配置与图4单节点测试架构一样

​ 图5 多节点测试架构

3.1.3 测试代码说明
3.1.3.1 应用系统配置说明
见代码块1,在应用系统的application.properties文件中指定了应用的端口和名称,后续测试会请求此端口进行测试

#应用名称
spring.application.name=sentinel-springboot-demo
#应用端口
server.port=****8094
1
2
3
4
​ 代码块1 应用系统部分配置

3.1.3.2 流量控制核心代码说明
见代码块2,在FlowService中,查看SentinelResource注解,定义资源名称为testFlow,如果未进行流控,则进入testFlow方法,返回passed,如果进行了流控进入exceptionFlowHandler方法返回block

@Service
@Slf4j
public class FlowService {


/**
 * @description:流量控制测试
 * @param: origin
 * @return: java.lang.String
 */
@SentinelResource(value = "testFlow", entryType = EntryType.IN, blockHandler = "exceptionFlowHandler")
public String testFlow(String origin) {
    //请求未被限流则进入此方法,返回passed结果
    System.out.println(String.format("Passed for resource testFlow, requestDate:%s , origin is %s", LocalDateTime.now(), origin));
    return "passed";
}


public String exceptionFlowHandler(String origin, BlockException ex) {
    //请求被限流则进入此方法,返回block结果
    System.out.println(String.format("request block error,requestDate:%s , param:%s ", LocalDateTime.now(), origin));
    return "block";

}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
​ 代码块2 流量控制核心测试代码

见代码块3,在TestController中定义了testFlow接口,url为/testFlow,来接收http请求进行测试

@RestController
public class TestController {

@Autowired
private FlowService demoService;
 
/**
 *
 * @description:限流测试
**/
@GetMapping(value = "/testFlow")
public String testFlow(@RequestParam("origin") String origin) {
  return demoService.testFlow(origin);
}

}


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
​ 代码块3 流量控制测试接口

3.1.3.3 系统降级核心代码说明
见代码块4,在FlowService中,通过SentinelResource注解,定义资源名称为queryUserInfoSlowRequest,同时queryUserInfoSlowRequest方法为核心业务处理方法,在此方法中通过随机数来随机模拟接口执行耗时不稳定,执行耗时区间在40-60ms之间,具体查看sleep方法,如果大于50ms则进行次数记录,以便于测试结果比对,如果系统进行了熔断则进入exceptionHandler方法,等待一定时间后进入熔断恢复逻辑,并重置进入熔断的时间,同时为了验证结果的便捷性,提供了restSlowValue方法,对相关参数进行重置,这样可以快速验证降级结果

@Service
@Slf4j
public class FlowService {
    double count = 0;
    double slowRatioThreshold = 0;
    double slowRatioThresholdCount = 0;
    long failStartTime = 0;


/**
     * @description:熔断降级测试
     * @param:
     * @return: java.lang.String
     */
    @SentinelResource(value = "queryUserInfoSlowRequest", blockHandler = "exceptionHandler")
    public String queryUserInfoSlowRequest() {
        long start = System.currentTimeMillis();
        ++count;
        if (failStartTime != 0) {
            System.out.println("request fusing recovery time: " + (System.currentTimeMillis() - failStartTime) + " ms");
            failStartTime = 0;
            sleep(1000 * 2);
        }
        sleep(ThreadLocalRandom.current().nextInt(40, 60));
        long end = System.currentTimeMillis();
        long callTime = end - start;
        //调用时间
        if (callTime > 50) {
            slowRatioThresholdCount += 1;
        }
        slowRatioThreshold = slowRatioThresholdCount / count;
        System.out.println("request success time: " + (end - start) + " ms count:" + count + "  slowRatioThreshold:" + slowRatioThreshold);
        return "success";
    }


    // Block 异常处理函数,参数最后多一个 BlockException,其余与原函数一致.
    public String exceptionHandler(BlockException ex) {
        if (failStartTime == 0) {
            failStartTime = System.currentTimeMillis();
        }
        System.out.println("request fusing time: " + LocalDateTime.now() + " ms");
        sleep(ThreadLocalRandom.current().nextInt(5, 10));
        return "Oops ";
    }
    //接收指定毫秒数进行休眠处理
    public static void sleep(int timeMs) {
        try {
            TimeUnit.MILLISECONDS.sleep(timeMs);
        } catch (InterruptedException e) {
            // ignore
        }
    }

/**
 * @description:重置降级的相关参数,主要为了更好复现熔断结果
 * @return: java.lang.String
 */
public String restSlowValue() {
    count = 0;
    slowRatioThreshold = 0;
    slowRatioThresholdCount = 0;
    failStartTime = 0;
    return "success";
}

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
​ 代码块4 系统降级核心测试代码

见代码块5,定义queryUserInfoSlowRequest接口,url为/queryUserInfoSlowRequest,接收外部http请求进行测试,也提供了辅助测试接口restSlowValue接口,url为/restSlowValue,便于对相关参数进行重置

RestController
public class TestController {

    @Autowired
    private FlowService demoService;

    /**
     * @description:熔断降级测试
     * @author: dongweizhao
     * @date: 2021/6/30 3:07 下午
     * @param:
     * @return: void
     */
    @RequestMapping(value = "/queryUserInfoSlowRequest")
    public String queryUserInfoSlowRequest() {
        return demoService.queryUserInfoSlowRequest();
    }

/**
 * @description:重置降级的相关参数,主要为了更好复现熔断结果
 * @return: void
 */
@GetMapping(value = "/restSlowValue")
public String restSlowValue() {
    return demoService.restSlowValue();
}


}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
​ 代码块5 系统降级测试接口

3.1.3.4 系统自适应保护核心代码说明
见代码块6,系统自适应保护是Sentinel提供的整个系统级的控制功能,只需要在控制进行配置参数,请求任意接口测试即可,秉着测试便捷性、覆盖更多场景,后续此接口会结合其他接口进行自适应保护测试,如下代码表示如果系统未进行保护则进入testSysProtect方法,打印对应字符串,返回passed,否则直接抛出系统异常

@Service
@Slf4j
public class FlowService {
/**
 * @description:系统自适应保护测试接口
 * @author: dongweizhao
 * @date: 2022/1/29 3:03 下午
 * @param: origin
 * @return: java.lang.String
 */
public String testSysProtect(String origin) {

    System.out.println(String.format("Passed for resource testSysProtect, requestDate:%s , origin is %s", LocalDateTime.now(), origin));
    return "passed";
}

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
​ 代码块6 系统自适应保护测试代码

见代码块7,定义testSysProtect接口,url为/testSysProtect,接收外部http请求进行测试

@RestController
public class TestController {

   @Autowired
   private FlowService demoService;
   /**
    * @description:自适应保护接口
    * @author: dongweizhao
    * @date: 2021/6/30 3:07 下午
    * @param:
    * @return: void
    */
   @GetMapping(value = "/testSysProtect")
   public String testSysProtect(@RequestParam("origin") String origin) {
       return demoService.testSysProtect(origin);
   }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
​ 代码块7 系统自适应保护测试接口

3.1.3.5 测试脚本说明
见图6,另存附录A中test.zip,并进行解压,会看到解压后以下脚本文件,此脚本由shell脚本语言编写,此脚本在Linux、Macos系统中通过终端命令行即可执行,Windows需要安装git或者Linux、Macos虚拟机,本论文使用Macos系统进行执行,下面就脚本一一说明

​ 图6 附录A test.zip包含的测试脚本文件

3.1.3.5.1 start_dashboard.sh
此脚本为启动Sentinel控制台脚本,可以看到设置启动端口为8080,名称为sentinel-dashboard,并且会在脚本当前目录执行sentinel-dashboard-1.8.1.jar,所以控制台包必须与此脚本在同一目录

#!/bin/bash
nohup java -Dserver.port=****8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.1.jar &
1
2
3.1.3.5.2 start_macos.sh
此脚本为在Macos系统中启动应用系统,在启动命令中指定了Sentinel控制台的ip和端口,并且指定应用名称为sentinel-springboot-demo,并且会在脚本当前目录执行sentinel-springboot-demo.jar,所以应用系统包必须与此脚本在同一目录

#!/bin/bash
nohup java  -Dcsp.sentinel.dashboard.server=127.0.0.1:8080 -Dproject.name=sentinel-springboot-demo -jar sentinel-springboot-demo.jar &
1
2
3.1.3.5.3 start_ubuntu.sh
此脚本为在Ubuntu系统中启动应用系统,在启动命令中指定了Sentinel控制台的ip和端口,因为控制台是在Masos系统进行了部署,所以ip地址需要指定Macos系统地址,其他与3.1.3.5.2章节start_macos.sh脚本一致

#!/bin/bash
nohup java  -Dcsp.sentinel.dashboard.server=10.211.55.2:8080 -Dproject.name=sentinel-springboot-demo -jar sentinel-springboot-demo.jar &

1
2
3
4
5
3.1.3.5.4 queryUserInfoSlowRequest_local.sh
此脚本为熔断降低测试脚本,名称中local本地测试,即在Macos系统中进行测试,结合3.1.3.3章节中系统降级代码分析,首先请求了restSlowValue接口重置相关参数,紧接着在循环中持续调用queryUserInfoSlowRequest接口进行测试,注意:内容的循环次数为1000000只是为了模拟用户不间断的请求,没有其它含义

#!/bin/bash
### 基于分组规则测试脚本
curl http://127.0.0.1:8094/restSlowValue;
for (( i = **0; i < **1000000; i++ )); do
 curl http://127.0.0.1:8094/queryUserInfoSlowRequest
done

1
2
3
4
5
6
7
8
9
3.1.3.5.5 queryUserInfoSlowRequest_remote.sh
此脚本为在Ubuntu系统测试脚本与queryUserInfoSlowRequest_local.sh作用等同,唯一的区别就是IP不同,IP地址为Ubuntu部署的应用系统IP地址

#!/bin/bash
curl http://10.211.55.5:8094/restSlowValue;
### 基于分组规则测试脚本
for (( i = **0; i < **1000000; i++ )); do
 curl http://10.211.55.5:8094/queryUserInfoSlowRequest
done
1
2
3
4
5
6
3.1.3.5.6 testFlow_local.sh
此脚本为流量控制测试脚本,名称中local本地测试,即在Macos系统中进行测试,结合3.1.3.2章节中流量控制代码分析,在循环中调用testFlow接口进行测试,注意:内容的循环次数为1000000只是为了模拟用户不间断的请求,没有其它含义,请求参数origin无任何意义

#!/bin/bash
### 基于分组规则测试脚本
for (( i = **0; i < **1000000; i++ )); do
curl http://127.0.0.1:8094/testFlow?origin=appA
done

1
2
3
4
5
6
3.1.3.5.7 testFlow_remote.sh
此脚本为在Ubuntu系统测试脚本与3.1.3.5.6 testFlow_local.sh作用等同,唯一的区别就是IP不同,IP地址为Ubuntu部署的应用系统IP地址

#!/bin/bash
### 基于分组规则测试脚本
for (( i = **0; i < **1000000; i++ )); do
curl http://10.211.55.5:8094/testFlow?origin=appB

done

1
2
3
4
5
6
7
3.1.3.5.8 testSysProtect_local.sh
此脚本为自适应保护测试脚本,名称中local本地测试,即在Macos系统中进行测试,结合****3.1.3.4自适应保护代码分析,在循环中调用testSysProtect接口进行测试,注意:内容的循环次数为1000000只是为了模拟用户不间断的请求,没有其它含义,求参数origin无任何意义

#!/bin/bash
### 基于分组规则测试脚本
for (( i = **0; i < **1000000; i++ )); do
curl http://127.0.0.1:8094/testSysProtect?origin=appA
done
1
2
3
4
5
3.1.3.5.9 testSysProtect_remote.sh
此脚本为在Ubuntu系统测试脚本与testSysProtect_local.sh作用等同,唯一的区别就是IP不同,此地址为Ubuntu部署的应用系统IP地址

#!/bin/bash
### 基于分组规则测试脚本
for (( i = **0; i < **1000000; i++ )); do
curl http://10.211.55.5:8094/testSysProtect?origin=appA
done
1
2
3
4
5
3.1.4 控制台启动
3.1.4.1 控制台概述
此控制台为Sentinel官方提供的一个轻量级开源控制台,他提供了应用机器发现以及健康管理、流量监控、限流规则管理与下发

3.1.4.2 下载控制台jar
https://github.com/alibaba/Sentinel/releases/download/1.8.1/sentinel-dashboard-1.8.1.jar ,点击以上链接从github进行下载

3.1.4.3 启动控制台
下载附录A并解压test压缩包,拷贝****sentinel-dashboard-1.8.1.jar至test文件夹内,在终端执行"sh start_dashboard.sh"脚本启动控制台

sh start_dashboard.sh
1
3.1.4.4 查看启动结果
见图7,执行"tail -f nohup.out" 查询Sentinel控制台启动启动日志,如果出现Started字样,则表示启动成功

 图7 Sentinel控制台启动日志

见图8图9,打开浏览器输入http://localhost:8080地址,即可打开控制台登录界面,输入sentinel/sentinel,即可登录成功。

​ 图8 Sentinel控制台登录界面

​ 图9 Sentinel登陆成功界面

3.1.5 应用系统启动
3.1.5.1 应用系统概述
此应用系统为了验证Sentinel特性,特此开发的测试系统,此系统基于spring-boot开发,他提供了本次论文要调研的功能特性相关集成示例和测试脚本,具体代码已存放在Github中,可自行下载,地址如下:https://github.com/dongweizhao/sentinel-springboot-demo/,为了论文调研方便性和结果的快速验证,基于部署包进行验证

3.1.5.2 应用系统部署包下载
https://github.com/dongweizhao/sentinel-springboot-demo/raw/ha/src/test/jar/sentinel-springboot-demo.jar ,点击以上链接从github进行下载

3.1.5.3 启动应用系统
拷贝sentinel-springboot-demo.jar至test文件夹内,在命令行执行"start_macos.sh"脚本启动

3.1.5.4 查看启动结果
见图10,执行"tail -f nohup.out" 查询启动日志,如果出现Started字样,则表示启动成功

​ 图10 应用系统启动成功日志

3.2 单节点集成限流测试
3.2.1 流量控制测试
在测试程序test_script目录下,分别执行testFlow_local.sh、testFlow_remote.sh脚本

3.2.1.1 执行测试脚本
首先在test_script目录下执行testFlow_local.sh,因为Sentinel是在接受到应用的第一次访问,才进行应用信息上报

sh testFlow_local.sh
1
3.2.1.2 规则配置
见图11,在控制台,点击流控规则菜单,进行限流规则配置,以下表示每秒最大通过请求为5

​ 图11 流量控制配置界面

3.2.1.3 测试结果
见图12,查看根据返回结果可以看到请求执行5次,则进行了限流

​ 图12 流量控制限流结果,passed表示未进行限流,block表示进行了限流

3.2.1.4 总结
结果符合预期,测试通过

3.2.2 熔断降级测试
由于在流量控制应用系统信息已上报至Sentinel控制台,所以省略前面几步,从配置规则开始

3.2.2.1规则配置
见图13,在降级规则菜单中新增如下规则,在2000ms内执行请求100次,如果接口执行时间大于50ms,并且比例超过0.2,进行熔断10s

​ 图13 限流降级配置界面

3.2.2.2执行测试脚本
在test_script目录下执行queryUserInfoSlowRequest_local.sh脚本

sh queryUserInfoSlowRequest_local.sh
1
3.2.2.3结果查看
执行"tail -f nohup.out "查看测试结果,见图14,可以看到次数100并且比例大于0.2时,进行了熔断

 图14 限流降级日志,count表示请求次数,slowRatioThreshold表示耗时大于50ms的阈值

见图15,熔断恢复时间在10s左右,没法100%保证10s,因为系统负载还有io等等原因,只能尽可能接近

 图15 熔断恢复日志,recovery time表示熔断时长

见图16,通过控制台图表可以看出熔断期间通过qps为0

 图16 Sentinel控制台中限流降级监控图表

3.2.2.4总结
结果符合预期,测试通过

3.2.3 系统自适应保护测试
3.2.3.1 规则配置
由于自适应保护是系统级的,需要对所有请求的接口进行请求限流,所以Sentinel对spring boot框架进行了适配,不需要单独为每个接口配置,具体原理会在分析章节进行剖析,因此在控制台选择对应维度进行配置即可,见图17,这里选择入口QPS进行测试,因为测试结果较为直观,qps设置为2

 图17 系统自适应保护配置界面

3.2.3.2 请求测试
3.2.3.2.1 单一接口请求测试
在test_script目录下执行testFlow_local.sh接口进行测试 ,见图18,执行的日志为1秒两次,多余的请求已在入口处进行拦截

​ 图18 单接口自适应保护测试日志

见图19,图表中也能看出来每秒通过QPS为2

​ 图19 Sentinel控制台中单接口自适应保护限流监控图表

3.2.3.2.2 多接口并发请求测试
在test_script目录下执行testFlow_local.sh testSysProtect_local.sh脚本

sh testFlow_local.sh 
sh testSysProtect_local.sh
1
2
见图20,查看2个接口执行的日志分别为1秒1次,总通过为1秒2次,多余的请求已在入口处进行拦截

 图20 多接口自适应保护测试日志

见图21,查看监控图表与每个接口通过qps为1,符合预期

​ 图21 Sentinel控制台中多接口自适应保护限流监控图表

3.2.3.3 结论
通过单一接口和多接口请求测试,通过qps都与配置qps值相等,测试结果符合预期。

3.3 多节点集成限流测试
3.3.1 环境准备
使用2台节点进行测试验证,分别为:Macos与Ubuntu虚拟机

3.3.1.1 部署Ubuntu系统测试程序
见图22,拷贝测试包至Ubuntu系统的指定目录,并进行启动

​ 图22 Ubuntu操作系统下应用系统启动日志

3.3.1.2 部署Macos系统测试程序
启动控制台程序
见图23,执行start_dashboard.sh脚本,启动控制台程序

​ 图23 Macos操作系统下Sentinel控制台启动日志

启动测试应用系统
见图24,执行start_macos.sh脚本,启动程序

​ 图24 Macos操作系统应用系统启动日志

3.3.2 流量控制测试
3.3.2.1 执行测试脚本
见图25,在Macos系统测试程序test_script目录下,分别执行testFlow_local.sh、testFlow_remote.sh脚本

​ 图25 执行流量控制测试脚本

3.3.2.2 规则配置
见图26,在控制台配置,点击流控规则菜单,选择对应节点进行限流规则配置,以下表示每秒最大通过请求为6

​ 图26 基于控制台配置流量控制

3.3.2.3 结果查看
见图27,Macos执行日志,同一秒只有6条“Passed for resource testFlow”开头日志

​ 图27 Macos系统中流量控制测试日志,“Passed for resource testFlow”表示放行请求

见图28,Ubuntu执行日志,同一秒只有6条“Passed for resource testFlow”开头日志

​ 图28 Ubuntu系统中流量控制测试日志

见图29,因为总共有2个节点,每个节点qps控制为6,控制台总通过qps为12

​ 图29 控制台流量控制监控报表

3.3.2.4 总结
结果符合预期,测试通过

3.3.3 熔断降级测试
3.3.3.1 执行测试脚本
见图30,在Macos系统测试程序test_script目录下,分别执行以下脚本

sh queryUserInfoSlowRequest_local.sh

sh queryUserInfoSlowRequest_remote.sh

1
2
3
4


​ 图30 执行熔断降级测试脚本

3.3.3.2 规则配置
见图31,在控制台配置,点击降级规则菜单,选择对应节点进行限流规则配置,在2000ms内执行请求50次,如果接口执行时间大于50ms,并且比例超过0.2,进行熔断10s

​ 图31 基于控制台配置熔断降级

3.2.3.3 结果查看
见图32,如下图Macos系统中执行到50次并且慢调用比例大于0.2进行了熔断10s

图32 Macos系统中熔断降级测试日志,count表示请求次数,slowRatioThreshold表示耗时大于50ms的阈值,recovery time表示熔断时长

见图33,执行到50次并且慢调用比例大于0.2进行了熔断10s

​ 图33 Ubunt系统中熔断降级测试日志

图34,通过控制台图表可以看出熔断期间通过qps为0

​ 图34 控制台熔断降级监控报表

3.3.3.4 总结
结果符合预期,测试通过

3.3.4 系统自适应保护测试
3.3.4.1 规则配置
见图35,选择不同进行配置,qps为2

​ 图35 基于控制台配置自适应保护

3.3.4.2 执行测试脚本
见图36,在Macos系统测试程序test_script目录下,分别执行testSysProtect_local.sh、testSysProtect_remote.sh、testFlow_local.sh、testFlow_remote.sh脚本

​ 图36执行自适应保护测试脚本

3.3.4.3 结果查看
见图37,Macos执行日志,每秒通过请求2个

​ 图37 Macos系统中自适应保护日志

见图38,Ubuntu执行日志,每秒通过请求2个

 图38 Ubuntu系统中自适应保护日志

见图39,Sentinel控制台实时统计结果,请求2个接口,每个接口qps为2,总qps为4

​ 图39 控制台熔断降级监控报表

3.3.4.4 总结
结果符合预期,测试通过

链接
分布式系统并发请求限流平台Sentinel功能特性调研(上篇)

作者微信

————————————————
版权声明:本文为CSDN博主「架构成长指南」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/dweizhao/article/details/125233227

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值