负载均衡算法 — 轮询

首发于 樊浩柏科学院

在分布式系统中,为了实现负载均衡,必然会涉及到负载调度算法,如 Nginx 和 RPC 服务发现等场景。常见的负载均衡算法有 轮询源地址 Hash最少连接数,而 轮询 是最简单且应用最广的算法。

预览图

3 种常见的轮询调度算法,分别为 简单轮询加权轮询平滑加权轮询。本文将用如下 4 个服务,来详细说明轮询调度过程。

服务实例 权重值
192.168.10.1:2202 1
192.168.10.2:2202 2
192.168.10.3:2202 3
192.168.10.4:2202 4

简单轮询

简单轮询是轮询算法中最简单的一种,但由于它不支持配置负载,所以应用较少。

算法描述

假设有 N 台实例 S = {S1, S2, …, Sn},指示变量 currentPos 表示当前选择的实例 ID,初始化为 -1。算法可以描述为:
1、调度到下一个实例;
2、若所有实例已被 调度 过一次,则从头开始调度;
3、每次调度重复步骤 1、2;

调度过程,如下:

请求 currentPos 选中的实例
1 0 192.168.10.1:2202
2 1 192.168.10.2:2202
3 2 192.168.10.3:2202
4 3 192.168.10.4:2202
5 0 192.168.10.1:2202

代码实现

这里使用 PHP 来实现,源码见 fan-haobai/load-balance 部分。

首先,定义一个统一的操作接口,主要有init()next()这 2 个方法。

interface RobinInterface
{
    /**
     * 初始化服务权重
     *
     * @param array $services
     *
     * @return mixed
     */
    public function init(array $services);

    /**
     * 获取一个服务
     *
     * @return mixed
     */
    public function next();

}

然后,根据简单轮询算法思路,实现上述接口:

class Robin implements RobinInterface
{
    private $services = array();

    private $total;

    private $currentPos = -1;

    public function init(array $services)
    {
        $this->services = $services;
        $this->total = count($services);
    }

    public function next()
    {
        // 已调度完一圈,重置currentPos值为第一个实例位置
        $this->currentPos = ($this->currentPos + 1) % $this->total;

        return $this->services[$this->currentPos];
    }

}

其中,total为总实例数量,services为服务实例列表。由于简单轮询不需要配置权重,因此可简单配置为:

$services = [
    '192.168.10.1:2202',
    '192.168.10.2:2202',
    '192.168.10.3:2202',
    '192.168.10.4:2202',
];

优缺点分析

在实际应用中,同一个服务会部署到不同的硬件环境,会出现性能不同的情况。若直接使用简单轮询调度算法,给每个服务实例相同的负载,那么,必然会出现资源浪费的情况。因此为了避免这种情况,一些人就提出了下面的 加权轮询 算法。

加权轮询

加权轮询算法引入了“权”值,改进了简单轮询算法,可以根据硬件性能配置实例负载的权重,从而达到资源的合理利用。

算法描述

假设有 N 台实例 S = {S1, S2, …, Sn},权重 W = {W1, W2, ..., Wn},指示变量 currentPos 表示当前选择的实例 ID,初始化为 -1;变量 currentWeight 表示当前权重,初始值为 max(S);max(S) 表示 N 台实例的最大权重值,gcd(S) 表示 N 台实例权重的最大公约数。

算法可以描述为:
1、从上一次调度实例起,遍历后面的每个实例;
2、若所有实例已被遍历过一次,则减小 currentWeight 为 currentWeight - gcd(S),并从头开始遍历;若 currentWeight 小于等于 0,则重置为 max(S);
3、直到 遍历的实例的权重大于等于 currentWeight 时结束,此时实例为需调度的实例;
4、每次调度重复步骤 1、2、3;

例如,上述 4 个服务,最大权重 max(S) 为 4,最大公约数 gcd(S) 为 1。其调度过程如下:

请求 currentPos currentWeight 选中的实例
1 3 4 192.168.10.4:2202
2 2 3 192.168.10.3:2202
3 3 3 192.168.10.4:2202
4 1 2 192.168.10.2:2202
... ... ... ....
9 2 1 192.168.10.3:2202
10 3 4 192.168.10.4:2202

代码实现

这里使用 PHP 来实现,源码见 fan-haobai/load-balance 部分。

class WeightedRobin implements RobinInterface
{
    private $services = array();

    private $total;

    private $currentPos = -1;

    private $currentWeight;

    public function init(array $services)
    {
        foreach ($services as $ip => $weight) {
            $this->services[] = [
                'ip'     => $ip,
                'weight' => $weight,
            ];
        }

        $this->total = count($this->services);
    }

    public function next()
    {
        $i = $this->currentPos;
        while (true) {
            $i = ($i + 1) % $this->total;

            // 已全部被遍历完一次
            if (0 === $i) {
                // 减currentWeight
                $this->currentWeight -= $this->getGcd();

                // 赋值currentWeight为0,回归到初始状态
                if ($this->currentWeight <= 0) {
                    $this->currentWeight = $this->getMaxWeight();
                }
            }

            // 直到当前遍历实例的weight大于或等于currentWeight
            if ($this->services[$i]['weight'] >= $this->currentWeight) {
                $this->currentPos = $i;

                return $this->services[$this->currentPos]['ip'];
            }
        }
    }

其中,getMaxWeight()为所有实例的最大权重值;getGcd()为所有实例权重的最大公约数,主要是通过gcd()方法(可用gmp_gcd()函数)求得 2 个数的最大公约数,然后求每一个实例的权重与当前最大公约数的最大公约数。实现如下:

private function getGcd()
{
    $gcd = $this->services[0]['weight'];

    for ($i = 0; $i < $this->total; $i++) {
        $gcd = $this->gcd($gcd, $this->services[$i]['weight']);
    }

    return $gcd;
}

需要注意的是,在配置services服务列表时,需要指定其权重:

$services = [
    '192.168.10.1:2202' => 1,
    '192.168.10.2:2202' => 2,
    '192.168.10.3:2202' => 3,
    '192.168.10.4:2202' => 4,
];

优缺点分析

加权轮询 算法虽然通过配置实例权重,解决了 简单轮询 的资源利用问题,但是它还是存在一个比较明显的 缺陷。例如:

服务实例 S = {a, b, c},权重 W = {5, 1, 1},使用加权轮询调度生成的实例序列为 {a, a, a, a, a, b, c},那么就会存在连续 5 个请求都被调度到实例 a。而实际中,这种不均匀的负载是不被允许的,因为连续请求会突然加重实例 a 的负载,可能会导致严重的事故。

为了解决加权轮询调度不均匀的缺陷,一些人提出了 平滑加权轮询 调度算法,它会生成的更均匀的调度序列 {a, a, b, a, c, a, a}。对于神秘的平滑加权轮询算法,我将在后续文章中详细介绍它的原理和实现。

总结

轮询算法是最简单的调度算法,因为它无需记录当前所有连接的状态,所以它是一种 无状态 的调度算法,这些特性使得它应用较广。

轮询调度算法并不能动态感知每个实例的负载,它完全依赖于我们的工程经验,人为配置权重来实现基本的负载均衡,并不能保证服务的高可用性。若服务的某些实例因其他原因负载突然加重,轮询调度还是会一如既往地分配请求给这个实例,因此可能会形成小面积的宕机,导致服务的局部不可用。

相关文章 »

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