Spring Cloud 进阶--Ribbon核心组件IRule的使用及自定义负载均衡算法

版权声明:本文为博主原创文章,如果觉得写的不错需要转载,在转载时请注明博文出处! https://blog.csdn.net/Hello_World_QWP/article/details/88185574

                              《 Ribbon核心组件IRule的使用及自定义负载均衡算法 》

前言

在上一篇文章中,主要完成了 《 Rest微服务加入Ribbon负载均衡客户端组件实现负载均衡 》,本篇将带领读者一步一步认识Ribbon的核心插件 “ IRule ”的常用 API 以及自定义算法规则详细说明,本篇博文涉及的服务模块包括:

  • 修改消费者模块 “ microservice-consumer-80 ”,新增具体的算法配置类,实现指定规则的调用;

Ribbon核心组件IRule的使用及自定义负载均衡算法

1、IRule 常见的API

这儿先细列哈Ribbon核心组件IRule中常见的几个算法API,一般情况下,这几个API已经可以满足实际的业务需求了,如下:

API 说明
RoundRobinRule 广为人知和最基本的负载平衡策略,即轮询算法。(是Ribbon默认的负载均衡机制)
RandomRule 一种随机分配现有流量的负载平衡策略,即随机访问算法
RetryRule 先按照 RoundRobinRule 的策略访问服务,如果访问的服务宕机或者出现异常的情况,则在指定时间内会进行重试,访问其它可用的服务
BestAvailableRule 首先会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务访问
ZoneAvoidanceRule 默认规则,复合判断server所在区域的性能和server的可用性选择服务器
AvailabilityFilteringRule 首先会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,还有并发的连接数量超过阈值的服务,然后对剩余的服务列表按照轮询策略进行访问
WeightedResponseTimeRule 根据平均响应时间计算所有服务的权重,响应时间越快服务权重越大被选中的概率越高。
刚启动时如果统计信息不足,则使用RoundRobinRule策略,等统计信息足够,
会切换到WeightedResponseTimeRule

接下将列举前三个API的源码,只为展示其表面的算法,当然更多的内部实现细节,请转到Ribbon官网咯。

RoundRobinRule:

package com.netflix.loadbalancer;

import com.netflix.client.config.IClientConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * The most well known and basic load balancing strategy, i.e. Round Robin Rule.
 *
 * @author stonse
 * @author Nikos Michalakis <[email protected]>
 *
 */
public class RoundRobinRule extends AbstractLoadBalancerRule
{

	private AtomicInteger nextServerCyclicCounter;
	private static final boolean AVAILABLE_ONLY_SERVERS = true;
	private static final boolean ALL_SERVERS = false;

	private static Logger log = LoggerFactory.getLogger(RoundRobinRule.class);

	public RoundRobinRule()
	{
		nextServerCyclicCounter = new AtomicInteger(0);
	}

	public RoundRobinRule(ILoadBalancer lb)
	{
		this();
		setLoadBalancer(lb);
	}

	public Server choose(ILoadBalancer lb, Object key)
	{
		if (lb == null)
		{
			log.warn("no load balancer");
			return null;
		}

		Server server = null;
		int count = 0;
		while (server == null && count++ < 10)
		{
			List<Server> reachableServers = lb.getReachableServers();
			List<Server> allServers = lb.getAllServers();
			int upCount = reachableServers.size();
			int serverCount = allServers.size();

			if ((upCount == 0) || (serverCount == 0))
			{
				log.warn("No up servers available from load balancer: " + lb);
				return null;
			}

			int nextServerIndex = incrementAndGetModulo(serverCount);
			server = allServers.get(nextServerIndex);

			if (server == null)
			{
				/* Transient. */
				Thread.yield();
				continue;
			}

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

			// Next.
			server = null;
		}

		if (count >= 10)
		{
			log.warn("No available alive servers after 10 tries from load balancer: " + lb);
		}
		return server;
	}

	/**
	 * Inspired by the implementation of {@link AtomicInteger#incrementAndGet()}.
	 *
	 * @param modulo
	 *            The modulo to bound the value of the counter.
	 * @return The next value.
	 */
	private int incrementAndGetModulo(int modulo)
	{
		for (;;)
		{
			int current = nextServerCyclicCounter.get();
			int next = (current + 1) % modulo;
			if (nextServerCyclicCounter.compareAndSet(current, next))
				return next;
		}
	}

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

RandomRule(这个博主在实际的业务中也常常遇到:首先获取服务列表,再获取服务总的个数,获取这个总数内的随机数,得到的这个随机数即服务的下标,拿到下标后,再去服务列表中取指定的服务名称即可,不知道你们有没有遇到过呢!):

package com.netflix.loadbalancer;

import com.netflix.client.config.IClientConfig;

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

/**
 * A loadbalacing strategy that randomly distributes traffic amongst existing
 * servers.
 * 
 * @author stonse
 * 
 */
public class RandomRule extends AbstractLoadBalancerRule
{

	/**
	 * Randomly choose from all living servers
	 */
	@edu.umd.cs.findbugs.annotations.SuppressWarnings(value = "RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE")
	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)
			{
				/*
				 * No servers. End regardless of pass, because subsequent passes only get more
				 * restrictive.
				 */
				return null;
			}

			int index = chooseRandomInt(serverCount);
			server = upList.get(index);

			if (server == null)
			{
				/*
				 * The only time this should happen is if the server list were somehow trimmed.
				 * This is a transient condition. Retry after yielding.
				 */
				Thread.yield();
				continue;
			}

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

			// Shouldn't actually happen.. but must be transient or a bug.
			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);
	}
}

RetryRule:

package com.netflix.loadbalancer;

import com.netflix.client.config.IClientConfig;

/**
 * Given that {@link IRule} can be cascaded, this {@link RetryRule} class allows
 * adding a retry logic to an existing Rule.
 * 
 * @author stonse
 * 
 */
public class RetryRule extends AbstractLoadBalancerRule
{
	IRule subRule = new RoundRobinRule();
	long maxRetryMillis = 500;

	public RetryRule()
	{
	}

	public RetryRule(IRule subRule)
	{
		this.subRule = (subRule != null) ? subRule : new RoundRobinRule();
	}

	public RetryRule(IRule subRule, long maxRetryMillis)
	{
		this.subRule = (subRule != null) ? subRule : new RoundRobinRule();
		this.maxRetryMillis = (maxRetryMillis > 0) ? maxRetryMillis : 500;
	}

	public void setRule(IRule subRule)
	{
		this.subRule = (subRule != null) ? subRule : new RoundRobinRule();
	}

	public IRule getRule()
	{
		return subRule;
	}

	public void setMaxRetryMillis(long maxRetryMillis)
	{
		if (maxRetryMillis > 0)
		{
			this.maxRetryMillis = maxRetryMillis;
		} else
		{
			this.maxRetryMillis = 500;
		}
	}

	public long getMaxRetryMillis()
	{
		return maxRetryMillis;
	}

	@Override
	public void setLoadBalancer(ILoadBalancer lb)
	{
		super.setLoadBalancer(lb);
		subRule.setLoadBalancer(lb);
	}

	/*
	 * Loop if necessary. Note that the time CAN be exceeded depending on the
	 * subRule, because we're not spawning additional threads and returning early.
	 */
	public Server choose(ILoadBalancer lb, Object key)
	{
		long requestTime = System.currentTimeMillis();
		long deadline = requestTime + maxRetryMillis;

		Server answer = null;

		answer = subRule.choose(key);

		if (((answer == null) || (!answer.isAlive())) && (System.currentTimeMillis() < deadline))
		{

			InterruptTask task = new InterruptTask(deadline - System.currentTimeMillis());

			while (!Thread.interrupted())
			{
				answer = subRule.choose(key);

				if (((answer == null) || (!answer.isAlive())) && (System.currentTimeMillis() < deadline))
				{
					/* pause and retry hoping it's transient */
					Thread.yield();
				} else
				{
					break;
				}
			}

			task.cancel();
		}

		if ((answer == null) || (!answer.isAlive()))
		{
			return null;
		} else
		{
			return answer;
		}
	}

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

2、实现随机访问策略,一种随机分配现有流量的负载平衡策略

修改消费者服务 “ microservice-consumer-80 ” 的 “ ConfigBean ” 类,新增内容:

	@Bean
	public IRule randomIRule() {
		return new RandomRule();
	}

完整内容:

package com.huazai.springcloud.cfgbeans;

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;

import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;

/**
 * 
 * <p>
 * 
 * @ClassName : ConfigBean
 *            </p>
 *            <p>
 * @Description : TODO
 *              </p>
 * 
 * @Author : HuaZai
 * @ContactInformation : [email protected]
 * @Date : 2018年05月23日 下午9:01:21
 * @Version : V1.0.0
 * 
 * @param
 */
@Configuration
public class ConfigBean
{
	@Bean
	@LoadBalanced
	public RestTemplate getRestTemplate()
	{
		return new RestTemplate();
	}
	
	@Bean
	public IRule randomIRule() {
		return new RandomRule();
	}
	
	
}

测试:

首先启动 Eureka 集群,再启动三台提供者服务器,最后启动消费者服务器,并访问消费者服务器地址,反复刷新,发现访问的服务器是没有规则的,随机访问,注意数据库的变化,如下图:

3、自定义访问策略

新建一个类 “ IRuleConfig ”,用于定义基于 Ribbon 的访问策略,完整内容如下:

package com.huazai.springcloud.cfgbeans;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;

/**
 * 
 * @author HuaZai
 * @contact [email protected]
 *          <ul>
 * @description 自定义访问策略
 *              </ul>
 * @className IRuleConfig
 * @package com.huazai.springcloud.cfgbeans
 * @createdTime 2018年05月23日 下午3:57:31
 *
 * @version V1.0.0
 */
@Configuration
public class IRuleConfig
{
	@Bean
	public IRule myIRule()
	{
		// return new RetryRule();
		return new RandomRule();
		// return new BestAvailableRule();
		// return new ZoneAvoidanceRule();
		// return new AvailabilityFilteringRule();
		// return new WeightedResponseTimeRule();
	}
}

修改消费者 “ microservice-consumer-80 ” 的主启动类,新增注解 “ @RibbonClient ” ,其目的是在启动该微服务的时候就能去加载自定义 Ribbon 配置类,从而使配置立即生效,完整内容如下:

package com.huazai.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.ribbon.RibbonClient;

import com.huazai.springcloud.cfgbeans.ConfigBean;
import com.huazai.springcloud.cfgbeans.IRuleConfig;

/**
 * 
 * @author HuaZai
 * @contact [email protected]
 *          <ul>
 * @description
 *              <li>服务消费者
 *              </ul>
 * @className MicroServiceConsumerApp
 * @package com.huazai.springcloud
 * @createdTime 2018年05月22日 下午3:47:02
 *
 * @version V1.0.0
 */
@SpringBootApplication
@EnableEurekaClient
@RibbonClient(name = "MICROSERVICE-PROVIDER", configuration = IRuleConfig.class)
public class MicroServiceConsumerApp
{

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

测试

首先启动 Eureka 集群,再启动三台提供者服务器,最后启动消费者服务器,并访问消费者服务器地址,反复刷新,发现访问的服务器是没有规则的,随机访问,注意数据库的变化,和上面一样的哈,如下图:

4、新增需求

需求一:由于公司资源有限(说白了就是降低成本),现在有三台服务器,配置由高到低排序 1号服务器 > 2号服务器 > 3号服务器,要求:1号服务器承载50%的流量,2号服务器承载30%的流量,3号服务器承载20%的流量,要求自定义Ribbon 算法实现,这个是当年博主遇到的一个很犯贱的问题,对于当时而言很难,因为什么都要自己写,不像现在什么都封装成了API,直接调用即可,当然现在一般的互联网公司很难遇到了,所以这儿的实现就省略咯。。。

需求二:根据客户那边对环境的需求,指定方案需要对流量进行绝对的限制,但依然是轮询机制,例如:1号服务器被调用3次,之后,2号服务器被调用3次,之后,3号服务器被调用3次,因为也基于轮询机制的,所以可以直接在上面的 " RoundRobinRule.java " 的源代码中进行简单修改即可。

1)新增自定义算法 “ Custom_RandomRule ” 类,并重写IRule的 “ choose ” 方法和IClientConfigAware的 “  initWithNiwsConfig”方法,完整内容如下:

package com.huazai.springcloud.cfgbeans;

import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;

/**
 * 
 * @author HuaZai
 * @contact [email protected]
 *          <ul>
 * @description
 *              <li>自定义轮询机制
 *              </ul>
 * @className Custom_RandomRule
 * @package com.huazai.springcloud.cfgbeans
 * @createdTime 2018年05月24日 下午4:49:51
 *
 * @version V1.0.0
 */
@SuppressWarnings("unused")
public class Custom_RandomRule extends AbstractLoadBalancerRule
{
	private AtomicInteger nextServerCyclicCounter;
	private static final boolean AVAILABLE_ONLY_SERVERS = true;
	private static final boolean ALL_SERVERS = false;

