得物架构面试:怎样保证服务发布过程中流量无损?

打印 上一主题 下一主题

主题 626|帖子 626|积分 1888

哈喽,大家好,我是明智
  本日跟大家讨论一个服务稳固性相关的话题,对于大部门做业务的小同伴来说,很少会被问到这类问题
  不外你如果你盼望面试公司的一些基础部门,例如:基础架构、效能开辟、服务稳固性保障等,就很可能遇到
  笔者在前段时间的面试中就被问到了,这个问题还有一系列的问法,例如:
  

  • 服务发布过程中总是出现5xx怎么办?
  • 怎样实现服务的优雅启停?
  本文重要分为以下几个部门
  

  • 保证流量无损的关键是什么?
  • K8s情况下怎样保证流量无损?
  • 怎样验证?
  • 会有什么消极影响?
     ❝   本文只讨论K8s情况下的怎样做到优雅启停
    流量无损的关键

  发布过程中丧失的流量重要包罗:
  

  • 在启动一个新pod时,尚未完全停当,就接收到了流量,导致服务响应500。例如服务依靠的db、redis还未启动
  • 在克制一个旧pod时,这个pod中仍旧存在正在处理中的请求,请求尚未处理完,pod就被杀死
  • 在pod克制的过程中,仍旧有部门流量被路由到这个pod上
  • pod已经被杀死了,但是网关、注册中心等没有及时更新数据,导致仍旧有请求被路由到一个不存在的实例上
  因此我们要保证流量无损,要做到以下几点
  

  • 每次杀死一个pod前,应该先启动一个新pod。即:滚动发布
  • 启动一个新pod时,必要等待pod完全停当,才气让这个pod接收流量。这必要利用到K8s的三种探针
  • pod在被杀死前,必要先保证流量被摘除干净。这必要利用到K8s提供的preStopHook
  • pod的流量摘除干净后,必要保证pod中正在处理的流量正常响应。这必要服务自身响应kill信息
  K8s情况下怎样保证流量无损

  K8s中的滚动发布

  如果是Deployment ,可以通过设置 Deployment 的 rollingUpdate 计谋来控制滚动更新的行为。
  关键参数

  

  • maxUnavailable:指定在更新过程中,最多可以有多少个 Pod 处于不可用状态。可以设置为绝对数量(如 1)或百分比(如 25%)。
  • maxSurge:指定在更新过程中,最多可以额外创建多少个 Pod。可以设置为绝对数量(如 1)或百分比(如 25%)。
  1. piVersion: apps/v1
  2. kind: Deployment
  3. metadata:
  4.   name: example-deployment
  5. spec:
  6.   replicas: 3
  7.   strategy:
  8.     type: RollingUpdate
  9.     rollingUpdate:
  10.       maxUnavailable: 1        # 在更新过程中最多允许 1 个 Pod 不可用
  11.       maxSurge: 1              # 在更新过程中最多允许额外创建 1 个 Pod
