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

哈喽,大家好,我是明智

今天跟大家讨论一个服务稳定性相关的话题,对于大部分做业务的小伙伴来说,很少会被问到这类问题

不过你如果你希望面试公司的一些基础部门,例如:基础架构、效能开发、服务稳定性保障等,就很可能遇到

笔者在前段时间的面试中就被问到了,这个问题还有一系列的问法,例如:

  1. 服务发布过程中总是出现5xx怎么办?

  2. 如何实现服务的优雅启停?

本文主要分为以下几个部分

  1. 保证流量无损的关键是什么?

  2. K8s环境下如何保证流量无损?

  3. 如何验证?

  4. 会有什么消极影响?

本文只讨论K8s环境下的如何做到优雅启停

流量无损的关键

发布过程中损失的流量主要包括:

  1. 在启动一个新pod时,尚未完全就绪,就接收到了流量,导致服务响应500。例如服务依赖的db、redis还未启动

  2. 在停止一个旧pod时,这个pod中仍然存在正在处理中的请求,请求尚未处理完,pod就被杀死

  3. 在pod停止的过程中,仍然有部分流量被路由到这个pod上

  4. pod已经被杀死了,但是网关、注册中心等没有及时更新数据,导致仍然有请求被路由到一个不存在的实例上

因此我们要保证流量无损,要做到以下几点

  1. 每次杀死一个pod前,应该先启动一个新pod。即:滚动发布

  2. 启动一个新pod时,需要等待pod完全就绪,才能让这个pod接收流量。这需要使用到K8s的三种探针

  3. pod在被杀死前,需要先保证流量被摘除干净。这需要使用到K8s提供的preStopHook

  4. pod的流量摘除干净后,需要保证pod中正在处理的流量正常响应。这需要服务自身响应kill信息

K8s环境下如何保证流量无损

K8s中的滚动发布

如果是Deployment ,可以通过配置 DeploymentrollingUpdate 策略来控制滚动更新的行为。

关键参数

  • maxUnavailable:指定在更新过程中,最多可以有多少个 Pod 处于不可用状态。可以设置为绝对数量(如 1)或百分比(如 25%)。

  • maxSurge:指定在更新过程中,最多可以额外创建多少个 Pod。可以设置为绝对数量(如 1)或百分比(如 25%)。

piVersion: apps/v1
kind: Deployment
metadata:
  name: example-deployment
spec:
  replicas: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 1        # 在更新过程中最多允许 1 个 Pod 不可用
      maxSurge: 1              # 在更新过程中最多允许额外创建 1 个 Pod

如果是statefulSet,其滚动更新策略由 updateStrategy 字段指定,主要有以下几个配置选项:

  • **RollingUpdate**:这是 StatefulSet 唯一支持的更新策略类型,按序号从高到低逐个更新 Pod。

  • **Partition**:可选字段,用于指定更新过程中保留的 Pod 的最小编号。只有编号大于该分区的 Pod 会被更新。默认值为 0,这意味着所有 Pod 都会被更新。

K8s的三种探针

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

  1. 保证pod在完全就绪后才承接流量

  2. 当应用出现故障时可以及时重启,实现故障自动恢复

da8c1d0bdde42d911c06942f71352533.jpeg
webp

Startup Probe   (启动探针):如果配置了启动探针,在进行启动探测成功之前,不会进行存活探测。如果启动探测失败,k8s会重启pod。启动探针探测成功后,Liveness ProbeReadiness Probe 将接管健康检查。Startup Probe 适合用于那些启动时间较长的应用程序,防止它们在启动过程中被误判为失败。如果应用启动时间不是特别长,没有必要配置这个探针。

Liveness Probe (存活探针):存活探测如果失败,K8s会重启Pod。主要用于检测可通过重启解决的故障,例如:服务端可用线程耗尽、死锁等

Readiness Probe (就绪探针):就绪探测成功之后,才会将pod加入到svc的端点列表中。如果就绪探测失败,会将pod从端点中剔除

使用示例

deployment文件添加探针:

apiVersion: apps/v1
kind: Deployment
spec:
  template:
    spec:
      containers:
      # 在containers下添加两个指针
        livenessProbe:
          httpGet:
            path: /actuator/health/liveness
            port: 8080
          # pod启动后延迟60s探测
          initialDelaySeconds: 45
          # 每30s测一次
          periodSeconds: 10
          # 每次探测最大超时时间3s
          timeoutSeconds: 3
          # 连续6次探测失败则重启pod
          failureThreshold: 3
        readinessProbe:
          httpGet:
            path: /actuator/health/readiness
                  livenessProbe:
            httpGet:
              path: /actuator/health/liveness
              port: 8080
            initialDelaySeconds: 45
            periodSeconds: 10
            timeoutSeconds: 3
            failureThreshold: 3

