凌晨三点批量掉授权,我花了四小时才搞明白LAC心跳链路是怎么算的



兼容 是对前人努力的尊重 是确保业务平稳过渡的基石 然而 这仅仅是故事的起点


上个月出了个事故,差点被开除。

事情是这样的,某政务云项目,70多台KES实例跑在LAC集中授权模式下,一直风平浪静。结果某天凌晨三点多,值班群里突然炸了------二十多台客户端同时掉授权,数据库虽然没直接挂,但授权状态全标成了offline,业务方那边开始报WARNING日志,告警一个接一个。

我当时睡得正香,被电话叫起来的时候脑子还是糊的。

先说结论:不是授权文件过期,是心跳链路断了

到了现场第一反应是查授权有效期,用 get_license_validdays() 一查,还剩200多天,排除了过期。再查 get_license_info(),授权文件路径、版本信息都没问题。

那就只剩一种可能------心跳断了。

但问题是一个两个断了我能理解,二十多台同时断,这不太正常。

LAC心跳链路到底是怎么工作的

先把基本原理捋清楚,不然后面调参的部分看不懂。

KingbaseLAC(License Access Control)是个C/S架构的授权管理系统,说白了就是:服务端集中管着授权池,客户端(lac_agent)定时向服务端发心跳,服务端通过心跳判断客户端是否还活着。如果连续若干个心跳周期没收到某个客户端的心跳,就把这台机器上的授权标记为offline,然后把授权回收到池子里。

这里面有三个关键参数在控制整个链路的行为:

第一个:lac_interval,客户端校验license的间隔时间,单位是分钟,取值范围5~1440。也就是说lac_agent每隔这么多分钟会跟服务端通信一次,要么上报状态,要么申请新授权。默认值5分钟。

第二个:heart_offline_times,服务端等待客户端心跳的容忍次数,取值范围3~10,默认3。意思是如果服务端连续这么多个心跳周期没收到客户端的消息,就判定掉线。

第三个:receive_timeout,数据接收超时时间,默认5秒。这是服务端在处理单次心跳通信时的socket级超时。

看到没,这里面有个乘法关系------客户端掉线判定时间 = lac_interval × heart_offline_times

默认配置下,5分钟 × 3次 = 15分钟。也就是说一个客户端从"最后一次心跳成功"到"被服务端判定为offline",最多需要15分钟。

那天晚上到底发生了什么

回到事故现场。我先去LAC服务端翻了日志:

bash 复制代码
# 查看LAC服务端日志
tail -500 /home/lac/KingbaseLAC/log/lac_server.log | grep "offline"

日志里密密麻麻全是offline记录,时间戳集中在凌晨3:02到3:17之间。也就是说十五分钟内,二十多台机器被陆续判定掉线。

但我查了这些客户端的lac_agent进程,全都在跑:

bash 复制代码
# 逐台检查lac_agent进程
ps aux | grep lac_agent

进程在,说明不是进程挂了。那问题出在哪?

我又去看了客户端侧的日志:

bash 复制代码
# 客户端侧lac_agent日志
tail -200 /opt/Kingbase/ES/V9/Server/log/lac_agent.log

日志里反复出现一条:connect to lac server timeout, retry after 30s

这时候我才意识到------不是客户端不发了,是发了服务端收不到,或者说收到了但处理不过来。

receive_timeout是个坑

receive_timeout 这个参数,官方文档里写的默认值是5秒,配置在 lac_server.conf 里面。这个参数控制的是服务端在处理单次心跳请求时的等待时间------如果5秒内没把数据收完整,这次通信就超时了。

问题在哪呢?当天凌晨有一波自动化运维任务在跑,大概二十多台机器同时在做全库备份,备份脚本里会调用 get_license_info() 查询授权状态,这个查询走的是数据库本地连接,不会经过LAC。但问题是备份任务同时也在做一些资源巡检,其中一项就是调lac_agent的状态接口------这个接口是lac_agent对外暴露的一个轻量级HTTP端点,巡检脚本去读它。

二十多台机器同时被巡检脚本扫了一遍,lac_agent的CPU占用瞬间飙上去了,再加上备份本身的IO压力,lac_agent的心跳发送就开始延迟。而服务端那边的 receive_timeout 只有5秒,心跳包稍微慢一点就超时丢弃了。

