【Kubernetes】API server 限流 之 maxinflight.go
这个文件实现了一个基于信号量(Channel)的简单限流器。
基础知识
总共有四种channel
-
带缓冲的channel
nonMutatingChan、mutatingChan 都是带缓冲的channel ,这类channel 的特点是:
这允许最多 mutatingLimit /nonMutatingLimit 个请求同时获取令牌并执行,直到缓冲区满了才会阻塞新的请求。对带缓冲 channel 的发送操作在缓冲区未满、接收操作在缓冲区非空的情况下是异步的(发送或接收不需要阻塞等待)。
但当缓冲区满了的情况下,对它进行发送操作的 Goroutine 就会阻塞挂起;当缓冲区为空的情况下,对它进行接收操作的 Goroutine 也会阻塞挂起。
本代码中限流通道从创建到服务器关闭始终保持打开状态,因为这些通道用于控制并发请求数量的计数器,需要持续使用。
-
不带缓冲的channel
对无缓冲 channel 类型的发送与接收操作,一定要放在两个不同的 Goroutine 中进行,否则会导致 deadlock。
通道类型 | 创建方式 | 主要特点 | 典型使用场景 |
---|---|---|---|
无缓冲通道 | make(chan T) | 同步、阻塞 | 信号通知、精确同步 |
带缓冲通道 | make(chan T, size) | 异步、缓冲 | 限流控制、任务队列 |
只读通道 | <-chan T | 只能接收 | 消费者模式、类型安全 |
只写通道 | chan<- T | 只能发送 | 生产者模式、类型安全 |
nil 通道 | var ch chan T | 阻塞操作 | 条件化通信、禁用分支 |
Kubernetes 的 maxinflight 限流器选择带缓冲通道作为实现方式,正是利用了其特有的信号量特性、非阻塞操作和容量控制能力。
当 select 语句中没有 default 分支,而且所有 case 中的 channel 操作都阻塞了的时候,整个 select 语句都将被阻塞,直到某一个 case 上的 channel 变成可发送,或者某个 case 上的 channel 变成可接收,select 语句才可以继续进行下去。
Kubernetes API Server maxinflight.go 限流分析
这个文件实现了一个基于信号量(Channel)的简单限流器,主要使用了两种限流通道:
1. 限流分类
var nonMutatingChan chan bool // 非修改性请求的限流通道
var mutatingChan chan bool // 修改性请求的限流通道
请求分类
- 非修改性请求(Non-Mutating):get、list、watch 操作
- 修改性请求(Mutating):create、update、patch、delete 等操作
2. 限流实现机制
核心实现使用了带缓冲的 Channel 作为信号量:
if nonMutatingLimit != 0 {nonMutatingChan = make(chan bool, nonMutatingLimit) // 非修改性请求限流器
}
if mutatingLimit != 0 {mutatingChan = make(chan bool, mutatingLimit) // 修改性请求限流器
}
限流处理流程
- 请求到达时:
select {
case c <- true: // 尝试获取令牌// 处理请求defer func() {<-c // 释放令牌}()handler.ServeHTTP(w, r)
default:// 获取不到令牌时返回 429 Too Many RequeststooManyRequests(r, w, retryAfter)
}
这里的select 实现了快速失败策略,所有的请求,要么被处理,要么直接扔到default中。 实现了非阻塞并发
2. 特权用户处理:
// 系统管理员(system:master)不受限流影响
if currUser, ok := apirequest.UserFrom(ctx); ok {for _, group := range currUser.GetGroups() {if group == user.SystemPrivilegedGroup {handler.ServeHTTP(w, r)return}}
}
3. 监控指标
使用 watermark 记录并发请求数:
type requestWatermark struct {phase stringreadOnlyObserver, mutatingObserver fcmetrics.RatioedGaugereadOnlyWatermark, mutatingWatermark intlock sync.Mutex
}
指标更新
- 定期更新 inflight 使用指标
- 记录最大并发数
- 分别统计读写请求
4. 特点总结
-
简单高效
- 使用 Channel 实现,开销小
- 无需复杂的算法
-
区分请求类型
- 读写请求分开限流
- 更细粒度的控制
-
特权用户豁免
- 系统管理员不受限
- 确保关键操作可执行
-
监控完善
- 记录限流指标
- 支持监控告警
-
无时间窗口
- 不同于令牌桶等算法
- 纯并发数控制
这是一个轻量级但有效的限流方案,适合 API Server 的需求。
Kubernetes API Server 中的两种限流通道分析
Kubernetes API Server 在 maxinflight.go 文件中实现了两种不同的限流通道,分别用于处理不同类型的请求:
1. 非修改性请求限流通道 (nonMutatingChan)
var nonMutatingChan chan bool
if nonMutatingLimit != 0 {nonMutatingChan = make(chan bool, nonMutatingLimit)klog.V(2).InfoS("Initialized nonMutatingChan", "len", nonMutatingLimit)
}
- 处理的请求类型:
get
、list
、watch
等读取操作 - 判断依据:
nonMutatingRequestVerbs.Has(requestInfo.Verb)
- 目的:限制并发读取操作的数量
2. 修改性请求限流通道 (mutatingChan)
var mutatingChan chan bool
if mutatingLimit != 0 {mutatingChan = make(chan bool, mutatingLimit)klog.V(2).InfoS("Initialized mutatingChan", "len", mutatingLimit)
}
- 处理的请求类型:
create
、update
、delete
、patch
等写入操作 - 判断依据:
!nonMutatingRequestVerbs.Has(requestInfo.Verb)
- 目的:限制并发写入操作的数量
主要区别
-
目标请求不同
nonMutatingChan
针对读操作mutatingChan
针对写操作
-
资源消耗的差异
- 写操作通常消耗更多资源,可能会修改集群状态
- 读操作相对轻量,但数量可能更大
-
限流阈值不同
- API Server 配置中可以为这两种通道设置不同的限流阈值
- 通常写操作的限流阈值会低于读操作
-
监控指标分离
- 代码中对两种请求的监控也是分开的:
watermark.recordMutating(len(c)) watermark.recordReadOnly(len(c))
- 代码中对两种请求的监控也是分开的:
-
限流效果的差异
- 当读请求过多时,只会拒绝新的读请求,写请求不受影响
- 当写请求过多时,只会拒绝新的写请求,读请求不受影响
实现原理
两种通道的底层实现方式相同,都是使用带缓冲的 channel 作为信号量:
select {
case c <- true: // 尝试获取令牌// 处理请求并记录指标defer func() {<-c // 释放令牌}()handler.ServeHTTP(w, r)
default:// 无法获取令牌,返回 429 Too Many RequeststooManyRequests(r, w, retryAfter)
}
这种区分读写请求的限流设计,使 Kubernetes API Server 能够在高负载情况下更有效地分配资源,确保系统的稳定性和可用性。
限流参数 nonMutatingLimit 、mutatingLimit 意义:
这段代码:
if nonMutatingLimit == 0 && mutatingLimit == 0 {return handler
}
意思解释
这是一个前置检查,用于判断是否需要启用限流功能:
- 如果
nonMutatingLimit
和mutatingLimit
都为 0,表示不需要对任何类型的请求进行限流 - 在这种情况下,函数直接返回原始的
handler
,不添加任何限流逻辑 - 相当于完全跳过限流处理,请求会直接传递给下一个处理器
为什么需要这个检查
-
性能优化
- 如果不需要限流,避免创建不必要的通道和记录指标的开销
- 减少请求处理的额外层级,提高性能
-
功能开关
- 提供一种方式完全禁用限流功能
- 管理员可以通过配置参数控制是否启用限流
-
兼容性
- 允许那些不需要限流的环境(如小型开发集群)简化配置
- 保持与低资源环境的兼容性
实际应用
在 Kubernetes API Server 配置中,可以通过这些参数控制限流:
apiServer:maxRequestsInflight: 400 # nonMutatingLimit 参数maxMutatingRequestsInflight: 200 # mutatingLimit 参数
如果将这两个值都设为 0,API Server 将不会对任何请求进行并发限制,可能适用于:
- 开发/测试环境
- 低负载集群
- 有外部限流机制的环境
这提供了一个简单的开关,使管理员能够灵活控制是否启用 API Server 的内置限流功能。
基于"通过通信共享内存"原则分析maxinflight.go
从Go语言的设计哲学"不要通过共享内存来通信,而要通过通信来共享内存"(Don’t communicate by sharing memory; share memory by communicating)来看,maxinflight.go
有几个违背此原则的地方:
违背原则的地方
1. 共享状态管理方式
// 使用互斥锁保护共享状态
type requestWatermark struct {phase stringreadOnlyObserver, mutatingObserver fcmetrics.RatioedGaugelock sync.Mutex // 互斥锁readOnlyWatermark, mutatingWatermark int
}// 全局共享变量
var watermark = &requestWatermark{phase: metrics.ExecutingPhase,
}
这里使用了传统的"共享内存+锁"的并发控制模式,而不是Go推荐的基于通道的模式。
2. 记录指标的方法
func (w *requestWatermark) recordMutating(mutatingVal int) {w.mutatingObserver.Set(float64(mutatingVal))w.lock.Lock()defer w.lock.Unlock()if w.mutatingWatermark < mutatingVal {w.mutatingWatermark = mutatingVal}
}
这里直接修改共享状态,而不是通过消息传递。
3. 周期性更新指标
go wait.Until(func() {watermark.lock.Lock()readOnlyWatermark := watermark.readOnlyWatermarkmutatingWatermark := watermark.mutatingWatermarkwatermark.readOnlyWatermark = 0watermark.mutatingWatermark = 0watermark.lock.Unlock()metrics.UpdateInflightRequestMetrics(watermark.phase, readOnlyWatermark, mutatingWatermark)
}, inflightUsageMetricUpdatePeriod, stopCh)
使用锁直接访问和修改共享状态,而不是使用通道接收信息。
符合Go哲学的重构方案
根据"通过通信共享内存"的原则,可以这样重构:
// 定义度量指标更新消息
type MetricUpdate struct {ReadOnly intMutating int
}// 创建通道
var metricCh = make(chan MetricUpdate)
var readOnlyMetricCh = make(chan int)
var mutatingMetricCh = make(chan int)// 启动指标收集器goroutine
func startMetricCollector(stopCh <-chan struct{}) {readOnlyMax := 0mutatingMax := 0// 定期更新指标ticker := time.NewTicker(inflightUsageMetricUpdatePeriod)defer ticker.Stop()for {select {case val := <-readOnlyMetricCh:if val > readOnlyMax {readOnlyMax = val}case val := <-mutatingMetricCh:if val > mutatingMax {mutatingMax = val}case <-ticker.C:// 更新并重置最大值metrics.UpdateInflightRequestMetrics(metrics.ExecutingPhase, readOnlyMax, mutatingMax)readOnlyMax = 0mutatingMax = 0case <-stopCh:return}}
}// 记录指标的新函数
func recordReadOnly(val int) {select {case readOnlyMetricCh <- val:// 成功发送default:// 通道已满,丢弃}
}func recordMutating(val int) {select {case mutatingMetricCh <- val:// 成功发送default:// 通道已满,丢弃 }
}
为什么现有实现没有使用通道模式
尽管代码违背了Go的设计哲学,但有几个可能的原因:
-
历史兼容性:可能是从早期版本演化而来,完全重构成本高
-
性能考虑:在高频调用的代码路径上,锁可能比通道有更低的开销
-
简单直接:对于简单的计数器场景,锁实现可能更直观
-
限流部分确实用了通道:
nonMutatingChan = make(chan bool, nonMutatingLimit) mutatingChan = make(chan bool, mutatingLimit)
这部分确实体现了Go的设计哲学,使用通道的缓冲区容量来限制并发请求数
总结
maxinflight.go中的代码部分遵循了Go的设计哲学(使用通道进行限流),但指标收集部分仍然采用了传统的"共享内存+锁"模式。一个更符合Go哲学的实现应该将指标收集也改为基于通道的模式,消除所有对共享内存的直接访问和修改。
相关文章:
【Kubernetes】API server 限流 之 maxinflight.go
这个文件实现了一个基于信号量(Channel)的简单限流器。 基础知识 总共有四种channel 带缓冲的channel nonMutatingChan、mutatingChan 都是带缓冲的channel ,这类channel 的特点是: 这允许最多 mutatingLimit /nonMutatingLimit 个请求同时获取令牌并执…...
推荐算法工程师的技术图谱和学习路径
推荐算法工程师的技术图谱和学习路径可以从多个维度进行概述,可以总结如下: 一、技术图谱 推荐算法工程师需要掌握的技术栈主要分为以下几个方面: 数学基础: 微积分、线性代数、概率论与统计学是推荐算法的基础,用于理解模型的数学原理和优化算法。高等数学、最优化理论…...

Milvus高性能向量数据库与大模型结合
Milvus | 高性能向量数据库,为规模而构建Milvus 是一个为 GenAI 应用构建的开源向量数据库。使用 pip 安装,执行高速搜索,并扩展到数十亿个向量。https://milvus.io/zh Milvus 是什么? Milvus 是一种高性能、高扩展性的向量数据…...
轮式机器人在复杂地形中如何选择合适的全局路径规划算法?
已思考完成 收起 嗯,用户问的是轮式机器人在复杂地形中如何选择合适的全局路径规划算法。首先,我需要理解复杂地形可能带来的挑战,比如崎岖的路面、动态障碍物、非结构化环境等等。轮式机器人在这里的运动控制需要考虑地形通过性、稳定性&…...

Metal学习笔记九:光照基础
光和阴影是使场景流行的重要要求。通过一些着色器艺术,您可以突出重要的对象、描述天气和一天中的时间并设置场景的气氛。即使您的场景由卡通对象组成,如果您没有正确地照亮它们,场景也会变得平淡无奇。 最简单的光照方法之一是 Phong 反射模…...

【字符串】最长公共前缀 最长回文子串
文章目录 14. 最长公共前缀解题思路:模拟5. 最长回文子串解题思路一:动态规划解题思路二:中心扩散法 14. 最长公共前缀 14. 最长公共前缀 编写一个函数来查找字符串数组中的最长公共前缀。 如果不存在公共前缀,返回空字符…...

Linux提权之详细总结版(完结)
这里是我写了折磨多提权的指令的总结 我这里毫无保留分享给大家哦 首先神魔是提权 我们完整的渗透测试的流程是(个人总结的) 首先提升权限是我们拿到webshell之后的事情,如何拿到webshell,怎末才能拿到webshell,朋友们等我更新,持续更新中,下一篇更新的是windows提权 好了 废…...

week 3 - More on Collections - Lecture 3
一、Motivation 1. Java支持哪种类型的一维数据结构? Java中用于在单一维度中存储数据的数据结构,如arrays or ArrayLists. 2. 如何在Java下创建一维数据结构?(1-dimensional data structure) 定义和初始化这些一…...
Pwntools 的详细介绍、安装指南、配置说明
Pwntools:Python 开源安全工具箱 一、Pwntools 简介 Pwntools 是一个由 Security researcher 开发的 高效 Python 工具库,专为密码学研究、漏洞利用、协议分析和逆向工程设计。它集成了数百个底层工具的功能,提供统一的 Python API 接口&am…...

PLC(电力载波通信)网络机制介绍
1. 概述 1.1 什么是PLC 电力载波通讯即PLC,是英文Power line Carrier的简称。 电力载波是电力系统特有的通信方式,电力载波通讯是指利用现有电力线,通过载波方式将模拟或数字信号进行高速传输的技术。最大特点是不需要重新架设网络…...

Qt监控系统远程回放/录像文件远程下载/录像文件打上水印/批量多线程极速下载
一、前言说明 在做这个功能的时候,着实费了点心思,好在之前做ffmpeg加密解密的时候,已经打通了极速加密保存文件,主要就是之前的类中新增了进度提示信号,比如当前已经处理到哪个position位置,发个信号出来…...
自学微信小程序的第八天
DAY8 1、使用动画API即可完成动画效果的制作,先通过wx.createAnimation()方法获取Animation实例,然后调用Animation实例的方法实现动画效果。 表40:wx.createAnimation()方法的常用选项 选项 类型 说明 duration number 动画持续时间,单位为毫秒,默认值为400毫秒 timing…...

【java】@Transactional导致@DS注解切换数据源失效
最近业务中出现了多商户多租户的逻辑,所以需要分库,项目框架使用了mybatisplus所以我们自然而然的选择了同是baomidou开发的dynamic.datasource来实现多数据源的切换。在使用初期程序运行都很好,但之后发现在调用com.baomidou.mybatisplus.ex…...
003 SpringBoot集成Kafka操作
4.SpringBoot集成Kafka 文章目录 4.SpringBoot集成Kafka1.入门示例2.yml完整配置3.关键配置注释说明1. 生产者优化参数2. 消费者可靠性配置3. 监听器高级特性4. 安全认证配置 4.配置验证方法5.不同场景配置模板场景1:高吞吐日志收集场景2:金融级事务消息…...

Android SystemUI开发(一)
frameworks/base/packages/SystemUI/src/com/android/systemui/SystemUI.java frameworks/base/packages/SystemUI/src/com/android/systemui/SystemUIService.java 关键文件 SystemUI 关键服务 简介 Dependency.class:处理系统依赖关系,提供资源或服…...

C#贪心算法
贪心算法:生活与代码中的 “最优选择大师” 在生活里,我们常常面临各种选择,都希望能做出最有利的决策。比如在超市大促销时,面对琳琅满目的商品,你总想用有限的预算买到价值最高的东西。贪心算法,就像是一…...

Vue程序下载
Vue是一个基于JavaScript(JS)实现的框架,想要使用它,就得先拿到Vue的js文件 Vue官网 Vue2:Vue.js Vue3:Vue.js - 渐进式 JavaScript 框架 | Vue.js 下载并安装vue.js 第一步:打开Vue2官网&a…...

【UCB CS 61B SP24】Lecture 17 - Data Structures 3: B-Trees学习笔记
本文以 2-3-4 树详细讲解了 B 树的概念,逐步分析其操作,并用 Java 实现了标准的 B 树。 1. 2-3 & 2-3-4 Trees 上一节课中讲到的二叉搜索树当数据是随机顺序插入的时候能够使得树变得比较茂密,如下图右侧所示,时间复杂度也就…...

机器学习决策树
一、香农公式 熵: 信息增益: 信息增益信息熵-条件熵 前者是初始信息熵大小,后者是因为条件加入后带来的确定性增加 信息增益表示得知特征X的信息而使得类Y的信息的不确定性减少的程度 信息增益越大说明影响越大 二、代码 ""&…...
Spring Boot + MyBatis 实现 RESTful API 的完整流程
后端开发:Spring Boot 快速开发实战 引言 在现代后端开发中,Spring Boot 因其轻量级、快速开发的特性而备受开发者青睐。本文将带你从零开始,使用 Spring Boot MyBatis 实现一个完整的 RESTful API,并深入探讨如何优雅地处理异…...
[特殊字符] 智能合约中的数据是如何在区块链中保持一致的?
🧠 智能合约中的数据是如何在区块链中保持一致的? 为什么所有区块链节点都能得出相同结果?合约调用这么复杂,状态真能保持一致吗?本篇带你从底层视角理解“状态一致性”的真相。 一、智能合约的数据存储在哪里…...

突破不可导策略的训练难题:零阶优化与强化学习的深度嵌合
强化学习(Reinforcement Learning, RL)是工业领域智能控制的重要方法。它的基本原理是将最优控制问题建模为马尔可夫决策过程,然后使用强化学习的Actor-Critic机制(中文译作“知行互动”机制),逐步迭代求解…...

【力扣数据库知识手册笔记】索引
索引 索引的优缺点 优点1. 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。2. 可以加快数据的检索速度(创建索引的主要原因)。3. 可以加速表和表之间的连接,实现数据的参考完整性。4. 可以在查询过程中,…...

解决Ubuntu22.04 VMware失败的问题 ubuntu入门之二十八
现象1 打开VMware失败 Ubuntu升级之后打开VMware上报需要安装vmmon和vmnet,点击确认后如下提示 最终上报fail 解决方法 内核升级导致,需要在新内核下重新下载编译安装 查看版本 $ vmware -v VMware Workstation 17.5.1 build-23298084$ lsb_release…...
电脑插入多块移动硬盘后经常出现卡顿和蓝屏
当电脑在插入多块移动硬盘后频繁出现卡顿和蓝屏问题时,可能涉及硬件资源冲突、驱动兼容性、供电不足或系统设置等多方面原因。以下是逐步排查和解决方案: 1. 检查电源供电问题 问题原因:多块移动硬盘同时运行可能导致USB接口供电不足&#x…...
在四层代理中还原真实客户端ngx_stream_realip_module
一、模块原理与价值 PROXY Protocol 回溯 第三方负载均衡(如 HAProxy、AWS NLB、阿里 SLB)发起上游连接时,将真实客户端 IP/Port 写入 PROXY Protocol v1/v2 头。Stream 层接收到头部后,ngx_stream_realip_module 从中提取原始信息…...
Java多线程实现之Callable接口深度解析
Java多线程实现之Callable接口深度解析 一、Callable接口概述1.1 接口定义1.2 与Runnable接口的对比1.3 Future接口与FutureTask类 二、Callable接口的基本使用方法2.1 传统方式实现Callable接口2.2 使用Lambda表达式简化Callable实现2.3 使用FutureTask类执行Callable任务 三、…...
C++中string流知识详解和示例
一、概览与类体系 C 提供三种基于内存字符串的流,定义在 <sstream> 中: std::istringstream:输入流,从已有字符串中读取并解析。std::ostringstream:输出流,向内部缓冲区写入内容,最终取…...
智能AI电话机器人系统的识别能力现状与发展水平
一、引言 随着人工智能技术的飞速发展,AI电话机器人系统已经从简单的自动应答工具演变为具备复杂交互能力的智能助手。这类系统结合了语音识别、自然语言处理、情感计算和机器学习等多项前沿技术,在客户服务、营销推广、信息查询等领域发挥着越来越重要…...
纯 Java 项目(非 SpringBoot)集成 Mybatis-Plus 和 Mybatis-Plus-Join
纯 Java 项目(非 SpringBoot)集成 Mybatis-Plus 和 Mybatis-Plus-Join 1、依赖1.1、依赖版本1.2、pom.xml 2、代码2.1、SqlSession 构造器2.2、MybatisPlus代码生成器2.3、获取 config.yml 配置2.3.1、config.yml2.3.2、项目配置类 2.4、ftl 模板2.4.1、…...