dpvs學(xué)習(xí)筆記: 8 rs調(diào)度算法

后端 rs 一般有多個,通過一定算法做負(fù)載均衡徒扶。程序初始化時 dp_vs_init 調(diào)用 dp_vs_sched_init 進(jìn)行全局注冊。

int dp_vs_sched_init(void)
{
    INIT_LIST_HEAD(&dp_vs_schedulers);
    rte_rwlock_init(&__dp_vs_sched_lock);
    dp_vs_rr_init();
    dp_vs_wrr_init();
    dp_vs_wlc_init();
    dp_vs_conhash_init();

    return EDPVS_OK;
}

可以看到 dpvs 當(dāng)前支持 rr, wrr, wlc, conhash 四種調(diào)度算法八回。

調(diào)度入口

當(dāng) client 首次請求時酷愧,dp_vs_schedule 選擇 rs驾诈,然后建立連接

dest = svc->scheduler->schedule(svc, mbuf);

這就是調(diào)度入口缠诅,具體使用哪種算法,由程序初始化時乍迄,配置 service 服務(wù)時決定管引。dp_vs_bind_scheduler 調(diào)用 scheduler->init_service 函數(shù)指針初始化。

輪循算法 rr

首先這是最簡單的算法闯两,看下如何實(shí)現(xiàn)的褥伴。首先看 init_service 初始化函數(shù)

static int dp_vs_rr_init_svc(struct dp_vs_service *svc)
{
    svc->sched_data = &svc->dests;
    return EDPVS_OK;
}

很簡單谅将,直接將后端 rs 鏈表 dests 賦給 sched_data. 再看一下 schedule 入口

static struct dp_vs_dest *dp_vs_rr_schedule(struct dp_vs_service *svc,
                        const struct rte_mbuf *mbuf)
{
    struct list_head *p, *q;
    struct dp_vs_dest *dest;

    rte_rwlock_write_lock(&svc->sched_lock);

    p = (struct list_head *)svc->sched_data;
    p = p->next;
    q = p;

    do {
        /* skip list head */
        if (q == &svc->dests) {
            q = q->next;
            continue;
        }

        dest = list_entry(q, struct dp_vs_dest, n_list);
        if (!(dest->flags & DPVS_DEST_F_OVERLOAD) &&
            (dest->flags & DPVS_DEST_F_AVAILABLE) &&
            rte_atomic16_read(&dest->weight) > 0)
            /* HIT */
            goto out;
        q = q->next;
    } while (q != p);
    rte_rwlock_write_unlock(&svc->sched_lock);

    return NULL;

out:
    svc->sched_data = q;
    rte_rwlock_write_unlock(&svc->sched_lock);

    return dest;
}

原理也很明了,遍歷 sched_data 鏈表重慢,找到第一個可用的 dest 后返回饥臂,并保存找到的位置,下次選擇時從這個位置的下一個開始查找似踱。

加權(quán)輪循 wrr

帶權(quán)重的輪循隅熙,首先看 init_service 初始化函數(shù)

static int dp_vs_wrr_init_svc(struct dp_vs_service *svc)
{
    struct dp_vs_wrr_mark *mark;

    /*
     *    Allocate the mark variable for WRR scheduling
     */
    mark = rte_zmalloc("wrr_mark", sizeof(struct dp_vs_wrr_mark), RTE_CACHE_LINE_SIZE);
    if (mark == NULL) {
        return EDPVS_NOMEM;
    }
    mark->cl = &svc->dests;
    mark->cw = 0;
    mark->mw = dp_vs_wrr_max_weight(svc);
    mark->di = dp_vs_wrr_gcd_weight(svc);
    svc->sched_data = mark;

    return EDPVS_OK;
}
struct dp_vs_wrr_mark {
    struct list_head *cl;   /* current list head */
    int cw;         /* current weight */
    int mw;         /* maximum weight */
    int di;         /* decreasing interval */
};

分配 dp_vs_wrr_mark 結(jié)構(gòu)體,初始化賦值給 svc->sched_data. dp_vs_wrr_max_weight 求出后端 rs 權(quán)重最大值核芽,dp_vs_wrr_gcd_weight 求出這些權(quán)重值的最大公約數(shù)囚戚,這個 gcd 用于權(quán)重值增減的步長。比如權(quán)重分別是 100轧简,20驰坊,50,那么 gcd 就是 10哮独,所謂的 decreasing interval 就是 10.

