likes
comments
collection
share

Spring Cloud 使用 Ribbon 来实现客户端负载均衡

作者站长头像
站长
· 阅读数 14

文章目录

Spring cloud 使用 Ribbon 来实现客户端负载均衡

前言

在Spring cloud 中当统一类型多个服务开始注册到服务注册中心中,次数服务即是集群 消费端(客户端)消费的时候需要进行选择调用服务。

服务注册 点击

Spring cloud 中默认调用是依赖eureka 的 ribbon 来实现客户端负载均衡策略

负载均衡

负载均衡⼀般分为服务器端负载均衡和客户端负载均衡

所谓服务器端负载均衡,⽐如NginxF5这些,请求到达服务器之后由这些负载均衡 器根据⼀定的算法将请求路由到⽬标服务器处理。

所谓客户端负载均衡,⽐如我们要说的Ribbon,服务消费者客户端会有⼀个服务器 地址列表,调⽤⽅在请求前通过⼀定的负载均衡算法选择⼀个服务器进⾏访问,负 载均衡算法的执⾏是在请求客户端进⾏。

Ribbon是Netflix发布的负载均衡器。Eureka⼀般配合Ribbon进⾏使⽤,Ribbon利 ⽤从Eureka中读取到服务信息,在调⽤服务提供者提供的服务时,会根据⼀定的算 法进⾏负载。

Spring Cloud 使用 Ribbon 来实现客户端负载均衡

我们先来注册两个服务 Spring Cloud 使用 Ribbon 来实现客户端负载均衡

然后采用 ribbon 来实现负载均衡策略

ribbon依赖

ribbon 依赖eureka进行实现负载均衡,所以我们不需要导入包,默认在eureka 依赖中存在 Spring Cloud 使用 Ribbon 来实现客户端负载均衡

实现负载均衡

仅仅需要在 上面增加 @LoadBalanced注解即可


@EnableDiscoveryClient
@SpringBootApplication
public class EurekaclientApplication {

    //开启负载均衡
    @LoadBalanced
    @Bean
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }

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

}

消费调用

@RestController
public class GetUserController {

    @Autowired
    RestTemplate restTemplate;

    //服务发现对象
    @Autowired
    DiscoveryClient discoveryClient;


    @RequestMapping("get")
    public User getUser(int id){
        //user微服务的名字 用来想此服务发送请求
        String  servceId = "user-server";

        String url = "http://" + servceId  + "/getUser?id=" + id;
        System.out.println(url +  " =  " +  url);
        User forObject = restTemplate.getForObject(url, User.class);


}

测试

打印每次调用的 url 和 端口


    @Autowired
    LoadBalancerClient loadBalancerClient;


        for (int i = 0; i < 100; i++) {

            ServiceInstance choose = loadBalancerClient.choose("user-server");
            System.out.println("第 " + (i+1) + "次 执行 " +  choose.getPort() + choose.getUri());
        }
        return forObject;
    }

结果如下

我们可以看到每次是轮训调用,一个接着一个循环调用请求(总共2个服务)。

Spring Cloud 使用 Ribbon 来实现客户端负载均衡

默认策略

ribbon自动配置中没有指定配置策略默认实现轮训策略

Spring Cloud 使用 Ribbon 来实现客户端负载均衡

设置随机策略

设置负载均衡为随机策略

serviceId.ribbon.NFLoadBalancerRuleClassName=自定义的负载均衡策略类

在配置文件里面配置


user-server: # 这个配置是请求的服务名
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

或者通过硬编码方式


@Configuration
public class RibbonConfiguration {
 
    @Bean
    public IRule ribbonRule() {
        // 负载均衡规则,改为随机
        return new RandomRule();
    }

默认情况下 配置文件大于硬编码方式

测试结果

每次请求随机访问 Spring Cloud 使用 Ribbon 来实现客户端负载均衡

自定义策略

Ribbon 默认通过 IRule 接口来实现负载均衡策略,默认实现通过ZoneAvoidanceRule 轮训负载

