深入浅出负载均衡算法

1、算法分类

抛开技术细节、各个公司针对实际情况的特定实现,根据算法的期望可以大致分为以下几类:

类型 描述
任务平分类 将接受到的请求平均分发到RS,"平均"不是特指绝对数值的平均,可以是权重平均
Hash类 根据请求的某些关键信息进行hash运算,将相同hash值的请求负载到同一RS。常见有源ip hash,uid hash
响应优先类 根据RS的响应时间调整请求分配
系统负载优先类 根据RS的CPU负载、IO使用率、网卡吞吐量等系统性能指标调整请求分配

2、常见负载均衡算法与优缺点

2.1、轮询

接收到的请求按照顺序轮流分配请求到服务器。轮询是一种最简单的负载均衡策略,不关心服务器实际性能与负载。

优点:

      简单,只依赖服务器与负载均衡系统的连接感知服务器状态

缺点:

1.当服务器系统负载很高,或当服务器出现严重故障(bug)从而无法正常处理请求但未跟负载均衡系统断开连接时,轮询策略依旧会将请求分配到异常的服务器

2.不能根据服务器配置高低来分配请求

简单即是轮询的优点也是缺点。

实现代码:


private AtomicInteger nextServerCounter = new AtomicInteger(0);

/**

* 轮询得到下一服务器下标,不能使用AtomicInteger.incrementAndGet % module,存在溢出风险

* @param modulo  可用服务器数

* @return

*/

public int getNext(int modulo) {

  for (; ; ) {

      int current = nextServerCounter.get();

      int next = (current + 1) % modulo;

      if (nextServerCounter.compareAndSet(current, next)) {

        return next;

      }

  }

}

2.2、 随机

接收到的请求根据随机数分配请求到服务器。优缺点跟轮询类似

实现代码:

protected int chooseRandomInt(int serverCount) {

   return ThreadLocalRandom.current().nextInt(serverCount);

}

2.3、加权轮询

   针对轮询策略无法根据服务器处理能力高低分配请求的缺点,可以使用配置权重的方式来实现。但加权轮询也无法处理服务器应用异常而未与负载均衡系统断开的场景。

实现代码:

public static class ServerWeight {

   private String target;

   private Integer weight;

}

/**服务器列表(带权重)*/

private static List<ServerWeight> serverWeights;

/**上次选择的服务器*/

private int currentIndex;

/**当前调度的权值*/

private int currentWeight;

/**最大权重*/

private int maxWeight;

/**权重的最大公约数*/

private int gcdWeight;

/**服务器数*/

private int serverCount;



private void init(){

  // 初始化服务器权重信息,最大公约数等信息

  ...

}

public int choose() {

   while (true) {

      currentIndex = (currentIndex + 1) % serverCount;

      if (currentIndex == 0) {

         currentWeight = currentWeight - gcdWeight;

         if (currentWeight <= 0) {

            currentWeight = maxWeight;

            if (currentWeight == 0) {

               return -1;

            }

         }

      }

      if (serverWeights.get(currentIndex).getWeight() >= currentWeight) {

         return currentIndex;

      }

   }

}

2.4、 一致性hash算法

根据请求的某些特定信息进行hash运算,将相同hash值分配到同一服务器。

根据uid/session id hash,相同的用户会话会负载到相同的服务器
根据client ip hash,相同的源设备负载到相同的服务器,应用场景如事务性操作
实现代码:

public int choose(Object key, int serverCount) {

   int hashcode = key.hashCode();

   int selectedIndex = Hashing.consistentHash(hashcode, serverCount); // 使用Guava的一致性哈希算法



   return selectedIndex;

}

2.5、响应优先算法

响应优先算法是从客户端角度来选择服务器的算法,优先分配响应速度最快的服务器,通过这种方式让客户端能最快响应。

实现响应优先算法需要感知服务器状态,收集维护响应时间这个维度信息。

复杂点:

1.负载均衡系统需要收集、维护、分析每个服务器每次请求的响应时间。

