Spring Cloud系列之 RibbonOpenFeign

Posted AC_Jobim

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring Cloud系列之 RibbonOpenFeign相关的知识,希望对你有一定的参考价值。

Spring Cloud系列(二)之 Ribbon、OpenFeign

使用SpringCloud版本:2.1.1.RELEASE

一、Ribbon负载均衡服务调用

  • Ribbon是Netflix发布的负载均衡器,有助于控制HTTP客户端行为。为Ribbon配置服务提供者地址列表后,Ribbon就可基于负载均衡算法,自动帮助服务消费者请求。

  • Ribbon默认提供的负载均衡算法:轮询,随机其他…。当然,我们可用自己定义负载均衡算法

Ribbon 与 Nginx 区别?

  • 服务器端负载均衡 Nginx

    nginx 是客户端所有请求统一交给 nginx,由 nginx 进行实现负载均衡请求转发,属于服务器端负载均衡。即请求由 nginx 服务器端进行转发。

  • 客户端负载均衡 Ribbon

    Ribbon 是从 eureka 注册中心服务器端上获取服务注册信息列表,缓存到本地,然后在本地实现轮询负载均衡策略。即在客户端实现负载均衡。

1.1 Ribbon负载均衡实现过程

RestTemplate的使用:

  • GET请求方法

    <T> T getForObject(String url, Class<T> responseType, Object... uriVariables);
    
    <T> T getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables);
    
    <T> T getForObject(URI url, Class<T> responseType);
    
    <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables);
    
    <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Map<String, ?> uriVariables);
    
    <T> ResponseEntity<T> getForEntity(URI var1, Class<T> responseType);
    
    • getForObject方法:

      返回对象为响应体中数据转化成的对象。

      @GetMapping("/consumer/payment/get/id")
      public CommonResult<Payment> getPayment(@PathVariable("id") Long id) 
          return restTemplate.getForObject(PAYMENT_URL+"/payment/get/"+id,CommonResult.class);
      
      
    • getForEntity方法

      返回对象为ResponseEntity对象,包含了响应中的一些重要信息,比如响应头、响应状态码、响应体等,举例如下:

      @GetMapping("/consumer/payment/getFroEntity/id")
      public CommonResult<Payment> getPayment2(@PathVariable("id") Long id) 
          ResponseEntity<CommonResult> entity = restTemplate.getForEntity(PAYMENT_URL + "/payment/get/" + id, CommonResult.class);
          if(entity.getStatusCode().is2xxSuccessful()) 
              return entity.getBody();
           else 
              return new CommonResult<>(444,"操作失败");
          
      
      
  • POST请求方法

    <T> T postForObject(String url, @Nullable Object request, Class<T> responseType, Object... uriVariables);
    
    <T> T postForObject(String url, @Nullable Object request, Class<T> responseType, Map<String, ?> uriVariables);
    
    <T> T postForObject(URI url, @Nullable Object request, Class<T> responseType);
    
    <T> ResponseEntity<T> postForEntity(String url, @Nullable Object request, Class<T> responseType, Object... uriVariables);
    
    <T> ResponseEntity<T> postForEntity(String url, @Nullable Object request, Class<T> responseType, Map<String, ?> uriVariables);
    
    <T> ResponseEntity<T> postForEntity(URI url, @Nullable Object request, Class<T> responseType);
    

    代码示例:

    @PostMapping("/insert")
    public Result insert(@RequestBody User user) 
        return restTemplate.postForObject(userServiceUrl + "/user/insert", user, Result.class);
    
    

