Spring cloud alibaba实战

这里不做过多理论说明,直接进入实战操作。

第一章:Nacos Discovery——服务发现与注册管理

  • Nacos Server启动。
    • 源码下载,下载地址https://gitee.com/mirrors/Nacos/repository/archive/2.1.0
    • 解压进入文件根目录,编辑pom.xml,配置本地及alibaba的maven仓库地址保存。(<url>file://xxx/Repository</url>改为自己本地仓库位置)
    • <repositories>
      <repository>
              <id>Localhost</id>
              <name>Localhost</name>
              <url>file://xxx/Repository</url>
          </repository>
          <repository>
              <id>alimaven</id>
              <name>aliyun maven</name>
              <url>https://maven.aliyun.com/nexus/content/repositories/central/</url>
              <releases>
                  <enabled>true</enabled>
              </releases>
              <snapshots>
                  <enabled>false</enabled>
              </snapshots>
          </repository>
      </repositories>
      <pluginRepositories>
          <pluginRepository>
              <id>alimaven</id>
              <name>aliyun maven</name>
              <url>https://maven.aliyun.com/nexus/content/repositories/central/</url>
              <releases>
                  <enabled>true</enabled>
              </releases>
              <snapshots>
                  <enabled>false</enabled>
              </snapshots>
          </pluginRepository>
          <pluginRepository>
              <id>Localhost</id>
              <name>Localhost</name>
              <url>file://xxx/Repository</url>
          </pluginRepository>
      </pluginRepositories>

    • 进入cmd,执行mvn -Dmaven.test.skip=true clean package命令进行编译。
    • 编译完成,进入\distribution\target\nacos-server-2.1.0\nacos\bin,打开cmd,执行startup.cmd -m standalone命令单机启动nacos server。
    • 启动之后,打开浏览器访问http://127.0.0.1:8848访问控制台,输入账号密码nacos登录进如控制台。
    • Nacos server启动完成。
  • 创建父工程alibaba-spring-cloud。
    • 创建maven工程,只保留pom.xml用作添加公共依赖。
    • 添加公共依赖,仓库路径,以及版本定位。
    • <properties>
          <spring-cloud.version>2021.0.1</spring-cloud.version>
          <spring-cloud-alibaba.version>2021.0.1.0</spring-cloud-alibaba.version>
      </properties>
      
      <!-- 引入 Spring Boot 的依赖 -->
      <parent>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-build</artifactId>
          <version>3.1.1</version>
          <relativePath/>
      </parent>
      
      <dependencies>
          <dependency>
              <groupId>org.projectlombok</groupId>
              <artifactId>lombok</artifactId>
          </dependency>
          <dependency>
              <groupId>org.springframework.cloud</groupId>
              <artifactId>spring-cloud-starter-bootstrap</artifactId>
          </dependency>
      </dependencies>
      
      <dependencyManagement>
          <dependencies>
      
              <!-- Spring Dependencies -->
              <dependency>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-dependencies</artifactId>
                  <version>${spring-boot.version}</version>
                  <type>pom</type>
                  <scope>import</scope>
              </dependency>
      
              <dependency>
                  <groupId>org.springframework.cloud</groupId>
                  <artifactId>spring-cloud-dependencies</artifactId>
                  <version>${spring-cloud.version}</version>
                  <type>pom</type>
                  <scope>import</scope>
              </dependency>
      
              <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>
      
      <pluginRepositories>
          <pluginRepository>
              <id>aliyun-plugin</id>
              <url>https://maven.aliyun.com/repository/public</url>
              <releases>
                  <enabled>true</enabled>
              </releases>
              <snapshots>
                  <enabled>false</enabled>
              </snapshots>
          </pluginRepository>
      </pluginRepositories>
      <repositories>
          <!--阿里云代理-->
          <repository>
              <id>aliyun</id>
              <name>aliyun</name>
              <url>http://maven.aliyun.com/nexus/content/groups/public</url>
          </repository>
      </repositories>
      3.	Nacos client provider创建。
      a)	创建alibaba-spring-cloud-provider子工程,添加依赖。
      <parent>
          <groupId>com.cloud</groupId>
          <artifactId>alibaba-spring-cloud</artifactId>
          <version>1.0-SNAPSHOT</version>
          <relativePath>../pom.xml</relativePath>
      </parent>
      <properties>
          <java.version>1.8</java.version>
      </properties>
      <dependencies>
          <!--注册中心客户端-->
          <dependency>
              <groupId>com.alibaba.cloud</groupId>
              <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
          </dependency>
      
          <!--web 模块-->
          <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>
      </dependencies>

    • 启动类添加@EnableDiscoveryClient注解。
    • application.yml配置文件添加配置,定义服务名称,配置nacos连接地址及账号密码。
    • server:
        port: 18080
      
      spring:
        application:
          name: provider-service
        cloud:
          nacos:
            discovery:
              server-addr: 127.0.0.1:8848
              enabled: true
            username: nacos
            password: nacos
      
      management:
        endpoints:
          web:
            exposure:
              include: "*"
        endpoint:
          health:
            show-details: always

    • 创建Controller并添加接口。
    • package com.cloud.controller;
      
      import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
      import org.springframework.http.HttpStatus;
      import org.springframework.http.ResponseEntity;
      import org.springframework.web.bind.annotation.*;
      
      import javax.annotation.Resource;
      import java.util.Map;
      
      /**
       * 项目名称: provider
       * 包名称:   com.cloud.controller
       * 类名称:   ProviderController
       * 类描述:
       * 创建人:   zhihong.zhu
       * 创建时间: 2022/8/9 14:45
       * 修改人:
       * 修改时间:
       * 修改备注:
       */
      @RestController
      public class ProviderController {
      
          @GetMapping("/echo/{string}")
          public String echo(@PathVariable String string) {
              return "hello Nacos Discovery " + string;
          }
      
      }

    • 启动之后,登录nacos查看服务列表已存在该服务。
  • Nacos client consumer创建。
    • 创建alibaba-spring-cloud-consumer子工程,添加依赖。
    • <parent>
          <groupId>com.cloud</groupId>
          <artifactId>alibaba-spring-cloud</artifactId>
          <version>1.0-SNAPSHOT</version>
          <relativePath>../pom.xml</relativePath>
      </parent>
      <properties>
          <java.version>1.8</java.version>
      </properties>
      <dependencies>
          <dependency>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-starter-web</artifactId>
          </dependency>
          <dependency>
              <groupId>com.alibaba.cloud</groupId>
              <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
          </dependency>
      
          <dependency>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-starter-actuator</artifactId>
          </dependency>
          <dependency>
              <groupId>org.springframework.cloud</groupId>
              <artifactId>spring-cloud-starter-loadbalancer</artifactId>
          </dependency>
      </dependencies>

    • 启动类添加@EnableDiscoveryClient注解。
    • application.yml配置文件添加配置,定义服务名称,配置nacos连接地址及账号密码。
    • server:
        port: 18011
      
      spring:
        application:
          name: consumer-service
        cloud:
          nacos:
            discovery:
              server-addr: 127.0.0.1:8848
              enabled: true
            username: nacos
            password: nacos
          loadbalancer.nacos.enabled: true
      
      management:
        endpoints:
          web:
            exposure:
              include: "*"
        endpoint:
          health:
            show-details: always

    • 创建Controller并添加接口。
    • package com.cloud.controller;
      
      import com.cloud.service.EchoService;
      import org.springframework.http.ResponseEntity;
      import org.springframework.web.bind.annotation.PathVariable;
      import org.springframework.web.bind.annotation.RequestMapping;
      import org.springframework.web.bind.annotation.RestController;
      import org.springframework.web.client.RestTemplate;
      
      import javax.annotation.Resource;
      
      /**
       * 项目名称: consumer
       * 包名称:   com.cloud.controller
       * 类名称:   ConsumerController
       * 类描述:
       * 创建人:   zhihong.zhu
       * 创建时间: 2022/8/11 9:34
       * 修改人:
       * 修改时间:
       * 修改备注:
       */
      @RestController
      public class ConsumerController {
      
          @Resource
          private RestTemplate restTemplate;
      
          @RequestMapping("/echo/{string}")
          public String echo(@PathVariable String string) {
              return restTemplate.getForObject("http://provider-service/echo/{string}", String.class, string);
          }
      
      }

    • 创建RestTemplateConfig配置类,同时添加负载均衡支持注解。
    • package com.cloud.configurer;
      
      import org.springframework.cloud.client.loadbalancer.LoadBalanced;
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;
      import org.springframework.web.client.RestTemplate;
      
      /**
       * 项目名称: consumer
       * 包名称:   com.cloud.configurer
       * 类名称:   RestTemplateConfig
       * 类描述:   RestTemplate配置
       * 创建人:   zhihong.zhu
       * 创建时间: 2022/8/11 9:32
       * 修改人:
       * 修改时间:
       * 修改备注:
       */
      @Configuration
      public class RestTemplateConfig {
      
          @Bean
          @LoadBalanced
          public RestTemplate createRestTemplate(){
              return new RestTemplate();
          }
      
      }

    • 启动之后,登录nacos查看服务列表已存在该服务。
    • 访问http://localhost:18011/echo/hello-world实现服务调用。

