• springcloud第一篇:Eureka、Feign、Ribbon


    Eureka是负责服务注册与发现的。

    Eureka分为服务端Eureka Server和客户端Eureka Client。服务端就是注册中心,可单机,可集群,生产上肯定集群。客户端就是各业务应用,根据调用关系又分为服务提供者和服务调用者,这些应用都要注册到服务端。

    Eureka服务端和客户端都是springboot应用。

    Eureka服务端:本处讲解单机Eureka Server。其实集群也非常简单,就是把每个Eureka Server节点都注册到其他Eureka server节点上。

    1、在应用中引入Eureka Server依赖

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

    2、用@EnableEurekaServer标注启动类

    3、在配置文件中添加

    spring.application.name=spring-cloud-eureka-server
    server.port=8000
    eureka.client.register-with-eureka=false
    eureka.client.fetch-registry=false
    eureka.client.serviceUrl.defaultZone=http://127.0.0.1:8000/eureka/

    以上两个false表示不将自己注册到Eureka Server。eureka.client.serviceUrl.defaultZone不能省,不然Eureka Server应用日志会一直报Network level connection to peer localhost; retrying after delay,虽然不影响注册中心的使用。

    启动应用,访问127.0.0.1:8000,可以看到注册中心启动成功

    Eureka Server有定时任务:

    每1分钟会执行AbstractInstanceRegistry的run()方法。AbstractInstanceRegistry有个EvictionTask内部类,EvictionTask其实是个线程类,继承了实现Runnable接口的TimerTask类。

    每15分钟更新renewal threshold。PeerAwareInstanceRegistryImpl类中,有一个Timer实例,每15分钟执行一次updateRenewalThreshold()方法,这个15分钟是由EurekaServerConfig接口的getRenewalThresholdUpdateIntervalMs()方法取的,默认15分钟,我们可以在配置文件中用eureka.server.renewal-threshold-update-interval-ms修改间隔时间

    Eureka Client之服务提供者

    1、在一个对外提供RESTful接口服务的springboot应用中(比如说X管家应用)引入Eureka Client依赖

    <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>

    2、用@EnableDiscoveryClient标注启动类

    3、把自己注册到Eureka Server。在配置文件中添加

    spring.application.name=spring-cloud-eureka-client-producer
    server.port=9000
    eureka.client.serviceUrl.defaultZone=http://127.0.0.1:8000/eureka/

    spring.application.name很重要,届时服务调用者就根据这个来找服务提供者。eureka.client.serviceUrl.defaultZone值是Eureka Server的地址,如果Eureka Server有多个节点,则用逗号分隔。

    启动应用,刷新Eureka Server的页面,发现Instances currently registered with Eureka处多了一行数据,正是刚刚启动的Eureka Client,说明服务提供者注册成功了。

    Eureka Client之服务调用者

    1、引入Eureka Client依赖,推荐用Feign调用服务。如果当前应用也对外提供RESTful接口,则还需要引入springboot的web starter。

    <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-openfeign</artifactId>
    </dependency>

    2、用@EnableDiscoveryClient和@EnableFeignClients标注启动类。

    3、把自己注册到Eureka Server。在配置文件中添加

    spring.application.name=spring-cloud-eureka-client-consumer
    server.port=9001
    eureka.client.serviceUrl.defaultZone=http://127.0.0.1:8000/eureka/

    4、写Feign代码,调用服务提供者的接口。

    @FeignClient(name = "spring-cloud-eureka-client-producer")
    @Component
    public interface HelloRemote {
    @RequestMapping(value = "/hello")
    String hello(@RequestParam String name);
    }

    @FeignClient的name属性值必须是Eureka Client服务提供者应用的spring.application.name值。@RequestMapping的value属性值就是要调用的服务提供者的接口,本例中是/hello接口。用@RequestMapping标注的接口,既可以向服务提供者发送GET请求,又可以发送POST请求,默认是GET请求。如果想发起POST请求,则要用@PostMapping。不管是GET请求,还是POST请求,各参数都要用@RequestParam标注,否则发出的请求是不带参数的。如果是发application/json请求,则要用@RequestBody标注参数,参数类型可以是Map,也可以是自定义实体类。服务提供者的接口如果用自定义实体类接收参数的话,则要有无参构造器,否则会报Invalid JSON input: Cannot construct instance。

    在Controller或者Service代码中注入HelloRemote即可,如此服务调用者就可以调用服务提供者提供的接口了。例如

    @RestController
    public class ConsumerController {

    @Autowired
    HelloRemote helloRemote;

    @RequestMapping("/hello/{name}")
    public String index(@PathVariable("name") String name) {
    return helloRemote.hello(name);
    }

    }

    启动Eureka Client服务调用者,刷新Eureka Server页面,会发现Instances currently registered with Eureka处新增了本应用。

    在浏览器中调用服务消费者提供的/hello/{name}接口,如127.0.0.1:9001/hello/zhansan,服务消费者在处理请求过程中会调用服务提供者提供的/hello接口,即127.0.0.1:9000/hello/zhansan,服务提供者响应数据给服务调用者,服务调用者得到数据后再处理,最终响应数据给前端页面。

    Eureka client启动的时候会向Eureka server发送注册请求,Eureka server的类会处理这些请求。

    客户端的DiscoveryClient在实例化时,会构造几个定时任务,拉取注册信息的任务、发送心跳的任务,默认都是30s执行一次。

    Eureka自我保护机制:

    在某些情况下,Eureka Server页面会出现

    这就说明Eureka自我保护机制在起作用了。

    Feign

    Feign能够在接口上添加注释,成为一个REST API的客户端。用@FeignClient标注接口,其name属性值表示调用哪个服务,fallback值是当前接口的实现类的class实例,在服务提供者不稳定或宕机导致熔断时,提供降级数据。

    上面Feign的配置,在调用第三方接口时,使用的是jdk原生的HttpURLConnection发送http请求,没有连接池。可以在服务提供者应用中查看请求的user-agent请求头来验证这一点,此时user-agent的值是Java。

    这里可以优化下,用HttpClient,通过设置连接池、超时时间等对服务之间的调用进行优化。

    如果用HttpClient,则需要修改如下

    1、引入Feign和HttpClient的整合包

    <dependency>
        <groupId>io.github.openfeign</groupId>
        <artifactId>feign-httpclient</artifactId>
        <version>10.8</version>
    </dependency>

    2、在配置文件中添加

    feign.httpclient.enabled=true

    这样就可以了。在服务提供者应用中查看请求的user-agent请求头,发现user-agent的值变为Apache-HttpClient。连接池的最大连接数、每个连接的存活时间会用一些默认配置,这些默认配置可以在配置文件中用feign.httpclient.来查看,如果想更改这些参数,只需新增配置项覆盖默认配置即可。注意,feign.httpclient.connection-timeout这个配置本是用来指定连接超时时间的,但是实际使用不生效,超时配置需要使用feign.client.config。

    超时重试机制:

    重试只会在超时的情况下才会发生,404、50X的情况不会重试,每次重试都会根据负载均衡策略重新找服务器。

    超时重试配置:

    1、在配置文件中添加:

    feign.client.config.default.connect-timeout=10000
    feign.client.config.default.read-timeout=10000

    default表示对所有的服务都适用,本例连接超时和读取超时都是10s。如果想针对不同的服务指定不同的超时时间,把default用特定服务名替代即可。

    2、生成一个Retryer实例

    @Bean
    public Retryer feignRetryer() {
        return new Retryer.Default(1000, 2000, 3);
    }

    Retryer是个接口,可以用其内部实现类Default来构造Retryer实例。第三个参数3表示最多执行3次,最多重试2次。

    重试相关的源码在SynchronousMethodHandler的invoke()方法中:

      public Object invoke(Object[] argv) throws Throwable {
        RequestTemplate template = buildTemplateFromArgs.create(argv);
        Options options = findOptions(argv);
        Retryer retryer = this.retryer.clone();
        while (true) {
          try {
            return executeAndDecode(template, options);
          } catch (RetryableException e) {
            try {
              retryer.continueOrPropagate(e);
            } catch (RetryableException th) {
              Throwable cause = th.getCause();
              if (propagationPolicy == UNWRAP && cause != null) {
                throw cause;
              } else {
                throw th;
              }
            }
            if (logLevel != Logger.Level.NONE) {
              logger.logRetry(metadata.configKey(), logLevel);
            }
            continue;
          }
        }
      }

    重试这里有个死循环,第一次调用,假如不抛出超时异常,则跳出循环,否则捕获异常,在catch语句中,我们可以做一些延时操作,使得可以稍微等一会再重试。如果重试次数达到上限,则在catch语句中抛出异常,跳出死循环。在创建Retryer实例时可以指定每次重试之前等多少时间以及重试几次,默认会最多等1s,重试5次。

    Ribbon

    如果有多个服务提供者,则会自动用Ribbon做负载均衡。Ribbon提供多种负载均衡策略,如默认的ZoneAvoidanceRule、轮询RoundRobinRule、根据响应时间加权轮询WeightedResponseTimeRule、随机RandomRule、可用性过滤AvailabilityFilteringRule、最少连接数BestAvailableRule、RetryRule。

    假设一个应用内会调用多种服务,那么如何针对不同的服务指定不同的负载均衡策略呢?

    假设想调用service1时根据响应时间加权轮询,调用service2时随机,则只需在配置文件中添加

    service1.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.WeightedResponseTimeRule
    service2.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule

    如果想所有的服务都用相同的负载均衡策略,比如说轮询策略,则可以在启动类所在包或其子包下新建一个config类,里面用@Bean生成一个IRule实例:

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

    不生成IRule实例的话,默认所有服务的负载均衡策略都是ZoneAvoidanceRule。

    我们也可以用spring cloud balancer替换Ribbon(在配置文件中,设置spring.cloud.loadbalancer.ribbon.enabled=false),但是spring cloud balancer目前只提供一个原生的负载策略,即轮询,其他策略需要我们自己实现,所有还是Ribbon香。

  • 相关阅读:
    vue.js3: 使用全局css样式文件(vue@3.2.37)
    vue.js3:多张图片转pdf(jspdf@2.5.1 / vue@3.2.37)
    技术解读:现代化工具链在大规模 C++ 项目中的运用 | 龙蜥技术
    项目环境稳定性指标建设之路
    从趋势到挑战,资深工程师一站式解读:操作系统运维和可观测性
    一文剖析PolarDB HTAP的列存数据压缩
    EasyCV DataHub 提供多领域视觉数据集下载,助力模型生产
    OpenKruise v1.3:新增自定义 Pod Probe 探针能力与大规模集群性能显著提升
    《领域驱动设计》:从领域视角深入仓储(Repository)的设计和实现
    基于IoT全链路实时质量魔洛哥
  • 原文地址:https://www.cnblogs.com/koushr/p/9211801.html
Copyright © 2020-2023  润新知