负载均衡

本文档主要介绍Pegasus负载均衡的概念、使用和设计。

概念篇

在Pegasus中,负载均衡主要包括以下几个方面的内容:

  1. 如果某个partition分片不满足一主两备,要选择一个机器将缺失的分片补全。这个过程在Pegasus中叫做cure
  2. 当所有的分片都满足一主两备份后,对集群各个replica server上分片的个数做调整,尽量让每个机器上服务的分片数都维持在一个相近的水平上。这个过程在Pegasus中叫做balance
  3. 如果一个replica server上挂载了多个磁盘,并且通过配置文件data_dirs提供给Pegasus使用。replica server要尽量让每个磁盘上分片的数量都维持在一个相近的水平上。

围绕这几点内容,Pegasus引入了一些概念方便描述这些情况:

  1. Partition的健康状况

    Pegasus为Partition定义了几种健康状况:

    • 【fully healthy】: 健康的,完全满足一主两备
    • 【unreadable】: 分片不可读了。指的是分片缺少primary, 但有一个或两个secondary。
    • 【readable but unwritable】: 分片可读但是不可写。指的是只剩下了一个primary,两个secondary副本全部丢失
    • 【readable and writable but unhealthy】: 分片可读可写,但仍旧不健康。指的是三副本里面少了一个secondary
    • 【dead】: partition的所有副本全不可用了,又称之为DDD状态。

pegasus-healthy-status

当通过pegasus shell来查看集群、表以及分片的状态时,会经常看到对分片健康情况的整体统计或单个描述。譬如通过ls -d命令,可以看到各个表处于不同健康状况的partition的个数,包括这些:

  • fully_healthy:完全健康。
  • unhealthy:不完全健康。
  • write_unhealthy:不可写,包括上面的readable but unwritable和dead。
  • read_unhealthy:不可读,包括上面的unreadable和dead。
  1. Meta server的运行level

    meta server的运行level决定了meta server会对整个分布式系统做到何种程度的管理。最常用的运行level包括:

    • blind:在这种运行level之下,meta_server拒绝任何可能会修改元数据状态的操作。一般在做zookeeper迁移的时候会用到这个level。
    • steady:在这种运行level下,meta server只做cure,即只处理unhealthy的partition。
    • lively:在这种运行level下,一旦所有partion都进入了healthy, meta server就会尝试进行balance,来调整各个机器的分片数。

操作篇

观察系统情况