第二章:Nacos OpenFeign——使用openFeign实现服务调用

  • 还使用alibaba-spring-cloud-consumer子工程,添加依赖。
  • <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>

  • 创建EchoService接口类,添加@FeignClient(name = "provider-service",fallback = EchoServiceFallback.class)注解。name—远程服务名称,fallback—熔断降级处理类。(FeignClient 已经默认集成了 Ribbon)
  • package com.cloud.service;
    
    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestParam;
    
    @FeignClient(name = "provider-service",fallback = EchoServiceFallback.class)
    public interface EchoService {
    
        @GetMapping("/echo/{str}")
        String echo(@PathVariable("str") String str);
    
        @GetMapping("/notFound")
        String notFound();
    
    }
  • 创建EchoServiceFallback类,实现EchoService接口类,实现两个方法,编写降级后处理业务。
  • package com.cloud.service;
    
    import org.springframework.stereotype.Component;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestParam;
    
    /**
     * 项目名称: consumer
     * 包名称:   com.cloud.service
     * 类名称:   EchoServiceFallback
     * 类描述:
     * 创建人:   zhihong.zhu
     * 创建时间: 2022/8/11 9:53
     * 修改人:
     * 修改时间:
     * 修改备注:
     */
    @Component
    public class EchoServiceFallback implements EchoService{
        @Override
        public String echo(@PathVariable("str") String str) {
            return "echo fallback";
        }
    
        @Override
        public String notFound() {
            return "notFound fallback";
        }
    }

  • 在ConsumerController再添加接口,调用EchoService的两个方法。
  • package com.cloud.controller;
    
    import com.cloud.service.EchoService;
    import org.springframework.http.ResponseEntity;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    import org.springframework.web.client.RestTemplate;
    
    import javax.annotation.Resource;
    
    /**
     * 项目名称: consumer
     * 包名称:   com.cloud.controller
     * 类名称:   ConsumerController
     * 类描述:
     * 创建人:   zhihong.zhu
     * 创建时间: 2022/8/11 9:34
     * 修改人:
     * 修改时间:
     * 修改备注:
     */
    @RestController
    public class ConsumerController {
    
        @Resource
        private RestTemplate restTemplate;
        @Resource
        private EchoService echoService;
    
        @RequestMapping("/echo/{string}")
        public String echo(@PathVariable String string) {
            return restTemplate.getForObject("http://provider-service/echo/{string}", String.class, string);
        }
        
        @RequestMapping("/echoForFeign/{string}")
        public String echoForFeign(@PathVariable String string) {
            return echoService.echo(string);
        }
    
        @RequestMapping("/notFound")
        public String notFound(){
            return echoService.notFound();
        }
    
    }

  • 启动服务,访问http://localhost:18011/echoForFeign/hello-world实现服务调用,访问http://localhost:18011/notFound返回降级的业务内容。

第三章:Nacos config——配置管理

  • 与spring cloud config相比,Nacos config不需要单独创建配置中心服务,不需要创建云仓库,不需要MQ传递更新消息。一站式完成动态配置。
  • 创建alibaba-spring-cloud-config子工程,添加依赖。
  • <parent>
        <groupId>com.cloud</groupId>
        <artifactId>alibaba-spring-cloud</artifactId>
        <version>1.0-SNAPSHOT</version>
        <relativePath>../pom.xml</relativePath>
    </parent>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bootstrap</artifactId>
        </dependency>
    </dependencies>

  • resources下创建bootstrap.yml并添加配置。(项目启动时最先加载bootstrap.yml配置)
  • server:
      port: 18082
    
    spring:
      application:
        name: nacos-config
      cloud:
        nacos:
          config:
            server-addr: 127.0.0.1:8848
            file-extension: yml
            #优先级shared-configs < extension-configs < ${spring.application.name}-${spring-profiles-active}.${file-extension}
            shared-configs:
              - data-id: nacos-config-shared1.yml
                group: SHARED1_GROUP
                refresh: true
              - data-id: nacos-config-shared2.yml
                group: SHARED1_GROUP
                refresh: false
            extension-configs:
              - data-id: nacos-config-extension1.yml
                group: EXTENSION1_GROUP
                refresh: true
              - data-id: nacos-config-extension2.yml
                group: EXTENSION2_GROUP
                refresh: false
      profiles:
        active: dev

  • 配置属性说明:
    • file-extension——配置文件后缀(properties、yml)
    • shared-configs——共享配置,有多个配置可用此属性,格式如上。
    • data-id——资源ID,指定Nacos创建配置文件时的Data ID。此属性值必须加后缀名,file-extension不会自动拼接到此属性值。
    • group——分组,指定Nacos创建配置文件时的Group。默认DEFAULT_GROUP。
    • refresh——是否动态刷新。此属性实现时需与注解配合,后面讲。
    • extension-configs——扩展配置。与shared-configs用途一样,只是优先级不一样。格式如上。
    • 优先级shared-configs < extension-configs < ${spring.application.name}-${spring-profiles-active}.${file-extension}
  • 创建Controller类,并创建接口。添加@RefreshScope注解结合配置文件的refresh配置实现动态刷新。
    • package com.cloud.controller;
      
      import com.google.common.collect.Maps;
      import org.springframework.beans.factory.annotation.Value;
      import org.springframework.cloud.context.config.annotation.RefreshScope;
      import org.springframework.web.bind.annotation.RequestMapping;
      import org.springframework.web.bind.annotation.RestController;
      
      import java.util.HashMap;
      import java.util.Map;
      
      /**
       * 项目名称: config
       * 包名称:   com.cloud.controller
       * 类名称:   ConfigController
       * 类描述:
       * 创建人:   zhihong.zhu
       * 创建时间: 2022/8/11 14:31
       * 修改人:
       * 修改时间:
       * 修改备注:
       */
      @RestController
      @RefreshScope
      public class ConfigController {
      
          @Value("${user.name}")
          private String name;
          @Value("${user.age}")
          private String age;
          @Value("${shared1}")
          private String shared1;
          @Value("${shared2}")
          private String shared2;
          @Value("${extension1}")
          private String extension1;
          @Value("${extension2}")
          private String extension2;
      
          @RequestMapping("/configTest")
          public Map configTest() {
              HashMap<Object, Object> map = Maps.newHashMap();
              map.put("name", name);
              map.put("age", age);
              map.put("shared1", shared1);
              map.put("shared2", shared2);
              map.put("extension1", extension1);
              map.put("extension2", extension2);
      
              return map;
          }
      
      }

  • 登录Nacos控制台,添加nacos-config-dev.yml(DEFAULT_GROUP)、nacos-config-extension1.yml(EXTENSION1_GROUP)、nacos-config-extension2.yml(EXTENSION2_GROUP)、nacos-config-shared1.yml、(SHARED1_GROUP)nacos-config-shared2.yml(SHARED1_GROUP)配置。
  • #nacos-config-dev.yml
    user:
      name: zhihong.zhu
      age: 288
    #nacos-config-extension1.yml
    user:
      name: zhihong.zhu
      age: 288
    extension1: extension11
    #nacos-config-extension2.yml
    user:
      name: zhihong.zhu
      age: 289
    extension2: extension22
    #nacos-config-shared1.yml
    user:
      name: zhihong.zhu
      age: 28
    shared1: shared1
    #nacos-config-shared2.yml
    user:
      name: zhihong.zhu
      age: 28
    shared2: shared2

  • 启动服务,调用接口http://localhost:18082/configTest查询配置。多次修改配置文件多次访问。可验证动态刷新配置及优先级。

