Ribbon负载均衡

目录

Ribbon

Load Blancer (LB)

进程内LB

集中式LB

区别

Ribbon工作步骤

Ribbon 示例

1.pom配置文件

 2. controller

 Ribbon常用负载均衡算法

RandomRule:随机

RoundRobinRule:轮询

AvailabilityFilteringRule

WeightedResponseTimeRule

定时任务

权重计算

 实例选择

RetryRule

BestAvailableRule

ZoneAvoidanceRule

 Ribbon使用

1. 修改order模块

2. 额外创建一个包

3. 创建配置类

4. 主启动类

自定义的负载均衡算法

算法原理

代码示例

1. 添加方法

2. 修改order模块 

3. 自定义接口

4. 接口实现类

5. 修改controller


Ribbon

Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具

简单的说,Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法和服务调用

Load Blancer (LB)

Ribbon客户端组件提供一系列的配置项如连接超时、重试等。简单的说,就是在配置文件中列出Load Blancer(简称LB) 后面的所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询/随机连接等)去连接这些机器。

我们很容易使用Ribbon实现自定义的负载均衡算法。Ribbon目前也进入维护,基本上不准备更新了 。

@LoadBalanced 我们之前学习Eureka 开启这个注解会默认自定义负载均衡

LB负载均衡(Load Balance)是什么 ?

简单的说就是将用户的请求平摊的分配到多个服务商,从而达到系统的HA(高可用)。

常见的负载均衡有软件Nginx、LVS,硬件F5等。

进程内LB

本地负载均衡

将LB逻辑集成到消费方,消费方从服务注册中心获取有哪些地址(注册中心服务器)可用,然后自己再从这些地址中选择出一个合适的服务器

Ribbon就属于进程内LB,它只是一个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址(本地jvm进程进行负载均衡)

集中式LB

服务端负载均衡

在服务的消费方和提供方之间使用独立的LB设施(可以是硬件,如F5,也可以是软件,如Nginx),由该设施负责把访问请求通过某种策略转发至服务的提供方

区别

Ribbon本地负载均衡客户端  VS  Nginx服务端负载均衡区别

Nginx是服务器负载均衡,客户端所有请求都会交给Nginx,然后由Nginx实现转发请求,即负载均衡是由服务端实现的。

Ribbon本地负载均衡,在调用微服务接口时,会在注册中心上获取注册信息服务列表之后缓存到JVM本地,从而在本地实现RPC远程服务调用技术。

Ribbon就是 负载均衡 + RestTemplate

总结:Ribbon其实就是一个软负载均衡的客户端组件,它可以和其他所需请求的客户端结合使用,和eureka结合只是其中的一个实例

Ribbon工作步骤

在工作时分成两步

第一步先选择EurekaServer,它优先选择在同一个区域内负载较少的server.

第二步再根据用户指定的策略,在从server取到的服务注册列表中选择一个地址。

其中Ribbon提供了多种策略:比如轮询、随机和根据响应时间加权。

Ribbon 示例

1.pom配置文件

默认我们使用eureka的新版本时,它默认集成了ribbon:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

这个starter中集成了ribbon了,我们也可以手动引入ribbon。放到order模块中,因为只有order访问pay时需要负载均衡

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
 2. controller
//返回对象为响应体中数据转化成的对象,基本上可以理解为Json
@GetMapping("/consumer/payment/getId/{id}")
public CommonResult<Payment> getId(@PathVariable("id") String id) {
    return restTemplate.getForObject(PAYMENT_URL + "/payment/getPaymentByid/" + id, CommonResult.class);
}

@GetMapping("/consumer/payment/getEntity/{id}")
public CommonResult<Payment> getId1(@PathVariable("id") String id) {
    //返回对象为ResponseEntity对象,包含了响应中的一些重要信息,比如响应头、响应状态码、响应体等
    ResponseEntity<CommonResult> entity = restTemplate.getForEntity(PAYMENT_URL + "/payment/getPaymentByid/" + id, CommonResult.class);
    if (entity.getStatusCode().is2xxSuccessful()) {
        return entity.getBody();//这个ResponseEntity中有判断,这里是判断,状态码是不是2xx,
    } else {
        return new CommonResult<>(444, "操作失败");
    }
}

