动态熔断策略
在进行熔断时,我们需要根据一个依据来判定当前节点/服务是否应该进行熔断降级,这个依据可以依赖于健康检查模块的自检结果,也可以依赖于负载均衡模块的负载比例,或其他自定义策略。
在tl-ops-manage中,我提供了两种熔断依赖策略,实时健康检查节点状态
,熔断周期内节点负载失败率
实时健康检查节点状态
如果是依赖于实时健康状态策略,如果节点一直处于下线状态,那么熔断后的限流机制会呈献一个平滑缩容的状态。因为节点在触发全熔断后,自动恢复策略不会干预健康检查的状态数据,周期结束后会将服务短暂升级,触发缩容。节点将会在全熔断后自我恢复到半熔断,随之反复,也就是一个单位周期触发缩容一次。直到缩为单个单位大小。
熔断周期内节点负载失败率
如果是依赖于熔断周期内节点负载失败率,那么熔断后的限流机制会呈献一个平滑缩容的状态。因为节点在触发全熔断后,自动恢复策略会干预清除周期内的有效路由数据。节点将会在全熔断后自我恢复到半熔断,随之反复,也就是一个单位周期触发缩容一次。直到缩为单个单位大小。
自动化熔断
假设某个服务在内存溢出时,不断返回504(请求超时),那这个时候如果其他服务存在rpc一直调用此服务,会导致整个系统被拖垮。所以这个时候应该将此服务降级,让其处理更少的请求或不处理请求。
下面我们看回tl-ops-manage实现自动化熔断代码,依然还是用定时任务来实现,主入口如下
# 代码位置 : conf/tl_ops_manage.conf
init_worker_by_lua_block {
tlops:tl_ops_process_init_worker();
}
# 代码位置 : tl_ops_manage.lua
function _M:tl_ops_process_init_worker()
...
-- 启动限流熔断
m_limit_fuse:init();
...
end
进入主入口后,我们可以看到代码和健康检查实现类似,都是启动相应定时器,以及服务配置版本初始化,配置版本号的初始化主要是用于加载最新配置到定时任务中。也用于动态同步配置到worker中
# 代码位置 : limit/fuse/tl_ops_limit_fuse.lua
function _M:init( )
-- 给定配置启动限流熔断检查,支持动态加载已有服务变更配置
local limit_fuse = tl_ops_limit_fuse_check:new(
tl_ops_constant_limit.fuse.options, tl_ops_constant_limit.fuse.service
);
limit_fuse:tl_ops_limit_fuse_start();
-- 启动动态新增配置检测
tl_ops_limit_fuse_check_dynamic_conf.dynamic_conf_add_start()
-- 默认初始化一次version, 启动时读取最新数据
for i = 1, #tl_ops_constant_limit.fuse.options do
local option = tl_ops_constant_limit.fuse.options[i]
local service_name = option.service_name
if service_name then
tl_ops_limit_fuse_check_version.incr_service_version(service_name)
end
end
-- 启动动态检测配置版本
tl_ops_limit_fuse_check_version.incr_service_option_version()
end
接着我们看回主逻辑 tl_ops_limit_fuse_start
,这里的主逻辑前部分和健康检查类似,这里直接讲解定时器中的核心逻辑 tl_ops_limit_fuse_main
,可以看到在锁内执行了两段逻辑,除了对服务进行降级升级操作外,多了一个自动恢复逻辑块 tl_ops_limit_fuse_auto_recover
,也就是靠这个逻辑来做到自动化熔断恢复的。
# 代码位置 : limit/fuse/tl_ops_limit_fuse_check.lua
tl_ops_limit_fuse_main = function( conf )
--同步配置
tl_ops_limit_fuse_check_dynamic_conf.dynamic_conf_change_start( conf )
-- 自动熔断/恢复
if tl_ops_limit_fuse_get_lock( conf ) then
tl_ops_limit_fuse_auto_recover( conf )
tl_ops_limit_fuse_check_nodes( conf )
end
end
我们先看主要的自检逻辑tl_ops_limit_fuse_check_nodes
,和健康检查自检不同,对于熔断的自检我默认用的是熔断周期内节点负载失败率
来衡量节点处于何种状态。
节点熔断
我们可以看到在自检时,是轮询所有节点,获取每个节点的成功负载次数,负载失败次数,并根据得出的失败率和用户设定的节点失败率作对比,如果超过这个阈值,进行节点降级 ,代码里面写的是状态升级,也就是从 0 [正常节点] -> 1 [节点半熔断] -> 2 [节点全熔断]。
# 代码位置 : limit/fuse/tl_ops_limit_fuse_check.lua
tl_ops_limit_fuse_check_nodes = function ( conf )
...
-- node层级
for i = 1, #nodes do
local node_id = i-1
local upgrade = false
-- 路由失败率策略
if mode == tl_ops_constant_limit.mode.balance_fail then
local success_count_key = tl_ops_utils_func:gen_node_key(tl_ops_constant_limit.fuse.cache_key.node_req_succ, service_name, node_id)
local success_count = shared:get(success_count_key)
if not success_count then
success_count = 0
end
local failed_count_key = tl_ops_utils_func:gen_node_key(tl_ops_constant_limit.fuse.cache_key.node_req_fail, service_name, node_id)
local failed_count = shared:get(failed_count_key)
if not failed_count then
failed_count = 0
end
local total_count = success_count + failed_count
if total_count == 0 then
total_count = -1 -- can not be 0
end
-- 超过阈值
if failed_count / total_count >= node_threshold then
upgrade = true
end
end
-- 健康状态策略
if mode == tl_ops_constant_limit.mode.health_state then
local health_state_key = tl_ops_utils_func:gen_node_key(tl_ops_constant_health.cache_key.state, service_name, node_id)
local health_state = shared:get(health_state_key)
-- 节点下线
if not health_state then
upgrade = true
end
end
if upgrade then
upgrade_count = upgrade_count + 1
tlog:dbg("node state upgrade : service_name=",service_name, ",node_name=",nodes[i].name, ",mode=",mode, ",upgrade_count=",upgrade_count)
tl_ops_limit_fuse_node_upgrade( conf, node_id )
else
degrade_count = degrade_count + 1
tlog:dbg("node state degrade : service_name=",service_name, ",node_name=",nodes[i].name, ",mode=",mode, ",degrade_count=",degrade_count)
tl_ops_limit_fuse_node_degrade( conf, node_id )
end
end
...
tlog:dbg("tl_ops_limit_fuse_check_nodes done")
end
服务熔断
在轮询完所有节点后,得出每个节点的状态,如果降级节点超过设定的服务阈值,那么进行服务降级 ,代码里面写的是状态升级,也就是从 0 [正常服务] -> 1 [服务半熔断] -> 2 [服务全熔断]。
# 代码位置 : limit/fuse/tl_ops_limit_fuse_check.lua
tl_ops_limit_fuse_check_nodes = function ( conf )
...
-- service层级
local service_total_count = upgrade_count + degrade_count
if service_total_count == 0 then
service_total_count = -1 -- can not be 0
end
-- 节点状态升级比率超过阈值,对服务进行状态升级
if upgrade_count / service_total_count >= service_threshold then
tl_ops_limit_fuse_service_upgrade( conf )
else
tl_ops_limit_fuse_service_degrade( conf )
end
tlog:dbg("tl_ops_limit_fuse_check_nodes done")
end
自动化恢复
# 代码位置 : limit/fuse/tl_ops_limit_fuse.lua
-- 全熔断自动恢复
tl_ops_limit_fuse_auto_recover = function( conf )
local nodes = conf.nodes
local service_state = conf.state
local mode = conf.mode
local service_name = conf.service_name
-- 服务熔断自动恢复
if service_state == _STATE.LIMIT_FUSE_OPEN then
tl_ops_limit_fuse_service_degrade( conf )
tlog:dbg("tl_ops_limit_fuse_auto_recover service done : service=", service_name, ",state=",service_state)
end
if nodes == nil then
tlog:err("tl_ops_limit_fuse_auto_recover nodes nil")
return
end
-- 节点熔断自动恢复
for i = 1, #nodes do
local node_id = i-1
local node_state = nodes[i].state
if node_state == _STATE.LIMIT_FUSE_OPEN then
-- 路由失败率熔断模式下 : 单个周期内请求次数统计,周期结束清除全熔断的统计值
if mode == tl_ops_constant_limit.mode.balance_fail then
local success_count_key = tl_ops_utils_func:gen_node_key(tl_ops_constant_limit.fuse.cache_key.node_req_succ, service_name, node_id)
shared:set(success_count_key, 0)
local failed_count_key = tl_ops_utils_func:gen_node_key(tl_ops_constant_limit.fuse.cache_key.node_req_fail, service_name, node_id)
shared:set(failed_count_key, 0)
tlog:dbg("tl_ops_limit_fuse_auto_recover reset count done service_name=",service_name,",node_id=",node_id)
end
-- 健康状态模式下 : 自动状态降级
if mode == tl_ops_constant_limit.mode.health_state then
tl_ops_limit_fuse_node_degrade( conf , node_id )
end
end
tlog:dbg("tl_ops_limit_fuse_auto_recover node done : node=", nodes[i].name, ",state=",node_state)
end
end