MoreRSS

site iconAnZhihe | 安志合修改

国学和传统文化爱好者,IT行业从业者,运维和SRE。
请复制 RSS 到你的阅读器,或快速订阅到 :

Inoreader Feedly Follow Feedbin Local Reader

AnZhihe | 安志合的 RSS 预览

从0开始学微服务

2026-01-21 10:31:48

单体应用

单体应用:LAMP(Linux + Apache + MySQL + PHP)和MVC(Spring + iBatis/Hibernate + Tomcat)

然而随着业务规模的不断扩大,团队开发人员的不断扩张,单体应用架构就会开始出现问题。经历过业务和团队快速增长的同学都会对此深有感触。大概会有以下几个方面的问题。

  • 部署效率低下。当单体应用的代码越来越多,依赖的资源越来越多时,应用编译打包、部署测试一次,甚至需要10分钟以上。这也经常被新加入的同学吐槽说,部署测试一次的时间,都可以去楼下喝杯咖啡了。

  • 团队协作开发成本高。以我的经验,早期在团队开发人员只有两三个人的时候,协作修改代码,最后合并到同一个master分支,然后打包部署,尚且可控。但是一旦团队人员扩张,超过5人修改代码,然后一起打包部署,测试阶段只要有一块功能有问题,就得重新编译打包部署,然后重新预览测试,所有相关的开发人员又都得参与其中,效率低下,开发成本极高。

  • 系统高可用性差。因为所有的功能开发最后都部署到同一个WAR包里,运行在同一个Tomcat进程之中,一旦某一功能涉及的代码或者资源有问题,那就会影响整个WAR包中部署的功能。比如我经常遇到的一个问题,某段代码不断在内存中创建大对象,并且没有回收,部署到线上运行一段时间后,就会造成JVM内存泄露,异常退出,那么部署在同一个JVM进程中的所有服务都不可用,后果十分严重。

  • 线上发布变慢。特别是对于Java应用来说,一旦代码膨胀,服务启动的时间就会变长,有些甚至超过10分钟以上,如果机器规模超过100台以上,假设每次发布的步长为10%,单次发布需要就需要100分钟之久。因此,急需一种方法能够将应用的不同模块的解耦,降低开发和部署成本。

服务化

用通俗的话来讲,服务化就是把传统的单机应用中通过JAR包依赖产生的本地方法调用,改造成通过RPC接口产生的远程方法调用。一般在编写业务代码时,对于一些通用的业务逻辑,把它抽象并独立成为专门的模块,因为这对于代码复用和业务理解都大有裨益。

通过服务化,可以解决单体应用膨胀、团队开发耦合度高、协作效率低下的问题。

服务化拆分

从我所经历过的多个项目来看,项目第一阶段的主要目标是快速开发和验证想法,证明产品思路是否可行。这个阶段功能设计一般不会太复杂,开发采取快速迭代的方式,架构也不适合过度设计。所以将所有功能打包部署在一起,集中地进行开发、测试和运维,对于项目起步阶段,是最高效也是最节省成本的方式。当可行性验证通过,功能进一步迭代,就可以加入越来越多的新特性。

比如做一个社交App,初期为了快速上线,验证可行性,可以只开发首页信息流、评论等基本功能。产品上线后,经过一段时间的运营,用户开始逐步增多,可行性验证通过,下一阶段就需要进一步增加更多的新特性来吸引更多的目标用户,比如再给这个社交App添加个人主页显示、消息通知等功能。

一般情况下,这个时候就需要大规模地扩张开发人员,以支撑多个功能的开发。如果这个时候继续采用单体应用架构,多个功能模块混杂在一起开发、测试和部署的话,就会导致不同功能之间相互影响,一次打包部署需要所有的功能都测试OK才能上线。

不仅如此,多个功能模块混部在一起,对线上服务的稳定性也是个巨大的挑战。比如A开发的一个功能由于代码编写考虑不够全面,上线后产生了内存泄漏,运行一段时间后进程异常退出,那么部署在这个服务池中的所有功能都不可访问。一个经典的案例就是,曾经有一个视频App,因为短时间内某个付费视频访问量巨大,超过了服务器的承载能力,造成了这个视频无法访问。不幸的是,这个网站付费视频和免费视频的服务部署在一起,也波及了免费视频,几乎全站崩溃。

根据我的实际项目经验,一旦单体应用同时进行开发的人员超过10人,就会遇到上面的问题,这个时候就该考虑进行服务化拆分了。

那么服务化拆分具体该如何实施呢?一个最有效的手段就是将不同的功能模块服务化,独立部署和运维。以前面提到的社交App为例,你可以认为首页信息流是一个服务,评论是一个服务,消息通知是一个服务,个人主页也是一个服务。

这种服务化拆分方式是纵向拆分,是从业务维度进行拆分。标准是按照业务的关联程度来决定,关联比较密切的业务适合拆分为一个微服务,而功能相对比较独立的业务适合单独拆分为一个微服务。

还有一种服务化拆分方式是横向拆分,是从公共且独立功能维度拆分。标准是按照是否有公共的被多个其他服务调用,且依赖的资源独立不与其他业务耦合。

继续以前面提到的社交App举例,无论是首页信息流、评论、消息箱还是个人主页,都需要显示用户的昵称。假如用户的昵称功能有产品需求的变更,你需要上线几乎所有的服务,这个成本就有点高了。显而易见,如果我把用户的昵称功能单独部署成一个独立的服务,那么有什么变更我只需要上线这个服务即可,其他服务不受影响,开发和上线成本就大大降低了。

但并不是说功能拆分的越细越好,过度的拆分反而会让服务数量膨胀变得难以管理,因此找到符合自己业务现状和团队人员技术水平的拆分粒度才是可取的。建议的标准是按照每个开发人员负责不超过3个大的服务为标准,毕竟每个人的精力是有限的,所以在拆分微服务时,可以按照开发人员的总人数来决定。

服务化拆分的前置条件:

下面几个问题,是从单体应用迁移到微服务架构时必将面临也必须解决的。

  • 服务如何定义。对于单体应用来说,不同功能模块之前相互交互时,通常是以类库的方式来提供各个模块的功能。对于微服务来说,每个服务都运行在各自的进程之中,应该以何种形式向外界传达自己的信息呢?答案就是接口,无论采用哪种通讯协议,是HTTP还是RPC,服务之间的调用都通过接口描述来约定,约定内容包括接口名、接口参数以及接口返回值。

  • 服务如何发布和订阅。单体应用由于部署在同一个WAR包里,接口之间的调用属于进程内的调用。而拆分为微服务独立部署后,服务提供者该如何对外暴露自己的地址,服务调用者该如何查询所需要调用的服务的地址呢?这个时候就需要一个类似登记处的地方,能够记录每个服务提供者的地址以供服务调用者查询,在微服务架构里,这个地方就是注册中心。

  • 服务如何监控。通常对于一个服务,我们最关心的是QPS(调用量)、AvgTime(平均耗时)以及P999(99.9%的请求性能在多少毫秒以内)这些指标。这时候就需要一种通用的监控方案,能够覆盖业务埋点、数据收集、数据处理,最后到数据展示的全链路功能。

  • 服务如何治理。可以想象,拆分为微服务架构后,服务的数量变多了,依赖关系也变复杂了。比如一个服务的性能有问题时,依赖的服务都势必会受到影响。可以设定一个调用性能阈值,如果一段时间内一直超过这个值,那么依赖服务的调用可以直接返回,这就是熔断,也是服务治理最常用的手段之一。

  • 故障如何定位。在单体应用拆分为微服务之后,一次用户调用可能依赖多个服务,每个服务又部署在不同的节点上,如果用户调用出现问题,需要有一种解决方案能够将一次用户请求进行标记,并在多个依赖的服务系统中继续传递,以便串联所有路径,从而进行故障定位。

针对上述问题,必须有可行的解决方案之后,才能进一步进行服务化拆分。

微服务化

微服务的概念最早是在2014年由Martin Fowler和James Lewis共同提出,他们定义了微服务是由单一应用程序构成的小服务,拥有自己的进程与轻量化处理,服务依赖,业务功能设计,以全自动的方式部署,与其他服务使用HTTP API通讯。同时,服务会使用最小规模的集中管理 (例如Docker)技术,服务可以用不同的编程语言与数据库等。

微服务就是将庞杂臃肿的单体应用拆分成细粒度的服务,独立部署,并交给各个中小团队来负责开发、测试、上线和运维整个生命周期。

微服务相比于服务化又有什么不同呢?在我看来,可以总结为以下四点:

  • 服务拆分粒度更细。微服务可以说是更细维度的服务化,小到一个子模块,只要该模块依赖的资源与其他模块都没有关系,那么就可以拆分为一个微服务。

  • 服务独立部署。每个微服务都严格遵循独立打包部署的准则,互不影响。比如一台物理机上可以部署多个Docker实例,每个Docker实例可以部署一个微服务的代码。

  • 服务独立维护。每个微服务都可以交由一个小团队甚至个人来开发、测试、发布和运维,并对整个生命周期负责。

  • 服务治理能力要求高。因为拆分为微服务之后,服务的数量变多,因此需要有统一的服务治理平台,来对各个服务进行管理。

微服务架构

419b77a39e6b7e4fcaa5456aa9d9253b.png

首先服务提供者(就是提供服务的一方)按照一定格式的服务描述,向注册中心注册服务,声明自己能够提供哪些服务以及服务的地址是什么,完成服务发布。

接下来服务消费者(就是调用服务的一方)请求注册中心,查询所需要调用服务的地址,然后以约定的通信协议向服务提供者发起请求,得到请求结果后再按照约定的协议解析结果。

而且在服务的调用过程中,服务的请求耗时、调用量以及成功率等指标都会被记录下来用作监控,调用经过的链路信息会被记录下来,用于故障定位和问题追踪。在这期间,如果调用失败,可以通过重试等服务治理手段来保证成功率。

总结一下,微服务架构下,服务调用主要依赖下面几个基本组件:

  • 服务描述

  • 注册中心

  • 服务框架

  • 服务监控

  • 服务追踪

  • 服务治理

不管是采用开源方案还是自行研发,都必须吃透每个组件的工作原理并能在此基础上进行二次开发。

采用微服务架构,可以帮助我们很好的解决实际业务场景中的问题:

  • 从组织层面讲,系统的分工更加明确,责任更加清晰。

  • 按照业务来划分服务,单个服务代码量小,业务单一,更易于维护。

  • 服务能力更专一,代码重复度降低,可复用性高,使服务调用更加简便。

  • 服务之间耦合性低,可随时加入服务,剔除过时服务。

  • 单个服务也可以弹性扩展,容错能力更强。

  • 更贴合云时代背景的软件开发运维环境,高效自动化构建部署。

  • 数据存储解耦,不必将所有数据集中在一个大库中,不易拆分扩容。


整理自:从 0 开始学微服务

用通俗的方式介绍大语言模型训练过程

2026-01-18 15:14:10

站在大语言模型外部看需要准备些什么样的训练数据,分什么阶段,怎样去训练大语言模型,把大语言模型看成一个黑盒。

LLM都是如何训练出来的呢?

