【知识梳理】SpringCloud总结

一、Eureka-注册中心

1.创建父工程

创建一个父工程,在父工程指定SpringCloud的版本,并且将packaging修改为pom。

#父类模块
<packaging>pom</packaging>

<dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring.cloud-version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
</dependencyManagement>

2.创建eureka server

创建eureka的server,创建SpringBoot工程(maven工程),自己添加启动类,导入依赖(c),在启动类添加注解,编写yml文件。

2.1导入依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
    </dependency>
</dependencies>

2.2启动类添加注解

/**
 * @author Zouch
 * @date 2020/10/29 9:29
 * @description
 */
@EnableEurekaServer
@SpringBootApplication
public class EurekaApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaApplication.class,args);
    }
}

2.3编写yml配置文件

server:
  port: 8761    #端口号

eureka:
  instance:
    hostname: localhost     #localhost
  client:
    #当前服务是单机版
    register-with-eureka: false
    fetch-registry: false
    serviceUrl:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

3.创建eureka client

3.1创建maven工程,修改为SpringBoot,参考上面2.1

3.2导入依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
</dependencies>

3.3启动类添加注解

/**
 * @author Zouch
 * @date 2020/10/29 9:56
 * @description
 */
@SpringBootApplication
@EnableEurekaClient
public class CustomerApplication {
    public static void main(String[] args) {
        SpringApplication.run(CustomerApplication.class,args);
    }
}

3.4编写配置文件

#指定eureka服务地址
eureka:
  client:
    service-url:    #点进去就能看到
      defaultZone: http://localhost:8761/eureka
spring:
  application:
    name: CUSTOMER

启动,浏览器可以看到已经注册到eureka。

4.测试

4.1创建search模块注册到eureka

同3.3

4.2使用EurekaClient 的对象去获取服务信息

4.3使用RestTemplate去调用

/**
 * @author Zouch
 * @date 2020/10/29 10:19
 * @description
 */
@RestController
public class CustomerController {
    //启动类要bean new一个RestTemplate
    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private EurekaClient eurekaClient;

    @RequestMapping("/customer")
    public String customer(){
        //1.通过eurekaclient 获取到search服务的信息
        InstanceInfo search = eurekaClient.getNextServerFromEureka("SEARCH", false);
        //2.获取到访问地址
        String url = search.getHomePageUrl();
        System.out.println(url);
        //3.通过restTemplate访问
        String result = restTemplate.getForObject(url+"/search",String.class);
        //4.返回
        return result;
    }
}

5.Eureka的安全

实现Eureka认证。

1.导入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

2.编写配置类

/**
 * @author Zouch
 * @date 2020/10/29 10:42
 * @description
 */
@EnableWebSecurity
public class WebSecurityConfig  extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //忽略掉路径
        http.csrf().ignoringAntMatchers("/eureka/**");
        super.configure(http);
    }
}

3.编写配置文件

spring:
  security:
    user:
      name: root
      password: root

4.其他服务向eureka注册需要添加用户名和密码

#指定eureka服务地址
eureka:
  client:
    service-url:
      defaultZone: http://root:root@localhost:8761/eureka    ##root:root@   用户名和密码
spring:
  application:
    name: SEARCH
server:
  port: 8081

6.Eureka的高可用

如果程序正在运行,突然eureka宕机了。

1.如果调用方访问过一次被调用方,则不会影响到功能。
2.如果没有访问过,就会造成当前功能不可用。

搭建Eureka高可用

  1. 搭建多台eureka
    采取了复制的方式,直接文件夹复制项目。删除iml和target文件,再给父工程添加一个module

  2. 让服务注册到多台eureka

    eureka:
      client:
        service-url:
          defaultZone: http://root:root@localhost:8761/eureka,http://root:root@localhost:8762/eureka
    
  3. 让多台eureka相互通讯

    server:
      port: 8761    #端口号
    
    eureka:
      instance:
        hostname: localhost     #localhost
      client:
        register-with-eureka: true   #注册到eureka
        fetch-registry: true         #从eureka拉取信息
        serviceUrl:
          defaultZone: http://root:root@localhost:8762/eureka/
    

7.Eureka的细节

1.EurekaClient启动时,将自己的信息注册到EurekaServer上,EurekaServer会存储EurekaClient的信息。

2.当EurekaClient调用服务时,本地无注册信息的缓存时,会去EurekaServer获取注册信息。

