目录

Ribbon 负载均衡策略概述

Ribbon 负载均衡策略配置

Ribbon 脱离 Eureka 使用

没有 Eureka 时

已有 Eureka 时

Using the Ribbon API Directly(直接使用 Ribbon API)


Ribbon 负载均衡策略概述

1、如有微服务 mc 下有 3 个节点 A、B、C,当微服务 mk 请求微服务 mc 时,应该使用何种规则向节点 A、B、C 发起请求呢?于是 Ribbon 有了负载均衡策略。

2、Ribbon 负载均衡继承结构如下图所示,IRule 接口是整个规则/策略的超类/接口:

策略名描述
BestAvailableRule选择一个最小的并发请求的server。逐个考察 Server,如果 Server 被 tripped(跳闸)了,则忽略,再选择其中 ActiveRequestsCount 最小的 server。
AvailabilityFilteringRule过滤掉那些因为一直连接失败的被标记为circuit tripped的后端server,并过滤掉那些高并发的的后端server(active connections 超过配置的阈值)
WeightedResponseTimeRule根据响应时间分配一个weight,响应时间越长,weight越小,被选中的可能性越低。
RetryRule对选定的负载均衡策略机上重试机制。在一个配置时间段内当选择server不成功,则一直尝试使用subRule的方式选择一个可用的server
RoundRobinRule轮询index,选择index对应位置的server
RandomRule随机选择一个server。在index上随机,选择index对应位置的server
ZoneAvoidanceRule复合判断server所在区域的性能和server的可用性选择server

Ribbon 负载均衡策略配置

1、仍然以 <<netflix ribbon 概述与基本使用、以及 RestTemplate 概述>> 中的 3 个应用作为本文介绍的基础:

eurekaserverchangSha 应用作为 Eureka 服务端, eurekaclientfood、eurekaclient_cat 作为客户端,eurekaclientcat 微服务请求 eurekaclientfood 微服务。

2、eureka 依赖 ribbon,导入了 eureka 客户端组件就同时导入了 ribbon。环境:Java jdk 8 + Spring boot 2.1.3 + spring cloud Greenwich.SR1 + spring 5.1.5。

3、官网 "6.4 Customizing the Ribbon Client by Setting Properties" 有说明:

全局配置文件的选项优先级高于 @RibbonClient(configuration=MyRibbonConfig.class 代码方式,@RibbonClient 代码方式高于默认值。

从 1.2.0 版本开始 Spring Cloud Netflix 支持从全局文件进行配置,支持的配置如下:

<clientName>.ribbon.NFLoadBalancerClassName: Should implement ILoadBalancer
<clientName>.ribbon.NFLoadBalancerRuleClassName: Should implement IRule
<clientName>.ribbon.NFLoadBalancerPingClassName: Should implement IPing
<clientName>.ribbon.NIWSServerListClassName: Should implement ServerList
<clientName>.ribbon.NIWSServerListFilterClassName: Should implement ServerListFilter

其中的 <clientName> 为 Eureka 注册中心注册好的微服务名称,也是微服务应用配置的 spring.applicatoin.name 属性值。

配置的属性值为各个接口实现类的全类名。如下所示 users 为需要请求的微服务名称,属性值为各接口实现类的全类名:

users:
  ribbon:
    NIWSServerListClassName: com.netflix.loadbalancer.ConfigurationBasedServerList
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.WeightedResponseTimeRule   #加权响应时间规则

4、eurekaclientcat 请求 eurekaclientfood,所以修改 eurekaclient_cat 的配置文件如下,其余的内容都无需修改:

server:
  port: 9394

spring:
  application:
    name: eureka-client-cat  #微服务名称

eureka:
  client:
    service-url:
      defaultZone: http://localhost:9393/eureka/ #eureka 服务器地址
  instance:
    prefer-ip-address: true # IP 地址代替主机名注册
    instance-id: changSha-cat # 微服务实例id名称

#EUREKA-CLIENT-FOOD 请求的微服务名称,即对方的 spring.application.name 属性值
EUREKA-CLIENT-FOOD:
  ribbon:
    #随机规则,对 EUREKA-CLIENT-FOOD 微服务下的节点随机访问
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

5、启动 eurekaserverchangSha 、eurekaclient_cat  应用 ,然后使用下面的命令再启动打包好的 eurekaclient_food 3 个实例,使用不同的端口以及实例 id:

java -jar eurekaclient_food-0.0.1-SNAPSHOT.jar --server.port=9396 --eureka.instance.instance-id=changSha-food-9396
java -jar eurekaclient_food-0.0.1-SNAPSHOT.jar --server.port=9397 --eureka.instance.instance-id=changSha-food-9397
java -jar eurekaclient_food-0.0.1-SNAPSHOT.jar --server.port=9398 --eureka.instance.instance-id=changSha-food-9398

此时 eureka 注册中心如下所示:

6、访问 http://localhost:9394/getCatById?id=110 通过 changSha-cat 微服务后台请求 Eureka-client-food 微服务下的3个节点:

如果没有在 eurekaclient_cat  中配置随机访问负载均衡策略,则默认情况下是使用轮询策略的,如上所示,显示现在是随机访问的,负载均衡配置生效。其它的均衡策略也是同理。

Ribbon 脱离 Eureka 使用

Ribbon 脱离 Eureka 使用分为两种情况,一是项目中并没有使用 Eureka,二是项目中已经有 Eureka

没有 Eureka 时

1、之前说过 Eureka 无论是服务端还是客户端都依赖了 Ribbon,所以导入了 Eureka 组件后,同时已经导入了 Ribbon 组件,所以直接编码 Ribbon 即可。现在没有 Eureka 时,需要单独导入 Ribbon 组件,修改 eureka-client-cat 的 pom.xml 文件如下(此时没有 Eureka 组件):

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

2、官网介绍地址:"How to Use Ribbon Without Eureka" ,当没有 Eureka 时,只需要修改 application.yml 如下:

stores:
  ribbon:
    listOfServers: example.com,google.com

#stores 是一个自定义的标识符,建议写成请求的微服务名称,即对方的 spring.application.name 属性值。
#listOfServers 为请求的服务域名地址,也可以直接是 Ip:Port 格式,不用带应用名称,而是在代码中写应用名

3、现在继续修改 eureka-client-cat 配置文件如下(此时没有 eureka 的配置了):

server:
  port: 9394

#EUREKA-CLIENT-FOOD 纯粹是一个自定义的标识,会在代码中使用类似如下的方式进行识别,根据标识找到服务器地址,然后发起请求
#restTemplate.getForObject("http://EUREKA-CLIENT-FOOD/getHunanCuisine", String.class);#如果对方写提供了应用名称,则也要在URL中加上
EUREKA-CLIENT-FOOD:
  ribbon:
    #随机规则,对 EUREKA-CLIENT-FOOD 微服务下的节点随机访问
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
    listOfServers: 192.168.3.6:9396,192.168.3.6:9397 #请求的服务地址,ip:port,多个时使用 逗号 隔开
    #注意不用带应用名称,而是在代码中写应用名

4、eureka-client-cat 其它位置都不需要修改。后台代码使用:

String foodMenu = restTemplate.getForObject("http://EUREKA-CLIENT-FOOD/getHunanCuisine", String.class); 

因为 restTemplate 具有负载均衡的能力,所以会根据标识 EUREKA-CLIENT-FOOD 查找配置文件中的服务器实际地址(listOfServers),然后根据策略发起请求。后台代码不变

5、此时因为没有使用 Eureka 客户端,所以 Eureka 注册中心是没有 eureka-client-cat 微服务的,但是并不影响对 EUREKA-CLIENT-FOOD 的访问。

总结:没有使用 Eureka 时,先导入 Ribbon 组件,然后修改配置文件添加服务器列表,最后使用具有负载均衡能力的 RestTemplate 向目标微服务发起 http 请求。

已有 Eureka 时

1、官网文档 Disable Eureka Use in Ribbon,对于项目中已经使用了 Eureka 时,需要 Ribbon 禁用 Eureka ,配置如下:

ribbon:
  eureka:
   enabled: false

2、ribbon.eureka.enabled=false 是 Ribbon 不再使用 Eureka 发现的服务,而使用自己 stores.ribbon.listOfServers 配置的服务。

所以仅仅是 Ribbon 不再依赖 Eureka,而不是项目中禁用 Eureka。

3、修改 eureka-client-cat 的 pom.xml 文件重新添加 spring-cloud-starter-netflix-eureka-client,然后修改配置文件如下:

server:
  port: 9394

spring:
  application:
    name: eureka-client-cat  #微服务名称

eureka:
  client:
    service-url:
      defaultZone: http://localhost:9393/eureka/ #eureka 服务器地址
  instance:
    prefer-ip-address: true # IP 地址代替主机名注册
    instance-id: changSha-cat # 微服务实例id名称

#EUREKA-CLIENT-FOOD :虽然 Ribbon 脱离 Eureka 使用可以自定义标识符,但还是建议写成对方的微服务名称
EUREKA-CLIENT-FOOD:
  ribbon:
    #随机规则,对 EUREKA-CLIENT-FOOD 微服务下的节点随机访问
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
    listOfServers: 192.168.3.6:9396 #请求的服务地址,ip:port,多个时使用 逗号 隔开。不要带应用名称

ribbon:
  eureka:
    enabled: false #Ribbon 禁用 Eureka,禁用后 Ribbon 自己的 *.ribbon.listOfServers 服务配置才会生效

总结:有 Eureka 与没有 Eureka 相比多了一个 Ribbon 禁用 Eureka 的操作,其余是一样的。

后台代码:String foodMenu = restTemplate.getForObject("http://EUREKA-CLIENT-FOOD/getHunanCuisine", String.class);(如果有应用名称,则在 url 中加上)

原理:Ribbon 没有脱离 Eureka 时,负载均衡请求的服务名称 "EUREKA-CLIENT-FOOD" 会自动从 Eureka 客户端服务发现的服务列表中进行查询解析,然后根据实际地址发起请求。当 Ribbon 脱离了 Eureka 时,显然无法再从 Eureka 发现的服务列表中获取,所以需要在配置文件中使用 *.ribbon.listOfServers 进行服务配置,配置服务实际的 ip 与 端口。

因为 EUREKA-CLIENT-FOOD.ribbon.listOfServers 只配置了一个服务器地址,所以永远都是请求它。

Using the Ribbon API Directly(直接使用 Ribbon API)

1、可以通过 LoadBalancerClient API 来获取请求的服务实例 org.springframework.cloud.client.ServiceInstance。

2、直接 @Autowired、@Resource 从容器中获取 org.springframework.cloud.client.loadbalancer.LoadBalancerClient 使用即可。

3、官网文档 "Using the Ribbon API Directly" 已经写的很详细,这里在依样画葫芦:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;
import java.net.URI;

@RestController
public class SystemController {

    //获取容器中创建好的 RestTemplate 实例
    @Resource
    private RestTemplate restTemplate;

    //默认已经在容器中创建好了实例,直接获取即可
    @Autowired
    private LoadBalancerClient loadBalancerClient;

    /**
     * localhost:9394/loadBalancerClient
     *
     * @return
     */
    @GetMapping("loadBalancerClient")
    public String testLoadBalancerClient() {
        //choose(String serviceId):服务id,没有脱离 Eureka 时,这里通常就是对方服务名称,即 spring.application 属性值
        //当 Ribbon 脱离 Eureka 时,服务id 就是与自己的配置文件 xxx.ribbon.listOfServers 中的 xxx 保持一致
        ServiceInstance serviceInstance = loadBalancerClient.choose("EUREKA-CLIENT-FOOD");
        String host = serviceInstance.getHost();
        int port = serviceInstance.getPort();
        String instanceId = serviceInstance.getInstanceId();
        String serviceId = serviceInstance.getServiceId();
        URI uri = serviceInstance.getUri();
        URI storesUri = URI.create(String.format("http://%s:%s", serviceInstance.getHost(), serviceInstance.getPort()));

        System.out.println("host:" + host);
        System.out.println("port:" + port);
        System.out.println("instanceId:" + instanceId);
        System.out.println("serviceId:" + serviceId);
        System.out.println("uri:" + uri);
        System.out.println("storesUri:" + storesUri);

        return "";
    }
}

//控制台输出如下:
host:192.168.3.6
port:9395
instanceId:192.168.3.6:9395
serviceId:wmx
uri:http://192.168.3.6:9395
storesUri:http://192.168.3.6:9395

演示源码 github 地址:https://github.com/wangmaoxiong/ribbon_study

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