	private int total = 0; // 总的被调用次数
	private int currentIndex = 0;// 当前提供服务的机器号

	private static Logger log = LoggerFactory.getLogger(Custom_RandomRule.class);

	public Server choose(ILoadBalancer lb, Object key)
	{
		if (lb == null)
		{
			log.warn("no load balancer");
			return null;
		}

		Server server = null;

		while (server == null)
		{
			if (Thread.interrupted())
			{
				return null;
			}

			List<Server> reachableServers = lb.getReachableServers();
			List<Server> allServers = lb.getAllServers();
			int upCount = reachableServers.size();
			int serverCount = allServers.size();

			if ((upCount == 0) || (serverCount == 0))
			{
				log.warn("No up servers available from load balancer: " + lb);
				return null;
			}

			// 实现每台服务请求三次的业务逻辑
			if (total < 3)
			{
				server = reachableServers.get(currentIndex);
				total++;
			} else
			{
				total = 0;
				currentIndex++;
				if (currentIndex >= upCount)
				{
					currentIndex = 0;
				}
			}

			if (server == null)
			{
				/* Transient. */
				Thread.yield();
				continue;
			}

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

			// Next.
			server = null;
			Thread.yield();
		}
		return server;
	}

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

	@Override
	public void initWithNiwsConfig(IClientConfig clientConfig)
	{
		// TODO Auto-generated method stub

	}

}

修改配置文件 “ IRuleConfig ” ,将 IRule 修改为自定义的服务调用算法机制,完整内容如下:

package com.huazai.springcloud.cfgbeans;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;

/**
 * 
 * @author HuaZai
 * @contact [email protected]
 *          <ul>
 * @description 自定义访问策略
 *              </ul>
 * @className IRuleConfig
 * @package com.huazai.springcloud.cfgbeans
 * @createdTime 2018年05月23日 下午3:57:31
 *
 * @version V1.0.0
 */
@Configuration
public class IRuleConfig
{
	@Bean
	public IRule myIRule()
	{
		return new Custom_RandomRule(); // 自定义算法
	}
}

效果图如下:

GitLab 源码地址:

项目源码地址(zip格式的工程包):


好了,关于 Spring Cloud 进阶--Ribbon核心组件IRule的使用及自定义负载均衡算法 就写到这儿了,如果还有什么疑问或遇到什么问题欢迎扫码提问,也可以给我留言哦,我会一一详细的解答的。 
歇后语:“ 共同学习,共同进步 ”,也希望大家多多关注CSND的IT社区。


作       者: 华    仔
联系作者: [email protected]
来        源: CSDN (Chinese Software Developer Network)
原        文: https://blog.csdn.net/Hello_World_QWP/article/details/88185574
版权声明: 本文为博主原创文章,请在转载时务必注明博文出处!

猜你喜欢

转载自blog.csdn.net/Hello_World_QWP/article/details/88185574
今日推荐