复制代码
如果是statefulSet,其滚动更新计谋由 updateStrategy 字段指定,重要有以下几个设置选项:
  

  • **RollingUpdate**:这是 StatefulSet 唯一支持的更新计谋类型,按序号从高到低逐个更新 Pod。
  • **Partition**:可选字段,用于指定更新过程中保留的 Pod 的最小编号。只有编号大于该分区的 Pod 会被更新。默认值为 0,这意味着所有 Pod 都会被更新。
  K8s的三种探针

  我们利用这三种探针的目的在于:
  

  • 保证pod在完全停当后才承接流量
  • 当应用出现故障时可以及时重启,实现故障自动恢复
  
     webp    Startup Probe   (启动探针):如果设置了启动探针,在进行启动探测成功之前,不会进行存活探测。如果启动探测失败,k8s会重启pod。启动探针探测成功后,Liveness Probe 和 Readiness Probe 将接管健康查抄。Startup Probe 适合用于那些启动时间较长的应用程序,防止它们在启动过程中被误判为失败。如果应用启动时间不是特别长,没有须要设置这个探针。
  Liveness Probe (存活探针):存活探测如果失败,K8s会重启Pod。重要用于检测可通过重启办理的故障,例如:服务端可用线程耗尽、死锁等
  Readiness Probe (停当探针):停当探测成功之后,才会将pod加入到svc的端点列表中。如果停当探测失败,会将pod从端点中剔除
  利用示例

  deployment文件添加探针:
  1. apiVersion: apps/v1
  2. kind: Deployment
  3. spec:
  4.   template:
  5.     spec:
  6.       containers:
  7.       # 在containers下添加两个指针
  8.         livenessProbe:
  9.           httpGet:
  10.             path: /actuator/health/liveness
  11.             port: 8080
  12.           # pod启动后延迟60s探测
  13.           initialDelaySeconds: 45
  14.           # 每30s测一次
  15.           periodSeconds: 10
  16.           # 每次探测最大超时时间3s
  17.           timeoutSeconds: 3
  18.           # 连续6次探测失败则重启pod
  19.           failureThreshold: 3
  20.         readinessProbe:
  21.           httpGet:
  22.             path: /actuator/health/readiness
  23.                   livenessProbe:
  24.             httpGet:
  25.               path: /actuator/health/liveness
  26.               port: 8080
  27.             initialDelaySeconds: 45
  28.             periodSeconds: 10
  29.             timeoutSeconds: 3
  30.             failureThreshold: 3
复制代码
留意事项

  1、添加liveness跟readiness探针后,如果组件状态异常,会导致pod重启, 在特殊场景下, 组件异常并不必要重启, 必要利用方自行判断。
  例如:redis挂掉, 但是在服务中, redis缓存允许穿透或者异常, redis挂掉不应该重启呆板, 这个在healthcheck中,redis状态异常不应该触发liveness和readness失败
  2、initialDelaySeconds的值应该根据应用启动时间进行公道设置,如果设置过短,会导致pod反复被kill无法正常启动
  K8s的preStopHook

  K8s杀死一个pod的过程

  在 Kubernetes Pod 的删除过程中,同时会存在两条并行的时间线,如下图所示。
  

  • 一条时间线是网络规则的更新过程。
  • 另一条时间线是 Pod 的删除过程。
  
     pod删除过程    网络层面

  

  • Pod 被删除,状态置为 Terminating。
  • Endpoint Controller 将该 Pod 的 ip 从 Endpoint 对象中删除。
  • Kube-proxy 根据 Endpoint 对象的改变更新 iptables 规则,不再将流量路由到被删除的 Pod。
  • 网关、注册中心等更新数据,剔除对应的pod ip。
  Pod 层面

  

  • Pod 被删除,状态置为 Terminating。
  • Kubelet 捕获到 ApiServer 中 Pod 状态变化,执行 syncPod 动作。
  • 如果 Pod 设置了 preStop Hook ,将会执行。
  • kubelet 对 Pod 中各个 container 发送调用 cri 接口中 StopContainer 方法,向 dockerd 发送 stop -t 指令,用 SIGTERM 信号以通知容器内应用进程开始优雅克制。
  • 等待容器内应用进程完全克制,如果在 terminationGracePeriodSeconds (默认 30s) - preStop 执行时间内还未完全克制,就发送 SIGKILL 信号强制杀死应用进程。
  • 所有容器进程终止,清算 Pod 资源。
  由于网络层面跟pod层面的变更是并行的,二者状态的不同等,会导致pod在停机的过程中仍旧在接收流量。为了规避这个问题,我们必要利用到K8s提供的preStopHook
  
  引入preStopHook后,可以在pod杀死前,先执行preStopHook,在preStopHook中我们可以执行一个脚本,这个脚本可以简单的sleep一段时间,等待网络层变更完成后pod再响应SIGTERM信号,确保pod克制时,流量已经完全摘除,如下:
  1. spec:
  2.   contaienrs:
  3.   - name: my-awesome-container
  4.     lifecycle:
  5.       preStop:
  6.         exec:
  7.          command: ["sh", "-c", "sleep 10"] # 延迟关闭 10秒