RestTemplate的: xxxForObject()方法,返回的是响应体中的数据 xxxForEntity()方法.返回的是entity对象,这个对象不仅仅包含响应体数据,还包含响应体信息(状态码等)

执行: http://localhost/consumer/payment/getEntity/31

负载均衡分发到支付模块8001与8002机器上面

 Ribbon常用负载均衡算法

IRule接口,Riboon使用该接口,根据特定算法从所有服务中,选择一个服务,IRule接口有7个实现类,每个实现类代表一个负载均衡算法

  • RandomRule:随机

该策略实现了从服务实例清单中随机选择一个服务实例的功能。

从下面的源码可以看到,该实现类的choose方法传入了一个负载均衡器,并且使用负载均衡器获取对应的可用服务列表和全部服务列表,并通过chooseRandomInt方法获取一个随机数,该随机数作为可用服务列表的索引来获取具体的实例

这里有个问题,选择服务实例时使用的是while获取,正常情况下,每次选择都应该能选择一个实例进行返回,但是如果出现异常导致每一次都获取不到可用的实例,那么如果出现死循环而获取不到服务实例时,则很有可能存在并发的BUG

/**
 * 从负载均衡器中选择一个服务器
 *
 * @param lb 负载均衡器对象
 * @param key 选择服务器的键
 * @return 选定的服务器,如果没有可用服务器则返回null
 */
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) {
            /*
             * 如果没有可用的服务器,直接返回null。
             */
            return null;
        }
        int index = chooseRandomInt(serverCount);  //随机选择一个服务器
        server = upList.get(index);

        if (server == null) {
            /*
             *如果选定的服务器为空,说明服务器列表可能被修剪,这是一个短暂的情况,通过让出线程后重试
             */
            Thread.yield();
            continue;
        }
        if (server.isAlive()) {
            return server;
        }

        // 实际上不应该发生,但必须是短暂的或者是一个bug。
        server = null;
        Thread.yield();
    }
    return server;
}
  • RoundRobinRule:轮询

 该策略实现了按照轮询的方式依次选择每个服务实例的功能。该实现和上述的RandomRule类似,只是获取逻辑不同,该负载均衡策略实现逻辑是直接获取下一个可用实例,如果超过10次没有获取到可用的实例,则返回空且打印异常信息

/**
 * 从负载均衡器中选择一个服务器
 *
 * @param lb 负载均衡器对象
 * @param key 选择服务器的键
 * @return 选定的服务器,如果没有可用服务器则返回null
 */
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); //CAS自增获取下一个服务器索引
            server = allServers.get(nextServerIndex);

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

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

            // Next.
            server = null;
        }

        if (count >= 10) { //从负载均衡器中尝试10次后没有可用的活动服务器warn
            log.warn("No available alive servers after 10 tries from load balancer: "
                    + lb);
        }
        return server;
    }
  • AvailabilityFilteringRule

该策略实现了轮询获取Server并校验Server状态的功能会先过滤掉由于多次访问故障而处于断路器状态的服务,还有并发的连接数量超过阈值的服务,然后对剩余的服务列表按照轮询策略进行访问

public class AvailabilityFilteringRule extends PredicateBasedRule {    

    private AbstractServerPredicate predicate;
    
    public AvailabilityFilteringRule() {
        super();
        predicate = CompositePredicate.withPredicate(new AvailabilityPredicate(this, null))
                .addFallbackPredicate(AbstractServerPredicate.alwaysTrue())
                .build();
    }
    
    
    @Override
    public void initWithNiwsConfig(IClientConfig clientConfig) {
        predicate = CompositePredicate.withPredicate(new AvailabilityPredicate(this, clientConfig))
                    .addFallbackPredicate(AbstractServerPredicate.alwaysTrue())
                    .build();
    }

    @Override
    public Server choose(Object key) {
        int count = 0;
        Server server = roundRobinRule.choose(key);
        while (count++ <= 10) { //轮询10次满足就返回
            if (predicate.apply(new PredicateKey(server))) {
                return server;
            }
            server = roundRobinRule.choose(key);
        }
        // 10次不行走父类的server
        return super.choose(key);
    }

    @Override
    public AbstractServerPredicate getPredicate() {
        return predicate;
    }
}

AvailabilityFilteringRule继承自PredicateBasedRule,从其choose方法可见,其并没有完全使用父类的实现方式。

  • 先轮询获取一个Server
  • 判断该Server是否满足需要
  • 如果满足,直接返回
  • 如果不满足,就继续获取下一个Server
  • 如果一直轮询10次还没有符合要求的Server
  • 那么再使用父类的实现方式

(先获取所有满足需求的Server列表,然后从该Server列表中轮询获取一个Server对象)

如果10次没有返回就走super.choose(key)

PredicateBasedRule继承自ClientConfigEnabledRoundRobinRule,是一个抽象类,它首先使用getPredicate方法获取一个AbstractServerPredicate的实现。而choose方法则是调用AbstractServerPredicate类的chooseRoundRobinAfterFiltering方法获取对应的Server实例并返回

。。。。。。其他等等不用管到最后父类的实现到apply方法,从下述源码可见,apply方法主要是通过shouldSkipServer方法进行判断的,在该方法中,有两个判断维度:是否故障(断路器是否断开)、实例的并发请求数是否大于阈值(int的最大值)

private static final DynamicBooleanProperty CIRCUIT_BREAKER_FILTERING =
            DynamicPropertyFactory.getInstance().getBooleanProperty("niws.loadbalancer.availabilityFilteringRule.filterCircuitTripped", true);

    private static final DynamicIntProperty ACTIVE_CONNECTIONS_LIMIT =
            DynamicPropertyFactory.getInstance().getIntProperty("niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit", Integer.MAX_VALUE);

    private ChainedDynamicProperty.IntProperty activeConnectionsLimit = new ChainedDynamicProperty.IntProperty(ACTIVE_CONNECTIONS_LIMIT);

//判断是否应用谓词过滤器到特定的服务器
   @Override
    public boolean apply(@Nullable PredicateKey input) {
        LoadBalancerStats stats = getLBStats();
        if (stats == null) { //获取负载均衡器的统计信息stats,如果统计信息为null,则返回true
            return true;
        }
        return !shouldSkipServer(stats.getSingleServerStat(input.getServer()));
    }
    
    //判断是否应该跳过该服务器
    private boolean shouldSkipServer(ServerStats stats) {
//如果CIRCUIT_BREAKER_FILTERING为true且服务器的断路器已触发,或者服务器的活动请求数超过了activeConnectionsLimit的限制,则返回true,表示应该跳过该服务器        
        if ((CIRCUIT_BREAKER_FILTERING.get() && stats.isCircuitBreakerTripped()) 
                || stats.getActiveRequestsCount() >= activeConnectionsLimit.get()) {
            return true;
        }
        return false;
    }
  • WeightedResponseTimeRule

该策略继承自RoundRobinRule,增加了根据实例的运行情况来计算权重,并根据权重来挑选实例,以达到更优的分配效果,其核心内容分为三块:定时任务、权重计算、实例选择。

根据平均响应时间计算所有服务的权重,响应时间越快的服务权重越大被选中的概率越大。刚启动时如果统计信息不足,则使用RoundRobinRule(轮询)策略,等统计信息足够,会切换到WeightedResponseTimeRule 

