2022年最新的SpringCloud(H版&Alibaba)技术(高级部分,熔断与限流【Sentinel】)

导读:本篇文章讲解 2022年最新的SpringCloud(H版&Alibaba)技术(高级部分,熔断与限流【Sentinel】),希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

三、SpringCloud Alibaba Sentinel实现熔断和限流

简介

在这里插入图片描述
官网:https://github.com/alibaba/sentinel
中文版:https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8D

下载

下载地址:https://github.com/alibaba/Sentinel/releases
在这里插入图片描述

初始化演示工程

在这里插入图片描述
1.新建模块cloudalibaba-sentinel-service8401
2.pom

<dependencies>
    <!-- SpringCloud ailibaba nacos-->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <!-- SpringCloud ailibaba sentinel-datasource-nacos 持久化需要用到-->
    <dependency>
        <groupId>com.alibaba.csp</groupId>
        <artifactId>sentinel-datasource-nacos</artifactId>
    </dependency>
    <!-- SpringCloud ailibaba sentinel-->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!--监控-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <!--热部署-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

3.Yml文件

server:
  port: 8401

spring:
  application:
    name: cloudalibaba-sentinal-service
  cloud:
    nacos:
      discovery:
        #Nacos服务注册中心地址(改成自己的服务器ip地址,本地用localhost‍)
        server-addr: 10.211.55.26:8848
    sentinel:
      transport:
        #配置Sentin dashboard地址(改成自己的服务器ip地址,本地用localhost‍)
        dashboard: 10.211.55.26:8858
        # 默认8719端口,假如被占用了会自动从8719端口+1进行扫描,直到找到未被占用的 端口
        port: 8719
        

management:
  endpoints:
    web:
      exposure:
        include: '*'

4.主启动类、

@EnableDiscoveryClient
@SpringBootApplication
public class MainApp8401 {

    public static void main(String[] args) {
        SpringApplication.run(MainApp8401.class, args);
    }
}

5.Controller

@RestController
public class FlowLimitController {

    @GetMapping("/testA")
    public String testA() {
        return "----testA";
    }

    @GetMapping("/testB")
    public String testB() {
        return "----testB";
    }

}

6.测试,启动8401,然后刷新sentinel后台页面(因为sentinel采用懒加载策略)
http://localhost:8401/testA;http://localhost:8401/testB
在这里插入图片描述

流控规则

在这里插入图片描述
流量控制:
在这里插入图片描述
可在流控规则处新建;
在这里插入图片描述
也可族点链路处指定添加
在这里插入图片描述在这里插入图片描述
在这里插入图片描述

阀值类型

QPS与线程数的区别

  • qps :当调用该api的QPS达到阈值的时候,进行限流
  • 线程数:当调用该api的线程数达到阈值的时候,进行限流
    在这里插入图片描述

流控模式

在这里插入图片描述

直接

在这里插入图片描述

关联

在这里插入图片描述
在这里插入图片描述
此时不管调用多少次A都不会限流,而此时超过1秒调用1次B,则会限流A;使用Postman进行多次访问。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

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

链路

在网上关于链路的用于下面这个例子,然而显示不了限流的效果
新建一个TestService

@Service
public class TestService {

    @SentinelResource("getTest")
    public void getTest(){
    System.out.println("getTest...");
    }

}

然后再FlowLimitController添加修改

    @Resource
    TestService testService;
    
    @GetMapping("/testA")
    public String testA() {
        testService.getTest();
        return "----testA";
    }

    @GetMapping("/testB")
    public String testB() {
        testService.getTest();
        return "----testB";
    }

然后重新启动8401,输入http://localhost:8401/testA和http://localhost:8401/testB
在这里插入图片描述
给get Test添加流控链路规则
在这里插入图片描述

流控效果

  • 流控效果只有QPS有,线程数没有
    在这里插入图片描述
快速失败

在这里插入图片描述

预热(warm up)