static struct dp_vs_dest *dp_vs_wrr_schedule(struct dp_vs_service *svc,
                         const struct rte_mbuf *mbuf)
{
    struct dp_vs_dest *dest;
    struct dp_vs_wrr_mark *mark = svc->sched_data;
    struct list_head *p;

    /*
     * This loop will always terminate, because mark->cw in (0, max_weight]
     * and at least one server has its weight equal to max_weight.
     */
    rte_rwlock_write_lock(&svc->sched_lock);
    p = mark->cl;
    while (1) {
        if (mark->cl == &svc->dests) {
            /* it is at the head of the destination list */

            if (mark->cl == mark->cl->next) {
                /* no dest entry */
                dest = NULL;
                goto out;
            }

            mark->cl = svc->dests.next;
            mark->cw -= mark->di;
            if (mark->cw <= 0) {
                mark->cw = mark->mw;
                /*
                 * Still zero, which means no available servers.
                 */
                if (mark->cw == 0) {
                    mark->cl = &svc->dests;
                    dest = NULL;
                    goto out;
                }
            }
        } else
            mark->cl = mark->cl->next;

        if (mark->cl != &svc->dests) {
            /* not at the head of the list */
            dest = list_entry(mark->cl, struct dp_vs_dest, n_list);
            if (!(dest->flags & DPVS_DEST_F_OVERLOAD) &&
                (dest->flags & DPVS_DEST_F_AVAILABLE) &&
                rte_atomic16_read(&dest->weight) >= mark->cw) {
                /* got it */
                break;
            }
        }

        if (mark->cl == p && mark->cw == mark->di) {
            /* back to the start, and no dest is found.
               It is only possible when all dests are OVERLOADED */
            dest = NULL;
            goto out;
        }
    }

      out:
    rte_rwlock_write_unlock(&svc->sched_lock);

    return dest;
}
  1. 首次循環(huán) mark->cl = &svc->dests, 也就是隊(duì)首拳芙,cw 減去一個步長 di. 并且將 mark->cl 置為隊(duì)首后的第一個元素 svc->dests.next 然后查看 mark->cl 的權(quán)重如果大于 cw, 那么返回當(dāng)前 mark->cl 做為 rs.
  2. 如果此時 mark->cl 不是隊(duì)首,那么直接 mark->cl = mark->cl->next 查看下一個元素借嗽,是否滿足可用态鳖,并且權(quán)重大于 cw, 符合要求返回 mark->cl 做為 rs
  3. 如果mark->cl 權(quán)重小于 cw, 那么 while 循環(huán),繼續(xù)遍歷下一個恶导。
    這個算法的原理清楚了浆竭,但是 while 循環(huán)的復(fù)雜度暫時不知道,水平有限...

加權(quán)最小連接 wlc

static struct dp_vs_scheduler dp_vs_wlc_scheduler = {
    .name = "wlc",
    .n_list = LIST_HEAD_INIT(dp_vs_wlc_scheduler.n_list),
    .schedule = dp_vs_wlc_schedule,
};

這個調(diào)度算法是沒有 init_service 函數(shù)的惨寿,那么直接看 dp_vs_wlc_schedule

static struct dp_vs_dest *dp_vs_wlc_schedule(struct dp_vs_service *svc,
                   const struct rte_mbuf *mbuf)
{
    struct dp_vs_dest *dest, *least;
    unsigned int loh, doh;

    /*
     * We calculate the load of each dest server as follows:
     *                (dest overhead) / dest->weight
     *
     * The server with weight=0 is quiesced and will not receive any
     * new connections.
     */

    list_for_each_entry(dest, &svc->dests, n_list) {
        if (!(dest->flags & DPVS_DEST_F_OVERLOAD) &&
            (dest->flags & DPVS_DEST_F_AVAILABLE) &&
            rte_atomic16_read(&dest->weight) > 0) {
            least = dest;
            loh = dp_vs_wlc_dest_overhead(least);
            goto nextstage;
        }
    }
    return NULL;

    /*
     *    Find the destination with the least load.
     */
      nextstage:
    list_for_each_entry_continue(dest, &svc->dests, n_list) {
        if (dest->flags & DPVS_DEST_F_OVERLOAD)
            continue;
        doh = dp_vs_wlc_dest_overhead(dest);
        if (loh * rte_atomic16_read(&dest->weight) >
            doh * rte_atomic16_read(&least->weight)) {
            least = dest;
            loh = doh;
        }
    }

    return least;
}