定时任务
@Override
    public void setLoadBalancer(ILoadBalancer lb) {
        super.setLoadBalancer(lb);
        if (lb instanceof BaseLoadBalancer) {
            name = ((BaseLoadBalancer) lb).getName();
        }
        initialize(lb);
    }
    // 默认的定时器间隔为30秒
    public static final int DEFAULT_TIMER_INTERVAL = 30 * 1000;
    // 存储服务器权重任务的定时器间隔
    private int serverWeightTaskTimerInterval = DEFAULT_TIMER_INTERVAL;

    void initialize(ILoadBalancer lb) {        
        if (serverWeightTimer != null) {
            // 如果serverWeightTimer定时器存在则取消之前的定时器
            serverWeightTimer.cancel();
        }
        // 创建一个新的定时器serverWeightTimer
        serverWeightTimer = new Timer("NFLoadBalancer-serverWeightTimer-"
                + name, true);
        // 使用DynamicServerWeightTask任务以serverWeightTaskTimerInterval间隔执行更新服务器权重的操作
        serverWeightTimer.schedule(new DynamicServerWeightTask(), 0,
                serverWeightTaskTimerInterval);
        // do a initial run  执行一次ServerWeight对象的maintainWeights方法,用于初始化服务器权重信息。
        ServerWeight sw = new ServerWeight();
        
        sw.maintainWeights();

        // 在系统关闭时会执行这段代码,会输出日志信息并取消serverWeightTimer定时任务
        Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
            public void run() {
                logger
                        .info("Stopping NFLoadBalancer-serverWeightTimer-"
                                + name);
                serverWeightTimer.cancel();
            }
        }));
    }

    class DynamicServerWeightTask extends TimerTask {
        public void run() {
            ServerWeight serverWeight = new ServerWeight();
            try {
                serverWeight.maintainWeights();
            } catch (Exception e) {
                logger.error("Error running DynamicServerWeightTask for {}", name, e);
            }
        }
    }

从上述源码可见,在设置负载均衡策略对应的负载均衡器时,调用了initialize方法,而该方法创建了一个定时任务来计算权重(最终调用的serverWeight.maintainWeights()方法),每30秒执行一次。

权重计算
// 存储服务器的权重信息
private volatile List<Double> accumulatedWeights = new ArrayList<Double>();

    class ServerWeight {
        // 根据服务器的响应时间动态调整服务器的权重
        public void maintainWeights() {
            ILoadBalancer lb = getLoadBalancer();
            if (lb == null) { // 负载均衡器实例lb
                return;
            }
            // CAS 确保高并发情况 只有一个线程可以执行权重调整任务
            if (!serverWeightAssignmentInProgress.compareAndSet(false,  true))  {
                return; 
            }
            
            try {
                logger.info("Weight adjusting job started");
                AbstractLoadBalancer nlb = (AbstractLoadBalancer) lb;
                LoadBalancerStats stats = nlb.getLoadBalancerStats();
                if (stats == null) {
                    // 没有统计数据,无需操作
                    return;
                }
                double totalResponseTime = 0;
                // find maximal 95% response time
                for (Server server : nlb.getAllServers()) {
                    // 如果缓存中没有数据,会自动加载统计数据
                    ServerStats ss = stats.getSingleServerStat(server);
                    totalResponseTime += ss.getResponseTimeAvg();
                }
                // weight for each server is (sum of responseTime of all servers - responseTime)  
                // 每个服务器的权重为(所有服务器响应时间之和 - 该服务器平均响应时间)
                // so that the longer the response time, the less the weight and the less likely to be chosen
                // so,响应时间越长,权重越小,被选择的可能性也越小
                Double weightSoFar = 0.0;
                
                // create new list and hot swap the reference
                List<Double> finalWeights = new ArrayList<Double>();
                for (Server server : nlb.getAllServers()) {
                    ServerStats ss = stats.getSingleServerStat(server);
                    // 所有服务器响应时间之和 - 该服务器平均响应时间
                    double weight = totalResponseTime - ss.getResponseTimeAvg();
                    weightSoFar += weight;
                    finalWeights.add(weightSoFar);   
                }
                setWeights(finalWeights);
            } catch (Exception e) {
                logger.error("Error calculating server weights", e);
            } finally {
                serverWeightAssignmentInProgress.set(false);
            }

        }
    }

    void setWeights(List<Double> weights) {
        this.accumulatedWeights = weights;
    }