实现过程:

  1. 导入依赖

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

    注意eureka的客户端中包含ribbon依赖,所以如果导入了eureka依赖就不用再导入ribbon依赖了。

  2. 在Eureka中注册服务提供者集群(8001、8002)

  3. 编写服务提供者的Controller方法

    @GetMapping(value = "/payment/get/id")
    public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id)
        Payment payment = paymentService.getPaymentById(id);
        log.info("*****查询结果:",payment);
        int w = 5/2;
        if (payment != null) 
            return new CommonResult(200,"查询成功"+", 服务端口:"+serverPort,payment);
        else
            return new CommonResult(444,"没有对应记录,查询ID: "+id,null);
        
    
    
  4. 在服务消费者配置类中给RestTemplate这个Bean添加一个@LoadBalanced注解,用来实现负载均衡功

    @Configuration
    public class ApplicationContextConfig 
    
        @Bean
        @LoadBalanced //使用@LoadBalanced注解赋予RestTemplate负载均衡的能力
        public RestTemplate getRestTemplate() 
            return new RestTemplate();
        
    
    
    
  5. 编写服务消费者中的Controller中的方法。不再手动获取ip和端口,而是直接通过服务名称调用

    @Resource
    private RestTemplate restTemplate;
    public static final String PAYMENT_URL = "http://CLOUD-PAYMENT-SERVICE";
    @GetMapping("/consumer/payment/get/id")
    public CommonResult<Payment> getPayment(@PathVariable("id") Long id) 
        return restTemplate.getForObject(PAYMENT_URL+"/payment/get/"+id,CommonResult.class);
    
    
  6. 访问测试,spring会自动帮助我们从eureka-server端,根据服务名称,获取实例列表,而后完成负载均衡

1.2 Ribbon的负载均衡策略

所谓的负载均衡策略,就是当A服务调用B服务时,此时B服务有多个实例,这时A服务以何种方式来选择调用的B实例,ribbon可以选择以下几种负载均衡策略。

内置负载均衡规则类规则描述
RoundRobinRule简单轮询服务列表来选择服务器。它是Ribbon默认的负载均衡规则。
AvailabilityFilteringRule先过滤掉故障实例,再选择并发较小的实例
WeightedResponseTimeRule对RoundRobinRule的扩展,响应速度越快的实例选择权重越大,越容易被选择
ZoneAvoidanceRule采用双重过滤,同时过滤不是同一区域的实例和故障实例,选择并发较小的实例。
BestAvailableRule会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务
RandomRule随机选择一个可用的服务器
RetryRule先按照RoundRobinRule的策略获取服务,如果获取服务失败则在指定时间内会进行重试,获取可用的服务

继承关系:

修改Ribbon负载均衡策略(修改成随机选择):

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

  1. 在@ComponentScan所扫描不到的包中新建配置类MySelfRule

    @Configuration
    public class MySelfRule 
        @Bean
        public IRule myRule()
            return new RandomRule();//定义为随机
        
    
    
  2. 在主启动类添加@RibbonClient注解

    @SpringBootApplication
    @EnableEurekaClient
    // 用来指定CLOUD-PAYMENT-SERVICE服务采用MySelfRule配置类中配置的负载均衡策略
    @RibbonClient(name = "CLOUD-PAYMENT-SERVICE",configuration= MySelfRule.class)
    public class OrderMain80 
    
        public static void main(String[] args) 
            SpringApplication.run(OrderMain80.class, args);
        
    
    
    
  3. 测试可以发现restTemplate将使用随机调用(注意使用restTemplate调用时服务名称改成大写,否则不起作用)

1.3 负载均衡算法原理分析

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

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, 按照轮询算法原理:
 
当总请求数为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时: 4 % 2 =0 对应下标位置为0 ,则获得服务地址为127.0.0.1:8002

RoundRobinRule源码:

public class RoundRobinRule extends AbstractLoadBalancerRule 
    private AtomicInteger nextServerCyclicCounter;
    private static final boolean AVAILABLE_ONLY_SERVERS = true;
    private static final boolean ALL_SERVERS = false;
    private static Logger log = LoggerFactory.getLogger(RoundRobinRule.class);

    public RoundRobinRule() 
        this.nextServerCyclicCounter = new AtomicInteger(0);
    

    public RoundRobinRule(ILoadBalancer lb) 
        this();
        this.setLoadBalancer(lb);
    

    public Server choose(ILoadBalancer lb, Object key) 
        if (lb == null) 
            log.warn("no load balancer");
            return null;
         else 
            Server server = null;
            int count = 0;

            while(true) 
                if (server == null && count++ < 10) 
                    List<Server> reachableServers = lb.getReachableServers();//得到可以连接的server
                    List<Server> allServers = lb.getAllServers();//得到所有的server  并且放在list中了。
                    int upCount = reachableServers.size();//在注册中心连接的。得到数量
                    int serverCount = allServers.size();// 所有server的数量
                    if (upCount != 0 && serverCount != 0) 
                        int nextServerIndex = this.incrementAndGetModulo(serverCount);//关键在于这个方法的的next值。得到list集合中要访问服务器的下标
                        server = (Server)allServers.get(nextServerIndex);
                        if (server == null) 
                            Thread.yield();
                         else 
                            if (server.isAlive() && server.isReadyToServe()) 
                                return server;
                            

                            server = null;
                        
                        continue;
                    

                    log.warn("No up servers available from load balancer: " + lb);
                    return null;
                

                if (count >= 10) 
                    log.warn("No available alive servers after 10 tries from load balancer: " + lb);
                

                return server;
            
        
    
	
    // 通过CAS保证线程安全
    private int incrementAndGetModulo(int modulo) 
        int current;
        int next;
        do 
            current = this.nextServerCyclicCounter.get();//这个是我们的atmic原子类。得到默认的值0
            next = (current + 1) % modulo;//这个是服务的个数。这个next的值如果这一样取,其实就是和serverlist的下标对应,并且,每次加1.
         while(!this.nextServerCyclicCounter.compareAndSet(current, next));//因为是多线程,我们要保证安全,所以一用了循环的CAS

        return next;
    

    public Server choose(Object key) 
        return this.choose(this.getLoadBalancer(), key);
    

    public void initWithNiwsConfig(IClientConfig clientConfig) 
    

二、OpenFeign服务接口调用

  • 在认识 Feign 之前,微服务之间调用。我们使用的是 Ribbon + RestTemplate 方式

  • Feign是声明式的服务调用工具,我们只需创建一个接口并用注解的方式来配置它,就可以实现对某个服务接口的调用,简化了直接使用RestTemplate来调用服务接口的开发量。Feign具备可插拔的注解支持,同时支持Feign注解、JAX-RS注解及SpringMvc注解。当使用Feign时,Spring Cloud集成了Ribbon和Eureka以提供负载均衡的服务调用及基于Hystrix的服务容错保护功能。

2.1 OpenFeign使用步骤

项目架构:

  1. 新建一个cloud-consumer-feign-order80模块

  2. 导入依赖

    <!--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.zb.springcloud</groupId>
        <artifactId>cloud-api-commons</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
    <!--web-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
  3. 编写application.yml文件

    server:
      port: 80
    
    eureka:
      client:
        # 表示是否将自己注册进EurekaServer默认为true。
        register-with-eureka: false
        service-url:
          defaultZone: http://eureka7001.com:7001/eureka/
    
  4. 启动类上添加@EnableFeignClients注解来启用Feign的客户端功能

    @SpringBootApplication
    @EnableFeignClients // 激活并开启Feign
    public class OrderFeignMain80 
    
        public static void main(String[] args) 
            SpringApplication.run(OrderFeignMain80.class, args);
        
    
    
    
  5. 添加Service接口完成对服务提供者接口绑定

    我们通过@FeignClient注解实现了一个Feign客户端,其中的value指定服务提供方服务名称

    @Component
    @FeignClient(value = "CLOUD-PAYMENT-SERVICE") //添加@FeignClient注解,指定服务提供方服务名称
    public interface PaymentFeignService 
    
        @GetMapping(value = "/payment/get/id")
        CommonResult<Payment> getPaymentById(@PathVariable("id") Long id);
    
        @GetMapping(value = "/payment/feign/timeout")
        String paymentFeignTimeOut();
    
    
    
  6. 添加Controller调用Service实现服务调用

    @RestController
    @Slf4j
    public class OrderFeignController 
    
        @Autowired
        private PaymentFeignService paymentFeignService;
    
        @GetMapping(value = "/consumer/payment/get/id")
        public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id) 
            return paymentFeignService.getPaymentById(id)以上是关于Spring Cloud系列之 RibbonOpenFeign的主要内容,如果未能解决你的问题,请参考以下文章

    Spring Cloud系列之 RibbonOpenFeign

    Spring Cloud 系列之 Config 配置中心

    Spring Cloud 系列之 Config 配置中心

    Spring Cloud系列之 EurekaZookeeperConsul

    Spring Cloud系列之 EurekaZookeeperConsul

    Spring Cloud系列之 HystrixZuulGateway