GPT的训练分为以下3个阶段:

1、预训练Pretrain

2、监督微调SFT (Supervised Fine-Tuning)

3、基于反馈的强化学习RLHF(包含了Reward Model、PPO (Proximal Policy Optimization)

一、Pretrain预训练

在这个阶段,我们不教他具体的专业技能(比如怎么写代码、怎么当医生),而是让他进行海量的广泛阅读

Pretrain的三个核心步骤:

1. 喂给它海量的书(数据输入)

研究人员把互联网上能找到的几乎所有高质量文本——百科全书、小说、新闻、代码库、论文——全部打包,喂给这个模型。

量级: 这相当于让一个人读了几千万辈子的书。

2. 玩“完形填空”游戏(学习机制)

模型在读这些书时,主要在做一件事:猜下一个字是什么

  • 例子: 看到“床前明月__”,模型要猜出是“光”。

  • 原理: 如果猜对了,给予奖励(参数调整);如果猜错了(比如猜成了“饼”),就狠狠修正。

  • 结果: 通过千亿次的猜测和修正,模型学会了语言的语法、词语的搭配,以及人类世界的逻辑和常识(比如知道“水是流动的”、“1+1=2”)。

3. 产出“基座模型”(Base Model)

预训练结束后,我们就得到了一个基座模型

  • 它的状态: 它博学多才,什么都知道一点,懂中文也懂英文。

  • 它的缺点: 它还很“野”,不懂礼貌,也不知道怎么听指令。如果你问它“怎么做番茄炒蛋?”,它可能会给你续写一段关于番茄的历史,而不是给你食谱。因为它只会“接着往下写”,还不会“回答问题”。

总之,Pretrain(预训练) = 通过海量阅读,学会语言规律和世界知识的过程它是大模型最昂贵、最耗时的阶段(通常需要几千张显卡跑几个月),是模型能力的地基。这个阶段的大模型说起话来非常像“接话茬”,并不是在“做任务”。

二、SFT(Supervised Fine-Tuning)有监督的微调训练

如果说 Pretrain(预训练) 是让模型在图书馆里泛读万卷书,成了一个博学的“书呆子”。

那么 SFT(Supervised Fine-Tuning,有监督微调) 就是给这个书呆子进行“职业技能培训”,或者是找了个“金牌导师”手把手教它怎么说话

在这个阶段,我们的目标不再是让它“学知识”,而是让它“懂规矩”

以下是 SFT 的三个核心变化:

1. 教材变了:从“无字天书”到“习题集”

在预训练阶段,我们给它看的是并没有标注重点的海量文本。而在 SFT 阶段,我们给它看的是高质量的问答对(Q&A)

人类老师(标注员) 会编写成千上万个例子,告诉模型:“当用户问这个问题时,标准的回答应该是这样的。”

例子:

  • 输入: “请把这段话翻译成英文。”

  • 标准答案: “Please translate this sentence into English.”

量级: 相比预训练的数据海,SFT 的数据量要小得多(通常是几万到几十万条),但质量要求极高。

2. 教学方式变了:从“瞎猜”到“模仿”

  • Pretrain 时: 模型看到“番茄炒蛋”,可能会接着写“是一道中国名菜,起源于……”(因为它在做文本接龙)。

  • SFT 时: 老师拿着戒尺站在旁边。模型看到“番茄炒蛋怎么做?”,如果它还想背历史课文,老师会敲它一下,指着标准答案说:“不对!这时候你应该列出步骤:第一步打蛋,第二步切番茄……”

  • 效果: 模型开始模仿人类说话的语气、格式和逻辑。它学会了:当人类用问句开头时,我应该提供答案,而不是接着编故事。

3. 身份变了:从“复读机”到“助手”

经过 SFT 之后,这个模型就发生了质的飞跃:

  • 听懂指令: 你让它缩写文章,它就不会扩写;你让它写代码,它就不会写诗。

  • 格式规范: 它学会了分点作答、使用礼貌用语。

  • ChatGPT 的诞生: GPT-3 只是一个预训练模型(书呆子),而 ChatGPT 则是经过了 SFT(以及后续步骤)后的产物(智能助手)。

小结,SFT(指令微调) = 通过高质量的问答范例,教模型如何正确地使用它学到的知识来服务人类。它让模型从“懂语言”变成了“懂人话”。

三、RLHF(Reinforcement Learning from Human Feedback)基于人类反馈的强化学习

如果说 Pretrain(预训练) 造就了博学的“书呆子”,SFT(微调) 把它变成了能干活的“职场新人”;

那么 RLHF(Reinforcement Learning from Human Feedback,人类反馈强化学习) 就是给这个新人上的“思想品德课”“情商培训班”

在 SFT 阶段,模型虽然学会了回答问题,但它有个大毛病:它不知道哪个答案更好,甚至为了“回答”而撒谎或干坏事。 比如你问“怎么偷东西?”,SFT 模型可能会很详细地教你(因为它觉得回答就是它的工作)。

RLHF 就是为了解决这个问题,给模型“树立三观”以下是通俗易懂的两个关键环节:

1. 建立“评分老师”(训练奖励模型Reward Model)

我们不再让老师手把手写标准答案了(太累了),而是让模型针对同一个问题生成两个不同的回答,让老师来打分(或者二选一)。

场景: 用户问“怎么制作炸弹?”

  • 回答 A: 详细列出化学配方(虽然准确,由于危险,人类老师判0分)。

  • 回答 B: “制作炸弹是违法的,我不能帮你,但我可以介绍炸药的历史。”(虽然拒绝了,但符合安全规范,人类老师判100分)。

  • 回答 C: 胡言乱语(50分)。

结果: 我们训练出了一个专门的“判卷老师”(Reward Model,奖励模型)。这个判卷老师并不生产内容,它唯一的任务就是:判断这句话人类爱不爱听,符不符合人类的价值观。

2. 疯狂刷题拿奖励(强化学习PPO)

现在,让大模型开始大量做题,然后让刚才训练好的“判卷老师”给它打分。

机制(类似训狗):

  • 如果模型生成了一段话,判卷老师给了高分(比如态度谦逊、内容安全),系统就给模型发一个“糖果”(正向反馈),告诉它:“做得好,以后多这样说话!”

  • 如果判卷老师给了低分(比如脏话、撒谎、种族歧视),系统就给它一个“惩罚”(负向反馈),告诉它:“错了,以后这种话少说!”

进化: 模型为了以此获得更多的“糖果”,会拼命调整自己的说话方式,去迎合“判卷老师”的喜好。

3. “三观”树立完成

经过 RLHF,模型不仅仅是“能说话”,它变成了符合 3H 原则 的模范生:

  • Helpful(有帮助): 甚至能从你模糊的问题中猜到你想问什么。

  • Honest(诚实): 不懂装懂的情况变少了(虽然还不能完全根除)。

  • Harmless(无害): 拒绝回答违法、暴力、色情的问题,变得非常有“求生欲”。

SFT及RLHF的过程如下图所示:

640.png

转载自:https://mp.weixin.qq.com/s/-lflK8HR_IkughslJ2s3_g


参考:

JMeter混合场景压测

2026-01-13 22:39:03

最近客户项目上线前小程序需要做性能压测,使用JMeter做了下单场景和混合场景压测,Mark一下。

压测任务需求(示例):

测试类型

测试项

事务

并发用户数

需求指标

执行时间(分)

事务成功率指标要求

平均响应时间(秒)

TPS

事务成功率

压力测试

首页

首页版面版本检测

3000

3秒

10

99%




首页版面下载

3000

3秒

10

99%




登录小程序

小程序版本检测

3000

3秒

10

99%




登录小程序下载

3000

3秒

10

99%




合场景

首页(占比50%)

首页版面版本检测

首页版面下载

1000

3秒

10

99%




登录小程序(占比50%)

小程序版本检测
登录小程序下载

1000

3秒

10

99%




JMeter官方地址:https://jmeter.apache.org/

jmeter控制台启动:

cd apache-jmeter-5.6.3/bin

sh jmeter.sh

image.png

图形化模式适用于调试,不要进行压测

图形化的压测方式会消耗较多的客户端性能,在压测过程中容易因为客户端问题导致内存溢出。官方不推荐我们使用图形化界面,推荐使用命令行方式执行测试脚本:

jmeter -n -t [jmx file] -l [results file] -e -o [Path to web report folder]

命令行执行的方式同样适用于 Windows、Mac 和 Linux 系统,不需要纠结系统兼容性的问题。相对于命令行执行,界面化的方式更为简单、方便,但命令行执行也并不是完美无缺的。

命令行执行参数含义:

jmeter -n -t [jmx file] -l [results file] -e -o [Path to web report folder]

  • -n 表示在非 GUI 模式下运行 JMeter;

  • -t 表示要运行的 JMeter 测试脚本文件,一般是 jmx 结尾的文件;

  • -l 表示记录结果的文件,默认以 jtl 结尾;

  • -e 表示测试完成后生成测试报表;

  • -o 表示指定的生成结果文件夹位置。

混合场景压测使用的是多线程组方案。白屏编辑好测试计划,然后导出 test.jmx 到压测机器,进入 jmeter 工具 apache-jmeter-5.6.3/bin 路径,黑屏执行:

nohup ./jmeter -n -t test.jmx -l result.jtl > jmeter-run.log 2>&1 & echo $! > jmeter.pid

生成html测试报告:

./jmeter -g result.jtl -e -o ./HtmlReport

相关参考:

K8s HPA原理及最佳实践

2026-01-12 10:28:12

在Kubernetes整个体系中,弹性伸缩是至关重要的功能,其分为两种:水平弹性伸缩(Horizontal Pod Autoscaling,简称HPA)垂直弹性伸缩(Vertical Pod Autoscaler,简称VPA)。HPA可以根据观察到的资源实际使用情况(如CPU/内存)或其它自定义指标负载自动调整Pod副本数,VPA可以根据资源实际使用情况为其Pod中的容器设置最新的资源配额requests。这两者合理使用既可以满足业务对实例数的要求保证系统稳定性,同时又能充分提升集群资源的利用率。

1758179126855162.png

一、HPA 原理

HPA (Horizontal Pod Autoscaler) 的核心原理可以概括为:定期查询监控指标,然后根据其目标值(Target Value) 和当前值(Current Value),通过特定的算法计算出所需的 Pod 副本数量,自动控制 Deployment、StatefulSet 等资源的副本数量,以使应用的平均资源利用率(如 CPU、内存)维持在你设定的目标值

1758176659278527.png

下面是图中涉及的核心组件及其作用的文字说明:

  • HPA Controller:HPA 的“决策大脑”,是 kube-controller-manager 的一部分。它负责[定期循环](默认每隔 15秒)通过 API Server 查询其管理的所有 HPA 对象及其对应的监控指标,并根据指标数据计算是否需要扩缩容以及需要多少个 Pod 副本

  • API Server:Kubernetes 的“信息枢纽”。HPA Controller 通过它查询 HPA 配置、获取监控指标,也是通过它来更新工作负载的副本数量。

  • Metrics API指标数据的标准化接口。HPA Controller 只通过统一的 Metrics API 来获取指标数据,而不关心数据的具体来源。这层抽象使得 HPA 可以灵活地使用不同种类的指标。

    • resource.metrics.k8s.io:提供资源指标,如 Pod 的 CPU 和内存使用率,通常由 Metrics Server 实现

    • custom.metrics.k8s.io:提供自定义指标,这些指标通常与特定应用或 Kubernetes 对象(如 Pod 的 QPS)相关。常用 Prometheus Adapter 来从 Prometheus 中获取并暴露这些指标

    • external.metrics.k8s.io:提供外部指标,这些指标完全来自于 Kubernetes 集群之外的系统,如消息队列的长度、云服务的监控指标等。

  • Metrics Server:一个集群范围内的资源使用数据聚合器。它从每个节点上的 kubelet 收集核心资源指标(如 CPU 和内存),并通过 resource.metrics.k8s.io API 暴露它们,供 HPA 等组件使用。

  • Prometheus + Prometheus Adapter:一个常见的自定义指标方案Prometheus 负责采集和存储丰富的监控数据。Prometheus Adapter 则作为一个桥梁,查询 Prometheus 中的数据,并将其转换为 Kubernetes 能够理解的格式,通过 custom.metrics.k8s.io API 暴露给 HPA Controller

HPA 的工作流程是一个持续的控制循环,上图的数字编号也大致对应了这些步骤:

  1. 查询 HPA 配置:HPA Controller 定期(默认 15s,可通过 --horizontal-pod-autoscaler-sync-period 参数调整)通过 API Server 查询所有 HPA 对象的配置定义,包括目标工作负载、目标指标类型及其目标值、最小/最大副本数等。

  2. 获取监控指标:对于每个 HPA,Controller 通过对应的 Metrics API(资源、自定义或外部)来获取当前的监控指标值。

  • 源指标(如 CPU、内存)由 Metrics Server 提供。Metrics Server 通过 Kubelet 从每个节点的 cAdvisor 中采集所有 Pod 的资源使用数据,并聚合到集群层面。

    1. metrics.k8s.io: 主要提供Pod和Node的CPU和Memory相关的监控指标。

    2. custom.metrics.k8s.io: 主要提供Kubernetes Object相关的自定义监控指标。

    3. external.metrics.k8s.io:指标来源外部,与任何的Kubernetes资源的指标无关。

  • 自定义指标(如 QPS、应用内部指标)由 Kubernetes Custom Metrics API 提供,这通常需要安装像 Prometheus Adapter 这样的组件来将 Prometheus 等监控系统的指标转换为 Kubernetes API 可以理解的格式。

  • 计算所需副本数:Controller 使用获取到的当前指标值和 HPA 中定义的目标指标值,通过算法计算出期望的 Pod 副本数量。

    • 计算公式期望副本数 = ceil[当前副本数 * (当前指标值 / 目标指标值)]

      例如,当前有 2 个 Pod,CPU 使用率为 80%,HPA 目标 CPU 使用率为 40%,则期望副本数 = ceil[2 * (80 / 40)] = ceil[4] = 4。HPA 会考虑多种指标(选择计算结果中最大的副本数)并内置了容忍度(默认 0.1,即比率变化不超过 10% 则忽略)和冷却窗口等机制来防止副本数抖动

  • 调整副本数:如果计算出的期望副本数与当前副本数不同,HPA Controller 会通过 API Server更新目标工作负载(如 Deployment)的 replicas 字段,为了避免副本数量因指标瞬时波动而剧烈变化(“抖动”),HPA 引入了冷却机制

    • 扩容:如果计算出的 期望副本数 > 当前副本数,HPA 就会增加副本数。

      扩容冷却 (默认 3 分钟):扩容操作没有全局等待期,但快速连续扩容时,每次扩容后等待时间会逐渐增加。

    • 缩容:如果计算出的 期望副本数 < 当前副本数,HPA 就会减少副本数。

      缩容冷却 (--horizontal-pod-autoscaler-downscale-stabilization,默认 5 分钟):在一次缩容操作后,会等待一段时间(默认5分钟),再执行下一次缩容操作。

  • 工作负载控制器响应:相应的工作负载控制器(如 Deployment Controller)检测到 replicas 字段变化,会开始创建删除 Pod,以使实际运行的 Pod 数量等于期望值。

  • 调度新 Pod:新创建的 Pod 会被 Scheduler 调度到合适的节点上运行。如果节点资源不足,这些 Pod 会处于 Pending 状态。

  • 节点扩缩容(可选):如果集群中使用了 Cluster Autoscaler (CA),它会检测到因资源不足而无法调度的 Pod(Pending状态),然后自动扩容集群节点来提供更多资源。Pod 被成功调度和运行后,才能开始处理业务流量,此时指标数据也会随之变化,HPA 的下一个循环周期又会开始。

  • 在实际应用中,基于原生HPA系统架构实现适配业务自定义特性的整体设计架构如下:

    171a62d0e6002878~tplv-t2oaga2asx-jj-mark_3024_0_0_0_q75.png

    二、基于 CPU 和内存的最佳实践示例

    前提条件

    1. 安装 Metrics Server

    • HPA 需要 Metrics Server 来获取 CPU/内存指标。

    • 安装命令(对于大多数集群):

    kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
    • 验证安装:kubectl top nodes

  • 为 Pod 设置资源请求

    • 这是最关键的一步! HPA 计算使用率的公式是:(Pod 当前实际使用量 / Pod 的资源请求值) * 100%

    • 如果 Pod 没有设置 spec.containers[].resources.requests,HPA 将无法进行有意义的计算。

    示例:为 Nginx Deployment 配置同时基于 CPU 和内存的 HPA

    1. 创建 Deployment (nginx-deployment.yaml)

    这个 Deployment 明确设置了 CPU 和内存的 requests

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: nginx-deployment
      labels:
        app: nginx
    spec:
      replicas: 3 # 初始副本数
      selector:
        matchLabels:
          app: nginx
      template:
        metadata:
          labels:
            app: nginx
        spec:
          containers:
          - name: nginx
            image: nginx:1.25
            ports:
            - containerPort: 80
            resources:
              requests:
                cpu: "250m"   # 每个 Pod 请求 0.25 核 CPU
                memory: "256Mi" # 每个 Pod 请求 256MiB 内存
              limits:
                cpu: "500m"
                memory: "512Mi"

    应用它:kubectl apply -f nginx-deployment.yaml

    2. 创建 HPA (nginx-hpa.yaml)

    我们使用 autoscaling/v2 API,它支持多指标和更丰富的配置。

    apiVersion: autoscaling/v2
    kind: HorizontalPodAutoscaler
    metadata:
      name: nginx-hpa
    spec:
      scaleTargetRef:
        apiVersion: apps/v1
        kind: Deployment
        name: nginx-deployment
      minReplicas: 2   # 最小副本数
      maxReplicas: 10  # 最大副本数
      metrics:
      - type: Resource
        resource:
          name: cpu
          target:
            type: Utilization # 目标类型为利用率
            averageUtilization: 50 # CPU 平均使用率目标为 50%
      - type: Resource
        resource:
          name: memory
          target:
            type: Utilization # 目标类型为利用率
            averageUtilization: 70 # 内存平均使用率目标为 70%
      behavior: # (可选) 高级伸缩行为控制
        scaleDown:
          stabilizationWindowSeconds: 300 # 缩容冷却期 300 秒 (5分钟)
          policies:
          - type: Pods
            value: 2
            periodSeconds: 60
        scaleUp:
          stabilizationWindowSeconds: 0
          policies:
          - type: Pods
            value: 2
            periodSeconds: 60

    应用它:kubectl apply -f nginx-hpa.yaml

    HPA 行为解释

    • CPU 规则:HPA 会努力使所有 Pod 的 CPU 平均使用率 维持在 50%

      • 如果平均使用率是 75%,期望副本数 = ceil[3 * (75 / 50)] = ceil[3 * 1.5] = ceil[4.5] = 5。HPA 会将副本数扩容到 5。

    • 内存规则:HPA 会努力使所有 Pod 的 内存平均使用率 维持在 70%

      • 如果平均使用率是 85%,期望副本数 = ceil[3 * (85 / 70)] = ceil[3 * 1.21] = ceil[3.63] = 4

    • 多指标决策:当同时配置了多个指标时,HPA 会分别计算每个指标所需的副本数,然后选择其中最大的那个值

      • 在上面的例子中,CPU 算出来需要 5 个,内存算出来需要 4 个,那么 HPA 最终会将副本数扩容到 5。

    验证与监控

    1.查看 HPA 状态

    kubectl get hpa nginx-hpa -w

    输出会显示当前的指标值、目标值、最小/最大副本数和当前的副本数。

    NAME         REFERENCE                       TARGETS             MINPODS   MAXPODS   REPLICAS   AGE
    nginx-hpa    Deployment/nginx-deployment    50%/70%, 45%/65%    2         10        3          5m
    # TARGETS 列显示为 CPU目标%/内存目标%,当前CPU%/当前内存%

    2.查看 HPA 详细信息

    kubectl describe hpa nginx-hpa

    这个命令会输出非常详细的信息,包括事件日志,帮助你诊断 HPA 为什么不伸缩。

    三、最佳实践总结

    1. 必须设置 resources.requests:没有它,HPA 无法工作。

    2. 使用 autoscaling/v2 APIv2 比 v2beta2 更稳定,且功能强大。

    3. 谨慎设置目标值

    • 目标值设置过高(如 CPU 90%)可能导致 Pod 在触发扩容前就已过载。

    • 目标值设置过低(如 CPU 10%)可能导致资源浪费和过多的副本。

    • 建议:从保守值开始(如 CPU 50-70%),根据实际生产监控数据进行调整。

  • 合理配置 minReplicas 和 maxReplicas

    • minReplicas 应保证应用的基本可用性。

    • maxReplicas 应避免因意外流量或程序 Bug 导致资源耗尽。

  • 利用 behavior 字段控制伸缩速率和冷却:根据应用的启动和冷却特性,调整 scaleUp 和 scaleDown 策略,避免抖动。

  • 结合就绪探针和存活探针:确保新扩容的 Pod 真正准备好接收流量后,才被纳入服务的负载均衡。

  • 结合日志与事件:注意容忍度、冷却窗口对扩缩容的影响,关注 ScalingActive、ScalingLimited 等状态变化。

  • 考虑使用自定义指标:对于 Web 服务,基于 HTTP QPS(每秒请求数)进行扩缩容通常比基于 CPU 更直接、更灵敏。这需要安装 Prometheus 和 Prometheus Adapter。

  • 让应用支持平滑启动和终止:确保新 Pod 启动后能快速就绪(利用就绪探针),并在终止时能优雅处理完已有请求(处理 SIGTERM 信号),避免服务中断。

  • 四、关于 HPA 的进阶思考

    1. HPA 的局限性及与其他自动伸缩方案的搭配

    HPA 主要负责 Pod 水平扩缩容,但它依赖于集群有足够的资源来调度新创建的 Pod。如果集群资源不足,即使 HPA 创建了新的 Pod,这些 Pod 也会因无法调度而处于 Pending 状态,无法提供服务。

    因此,在生产环境中,HPA 通常需要与 Cluster Autoscaler (CA) 或 Karpenter 搭配使用

    • HPA: 负责应用层伸缩,根据业务负载调整 Pod 数量。

    • CA/Karpenter: 负责基础设施层伸缩,根据 Pod 的资源请求调整集群节点数量,为 HPA 创建的 Pod 提供运行资源

    2. 克服“弹性滞后”:AHPA 与预测性伸缩

    传统 HPA 基于当前的监控指标进行反应式扩缩容,这存在一定的“弹性滞后”(Reactive Scaling)。即从流量增加到 HPA 完成扩容需要一定时间(指标采集、计算、Pod 启动、应用预热等),可能导致流量高峰时应用响应变慢

    为解决这个问题,出现了预测性弹性伸缩(Predictive Scaling)方案,如 Advanced Horizontal Pod Autoscaler (AHPA)。AHPA 能够分析历史指标数据(如过去几天的 CPU 负载、QPS 变化规律),预测未来的负载波动,并提前进行扩容。例如,对于每天早高峰流量明显增大的应用,AHPA 可以在高峰来临前就提前扩容,避免流量激增时的响应延迟。在业务低谷时,它也会定时回收资源,节约成本


    参考:

    vLLM 部署Qwen2.5 LLM &amp; VLM 大模型

    2026-01-05 21:16:14

    Qwen2.5-32B 和 Qwen2.5-VL-32B 是通义千问(Qwen)系列中的两个大模型,分别对应纯语言模型(LLM)和多模态视觉-语言模型(VLM)。Docker环境安装与配置 NVIDIA Container Toolk,下载大模型参考 Docker部署bge-m3/bge-reranker模型

    模型简介

    模型名称 类型 参数量 特点
    Qwen2.5-32B 纯文本语言模型 ~32B 支持中英文,推理、代码、对话能力强
    Qwen2.5-VL-32B 视觉-语言多模态模型 ~32B(含视觉编码器) 支持图像理解、图文问答、多模态推理

    注意:Qwen2.5-VL 基于 Qwen2.5 语言主干 + 视觉编码器(如 SigLIP 或 CLIP 变体),需同时加载视觉和语言组件。

    部署前的硬件准备

    部署前,需确认硬件环境满足基本要求。由于Qwen2.5-VL-32B是多模态模型,其显存需求通常高于纯文本模型。

    资源 Qwen2.5-32B (纯文本) Qwen2.5-VL-32B (多模态) 说明
    GPU显存 建议 ≥ 24GB (单卡) 建议 ≥ 32GB (单卡或多卡) 使用量化版本(如GPTQ-Int4)可大幅降低显存需求。
    系统内存 建议 32GB+ 建议 64GB+ 避免模型加载时内存溢出(OOM)
    磁盘空间 模型文件约需 60GB+ 模型文件约需 70GB+ 需预留额外空间存放Docker镜像和依赖。

    OpenLLM - Qwen

    model    version                repo     required GPU RAM    platforms
    -------  ---------------------  -------  ------------------  -----------
    qwen2.5  qwen2.5:0.5b           default  12G                 linux
             qwen2.5:1.5b           default  12G                 linux
             qwen2.5:3b             default  12G                 linux
             qwen2.5:7b             default  24G                 linux
             qwen2.5:14b            default  80G                 linux
             qwen2.5:14b-ggml-q4    default                      macos
             qwen2.5:14b-ggml-q8    default                      macos
             qwen2.5:32b            default  80G                 linux
             qwen2.5:32b-ggml-fp16  default                      macos
             qwen2.5:72b            default  80Gx2               linux
             qwen2.5:72b-ggml-q4    default                      macos

    硬件要求

    • Qwen2.5-32B(纯文本)

      • 推荐:2×A100 80GB(FP16)或 4×A10 24GB(INT4 量化)

      • 最低:单卡 A100 80GB(INT4)

    • Qwen2.5-VL-32B(多模态)

      • 推荐:2×A100 80GB(FP16)或 4×A10 24GB(INT4)

      • 需额外加载视觉编码器(如 ViT),显存需求略高

    基于vLLM的Docker部署通用流程

    下面以 vLLM框架 为例,介绍在Docker中部署这两个模型的通用步骤。vLLM是一个高效的推理和服务框架

    步骤一:准备环境与下载模型

    1.安装依赖:确保系统已安装DockerNVIDIA驱动NVIDIA Container Toolkit(使Docker支持GPU)。

    2.下载模型:推荐使用国内镜像源(如ModelScope)下载模型到本地目录。

    # 示例:通过ModelScope下载Qwen2.5-32B-Instruct
    pip install modelscope
    modelscope download --model Qwen/Qwen2.5-32B-Instruct --local_dir /your/local/model/path

    3.拉取Docker镜像:拉取官方vLLM镜像。

    docker pull vllm/vllm-openai:latest

    步骤二:启动Docker容器并运行模型

    使用以下命令启动容器。请务必将命令中的 /your/local/model/path 和 Qwen2.5-32B-Instruct 替换为你实际部署的模型路径和名称(例如,部署VL模型时需替换为Qwen2.5-VL-32B-Instruct)。

    docker run -d \
      --gpus all \
      -p 8000:8000 \
      -v /your/local/model/path:/model \  # 将宿主机模型目录挂载到容器
      --name vllm-qwen \
      vllm/vllm-openai:latest \
      --model /model \  # 容器内的模型路径
      --served-model-name Qwen2.5-32B \  # 服务名称,可按需修改
      --tensor-parallel-size 1 \  # 使用的GPU数量,单卡设为1
      --gpu-memory-utilization 0.9 \  # GPU内存利用率,可调整以防OOM
      --max-model-len 8192 \  # 最大序列长度,可根据需要调整
      --trust-remote-code  # Qwen模型需要此参数

    部署 Qwen2.5-VL-32B-Instruct

    docker run -d \
      --gpus all \  # 允许容器使用所有GPU
      -p 8000:8000 \  # 映射端口(主机端口:容器端口)
      -v /your/local/model/path:/model \  # 挂载模型目录到容器内
      --name vllm-qwen-vl \  # 容器名称
      vllm/vllm-openai:latest \  # 使用的镜像
      --model /model \  # 容器内模型路径 外部调用时入参model 需要与此处相同 注意此处有 /
      --served-model-name Qwen2.5-VL-32B \  # 服务名称,可按需修改
      --port 8000 \  # 容器内服务端口
      --host 0.0.0.0 \  # 允许外部访问
      --tensor-parallel-size 1 \  # 若单卡则设1,多卡按实际数量调整
      --gpu-memory-utilization 0.9 \  # 允许使用90%的GPU内存(避免OOM)
      --trust-remote-code  # 信任远程模型代码(Qwen模型需要)

    关键参数说明

    • --tensor-parallel-size:根据你使用的GPU数量设置。例如,单卡设为1,双卡可设为2

    • --gpu-memory-utilization控制显存使用率。如果启动时出现内存不足(OOM)错误,可以尝试降低此值(如调整为0.8)。

    • --max-model-len:根据模型支持的上下文长度和你的需求调整。

    • --dtype float16:模型运行的数据类型:float16 是半精度浮点类型,相比 float32 可减少 GPU 内存占用(约节省一半),同时保持较好的推理精度。Qwen2.5 模型支持 float16,推荐使用(若 GPU 支持 bfloat16,也可改为 bfloat16)。

    测试与验证部署

    服务启动后,可以通过以下方式验证模型是否正常工作:

    1.检查服务状态

    curl http://localhost:8000/health

    如果返回 {"status":"OK"} 或类似信息,说明服务已就绪

    2.发送推理请求进行测试

    Qwen2.5-32B (文本):直接向其 /v1/completions 或 /v1/chat/completions 端点发送文本Prompt。

    1. 测试聊天接口(推荐)

    curl http://localhost:8000/v1/chat/completions \
      -H "Content-Type: application/json" \
      -d '{
        "model": "Qwen2.5-32B",
        "messages": [
          {"role": "user", "content": "你好,请介绍一下你自己。"}
        ],
        "max_tokens": 100,
        "temperature": 0.7
      }'

    2. 测试流式输出(streaming)

    curl http://localhost:8000/v1/chat/completions \
      -H "Content-Type: application/json" \
      -d '{
        "model": "Qwen2.5-32B",
        "messages": [{"role": "user", "content": "写一首关于春天的诗。"}],
        "stream": true,
        "max_tokens": 150
      }'

    Qwen2.5-VL-32B (多模态):需要构建一个包含图像和文本的多模态请求。可以参考官方GitHub仓库中的示例代码

    自定义 FastAPI + Transformers

    1. Dockerfile(多模态)

    # Dockerfile(多模态)
    FROM nvcr.io/nvidia/pytorch:24.06-py3
    
    RUN pip install --upgrade pip && \
        pip install "transformers>=4.40" "accelerate" "torch" "torchvision" \
        "pillow" "fastapi" "uvicorn" "einops" "timm" "qwen-vl-utils" \
        -i https://pypi.tuna.tsinghua.edu.cn/simple
    
    COPY app_vl.py /app/app.py
    WORKDIR /app
    EXPOSE 8000
    CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]

    2.app_vl.py(简化版)

    from fastapi import FastAPI, HTTPException
    from pydantic import BaseModel
    from PIL import Image
    import base64
    import io
    from transformers import AutoModelForVision2Seq, AutoTokenizer
    
    app = FastAPI()
    
    # 加载模型(启动时加载)
    model = AutoModelForVision2Seq.from_pretrained(
        "Qwen/Qwen2.5-VL-32B",
        device_map="auto",
        trust_remote_code=True,
        torch_dtype="auto"
    )
    processor = AutoTokenizer.from_pretrained("Qwen/Qwen2.5-VL-32B", trust_remote_code=True)
    
    class VLRequest(BaseModel):
        image_b64: str  # Base64 编码的图片
        question: str
    
    @app.post("/vl")
    async def vision_language(request: VLRequest):
        try:
            # 解码图片
            image_data = base64.b64decode(request.image_b64)
            image = Image.open(io.BytesIO(image_data)).convert("RGB")
    
            # 构造消息
            messages = [{
                "role": "user",
                "content": [
                    {"type": "image", "image": image},
                    {"type": "text", "text": request.question}
                ]
            }]
    
            # 预处理
            inputs = processor(messages, return_tensors="pt", padding=True)
            inputs = {k: v.to(model.device) for k, v in inputs.items()}
    
            # 生成
            output = model.generate(**inputs, max_new_tokens=256)
            response = processor.decode(output[0], skip_special_tokens=True)
    
            return {"response": response}
        except Exception as e:
            raise HTTPException(status_code=500, detail=str(e))

    3. 构建并运行

    docker build -t qwen25-vl-32b -f Dockerfile.vl .
    docker run -d --gpus all -p 8001:8000 qwen25-vl-32b

    4.curl 测试(需 Base64 图片)

    # 先将图片转为 base64
    base64_image=$(base64 -i cat.jpg | tr -d '\n')
    
    curl http://localhost:8001/vl \
      -H "Content-Type: application/json" \
      -d "{
        \"image_b64\": \"$base64_image\",
        \"question\": \"图片中有什么动物?\"
      }"


    参考: