一、先搞懂:啥是EDRS策略下的频繁迁移?为啥会搞出问题?
很多做线上服务的朋友应该都碰到过这种糟心事儿:本来好好跑着的业务,突然就卡了、慢了,甚至还报错,查日志才发现是服务实例被挪来挪去(专业点叫“迁移”),而且这种迁移还不是偶尔一次,是隔三差五就来一次。这背后大概率就跟EDRS策略脱不了关系。
先给大家把EDRS用大白话讲明白,别被名字唬住。EDRS的核心就是“弹性动态伸缩”——简单说就是你的服务集群里,服务器(或者说跑服务的容器、实例)不能闲得慌,也不能累趴下。具体规则一般是两条:第一,要是某台服务器上跑的服务太多、资源占满了(比如CPU飙到90%以上),就得把上面的一部分服务挪到资源空的服务器上;第二,要是某台服务器空了大半天(比如连续几小时CPU都不到10%),就得把上面的服务全挪走,把这台服务器关掉省成本。
那为啥会频繁迁移?举个最常见的例子:你做的是电商大促的实时推荐服务,平时流量少的时候,集群里只开2台服务器,每台跑5个推荐实例,CPU都在20%左右;大促前1小时,流量突然涨了,每台服务器的CPU直接冲到95%,EDRS一看“这台要累垮了”,就触发迁移,把每台的2个实例挪到新开的2台服务器上;结果大促开场才20分钟,因为用户没抢到心仪商品退单,流量又骤降,新的2台服务器CPU直接掉到5%,EDRS又触发迁移,把这2台的实例挪回原来的2台,然后把新服务器关掉。结果这大促前前后后1小时里,推荐服务就被挪了4次,用户刷商品页的时候要么加载半天,要么直接报错——这就是“频繁迁移”搞出来的“业务抖动”和“性能衰减”。
说白了,频繁迁移的本质就是EDRS的规则太“敏感”,稍微有点资源波动就触发动作,结果反而折腾了业务。
二、核心解决思路:给EDRS加“刹车”,别让它瞎动
要解决频繁迁移的问题,核心不是把EDRS关掉(毕竟EDRS是为了省成本、保服务),而是给它加几个“刹车机制”,让它别一有点风吹草动就乱动。具体可以从三个层面入手:给迁移加“冷静期”、给资源阈值加“缓冲带”、给迁移加“白名单”。
2.1 给迁移加“冷静期”:别刚挪完又挪
啥是“冷静期”?就是EDRS触发一次迁移之后,必须等一段时间才能触发下一次,这段时间里不管资源怎么波动,都不能再动。比如我们给EDRS设个30分钟的冷静期,刚才大促的例子里,第一次迁移是大促前1小时,冷静期到30分钟后才结束,那大促开场20分钟的流量骤降,EDRS因为还在冷静期里,就不会触发第二次迁移,直到冷静期结束才会判断要不要动,这样就避免了1小时内挪4次的情况。
冷静期的设置不是随便定的,得根据你的业务特点来:比如实时推荐服务的业务逻辑比较轻,实例启动只要1分钟,那冷静期可以设成10分钟;要是你做的是大数据离线计算服务,实例启动要20分钟,那冷静期就得设成1小时甚至更久。
2.2 给资源阈值加“缓冲带”:别一沾线就触发
EDRS原来的触发规则是“硬阈值”,比如CPU超过90%就触发迁移,低于10%就触发缩容(也就是迁移所有实例后关服务器)。但这种硬阈值太敏感,稍微波动就触发。“缓冲带”就是把硬阈值改成“区间阈值”,比如把CPU的触发规则改成:只有当CPU连续5分钟超过95%,才触发迁移;只有当CPU连续10分钟低于5%,才触发缩容。
为啥要加“连续时间”?因为很多资源波动是临时的,比如某台服务器突然有个用户集中刷商品,CPU瞬间冲到92%,但1分钟后就降下来了,要是没有连续时间的要求,EDRS就会误判,触发没必要的迁移。
2.3 给迁移加“白名单”:重要业务别乱挪
不是所有服务都适合被随便迁移的,比如电商的支付服务、用户登录服务,这些服务一旦迁移,哪怕只卡1秒,都可能导致用户付款失败、登录超时,影响特别大。所以我们可以给EDRS加个“白名单”机制:白名单里的服务,EDRS绝对不能触发迁移,哪怕这台服务器资源满了,也得先挪其他非白名单的服务,实在不够了再扩容新服务器,绝对不能动白名单服务。
白名单的设置也得谨慎,不能啥服务都加,不然EDRS的弹性伸缩作用就没了。一般来说,只有核心交易类、强实时类的服务才加白名单。
三、完整实战示例:用K8s的HPA和VPA结合EDRS规则优化(技术栈:Kubernetes)
光说思路不够,我们来搞个完整的实战示例,用现在最常用的容器编排工具K8s来实现刚才的三个解决思路。先明确技术栈:Kubernetes(简称K8s),我们会用到K8s的两个核心组件:HPA(水平Pod自动伸缩)和VPA(垂直Pod自动伸缩),然后给EDRS加自定义规则。
3.1 先搞懂K8s里的EDRS逻辑
在K8s里,EDRS的逻辑是这样的:HPA负责水平伸缩(也就是加Pod或者减Pod),VPA负责垂直伸缩(也就是给Pod加资源或者减资源),然后K8s的调度器负责把Pod调度到合适的节点(服务器)上,要是节点资源满了,调度器就会触发Pod迁移,把Pod挪到其他节点上。
原来的默认规则是没有冷静期、没有缓冲带、没有白名单的,所以很容易出现频繁迁移的问题。
3.2 给HPA加冷静期和缓冲带
首先我们给HPA加冷静期和缓冲带,HPA的配置里有个参数叫stabilizationWindowSeconds,这个参数就是冷静期的核心,意思是“只有当连续这么多秒内,伸缩需求没有变化,才会触发伸缩”。还有个参数叫behavior,可以设置缓冲带的阈值。
我们来写个HPA的配置文件,针对电商的实时推荐服务:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: recommend-service-hpa
namespace: online-service
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: recommend-service
minReplicas: 2 # 最小Pod数
maxReplicas: 10 # 最大Pod数
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 80 # 目标CPU利用率80%
behavior:
scaleUp: # 扩容行为
stabilizationWindowSeconds: 300 # 冷静期:连续5分钟(300秒)内需要扩容,才触发
policies:
- type: Percent
value: 50 # 每次最多扩容50%的Pod
periodSeconds: 60
scaleDown: # 缩容行为
stabilizationWindowSeconds: 600 # 冷静期:连续10分钟(600秒)内需要缩容,才触发
policies:
- type: Percent
value: 20 # 每次最多缩容20%的Pod
periodSeconds: 60
这个配置的意思是:实时推荐服务的Pod,CPU利用率超过80%时,必须连续5分钟都需要扩容,才会触发扩容;CPU利用率低于80%时,必须连续10分钟都需要缩容,才会触发缩容,而且每次扩容缩容的幅度都控制在合理范围,不会一下子加很多或者减很多。
3.3 给VPA加缓冲带
VPA负责给Pod调整资源配额,原来的VPA可能会频繁调整资源,导致Pod被重新调度(也就是迁移)。我们给VPA加缓冲带,设置只有当资源需求连续一段时间变化,才会调整。
VPA的配置文件:
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: recommend-service-vpa
namespace: online-service
spec:
targetRef:
apiVersion: "apps/v1"
kind: Deployment
name: recommend-service
updatePolicy:
updateMode: "Auto"
resourcePolicy:
containerPolicies:
- containerName: recommend-service-container
minAllowed:
cpu: "100m" # 最小CPU配额100毫核
memory: "128Mi" # 最小内存128兆
maxAllowed:
cpu: "500m" # 最大CPU配额500毫核
memory: "512Mi" # 最大内存512兆
controlledResources: ["cpu", "memory"]
# 加缓冲带:只有当资源需求变化超过20%,才会调整
scaleDownStabilizationWindowSeconds: 600
scaleUpStabilizationWindowSeconds: 300
这个配置的意思是:实时推荐服务的Pod,只有当CPU或者内存的需求连续10分钟变化超过20%,才会调整资源配额,避免频繁调整导致迁移。
3.4 给K8s调度器加白名单
最后我们给K8s调度器加白名单,让核心服务不会被随便迁移。K8s里可以用nodeSelector和affinity来实现,我们给核心服务的Deployment加个标签,然后让调度器优先把核心服务调度到专门的节点上,不会因为资源满了就把核心服务挪走。
比如电商的支付服务的Deployment配置:
apiVersion: apps/v1
kind: Deployment
metadata:
name: payment-service
namespace: online-service
spec:
replicas: 2
selector:
matchLabels:
app: payment-service
template:
metadata:
labels:
app: payment-service
core-service: "true" # 核心服务标签
spec:
containers:
- name: payment-service-container
image: payment-service:v1.0
resources:
requests:
cpu: "200m"
memory: "256Mi"
limits:
cpu: "400m"
memory: "512Mi"
# 节点亲和性:优先调度到打了core-node标签的节点上
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: core-node
operator: In
values:
- "true"
然后我们给专门的核心节点打标签:
# 给节点node-01打core-node=true的标签
kubectl label nodes node-01 core-node=true
这样配置之后,支付服务的Pod只会被调度到打了core-node标签的节点上,这些节点的资源配额会提前预留足够的,不会因为其他服务的资源需求就把支付服务挪走,实现了白名单的效果。
四、应用场景、优缺点、注意事项
4.1 应用场景
这套优化方案几乎适用于所有用EDRS做弹性伸缩的服务,尤其是以下几个场景:
- 电商、直播等流量波动大的行业:大促、开播前流量骤增,开播后流量骤降,很容易触发频繁迁移,优化后可以避免业务抖动;
- 核心交易类服务:支付、登录、订单等服务,一旦迁移出现问题,影响极大,优化后可以保证核心服务的稳定性;
- 长连接服务:比如即时通讯、游戏服务,迁移会导致长连接断开,优化后可以减少迁移次数,提升用户体验;
- 大数据服务:比如离线计算、实时数仓,实例启动时间长,频繁迁移会导致计算任务中断,优化后可以保证任务的连续性。
4.2 技术优缺点
优点:
- 成本可控:没有关掉EDRS,还是可以根据流量变化调整资源,不会浪费成本;
- 稳定性提升:通过冷静期、缓冲带、白名单三个机制,大大减少了频繁迁移的次数,避免了业务抖动和性能衰减;
- 灵活适配:可以根据不同业务的特点调整参数,比如实时服务的冷静期短,大数据服务的冷静期长;
- 可扩展性强:这套方案可以和其他监控、告警系统结合,比如当资源波动超过阈值时,先告警,再判断要不要触发迁移,进一步优化。
缺点:
- 参数调整复杂:冷静期、缓冲带的参数需要根据业务的实际情况不断测试调整,一开始可能会走弯路;
- 核心节点资源浪费:白名单的核心节点需要预留足够的资源,平时可能会有一定的资源闲置;
- 可能错过最佳伸缩时机:比如冷静期内流量突然暴涨,EDRS不会触发扩容,可能会导致服务性能下降,需要结合告警系统及时调整。
4.3 注意事项
- 参数测试要充分:上线前一定要在测试环境模拟各种流量场景,测试冷静期、缓冲带的参数是否合理,比如模拟大促的流量骤增骤降,看会不会出现频繁迁移;
- 白名单不能滥用:只有真正的核心服务才加白名单,不然EDRS的弹性伸缩作用就会大打折扣,导致成本上升;
- 结合监控系统:一定要配套监控系统,实时监控资源利用率、迁移次数、业务性能指标,一旦出现异常及时调整参数;
- 定期复盘优化:每隔一段时间复盘一下EDRS的运行情况,比如看最近一个月有多少次迁移是不必要的,然后调整参数,让方案更贴合业务;
- 留好应急预案:比如冷静期内流量暴涨导致服务性能下降,要有应急预案,比如手动扩容,或者临时调整冷静期参数。
五、文章总结
EDRS策略下的频繁迁移,本质上是弹性伸缩规则太敏感,没有考虑业务的实际情况,导致为了调整资源反而折腾了业务。解决这个问题的核心思路是给EDRS加“刹车”,通过冷静期、缓冲带、白名单三个机制,让EDRS的动作更合理,既保证资源的弹性,又避免业务出现抖动和性能衰减。
从实战示例可以看到,这套方案不需要改动EDRS的核心逻辑,只需要在原来的规则基础上增加一些限制条件,就可以大大提升服务的稳定性。而且这套方案的灵活性很强,可以根据不同业务的特点调整参数,适配各种场景。
最后要强调的是,这套方案不是一成不变的,需要结合业务的实际情况不断测试、调整、优化,才能达到最佳的效果。毕竟没有最好的规则,只有最适合自己业务的规则。
评论
围绕“EDRS策略下动态负载均衡频繁触发迁移,如何避免业务抖动与性能衰减”参与讨论