微服务---7.负载均衡Ribbon

实际环境中,我们往往会开启很多个 user-service的集群。此时我们获取的服务列表中就会有多个,到底该访问
个呢?
一般这种情况下我们就需要编写负载均衡算法,在多个实例列表中进行选择。
不过 Eureka 中已经帮我们集成了负载均衡组件: Ribbon ,简单修改代码即可使用。
什么是 Ribbon
 

 

接下来,我们就来使用 Ribbon 实现负载均衡。
7.1. 启动两个服务实例
首先我们启动两个 user-service实例,一个8081,一个8083。

 配置端口

Eureka监控面板:

7.2. 开启负载均衡
consumer-service中添加 maven 依赖
<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
            <version>2.1.2.RELEASE</version>
        </dependency>
因为 Eureka 中已经集成了 Ribbon ,所以我们无需引入新的依赖。直接修改代码:
RestTemplate 的配置方法上添加 @LoadBalanced 注解:
# 在主启动类的中找到 RestTemplate 加上 @LoadBanced
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
注:新版本不用传入 new RestTemplate(new OkHttp3ClientHttpRequestFactory() )可不传
修改调用方式,不再手动获取 ip 和端口,而是直接通过服务名称调用:
package com.lucas.consumer.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import java.util.List;

/**
 * @Author Lucas
 * @Date 2019/12/18 17:53
 * @Version 1.0
 */
@RestController
@RequestMapping("/")
public class ConsumerController3 {
    @Autowired
    RestTemplate restTemplate;

    @Autowired
    LoadBalancerClient loadBalancerClient;

    @RequestMapping("findall2")
    public String findAll() {
        ServiceInstance instance = loadBalancerClient.choose("user-service");
        String host = instance.getHost();
        int port = instance.getPort();
//        return template.getForObject(SERVICEURL, String.class);
        String urlByEureka = "http://" + host + ":" + port + "/findall";
        return restTemplate.getForObject(urlByEureka, String.class);
    }

    @RequestMapping("findall3")
    public String findAll2() {
        return restTemplate.getForObject("http://user-service/findall",String.class);
    }

    @RequestMapping("findall4")
    public String findAll3() {
        ResponseEntity<String> forEntity = restTemplate.getForEntity("http://user-service/findall", String.class);
        HttpStatus statusCode = forEntity.getStatusCode();
        int statusCodeValue = forEntity.getStatusCodeValue();
        HttpHeaders headers = forEntity.getHeaders();
        String body = forEntity.getBody();
        StringBuilder result = new StringBuilder();
        result.append("responseEntity.getBody():").append(body).append("<hr>")
                .append("responseEntity.getStatusCode():").append(statusCode).append("<hr>")
                .append("responseEntity.getStatusCodeValue():").append(statusCodeValue).append("<hr>")
                .append("responseEntity.getHeaders():").append(headers).append("<hr>");
        return result.toString();
    }

}
7.3. 源码跟踪
为什么我们只输入了 service 名称就可以访问了呢?之前还要获取 ip 和端口。
显然有人帮我们根据 service 名称,获取到了服务实例的 ip 和端口。它就是 LoadBalancerInterceptor
我们进行源码跟踪:

 继续跟入execute方法:发现获取了8081端口的服务

 

再跟下一次,发现获取的是8083:

 

7.4. 负载均衡策略
Ribbon 默认的负载均衡策略是简单的轮询,我们可以测试一下:
编写测试类,在刚才的源码中我们看到拦截中是使用 RibbonLoadBalanceClient 来进行负载均衡的,其中一个 choose方法,是这样介绍的

 

 

现在这个就是负载均衡获取实例的方法。
添加 maven 依赖
<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
我们对注入这个类的对象,然后对其测试:
import com.lucas.consumer.ConsumerApplication;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient;
import org.springframework.test.context.junit4.SpringRunner;

/**
 * @Author Lucas
 * @Date 2019/12/19 9:46
 * @Version 1.0
 */
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ConsumerApplication.class)
public class LoadBalanceTest {
    @Autowired
    RibbonLoadBalancerClient client;

    @Test
    public void test() {
        for (int i = 0; i < 100; i++) {
            ServiceInstance instance = this.client.choose("user-service");
            System.out.println(instance.getHost() + ":" + instance.getPort());
        }
    }
}

 

符合了我们的预期推测,确实是轮询方式。
我们是否可以修改负载均衡的策略呢?
继续跟踪源码,发现这么一段代码:

我们看看这个rule是谁:

 

这里的 rule 默认值是一个 RoundRobinRule ,看类的介绍:

 

这不就是轮询的意思嘛。
我们注意到,这个类其实是实现了接口 IRule 的,查看一下:

 

定义负载均衡的规则接口。
它有以下实现

 SpringBoot也帮我们提供了修改负载均衡规则的配置入口:

user-service:
 ribbon:
  NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
7.5. 重试机制
Eureka 的服务治理强调了 CAP 原则中的 AP ,即可用性和可靠性。它与 Zookeeper 这一类强调CP (一致性,可靠性)
的服务治理框架最大的区别在于: Eureka 为了实现更高的服务可用性,牺牲了一定的一致性,极端情况下它宁愿接
收故障实例也不愿丢掉健康实例,正如我们上面所说的自我保护机制。
但是,此时如果我们调用了这些不正常的服务,调用就会失败,从而导致其它服务不能正常工作!这显然不是我们愿
意看到的。
我们现在关闭一个userservice实例:

 因为服务剔除的延迟,consumer并不会立即得到最新的服务列表,此时再次访问你会得到错误提示:

 

但是此时, 8081 服务其实是正常的。
因此 Spring Cloud 整合了 Spring Retry 来增强 RestTemplate 的重试能力,当一次服务调用失败后,不会立即抛出一 次,而是再次重试另一个服务。
只需要简单配置即可实现 Ribbon 的重试:
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/frame?characterEncoding=utf-8&serverTimezone=UTC
    username: root
    password: lucas
    driver-class-name: com.mysql.cj.jdbc.Driver
  thymeleaf:
    cache: false
  application:
    name: user-consumer
  cloud:
    loadbalancer:
      retry:
        enabled: true # 开启Spring Cloud的重试功能
  user-service:
    ribbon:
      ConnectTimeout: 250 # Ribbon的连接超时时间
      ReadTimeout: 1000 # Ribbon的数据读取超时时间
      OkToRetryOnAllOperations: true # 是否对所有操作都进行重试
      MaxAutoRetriesNextServer: 1 # 切换实例的重试次数
      MaxAutoRetries: 1 # 对当前实例的重试次数
根据如上配置,当访问到某个服务超时后,它会再次尝试访问下一个服务实例,如果不行就再换一个实例,如果不
行,则返回失败。切换次数取决于 MaxAutoRetriesNextServer 参数的值
引入 spring-retry依赖
<dependency>
            <groupId>org.springframework.retry</groupId>
            <artifactId>spring-retry</artifactId>
        </dependency>
我们重启 consumer-demo,测试,发现即使 user-service2宕机,也能通过另一台服务实例获取到结果!
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值