定制设计第三节:Ribbon负载均衡Rule详解

1、定制设计负载均衡流程

1.orde-service定制设计发起请求经过

2.Ribbon到-server定制设计中拉取服务列表

3.Ribbon定制设计负载均衡选择服务

4.定制设计然后修改请求url,定制设计将服务名替换为真正的url定制设计地址发起请求。

2、Ribbon定制设计负载均衡实现

public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {	@Override	public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,			final ClientHttpRequestExecution execution) throws IOException {		final URI originalUri = request.getURI();		String serviceName = originalUri.getHost();		Assert.state(serviceName != null,				"Request URI does not contain a valid hostname: " + originalUri);		return this.loadBalancer.execute(serviceName,				this.requestFactory.createRequest(request, body, execution));	}}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

LoadBalancerInterceptor实现了ClientHttpRequestInterceptor接口,定制设计这个接口会拦截客户端的http请求。

定制设计所以客户端请求先会经过intercept方法,定制设计从代码看出,定制设计获取服务名称,然后调用loadBalancer的execute方法。

定制设计这里调用的是RibbonLoadBalancerClient定制设计的下面方法:

	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 = new RibbonServer(serviceId, server,				isSecure(server, serviceId),				serverIntrospector(serviceId).getMetadata(server));		return execute(serviceId, ribbonServer, request);	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

定制设计然后经过以下方法,调用rule.choose方法,这里的key是“default”字符串

    public Server chooseServer(Object key) {        if (counter == null) {            counter = createCounter();        }        counter.increment();        if (rule == null) {            return null;        } else {            try {                return rule.choose(key);            } catch (Exception e) {                logger.warn("LoadBalancer [{}]:  Error choosing server for key {}", name, key, e);                return null;            }        }    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

rule定制设计是一个接口,定制设计有很多实现类,定制设计用来定义选择服务的规则,比如随机、等。

3、Ribbon Rule详解

rule定制设计关系图如下:

定制设计从名字我们就可以推断出这些选择器的作用:

内置负载均衡规则类规则描述
RetryRule重试机制的选择逻辑
RoundRobinRule简单轮询服务列表来选择服务器。它是Ribbon默认的负载均衡规则。
WeightedResponseTimeRule为每一个服务器赋予一个权重值。服务器响应时间越长,这个服务器的权重就越小。这个规则会随机选择服务器,这个权重值会影响服务器的选择。
RandomRule随机选择一个可用的服务器。
BestAvailableRule忽略那些短路的服务器,并选择并发数较低的服务器。
AvailabilityFilteringRule对以下两种服务器进行忽略: (1)在默认情况下,这台服务器如果3次连接失败,这台服务器就会被设置为“短路”状态。短路状态将持续30秒,如果再次连接失败,短路的持续时间就会几何级地增加。(2)并发数过高的服务器。如果一个服务器的并发连接数过高,配置了AvailabilityFilteringRule规则的客户端也会将其忽略。并发连接数的上限,可以由客户端的..ActiveConnectionsLimit属性进行配置。
ZoneAvoidanceRule以区域可用的服务器为基础进行服务器的选择。使用Zone对服务器进行分类,这个Zone可以理解为一个机房、一个机架等。而后再对Zone内的多个服务做轮询。

3.1、IRule接口

public interface IRule{    public Server choose(Object key);        public void setLoadBalancer(ILoadBalancer lb);        public ILoadBalancer getLoadBalancer();    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

这个接口只有3个方法,第一个用来拿到服务。剩下两个用来关联LoadBalancer

3.2、RetryRule

public class RetryRule extends AbstractLoadBalancerRule {	IRule subRule = new RoundRobinRule();	long maxRetryMillis = 500;}
  • 1
  • 2
  • 3
  • 4

RetryRule值放出了两个属性,一个是子规则,另一个是最大重试时间。

获取服务的方法choose主要做的事就是,一直循环调用子规则获取服务,如果获取到则返回。

如果获取不到,在超时时间之后,返回null。

3.3、RoundRobinRule

轮循RobinRule也很简单。

private AtomicInteger nextServerCyclicCounter;
  • 1

大概是这样的,每次将AtomicInteger值+1,获取服务列表list,然后和所有的对所有的服务数取余,然后再去list中取出服务返回。

3.4 WeightedResponseTimeRule

别人的设计:

这个的逻辑就是,首先初始化好每个节点的平均响应时间,作为权重,然后响应时间越短,被访问的概率越大。

这个权重的设计很巧妙,这里说明下。

假设某个服务有三个节点,每个节点的相应时间分别是1秒,2秒,3秒,这个类是这样做的:

有一个节点列表:节点1、节点2、节点3

然后有一个时间列表:a[0] = 节点1时间+节点2时间,a[1] = a[0]+节点1+节点3,a[2] = a[1] + 节点1+节点2

结果就是上图的样子,以a[2]算随机数rm,a[0] >= rm那么就是节点1,a[1] >= rm那么就是节点2,a[2] >= rm就是节点3.

思路:

这样想比较难。

我们换个思路,我们把时间画出区域,假如时间越短,概率越小,我们只需要下图这样。

图中各个地方访问概率相同,随机落在1、2、3上的概率分别是1/6 2/6 3/6。正好是时间越短,概率越小。

这个类用是逆思路,如果落在了2、3上,那么作为节点1的概率: 5/6

落在13上作为节点2的概率:4/6

落在12上作为节点3的概率:3/6

这个类上算出的三个节点的比率和正常的比率是一样的:
5 12 : 4 12 : 3 12 = 5 6 : 4 6 : 3 6 \frac{5}{12} : \frac{4}{12} : \frac{3}{12} = \frac{5}{6} : \frac{4}{6} : \frac{3}{6} 125:124:123=65:64:63
感觉真是巧妙。

3.4 RandomRule

protected int chooseRandomInt(int serverCount) {        return ThreadLocalRandom.current().nextInt(serverCount);}
  • 1
  • 2
  • 3

主要逻辑就是获取一个随机数,然后从从服务列表List中拿到服务。

3.5 ClientConfigEnabledRoundRobinRule

public class ClientConfigEnabledRoundRobinRule extends AbstractLoadBalancerRule {    RoundRobinRule roundRobinRule = new RoundRobinRule();    @Override    public void initWithNiwsConfig(IClientConfig clientConfig) {        roundRobinRule = new RoundRobinRule();    }    @Override    public void setLoadBalancer(ILoadBalancer lb) {    	super.setLoadBalancer(lb);    	roundRobinRule.setLoadBalancer(lb);    }       @Override    public Server choose(Object key) {        if (roundRobinRule != null) {            return roundRobinRule.choose(key);        } else {            throw new IllegalArgumentException(                    "This class has not been initialized with the RoundRobinRule class");        }    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

从名字可以看出,客户端配置启用轮询。

没有什么特别地方,给定的默认规则是轮训,主要得看子类的实现。

3.6 BestAvailableRule

    @Override    public Server choose(Object key) {        if (loadBalancerStats == null) {            return super.choose(key);        }        List<Server> serverList = getLoadBalancer().getAllServers();        int minimalConcurrentConnections = Integer.MAX_VALUE;        long currentTime = System.currentTimeMillis();        Server chosen = null;        for (Server server: serverList) {            ServerStats serverStats = loadBalancerStats.getSingleServerStat(server);            if (!serverStats.isCircuitBreakerTripped(currentTime)) {                int concurrentConnections = serverStats.getActiveRequestsCount(currentTime);                if (concurrentConnections < minimalConcurrentConnections) {                    minimalConcurrentConnections = concurrentConnections;                    chosen = server;                }            }        }        if (chosen == null) {            return super.choose(key);        } else {            return chosen;        }    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

主要代码逻辑是,第13行到第16行,选择了请求数最小的一个服务返回。

到这我们就能明白ClientConfigEnabledRoundRobinRule是什么意思了:由客户端决定是使用客户端自己的,还是使用轮询。

3.7 PredicateBasedRule

public abstract class PredicateBasedRule extends ClientConfigEnabledRoundRobinRule {    public abstract AbstractServerPredicate getPredicate();    @Override    public Server choose(Object key) {        ILoadBalancer lb = getLoadBalancer();        Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(lb.getAllServers(), key);        if (server.isPresent()) {            return server.get();        } else {            return null;        }           }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

这是个抽象类,主要是有个断言,需要子类来实现,然后先用子类的断言规则去过滤,最后通过轮训在剩下的服务中选择。

3.8 AvailabilityFilteringRule

    public AvailabilityFilteringRule() {    	super();    	predicate = CompositePredicate.withPredicate(new AvailabilityPredicate(this, null))                .addFallbackPredicate(AbstractServerPredicate.alwaysTrue())                .build();    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

这个子类的断言规则是如下:

    @Override    public boolean apply(@Nullable PredicateKey input) {        LoadBalancerStats stats = getLBStats();        if (stats == null) {            return true;        }        //如果短路或者请求数超出限制,返回false.        return !shouldSkipServer(stats.getSingleServerStat(input.getServer()));    }    //如果短路或者请求数超出限制,返回true.    private boolean shouldSkipServer(ServerStats stats) {                if ((CIRCUIT_BREAKER_FILTERING.get() && stats.isCircuitBreakerTripped())                 || stats.getActiveRequestsCount() >= activeConnectionsLimit.get()) {            return true;        }        return false;    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

1.首先轮训的方式选择服务,如果符合上面的断言,则返回,不符合,继续轮询选择进行断言。

2.超出10次后,调用上面介绍的父类PredicateBasedRule的方法获取。

@Overridepublic Server choose(Object key) {    int count = 0;    Server server = roundRobinRule.choose(key);    while (count++ <= 10) {        if (predicate.apply(new PredicateKey(server))) {            return server;        }        server = roundRobinRule.choose(key);    }    return super.choose(key);}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

3.9 ZoneAvoidanceRule

    private CompositePredicate compositePredicate;        public ZoneAvoidanceRule() {        super();        ZoneAvoidancePredicate zonePredicate = new ZoneAvoidancePredicate(this);        AvailabilityPredicate availabilityPredicate = new AvailabilityPredicate(this);        compositePredicate = createCompositePredicate(zonePredicate, availabilityPredicate);    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

这个类没有choose方法,说明是使用父类的方法,先断言过滤,然后剩下的服务中轮询选择。

这个类添加了2个断言,第一个是地区选择,第二个是3.8所说的断言。

我们看看如何过滤掉不可用的地区:

    //snapshot地区为key,value为每个地区下的节点信息list    //triggeringLoad触发选择,默认值为0.2d    //triggeringBlackoutPercentage:短路百分比,默认值为:0.99999d    public static Set<String> getAvailableZones(            Map<String, ZoneSnapshot> snapshot, double triggeringLoad,            double triggeringBlackoutPercentage) {        if (snapshot.isEmpty()) {            return null;        }        //可用地区集合        Set<String> availableZones = new HashSet<String>(snapshot.keySet());        if (availableZones.size() == 1) {            return availableZones;        }        //最差地区集合        Set<String> worstZones = new HashSet<String>();        //循环地区的过程中存放最大的 地区平均节点请求数         double maxLoadPerServer = 0;        //标志,是否有不可用的地区        boolean limitedZoneAvailability = false;        //循环每个地区        for (Map.Entry<String, ZoneSnapshot> zoneEntry : snapshot.entrySet()) {            String zone = zoneEntry.getKey();//地区名            ZoneSnapshot zoneSnapshot = zoneEntry.getValue();//地区下的节点信息            //节点数            int instanceCount = zoneSnapshot.getInstanceCount();            if (instanceCount == 0) {                //当前地区没有节点,从可用地区集合中移除该地区。                availableZones.remove(zone);                limitedZoneAvailability = true;//标志位设为true.            } else {                //未短路节点的平均请求数                double loadPerServer = zoneSnapshot.getLoadPerServer();                //短路的节点数/所有节点数 >= 0.99999d || 未短路节点的平均请求数 < 0                if (((double) zoneSnapshot.getCircuitTrippedCount())                        / instanceCount >= triggeringBlackoutPercentage                        || loadPerServer < 0) {                    //当前地区不可用,从可用地区中去除                    availableZones.remove(zone);                    limitedZoneAvailability = true;                } else {                    //当前行到49行,就是根据每个地区的未短路节点的平均请求数,选择请求数最大的放入最差地区集合中。                    //如果2个地区很接近,那么都是最差的。                    if (Math.abs(loadPerServer - maxLoadPerServer) < 0.000001d) {                        worstZones.add(zone);                    } else if (loadPerServer > maxLoadPerServer) {                        maxLoadPerServer = loadPerServer;                        worstZones.clear();                        worstZones.add(zone);                    }                }            }        }        //地区中最大的平均请求数 < 0.2 && 没有不可用地区        if (maxLoadPerServer < triggeringLoad && !limitedZoneAvailability) {            return availableZones;        }        //从最差地区中随机去除一个,有多个,也只去除一个。        String zoneToAvoid = randomChooseZone(snapshot, worstZones);        if (zoneToAvoid != null) {            availableZones.remove(zoneToAvoid);        }        return availableZones;    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
网站建设定制开发 软件系统开发定制 定制软件开发 软件开发定制 定制app开发 app开发定制 app开发定制公司 电商商城定制开发 定制小程序开发 定制开发小程序 客户管理系统开发定制 定制网站 定制开发 crm开发定制 开发公司 小程序开发定制 定制软件 收款定制开发 企业网站定制开发 定制化开发 android系统定制开发 定制小程序开发费用 定制设计 专注app软件定制开发 软件开发定制定制 知名网站建设定制 软件定制开发供应商 应用系统定制开发 软件系统定制开发 企业管理系统定制开发 系统定制开发