可以通过pegasus的shell客户端来观察系统的Partition情况:

  1. nodes -d

    可以用来观察系统中每个节点的partition个数:

    >>> nodes -d
    address              status              replica_count       primary_count       secondary_count
    10.132.5.1:32801     ALIVE               54                  18                  36
    10.132.5.2:32801     ALIVE               54                  18                  36
    10.132.5.3:32801     ALIVE               54                  18                  36
    10.132.5.5:32801     ALIVE               54                  18                  36
    

    如果节点间的partition个数分布差异太大,可以采用”set_meta_level lively”的命令来进行调整。

  2. app -d

    可以用来某张表的所有partition的分布情况:可以观察到某个具体partition的组成,也可以汇总每个节点服务该表的partition个数。

    >>> app temp -d
    [Parameters]
    app_name: temp
    detailed: true
       
    [Result]
    app_name          : temp
    app_id            : 14
    partition_count   : 8
    max_replica_count : 3
    details           :
    pidx      ballot    replica_count       primary                                 secondaries
    0         22344     3/3                 10.132.5.2:32801                        [10.132.5.3:32801,10.132.5.5:32801]
    1         20525     3/3                 10.132.5.3:32801                        [10.132.5.2:32801,10.132.5.5:32801]
    2         19539     3/3                 10.132.5.1:32801                        [10.132.5.3:32801,10.132.5.5:32801]
    3         18819     3/3                 10.132.5.5:32801                        [10.132.5.3:32801,10.132.5.1:32801]
    4         18275     3/3                 10.132.5.5:32801                        [10.132.5.2:32801,10.132.5.1:32801]
    5         18079     3/3                 10.132.5.3:32801                        [10.132.5.2:32801,10.132.5.1:32801]
    6         17913     3/3                 10.132.5.2:32801                        [10.132.5.1:32801,10.132.5.5:32801]
    7         17692     3/3                 10.132.5.1:32801                        [10.132.5.3:32801,10.132.5.2:32801]
       
    node                                    primary   secondary total
    10.132.5.1:32801                        2         4         6
    10.132.5.2:32801                        2         4         6
    10.132.5.3:32801                        2         4         6
    10.132.5.5:32801                        2         4         6
                                            8         16        24
       
    fully_healthy_partition_count   : 8
    unhealthy_partition_count       : 0
    write_unhealthy_partition_count : 0
    read_unhealthy_partition_count  : 0
       
    list app temp succeed
    
  3. server_stat

    可以用来观察各个replica server当前的一些监控数据。如果想分析流量的均衡程度,要重点观察各个操作的qps和latency。对于数据值明显异常的节点(和其他节点差异太大),需要排查下partition个数是不是分布不均,或者是不是出现了某个分片的读写热点。

    >>> server_stat -t replica-server
    COMMAND: server-stat
       
    CALL [replica-server] [10.132.5.1:32801] succeed: manual_compact_enqueue_count=0, manual_compact_running_count=0, closing_replica_count=0, disk_available_max_ratio=88, disk_available_min_ratio=78, disk_available_total_ratio=85, disk_capacity_total(MB)=8378920, opening_replica_count=0, serving_replica_count=54, commit_throughput=0, learning_count=0, shared_log_size(MB)=4, memused_res(MB)=2499, memused_virt(MB)=4724, get_p99(ns)=0, get_qps=0, multi_get_p99(ns)=0, multi_get_qps=0, multi_put_p99(ns)=0, multi_put_qps=0, put_p99(ns)=0, put_qps=0
    CALL [replica-server] [10.132.5.2:32801] succeed: manual_compact_enqueue_count=0, manual_compact_running_count=0, closing_replica_count=0, disk_available_max_ratio=88, disk_available_min_ratio=79, disk_available_total_ratio=86, disk_capacity_total(MB)=8378920, opening_replica_count=0, serving_replica_count=54, commit_throughput=0, learning_count=0, shared_log_size(MB)=4, memused_res(MB)=2521, memused_virt(MB)=4733, get_p99(ns)=0, get_qps=0, multi_get_p99(ns)=0, multi_get_qps=0, multi_put_p99(ns)=0, multi_put_qps=0, put_p99(ns)=0, put_qps=0
    CALL [replica-server] [10.132.5.3:32801] succeed: manual_compact_enqueue_count=0, manual_compact_running_count=0, closing_replica_count=0, disk_available_max_ratio=90, disk_available_min_ratio=78, disk_available_total_ratio=85, disk_capacity_total(MB)=8378920, opening_replica_count=0, serving_replica_count=54, commit_throughput=0, learning_count=0, shared_log_size(MB)=4, memused_res(MB)=2489, memused_virt(MB)=4723, get_p99(ns)=0, get_qps=0, multi_get_p99(ns)=0, multi_get_qps=0, multi_put_p99(ns)=0, multi_put_qps=0, put_p99(ns)=0, put_qps=0
    CALL [replica-server] [10.132.5.5:32801] succeed: manual_compact_enqueue_count=0, manual_compact_running_count=0, closing_replica_count=0, disk_available_max_ratio=88, disk_available_min_ratio=82, disk_available_total_ratio=85, disk_capacity_total(MB)=8378920, opening_replica_count=0, serving_replica_count=54, commit_throughput=0, learning_count=0, shared_log_size(MB)=4, memused_res(MB)=2494, memused_virt(MB)=4678, get_p99(ns)=0, get_qps=0, multi_get_p99(ns)=0, multi_get_qps=0, multi_put_p99(ns)=0, multi_put_qps=0, put_p99(ns)=0, put_qps=0
       
    Succeed count: 4
    Failed count: 0
    
  4. app_stat -a

    可以用来观察某个表中,各个partition的统计信息。对于数据值明显异常的分片,要关注是不是出现了分片热点。

    >>> app_stat -a temp
    pidx                 GET   MULTI_GET         PUT   MULTI_PUT         DEL   MULTI_DEL        INCR         CAS        SCAN     expired    filtered    abnormal  storage_mb  file_count
    0                      0           0           0           0           0           0           0           0           0           0           0           0           0           3
    1                      0           0           0           0           0           0           0           0           0           0           0           0           0           1
    2                      0           0           0           0           0           0           0           0           0           0           0           0           0           4
    3                      0           0           0           0           0           0           0           0           0           0           0           0           0           2
    4                      0           0           0           0           0           0           0           0           0           0           0           0           0           3
    5                      0           0           0           0           0           0           0           0           0           0           0           0           0           2
    6                      0           0           0           0           0           0           0           0           0           0           0           0           0           1
    7                      0           0           0           0           0           0           0           0           0           0           0           0           0           3
                           0           0           0           0           0           0           0           0           0           0           0           0           0          19
    

