SpringCloud系列之客户端负载均衡Netflix Ribbon

SpringCloud系列之客户端负载均衡Netflix Ribbon

1. 什么是负载均衡?

负载均衡是一种基础的网络服务,它的核心原理是按照指定的负载均衡算法,将请求分配到后端服务集群上,从而为系统提供并行处理和高可用的能力。提到负载均衡,你可能想到nginx。对于负载均衡,一般分为服务端负载均衡和客户端负载均衡

  • 服务端负载均衡:在消费者和服务提供方中间使用独立的代理方式进行负载,有硬件的负载均衡器,比如 F5,也有软件,比如 Nginx。
    在这里插入图片描述

  • 客户端负载均衡:所谓客户端负载均衡,就是客户端根据自己的请求情况做负载,本文介绍的Netflix Ribbon就是客户端负载均衡的组件
    在这里插入图片描述

2. 什么是Netflix Ribbon?

上一章的学习中,我们知道了微服务的基本概念,知道怎么基于Ribbon+restTemplate的方式实现服务调用,接着上篇博客,我们再比较详细学习客户端负载均衡Netflix Ribbon,学习本博客之前请先学习上篇博客,然后再学习本篇博客

Ribbon 是由 Netflix 发布的负载均衡器,它有助于控制 HTTP 和 TCP 的客户端的行为。Ribbon 属于客户端负载均衡。

3. Netflix Ribbon实验环境准备

环境准备:

  • JDK 1.8
  • SpringBoot2.2.3
  • SpringCloud(Hoxton.SR6)
  • Maven 3.2+
  • 开发工具
    • IntelliJ IDEA
    • smartGit

创建一个SpringBoot Initialize项目,详情可以参考我之前博客:SpringBoot系列之快速创建项目教程

可以引入Eureka Discovery Client,也可以单独添加Ribbon
在这里插入图片描述

Spring Cloud Hoxton.SR6版本不需要引入spring-cloud-starter-netflix-ribbon,已经默认集成
在这里插入图片描述
也可以单独添加Ribbon依赖:
在这里插入图片描述
本博客的是基于spring-cloud-starter-netflix-eureka-client进行试验,试验前要运行eureka服务端,eureka服务提供者,代码请参考上一章博客

补充:IDEA中多实例运行方法

step1:如图,不要加上勾选
在这里插入图片描述

step2:指定不同的server端口和实例id,如图:
在这里插入图片描述
启动成功后,是可以看到多个实例的
在这里插入图片描述

4. Netflix Ribbon API使用

使用LoadBalancerClient

 @Autowired
    LoadBalancerClient loadBalancerClient;

    @Test
    void contextLoads() {
        ServiceInstance serviceInstance = loadBalancerClient.choose("EUREKA-SERVICE-PROVIDER");
        URI uri = URI.create(String.format("http://%s:%s", serviceInstance.getHost() , serviceInstance.getPort()));
        System.out.println(uri.toString());
    }

构建BaseLoadBalancer 实例例子:

 @Test
    void testLoadBalancer(){
        // 服务列表
        List<Server> serverList = Arrays.asList(new Server("localhost", 8083), new Server("localhost", 8084));
        // 构建负载实例
        BaseLoadBalancer loadBalancer = LoadBalancerBuilder.newBuilder().buildFixedServerListLoadBalancer(serverList);
        loadBalancer.setRule(new RandomRule());
        for (int i = 0; i < 5; i++) {
            String result = LoadBalancerCommand.<String>builder().withLoadBalancer(loadBalancer).build()
                    .submit(new ServerOperation<String>() {
                        public Observable<String> call(Server server) {
                            try {
                                String address = "http://" + server.getHost() + ":" + server.getPort()+"/EUREKA-SERVICE-PROVIDER/api/users/mojombo";
                                System.out.println("调用地址:" + address);
                                return Observable.just("");
                            } catch (Exception e) {
                                return Observable.error(e);
                            }
                        }
                    }).toBlocking().first();
            System.out.println("result:" + result);
        }
    }

5. 负载均衡@LoadBalanced

Ribbon负载均衡实现,RestTemplate 要加上@LoadBalanced

package com.example.springcloud.ribbon.configuration;

import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

/**
 * <pre>
 *  RestConfiguration
 * </pre>
 *
 * <pre>
 * @author mazq
 * 修改记录
 *    修改后版本:     修改人:  修改日期: 2020/07/31 09:43  修改内容:
 * </pre>
 */
@Configuration
public class RestConfiguration {
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

yaml配置:

server:
  port: 8082
spring:
  application:
    name: eureka-service-consumer
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/
    fetch-registry: true
    register-with-eureka: false
    healthcheck:
      enabled: false
  instance:
    status-page-url-path: http://localhost:8761/actuator/info
    health-check-url-path: http://localhost:8761/actuator//health
    prefer-ip-address: true
    instance-id: eureka-service-consumer8082

在这里插入图片描述
关键点,使用SpringCloud的@LoadBalanced,才能调http://EUREKA-SERVICE-PROVIDER/api/users/? 接口的数据,浏览器是不能直接调的


import com.example.springcloud.ribbon.bean.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;

import java.net.URI;

@SpringBootApplication
@EnableEurekaClient
@RestController
@Slf4j
public class SpringcloudRibbonApplication {