在这里插入图片描述在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
然后输入http://localhost:8401/testA,不停刷新,前5秒会限流,5秒后只要每秒不超过10个请求,就不会限流

排队等待

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在testB方法添加

log.info(Thread.currentThread().getName() + "\t ...testB");

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

  • postman发起的每秒10个请求进行排队,testB每秒处理一个请求。
    在这里插入图片描述

降级规则

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

降级策略实战

在这里插入图片描述

RT

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在FlowLimtController中添加

    @GetMapping("/testD")
    public String testD() {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("testD 测试RT");
        return "----testD";
    }

启动8401,在浏览器输入http://localhost:8401/testD,然后再sentinel设置testD降级规则
在这里插入图片描述
请求处理完成的时间为200毫秒(阈值),超过这个时间熔断降级进入时间窗口期不处理请求,1秒后退出时间窗口期,继续处理请求。(前提是一秒请求数超过5个,如果请求数没超过5个,就算请求处理的时间超过阈值也不会熔断降级)

  • 用jmeter进行压力测试
    在这里插入图片描述
    在这里插入图片描述在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
异常比例

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
修改testD

    @GetMapping("/testD")
    public String testD() {
        log.info("testD 异常比例");
        int age = 10 / 0;   //百分百出错

        return "----testD";
    }

启动8401,浏览器输入http://localhost:8401/testD,然后在后台设置testD的降级规则。
在这里插入图片描述
处理请求出错超过0.2(20%,阈值),熔断降级,进入时间窗口期不处理请求,3秒后退出时间窗口期继续处理请求。(前提也是每秒请求数超过5个,防止不会熔断降级)

  • 在用Jemeter进行测试
    在这里插入图片描述
    在这里插入图片描述
异常数

在这里插入图片描述
因为是按照一分钟内的异常数对阈值进行比较,如果时间窗口小于60秒,可能会再次进入熔断状态。所以时间窗口一定要大于等于60秒。
在这里插入图片描述
在FlowLimitController里添加:“

    @GetMapping("/testE")
    public String testE() {
        log.info("testE 测试异常数");
        int age = 10 / 0;
        return "----testE 测试异常数";
    }

在这里插入图片描述
启动8401,浏览器输入http://localhost:8401/testE,然后在后台设置testE的降级规则
在这里插入图片描述
一分钟内,如果访问处理出现异常的次数超过5次,熔断降级,进入时间窗口期不处理请求,61秒后退出时间窗口期继续处理请求。(时间窗口必须大于等于60秒,防止再次熔断降级)
刷新6次,http://localhost:8401/testE
在这里插入图片描述

热点Key限流

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

  • 在FlowLimitController中添加:
    @GetMapping("/testHotKey")
    @SentinelResource(value = "testHotKey", blockHandler = "deal_testHotKey")
    public String testHotKey(@RequestParam(value = "p1", required = false)String p1,
                             @RequestParam(value = "p2", required = false)String p2) {
        return "----testHotKey";
    }
    
    //兜底方法
    public String deal_testHotKey(String p1, String p2, BlockException exception) {
        // sentinel的默认提示都是: Blocked by Sentinel (flow limiting)
        return "----deal_testHotKey, o(╥﹏╥)o";
    }

重启8401,浏览器输入http://localhost:8401/testHotKey,然后在后台对testHotKey进行热点规则配置。

在这里插入图片描述
如果每秒的访问请求带有索引为0的参数的数量超过1,进入统计窗口期,然后调用兜底方法,1秒后退出统计窗口期,继续处理请求。
http://localhost:8401/testHotKey?p1=1
http://localhost:8401/testHotKey?p1=1&p2=2
在这里插入图片描述
在这里插入图片描述
如果设置热点规则,而@SentinelResourece注解里没有明确指明blockHandler兜底方法,就会直接把错误页面信息打印到前台。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
参数例外项:
在这里插入图片描述
高级选项只有在热点规则里有,族点链路无法配置高级选项