控制集群的负载均衡

Peagsus提供以下几种命令来控制集群的负载均衡:

  1. set_meta_level

    这个命令用来控制meta的运行level,支持以下几种level:

    • freezed:meta server会停止unhealthy partition的cure工作,一般在集群出现较多节点宕机或极其不稳定的情况下使用,另外如果集群的节点数掉到一个数量或者比例以下(通过配置文件min_live_node_count_for_unfreezenode_live_percentage_threshold_for_update控制)就会自动变为freezed,等待人工介入。
    • steady:meta server的默认level, 只做cure,不做balance。
    • lively:meta server会调整分片数,力求均衡。

    可以使用cluster_info或者get_meta_level查看当前集群的运行level。

    关于调整的一些建议:

    • 先用shell的nodes -d命令查看集群是否均衡,当不均衡时再进行调整。通常在以下几种情况发生后,需要开启lively进行调整:
      • 新创建了表,这个时候分片数目可能不均匀。
      • 集群上线、下线、升级了节点,这时候分片数目也可能不均匀。
      • 有节点宕机,一些replica迁移到了别的节点上。
    • 调整过程会触发replica迁移,影响集群可用度,虽然影响不大,但是如果对可用度要求很高,并且调整需求不紧急,建议在低峰时段进行调整。
    • 调整完成后通过set_meta_level steady将level重置为steady状态,避免在平时进行不必要的replica迁移,减少集群抖动。
    • Pegasus还提供了一些精细控制balance的命令,参见负载均衡的高级选项
  2. balance

    balance命令用来手动发送分片迁移的命令。支持的迁移类型:

    • move_pri:把某个partition下的primary和secondary做调换(本质上为两步:1.将from降级;2.将to升级。如果meta server在第1步完成后挂掉,新的meta server不会继续进行第2步,可以视为move_pri命令被中断了)
    • copy_pri:把某个partition下的primary迁移到一个新节点下
    • copy_sec:把某个partition下的secondary迁移到一个新节点下

    注意在使用时,请保证meta server处在steady状态,不然命令无法生效。

    参见以下样例(不相关的输出已经被删去):

    >>> get_meta_level
    current meta level is fl_steady
       
    >>> app temp -d
    pidx      ballot    replica_count       primary                                 secondaries
    0         3         3/3                 10.231.58.233:34803                     [10.231.58.233:34802,10.231.58.233:34801]
       
    list app temp succeed
       
    >>> balance -g 1.0 -p move_pri -f 10.231.58.233:34803 -t 10.231.58.233:34802
    send balance proposal result: ERR_OK
       
    >>> app temp -d
    pidx      ballot    replica_count       primary                                 secondaries
    0         5         3/3                 10.231.58.233:34802                     [10.231.58.233:34801,10.231.58.233:34803]
    list app temp succeed
    
  3. propose

    propose命令用来发送更低原语的分片调整命令,主要有以下几种:

    • assign_primary:把某个partition的primary分配到某台机器上
    • upgrade_to_primary:把某个partition的secondary升级为primary
    • add_secondary: 为某个partition添加secondary
    • upgrade_to_secondary: 把某个partition下的某个learner升级为secondary
    • downgrade_to_secondary:把某个partition下的primary降级为secondary
    • downgrade_to_inactive:把某个partition下的primary/secondary降级为inactive状态
    • remove:移除掉某个partition下的某个副本
    >>> app temp -d
    pidx      ballot    replica_count       primary                                 secondaries                             
    0         5         3/3                 10.231.58.233:34802                     [10.231.58.233:34801,10.231.58.233:34803]
    list app temp succeed
    >>> propose -g 1.0 -p downgrade_to_inactive -t 10.231.58.233:34802 -n 10.231.58.233:34801
    send proposal response: ERR_OK
    >>> app temp -d
    pidx      ballot    replica_count       primary                                 secondaries                             
    0         7         3/3                 10.231.58.233:34802                     [10.231.58.233:34803,10.231.58.233:34801]
    list app temp succeed
    

    在上面的例子中,propose命令希望把10.231.38.233:34801降级。所以需要把这个命令发给partition的primary(10.231.58.233:34802),由它来执行具体某个副本降级的事宜。注意这里体现了pegasus系统的设计理念:meta server负责管理primary , pimary负责管理partition下的其他副本

    上面的例子也许看不出10.231.38.233:34801被降级的痕迹。这是因为系统cure功能的存在,对于unhealthy的partition会迅速修复。你可以通过观察ballot的变化来确认这个命令已经生效了。

    正常情况下,你应该不会需要使用到propose命令。

负载均衡的高级选项

meta server提供了一些更细粒度的参数用来做负载均衡的控制。这些参数是通过remote_command命令来调整的:

通过help查看所有的remote_command

>>> remote_command -l 127.0.0.1:34601 help
COMMAND: help

CALL [user-specified] [127.0.0.1:34601] succeed: help|Help|h|H [command] - display help information
repeat|Repeat|r|R interval_seconds max_count command - execute command periodically
...
meta.lb.assign_delay_ms [num | DEFAULT]
meta.lb.assign_secondary_black_list [<ip:port,ip:port,ip:port>|clear]
meta.lb.balancer_in_turn <true|false>
meta.lb.only_primary_balancer <true|false>
meta.lb.only_move_primary <true|false>
meta.lb.add_secondary_enable_flow_control <true|false>
meta.lb.add_secondary_max_count_for_one_node [num | DEFAULT]
...

Succeed count: 1
Failed count: 0

remote_command是pegasus的一个特性, 允许一个server注册一些命令,然后命令行可以通过rpc调用这些命令。这里我们使用help来访问meta server leader,获取meta server端支持的所有命令。例子中已经略掉了所有不相关的行,只留下以”meta.lb”开头的所有和负载均衡相关的命令。

由于文档和代码的不一致问题,文档里不一定覆盖了当前meta所有的lb控制命令。如果想获取最新的命令列表,请用最新的代码手动执行一下help。

assign_delay_ms

assign_delay_ms用来控制当partition缺少一个secondary时,我们要延时多久才选择一个新的。之所以这么做,是因为一个副本的掉线可能是临时性的,如果不给予一定的缓冲就选择新的secondary, 可能会导致巨量的数据拷贝。

>>> remote_command -t meta-server meta.lb.assign_delay_ms
COMMAND: meta.lb.assign_delay_ms
CALL [meta-server] [127.0.0.1:34601] succeed: 300000
CALL [meta-server] [127.0.0.1:34602] succeed: unknown command 'meta.lb.assign_delay_ms'
CALL [meta-server] [127.0.0.1:34603] succeed: unknown command 'meta.lb.assign_delay_ms'
Succeed count: 3
Failed count: 0
>>> remote_command -t meta-server meta.lb.assign_delay_ms 10
COMMAND: meta.lb.assign_delay_ms 10
CALL [meta-server] [127.0.0.1:34601] succeed: OK
CALL [meta-server] [127.0.0.1:34602] succeed: unknown command 'meta.lb.assign_delay_ms'
CALL [meta-server] [127.0.0.1:34603] succeed: unknown command 'meta.lb.assign_delay_ms'
Succeed count: 3
Failed count: 0
>>> remote_command -t meta-server meta.lb.assign_delay_ms
COMMAND: meta.lb.assign_delay_ms
CALL [meta-server] [127.0.0.1:34601] succeed: 10
CALL [meta-server] [127.0.0.1:34602] succeed: unknown command 'meta.lb.assign_delay_ms'
CALL [meta-server] [127.0.0.1:34603] succeed: unknown command 'meta.lb.assign_delay_ms'
Succeed count: 3
Failed count: 0