  • 结构图 Spring Cloud 使用 Ribbon 来实现客户端负载均衡

另外自带的负载均衡策略

Spring Cloud 使用 Ribbon 来实现客户端负载均衡

不依赖eureka实现Ribbon负载均衡

ribbon可以脱离eureka实现负载均衡如下

#取消Ribbon使用Eureka
ribbon.eureka.enabled=false

#配置Ribbon能访问 的微服务节点,多个节点用逗号隔开
 user-server.ribbon.listOfServers=localhost:6869,localhost:6870

最后可能会遇到的问题

可能会报错

springcloud ribbon实现负载均衡的时候,提示Request URI does not contain a valid hostname: http://PRODUCT_SERVICE/

原因是因为服务名不能 有 下划线

代码地址 传送门

Ribbon默认负载均衡源码解析

从自动装配开始 Spring Cloud 使用 Ribbon 来实现客户端负载均衡

Spring Cloud 使用 Ribbon 来实现客户端负载均衡

SpringClientFactory 类中默认构造方法 Spring Cloud 使用 Ribbon 来实现客户端负载均衡

如果配置文件没配置则默认轮训负载均衡 Spring Cloud 使用 Ribbon 来实现客户端负载均衡

LoadBalancerAutoConfiguration 类 Spring Cloud 使用 Ribbon 来实现客户端负载均衡

可以看到在使用RestTemplate的时候生效

Spring Cloud 使用 Ribbon 来实现客户端负载均衡

可以看到给每一个的 注⼊resttemplate对象到集合待⽤,注⼊resttemplate定制器 Spring Cloud 使用 Ribbon 来实现客户端负载均衡

然后给每一个注⼊resttemplate定制器添加一个拦截器

Spring Cloud 使用 Ribbon 来实现客户端负载均衡

然后LoadBalancerInterceptor拦截器 可以看到 获取服务id Spring Cloud 使用 Ribbon 来实现客户端负载均衡 进入execute方法可以看到

	public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint) throws IOException {
		ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
		Server server = getServer(loadBalancer, hint);
		if (server == null) {
			throw new IllegalStateException("No instances available for " + serviceId);
		}
		//将服务封装成一个RibbonServer 对象
		RibbonServer ribbonServer = new RibbonServer(serviceId, server, isSecure(server,
				serviceId), serverIntrospector(serviceId).getMetadata(server));

		return execute(serviceId, ribbonServer, request);
	}

Server server = getServer(loadBalancer, hint); 选择服务

具体代码在ZoneAwareLoadBalancer类#chooseServer方法

 @Override
    public Server chooseServer(Object key) {
    //一个服务直接走这个
        if (!ENABLED.get() || getLoadBalancerStats().getAvailableZones().size() <= 1) {
            logger.debug("Zone aware logic disabled or there is only one zone");
            return super.chooseServer(key);
        }
        Server server = null;
        try {
            LoadBalancerStats lbStats = getLoadBalancerStats();
            Map<String, ZoneSnapshot> zoneSnapshot = ZoneAvoidanceRule.createSnapshot(lbStats);
            logger.debug("Zone snapshots: {}", zoneSnapshot);
            if (triggeringLoad == null) {
                triggeringLoad = DynamicPropertyFactory.getInstance().getDoubleProperty(
                        "ZoneAwareNIWSDiscoveryLoadBalancer." + this.getName() + ".triggeringLoadPerServerThreshold", 0.2d);
            }

            if (triggeringBlackoutPercentage == null) {
                triggeringBlackoutPercentage = DynamicPropertyFactory.getInstance().getDoubleProperty(
                        "ZoneAwareNIWSDiscoveryLoadBalancer." + this.getName() + ".avoidZoneWithBlackoutPercetage", 0.99999d);
            }
            Set<String> availableZones = ZoneAvoidanceRule.getAvailableZones(zoneSnapshot, triggeringLoad.get(), triggeringBlackoutPercentage.get());
            logger.debug("Available zones: {}", availableZones);
            if (availableZones != null &&  availableZones.size() < zoneSnapshot.keySet().size()) {
                String zone = ZoneAvoidanceRule.randomChooseZone(zoneSnapshot, availableZones);
                logger.debug("Zone chosen: {}", zone);
                if (zone != null) {
                    BaseLoadBalancer zoneLoadBalancer = getLoadBalancer(zone);
                    server = zoneLoadBalancer.chooseServer(key);
                }
            }
        } catch (Exception e) {
            logger.error("Error choosing server using zone aware logic for load balancer={}", name, e);
        }
        if (server != null) {
            return server;
        } else {
            logger.debug("Zone avoidance logic is not invoked.");
            return super.chooseServer(key);
        }
    }

…往下看不懂了… 以后再看

LoadBalancerRetryProperties类

可以看到默认是生效的

Spring Cloud 使用 Ribbon 来实现客户端负载均衡