注意事项

1、添加liveness跟readiness探针后,如果组件状态异常,会导致pod重启, 在特殊场景下, 组件异常并不需要重启, 需要使用方自行判断。

例如:redis挂掉, 但是在服务中, redis缓存允许穿透或者异常, redis挂掉不应该重启机器, 这个在healthcheck中,redis状态异常不应该触发liveness和readness失败

2、initialDelaySeconds的值应该根据应用启动时间进行合理设置,如果设置过短,会导致pod反复被kill无法正常启动

K8s的preStopHook

K8s杀死一个pod的过程

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

  1. 一条时间线是网络规则的更新过程。

  2. 另一条时间线是 Pod 的删除过程。

c162be9d88f8bfca7d17bbeb304b9ada.png
pod删除过程

网络层面

  1. Pod 被删除,状态置为 Terminating。

  2. Endpoint Controller 将该 Pod 的 ip 从 Endpoint 对象中删除。

  3. Kube-proxy 根据 Endpoint 对象的改变更新 iptables 规则,不再将流量路由到被删除的 Pod。

  4. 网关、注册中心等更新数据,剔除对应的pod ip。

Pod 层面

  1. Pod 被删除,状态置为 Terminating。

  2. Kubelet 捕获到 ApiServer 中 Pod 状态变化,执行 syncPod 动作。

  3. 如果 Pod 配置了 preStop Hook ,将会执行。

  4. kubelet 对 Pod 中各个 container 发送调用 cri 接口中 StopContainer 方法,向 dockerd 发送 stop -t 指令,用 SIGTERM 信号以通知容器内应用进程开始优雅停止。

  5. 等待容器内应用进程完全停止,如果在 terminationGracePeriodSeconds (默认 30s) - preStop 执行时间内还未完全停止,就发送 SIGKILL 信号强制杀死应用进程。

  6. 所有容器进程终止,清理 Pod 资源。

由于网络层面跟pod层面的变更是并行的,二者状态的不一致,会导致pod在停机的过程中仍然在接收流量。为了规避这个问题,我们需要使用到K8s提供的preStopHook

ddd3c1928aedfa6978e3fb5eebb9a100.png

引入preStopHook后,可以在pod杀死前,先执行preStopHook,在preStopHook中我们可以执行一个脚本,这个脚本可以简单的sleep一段时间,等待网络层变更完成后pod再响应SIGTERM信号,确保pod停止时,流量已经完全摘除,如下:

spec:
  contaienrs:
  - name: my-awesome-container
    lifecycle:
      preStop:
        exec:
         command: ["sh", "-c", "sleep 10"] # 延迟关闭 10秒

很多情况下,pod除了接收K8s调度的流量外,还会接收来自rpc的流量,例如:dubbo,这个时候,我们可以在实现一个负责的脚本,除了等待k8s网络层变更完成外,还需要将服务从注册中心中下限

spec:
  contaienrs:
  - name: my-awesome-container
    lifecycle:
      preStop:
        exec:
          command: ["/bin/sh","-c","/pre-stop.sh"]

应用程序提供一个服务下线的借口,在pre-stop.sh中,可以调用这个接口,实现服务下线

如何验证

这里我直接给出一个验证脚本,大家将其中的参数替换为服务核心接口的url即可:

#!/bin/bash

# 目标 URL
url="your url"

# 请求失败的次数计数器
fail_count=0

# 总请求次数计数器
total_count=0

# 日志文件
log_file="request_log.txt"

# 发送请求并统计失败次数的函数
send_request() {
    response=$(curl --silent --show-error --write-out "HTTPSTATUS:%{http_code}" "$url")

    # 提取HTTP状态码
    http_status=$(echo "$response" | tr -d '\n' | sed -e 's/.*HTTPSTATUS://')
    response_body=$(echo "$response" | sed -e 's/HTTPSTATUS\:.*//g')

    timestamp=$(date "+%Y-%m-%d %H:%M:%S")
 ((total_count++))
    if [ "$http_status" -ne 200 ]; then
        ((fail_count++))
        echo "$timestamp - 请求失败, HTTP状态码: $http_status, 响应: $response_body" | tee -a "$log_file"
    else
        echo "$timestamp - 请求成功, HTTP状态码: $http_status, 响应: $response_body" | tee -a "$log_file"
    fi
}

# 每秒钟发送一个请求
while true; do
    send_request
    echo "失败次数: $fail_count,请求总次数:$total_count" | tee -a "$log_file"
    sleep 1
