zoukankan      html  css  js  c++  java
  • 四. SpringCloud负载均衡与调用

    1. Ribbon概述

    1.1 Ribbon是什么

    SpringCloud Ribbon是基于Netflix Ribbon实现的一套客户端,是负载均衡的工具。

    Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件复杂均衡算法和服务调用。Ribbon客户端组件提供一系列完整的配置项如连接超时、重试等。简单的说,就是在配置文件中列出Load Balancer(负载均衡简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。也可以使用Ribbon实现自定义的负载均衡算法。

    1.2 Ribbon能做什么

    主要是负载均衡(LB):所谓负载均衡,简单的说就是将用户的请求平摊的分配到多个服务上,从而达到系统的HA(High Available高可用),常见的负载均衡有软件Nginx、LVS,硬件F5等。

    Ribbon本地负载均衡客户端和Nginx服务端负载均衡的区别:

    • Nginx是服务器负载均衡,客户端所有请求都会交给Nginx,然后由Nginx实现转发请求,即负载均衡是由服务端实现的。
    • Ribbon是本地负载均衡,在调用微服务接口时候,会在注册中心上获取注册信息服务列表之后缓存到JVM本地,从而在本地实现RPC远程服务调用技术。

    负载均衡又分为两类,分别可以对应于Nginx和Ribbon:

    • 集中式LB:即在服务的消费方和提供方之间使用独立的LB设施(可以是硬件,如F5,也可以是软件,如Nginx),由该设施负责把访问请求通过某种策略转发至服务的提供方。
    • 进程内LB:将LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的服务器,Ribbon就属于进程内LB,它只是一个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址。

    Ribbon实际上就是负载均衡 + RestTemplate调用

    2. Ribbon使用案例

    2.1 架构说明

    Ribbon其实就是一个负载均衡的客户端组件,他可以和其他所需请求的客户端结合使用,和eureka结合只是其中的一个实例。Ribbon在工作的时候分两步:

    • 先选择EurekaServer,优先选择同一个区域内负载较少的Server

    • 根据用户指定的策略,从Server取到的服务注册列表中选择一个地址

    其中Ribbon提供了多种的负载均衡策略,如轮询、随机和根据响应时间加强等。

    image-20210128235245749
    2.2 pom.xml

    在POM文件中我们引入了如下依赖:

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

    点开该依赖的源码,我们发现事实上该依赖内部已经引入了Ribbon,其引入Ribbon的源码如下:

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
        <version>2.2.1.RELEASE</version>
        <scope>compile</scope>
    </dependency>
    

    我们在Maven的依赖中也可以看到,在引入 spring-cloud-starter-netflix-eureka-client 的同时我们就已经引入了 **spring-cloud-starter-netflix-ribbon **,所以我们没必要单独添加Ribbon的依赖。

    image-20210128231128616
    1.3 RestTemplate使用

    RestTemplate官方说明可以在RestTemplate官方API查看,下面简要说明其主要方法

    • getForObject方法:返回对象为响应体数据转化成的对象,基本上可以理解为Json对象。
    • getForEntity方法:返回对象为ResponseEntity对象,包含了响应中的一些重要信息,比如响应头、响应状态码、响应体等。
    @RestController
    @Slf4j
    @RequestMapping("/consumer")
    public class OrderController {
        @Resource
        private RestTemplate restTemplate;
    
        // private static final String PAYMENT_URL = "http://localhost:8001";
        private static final String PAYMENT_SRV = "http://CLOUD-PAYMENT-SERVICE";
    
        @GetMapping("/payment/get/{id}")
        public CommonResult<Payment> getPayment(@PathVariable("id") Long id) {
            return restTemplate.getForObject(PAYMENT_SRV
                    + "/payment/get/"
                    + id, CommonResult.class);
        }
    
        @GetMapping("/payment/getForEntity/{id}")
        public CommonResult<Payment> getPayment2(@PathVariable("id") Long id) {
            ResponseEntity<CommonResult> entity =
                    restTemplate.getForEntity(PAYMENT_SRV
                     + "/payment/get/" + id, CommonResult.class);
            if (entity.getStatusCode().is2xxSuccessful()) {
                log.info("===> " + entity.getStatusCode()
                      + "	" + entity.getHeaders());
                return entity.getBody(); //返回请求体
            } else {
                return new CommonResult<>(444, "操作失败");
            }
        }
    }
    

    在后台控制台也输出了状态码和请求头的如下日志:

    ===> 200 OK	[Content-Type:"application/json", Transfer-Encoding:"chunked", Date:"Thu, 28 Jan 2021 15:44:50 GMT", Keep-Alive:"timeout=60", Connection:"keep-alive"]
    

    3. Ribbon核心组件IRule接口

    3.1 IRule理解

    它可以根据特定算法从服务列表中选取一个要访问的服务

    IRule是一个接口,其源码如下:

    package com.netflix.loadbalancer;
    
    /**
     * Interface that defines a "Rule" for a LoadBalancer. A Rule can be thought of
     * as a Strategy for loadbalacing. Well known loadbalancing strategies include
     * Round Robin, Response Time based etc.
     * 
     * @author stonse
     * 
     */
    public interface IRule{
        /*
         * choose one alive server from lb.allServers or
         * lb.upServers according to key
         * 
         * @return choosen Server object. NULL is returned if none
         *  server is available 
         */
    
        public Server choose(Object key);
        
        public void setLoadBalancer(ILoadBalancer lb);
        
        public ILoadBalancer getLoadBalancer();    
    }
    

    以下是IRule接口的部分实现,这些实现分别对应了若干负载均衡算法

    image-20210128235959015

    以下简要说明7种主要的负载均衡算法,这些负载均衡算法均是抽象类com.netflix.loadbalancer.AbstractLoadBalancerRule 的实现,而给抽象类实现了IRule接口:

    • com.netflix.loadbalancer.RoundRobinRule:轮询,为默认的负载均衡算法
    • com.netflix.loadbalancer.RandomRule:随机
    • com.netflix.loadbalancer.RetryRule:先按照RoundRobinRule(轮询)的策略获取服务,如果获取服务失败则在指定时间内进行重试,获取可用的服务
    • com.netflix.loadbalancer.WeightedResponseTimeRule:对RoundRobinRule的扩展,响应速度越快的实例选择权重越大,越容易被选择。
    • com.netflix.loadbalancer.BestAvailableRule:先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务
    • com.netflix.loadbalancer.AvailabilityFilteringRule:先过滤掉故障实例,再选择并发较小的实例
    • com.netflix.loadbalancer.ZoneAvoidanceRule:复合判断Server所在区域的性能和Server的可用性选择服务器
    3.2 如何替换负载均衡算法

    服务消费者80添加轮询算法配置类

    首先我们应该明确是服务消费方采用轮询算法来访问同一服务提供方的不同微服务实例,所以我们应该在服务消费方80方的微服务中添加轮询算法配置类。

    在添加配置类时,有必须要注意的点,就是官方文档明确给出了警告:这个自定义的轮询算法配置类不能放在@ComponentScan注解所扫描的当前包下以及子包下,否则自定义的这个配置类就会被所有Ribbon客户端所共享,就达不到特殊化定制的目的了。换句话说,如果这个配置类我们能够被@ComponentScan注解扫描到,那么访问所有的微服务提供方的具体实例时,我们都会采取配置类中的算法,如果要特殊化定制 - 即指定访问某些微服务提供方时采用配置的轮询算法,那么我们就应该使这个配置类让@ComponentScan注解扫描不到,我们知道在主启动类的@SpringBootApplication注解中,其实这个注解包含了@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan这三个注解,所以我们写的轮询算法配置类不能和主启动类在同一个包下,所以我们需要建新的包,实现定制轮询算法的配置类:

    package com.polaris.myrule;
    
    /**
     * @author polaris
     */
    @Configuration
    public class MySelfRule {
    
        @Bean
        public IRule myRule() {
            return new RandomRule(); //定义随机负载均衡算法
        }
    }
    

    包结构的内容如下,我们可以看到,轮询算法配置类在主启动类的@ComponentScan扫描不到的包下:

    image-20210129000859823

    服务消费者80主启动类中添加@RibbonClient注解

    @SpringBootApplication
    @EnableEurekaClient
    //访问的微服务为CLOUD-PAYMENT-SERVICE,采用配置文件中的轮询算法
    @RibbonClient(name = "CLOUD-PAYMENT-SERVICE", configuration = MySelfRule.class)
    public class OrderMain {
        public static void main(String[] args) {
            SpringApplication.run(OrderMain80.class);
        }
    }
    

    测试

    测试发现我们用服务消费方访问服务提供方的微服务时,8001和8002不再交替轮询访问,而是随机访问。

    4. Ribbon负载均衡算法

    4.1 默认负载均衡算法(轮询)原理

    轮询负载均衡算法原理:Rest接口第几次请求数 % 服务器集群总数量 = 实际调用服务器位置下标, 每次服务重启后Rest接口计数从1开始。

    List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE")
    

    根据服务方的服务名,获取其所有实例,如有以下实例:

    List[0] List[1]
    服务名 payment8001 payment8002
    服务地址 127.0.0.1:8001 127.0.0.1:8002

    这两个实例组合成一个集群,共2台机器,集群总数为2,按照轮询负载均衡算法原理:

    • 请求总数为1时,1 % 2 = 1,对应下标位置是1,获得服务地址127.0.0.1:8001

    • 请求总数为2时,2 % 2 = 0,对应下标位置是0,获得服务地址127.0.0.1:8002

    • 请求总数为3时,3 % 2 = 1,对应下标位置是1,获得服务地址127.0.0.1:8001

    • ...

    4.2 轮询源码分析

    com.netflix.loadbalancer.RoundRobinRule源码的负载均衡算法部分分析如下(代码中标注了中文注释):

    package com.netflix.loadbalancer;
    
    /**
     * The most well known and basic load balancing strategy, i.e. Round Robin Rule.
     */
    public class RoundRobinRule extends AbstractLoadBalancerRule {
    
       	//...
    
        public Server choose(ILoadBalancer lb, Object key) {
            if (lb == null) {
                log.warn("no load balancer");
                return null;
            }
    
            Server server = null;
            int count = 0;
            while (server == null && count++ < 10) {
                //获得还活着的健康的服务实例(机器)即可达的,也就是Status为up的实例
                List<Server> reachableServers = lb.getReachableServers();
                //获取所有服务实例,无论是死是活,只要注册进服务中心即可
                List<Server> allServers = lb.getAllServers();
                //Status为up的服务实例数量
                int upCount = reachableServers.size();
                //所有服务实例的数量,对应上述原理分析中的服务器集群总数量
                int serverCount = allServers.size();
    
                //如果没有可达的服务实例的话,直接报警告
                if ((upCount == 0) || (serverCount == 0)) {
                    log.warn("No up servers available from load balancer: " + lb);
                    return null;
                }
    
                //调用服务器位置下标 = incrementAndGetModulo(服务器集群总数)
                int nextServerIndex = incrementAndGetModulo(serverCount);
                server = allServers.get(nextServerIndex);//根据下标获取服务实例
    
                if (server == null) {
                    /* Transient. */
                    Thread.yield();
                    continue;
                }
    
                if (server.isAlive() && (server.isReadyToServe())) {
                    return (server);
                }
    
                // Next.
                server = null;
            }
    
            if (count >= 10) {
                log.warn("No available alive servers after 10 tries from load balancer: "
                        + lb);
            }
            return server;
        }
    
        /**
         * Inspired by the implementation of {@link AtomicInteger#incrementAndGet()}.
         *
         * @param modulo The modulo to bound the value of the counter.
         * @return The next value.
         */
        private int incrementAndGetModulo(int modulo) {
            for (;;) {
                int current = nextServerCyclicCounter.get();
                int next = (current + 1) % modulo;
                if (nextServerCyclicCounter.compareAndSet(current, next))
                    return next;
            }
        }
    }
    
    4.3 自己实现轮询负载均衡算法

    首先我们将服务注册中心(7001/7002构成集群)启动,然后在服务提供方8001/8002中的Controller中添加功能,用来一会儿测试服务消费方80来轮询访问CLOUD-PAYMENT-SERVICE服务:

    @GetMapping("/payment/lb")
    public String getPaymentLB(){
    	return serverPort;
    }
    

    服务提供方的这个方法就是简单的在页面输出自己的端口号,也就是我们可以在页面区分访问的CLOUD-PAYMENT-SERVICE服务到底对应的是8001实例还是8002实例。

    启动8001/8002,将两个服务实例注册进服务注册中心后,我们再改造服务消费方80服务,分为以下四步:

    • 首先我们先让RestTemplate失去Ribbon中的负载均衡能力,取消掉@LoadBalanced注解即可:
    @Configuration
    public class ApplicationContextConfig {
        @Bean
    //    @LoadBalanced//使用该注解赋予RestTemplate负载均衡的能力
        public RestTemplate getRestTemplate() {
            return new RestTemplate();
        }
    }
    //applicationContext.xml <bean id="" class="">
    
    • 然后编写自己的负载均衡接口:

    给接口定义了方法instances用于在服务提供方服务的所有服务实例中选择一个具体实例。

    public interface LoadBalancer {
        /**
         * 从服务列表中用负载均衡算法选择出具体的实例
         * @param serviceInstances 服务列表
         * @return
         */
        ServiceInstance instances(List<ServiceInstance> serviceInstances);
    }
    
    • 用轮询负载均衡算法实现负载均衡接口:

    RoundRobinRule源码中用for(;;)实现的自旋锁,这里我们用do{} while();实现自旋锁。

    @Component
    public class MyLB implements LoadBalancer {
    
        private AtomicInteger atomicInteger = new AtomicInteger(0);
    
        public final int getAndIncrement() {
            int current;
            int next;
            //自旋锁
            do {
                current = this.atomicInteger.get(); //初始值为0
                next = current >= 2147483647 ? 0 : current + 1; //最大整数
            } while (!this.atomicInteger.compareAndSet(current, next));
            System.out.println("===> 访问次数next:" + next);
            return next;
        }
    
        /**
         * 从服务列表中用轮询负载均衡算法选择出具体的实例
         * Rest接口第几次请求数 % 服务器集群总数量 = 实际调用服务器位置下标
         *
         * @param serviceInstances 服务列表
         * @return
         */
        @Override
        public ServiceInstance instances(List<ServiceInstance> serviceInstances) {
            int index = getAndIncrement() % serviceInstances.size();
            return serviceInstances.get(index);
        }
    }
    
    • 最后我们在80服务的Controller中添加方法:
    @RestController
    @Slf4j
    @RequestMapping("/consumer")
    public class OrderController {
        @Resource
        private RestTemplate restTemplate;
    
        @Resource
        private LoadBalancer loadBalancer;
    
        @Resource
        private DiscoveryClient discoveryClient;
    
        @GetMapping("payment/lb")
        public String getPaymentLB() {
            //获取服务提供方所有的服务实例
            List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
            if (instances == null || instances.size() <= 0) {
                return null;
            }
            //采用自己实现的轮询负载均衡算法选择具体实例
            ServiceInstance serviceInstance = loadBalancer.instances(instances);
            URI uri = serviceInstance.getUri();
            return restTemplate.getForObject(uri + "/payment/lb", String.class);
        }
    }
    
    

    在浏览器中输入http://localhost/consumer/payment/lb,也就是80端口的服务消费方采用我们自己编写的轮询负载均衡算法访问CLOUD-PAYMENT-SERVICE服务的具体实例,测试成功,在服务消费方80服务的后端控制台也输出了的日志。

    5. OpenFeign概述

    5.1 OpenFeign是什么?

    Feign是一个声名式WebService客户端,使用Feign能让编写WebService客户端更加简单。它的使用方法是定义一个服务接口然后在上面添加注解。Feign也支持可拔插式的编码器和解码器。SpringCloud对Feign进行了封装,使其支持了Spring MVC标准注解和HttpMessageConverters。Feign可以与Eureka和Ribbon组合使用以支持负载均衡。

    5.2 Feign能做什么?

    Feign旨在使编写Java Http客户端变得更容易。之前我们使用Ribbon + RestTemplate时,利用RestTemplate对Http请求的封装处理,形成了一套模板化的调用方法。但是在实际开发中由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用。所以Feign在此基础上做了进一步封装,由它来帮助定义和实现依赖服务接口的定义。在Feign的实现下,只需要创建一个接口并使用注解的方式来配置它(例如以前是DAO接口上面标注Mapper注解,现在是一个微服务接口上面标注一个Feign注解),即可完成对服务提供方的接口绑定,简化了使用SpringCloud Ribbon时,自动封装服务调用客户端的开发量。

    Feign集成了Ribbon:利用Ribbon维护服务提供方的服务列表信息,并且通过如轮询的算法实现了客户端的负载均衡。而与Ribbon不同的是,通过Feign只需要定义服务绑定接口且以声明式的方式,优雅而简单的实现了服务调用。

    5.3 Feign和OpenFeign的区别

    Feign已经停止维护,所以我们只需要关注OpenFeign的使用即可,我们现在学习的就是利用OpenFeign实现我们之前用的Ribbon+RestTemplate实现的功能。

    Feign OpenFeign
    特点 Feign是SpringCloud组件中的一个轻量级RESTful的HTTP服务客户端,Feign内置了Ribbon,用来做客户端的负载均衡,去调用服务注册中心的服务。Feign的使用方式是:使用Feign的注解定义接口,调用这个接口,就可以调用服务注册中心的服务。 OpenFeign是SpringCloud在Feign的基础上支持了SpringMVC的注解,如@RequestMapping 等。OpenFeign的 @FeignClient 可以解析SpringMVC的 @RequestMapping 注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。
    启动器 spring-cloud-starter-feign spring-cloud-starter-openfeign

    6. OpenFeign使用案例

    6.1 接口+注解,新建Module

    新建cloud-consumer-feign-order80作为服务消费方服务。

    在微服务调用的接口上添加注解@FeignClient,注意OpenFeign在服务消费方使用

    6.2 pom.xml

    在POM中我们引入了OpenFeign的依赖以及Eureka客户端的依赖

    <dependencies>
        <!--openfeign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <!--eureka client-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
        <dependency>
            <groupId>com.polaris</groupId>
            <artifactId>cloud-api-common</artifactId>
            <version>${project.version}</version>
        </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>
        <!--一般基础通用配置-->
        <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>
    
    6.3 yml配置文件

    由于80为服务消费方,只是调用服务提供方的服务,所以可以不讲自己注册到服务注册中心。

    server:
      port: 80
    
    eureka:
      client:
        register-with-eureka: false # 客户端就不注册入服务注册中心了
        service-url:
          defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
    
    6.4 主启动类

    注意主启动类上需要添加 @EnableFeignClients 注解,这个注解声明80服务可以使用Feign来实现服务接口的调用。

    @SpringBootApplication
    @EnableFeignClients //激活Feign功能
    public class OrderFeignMain80 {
        public static void main(String[] args) {
            SpringApplication.run(OrderFeignMain80.class);
        }
    }
    
    6.5 业务类

    前面就提到过,在Feign的实现下,只需要创建一个接口并使用注解的方式来配置,这是什么意思呢,就是我们在服务消费方中的service中编写接口,并在该接口上使用 @FeignClient 注解,这样的话就能够实现对服务提供方的服务调用,首先我们看服务提供方8001中有如下的一个服务:

    @RestController
    @Slf4j
    @RequestMapping("/payment")
    public class PaymentController {
        //... 
        @GetMapping("/get/{id}")
        public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id) {
            Payment paymentById = paymentService.getPaymentById(id);
            log.info("===> payment: " + paymentById);
            if(paymentById != null) {
                return new CommonResult(200,
                                        "查询成功,端口号:" + serverPort,paymentById);
            }
            return new CommonResult(400,"查询失败",null);
    	}
        //... 
    }
    

    在服务消费方80中编写如下的接口即可对服务提供方的服务进行调用:

    @Component
    @FeignClient("CLOUD-PAYMENT-SERVICE")
    public interface PaymentFeignService {
        @GetMapping("/payment/get/{id}")
        public CommonResult getPaymentById(@PathVariable("id") Long id);
    }
    

    @FeignClient 注解中的value值为要调用的服务名称,也就是8001/8002服务提供方注册到Eureka注册中心的服务名,这个注解就是告诉该接口调用哪个服务,而默认OpenFeign会使用轮训的负载均衡算法来调用具体的服务实例,在这个接口中我们需要使用服务提供方服务的哪个具体方法,将该方法作为接口方法写入接口中即可。

    然后编写80服务消费方的Controller:

    @RestController
    @Slf4j
    @RequestMapping("/consumer")
    public class OrderFeignController {
    
        @Autowired
        private PaymentFeignService paymentFeignService;
    
        @GetMapping("/payment/get/{id}")
        public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id) {
            return paymentFeignService.getPaymentById(id);
        }
    }
    
    6.6 测试

    我们先启动Eureka的集群注册中心,然后启动服务提供方8001/8002,再启动服务消费方80,访问http://localhost/consumer/payment/get/1,我们可以发现OpenFeign使用轮询的负载均衡算法实现了服务提供方服务接口的调用。

    6.7 总结

    简言之就是客户端的服务接口使用用 @FeignClient 注解根据服务名称去调用服务提供方的具体服务。

    7. OpenFeign超时控制

    7.1 超时设置

    咱们这里故意设置超时演示出错情况

    服务提供方8001故意写暂停程序

    @GetMapping("/feign/timeout")
    public String paymentFeignTimeout() {
        //暂停几秒线程
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return serverPort;
    }
    

    服务消费方80service添加超时方法paymentFeignTimeout,controller添加超时接口paymentFeignTimeout

    @Component
    @FeignClient("CLOUD-PAYMENT-SERVICE")
    public interface PaymentFeignService {
        @GetMapping("/payment/feign/timeout")
        String paymentFeignTimeout();
    }
    
    @RestController
    @Slf4j
    @RequestMapping("/consumer")
    public class OrderFeignController {
        @Resource
        private PaymentFeignService paymentFeignService;
    
        @GetMapping("/payment/feign/timeout")
        public String paymentFeignTimeout() {
            //openfeign底层为ribbon,客户端一般默认等待1秒钟
            return paymentFeignService.paymentFeignTimeout();
        }
    }
    

    测试:http://localhost/consumer/payment/feign/timeout

    错误页面(OpenFeign默认等待1秒钟,超过后报错)

    image-20210203235305718
    7.2 开启OpenFeign客户端超时控制

    服务消费者80的yml配置文件中开启OpenFeign客户端超时控制

    server:
      port: 80
    
    eureka:
      client:
        register-with-eureka: false # 客户端就不注册入服务注册中心了
        service-url:
          defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
    # 设置feign客户端超时时间(OpenFeign默认支持ribbon)
    ribbon:
    # 指的是建立连接所用的时间,适用于网络状态正常的情况下,两端连接所用的时间
    ReadTimeout: 5000
    # 指的是建立连接后从服务器读取到可用资源所用的时间
    ConnectTimeout: 5000
    

    8. OpenFeign日志打印

    理解

    Feign提供了日志打印功能,我们可以通过配置来调整日志级别,从而了解 Fegin 中 Http 请求的细节。说白了就是对Feign接口的调用情况进行监控和输出。

    日志级别

    • NONE 默认的,不显示任何日志
    • BASIC 仅记录请求方法,URL,响应状态码及执行时间
    • HEADERS 除了BASIC中定义的消息外,还有请求和响应的头信息
    • FULL 除了 HEADERS 中定义的信息外,还有请求和响应的正文及元数据

    配置日志Bean

    @Configuration
    public class FeignConfig {
    
        /**
         * feignClient配置日志级别
         * @return
         */
        @Bean
        public Logger.Level feignLoggerLevel() {
            // 请求和响应的头信息,请求和响应的正文及元数据
            return Logger.Level.FULL;
        }
    }
    

    yml配置文件中开启日志的Feign客户端

    server:
      port: 80
    eureka:
      client:
        register-with-eureka: false
        fetch-registry: true
        service-url:
          defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
    ribbon:
      ReadTimeout: 5000
      ConnectTimeout: 5000
    logging:
      level:
        # feign日志以什么级别监控哪个接口
        com.polaris.springcloud.service.PaymentFeignService: debug
    

    后台日志查看

    image-20210204000730413
  • 相关阅读:
    利用AspNetPager控件实现数据分页(存储过程)
    System.Reflection
    规范管理提高效率——国内主要api接口文档工具盘点
    文件管理命令
    操作系统磁盘分区
    实体类配置(Entity)
    SpEL语法
    杂乱无章
    从struts2源码学到的技巧
    Spring基于注解的缓存配置
  • 原文地址:https://www.cnblogs.com/mpolaris/p/14370486.html
Copyright © 2011-2022 走看看