在这里插入图片描述
http://localhost:8401/testHotKey?p1=1
在这里插入图片描述
http://localhost:8401/testHotKey?p1=vip
在这里插入图片描述
给testHotKey方法添加int i = 1 / 0;异常。
然后重新测试,发现兜底方法不适用于异常,有异常会直接打印到页面。
在这里插入图片描述在这里插入图片描述

系统规则

在这里插入图片描述
在这里插入图片描述
针对整个系统的,每秒访问量超过1(阈值),限流。
http://localhost:8401/testA
在这里插入图片描述
http://localhost:8401/testB

在这里插入图片描述

@SentinelResource

在这里插入图片描述

按资源名称限流+后续处理

资源名称:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在pom添加

 <!--换成你们直接的包名-->
 <!-- 引用自己定义的api通用包,可以使用Payment支付Entity -->
 <dependency>
     <groupId>com.angenin.springcloud</groupId>
     <artifactId>cloud-api-commons</artifactId>
     <version>${project.version}</version>
 </dependency>

新建RateLimitController:

@RestController
public class RateLimitController {

    @GetMapping("/byResource")
    @SentinelResource(value = "byResource",blockHandler = "handleException")
    public CommonResult byResource() {
        return  new CommonResult(200,"按照资源名称限流测试",new Payment(2020L,"serial001"));
    }

	//兜底方法
    public CommonResult handleException(BlockException exception) {
        return  new CommonResult(444,exception.getClass().getCanonicalName() + "\t 服务不可用");
    }
}

启动8401
http://localhost:8401/buResource
在这里插入图片描述
用资源名称进行配置兜底方法才生效。
在这里插入图片描述
多次刷新页面
在这里插入图片描述
在这里插入图片描述
按照URL地址限流+后续处理
URL地址:
在这里插入图片描述
在这里插入图片描述
在RateLimitController中添加

    @GetMapping("/rateLimit/byUrl")
    @SentinelResource(value = "byUrl")	//没有兜底方法,系统就用默认的
    public CommonResult byUrl() {
        return  new CommonResult(200,"按照byUrl限流测试",new Payment(2020L,"serial002"));
    }

启动8401http://localhost:8401/rateLimit/buUrl
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

上面兜底方案面临的问题

  • 1.系统默认的,没有体现我们自己的业务要求
  • 2.依照现有条件,我们自定义处理的方法和业务代码耦合在一块,不够直观。
  • 3.每个业务方法都添加一个兜底的,代码冗余
  • 4.全局统一的处理方法没有体现

客户自定义限流处理逻辑

在这里插入图片描述
新建myhandleCustomerBlockHandle自定义限流处理类

public class CustomerBlockHandler {

    public static CommonResult handlerException(BlockException exception) {
        return  new CommonResult(444,"按照客户自定义限流测试,Glogal handlerException ---- 1");
    }

    public static CommonResult handlerException2(BlockException exception) {
        return  new CommonResult(444,"按照客户自定义限流测试,Glogal handlerException ---- 2");
    }
}

在RateLimitController中添加:

    //CustomerBlockHandler
    @GetMapping("/rateLimit/customerBlockHandler")
    @SentinelResource(value = "customerBlockHandler",
            blockHandlerClass = CustomerBlockHandler.class, blockHandler = "handlerException2")
    public CommonResult customerBlockHandler() {
        return  new CommonResult(200,"按照客户自定义限流测试",new Payment(2020L,"serial003"));
    }

启动8401http://localhost:8401/rateLimit/customerBlockerHandle
在这里插入图片描述
用资源名称进行配置兜底方法才生效
在这里插入图片描述

  • 进行多次刷新后
    在这里插入图片描述
    在这里插入图片描述

更多注解属性的说明

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

服务熔断

在这里插入图片描述

Ribbon系列

在这里插入图片描述在这里插入图片描述
在这里插入图片描述
1.新建模块cloudalibaba-provider-payment9003
2.pom