第四章:Nacos Gateway——API网关

  • Spring Cloud Gateway旨在为微服务架构提供一种简单而有效的统一的API路由管理方式。Spring Cloud Gateway作为Spring Cloud生态系中的网关,目标是替代ZUUL,其不仅提供统一的路由方式,并且基于Filter链的方式提供了网关基本的功能,例如:安全,监控/埋点,和限流等。
  • 创建alibaba-spring-cloud-gateway子工程,添加依赖。
  • <parent>
        <groupId>com.cloud</groupId>
        <artifactId>alibaba-spring-cloud</artifactId>
        <version>1.0-SNAPSHOT</version>
        <relativePath>../pom.xml</relativePath>
    </parent>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
    </dependencies>

  • application.yml配置文件添加配置。
  • server:
      port: 18083
    
    spring:
      application:
        name: nacos-gateway
      cloud:
        nacos:
          discovery:
            server-addr: 127.0.0.1:8848
            username: nacos
            password: nacos
        gateway:
          discovery:
            locator:
              enabled: true
          routes:
            - id: nacos-route
              uri: lb://provider-service
              predicates:
                - Path=/nacos/**
              filters:
                - StripPrefix=1
    
    management:
      endpoints:
        web:
          exposure:
            include: "*"
      endpoint:
        health:
          show-details: always

  • 配置属性说明:
    • routes——路由配置。
    • id——路由ID。
    • uri——路由指向地址。lb://是注册中心匹配方式,将从注册中心找对应的服务,所以注册中心必须有该服务。对服务命名方式有特殊要求。命名规则为:"[a-zA-Z]([a-zA-Z]|\\d|\\+|\\.|-)*:.*"。还有两种分别是ws(websocket)方式和http方式。
    • predicates——断言。
    • Path——与此值相匹配的路径进行路由。除此之外,断言有很多种路由方式。
    • filters——拦截。
    • StripPrefix——匹配路径的节数。
  • 启动服务,访问http://localhost:18083/nacos/echo/hello-world。实际会指向http://provider-service/echo/hello-world

第五章:Sentinel——流控、熔断、降级

  • 随着微服务的流行,服务和服务之间的稳定性变得越来越重要。 https://github.com/alibaba/Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。
  • 启动Sentinel 控制台。
    • 源码下载,下载地址https://gitee.com/mirrors/Sentinel/repository/archive/1.8.5
    • 解压进入文件根目录,编辑pom.xml,配置本地及alibaba的maven仓库地址保存。(<url>file://xxx/Repository</url>改为自己本地仓库位置)
    • <repositories>
      <repository>
              <id>Localhost</id>
              <name>Localhost</name>
              <url>file://xxx/Repository</url>
          </repository>
          <repository>
              <id>alimaven</id>
              <name>aliyun maven</name>
              <url>https://maven.aliyun.com/nexus/content/repositories/central/</url>
              <releases>
                  <enabled>true</enabled>
              </releases>
              <snapshots>
                  <enabled>false</enabled>
              </snapshots>
          </repository>
      </repositories>
      <pluginRepositories>
          <pluginRepository>
              <id>alimaven</id>
              <name>aliyun maven</name>
              <url>https://maven.aliyun.com/nexus/content/repositories/central/</url>
              <releases>
                  <enabled>true</enabled>
              </releases>
              <snapshots>
                  <enabled>false</enabled>
              </snapshots>
          </pluginRepository>
          <pluginRepository>
              <id>Localhost</id>
              <name>Localhost</name>
              <url>file://xxx/Repository</url>
          </pluginRepository>
      </pluginRepositories>

    • 进入cmd,执行mvn -Dmaven.test.skip=true clean package命令进行编译。
    • 编译完成,进入Sentinel-1.8.5\sentinel-dashboard,打开cmd,执行java -Dserver.port=8868 -Dcsp.sentinel.dashboard.server=localhost:8868 -Dproject.name=sentinel-dashboard -jar target/sentinel-dashboard.jar命令启动。
    • 启动之后,打开浏览器访问http://127.0.0.1:8868访问控制台,输入账号密码sentinel登录进如控制台。
    • Sentinel控制台启动完成。
  • 创建alibaba-spring-cloud-sentinel子工程,添加依赖。
  • <parent>
        <groupId>com.cloud</groupId>
        <artifactId>alibaba-spring-cloud</artifactId>
        <version>1.0-SNAPSHOT</version>
        <relativePath>../pom.xml</relativePath>
    </parent>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-datasource-nacos</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-xml</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-sentinel-datasource</artifactId>
        </dependency>
    </dependencies>

  • application.yml配置文件添加配置。
  • server:
      port: 18086
    
    
    spring:
      application:
        name: nacos-sentinel
      cloud:
        sentinel:
          transport:
            dashboard: localhost:8868
          eager: true
          web-context-unify: true
          filter:
            enabled: false
          http-method-specify: false
          datasource:
    #        ds6:
    #          nacos:
    #            server-addr: 127.0.0.1:8848
    #            username: nacos
    #            password: nacos
    #            dataId: flowrule.json
    #            data-type: json
    #            rule-type: flow
            ds1:
              file:
                file: "classpath: flowrule.xml"
                data-type: xml
                rule-type: flow
            ds2:
              file:
                file: "classpath: degraderule.json"
                data-type: json
                rule-type: degrade
            ds3:
              file:
                file: "classpath: authority.json"
                rule-type: authority
            ds4:
              file:
                file: "classpath: system.json"
                rule-type: system
            ds5:
              file:
                file: "classpath: param-flow.json"
                rule-type: param_flow
    
    management:
      endpoints:
        web:
          exposure:
            include: "*"
    
    feign:
      sentinel:
        enabled: true

  • 配置属性说明:
    • eager——是否提前触发 Sentinel 初始化。
    • web-context-unify——是否根据不同的URL 进行链路限流。
  • 添加文件到resources。文件地址https://gitee.com/zzh13520704819/sentinel-config.git
  • 文件说明:
    • authority.json——授权规则。
    • degraderule.json——熔断降级规则。
    • flowrule.json——流控规则。
    • flowrule.json——流控规则,xml数据类型配置。
    • param-flow.json——热点规则。
    • system.json——系统规则。
  • 文件属性说明:
    • 限流规则配置:
      |属性|说明|
      |:----|
      |app |应用名|
      |resource |资源名(唯一名称,默认请求路径)|
      |limitApp |针对来源 (Sentinel可以针对调用者进行限流,填写微服务名,指定对哪个微服务进行限流 ,默认default(不区分来源,全部限制))|
      |grade |阈值类型(阀值类型,0:线程数,1:QPS)|
      |count|单机阀值|
      |clusterMode|是否集群(false:否,true:是)|
      |controlBehavior|流控效果 (0:失败,1:warmUp,2:排队等待)
      |strategy|流控模式(0:直接,1:关联,2:链路)|
      |clusterConfig|thresholdType: 0|
      降级规则配置:
      |属性|说明|
      |:----|
      |app |应用名|
      |count |熔断策略为慢调用比例:最大Rt(需要设置的阈值,超过该值则为慢应用),异常比例中为:比例阈值,异常数中为:异常数|
      |limitApp |针对来源 (Sentinel可以针对调用者进行限流,填写微服务名,指定对哪个微服务进行限流 ,默认default(不区分来源,全部限制))|
      |grade |熔断策略(0:慢调用比例,1:异常比例,2:异常数)|
      |minRequestAmount|最小请求数(允许通过的最小请求数,在该数量内不发生熔断)|
      |timeWindow|熔断时长(在这段时间内发生熔断,拒绝所有请求)|
      |slowRatioThreshold|比例阈值 (慢调用占所有的调用比率,范围[0~1])
      |resource |资源名(唯一名称,默认请求路径)|
      |statIntervalMs |熔断时长(熔断时长,默认为1秒)|
      热点规则配置:
      |属性|说明|
      |:----|
      |app |应用名|
      |resource |资源名(唯一名称,默认请求路径)|
      |limitApp |针对来源 (Sentinel可以针对调用者进行限流,填写微服务名,指定对哪个微服务进行限流 ,默认default(不区分来源,全部限制))|
      |grade |限流模式(0:线程数,1:QPS)|
      |count|单机阀值|
      |durationInSec|统计窗口时间||
      |clusterMode|是否集群(false:否,true:是)|
      |paramIdx|参数索引|
      |paramFlowItemList|参数例外项,可以针对指定的参数值单独设置限流阈值,不受前面 count 阈值的限制。仅支持基本类型|
      |controlBehavior|流控效果,默认为0 (0:快速失败,1:warmUp,2:排队等待)
      |maxQueueingTimeMs|最大排队等待时长,默认0(仅在匀速排队模式生效)|
      授权规则配置:
      |属性|说明|
      |:----|
      |app |应用名|
      |resource|资源名|
      |limitApp |流控应用(指调用方,多个调用方名称用半角英文逗号(,)分隔)|
      |strategy |授权类型(0:白名单,1:黑名单)|
      系统规则配置:
      |属性|说明|
      |:----|
      |app |应用名|
      |highestSystemLoad |阈值(阈值类型为Load的阈值)[0,1)的正整数|
      |avgRt |阈值(阈值类型为RT的阈值)所有入口流量的平均响应时间,[0,1)的正整数|
      |maxThread |阈值(阈值类型为线程数的阈值)入口流量的最大并发数,[0,1)的正整数|
      |qps|阈值 (阈值类型为入口 QPS的阈值)所有入口资源的 QPS,[0,1)的正整数|
      |highestCpuUsage|阈值(阈值类型为CPU 使用率的阈值)[0,1]的小数,代表百分比|
  • 启动类添加@EnableDiscoveryClient注解。
  • 启动服务,查看sentinel控制台,可以看到各项配置已通过本地文件自动创建。
  • 可以继续在控制台创建各种规则。
  • 流控规则测试:
    • 创建SentinelController类,添加test方法。
    • package com.cloud.controller;
      
      import com.alibaba.cloud.commons.lang.StringUtils;
      import com.alibaba.csp.sentinel.annotation.SentinelResource;
      import org.springframework.web.bind.annotation.GetMapping;
      import org.springframework.web.bind.annotation.PathVariable;
      import org.springframework.web.bind.annotation.RestController;
      import org.springframework.web.client.RestTemplate;
      
      import javax.annotation.Resource;
      import javax.servlet.http.HttpServletRequest;
      import java.util.Arrays;
      
      /**
       * 项目名称: consumer
       * 包名称:   com.cloud.controller
       * 类名称:   SentinelController {
       * 类描述:
       * 创建人:   zhihong.zhu
       * 创建时间: 2022/8/15 14:12
       * 修改人:
       * 修改时间:
       * 修改备注:
       */
      @RestController
      public class SentinelController {
          
          @GetMapping("/test")
          @SentinelResource(value = "test")
          public String test(){
              return "Hello test";
          }
      }

    • 启动服务,访问http://localhost:18086/test,QPS超过1触发流控规则。
    • 响应的信息可以看出,触发流控规则后直接返回状态码为500的错误信息,不够友好,太过笼统,所以我们可以利用注解的一些属性自由处理一些可选的异常。
    • 创建ExceptionUtil类,添加blockException方法。
    • package com.cloud.configurer;
      
      import com.alibaba.csp.sentinel.slots.block.BlockException;
      
      public final class ExceptionUtil {
      
          private ExceptionUtil() {
          }
      
          public static String blockException(BlockException ex) {
              System.out.println("Oops: " + ex.getClass().getCanonicalName());
              return ex.getClass().getCanonicalName();
          }
      
      }

    • SentinelController类的test的@SentinelResource注解添加blockHandler = "blockException",blockHandlerClass = ExceptionUtil.class属性。
    • package com.cloud.controller;
      
      import com.alibaba.csp.sentinel.annotation.SentinelResource;
      import com.cloud.configurer.ExceptionUtil;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.web.bind.annotation.GetMapping;
      import org.springframework.web.bind.annotation.RestController;
      import org.springframework.web.client.RestTemplate;
      
      /**
       * 项目名称: sentinel
       * 包名称:   com.cloud.controller
       * 类名称:   SentinelController
       * 类描述:
       * 创建人:   zhihong.zhu
       * 创建时间: 2022/8/12 14:56
       * 修改人:
       * 修改时间:
       * 修改备注:
       */
      @RestController
      public class SentinelController {
      
          @GetMapping("/test")
          @SentinelResource(value = "test",
                  blockHandler = "blockException",
                  blockHandlerClass = ExceptionUtil.class)
          public String test(){
              return "Hello test";
          }
      
      }

    • 注解属性说明:
      • blockHandler——异常处理方法名称。
      • blockHandlerClass——异常处理类。(如果和主方法在同一类里,可省略此属性,如果不在则必须添加此属性)
      • 异常处理方法返回类型需要与原方法相匹配,参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为BlockException。
    • 启动服务访问http://localhost:18086/test,QPS超过1触发流控规则。并返回处理后的数据。
    • 响应中文乱码处理:添加mvc配置类如下
    • package com.cloud.configurer;
      
      import com.alibaba.fastjson.serializer.SerializerFeature;
      import com.alibaba.fastjson.support.config.FastJsonConfig;
      import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
      import org.springframework.context.annotation.Configuration;
      import org.springframework.http.MediaType;
      import org.springframework.http.converter.HttpMessageConverter;
      import org.springframework.web.servlet.config.annotation.EnableWebMvc;
      import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
      import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
      
      import java.nio.charset.Charset;
      import java.util.Arrays;
      import java.util.List;
      
      /**
       * @author yuhuangbin
       */
      @Configuration
      @EnableWebMvc
      public class WebMvcConfiguration implements WebMvcConfigurer {
      
         // 使用阿里 FastJson 作为JSON MessageConverter
         @Override
         public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
            FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
            FastJsonConfig config = new FastJsonConfig();
            config.setSerializerFeatures(SerializerFeature.WriteNullListAsEmpty, // 集合为null时返回空集合
                  SerializerFeature.WriteMapNullValue, // 保留空的字段
                  SerializerFeature.WriteDateUseDateFormat,// 使用时间转换
                  SerializerFeature.WriteNullStringAsEmpty);//String null -> ""
            // SerializerFeature.WriteNullNumberAsZero//Number null -> 0
            // 按需配置,更多参考FastJson文档
      //        config.setParserConfig();
            converter.setFastJsonConfig(config);
      //    converter.setDateFormat("yyyy-MM-dd HH:mm:ss");
            converter.setDefaultCharset(Charset.forName("UTF-8"));
            converter.setSupportedMediaTypes(Arrays.asList(MediaType.APPLICATION_JSON_UTF8));
            converters.add(0, converter);
         }
      }

  • 熔断降级规则测试:
    • SentinelController添加test2方法:
    • package com.cloud.controller;
      
      import com.alibaba.cloud.commons.lang.StringUtils;
      import com.alibaba.csp.sentinel.annotation.SentinelResource;
      import com.cloud.configurer.ExceptionUtil;
      import com.cloud.configurer.SentinelFallbackFactory;
      import org.springframework.web.bind.annotation.GetMapping;
      import org.springframework.web.bind.annotation.PathVariable;
      import org.springframework.web.bind.annotation.RestController;
      
      /**
       * 项目名称: sentinel
       * 包名称:   com.cloud.controller
       * 类名称:   SentinelController
       * 类描述:
       * 创建人:   zhihong.zhu
       * 创建时间: 2022/8/12 14:56
       * 修改人:
       * 修改时间:
       * 修改备注:
       */
      @RestController
      public class SentinelController {
      
      
          @GetMapping("/test")
          @SentinelResource(value = "test",
                  blockHandler = "blockException",
                  blockHandlerClass = ExceptionUtil.class)
          public String test(){
              return "Hello test";
          }
      
      
          @GetMapping("/test2/{p1}")
          @SentinelResource(value = "abc0",
                  fallback = "test2Fallback",
                  fallbackClass = SentinelFallbackFactory.class)
          public String test2(@PathVariable String p1){
              if (StringUtils.equals("1",p1)){
                  throw new RuntimeException("参数为1导致异常");
              }
              return "Hello(你好) test1-"+p1;
          }
      
      }

    • 创建熔断处理类SentinelFallbackFactory,添加test2Fallback方法:
    • package com.cloud.configurer;
      
      import org.springframework.web.bind.annotation.PathVariable;
      
      /**
       * 项目名称: sentinel
       * 包名称:   com.cloud.configurer
       * 类名称:   SentinelFallbackFactory
       * 类描述:
       * 创建人:   zhihong.zhu
       * 创建时间: 2022/8/15 14:17
       * 修改人:
       * 修改时间:
       * 修改备注:
       */
      public class SentinelFallbackFactory {
      
          public static String test2Fallback(@PathVariable String p1,Throwable throwable) {
              return "触发熔断"+throwable.getMessage();
          }
      }

    • 注解属性说明:
      • value——规则资源名称。(此资源通过本地规则文件已配置,可通过控制台查看此资源规则配置)
      • fallback——熔断后处理方法。(该方法必须为静态方法,可以额外多一个 Throwable 类型的参数用于接收对应的异常。)
      • fallbackClass——熔断处理类。(如果和主方法在同一类里,可省略此属性,如果不在则必须添加此属性)
      • 熔断处理方法返回类型需要与原方法相匹配,参数类型需要和原方法相匹配。
    • 启动服务访问http://localhost:18086/test2/1,返回熔断及异常信息。
  • 热点参数规则测试:
    • 热点参数规则是一种精准的流控规则,它允许将规则绑定到参数上。比如方法有两个参数,我们对第一个参数进行限流,对第二个参数不限流。
    • SentinelController添加test3方法:
    • package com.cloud.controller;
      
      import com.alibaba.cloud.commons.lang.StringUtils;
      import com.alibaba.csp.sentinel.annotation.SentinelResource;
      import com.cloud.configurer.ExceptionUtil;
      import com.cloud.configurer.SentinelFallbackFactory;
      import org.springframework.web.bind.annotation.GetMapping;
      import org.springframework.web.bind.annotation.PathVariable;
      import org.springframework.web.bind.annotation.RestController;
      
      /**
       * 项目名称: sentinel
       * 包名称:   com.cloud.controller
       * 类名称:   SentinelController
       * 类描述:
       * 创建人:   zhihong.zhu
       * 创建时间: 2022/8/12 14:56
       * 修改人:
       * 修改时间:
       * 修改备注:
       */
      @RestController
      public class SentinelController {
      
          @GetMapping("/test")
          @SentinelResource(value = "test",
                  blockHandler = "blockException",
                  blockHandlerClass = ExceptionUtil.class)
          public String test(){
              return "Hello test";
          }
      
      
          @GetMapping("/test2/{p1}")
          @SentinelResource(value = "abc0",
                  fallback = "test2Fallback",
                  fallbackClass = SentinelFallbackFactory.class)
          public String test2(@PathVariable String p1){
              if (StringUtils.equals("1",p1)){
                  throw new RuntimeException("参数为1导致异常");
              }
              return "Hello(你好) test1"+p1;
          }
      
          @GetMapping("/test3/{p1}/{p2}")
          @SentinelResource(value = "aa",
                  blockHandler = "test3BlockException",
                  blockHandlerClass = ExceptionUtil.class)
          public String test3(@PathVariable String p1,@PathVariable String p2){
              return "Hello(你好) test3-p1="+p1+"p2="+p2;
          }
      
      }

    • ExceptionUtil类添加test3BlockException方法:
    • package com.cloud.configurer;
      
      import com.alibaba.csp.sentinel.slots.block.BlockException;
      
      public final class ExceptionUtil {
      
          private ExceptionUtil() {
          }
      
          public static String blockException(BlockException ex) {
              System.out.println("Oops: " + ex.getClass().getCanonicalName());
              return ex.getClass().getCanonicalName();
          }
          
          public static String test3BlockException(String p1,String p2,BlockException ex) {
          System.out.println("Oops: " + ex.getClass().getCanonicalName());
          return ex.getClass().getCanonicalName();
      }
      
      }

    • 热点流控规则说明:
      • 查看Sentinel控制台的热点规则可知:
        • 参数索引=0——第一个参数。
        • 单机阀值=0——QPS超过0触发规则。
    • 启动服务访问http://localhost:18086/test3/1/2,QPS超过1触发流控规则。并返回处理后的数据。
  • 授权规则测试:
    • Sentinel提供了RequestOriginParser接口来处理来源。如果Sentinel保护的资源被访,Sentinel就会调用RequestOriginParser的实现类去处理访问来源。
    • SentinelController添加test4方法:
    • package com.cloud.controller;
      
      import com.alibaba.cloud.commons.lang.StringUtils;
      import com.alibaba.csp.sentinel.annotation.SentinelResource;
      import com.cloud.configurer.ExceptionUtil;
      import com.cloud.configurer.SentinelFallbackFactory;
      import org.springframework.web.bind.annotation.GetMapping;
      import org.springframework.web.bind.annotation.PathVariable;
      import org.springframework.web.bind.annotation.RestController;
      
      import javax.servlet.http.HttpServletRequest;
      
      /**
       * 项目名称: sentinel
       * 包名称:   com.cloud.controller
       * 类名称:   SentinelController
       * 类描述:
       * 创建人:   zhihong.zhu
       * 创建时间: 2022/8/12 14:56
       * 修改人:
       * 修改时间:
       * 修改备注:
       */
      @RestController
      public class SentinelController {
      
          @GetMapping("/test")
          @SentinelResource(value = "test",
                  blockHandler = "blockException",
                  blockHandlerClass = ExceptionUtil.class)
          public String test() {
              return "Hello test";
          }
      
      
          @GetMapping("/test2/{p1}")
          @SentinelResource(value = "abc0",
                  fallback = "test2Fallback",
                  fallbackClass = SentinelFallbackFactory.class)
          public String test2(@PathVariable String p1) {
              if (StringUtils.equals("1", p1)) {
                  throw new RuntimeException("参数为1导致异常");
              }
              return "Hello(你好) test1" + p1;
          }
      
          @GetMapping("/test3/{p1}/{p2}")
          @SentinelResource(value = "aa",
                  blockHandler = "test3BlockException",
                  blockHandlerClass = ExceptionUtil.class)
          public String test3(@PathVariable String p1, @PathVariable String p2) {
              return "Hello(你好) test3-p1=" + p1 + "p2=" + p2;
          }
      
          @GetMapping("/test4")
          @SentinelResource(value = "bad",
                  blockHandler = "test4BlockException",
                  blockHandlerClass = ExceptionUtil.class)
          public String test4(HttpServletRequest request) {
              return "Hello(你好) test4-p1=" + request.getParameter("p1");
          }
      
      }

    • 创建MyRequestOriginParser类实现RequestOriginParser接口类:
    • package com.cloud.configurer;
      
      import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser;
      import org.springframework.stereotype.Component;
      
      import javax.servlet.http.HttpServletRequest;
      
      /**
       * 项目名称: sentinel
       * 包名称:   com.cloud.configurer
       * 类名称:   MyRequestOriginParser
       * 类描述:   自定义来源处理规则
       * 创建人:   zhihong.zhu
       * 创建时间: 2022/8/15 16:40
       * 修改人:
       * 修改时间:
       * 修改备注:
       */
      @Component
      public class MyRequestOriginParser implements RequestOriginParser {
      
          @Override
          public String parseOrigin(HttpServletRequest request) {
              return request.getParameter("p1");
          }
      
      }

    • 授权规则说明:
      • 查看Sentinel控制台的热点规则可知:
        • 资源名=bad——规则资源名称。
        • 流控应用=bcd——资源bad参数p1=bcd。
        • 授权类型=黑名单——资源bad参数p1=bcd时无法访问(黑名单)
    • 注意:如果配置文件spring.cloud.sentinel.filter.enabled=false,授权规则不生效。
    • 启动服务访问http://localhost:18086/test4?p1=bcd,触发授权规则。并返回处理后的数据。
  • OpenFeign 支持
    • SentinelController添加test5方法:
    • package com.cloud.controller;
      
      import com.alibaba.cloud.commons.lang.StringUtils;
      import com.alibaba.csp.sentinel.annotation.SentinelResource;
      import com.cloud.configurer.ExceptionUtil;
      import com.cloud.configurer.SentinelFallbackFactory;
      import com.cloud.service.SentinelService;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.web.bind.annotation.GetMapping;
      import org.springframework.web.bind.annotation.PathVariable;
      import org.springframework.web.bind.annotation.RestController;
      
      import javax.servlet.http.HttpServletRequest;
      
      /**
       * 项目名称: sentinel
       * 包名称:   com.cloud.controller
       * 类名称:   SentinelController
       * 类描述:
       * 创建人:   zhihong.zhu
       * 创建时间: 2022/8/12 14:56
       * 修改人:
       * 修改时间:
       * 修改备注:
       */
      @RestController
      public class SentinelController {
      
          @Autowired
          private SentinelService sentinelService;
      
          @GetMapping("/test")
          @SentinelResource(value = "test",
                  blockHandler = "blockException",
                  blockHandlerClass = ExceptionUtil.class)
          public String test() {
              return "Hello test";
          }
      
      
          @GetMapping("/test2/{p1}")
          @SentinelResource(value = "abc0",
                  fallback = "test2Fallback",
                  fallbackClass = SentinelFallbackFactory.class)
          public String test2(@PathVariable String p1) {
              if (StringUtils.equals("1", p1)) {
                  throw new RuntimeException("参数为1导致异常");
              }
              return "Hello(你好) test1" + p1;
          }
      
          @GetMapping("/test3/{p1}/{p2}")
          @SentinelResource(value = "aa",
                  blockHandler = "test3BlockException",
                  blockHandlerClass = ExceptionUtil.class)
          public String test3(@PathVariable String p1, @PathVariable String p2) {
              return "Hello(你好) test3-p1=" + p1 + "p2=" + p2;
          }
      
          @GetMapping("/test4")
          @SentinelResource(value = "bad",
                  blockHandler = "test4BlockException",
                  blockHandlerClass = ExceptionUtil.class)
          public String test4(HttpServletRequest request) {
              return "Hello(你好) test4-p1=" + request.getParameter("p1");
          }
      
          @GetMapping("/test5/{p1}")
          public String test5(@PathVariable String p1) {
              return sentinelService.echo(p1);
          }
      
      }

    • 创建SentinelService接口类,添加echo方法:
    • package com.cloud.service;
      
      
      import com.cloud.configurer.FeignFallbackFactory;
      import org.springframework.cloud.openfeign.FeignClient;
      import org.springframework.web.bind.annotation.GetMapping;
      import org.springframework.web.bind.annotation.PathVariable;
      
      @FeignClient(name = "provider-service",fallbackFactory = FeignFallbackFactory.class)
      public interface SentinelService {
      
          @GetMapping("/echo/{string}")
          String echo(@PathVariable String string);
      }

    • 创建FeignFallbackFactory类,实现FallbackFactory接口,重写方法:
    • package com.cloud.configurer;
      
      import com.cloud.service.SentinelService;
      import org.springframework.cloud.openfeign.FallbackFactory;
      import org.springframework.stereotype.Component;
      
      /**
       * 项目名称: sentinel
       * 包名称:   com.cloud.service
       * 类名称:   FeignFallbackFactory
       * 类描述:
       * 创建人:   zhihong.zhu
       * 创建时间: 2022/8/15 14:17
       * 修改人:
       * 修改时间:
       * 修改备注:
       */
      @Component
      public class FeignFallbackFactory implements FallbackFactory<SentinelService> {
          @Override
          public SentinelService create(Throwable throwable) {
              return new SentinelService() {
                  @Override
                  public String echo(String str) {
                      return throwable.getClass().getCanonicalName();
                  }
              };
          }
      }

    • pom.xml添加依赖:
    • <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-loadbalancer</artifactId>
      </dependency>

    • 启动类添加@EnableFeignClients注解。
    • 修改provider-service服务的echo接口,添加错误代码抛出异常。
    • 启动provider-service服务和该服务访问http://localhost:18086/test5/sentinel,触发熔断降级处理。

以上为入门实战的一些操作,如有初学者按照上面方式操作发现问题,请留言,看到后会修正解决。

想了解更多建议看相关源码:https://gitee.com/mirrors/Spring-Cloud-Alibaba/repository/archive/2021.0.1.0 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值