heart_offline_times 默认3次,lac_interval 默认5分钟,三个周期一过,全判掉线。

说白了就是:巡检脚本把lac_agent打满了 → 心跳延迟 → 服务端5秒超时 → 连续3次失败 → 批量offline。

我靠。

怎么修的

短期止血很简单,先把 heart_offline_times 从3调到7,把判定离线的时间窗口从15分钟拉到35分钟:

ini 复制代码
# lac_server.conf
heart_offline_times = 7

然后重启LAC服务:

bash 复制代码
./bin/lac_ctl restart

客户端那边不用改配置,因为 heart_offline_times 是服务端的判定参数。重启完服务之后,已经offline的客户端会在下一个心跳周期(5分钟内)自动重新申请授权,因为 enable_auto_refresh 默认是1,也就是授权失效后自动申请。

大概过了十分钟,服务端日志里陆续出现 client re-registered, license granted 的记录,授权恢复了。

但根本问题没解决------巡检脚本不应该在业务高峰期去扫lac_agent状态。

长期调优方案

事后复盘,我做了几件事:

第一,把lac_interval从5分钟调到15分钟。

5分钟一次心跳说实话太频繁了。对70多台客户端的规模来说,每5分钟全量发一遍心跳,服务端在高峰时段压力不小。调到15分钟完全够用------授权校验的粒度不需要那么细,数据库重启才会校验授权有效期,运行时的心跳主要是保活和状态上报。

ini 复制代码
# lac_agent.conf
lac_interval = 15

改完之后所有客户端都要重启lac_agent:

bash 复制代码
cd /opt/Kingbase/ES/V9/Server/bin
./lac_agent stop
./lac_agent start

第二,把receive_timeout从5调到15。

5秒在正常网络环境下够了,但生产环境哪有那么正常?网络抖动、客户端CPU毛刺、GC暂停......什么情况都有。15秒是个比较安全的值,不会太长导致服务端线程被占满,也不会太短导致误判。

ini 复制代码
# lac_server.conf
receive_timeout = 15

第三,巡检脚本的时间窗口要错开。

这个不是改LAC配置能解决的了,是运维流程的问题。最后改成凌晨5点以后才允许跑巡检,备份任务也做了分批,每批不超过5台。

心跳参数的计算逻辑,再说一遍

怕有人看到这里还是糊涂,我把计算公式再理一遍:

复制代码
客户端最大掉线判定时间 = lac_interval × heart_offline_times

这个时间意味着什么?意味着一个客户端从"最后一次心跳成功"到"被服务端判定为offline",最长要等这么久。在这个窗口内,如果客户端恢复了心跳通信(比如进程重启了、网络恢复了),就不会被判掉线。

所以这个值不能设太小------太小了网络稍微抖一下就批量掉授权,跟我遇到的情况一样。也不能设太大------太大了真挂了的机器要很久才能被识别出来,授权一直占着不释放,别的机器申请不到。

我自己的经验是:

  • 小规模集群(10台以内):lac_interval=5, heart_offline_times=5, 判定窗口25分钟
  • 中等规模(10~50台):lac_interval=10, heart_offline_times=5, 判定窗口50分钟
  • 大规模(50台以上):lac_interval=15, heart_offline_times=7, 判定窗口105分钟

当然这只是参考,具体还得看你的网络质量和客户端机器的稳定性。如果你的机器动不动就CPU 100%,那 heart_offline_times 再大也不够使。

lac_agent的守护方式也有讲究

说到心跳,不得不提lac_agent本身的守护机制。

官方文档里提供了两种启动方式:一种是 lac_agent start 直接跑,会自动写crontab每5分钟尝试拉起一次;另一种是用 lac_agentd.sh 注册成systemd服务。

bash 复制代码
# 方式一:crontab守护
./lac_agent start -l /var/log/lac_agent.log

# 方式二:systemd守护(需要root权限)
su -
./lac_agentd.sh

说实话我一开始用的crontab方式,觉得简单。后来发现一个问题------crontab只管拉起进程,不管进程是不是僵死了。有次lac_agent进程还在,但实际上已经卡在一个网络IO上不动了,crontab一看进程在就不重新拉,结果心跳断了二十分钟才发现。