<dependencies>
    <!-- SpringCloud ailibaba nacos-->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <!-- SpringCloud ailibaba sentinel-->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
    </dependency>
    <!-- 引用自己定义的api通用包,可以使用Payment支付Entity -->
    <dependency>
        <groupId>com.angenin.springcloud</groupId>
        <artifactId>cloud-api-commons</artifactId>
        <version>${project.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!--监控-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <!--热部署-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

3.yml

server:
  port: 9003

spring:
  application:
    name: nacos-payment-provider
  cloud:
    nacos:
      discovery:
        server-addr: 10.211.55.26:8848  #nacos

management:
  endpoints:
    web:
      exposure:
        include: '*'

4.主启动类

@SpringBootApplication
@EnableDiscoveryClient
public class PaymentMain9003 {

    public static void main(String[] args) {
        SpringApplication.run(PaymentMain9003.class,args);
    }
}

5.controller

@RestController
public class PaymentController {

    @Value("${server.port}")    //spring的注解
    private  String serverPort;

    public static HashMap<Long, Payment> map = new HashMap<>();
    static {
        map.put(1L,new Payment(1L,"1111"));
        map.put(2L,new Payment(2L,"2222"));
        map.put(3L,new Payment(3L,"3333"));
    }
    @GetMapping(value = "/paymentSQL/{id}")
    public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id) {
        Payment payment = map.get(id);
        CommonResult<Payment> result = new CommonResult<>(200,"from mysql,serverPort: " + serverPort,payment);
        return result;
    }
}

新建消费者
在这里插入图片描述
1.新建模块cloudalibaba-consumer-nacos-order84
2.pom

<dependencies>
    <!-- SpringCloud ailibaba nacos-->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <!-- SpringCloud ailibaba sentinel-->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
    </dependency>
    <!-- 引用自己定义的api通用包,可以使用Payment支付Entity -->
    <dependency>
        <groupId>com.angenin.springcloud</groupId>
        <artifactId>cloud-api-commons</artifactId>
        <version>${project.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!--监控-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <!--热部署-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

3.yml

server:
  port: 84

spring:
  application:
    name: nacos-order-consumer
  cloud:
    nacos:
      discovery:
        server-addr: 10.211.55.26:8848  #nacos
    sentinel:
      transport:
        dashboard: 10.211.55.26:8858    #sentinel
        port: 8719

#消费者将去访问的微服务名称
server-url:
  nacos-user-service: http://nacos-payment-provider

#激活Sentinel对Feign的支持
feign:
  sentinel:
    enabled: true

4.主启动类

@EnableDiscoveryClient
@SpringBootApplication
@EnableFeignClients
public class OrderMain84 {

    public static void main(String[] args) {
        SpringApplication.run(OrderMain84.class,args);
    }
}

5.config

@Configuration
public class ApplicationContextConfig {

    @Bean
    @LoadBalanced
    public RestTemplate getRestTemplate() {
        return new RestTemplate();
    }
}

6.controller

@RestController
@Slf4j
public class CircleBreakerController {

    public static  final  String SERVICE_URL = "http://nacos-payment-provider";

    @Resource
    private RestTemplate restTemplate;

    @RequestMapping("/consumer/fallback/{id}")
    @SentinelResource(value = "fallback")   //没有配置
    public CommonResult<Payment> fallback(@PathVariable Long id) {
        CommonResult<Payment> result = restTemplate.getForObject(
                SERVICE_URL + "/paymentSQL/" + id,CommonResult.class,id);

        if(id == 4){
            throw new IllegalArgumentException("IllegalArgument,非法参数异常...");
        }else if(result.getData() == null) {
            throw new NullPointerException("NullPointerException,该ID没有对应记录,空指针异常");
        }

        return  result;
    }
    
}

在这里插入图片描述
启动9003,9004,84;http://localhost:84/consumer/fallback/1
在这里插入图片描述
修改84的CircleBreakerController类的fallback方法中的@SentinelResource注解,并在类中添加

    @SentinelResource(value = "fallback",fallback ="handlerFallback")   //只配置fallback(只负责业务异常)


    //fallback兜底
    public CommonResult handlerFallback(@PathVariable Long id,Throwable e) {
        Payment payment = new Payment(id,"null");
        return new CommonResult(444,"异常handlerFallback,exception内容: " + e.getMessage(), payment);
    }

重启项目
访问http://localhost:84/consumer/fallback/1,然后在sentinel后台进行配置。
在这里插入图片描述
http://localhost:84/consumer/fallback/5
在这里插入图片描述
在这里插入图片描述
配置fallback和blockhandle(两个都配置)
修改@SentinelResource注解

    @SentinelResource(value = "fallback", fallback ="handlerFallback", blockHandler = "blockHandler")

重启项目,输入http://localhost:84/consumer/fallback/1,然后到后台配置
在这里插入图片描述
http://localhost:84/consumer/fallback/1多次刷新执行blockHandler兜底方法。
在这里插入图片描述
当@SentinelResource注解fallback和blockHandler都指定后,然后同时符合,优先执行blockHandler兜底方法。

Feign系列

在这里插入图片描述
修改84
1.pom

<!--前面已添加了-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

2.yml

#前面也已经添加了
#激活Sentinel对Feign的支持
feign:
  sentinel:
    enabled: true

3.主启动类加上@EnableFeignClients
4.service PaymentService接口

@FeignClient(value = "nacos-payment-provider",fallback = PaymentFallbackService.class)
public interface PaymentService {

    @GetMapping(value = "/paymentSQL/{id}")
    public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id);

}