static inline unsigned int dp_vs_wlc_dest_overhead(struct dp_vs_dest *dest)
{
    return (rte_atomic32_read(&dest->actconns) << 8) +
           rte_atomic32_read(&dest->inactconns);
}
  1. 首先是給后端 rs 打分的算法, (dest overhead) / dest->weight邦泄,負(fù)載除以權(quán)重。負(fù)載是由 dp_vs_wlc_dest_overhead 完成計(jì)算裂垦,活躍連接和非活躍連接的加權(quán)統(tǒng)計(jì)顺囊,活躍占比最大。那么這個打分蕉拢,肯定是越小特碳,被選擇的可能性越大
  2. 第一個 list_for_each_entry,找出第一個 rs, 算出打分晕换。第二個遍歷所有 rs, 將打分進(jìn)行對比午乓,最后分最少的被賦值 least, 返回給上游使用。

連接哈希 conhash

這個算法用的好像不多闸准,會將相同 hash 算法的固定分配置某個后端 rs. 先看 init_service

static int dp_vs_conhash_init_svc(struct dp_vs_service *svc)
{
    svc->sched_data = conhash_init(NULL);
    if (!svc->sched_data) {
        RTE_LOG(ERR, SERVICE, "%s: conhash init faild!\n", __func__);
        return EDPVS_NOMEM;
    }
    dp_vs_conhash_assign(svc);

    return EDPVS_OK;
}

這塊 conhash_init 居然用了紅黑樹的實(shí)現(xiàn)益愈,然后調(diào)用 dp_vs_conhash_assign 將后端 rs hash 到這個 rbtree 里.

static int
dp_vs_conhash_assign(struct dp_vs_service *svc)
{
    struct dp_vs_dest *dest;
    struct node_s *p_node;
    int weight = 0;
    char str[40];

    list_for_each_entry(dest, &svc->dests, n_list) {
       weight = rte_atomic16_read(&dest->weight);
       if (weight > 0) {

           p_node = rte_zmalloc("p_node", sizeof(struct node_s), RTE_CACHE_LINE_SIZE);
           if (p_node == NULL) {
                return EDPVS_NOMEM;
            }

           rte_atomic32_inc(&dest->refcnt);
           p_node->data = dest;

           snprintf(str, sizeof(str), "%u%d", dest->addr.in.s_addr, dest->port);

           conhash_set_node(p_node, str, weight*REPLICA);
           conhash_add_node(svc->sched_data, p_node);
        }
    }
    return EDPVS_OK;
}
  1. 只有 weight 大于 0 才是可用的 rs
  2. 生成 node 節(jié)點(diǎn)加入到樹里,這里看節(jié)點(diǎn)分復(fù)制成 weightREPLICA 份,做一致性 hash 用蒸其,建了 權(quán)重160個虛擬節(jié)點(diǎn)
static struct dp_vs_dest *
dp_vs_conhash_schedule(struct dp_vs_service *svc, const struct rte_mbuf *mbuf)
{
    struct dp_vs_dest *dest;

    dest = dp_vs_conhash_get(svc, (struct conhash_s *)svc->sched_data, mbuf);

    if (!dest
        || !(dest->flags & DPVS_DEST_F_AVAILABLE)
        || rte_atomic16_read(&dest->weight) <= 0
        || is_overloaded(dest)) {

        return NULL;
    }
    else
        return dest;
}

schedule 函數(shù)也不難敏释,直接根據(jù) mbuf 調(diào)用 dp_vs_conhash_get, 獲取最近的虛擬節(jié)點(diǎn)即可。