3.EurekaClient会通过心跳的方式去和EurekaServer进行连接。(默认30s会发送一次心跳请求,如果超过了90s还没有发送心跳信息的话,EurekaServer就默认认为你宕机了,将当前EurekaClient从注册表中移除)

#指定eureka服务地址
eureka:
  client:
    service-url:
      defaultZone: http://root:root@localhost:8761/eureka,http://root:root@localhost:8762/eureka
  instance:
    lease-renewal-interval-in-seconds: 30    #心跳间隔
    lease-expiration-duration-in-seconds: 90  #多久没发生会认为你宕机

4.EurekaClient会每隔30s 去EurekaServer更新本地注册表缓存信息。

eureka:
  client:
    service-url:
      defaultZone: http://root:root@localhost:8761/eureka,http://root:root@localhost:8762/eureka
    registry-fetch-interval-seconds: 30   #多久去更新一下本地注册表信息

5.Eureka自我保护机制,统计15分钟内,一个服务的心跳发送比例低于85%,EurekaServer就会开启自我保护机制

  • 不会从EurekaServer去移除长时间没有收到心跳的服务。
  • EurekaServer还是可以正常提供服务。
  • 网络比较稳定时,EurekaServer才会将自己的信息被其他节点同步过去。
eureka:
  instance:
    hostname: localhost     #localhost
  server:
    enable-self-preservation: true    #开启自我保护机制
  client:
    register-with-eureka: true   #注册到eureka
    fetch-registry: true         #从eureka拉取信息
    serviceUrl:
      defaultZone: http://root:root@localhost:8762/eureka/

6.CAP定理 (之能满足其二,且P分区容错性必须要有)

  • C -一致性
  • A -可用性
  • P -分区容错性

如果选择了CP、可能导致系统在一定时间内不可用,如果同步的数据大,造成的损失很大。

Eureka就是一个AP的效果,高可用的集群,Eureka集群无中心,即使宕机几个也不会导致系统不可用,不需要选举新的master、也会导致一定时间内数据不一致。

二、Ribbon-服务间负载均衡

Ribbon是客户端负载均衡;Dubbon采用的是服务端负载均衡。

1.使用Ribbon

1.启动两个search模块(服务模块)

详情见https://blog.csdn.net/woshizouqi/article/details/109360000

2.在客户端模块(调用方)中加入依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-netflix-ribbon</artifactId>
</dependency>

3.配置整合RestTemplate和Ribbon

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

4.在客户端去访问

@RequestMapping("/customer")
    public String customer(){
        String result=restTemplate.getForObject("http://SEARCH/search",String.class);
        return result;
    }

2.Ribbin配置负载均衡策略

2.1负载均衡策略

  1. RandomRule() :随机策略;
  2. RoundRobbinRule() :轮询策略;
  3. WeightedResponseTimeRule() :默认会采用轮询的策略,后续根据响应时间,自动分配权重。
  4. BestAvailableRule():根据被调用方并发数最小的去分配

2.2采用注解形式

@Bean
public IRule rule(){
    return new RandomRule();
}

3.3配置文件去指定策略(推荐,可以实现单个服务的负载均衡)

#指定具体服务的负载均衡策略
SEARCH:   #编写服务名称
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.WeightedResponseTimeRule  #策略类

三、Feign-服务调用

1.使用

1.导入依赖

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

2.添加注解

启动类添加@EnableFeignClients

3.创建接口和目标模块做映射

/**
 * @author Zouch
 * @date 2020/10/29 15:48
 * @description
 */
@FeignClient("SEARCH")   //目标服务名
public interface SearchClient {

    //value ->服务访问路径
    @RequestMapping(value = "search", method = RequestMethod.GET)
    public String search();

}

4.测试

/**
 * @author Zouch
 * @date 2020/10/29 10:19
 * @description
 */
@RestController
public class CustomerController {

    @Autowired
    private SearchClient searchClient;

    @RequestMapping("/customer")
    public String customer(){
        String result = searchClient.search();
        return result;
    }
}

2.feign的参数传递方式

2.1注意事项

  • 如果传递的参数比较复杂。默认会采用POST的请求方式
  • 传递单个参数时,推荐使用@PathVariable,如果传递的参数个数比较多,这里也可以采用@RequestParam,不要省略value属性。
  • 传递对象信息,统一采用json,@RequestBody。

3.Feign的fallback和fallbackfactory

3.1新建一个类实现Client所有方法

/**
 * @author Zouch
 * @date 2020/10/29 16:23
 * @description
 */
@Component
public class SearchFallback implements SearchClient{
    @Override
    public String search() {
        return "服务调用失败";
    }
}

3.2修改@FeignClient

@FeignClient(value = "SEARCH",fallback = SearchFallback.class)   //目标服务名

3.3添加配置

#开启fallback
feign:
  hystrix:
    enabled: true

光使用fallback看不到服务方的错误信息,可以使用fallbackFactory

3.4创建一个类实现FallbackFactory<>接口,泛型内类型是Client接口类,注意加@Component注解

/**
 * @author Zouch
 * @date 2020/10/29 16:33
 * @description
 */
@Component
public class SearchFallbacklFactory implements FallbackFactory<SearchClient> {
    //一定要注意注入,然后调用 return
    @Autowired
    SearchFallback searchFallback;

    @Override
    public SearchClient create(Throwable throwable) {
        throwable.printStackTrace();
        return searchFallback;
    }
}

3.5修改Client接口的注解

@FeignClient(value = "SEARCH",fallbackFactory = SearchFallbacklFactory.class)   //目标服务名

这样就能在客户端也能打印错误日志!

四、Hystrix-服务容错和降级

1.Hystrix功能

Hystrix主要是为了解决服务雪崩问题

  1. 降级机制:当你的服务出现超时或者异常等,可以执行一个降级方法,返回一个托底数据。
  2. 隔离:提供了一个Hystrix线程池,信号量,和tomcat的线程池相互隔离。
  3. 熔断:当你服务的失败率达到一定阈值,自动触发降级。
  4. 缓存:请求缓存的功能。

2.降级机制实现

2.1导入依赖

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

2.2启动类添加一个注解

//Hystric熔断
@EnableCircuitBreaker

2.3针对某一个方法去编写他的降级方法

//getStudent的降级方法   方法的描述要和接口一致
public Student getStudentFallBack(@RequestParam String id) {
    return new Student("fallback","1","1");
}

2.4在接口上添加注解

@RequestMapping("/get")
@HystrixCommand(fallbackMethod = "getStudentFallBack")
public Student getStudent(@RequestParam String id){
    int i=1/0;
    Student student = searchClient.searchStudent("2");
    return student;
}

3.线程隔离

如果使用Tomcat的线程池去处理用户的请求,使用当前线程去执行其他服务的功能,如果某一个服务出现故障,导致Tomcat的线程大量的堆积,无法处理其他业务。
github.com/Netflix/Hystrix/Wiki/Configuration

  1. Hystrix的线程池(默认),接受用户请求采用tomcat的线程池,调用其他服务时,采用Hystrix的线程池。
  2. 信号量,使用的还是Tomcat的线程池,帮助我们去管理Tomcat的线程池。

3.1Hystrix的线程池的配置

3.2Hystrix的信号量的配置

4.断路器

在调用指定服务时,如果说这个服务的失败率达到你输入的一个阈值,将断路器从closed状态,转变为open状态,指定服务时无法被访问的,如果你访问就直接走fallback方法,在一定的时间内,open状态会再次转变为half open状态,允许一个请求发送到我的指定服务,如果成功,转变为closed,失败则再次转为open,然后循环到half open ,直到断路器转回到closed状态。

配置断路器监控界面

  1. 导入依赖
  2. 在启动类添加注解

6.请求缓存

6.1创建Service调用client

/**
 * @author Zouch
 * @date 2020/11/2 17:07
 * @description
 */
@Service
public class CustomerService {

    @Autowired
    SearchClient searchClient;


    @CacheResult
    @HystrixCommand(commandKey = "findById")
    public Student findById(@CacheKey String id){
        return searchClient.searchStudent(id);
    }

    @CacheRemove(commandKey = "findById")
    public void clearFindById(@CacheKey String id ){
        System.out.println("findById缓冲"+id+"被清空");
    }

}

6.2使用请求缓存的注解

@CacheResult    缓存当前方法的返回结果(必须和HystrixCommand配合使用)
@CacheRemove    帮助我们清除某一个缓存信息(基于commandKey)
@CacheKey       指定哪个方法参数作为缓存的key

6.3编写Filter,去构建HystrixRequestContext

/**
 * @author Zouch
 * @date 2020/11/2 17:13
 * @description
 */
@WebFilter("/*")
public class HystrixRequestContextInitFilter implements Filter {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HystrixRequestContext.initializeContext();
        filterChain.doFilter(servletRequest,servletResponse);
    }
}

//!!注意要在启动类添加 @ServletComponentScan("com.zouch.filter")

五、Zuul-服务网关

客户端维护大量的ip和端口信息,直接访问指定服务

认证和授权操作,需要每一个模块都添加认证和授权的操作

项目迭代,服务拆分,客户端需要进行大量的变化

统一把安全性校验都放在zuul中

1.使用Zuul

5.1创建maven项目,修改为springboot

​ new module ->

5.2导入依赖

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

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

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

5.3添加注解

@EnableEurekaClient
@EnableZuulProxy

5.4编写配置文件

#指定eureka服务地址
eureka:
  client:
    service-url:
      defaultZone: http://root:root@localhost:8761/eureka,http://root:root@localhost:8762/eureka


spring:
  application:
    name: ZUUL
server:
  port: 8083

5.5测试从Zuul调用

http://localhost/customer/customer             #   http://localhost/服务名/接口地址

2.Zuul监控配置

2.1导入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-actuator</artifactId>
</dependency>

2.2增加配置

#查看Zuul的监控界面(开发时配置为*,上生产不配置)
management:
  endpoints:
    web:
      exposure:
        include: "*"

2.3输入网址查看

http://localhost/actuator/routes http://localhost/服务名/路径

{

  • /search/**: “search”,
  • /zuul/**: “zuul”,
  • /customer/**: “customer”,
  • /unknown/**: “unknown”

}

3.忽略服务配置

zuul:
  #基于服务名忽略服务,监控界面无法查看到     如果忽略全部服务   “*”
  ignored-services: eureka
  #路径含有search的都会被拦截,监控页面可以查看到,但访问时会404(被拦截)
  ignored-patterns: /**/search/**

4.自定义服务配置

zuul:
  #指定自定义服务(方式一,key(服务名):value(路径))
#  routes:
#    search: /ss/**
#    customer: /cc/**
  #指定自定义服务(方式二)
  routes:
    kehu:      #自定义名称
      path: /ccc/**      #映射的路径
      serviceId: customer      #服务名称
      #还有很多属性可以定义

5.灰度发布

5.1添加一个配置类

@Bean
public PatternServiceRouteMapper serviceRouteMapper() {
    return new PatternServiceRouteMapper(
            "(?<name>^.+)-(?<version>v.+$)",
            "${version}/${name}");
}
//服务名-v几
// /v几/路径

5.2准备一个服务,提供两个版本

version: v1
spring:
  application:
    name: CUSTOMER-${version}

5.3修改Zuul的配置

6.Zuul的过滤器执行流程

客户端请求发送到Zuul服务上,首先通过PreFilter链,如果正常放行,会把请求转发给RoutingFilter,请求转发到一个指定的服务,在服务响应结果后,再次走PostFilter过滤连,最终将响应结果交给客户端。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Hq3kZ3Oj-1604556691347)(C:\Users\zouch\AppData\Roaming\Typora\typora-user-images\image-20201105112855763.png)]

6.1实现过滤器

创建pojo类实现ZuulFilter

/**
 * @author Zouch
 * @date 2020/11/5 11:37
 * @description
 */
@Component
public class ZuulFilterTest extends ZuulFilter {

指定过滤器类型

@Override
    public String filterType() {
        return FilterConstants.PRE_TYPE;
    }

指定执行顺序

@Override
public int filterOrder() {
    return FilterConstants.PRE_DECORATION_FILTER_ORDER - 1;
}

配置是否启用

@Override
public boolean shouldFilter() {
    //开启过滤器
    return true;
}

指定过滤器具体业务代码

@Override
public Object run() throws ZuulException {
    System.out.println("过滤器执行!!!!!");
    return null;
}

6.2Filter实现Token校验

1.准备访问路径,请求参数传递token

http://localhost/customer/customer?token=122

2.创建AuthenticationFilter

/**
 * @author Zouch
 * @date 2020/11/5 12:01
 * @description
 */
@Component
public class AuthenticationFilter extends ZuulFilter {


    @Override
    public String filterType() {
        return FilterConstants.PRE_TYPE;
    }

    @Override
    public int filterOrder() {
        return FilterConstants.PRE_DECORATION_FILTER_ORDER-2;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run() throws ZuulException {
        //获取request对象
        RequestContext currentContext = RequestContext.getCurrentContext();
        HttpServletRequest request = currentContext.getRequest();

        //获取token参数
        String token = request.getParameter("token");

        if (token==null||"123".equals(token)){
            //token校验失败,直接响应数据
            currentContext.setSendZuulResponse(false);
            currentContext.setResponseStatusCode(HttpStatus.SC_UNAUTHORIZED);
        }

        //这个return几乎没有意义,不用管
        return null;
    }
}

3.在run方法编写代码

@Override
public Object run() throws ZuulException {
    //获取request对象
    RequestContext currentContext = RequestContext.getCurrentContext();
    HttpServletRequest request = currentContext.getRequest();

    //获取token参数
    String token = request.getParameter("token");

    if (token==null||"123".equals(token)){
        //token校验失败,直接响应数据
        currentContext.setSendZuulResponse(false);
        currentContext.setResponseStatusCode(HttpStatus.SC_UNAUTHORIZED);
    }

    //这个return几乎没有意义,不用管
    return null;
}

4.测试

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IyIuHBQe-1604556691353)(C:\Users\zouch\AppData\Roaming\Typora\typora-user-images\image-20201105131306911.png)]

7.Zuul降级

1创建pojo类 ,实现FallBackProvider接口

@Component
public class ZuulFallBack implements FallbackProvider {

2.重写方法

/**
 * @author Zouch
 * @date 2020/11/5 13:16
 * @description Zuul这边处理降级,目的是可以处理客户端没有实现降级的服务
 */
@Component
public class ZuulFallBack implements FallbackProvider {
    @Override
    public String getRoute() {
        return "*";   //代表全部出现问题的方法,都走这个降级方法
    }

    @Override
    public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
        System.out.println("降级的服务"+route);
        System.out.println("降级的原因"+cause);

        return new ClientHttpResponse(){

            @Override
            public HttpHeaders getHeaders() {
                //指定响应头信息
                HttpHeaders headers = new HttpHeaders();
                headers.setContentType(MediaType.APPLICATION_JSON);
                return headers;
            }

            @Override
            public InputStream getBody() throws IOException {
                //给用户响应的信息
                String msg = "当前服务" + route + "出现问题";
                return new ByteArrayInputStream(msg.getBytes());
            }

            @Override
            public HttpStatus getStatusCode() throws IOException {
                //指定具体的httpstatus
                return HttpStatus.INTERNAL_SERVER_ERROR;
            }

            @Override
            public int getRawStatusCode() throws IOException {
                //返回的状态码
                return HttpStatus.INTERNAL_SERVER_ERROR.value();
            }

            @Override
            public String getStatusText() throws IOException {
                //指定错误信息
                return HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase();
            }

            @Override
            public void close() {

            }
        };
    }
}

3.测试

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-y2YfTttS-1604556691357)(C:\Users\zouch\AppData\Roaming\Typora\typora-user-images\image-20201105133018194.png)]

8.Zuul动态路由

1.创建过滤器

/**
 * @author Zouch
 * @date 2020/11/5 13:35
 * @description  从redis获取服务名实现动态路由,好处是修改时不需要重启项目,动态修改
 */
@Component
public class DynamicRoutingFilter extends ZuulFilter {

    @Override
    public String filterType() {
        return FilterConstants.PRE_TYPE;
    }

    @Override
    public int filterOrder() {
        //执行顺序放到pre过滤器后面
        return FilterConstants.PRE_DECORATION_FILTER_ORDER+ 2;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run() throws ZuulException {
        //1.拿到request对象
        RequestContext requestContext = RequestContext.getCurrentContext();
        HttpServletRequest request = requestContext.getRequest();

        //2.获取参数,rediskey
        String redisKey = request.getParameter("redisKey");
        //3.直接判断
        if (redisKey !=null&&redisKey.equalsIgnoreCase("customer")){
            requestContext.put(FilterConstants.SERVICE_ID_KEY,"/customer");
        } else if (redisKey !=null&&redisKey.equalsIgnoreCase("search")){
            requestContext.put(FilterConstants.SERVICE_ID_KEY,"/customer");
        }
        return null;
    }
}

2.run方法编写

   @Override
    public Object run() throws ZuulException {
        //1.拿到request对象
        RequestContext requestContext = RequestContext.getCurrentContext();
        HttpServletRequest request = requestContext.getRequest();

        //2.获取参数,rediskey
        String redisKey = request.getParameter("redisKey");
        //3.直接判断
        if (redisKey !=null&&redisKey.equalsIgnoreCase("customer")){
            requestContext.put(FilterConstants.SERVICE_ID_KEY,"/customer");
        } else if (redisKey !=null&&redisKey.equalsIgnoreCase("search")){
            requestContext.put(FilterConstants.SERVICE_ID_KEY,"/customer");
        }
        return null;
    }

未完待续

六、Spring Cloud Config

七、Sleuth

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

KISSING_MONSTER

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值