    @Autowired
    RestTemplate restTemplate;

    public static void main(String[] args) {
        SpringApplication.run(SpringcloudRibbonApplication.class, args);
    }

    @GetMapping("/findUser/{username}")
    public User index(@PathVariable("username")String username){
        return restTemplate.getForObject("http://EUREKA-SERVICE-PROVIDER/api/users/"+username,User.class);
    }

}

在这里插入图片描述

6. 定制Netflix Ribbon client

具体怎么定制?可以参考官网,@RibbonClient指定定制的配置类既可
在这里插入图片描述

package com.example.springcloud.ribbon.configuration;

import com.example.springcloud.ribbon.component.MyRule;
import com.netflix.loadbalancer.IPing;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.PingUrl;
import org.springframework.cloud.netflix.ribbon.ZonePreferenceServerListFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * <pre>
 *   Ribbon Clients configuration
 * </pre>
 *
 * <pre>
 * @author mazq
 * 修改记录
 *    修改后版本:     修改人:  修改日期: 2020/07/29 14:22  修改内容:
 * </pre>
 */
//@Configuration(proxyBeanMethods = false)
//@IgnoreComponentScan
public class RibbonClientConfiguration {

//    @Autowired
//    IClientConfig config;

    @Bean
    public IRule roundRobinRule() {
        return new MyRule();
    }

    @Bean
    public ZonePreferenceServerListFilter serverListFilter() {
        ZonePreferenceServerListFilter filter = new ZonePreferenceServerListFilter();
        filter.setZone("myTestZone");
        return filter;
    }

    @Bean
    public IPing ribbonPing() {
        return new PingUrl();
    }

}

在Application类加上@RibbonClient,name是为服务名称,跟bootstrap.yml配置的一样既可

@RibbonClient(name = "eureka-service-provider",configuration = RibbonClientConfiguration.class)

特别注意:官网这里特意提醒,这里的意思是说@RibbonClient指定的配置类必须加@Configuration(不过在Hoxton.SR6版本经过我的验证,其实是可以不加的,加了反而可能报错),@ComponentScan扫描要排除自定义的配置类,否则,它由所有@RibbonClients共享。如果你使用@ComponentScan(或@SpringBootApplication)
在这里插入图片描述
其实就是想让我们排除这个配置的全局扫描,所以我们可以进行编码,写个注解类@IgnoreComponentScan ,作用于类,指定@Target(ElementType.TYPE)

package com.example.springcloud.ribbon.configuration;

import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface IgnoreComponentScan {

}

加上自定义的注解类
在这里插入图片描述

任何在Application加上代码,避免全局扫描:

@ComponentScan(excludeFilters={@ComponentScan.Filter(type= FilterType.ANNOTATION,value= IgnoreComponentScan.class)})

7. Netflix Ribbon常用组件

ps:介绍Netflix Ribbon的负载策略之前,先介绍Netflix Ribbon常用组件及其作用:

组件 作用
ILoadBalancer 定义一系列的操作接口,比如选择服务实例。
IRule 负载算法策略,内置算法策略来为服务实例的选择提供服务。
ServerList 负责服务实例信息的获取(可以获取配置文件中的,也可以从注册中心获取。)
ServerListFilter 过滤掉某些不想要的服务实例信息。
ServerListUpdater 更新本地缓存的服务实例信息。
IPing 对已有的服务实例进行可用性检查,保证选择的服务都是可用的。

8. 定制Netflix Ribbon策略

因为服务提供者是多实例的,所以再写个接口测试,调用了哪个实例,来看看Netflix Ribbon的负载策略

@Autowired
    LoadBalancerClient loadBalancerClient;

    @GetMapping(value = {"/test"})
    public String test(){
        ServiceInstance serviceInstance = loadBalancerClient.choose("EUREKA-SERVICE-PROVIDER");
        URI uri = URI.create(String.format("http://%s:%s", serviceInstance.getHost() , serviceInstance.getPort()));
        System.out.println(uri.toString());
        return uri.toString();
    }

部署成功,多次调用,可以看到每次调用的服务实例都不一样?其实Netflix Ribbon默认是按照轮询的方式调用的
在这里插入图片描述

要定制Netflix Ribbon的负载均衡策略,需要实现AbstractLoadBalancerRule抽象类,下面给出类图:
在这里插入图片描述
Netflix Ribbon内置了如下的负载均衡策略,引用https://juejin.im/post/6854573215587500045的归纳:
在这里插入图片描述

ok,接着我们可以在配置类,修改规则

@Bean
    public IRule roundRobinRule() {
        return new BestAvailableRule();
    }

测试,基本都是调8083这个实例,因为这个实例性能比较好
在这里插入图片描述
显然,也可以自己写个策略类,代码参考com.netflix.loadbalancer.RandomRule,网上也有很多例子,思路是修改RandomRule原来的策略,之前随机调服务实例一次,现在改成每调5次后,再调其它的服务实例

package com.example.springcloud.ribbon.component;
 
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;


public class MyRule extends AbstractLoadBalancerRule
{
    // 总共被调用的次数,目前要求每台被调用5次
    private int total = 0;
    // 当前提供服务的机器号
    private int index = 0;