2.为了减少收集、分析所有请求响应时间的性能消耗,可以使用采样方式来收集,但采访方式会减少准确率,同时也会带来实现复杂度。

3.需要选择统计周期,如5分钟内、30分钟内....这也会带来一定的复杂度,且需要根据实际业务选择统计周期,没有统一最优周期。

2.6、 系统负载优先算法

同响应优先算法一样,本质上系统负载优先算法也是通过感知服务器状态,将请求分配到负载最低的服务器。不同的是系统负载优先是站在服务器角度来选择分配权重,

且关心的是服务器CPU负载、I/O使用率、网卡吞吐量、网络连接数等维度的状态。

不同的负载均衡系统会根据应用场景选择最关注的服务器状态。如CPU密集型系统关注CPU负载,I/O密集型系统关注I/O使用率,LVS关注连接数。

同响应优先算法一样,由于需要感知服务器状态,进行周期统计。需要对服务器和负载均衡系统都进行一定的开发才能实现,带来了较高的复杂度,容易出现隐蔽的bug

或者由于设计不好而成为性能瓶颈。

响应优先算法和系统负载优先算法理论上可以完美解决轮询算法的缺点,因为需要感知服务器的运行状态,但同时代价是复杂度的大幅上升。

2.7、动态加权轮询算法

负载均衡系统还可以引入熔断降级等机制来对服务质量出现问题的服务器进行异常处理。

下面简易代码是动态加权轮询算法的实现,通过分析周期内某台服务器请求的错误次数、成功次数来动态加减服务器权重。

/**服务器列表(带权重)*/

private static List<ServerWeight> serverWeights;

/** 基本加权轮询实现见2.3 */

/** 动态加权逻辑如下 */



/** key:服务器target(ip:port),value:CountLimit*/

private static Map<String, CountLimit> failCountLimitMap;

private static Map<String, CountLimit> successCountLimitMap;



public void whenRequestFail(RequestFailEvent event) {

String target = event.getTarget();

if (failCountLimitMap.containsKey(target)) {

Pair<Boolean, Boolean> pair = failCountLimitMap.get(target).grant();

if (!pair.getLeft() && pair.getRight()) {

ServerWeight expect = null;

for (ServerWeight serverWeight : serverWeights) {

    if (serverWeight.getTarget().equalsIgnoreCase(target)) {

      expect = serverWeight;

       break;

    }

}



if (expect != null) {

Integer toUpdate = expect.getWeight() - degrade;

// TODO 需要考虑新的权重是否<=0,进行特殊处理

expect.setWeight(toUpdate);

}

}

}





public void whenRequestSuccess(RequestSuccessEvent event) {

// 逻辑类似whenRequestFail,省略

...

   Integer toUpdate = expect.getWeight() + upgrade;

    // TODO 需要考虑新的权重是否达到初始权重,进行特殊处理

    expect.setWeight(toUpdate)

}



/**

 * 次数统计,在达到阈值时,一个周期只提醒一次

 */

public static class CountLimit {

   private long startPoint;

   private int count = 0;

   /**

    * 阈值

    */

   private int limit;

   /**

    * 时间间隔

    */

   private long period;

   /**

* 达到阈值通知标志

*/

   private boolean overNotify;

   private final Object lock = new Object();

   /**

    * @param limit    限制次数

    * @param period   时间间隔

    * @param timeUnit 间隔类型

    */

   public CountLimit(int limit, int period, TimeUnit timeUnit) {

      this.startPoint = System.currentTimeMillis();

      this.period = timeUnit.toMillis(period);

      this.limit = limit;

      overNotify = false;

   }



   public boolean grant() {

      long curTime = System.currentTimeMillis();

      synchronized (lock) {

         count++;

         if (count > limit) {

            if (curTime - startPoint > period) {

               startPoint = curTime;

               count = 0;

               overNotify = false;

               return ImmutablePair.of(true, false);

            } else {

               if (!overNotify) {

                   overNotify = true;

                   return ImmutablePair.of(false, true);

                }

              return ImmutablePair.of(false, false);

            }

         } else {

            return ImmutablePair.of(true, false);

         }

      }

   }

}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容