static inline struct dp_vs_dest *
dp_vs_conhash_get(struct dp_vs_service *svc, struct conhash_s *conhash,
                  const struct rte_mbuf *mbuf)
{
    char str[40] = {0};
    uint64_t quic_cid;
    uint32_t sip;
    const struct node_s *node;

    if (svc->flags & DP_VS_SVC_F_QID_HASH) {
        if (svc->proto != IPPROTO_UDP) {
            RTE_LOG(ERR, IPVS, "QUIC cid hash scheduler should only be set in UDP service.\n");
            return NULL;
        }
        /* try to get CID for hash target first, then source IP. */
        if (EDPVS_OK == get_quic_hash_target(mbuf, &quic_cid)) {
            snprintf(str, sizeof(str), "%lu", quic_cid);
        } else if (EDPVS_OK == get_sip_hash_target(mbuf, &sip)) {
            snprintf(str, sizeof(str), "%u", sip);
        } else {
            return NULL;
        }

    } else if (svc->flags & DP_VS_SVC_F_SIP_HASH) {
        if (EDPVS_OK == get_sip_hash_target(mbuf, &sip)) {
            snprintf(str, sizeof(str), "%u", sip);
        } else {
            return NULL;
        }

    } else {
        RTE_LOG(ERR, IPVS, "%s: invalid hash target.\n", __func__);
        return NULL;
    }

    node = conhash_lookup(conhash, str);
    return node == NULL? NULL: node->data;
}

可以看到摸袁,對于 udp 如果如果開啟了 CID钥顽,那么調(diào)用 get_quic_hash_target 生成 hash 值。其它情況一律使用 get_sip_hash_target 即源地址 ip 生成 hash

總結(jié)

這塊比較常見靠汁,也沒什么難度耳鸯,大家看看就好

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市膀曾,隨后出現(xiàn)的幾起案子县爬,更是在濱河造成了極大的恐慌,老刑警劉巖添谊,帶你破解...
    沈念sama閱讀 216,651評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件财喳,死亡現(xiàn)場離奇詭異,居然都是意外死亡斩狱,警方通過查閱死者的電腦和手機(jī)耳高,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來所踊,“玉大人泌枪,你說我怎么就攤上這事★醯海” “怎么了碌燕?”我有些...
    開封第一講書人閱讀 162,931評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長继薛。 經(jīng)常有香客問我修壕,道長,這世上最難降的妖魔是什么遏考? 我笑而不...
    開封第一講書人閱讀 58,218評論 1 292
  • 正文 為了忘掉前任慈鸠,我火速辦了婚禮,結(jié)果婚禮上灌具,老公的妹妹穿的比我還像新娘青团。我一直安慰自己,他們只是感情好咖楣,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,234評論 6 388
  • 文/花漫 我一把揭開白布督笆。 她就那樣靜靜地躺著,像睡著了一般截歉。 火紅的嫁衣襯著肌膚如雪胖腾。 梳的紋絲不亂的頭發(fā)上烟零,一...
    開封第一講書人閱讀 51,198評論 1 299
  • 那天瘪松,我揣著相機(jī)與錄音咸作,去河邊找鬼。 笑死宵睦,一個胖子當(dāng)著我的面吹牛记罚,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播壳嚎,決...
    沈念sama閱讀 40,084評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼桐智,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了烟馅?” 一聲冷哼從身側(cè)響起说庭,我...
    開封第一講書人閱讀 38,926評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎郑趁,沒想到半個月后刊驴,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,341評論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡寡润,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,563評論 2 333
  • 正文 我和宋清朗相戀三年捆憎,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片梭纹。...
    茶點(diǎn)故事閱讀 39,731評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡躲惰,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出变抽,到底是詐尸還是另有隱情础拨,我是刑警寧澤,帶...
    沈念sama閱讀 35,430評論 5 343
  • 正文 年R本政府宣布绍载,位于F島的核電站太伊,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏逛钻。R本人自食惡果不足惜僚焦,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,036評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望曙痘。 院中可真熱鬧芳悲,春花似錦、人聲如沸边坤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽茧痒。三九已至肮韧,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背弄企。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評論 1 269
  • 我被黑心中介騙來泰國打工超燃, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人拘领。 一個月前我還...
    沈念sama閱讀 47,743評論 2 368
  • 正文 我出身青樓意乓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親约素。 傳聞我的和親對象是個殘疾皇子届良,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,629評論 2 354

推薦閱讀更多精彩內(nèi)容