如例所示,命令不加参数表示返回当前设定的值。加参数表示期望的新值。

assign_secondary_black_list

该命令用来设定添加secondary的黑名单。这个命令在批量下线集群节点的时候非常有用, 例如:

add secondary时候的流控

在某些时候, 负载均衡的决策算法可能会要求一个机器上要新增不少secondary副本, 如

  • 一个或多个节点的宕机,会导致正常的节点瞬时接受很多的芬片
  • 新节点的加入,可能会有大量的涌入。

但在执行这些增加分片的决策动作时,我们应该避免同一时刻有大量的secondary分片同时添加, 因为

  • 添加secondary副本基本会涉及数据拷贝, 如果量太大可能会对正常读写造成影响
  • 带宽总量是有限的, 如果由分给多个添加分片的任务去分享这些带宽, 那么每个任务执行的时常都会拉长, 从而让系统长期处在一个大量分片都不健康的状态下, 增加了稳定性的风险。

所以, pegasus用两个命令来对流控做支持:

  1. meta.lb.add_secondary_enable_flow_control: 表示是否开启流控的feature。
  2. meta.lb.add_secondary_max_count_for_one_node: 表示对于每个节点,同时执行多少个add_secondary的动作。

精细控制balancer

balancer表示把各节点个数调匀的过程。在目前的pegasus实现中,balancer过程大概可以用四点来概括:

  1. 尽量通过角色互换来做到primary均衡
  2. 如果1做不到让primary变均匀,通过拷数据来做到primary均衡
  3. 在2做完后,通过拷数据做到secondary的均衡
  4. 分别针对每个表做1-2-3的动作

Pegasus提供了一些控制参数给些过程可以提供更精细的控制:

  • meta.lb.only_primary_balancer: 对于每个表,只进行1和2(减少copy secondary带来的数据拷贝)
  • meta.lb.only_move_primary: 对于每个表,primary调节的时候只考虑方法1(减少copy primary带来的数据拷贝)
  • meta.lb.balancer_in_turn:各个表的balancer用串行的方式做,而不是并行进行(用于调试,观察系统行为)

一些命令的使用案例

通过把上面的这些负载均衡原语结合起来,pegasus提供了一些一些脚本来执行滚动升级、节点下线等一些操作,如:

  1. scripts/migrate_node.sh

    这个脚本用来把某个节点上服务的所有primary都赶走

  2. scripts/pegasus_rolling_update.sh

    用来对集群中的节点做在线滚动升级

  3. scripts/pegasus_offline_node_list.sh

    用来做一批节点的下线

不过有部分脚本的逻辑依赖小米的minos部署系统。这里希望大家可以帮助pegasus, 可以支持更多的部署系统。

设计篇

待补充。

Copyright © 2023 The Apache Software Foundation. Licensed under the Apache License, Version 2.0.

Apache Pegasus is an effort undergoing incubation at The Apache Software Foundation (ASF), sponsored by the Apache Incubator. Incubation is required of all newly accepted projects until a further review indicates that the infrastructure, communications, and decision making process have stabilized in a manner consistent with other successful ASF projects. While incubation status is not necessarily a reflection of the completeness or stability of the code, it does indicate that the project has yet to be fully endorsed by the ASF.

Apache Pegasus, Pegasus, Apache, the Apache feather logo, and the Apache Pegasus project logo are either registered trademarks or trademarks of The Apache Software Foundation in the United States and other countries.