    public Server choose(ILoadBalancer lb, Object key)
    {
        if (lb == null) {
            return null;
        }
        Server server = null;

        while (server == null) {
            if (Thread.interrupted()) {
                return null;
            }
            // 获取可用的服务列表
            List<Server> upList = lb.getReachableServers();
            // 获取所有服务列表
            List<Server> allList = lb.getAllServers();

            int serverCount = allList.size();
            if (serverCount == 0) {
               // 没有获取到服务
                return null;
            }

            //int index = chooseRandomInt(serverCount);
            //server = upList.get(index);
            if(total < 5)
            {
                server = upList.get(index);
                total++;
            }else {
                total = 0;
                index++;
                if(index >= upList.size())
                {
                    index = 0;
                }
            }

            if (server == null) {
                // 释放线程
                Thread.yield();
                continue;
            }

            if (server.isAlive()) {
                return (server);
            }

            server = null;
            Thread.yield();
        }

        return server;
    }

    protected int chooseRandomInt(int serverCount) {
        return ThreadLocalRandom.current().nextInt(serverCount);
    }

    @Override
    public Server choose(Object key) {
        return choose(getLoadBalancer(), key);
    }

    @Override
    public void initWithNiwsConfig(IClientConfig clientConfig) {
    }
}

修改IRule ,返回MyRule

 @Bean
    public IRule roundRobinRule() {
        return new MyRule();
    }

附录:

ok,本博客参考官方教程进行实践,仅仅作为入门的学习参考资料,详情可以参考Spring Cloud官方文档https://docs.spring.io/spring-cloud-netflix/docs/2.2.x-SNAPSHOT/reference/html/#customizing-the-ribbon-client

代码例子下载:code download

优质学习资料参考:

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/2621.html原文链接:https://javaforall.net

(0)
上一篇 2020年11月19日 下午10:31
下一篇 2020年11月19日 下午10:36


相关推荐

  • 了解 Manus Sandbox – 您的云计算机

    了解 Manus Sandbox – 您的云计算机

    2026年3月15日
    1
  • linux efi shell,EFI Shell 命令说明「建议收藏」

    linux efi shell,EFI Shell 命令说明「建议收藏」EFIShell命令说明引导命令—EFIShell与nPartition引导有关的命令。autoboot设置(查看)自动引导超时变量。bcfg显示(或修改)驱动程序(或引导配置)。boottest设置(或查看)BootTest位。dbprofile显示/修改要由lanboot使用的直接引导配置文件。lanboot在LAN上引导。reconfigrese…

    2022年7月24日
    43
  • 无法启动IIS服务解决办法

    无法启动IIS服务解决办法无法启动 IIS 服务启动 iis 报错如下图错误 nbsp 解决办法 nbsp 原因可能是 World nbsp Wide nbsp Web nbsp Publishing nbsp Service 服务停止了 需要开启服务 如下图 找到 World nbsp Wide nbsp Web nbsp Publishing nbsp Service 服务 右键启动 然后在重启 IIS 就 ok 了 nbsp 注 请使用 administrato 身份

    2026年3月18日
    2
  • Java中使用JDBC连接数据库[通俗易懂]

    Java中使用JDBC连接数据库[通俗易懂]Java中使用JDBC连接数据库加载驱动创建数据库连接创建执行sql的语句执行语句处理执行结果释放资源源代码附上:packagecom.demo.test;importjava.sql.Connection;importjava.sql.DriverManager;importjava.sql.PreparedStatement;imp…

    2022年6月23日
    33
  • php中_initialize()函数与 __construct()函数的区别说明

    php中_initialize()函数与 __construct()函数的区别说明

    2022年2月8日
    55
  • SAE J1939介绍

    SAE J1939介绍CAN协议最初由美国博世公司提出,后来SAE在CAN2.0B的基础之上提出J1939协议,该协议主要面向客车和载重货车。J1939协议对应ISO提出的七层OSI模型中的物理层、数据链路层、网络层和应用层,除了这四层,为了保证数据的准确传输和故障诊断,J1939还具有网络管理和应用层诊断。物理层对应J1939-11、J1939-15;数据链路层对应协议中的J1939-21;网络层对应协议中的

    2022年5月1日
    115

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号