后来换成了systemd方式,配了个 WatchdogSecRestartSec

ini 复制代码
# /etc/systemd/system/lac_agentd.service
[Unit]
Description=KingbaseLAC Agent Daemon
After=network.target

[Service]
Type=forking
ExecStart=/opt/Kingbase/ES/V9/Server/bin/lac_agent start -n
ExecStop=/opt/Kingbase/ES/V9/Server/bin/lac_agent stop -n
Restart=on-failure
RestartSec=30
WatchdogSec=120

[Install]
WantedBy=multi-user.target

这样如果lac_agent进程挂了或者卡死了,systemd会在30秒后自动重启。比crontab靠谱多了。

不过有个坑要注意------lac_agentd.sh 注册服务需要root权限,而且如果你的KES是跑在kingbase用户下的,得确保systemd服务文件里的 User=kingbase,不然启动后文件权限会乱。

关于enable_auto_refresh这个参数

顺便提一嘴,lac_agent.conf 里有个 enable_auto_refresh 参数,默认值是1。这个参数决定了当授权失效后,lac_agent是否自动向服务端申请新授权。

如果设成0,授权掉了就掉了,lac_agent不会主动去要,你得手动跑:

bash 复制代码
# 手动申请授权
./lac_agent -h 10.10.12.252 -p 11234 -t Ent

生产环境千万不要设成0。除非你有非常完善的监控体系能在授权掉线的第一时间发现并手动处理,否则一旦出事就是大面积的。

ini 复制代码
# lac_agent.conf,建议保持默认
enable_auto_refresh = 1

强制替换授权的注意事项

还有一种情况------授权文件本身没过期,但你想主动换一个(比如换授权类型、换绑定信息),这时候用 -f 参数强制替换:

bash 复制代码
./lac_agent -h 10.10.12.252 -p 11234 -t Ent -f

这个操作不会检查当前授权是否有效,直接向服务端申请新授权并替换。好处是快,坏处是如果服务端那边的授权池已经满了(激活文件里限制的数量用完了),强制申请会失败,而且原来的授权可能已经被回收了,你就两头空了。

所以生产环境用 -f 之前,先在服务端WEB管理界面上确认一下授权池还有没有余量。

最后说两句

LAC这套系统的设计思路其实没问题------C/S架构集中管授权,客户端自动心跳保活,服务端自动回收闲置授权。比传统的一台一台去放license.dat文件强太多了,特别是规模上到几十台上百台的时候,传统方式光是换一轮授权就能折腾一星期。

但默认参数是面向"理想环境"设计的,生产环境哪有那么理想。网络会抖、CPU会飙、进程会僵死、巡检脚本会不听话。你不根据实际规模和网络状况去调这些参数,迟早出事。

lac_intervalheart_offline_timesreceive_timeout,这三个参数看着不起眼,但它们的组合直接决定了你的授权系统是稳如老狗还是凌晨三点给你打电话。

花点时间算清楚你的环境需要多大的心跳窗口,比出了事半夜爬起来排查强一百倍。

相关推荐
易协同低代码1 小时前
通达OA核心类库TD类深度解析
后端
Gopher_HBo1 小时前
Go语言学习笔记(十八)Gin处理Session
后端
谭光志2 小时前
工具塞满上下文窗口怎么办?深度拆解 AI Agent Tool Search 按需加载实现原理
前端·后端·ai编程
她说..2 小时前
Java 默认值设置方式
java·开发语言·后端·springboot
foggyprojects2 小时前
从0开始,一句话启动AI DataAgent
后端·数据分析·ai编程
郡杰2 小时前
一些基础和问题解决
后端
陈随易2 小时前
前端项目部署只要30秒
前端·后端·程序员
YIAN2 小时前
从零手写文件读取 MCP 服务:一文吃透 Model Context Protocol 全链路通信原理
前端·后端·mcp
Imchendiana2 小时前
《狂人日记NO.9》— 前后端一把梭,我的全栈实录
前端·后端
学渣超2 小时前
记一次分布式事务数据不一致的排查之旅:从超时到索引,层层剥茧
java·后端·架构