通过源码可见,代码中维护一个用于存储权重的List集合accumulatedWeights,同时,通过maintainWeights方法做了权重计算,该计算主要分为两步,

  1. 第一步,根据LoadBalancerStatus中记录的每个实例的统计信息,累加所有实例的平均响应时间,得到总的响应时间totalResponseTime
  2. 第二步,为负载均衡器中维护的实例清单逐个计算权重(从第一个开始),计算规则为weightSoFar+totalResponseTime-实例的平均响应时间,其中weightSoFar的初始值为0。

  举个例子,如果有ABCD4个实例,他们的平均响应时间是10、20、30、40,那么总的相应时间就是100,那么计算出4个实例的权重分别为:

    A:100 - 10 = 90

    B:90 +(100-20) = 170

    C:170 +(100-30) = 240

    D:240 +(100-40) = 300

  权重区间是左开右闭,但是第一个和最后一个比较特殊,由于在后续选择实例时会用随机数从区间中获取,但是随机数最小值可以是0,但是不会到达随机数的最大值,因此第一个左边的0是闭区间,而最后一个的右侧是开区间,因此这4个实例对应的权重区间即为:

    A:[0,90]

    B:(90,170]

    C:(170,240]

    D:(240,300)

  不难发现,区间的宽度就是总的平均响应时间-实例的平均响应时间,因此实例的平均响应时间越短,那么权重的区间就越大,那么被选中的几率就越大。

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

        while (server == null) {
            // 获取当前权重列表的引用,以防它被其他线程更改
            List<Double> currentWeights = accumulatedWeights;
            if (Thread.interrupted()) {
                return null;
            }
            List<Server> allList = lb.getAllServers();

            int serverCount = allList.size();

            if (serverCount == 0) {
                return null;
            }

            int serverIndex = 0;

            // 最后一个是所有权重的总和
            double maxTotalWeight = currentWeights.size() == 0 ? 0 : currentWeights.get(currentWeights.size() - 1); 
            // 没有服务器被选中且总权重未初始化
            // 回退到使用轮询
            if (maxTotalWeight < 0.001d || serverCount != currentWeights.size()) {
                server =  super.choose(getLoadBalancer(), key);
                if(server == null) {
                    return server;
                }
            } else {
                //生成一个介于0(包括)和maxTotalWeight(不包括)之间的随机权重
                double randomWeight = random.nextDouble() * maxTotalWeight;
                // 根据随机权重选择服务器索引
                int n = 0;
                for (Double d : currentWeights) {
                    if (d >= randomWeight) {
                        serverIndex = n;
                        break;
                    } else {
                        n++;
                    }
                }

                server = allList.get(serverIndex);
            }

            if (server == null) {
                /* 临时情况. */
                Thread.yield();
                continue;
            }

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

            // 下一个
            server = null;
        }
        return server;
    }

   通过上述源码可见,其首先生成了一个   [0,最大权重值) 区间内的随机数,然后循环权重区间,如果该随机数在权限区间内,则就拿当前权重列表的索引去服务实例获取对应的服务。还是以上面的ABCD四个实例来说明,那么随机数就是从  [0,300)  的区间中获取,如果获取的随机数数230, currentWeights.size() = 4

   for (Double d : currentWeights) {
                    if (d >= randomWeight) {
                        serverIndex = n;
                        break;
                    } else {
                        n++;
                    }
                }

240 > 230,n =4,满足条件 那么该随机数在实例D的权重区间内,因此就会选择D实例

  • RetryRule

该策略实现了一个具备重试机制的实力选择功能。重下述源码可以看出,其选择服务实例使用的是轮询选择策略RoundRobinRule,然后在获取不到服务实例的情况下,则反复尝试获取,直到调用时间超过设置的阈值,则返回空。

IRule subRule = new RoundRobinRule(); // 创建一个轮询规则的实例,用于确定服务器选择的顺序
    long maxRetryMillis = 500;  //设置最大重试时间为500毫
/**
 * 从负载均衡器中选择一个服务器
 *
 * @param lb 负载均衡器对象
 * @param key 选择服务器的键
 * @return 选定的服务器,如果没有可用服务器或超过重试时间则返回null
 */

public Server choose(ILoadBalancer lb, Object key) {
        // 获取当前时间和最大重试时间的截止时间。
        long requestTime = System.currentTimeMillis();
        long deadline = requestTime + maxRetryMillis;

        Server answer = null;
        // 通过轮询规则选择一个服务器作为初始服务
        answer = subRule.choose(key);

        // 如果初始服务为null或者服务器不可用,并且当前时间未超过重试截止时间,则进行重试
        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();
        }
        // 如果选择的服务器仍然为null或者不可用,则返回null;否则返回选定的服务器
        if ((answer == null) || (!answer.isAlive())) {
            return null;
        } else {
            return answer;
        }
    }
  • BestAvailableRule

该策略会选出负载最低的实例

 BestAvailableRule继承自ClientConfigEnabledRoundRobinRule,从choose方法看,会循环所有Server实例,过滤掉故障实例并选出负载最低的Server。同时我们可以发现,如果没有选择到Server的话,就会调用父类的choose方法,那么就会使用到上面说的 “通过继承该类,在子类中做一些其他的策略时,如果条件不满足,则会使用父类的策略 (轮询)” 。

public class BestAvailableRule extends ClientConfigEnabledRoundRobinRule { 
// 它继承了轮询规则的基本功能,可以根据负载均衡器中服务器的轮询顺序选择服务器
    private LoadBalancerStats loadBalancerStats; //用于存储负载均衡器的统计信息
    
    @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;
        }
    }

    @Override
    public void setLoadBalancer(ILoadBalancer lb) {
        super.setLoadBalancer(lb);
        if (lb instanceof AbstractLoadBalancer) {
            loadBalancerStats = ((AbstractLoadBalancer) lb).getLoadBalancerStats();            
        }
    }
    
}
  • ZoneAvoidanceRule

复合判断Server所在区域的性能和Server的可用性选择服务器

ZoneAvoidanceRule同样继承自PredicateBasedRule,同时ZoneAvoidanceRule中没有choose方法,说明完全复用了父类中的策略(先过滤所有可用的实例,然后使用轮询从满足需要的实例清单中获取一个Server)。

同时通过ZoneAvoidanceRule的构造函数可见,使用的是CompositePredicate进行的过滤,CompositePredicate的构造函数传入了两个AbstractServerPredicate的子类,分别是主过滤条件ZoneAvoidancePredicate (主过滤条件ZoneAvoidancePredicate可能用于根据服务器所在的区域或可用区来进行过滤和选择)和次过滤条件AvailabilityPredicate(次过滤条件AvailabilityPredicate可能用于根据服务器的可用性状态或其他条件来进一步筛选服务器)

public ZoneAvoidanceRule() {
        super();
        ZoneAvoidancePredicate zonePredicate = new ZoneAvoidancePredicate(this);
        AvailabilityPredicate availabilityPredicate = new AvailabilityPredicate(this);
        compositePredicate = createCompositePredicate(zonePredicate, availabilityPredicate);
    }
    
    private CompositePredicate createCompositePredicate(ZoneAvoidancePredicate p1, AvailabilityPredicate p2) {
        return CompositePredicate.withPredicates(p1, p2)
                             .addFallbackPredicate(p2)
                             .addFallbackPredicate(AbstractServerPredicate.alwaysTrue())
                             .build();
        
    }

 Ribbon使用

这里使用eureka的那一套服务。官方文档明确给出了警告:

这个自定义配置不能放在@ComponentScan所扫描的当前包下以及子包下,否则我们自定义的这个配置类就会被所有的Ribbon客户端所共享,达不到特殊化定制的目的。也就是不能放在主启动类所在的包及子包下

1. 修改order模块
2. 额外创建一个包
@SpringBootApplication
@EnableEurekaClient
@RibbonClient(name = "CLOUD-PAYMENT-SERVICE",configuration = Myrule.class)
public class OrderMain80 {
    public static void main(String[] args) {
        SpringApplication.run(OrderMain80.class, args);
    }
}

3. 创建配置类

指定负载均衡算法 

@Configuration
public class Myrule {
    @Bean
    public IRule myRule(){
        return new RandomRule();//定义为随机
    }
}
4. 主启动类
@SpringBootApplication
@EnableEurekaClient
@RibbonClient(name = "CLOUD-PAYMENT-SERVICE",configuration = Myrule.class)
public class OrderMain80 {
    public static void main(String[] args) {
        SpringApplication.run(OrderMain80.class, args);
    }
}

访问CLOUD_PAYMENT_SERVICE的服务时,使用我们自定义的负载均衡算法

自定义的负载均衡算法

负载均衡算法 :

rest接口请求次数(reqNum) % 服务器集群数量(serverNum) = 实际调用服务器位置下标(index)
每次服务重启后,rest接口请求次数从1开始计数。

算法原理

例如:通过服务名称,可以获取到总服务实例个数为2个:8001与8002

List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");

例如:服务器下标(index)为0,请求由8001服务器处理;
   服务器下标(index)为1,请求由8002服务器处理;

第1次请求,reqNum(1) % serverNum(2) = index(1),调用8001
第2次请求,reqNum(2) % serverNum(2) = index(0),调用8002
第3次请求,reqNum(3) % serverNum(2) = index(1),调用8001
第4次请求,reqNum(4) % serverNum(2) = index(0),调用8002

代码示例

演示ribbon的随机负载均衡

1. 添加方法

给pay模块(8001,8002),的controller方法添加一个方法,返回当前节点端口

 @Value("${server.port}")
 private String serverPort;

@GetMapping("/payment/lb")
  public String  getPayment(){
        return serverPort;
  }
2. 修改order模块 

去掉@LoadBalanced

@Configuration
public class RestTemlateConfig {
    @Bean
    //@LoadBalanced //注释掉
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}
3. 自定义接口
public interface LoadBalance {
    ServiceInstance instance(List<ServiceInstance> serviceInstances);
}
4. 接口实现类
@Component
public class MyLoadBalanceImpl implements LoadBalance {
    private AtomicInteger atomicInteger = new AtomicInteger(0);
	//这个方法是获取下一个要调用服务的id
    public final int getAndIncrement() {
        int current;
        int next;
        do {
            current = this.atomicInteger.get();
            next = current >= 2147483647 ? 0 : current + 1;
        } while (!this.atomicInteger.compareAndSet(current, next));//调用CAS进行自旋锁,每次next+1
        System.out.println("*******第几次访问,next:" + next);
        return next;
    }
@Override
public ServiceInstance instance(List<ServiceInstance> serviceInstances) {
    int size = serviceInstances.size();
    int index = 0;
    if (size > 0) {
       	//getAndIncrement() 拿到id,进行取余得到真正要调用服务的下标
        index = getAndIncrement() % size;
    }
    return serviceInstances.get(index);
}
5. 修改controller
@Resource
private DiscoveryClient discoveryClient;

@Resource
private LoadBalance loadBalance;//自定义类
@GetMapping("/consumer/payment/lb")
    public String getPaymentLB() {
        //拿到指定服务下的所有服务
        List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
        if (instances == null || instances.size() < 0) {
            return null;
        }
        ServiceInstance instance = loadBalance.instance(instances);
        URI uri = instance.getUri();
        return restTemplate.getForObject(uri + "/payment/lb", String.class);
    }

 

  • 16
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值