PaymentFallBackService类

@Component
public class PaymentFallbackService implements PaymentService{
    
    @Override
    public CommonResult<Payment> paymentSQL(Long id) {
        return new CommonResult<>(444,"服务降级返回,---PaymentFallbackService",new Payment(id,"ErrorSerial"));
    }
}

controller中的CircleBreakerController类

    //======= OpenFeign
    @Resource
    private PaymentService paymentService;

    @GetMapping(value = "/consumer/paymentSQL/{id}")
    public CommonResult< Payment > paymentSQL(@PathVariable("id") Long id){
        return paymentService.paymentSQL(id);
    }

···
![在这里插入图片描述](https://img-blog.csdnimg.cn/5701c059c8ef45cab3adfb4d1c873afc.png)

规则持久化

在这里插入图片描述
修改8701

  • 持久化会用到配置源
    1.pom
	<!--之前添加了-->
    <!-- SpringCloud ailibaba sentinel-datasource-nacos 持久化需要用到-->
    <dependency>
        <groupId>com.alibaba.csp</groupId>
        <artifactId>sentinel-datasource-nacos</artifactId>
    </dependency>

2.yml

	      datasource:
	        ds1:
	          nacos:
	            server-addr: 10.211.55.26:8848  #nacos
        		dataId: ${spring.application.name}
	            groupId: DEFAULT_GROUP
	            data-type: json
	            rule-type: flow

feign:
  sentinel:
    enabled: true #激活Sentinel 对Feign的支持

在这里插入图片描述
3.nacos后台添加配置
在这里插入图片描述

[
    {
        "resource": "/rateLimit/byUrl",
        "limitApp": "default",
        "grade": 1,
        "count": 1,
        "strategy": 0,
        "controlBehavior": 0,
        "clusterMode": false
    }
]	

在这里插入图片描述
在这里插入图片描述
启动8401
http://localhost:8401/rateLimit/byUrl多次刷新
在这里插入图片描述
停止8401
在这里插入图片描述
再次启动8401,因为sentinel是懒加载模式,所以需要先访问http://localhost:8401/rateLimit/byUrl,然后查看sentinel后台。
在这里插入图片描述
多次刷新http://localhost:8401/rateLimit/byUrl
在这里插入图片描述
实现sentinel配置的持久化。

学习视频(p111-p137):https://www.bilibili.com/video/BV18E411x7eT?p=111

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/102021.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!