什么是负载均衡?详解SpringCloud之客户端负载均衡Netflix Ribbon

1. 什么是负载均衡?

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

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


    image.png
  • 客户端负载均衡:所谓客户端负载均衡,就是客户端根据自己的请求情况做负载,本文介绍的Netflix Ribbon就是客户端负载均衡的组件
image.png

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项目

可以引入Eureka Discovery Client,也可以单独添加Ribbon

image.png

Spring Cloud Hoxton.SR6版本不需要引入spring-cloud-starter-netflix-ribbon,已经默认集成

image.png

也可以单独添加Ribbon依赖:

image.png

本博客的是基于spring-cloud-starter-netflix-eureka-client进行试验,试验前要运行eureka服务端

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

step1:如图,不要加上勾选

image.png

step2:指定不同的server端口和实例id,如图:

image.png

启动成功后,是可以看到多个实例的

image.png

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 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.builder().withLoadBalancer(loadBalancer).build()
                    .submit(new ServerOperation() {
                        public Observable 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;

/**
 * 
 *  RestConfiguration
 * 
* *
 * @author mazq
 * 修改记录
 *    修改后版本:     修改人:  修改日期: 2020/07/31 09:43  修改内容:
 * 
*/ @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
image.png

关键点,使用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);
    }

}
image.png

6. 定制Netflix Ribbon client

具体怎么定制?可以参考官网,@RibbonClient指定定制的配置类即可

image.png
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;

/**
 * 
 *   Ribbon Clients configuration
 * 
* *
 * @author mazq
 * 修改记录
 *    修改后版本:     修改人:  修改日期: 2020/07/29 14:22  修改内容:
 * 
*/ //@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)

image.png

其实就是想让我们排除这个配置的全局扫描,所以我们可以进行编码,写个注解类@IgnoreComponentScan,作用于类,指定@Target(ElementType.TYPE)

package com.example.springcloud.ribbon.configuration;

import java.lang.annotation.*;

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

}

加上自定义的注解类

image.png

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

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

7. Netflix Ribbon常用组件

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

image.png

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默认是按照轮询的方式调用的

image.png

要定制Netflix Ribbon的负载均衡策略,需要实现AbstractLoadBalancerRule抽象类,下面给出类图:

image.png

Netflix Ribbon内置了如下的负载均衡策略


image.png

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

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

测试,基本都是调8083这个实例,因为这个实例性能比较好

image.png

显然,也可以自己写个策略类,代码参考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 upList = lb.getReachableServers();
            // 获取所有服务列表
            List 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();
    }

你可能感兴趣的:(什么是负载均衡?详解SpringCloud之客户端负载均衡Netflix Ribbon)