复制代码
很多情况下,pod除了接收K8s调理的流量外,还会接收来自rpc的流量,例如:dubbo,这个时间,我们可以在实现一个负责的脚本,除了等待k8s网络层变更完成外,还必要将服务从注册中心中下限
  1. spec:
  2.   contaienrs:
  3.   - name: my-awesome-container
  4.     lifecycle:
  5.       preStop:
  6.         exec:
  7.           command: ["/bin/sh","-c","/pre-stop.sh"]
复制代码
    ❝   应用程序提供一个服务下线的借口,在pre-stop.sh中,可以调用这个接口,实现服务下线
    怎样验证

  这里我直接给出一个验证脚本,大家将其中的参数替换为服务核心接口的url即可:
  1. #!/bin/bash
  2. # 目标 URL
  3. url="your url"
  4. # 请求失败的次数计数器
  5. fail_count=0
  6. # 总请求次数计数器
  7. total_count=0
  8. # 日志文件
  9. log_file="request_log.txt"
  10. # 发送请求并统计失败次数的函数
  11. send_request() {
  12.     response=$(curl --silent --show-error --write-out "HTTPSTATUS:%{http_code}" "$url")
  13.     # 提取HTTP状态码
  14.     http_status=$(echo "$response" | tr -d '\n' | sed -e 's/.*HTTPSTATUS://')
  15.     response_body=$(echo "$response" | sed -e 's/HTTPSTATUS\:.*//g')
  16.     timestamp=$(date "+%Y-%m-%d %H:%M:%S")
  17.  ((total_count++))
  18.     if [ "$http_status" -ne 200 ]; then
  19.         ((fail_count++))
  20.         echo "$timestamp - 请求失败, HTTP状态码: $http_status, 响应: $response_body" | tee -a "$log_file"
  21.     else
  22.         echo "$timestamp - 请求成功, HTTP状态码: $http_status, 响应: $response_body" | tee -a "$log_file"
  23.     fi
  24. }
  25. # 每秒钟发送一个请求
  26. while true; do
  27.     send_request
  28.     echo "失败次数: $fail_count,请求总次数:$total_count" | tee -a "$log_file"
  29.     sleep 1
  30. done
复制代码

  • 将以下文件生存为xxx.sh,
  • 执行chmod +x xxx.sh
  • ./ xxx.sh,运行这个脚本
  • 发布服务,观察脚本输出
    在服务发布过程中,没有出现一次请求失败,说明接入成功
  带来的影响

  接入优雅启停对服务最大的影响在于发布时间会延伸。发布时间包罗:新pod的启动时间+旧pod的终止时间
  如果按照以下设置:
  

  • 探针初始延迟45s
  • preStopHook中等待15s
  • 滚动比例为25%,分4批 一个应用一个批次滚动完必要先启动新的pod,再杀死老的pod,因此如果一个应用的pod数量超过4个,单次发布的时间预计在4分钟左右
    办理方案:可以通过调解滚动比例跟探针的检测时间来调解这个时间,例如滚动比例调解到30%,探针初始延迟调解到30s
  总结

  本文重要跟大家介绍了K8s情况下怎样实现服务的优雅启停来保证服务发布过程中流量无损,咱们不仅学会了怎么做,也知道了为什么要怎么做。
  在文中我有提到,要实现服务的优雅启停还必要服务自身正确的响应kill信息,其实就是K8s发出的SIGTERM信号,对应kill -15命令。
  服务正确响应SIGTERM信号要怎么做本文没有提到,别的本文只提到了怎样设置K8s的探针,但没有涉及到探针怎样实现。思量到篇幅缘故因由,关于应用自身必要做的变乱,我们放到下篇文章中。
  作者简介
  

  • 大三退学,创业、求职、自考,一起升级
  • 7年it从业履历,多个开源社区contributor
  • 专注分享发展路上的所悟所得
  • 恒久探索 个人发展|职业发展|副业探索
  推荐阅读
  美团一面问我i++跟++i的区别是什么
  美团一面,你碰到过CPU 100%的情况吗?你是怎么处理的?
  美团一面:碰到过OOM吗?你是怎么处理的?
  美团一面,发生OOM了,程序还能继承运行吗?

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

来自云龙湖轮廓分明的月亮

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表