done
  1. 将以下文件保存为xxx.sh

  2. 执行chmod +x xxx.sh

  3. ./ xxx.sh,运行这个脚本

  4. 发布服务,观察脚本输出

    62bcc8ce387c44a7dd83d9e9dbf8bc9c.png

    在服务发布过程中,没有出现一次请求失败,说明接入成功

带来的影响

接入优雅启停对服务最大的影响在于发布时间会延长。发布时间包括:新pod的启动时间+旧pod的终止时间

如果按照以下配置:

  • 探针初始延迟45s

  • preStopHook中等待15s

  • 滚动比例为25%,分4批 一个应用一个批次滚动完需要先启动新的pod,再杀死老的pod,因此如果一个应用的pod数量超过4个,单次发布的时间预计在4分钟左右

    解决方案:可以通过调整滚动比例跟探针的检测时间来调整这个时间,例如滚动比例调整到30%,探针初始延迟调整到30s

总结

本文主要跟大家介绍了K8s环境下如何实现服务的优雅启停来保证服务发布过程中流量无损,咱们不仅学会了怎么做,也知道了为什么要怎么做。

在文中我有提到,要实现服务的优雅启停还需要服务自身正确的响应kill信息,其实就是K8s发出的SIGTERM信号,对应kill -15命令。

服务正确响应SIGTERM信号要怎么做本文没有提到,另外本文只提到了如何配置K8s的探针,但没有涉及到探针如何实现。考虑到篇幅原因,关于应用自身需要做的事情,我们放到下篇文章中。

作者简介

  1. 大三退学,创业、求职、自考,一路升级

  2. 7年it从业经验,多个开源社区contributor

  3. 专注分享成长路上的所悟所得

  4. 长期探索 个人成长职业发展副业探索

推荐阅读

美团一面问我i++跟++i的区别是什么

美团一面,你碰到过CPU 100%的情况吗?你是怎么处理的?

美团一面:碰到过OOM吗?你是怎么处理的?

美团一面,发生OOM了,程序还能继续运行吗?

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/707192.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ubtun虚拟机安装

