Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

资源访问层 #72

Closed
zhangyachen opened this issue Sep 10, 2016 · 0 comments
Closed

资源访问层 #72

zhangyachen opened this issue Sep 10, 2016 · 0 comments

Comments

@zhangyachen
Copy link
Owner

zhangyachen commented Sep 10, 2016

简介

全称 Resource Access Layer。以 PHP 扩展的方式提供一个客户端,实现了对后端服务端交互的支持,用户在添加相应服务配置后,即可使用 RAL 一站式接口与后端服务进行交互,而不需要关注数据格式处理与协议交互的过程,为后端交互提供简单可依赖的支持。

特点

  • 支持多种交互协议和数据打包格式:RAL将交互过程分成了数据打包(序列化为指定格式的数据)解包(反序列化)和实际交互两步,可以支持一些常用的后端交互协议,并且扩充很方便。目前版本支持 nshead、http 和 fcgi 三种交互协议,string / mcpack1 / mcpack2 / json / form 五种数据打包协议。打包解包、实际交互协议对调用者透明,在文件中配置即可。
  • 性能和简化的追求:高度封装了交互过程,极大地简化上游调用方的开发成本。将 RAL 集成了负载均衡、超时重试、资源定位、配置自动加载等功能,让上游端不需要再关注这些繁琐的通用逻辑,同时 C 实现版本可以在性能方面有更优的表现。
  • 支持资源定位,统一配置,降低运维成本:支持通过 Galileo 和 Webfoot(BNS,Baidu Naming Service)方式获取服务配置,在更新节点配置信息后,触发客户端回调并自动更新服务配置。此外 RAL 也支持传统的 Local (在本地配置文件中直接配置)方式使用资源定位时,RAL 可保证即使 zk 宕机也不会影响服务交互。
  • 支持本机配置健康检查和超级负载均衡策略:支持多种均衡策略: 一致哈希、随机、轮询。推荐的负载均衡参数, 可以直接使用配置套餐,负载均衡需要用户以参数形式,指定本次交互的 balance key。
  • 服务自动加载:RAL 支持服务配置自动加载,包括 Local、Galileo、Webfoot Naming Service 三种配置方式,修改配置文件或资源定位的数据发生变化时,RAL 会动态加载最新配置加载时支持数据合法行校验,通过后再更新本地备份。

配置文件

Local配置

