• 4--SpringCloud-Ribbon/OpenFeign周阳老师


    2021:4--SpringCloud-Ribbon/OpenFeign

    https://www.cnblogs.com/coderD/p/14350076.html SpringCloud

    https://www.cnblogs.com/coderD/p/14350073.html SpringCloud 和 Eureka

    https://www.cnblogs.com/coderD/p/14350082.html SpringCloud 和 Zookeeper

    https://www.cnblogs.com/coderD/p/14350086.html SpringCloud-Ribbon/OpenFeign

    https://www.cnblogs.com/coderD/p/14350091.html SpringCloud:Hystrix 断路器

    https://www.cnblogs.com/coderD/p/14350097.html SpringCloud:服务网关 gateway

    https://www.cnblogs.com/coderD/p/14350099.html SpringCloud:Config/Bus

    https://www.cnblogs.com/coderD/p/14350103.html SpringCloud:Stream/Sleuth

    https://www.cnblogs.com/coderD/p/14350110.html SpringCloud Alibaba:Nacos

    https://www.cnblogs.com/coderD/p/14350114.html SpringCloud Alibaba:Sentinel

    https://www.cnblogs.com/coderD/p/14350119.html SpringCloud Alibaba:Seata

    代码:https://gitee.com/xue--dong/spring-cloud

    阳哥脑图:https://gitee.com/xue--dong/spring-cloud

    主要内容

    1.  Ribbon
    2.  OpenFeign
    复制代码
    

    1. Ribbon

    1.1 Ribbon 概述

    Spring Cloud Ribbon是基于NetFlix Ribbon实现的一套客户端 负载均衡工具。
    
    简单的说,Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法和服务调用。
    
    Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。
    
    简单的说,就是在配置文件中列出Load Balancer后面所有的机器,Ribbon会自动帮你去基于某种规则
    (如简单轮询,随机连接等)去连接这些机器。
    
    我们很容易使用Ribbon实现自定义的负载均衡算法。
    
    Ribbon目前也进入了维护模式
    复制代码
    

    链接

    未来的替换方案:Spring Cloud LoadBanlance
    复制代码
    

    1.2 主要作用:

        1.  负载均衡LB:
        
            集中式LB
            即在服务的消费方和提供方之间独立的LB设置,可以是硬件,如F5,也可以是软件,如Nginx,
            由该设施负责把访问请求通过某种策略转发至服务的提供方。
            
            进程内LB
            将LB逻辑集成到消费方,消费方从服务注册中心获取有哪些地址可用,然后自己再从这些地址
            中选择一个合适的服务器。
            Ribbon就属于进程内LB,它只是一个类库,集成与消费方进程,消费方通过它来获取到服务提
            供方的地址。
            
        负载均衡LB是什么:
        
            简单地说就是将用户的请求平摊的分配到多个服务上,从而达到系统的HA(高可用性)
            
            常见的负载均衡软件有:Nginx,LV5;硬件有F5等。
            
        Ribbon本地负载均衡客户端 和 Nginx服务端负载均衡区别:
        
            Nginx是服务器负载均衡,客户端所有请求都会交给nginx,然后由nginx实现转发请求,
            即负载均衡是有服务端实现的。                        类似大门。
            即集中式的LB。
            
            Ribbon本地负载均衡,在调用微服务接口时候,会在注册中心上获取注册信息服务列表之后
            缓存到JVM本地,从而在本地实现RPC远程服务调用技术。      类似科室
            进程内的LB。
            
        
        Ribbon:
            一句话,实现负载均衡的一套客户端工具结合RestTemplate实现调用。
            
            Ribbon其实就是一个软负载均衡的客户端组件
            
            他可以和其他所需请求的客户端结合使用,和eureka结合只是其中的一个实例。
        
        2.  Ribbon架构
    复制代码
    

    img

        3.  Ribbon在工作时分成两步
        
            1.  先选择EurekaServer,它优先选择在同一个区域内负载较少的server
            
            2.  再根据用户指定的策略,再从server取到的服务注册列表中选择一个地址。
            
                其中Ribbon提供了多种策略:比如轮询,随机和根据响应时间加权。
    复制代码
    

    1.3 为什么没有引入 Ribbon 也可以使用负载均衡

        1.  之前我们写样例的时候没有引入spring-cloud-start-ribbon也可以使用ribbon
        
            我们通过一个注解:@LoadBalanced赋予了RestTemplate负载均衡的能力  
    复制代码
            @Configuration
            public class ApplicationContextConfig {
            
                @Bean
                @LoadBalanced //开启默认的负载均衡机制:赋予了RestTemplate负载均衡的能力
                public RestTemplate getRestTemplate(){
                    return new RestTemplate();
                }
            }
    复制代码
        2.  原因是spring-boot-netfix-eureka-client自带了spring-starter-ribbon引用
    复制代码
    

    img

        3.  我们无须再引入Ribbon坐标
        
            因为我们已经引入了netflix-eureka-client,里面自带的有Ribbon包
    复制代码
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            </dependency>
    复制代码
            所以我们项目中无需在引入了。
    复制代码
    

    1.4 二说 RestTemplate 的使用

        为什么说它:
            
            所谓的负载均衡,就是Ribbon结合RestTemplate实现调用微服务之间的调用。
        
        1.  getForObject/getForEntity
            
            返回对象为ResponseEntity对象,包含了响应中的一些重要信息,比如响应头,响应状态码,
            响应体等。
    复制代码
    

    img

            /**
             * 测试getForEntity
             * @return
             */
            @GetMapping("/consumer/payment/getForEntity/{id}")
            public CommonResult<Payment> getPayment2(@PathVariable("id") Long id){
                ResponseEntity<CommonResult> entity = template.getForEntity(PAYMENT_URL + "/payment/get/" + id, CommonResult.class);
        
                if(entity.getStatusCode().is2xxSuccessful()){
                    return entity.getBody();
                }else{
                    return new CommonResult<>(444, "操作失败");
                }
            }
    复制代码
    

    img

        这些方法对应着各种HTTP状态码。
    复制代码
    

    img

        可以跑通。
        
        可以用entity获取更详细的信息,比如头信息,响应体,状态码等。
    复制代码
        log.info(entity.getHeaders() + "	" +entity.getStatusCode());
    复制代码
        2.  postForObject/postForEntity
    复制代码
        @GetMapping("/consumer/payment/postForEntity/create")
        public CommonResult<Payment> create2(Payment payment){
            ResponseEntity<CommonResult> entity = template.postForEntity(PAYMENT_URL + "/payment/create", payment, CommonResult.class);
    
            if(entity.getStatusCode().is2xxSuccessful()){
                log.info(entity.getHeaders() + "	" +entity.getStatusCode());
    
                return entity.getBody();
            }else{
                return new CommonResult<Payment>(444, "操作失败");
            }
        }
    复制代码
        3   GET请求方法
        
        4.  POST请求方法
    复制代码
    

    1.5 Ribbon 的核心组件:IRule 接口

        根据特定算法从服务列表中选取一个要访问的服务。
        
        我们看一下它的源码:
    复制代码
    

    img

        很简单就是对负载均衡算法的get/set
    复制代码
    

    img

        Ribbon自带了这几种的负载均衡算法,都实现了IRule接口
    复制代码
    

    img

    1.6 Ribbon 负载规则替换

    1.  警告
    
        官方文档明确给出了警告:
        
        这个自定义配置类不能放在@ComponentScan所扫描的当前包下以及子包下
        
        否则我们自定义的这个配置类就会被所有的Ribbon客户端所共享,达不到特殊化定制的目的了。
        
        
    2.  什么是@ComponentScan
    复制代码
    

    img

        那么也就是@SpringBootApplication标注的主配置类所在的包,及所有子包都会被@ComponentScan所扫描到。
        
        所以不能放在这个目录结构下
    复制代码
    

    img

    3.  新建package:com.atguigu.myrule
        
        写 MySelfRule 配置类
    复制代码
            @Configuration
            public class MySelfRule {
                
                @Bean
                public IRule myRule(){
                    
                    return new RandomRule(); //定义为随机
                    
                    //定义为随机规则:new RoundRobinRule()
                    //定义为重置策略:new RetryRule();
                }
            }
    
    复制代码
    4.  指明访问的服务CLOUD-PAYMENT-SERVICE,和指定了负载均衡策略
    
        在主配置类加上如下注解:
        
        @RibbonClient(name = "CLOUD-PAYMENT-SERVICE", configuration = MySelfRule.class)
    复制代码
        @SpringBootApplication
        @EnableEurekaClient
        //指明访问的服务CLOUD-PAYMENT-SERVICE,和指定了负载均衡策略
        @RibbonClient(name = "CLOUD-PAYMENT-SERVICE", configuration = MySelfRule.class)
        public class OrderMain80 {
        
            public static void main(String[] args) {
                SpringApplication.run(OrderMain80.class, args);
            }
        }
    
    复制代码
    5.  启动80测试        
    复制代码
    

    img

        测试成功。
    复制代码
    

    2. 负载均衡算法

    2.1 原理

        1.  负载均衡算法:
        
            rest接口第几次请求数%服务器集群总数量 = 实际调用服务器位置下标,
            
            每次服务重启动后rest接口计数从1开始
            
        2.  服务器集群总数量:
            
            被请求的微服务的集群实例数量:2台
            
            List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
            
            如:List[0] instances = 127.0.0.1:8002
                List[1] instances = 127.0.0.1:8001
            
            8001+8002组合成为集群,他们共计2台机器,集群总数为2。
    复制代码
    

    img

            两个集群:
            CLOUD-ORDER-SERVER 集群1台
            CLOUD-PAYMENT-SERVER 集群两台
    
        
        3.  第一次请求:1%2=1 调用第1个微服务   List[1]
            
            第二次请求:2%2=0 调用第0个微服务   List[0]
            
            第三次请求:352=1 调用第1个微服务   List[1]
            
            开始轮询·····
                        
            每次服务重启动后rest接口计数从1开始
    复制代码
    

    2.1 手写一个负载的算法

        原理+JUC(CAS+自旋锁的复习)
    复制代码
    

    2.1.1 8001/8002 微服务改造:controller

            @GetMapping(value = "/payment/lb")
            public String getPaymentLB(){
                return serverPort;
            }
    复制代码
    

    2.1.2 80 微服务改造

        1.  ApplicationContextBean去掉注解@LoadBalance
        
            使用我们自己写的负载均衡算法
    复制代码
            @Configuration
            public class ApplicationContextConfig {
            
                @Bean
                //@LoadBalanced 开启默认的负载均衡机制:赋予了RestTemplate负载均衡的能力
                public RestTemplate getRestTemplate(){
                    return new RestTemplate();
                }
            }
    复制代码
        2.  LoadBalancer接口
    复制代码
            public interface LoadBalancer {
            
                //instances()方法:从List<ServiceInstance>得到一个ServiceInstance微服务实例对象
                ServiceInstance instances(List<ServiceInstance> serviceInstances);
            }
    
    复制代码
        3.  MyLoadBalancer
    复制代码
        @Component
        public class MyLoadBalancer implements LoadBalancer {
        
            private AtomicInteger atomicInteger = new AtomicInteger(0);
        
            public final int getAndIncrement(){
                int current;
                int next;
        
                //从下面的分析中得知:该循环主要是得到next值,单机是每循环一次返回一次next
                //高并发时:就不是这种情况了。因为数字会被抢占。
                do{
                    current = this.atomicInteger.get();
        
                    //Integer.MAX_VALUE = 2147483647
                    next = current >= 2147483647 ? 0 : current+1;
        
                    /**
                     * 当atomicInteger=0时,current=0, next=1
                     * atomicInteger和current进行比较,相等时返回true将atomicInteger更新为next,1,取反跳出循环,再返回next
                     * 当atomicInteger=2147483647时,current=2147483647,next=0
                     * atomicInteger和current进行比较,相等时返回true将atomicInteger更新为next, 0,取反跳出循环,再返回next
                     *
                     * 如果是高并发时
                     */
                }while(!this.atomicInteger.compareAndSet(current,next));
        
                System.out.println("******第几次访问次数next:" + next);
        
                return next;
            }
        
            @Override
            public ServiceInstance instances(List<ServiceInstance> serviceInstances) {
        
                //被调用的服务的下标
                int index = getAndIncrement() % serviceInstances.size();
                //被调用的服务
                return serviceInstances.get(index);
            }
        }
    
    复制代码
        4.  OrderController
    复制代码
        @Resource
        private LoadBalancer loadBalancer;
        @Resource
        private DiscoveryClient discoveryClient;
        
        /**
         * 测试我们自己的轮询算法
         * @return
         */
        @GetMapping(value = "/consumer/payment/lb")
        public String getPaymentLoadBalancer(){
    
            List<String> services = discoveryClient.getServices();
            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 template.getForObject(uri+"/payment/lb", String.class);
        }
    复制代码
        5.  测试:成功
    复制代码
    

    img

    3. OpenFegin

    3.1 OpenFegin 概述

    官网

    源码

    1.  概述
    
    Feign一个声明式WebService客户端,使用Feign能让WebService客户端更加简单。
    
    更多的用在消费者微服务模块。
    
    它的使用方法是定义一个服务接口然后在上面添加注解。Feign也支持可拔插式的编码器和解码器。
    SpringCloud对Feign进行了封装,使其支持SpringMVC标准注解和HttpMessageConverters。
    
    Feign可以与Eureka和Ribbon组合使用以支持服务在均衡。
    
    Feign参考Ribbon的基础,又做了一套接口加注解方式调用的整合器。
    
    小结:
        Feign一个声明式Rest/WebService客户端,使用Feign能让WebService客户端更加简单。
        只需创建一个接口并在接口上添加注解即可。
        
        
    2.  能做什么
    
        我们每个消费者去调用声场这模块时,都要通过RestTemplate调用一次。
        
        尽量的面向接口编程,分层解耦以后,要明确:
            我们要调用的服务,肯定会对外暴露服务接口,消费者和生产者就用接口沟通。
            生产者服务能做什么都定义在了接口中,消费者定义相对应的接口,然后在这个
            接口上规定我是个Feign接口。
            
            生产者接口有什么方法,我这个Feign接口就调哪些方法。实现Feign接口到业务
            提供方的接口一一配对。
            
            更好的实现面向微服务接口编程。
        
        Feign集成了Ribbon,所以也可以实现客户端的负载均衡。
    复制代码
    

    img

        小结:
            Feign就是一个服务接口绑定器,接口加一个相关的注解,就能完成调用。
            
        
        只需创建一个接口并在接口上添加注解即可。
    复制代码
    

    3.2 OpenFegin 使用步骤

        以前我们在消费者服务调用生产者服务时,采用Ribbon+restTemplate进行客户端服务调用和
        负载均衡。
        
        现在采用OpenFeign绑定服务接口。
    复制代码
    

    3.2.1 建 module

        1.  新建cloud-consumer-feign-order80
    复制代码
    

    3.2.2 pom

    <dependencies>
    
            <!--openfeign-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-openfeign</artifactId>
            </dependency>
    
            <dependency>
                <groupId>com.atguigu.springcloud</groupId>
                <artifactId>cloud-api-commons</artifactId>
                <version>${project.version}</version>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <!--监控-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-actuator</artifactId>
            </dependency>
    
            <!--eureka client-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-eureka-client</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>
    复制代码
    

    img

        整合的有Ribbon,所以也具有负载均衡的功能。
    复制代码
    

    3.2.3 yml

        不将其注入Eureka作为微服务,它就是个客户端
    复制代码
        server:
          port: 80
        
        eureka:
          client:
            # 表示不将其注入Eureka作为微服务,不作为Eureak客户端了,而是作为Feign客户端
            register-with-eureka: false
            service-url:
              # 集群版
              defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka,http://eureka7003.com:7003/eureka
    复制代码
    

    3.2.4 主启动类

            @SpringBootApplication
            @EnableFeignClients     //不作为Eureak客户端了,而是作为Feign客户端
            public class OrderFeignMain80 {
                public static void main(String[] args) {
                    SpringApplication.run(OrderFeignMain80.class, args);
                }
            }
    复制代码
    

    3.2.5 业务类

    1.  业务逻辑接口+@FeignClient配置调用provider服务
        
        新建PaymentFeignService接口并新增注解@FeignClient
    复制代码
            @Component
            @FeignClient(value = "CLOUD-PAYMENT-SERVICE") //作为一个Feign功能绑定的的接口
            public interface PaymentFeignService {
            
                @GetMapping(value = "/payment/get/{id}")
                public CommonResult getPaymentById(@PathVariable("id") Long id);
            
            }
    复制代码
        就会找到CLOUD-PAYMENT-SERVICE微服务下面的/payment/get/{id}这个地址。
        
        这里就说明:
            PaymentFeignService接口加@FeignClient注解,完成Feign的包装调用。
            指明找哪个微服务上面的地址。
            
        主启动类开启Feign,接口上使用FeignClient,结合使用。
    复制代码
    

    img

    2.  控制层Controller
    
        通过自己的80的接口层,取调用服务提供者中的接口
    复制代码
            @RestController
            @Slf4j
            public class OrderFeignController {
            
                @Resource
                private PaymentFeignService paymentFeignService;
            
                @GetMapping("/consumer/payment/getFeign/{id}")
                public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id){
            
                    //通过自己的80的接口层,取调用服务提供者中的接口
                    return paymentFeignService.getPaymentById(id);
                }
            }
    复制代码
    

    3.2.6 测试

        先启动2个Eureka集群 7001/7002
        
        再启动2个微服务8001/8002
        
        启动使用OpenFeign的80
        
        测试成功:
            我们仅仅将消费者微服务作为一个客户端,不注册到Euraka中。
            
            通过消费者本身的接口地址,去调用生产者微服务对应的接口,并且可以实现负载均衡。
    复制代码
    

    img

        比较符合我们的编程习惯,在80中还是controller调用service,service再去调用8001的controller。
    复制代码
    

    3.3 OpenFeign 的超时控制

        openfeign 客户端一般默认等待1s 就要得到调用的结果。
    
        客户端服务接口,根据在service类中指定的@FeignClient(value = "CLOUD-PAYMENT-SERVICE")微服务
        去调用服务提供侧所暴露对外提供的服务接口方法。
    复制代码
    

    3.3.1 超时设置,故意设置超时演示出错情况

        1.  服务提供方8001故意写暂停程序
    复制代码
        //服务提供方8001故意写暂停程序
            @GetMapping(value = "/payment/feign/timeout")
            public String paymentFeignTimeout(){
        
                //暂停几秒钟线程
                try {
                    TimeUnit.SECONDS.sleep(3);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
        
                return serverPort;
            }
    复制代码
        2.  服务消费方80添加超时方法PaymentFeignService
            
            客户端服务接口中添加该暂停方法
    复制代码
            @Component
            @FeignClient(value = "CLOUD-PAYMENT-SERVICE") //作为一个Feign功能绑定的的接口
            public interface PaymentFeignService {
            
                @GetMapping(value = "/payment/get/{id}")
                public CommonResult getPaymentById(@PathVariable("id") Long id);
            
                @GetMapping(value = "/payment/feign/timeout")
                public String paymentFeignTimeout();
            
            }
    复制代码
        3.  服务消费方80添加超时方法OrderFeignController
    复制代码
            @GetMapping(value = "/consumer/payment/feign/timeout")
            public String paymentFeignTimeout(){
                //openfeign-ribbon 客户端一般默认等待1s:就要得到调用的结果。
        
                return paymentFeignService.paymentFeignTimeout();
            }
    复制代码
        4.  超时演示
        
            8001自测成功:3s后执行打印
    复制代码
    

    img

            80测试:报超时错误
    复制代码
    

    img

            得到效果。
    复制代码
    

    3.3.2 设置 Feign 客户端的超时等待时间

        默认Feign客户端只等待一秒钟,但是如果客户端处理超过1s中,就会导致Feign客户端不想等待了
        直接报错。
    复制代码
    

    注意:Feign 客户端的负载均衡和超时控制都由 Ribbon 控制

        为了避免这种情况,我们需要设置Feign客户端的超时等待时间。
        
        
        yml文件中开启配置。
        
        注意:我配置ReadTimeout/ConnectTimeout时yml没有提示。
    复制代码
        # 设置feign客户端超时时间
        ribbon:
          # 建立连接所用的时间,适用于网络状况正常的情况下,两端连接所有的时间
          ReadTimeout: 5000
          # 建立连接后从服务器读取到可用资源所用的时间
          ConnectTimeout: 5000
    复制代码
        server:
          port: 80
        
        eureka:
          client:
            # 表示不讲其注入Eureka作为微服务,它就是个客户端
            register-with-eureka: false
            service-url:
              # 集群版
              defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka,http://eureka7003.com:7003/eureka
        
        # 设置feign客户端超时时间
        ribbon:
          # 建立连接所用的时间,适用于网络状况正常的情况下,两端连接所有的时间
          ReadTimeout: 5000
          # 建立连接后从服务器读取到可用资源所用的时间
          ConnectTimeout: 5000
    复制代码
    

    3.3.3 测试

        测试成功:
    复制代码
    

    img

    3.4 OpenFeign 日志打印功能

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

    img

        2.  配置
        
            1.  配置日志Bean
    复制代码
            @Configuration
            public class FeignConfig {
            
                @Bean
                Logger.Level feignLoggerLevel(){
                    return Logger.Level.FULL;
                }
            }
    复制代码
            2.  YML文件需要开启日志的Feign客户端
                
                以debug的形式打印full级别的所有日志。
    复制代码
                # 开启日志的
                logging:
                  level: 
                    # feign日志以什么级别监控那个接口
                    com.atguigu.springcloud.service.PaymentFeignService: debug
    复制代码
        3.  测试
        
            测试成功:
    复制代码
    

    img

  • 相关阅读:

    队列
    Collection类
    Hashtable类、IdentityHashMap和WeakHashMap类
    LinkedHashMap类
    广播的种类:有序广播和无序广播
    自定义的BroadCastReceiver
    String的两个API,判断指定字符串是否包含另一字符串,在字符串中删除指定字符串。
    BroadcastReceiver的最简单用法
    Notification通知栏
  • 原文地址:https://www.cnblogs.com/coderD/p/14350086.html
Copyright © 2020-2023  润新知