选择镜像后启动 选择第一个回车 加载完成后 ,进入Ubuntu安装界面,安装语言选择English,完成后按一下回车: 此时弹出安装器可更新提示,下方选项选择第二个Continue without updating(不更新,继续…

复旦微FMQL20SM全国产ARM+FPGA核心板,替代xilinx ZYNQ7020系列

FMQL20SM核心板一款全国产工业核心板。基于复旦微FMQL20S400M四核ARM Cortex-A7(PS端) FPGA可编程逻辑资源(PL端)异构多核SoC处理器设计的全国产工业核心板,PS端主频高达1GHz。 核心板简介 FMQL20SM核心板是一款全国…

PHP地方门户分类信息网站源码讯客分类信息系统源码(含手机版)

源码介绍 1.上传程序到网站根目录,访问http://域名/install/index.php 进行安装,不要直接打开网址,先直接安装; 2.安装完成后 后台恢复数据即可 默认帐号密码都是admin http://域名/admin/ 3.不要删除任何文件,因为删除文件或者修改代码可能造成错误 运…

【机器学习】鸢尾花分类:机器学习领域经典入门项目实战

学习机器学习,就像学习任何新技能一样,最好的方法之一就是通过实战来巩固理论知识。鸢尾花分类项目是一个经典的入门项目,它不仅简单易懂,还能帮助我们掌握机器学习的基本步骤和方法。 鸢尾花数据集(Iris Dataset&…

Django 5 Web应用开发实战

文章目录 一、内容简介二、目录内容三、值得一读四、适读人群 一、内容简介 《Django 5 Web应用开发实战》集Django架站基础、项目实践、开发经验于一体,是一本从零基础到精通Django Web企业级开发技术的实战指南。《Django 5 Web应用开发实战》内容以Python 3.x和…

Java多线程面试重点-1

0. 什么是并发?什么是并行? 并发:把时间分成一段一段,每个线程轮流抢占时间段。 如果时间段非常短,线程切换非常快,被称为伪并行。并行:多个线程可以同时运行。 并发与并行造成的影响&#xff…

如何把路由器设备的LAN口地址为三大私网地址

要将路由器的LAN口地址配置为三大私有IP地址范围之一(10.0.0.0/8、172.16.0.0/12 或 192.168.0.0/16),我们需要访问路由器的管理界面并进行相应的设置。 下面是步骤: 连接到路由器: 连接到路由器的管理界面&#xf…

在vue中循环中调用接口-promise.all();按顺序执行异步处理

🌈🌈🌈目录 场景一 解决 场景二 解决 场景一 数组遍历中每次遍历都需要去请求getStaffCover接口,拿到该接口的结果拼接到数组的每一项,等到数组遍历完之后,拿到拼接好的数组。拼接的数组必须是最终遍历…

计算机网络 —— 应用层(应用层概述及服务方式)

计算机网络 —— 应用层(应用层概述及服务方式) 应用层服务方式C/S(客户端-服务器(C/S)模型)基本概念特点B/S(Browser/Server)基本概念特点应用场景 p2p (对等网络&#…

这三款使用的视频、图片设计工具,提供工作效率

Videograp Videograp是一款专注于视频生成的工具,特别适合需要快速剪辑和编辑视频的用户。Videograp具备以下特点: 影音比例转换:Videograp支持调整视频的分辨率和比例,使其更适合不同的播放环境和设备。 AI快剪:该工…

超市陈列艺术:不仅仅是货品摆放,更是营销策略的体现

品类管理在门店落地的最直观表现就是单品的空间陈列管理,通过陈列细节的差异体现出门店的商品定位与策略。此文分析入木三分,值得学习。 在商品陈列的空间管理领域,不仅要考虑整体的空间陈列,也要对每个商品的空间陈列位置&#…

旅游行业电商平台:数字化转型的引擎与未来发展趋势

引言 旅游行业数字化转型的背景和重要性 随着信息技术的飞速发展,数字化转型成为各行业发展的必然趋势。旅游行业,作为一个高度依赖信息和服务的领域,数字化转型尤为重要。通过数字化手段,旅游行业能够实现资源的高效配置、服务的…

Apache Doris单机快速安装(已踩坑)

官方文档:https://doris.incubator.apache.org/zh-CN/docs/get-starting/quick-start/ 环境: 操作系统:CentOS7.6 X86_64 JDK:Oracle jdk1.8.0_351 1.版本下载 从 doris.apache.org 下载相应的 Doris 安装包,并且解压…

11.QLoRA微调ChatGLM3-6B

实战 QLoRA 微调 ChatGLM3-6B 大模型 实战 PEFT 库 QLoRA ChatGLM3-6B 微调数据集 AdvertiseGen AdvertiseGen 数据集获取 使用ChatGLM3-6b Tokenizer处理数据 关于ig nore_label_id 的设置: 在许多自然语言处理和机器学习框架中, ig nore_label_id 被…

idea在空工程中添加新模块并测试的步骤

ServicesTest是空的工程,没有pom文件。现在需要在ServicesTest目录下添加新模块作为新的工程,目的是写一下别的技术功能。 原先目录结构,ServicesTest是空的工程,没有pom文件。下面的几个模块是新的工程,相互独立。 1.…

树莓派4B学习笔记8:开机自启动Python脚本_kill关闭后台脚本

今日继续学习树莓派4B 4G:(Raspberry Pi,简称RPi或RasPi) 本人所用树莓派4B 装载的系统与版本如下: 版本可用命令 (lsb_release -a) 查询: Opencv 版本是4.5.1: 紧接着上篇文章学习的串口通信,今日学习如何让树莓派开机…

[Algorithm][贪心][柠檬水找零][将数组和减半的最少操作次数][最大数][摆动序列]详细讲解

目录 1.柠檬水找零1.题目链接2.算法原理详解3.代码实现 2.将数组和减半的最少操作次数1.题目链接2.算法原理详解3.代码实现 3.最大数1.题目链接2.算法原理详解3.代码实现 4.摆动序列1.题目链接2.算法原理详解3.代码实现 1.柠檬水找零 1.题目链接 柠檬水找零 2.算法原理详解 …

网络安全 - ARP 欺骗原理+实验

APR 欺骗 什么是 APR 为什么要用 APR A P R \color{cyan}{APR} APR(Address Resolution Protocol)即地址解析协议,负责将某个 IP 地址解析成对应的 MAC 地址。 在网络通信过程中会使用到这两种地址,逻辑 IP 地址和物理 MAC 地址&…

短剧APP小程序开发之小程序内存管理挑战:短剧缓存与释放策略探讨(第二篇)

在上一篇帖子中,我们探讨了小程序内存管理的限制以及缓存策略的设计。本篇将进一步探讨释放策略的具体实现以及优化方案,以支持大量短剧内容的加载和播放。 释放策略的具体实现 监听内存警告:小程序提供了监听内存警告的API,开发…

【PL理论】(22) 函数式语言:多参数 | 柯里化 (Currying) : 将多参数函数实现为返回一个函数的函数

💭 写在前面:本章我们将继续讲解函数式语言,介绍多参数,着重讲解柯里化的概念,将多参数函数实现为返回一个函数的函数。 目录 0x00 多参数(Multiple Arguments) 0x01 柯里化(Curr…