# 新增服务,请先找到 [..Local] 父节点,在这个父节点底下添加 Local 的服务的配置
[..Local]
[...@Service]
# 服务名
Name : demoService
DefaultPort : 8080
DefaultRetry : 2
# 连接类型,目前仅支持 SHORT,即短连接
DefaultConnectType : SHORT
# 连接超时,单位ms,默认值 200
DefaultConnectTimeOut : 200
# 读超时,单位ms,默认值 500
DefaultReadTimeOut : 500
# 写超时,单位ms,默认值 500
DefaultWriteTimeOut : 500
[....@Server]
IP : 10.10.10.10
# 此处的 Tag 表示这台后端 Server 属于 jx 机房,在负载均衡的时候会优先考虑访问本机房的后端
Tag: jx
[....@Server]
IP : 10.10.10.11
# 此处如果不配置 Tag,该后端 Server 对全部机房有效
# Tag: yf
# 此处如果配了 Port, 可覆盖上面的 DefaultPort
# Port :8080
[....@Server]
# 除了 IP,还可以配 Hostname,RAL 会帮你做域名解析
Hostname: cq01-ksarch-rdtest00.vm
# 如果同时配了 IP 和 Hostname,以 IP 为准
# 访问内网服务建议使用 IP 方式
IP : 10.10.10.12
[....Protocol]
# 交互协议名称 支持 http / nshead / pshead / fcgi
Name : http
[....Converter]
# 打包协议名称 支持 form / mcpack1 / mcpack2 / json / string 
Name : mcpack2
[....SuperStrategy]
# 直接使用配置套餐
# 可选套餐: RANDOM_PACK / CONSISTENCY_PACK / ROUNDROBIN_PACK
Package : RANDOM_PACK
# 若不使用套餐,可参考以下详细配置
# 超级负载均衡均衡策略 Random / Consistency / RoundRobin
# Random 随机选择一个可用的后端
# Consistency一致性hash 根据传入的第四个参数(bk),加server ipport进行计算后hash
# RoundRobin 轮询各个后端
#Balance : Random
#【以下如果配置不完整表示关闭对应的健康状态】
#连接失败率的状态队列,表示记录过去多少次连接状态,不可reload
#ConnectQueueSize : 100
# 控制连接失败率
#ConnectX1 : 10
#ConnectY1 : 95
#ConnectX2 : 40
#ConnectY2 : 5
#读取健康状态,表示记录过去多少秒的读取状态,不可reload
#HealthyQueueSize : 100
# client端读的超时时间,单位ms
#HealthyTimeout : 100
#计算选择概率的时间间隔,以s为单位
#HealthyCheckTime : 3
#选择概率的最小值,0.1表示最小概率为10%
#HealthyMinRate : 0.1
#速度大于这个倍数才能做流量切分,用于不对称节点的负载平衡
#HealthyBackupThreshold : 3
# 是否允许跨机房连接
#CrossRoom : Off
#是否打开全混联, 相当于该服务全部机器接受任意机房tag请求
#Hybrid : Off
打包格式
  • string 格式,指不做任何打包、解包操作,直接将 payload(ral() 的第三个参数,即“有效负载数据”) 发到后端;
  • form 格式,指 www-form-urlencoded,需要传一个 php array 作为 payload,若 payload 有嵌套的 array,内层 array 进行 json_encode 处理。例如:
    $payload = array( 'user' => 'tom', 'love' => array( 'first' => 'lily', 'second' => 'anna' ) );
    如果是 "post" 方式发送的,后端收到的 $_POST 请求是
    array( 'user' => 'tom', 'love' => '{"first":"lily","second":"anna"}` );
  • mcpack 格式,需要遵守 mcpack 打包格式规范。

WebFoot配置

[...@Service]
Name: group.icc-qta.iknow.cn
Rename: Qta
Timeout: 1500

IDC匹配规则

  • 首先决定当前调用方的idc,优先级从高到低:
    • 通过ral_set_idc设置的idc
    • 请求header中的HTTP_X_BD_IDC变量
    • 配置中的idc,在ral2.0中是在ral.ini配置文件,在ral1.x版本是中ral-common.conf配置文件
  • 其次决定后端服务器的idc:
    • 对于local配置,取其Server配置项中的Tag字段,如果没有配置Tag字段,则该server可以用于任何idc
    • 对于bns配置,取其Noah 上的 group 服务组包含的服务单元的后缀。比如group.raltest.ksarch.cn 这个组下面挂着echo.ksarch.cq和 echo.ksarch.tc 这两个服务单元,那么第一个服务单元内的所有机器都属于cq,第二个服务单元内的所有机器都属于tc。虽然配在 group 上,但是 group 本身的名字没有影响。
  • 最后决定匹配的规则,优先级从高到低:
    • 如果服务的local或bns配置中打开了Hybrid全混联开关 ,则会无视idc匹配,所有的后端服务器都会访问
    • 如果服务bns配置中配置了idc_map,则会按idc_map的规则(详见IDC MAPPING)来匹配
    • 如果前面配置都不开启,则只选择服务器idc与当前调用方idc相同的后端服务器来访问

IDC MAPPING

只支持配在 Webfoot 的“group”(服务组)节点,配在服务单元里不起作用。它提供了一种机房间切流量的方案。
如果服务组的配置中 idc_map 字段存在,且 Hybrid (全混连)关闭的情况下,会启用 idc_map,因为全混连会让所有的后端机器都可见。如果 idc_map 字段不存在,则请求时,只选择与前端同机房的后端去交互。

Webfoot 服务,在 Noah 上的 group 节点上可以有这样的配置:

"idc_map": {
    "jx": {
        "prefer": "cq",
    },
    "hz": {
        "prefer": ["self", "tc", "cq", "ai"]
        "backup": "tc"
    },
    "tc": {
        "prefer": "all"
    },
    "default": {
        "prefer": ["self", "jx"]
    }
}

在本例中,idc_map 配置的第一条 map,(key 中的)"jx" 表示调用者的机器的机房,(prefer里的)"cq" 表示 Noah 上的 group 服务组包含的服务单元的后缀,即所有被调用者的机器的机房。也就是说,如果 group.raltest.ksarch.cn 这个组下面挂着 echo.ksarch.cq 和 echo.ksarch.tc 这两个服务单元,那么第一个服务单元内的所有机器都属于 cq,第二个服务单元内的所有机器都属于 tc。虽然配在 group 上,但是 group 本身的名字,后缀是 all 呢,还是 cn,对 RAL 均没有影响。

整个第一条 map 的意思是,将来自 jx 机房的请求,引流到 cq 机房的后端。

第二条 map 规则中,prefer 还可以支持数组,请注意,数组需要用方括号括起来。idc_map 大小写敏感,机房名字用小写。prefer 中的 "self" 关键字,会被替换成 key 中的 idc。目前的实现,"prefer" 字段必须,"backup" 字段可选(2.0.10.0以后版本支持)。

第三条规则中,"all" 关键字则会对本次请求临时打开全混连,来自 "tc" 的流量,会按负载均衡规则分发到后端所有的机器。

第四条规则中,key 是 "default",意思是所有没有在上面出现过的 IDC 都会走这条 map 规则,map 到 "jx" 机房和自身。

IDC Mapping 的功能影响 ral()、ral_multi()、ral_get_service() 三个接口。

负载均衡的过程

负载均衡需要解决的问题

  • 如何合理利用后端资源,避免资源浪费
  • 如何提高服务吞吐量,降低响应时间
  • 特定应用场景的负载均衡,例如有cache的服务,如何保证其命中率
  • 有部分实例挂掉时,对外如何保障服务稳定性
  • 前端请求压力过大时,如何对后端进行过载保护

RAL的负载均衡目前支持的功能:

  • 均衡策略,确定机器选择的优先级
  • 根据连接健康状态选择
  • 根据读取健康状态选择
  • 根据机器的处理能力选择

均衡策略

对每一次负载均衡处理,均衡过程首先会对一个服务多个实例的优先级进行排序,一次请求的均衡过程如下:
image

每次请求的均衡过程会对后端实例进行打分,来决定本次请求优先级顺序。服务实例选择不会立刻选择最高优先级,随后的健康检查和流量切分等过程还会对其进行淘汰,最终在符合要求的实例中按优先级进行选择。

下面将分别对RAL目前的3种均衡策略一一介绍。

随机

随机(Random)是最传统的均衡方式,每个服务实例得分通过随机的方式获取。
这种方式服务请求会比较均匀的分配到不同的实例上,以保证后端机器资源使用比较均匀,避免某台机器压力过大而导致服务不稳定。这种方式也是最常使用的一种均衡策略。

一致性HASH

一致性hash(consistency)的均衡策略常用于存在cache的服务,对于这类服务均衡时需考虑其cache命中率,特定的请求应尽可能的映射到同一台机器。
使用Hash的方式做均衡时,还要考虑以下2点:

  • Hash均衡的均匀性:对于不同的请求,应当尽可能均匀的映射到不同的hash缓冲区,这一点与hash函数相关,目前选取md5作为hash函数。
  • Hash均衡的单调性:在hash时,需要保证当hash的缓冲区(即服务实例)变化时,仅影响相邻缓冲区,避免原有多个hash缓冲区都被重新映射到新的缓冲区。 这一点是考虑到添加和摘除服务实例后,避免出现大量请求cache失效的问题,大范围cache失效可能导致短时间服务性能有较大降低乃至拒绝服务。
    RAL负载均衡使用一致性hash,通过上层指定的balance_key进行均衡,对于给定的balance_key,将与各服务实例结合一并进行md5签名,并对结果排序,以确定优先级顺序。

这里使用md5做hash提高均衡性,避免多个balance_key在hash后的结果冲突,使不同balance_key的请求后尽可能落到不同的实例。当然,对于相同的balance_key则会落在同一实例。 另外,均衡时会对每个服务实例的得分进行排序来以保证单调性,以避免添加和摘除服务实例后出现大量请求cache失效的问题。添加和删除实例时,仅影响之前落到相邻的实例的请求,而不会影响到其他请求。

排序时,每个机器实例得分具体计算方式如下:md5(server_ip + server_port + balance_key) 即每个server的ip和port加上balance_key,三者做字符串拼接后再做md5签名。

轮询

轮询也是一种比较传统的均衡方式。这种均衡方式,可以确保对后端服务实例的访问在任何时刻都是非常均匀的。 考虑到还有健康检查都处理流程,轮询策略并不会直接确定本次请求的实例,只保本次轮询到的实例具有最高优先级。在计算优先级得分时,是按照以下方式进行:
均衡时确保每次轮询到的server实例得分最高,其它机器则退化为随机取模的方式计算均衡得分:
本期轮询到server实例得分:N + 1
其它实例得分:rand() % N
最后根据得分对均衡的优先级进行排序,以保证轮询到的机器具有最高优先级,其他实例优先级随机排序。

根据连接健康状态选择

在经过均衡策略处理后,每个服务实例的优先级将按照从高到低的顺序排列,其后将根据连接健康状态进行选择。这里要做的其实就是对连接健康状态异常的机器进行淘汰。
对每个实例的连接状态都需要进行记录,以判断该机器的健康状态,实现时会为每个服务实例都维护一个连接状态的队列,记录最近一段时间的连接情况。当然这个队列的长度是可以进行配置的,也就是ral配置文件中的ConnectQueueSize。每次有连接失败的情况进队,失败计数加一,失败状态出队时,则连接失败次数减一。
image
X轴表示连接失败次数,Y轴表示连接健康状态(100%为正常)

从上图可以看出,队列(长度为ConnectQueueSize)中失败次数与健康状态的关系,在连续多次失败时,一旦队列中的失败次数超过P点的X坐标,健康状态便会降到最低。这里K点及P点的坐标在RAL中都有其对应的配置:
ConnectX1 : 10 (K点X坐标)
ConnectY1 : 95 (K点Y坐标)
ConnectX2 : 40 (P点X坐标)
ConnectY2 : 5 (P点Y坐标)

最后根据连接健康状态进行选择,健康得分就是该实例的选择概率,健康得分越低淘汰的概率也就越大。
另外,需要注意的是,RAL中要使这个队列生效,需要运行在php-cgi的情况下,因为对于CLI方式,每次执行PHP连接健康状态的队列每次都会被重新初始化,从而无法保留状态信息。

根据读取健康状态选择

在根据连接健康状态进行选择后,连接异常的实例已经历过一次淘汰了。
负载均衡并未就此结束,对于一个稳定运行的服务实例,其时处理时间也应当收敛。 基于这点考虑,负载均衡还进一步支持根据服务访问的健康状态进行选择淘汰。 这里需要注意的是, 读取健康状态的判断是建立在服务处理时间收敛的基础上。对于稳定运行时处理时间不收敛的服务,该过程并不适合。
为了记录每个实例处理时间的历史状态,负载均衡会为每个实例都维护一个读取时间的队列,该队列保留过去一段时间内的状态,这里保留多久也是支持配置的,配置项为:HealthyQueueSize。 那么已经知道了最近一段时间内服务读取的时间,该如何进一步计算读取的健康状态呢,这里的思路如下:
image

每隔一段时间(M秒)计算该时间段内读取的平均时间,即上述的recent_avg(M)。将该平均时间与队列中总的平均处理时间进行比较,如果M秒内出现波动且耗时增加,健康状态便会有所降低,而耗时减少健康状态则会提高。但如果服务确实耗时增加且整体处理时间又稳定下来,那么经过一段时间,健康状态又会收敛到正常水平。

上式的间隔时间M和健康超时时间timeout(read)都可在配置文件中指定,对应的配置项分别是:HealthyCheckTime和HealthyTimeout。如果无HealthyTimeout配置则不启用读取健康状态选择。 得到健康得分f(healthy)后,选择或淘汰将按如下方式进行(其中R是选择概率的最小值,对应配置项是HealthyMinRate):
image

根据机器处理能力进行选择

在经过了前面两步健康选择和淘汰的过程后,负载均衡会进一步考虑根据机器的处理能力对请求进行分流,即流量切分。 当后端某个实例的性能过低时,应该考虑将流量切分给性能更好机器。至于性能差距在多大时考虑流量切分,可以通过配置HealthyBackupThreshold来指定流量切分阈值。 那么首先面临的问题是:该如何定义某个机器实例的性能呢?
可以通过该实例的平均处理时间来判断,即1/avgtime。 在获取机器实例性能的基础上,就可以对其进行流量切分,尽可能的将请求分配到性能更好的实例去处理,切分思路如下:

  • 首先:高于平均水平的机器并不需要进行流量切分。
  • 而对性能低于平均水平的实例,需要考虑对其进行流量切分,性能越低的后端实例被切流量的概率也就越大,切流量时会以配置的切分阈值为标准来查找更优的实例,当然如果找不到符合要求的后端实例就不作切分。最终切流量机器选择时,仍然会按照均衡策略给定优先级顺序考虑。

这里可以看到,即使有一些服务实例处理时间较长或网络延迟较大,通过负载均衡的切流量就可以避免请求延迟较大的服务实例,使服务整体对外的性能较优。

最终选择

在负载均衡经历了以上三个选择过程后,常规的负载均衡过程就以完成,这时已经能够确定本次交互优先选择的服务实例。
不过除了以上功能,负载均衡还支持进行跨机房访问。 考虑到有时服务会分机房部署,为降低网络延迟及带宽成本会优先请求当前机房的机器。当单边机房不稳定或宕机的情况下,为了避免拒绝服务,可以配置尝试跨机房。服务可通过CrossRoom配置开启跨机房。
在后端实例配置了机房且启用跨机房,那么当前机房的实例在经过以上三种状态选择被淘汰时,便可尝试跨机房去请求其他机房的实例。 如果不启用跨机房,便会在经历过以上选择淘汰的结果中,选择最高优先级且未因健康状态被淘汰的那个实例。

@zhangyachen zhangyachen changed the title ral-资源访问层 资源访问层 May 2, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant