C

CHEGVA

让我们面对现实 让我们忠于理想

Kubernetes PV数据卷缩容方案及统计PV容量

在 Kubernetes 中,直接对 Persistent Volume (PV) 进行容量缩容(减小容量)通常是不被支持的。这主要是出于数据安全的考虑,因为贸然缩小底层存储设备可能会破坏数据。 理解 PV 缩容的限制 Kubernetes 及其存储生态系统在设计上就偏向于扩容,对缩容则有严格限制,主要原因如下: 数据安全风险:缩容操作可能导致数据被截断或丢失。如果新的容量小于已存储的数据量,后果不堪设想。 底层存储限制:许多底层存储系统(如 AWS EBS、Longhorn 等)并不支持在线缩小卷容量。例如,LVM 的 pvresize 命令也会拒绝缩小已分配空间的物理卷。 Kubernetes 机制限制:Kubernetes 的 allowVolumeExpansion 配置项仅控制扩容,并不会开启缩容功能。 替代方案与变通方法 虽然不能直接缩容,但是可以考虑以下替代方案来管理存储资源: 方案核心步骤优点注意事项 重建 PV 和 PVC1. 备份数据。 2. 创建新的、容量更小的 PV 和 PVC。 3. 将数据从旧 PV 迁移到新 PV。 4. 更新 Pod 配置以使用新 PVC,并删除旧资源。可靠且通用,适用于大多数不支持直接缩容的场景。过程繁琐,涉及业务中断和数据迁移。 调整存储配额通过 Kubernetes 的 ResourceQuota 来限制命名空间未来的存储请求总量,而不是缩小已有卷。无数据风险,有效预防未来存储资源的过度使用。无法释放已分配的存储空间。 利用存储供应商特性某些云厂商的存储服务(如 NetApp Trident)可能通过精简配置(Thin Provisioning)等方式,在逻辑上限制容量使用,而无需物理缩容。高效利用存储资源。依赖特定存储后端,并非普适性解决方案。 总而言之,在 Kubernetes 中直接对 PV 进行缩容操作是不可行的。当需要减少 PV 容量时,数据迁移和PV/PVC重建是目前最主流和安全的做法。通过创建一个容量更小的新PV,然后迁移数据,可以实现存储空间的"缩容"。这个过程的核心步骤包括:准备新PV、迁移数据、更新应用指向新存储,以及清理旧资源。 为了更安全地进行操作,请务必先留意以下关键风险点和前提: 关键项目重要说明 数据备份务必先备份原始数据,以防迁移过程中发生意外丢失。 业务中断数据迁移通常需要停止相关Pod,请规划在业务低峰期进行。 存储类型确保Kubernetes集群和存储系统支持动态供应或静态PV创建。 应用兼容性确保应用能够适应存储卷的切换。 详细操作步骤 1、确认当前PV/PVC状态并备份数据 执行以下命令,记录当前PV(Persistent Volume)和PVC(Persistent Volume Claim)的详细信息,特别是当前的存储容量和访问模式。 kubectl get pv <old-pv-name> -o yaml kubectl get pvc <old-pvc-name> -n <namespace> -o yaml 务必确保已经对重要数据进行了可靠备份。 2、停止使用旧存储的Pod 为了避免数据不一致,需要先停止所有正在使用该PVC的Pod。具体的操作方法取决于工作负载类型: 如果是Deployment,可以将其副本数缩容到0:kubectl scale deployment <deployment-name> --replicas=0 -n <namespace> 如果是StatefulSet,同样缩容到0:kubectl scale statefulset <statefulset-name> --replicas=0 -n <namespace> 如果是直接创建的Pod,则直接删除:kubectl delete pod <pod-name> -n <namespace> 3、创建新的、容量更小的PV 根据存储供应方式,选择静态或者动态方式创建新PV。 静态供应:编写新的PV YAML文件,注意capacity.storage字段设置为目标缩容容量。 apiVersion: v1 kind: PersistentVolume metadata:   name: new-small-pv  # 给新PV起个名字 spec:   capacity:     storage: 5Gi  # 这里设置你想要的、更小的容量   accessModes:     - ReadWriteOnce  # 根据你的需求设置访问模式   persistentVolumeReclaimPolicy: Retain  # 建议先设置为Retain   storageClassName: manual  # 指定存储类   # ... 其他必要字段根据你的存储系统来定 然后应用这个YAML文件:kubectl apply -f new-pv.yaml。 动态供应:创建一个新的PVC,其spec.resources.requests.storage字段指定为缩容后的容量,并确保其绑定的StorageClass能正确工作。Kubernetes会自动为其创建并绑定一个符合要求的PV。 apiVersion: v1 kind: PersistentVolumeClaim metadata:   name: new-small-pvc   namespace: your-namespace spec:   accessModes:     - ReadWriteOnce   resources:     requests:       storage: 5Gi  # 指定缩容后的容量   storageClassName: standard  # 指定能动态创建PV的StorageClass 应用这个YAML文件:kubectl apply -f new-pvc.yaml。 4、将数据从旧PV迁移到新PV 这是最关键的一步。常见的方法有: 使用kubectl cp命令:如果PV支持文件系统模式,可以启动一个临时的Pod挂载旧PVC,将数据拷贝到本地,然后另一个临时Pod挂载新PVC,再将数据从本地拷贝过去。 使用rsync工具:对于大量数据,rsync效率更高。可能需要启动一个包含rsync的临时Pod来完成。 存储系统快照/克隆:部分高级存储系统(如Longhorn, Portworx)自身支持卷的快照和克隆功能,可以利用这些功能来加速数据迁移。 5、修改Pod配置,指向新的PV/PVC 数据迁移并验证无误后,更新Pod配置模板(例如Deployment或StatefulSet的YAML文件),将其volumes部分指向新的PVC名称。 volumes: - name: my-storage   persistentVolumeClaim:     claimName: new-small-pvc  # 这里改为新PVC的名字 然后应用这个更新:kubectl apply -f your-app.yaml。 6、重启应用Pod 根据第二步中停止Pod的方式,重新启动应用。 如果是Deployment,将副本数扩展回目标数量:kubectl scale deployment <deployment-name> --replicas=<target-replicas> -n <namespace> 如果是StatefulSet,同样操作:kubectl scale statefulset <statefulset-name> --replicas=<target-replicas> -n <namespace> 如果是直接创建的Pod,则重新创建。 7、验证应用和数据 密切观察新Pod的启动日志和行为,确认应用能够正常访问新PV上的数据,并且功能符合预期。 可以再次使用kubectl get pv和kubectl get pvc命令,确认新的PVC new-small-pvc已经处于Bound状态,并且绑定到了正确的PV上。 8、谨慎清理旧PV/PVC 在确认新环境稳定运行一段时间(例如24小时)后,并且确定不再需要旧数据时,再考虑清理旧的PV和PVC。 注意:清理操作是不可逆的,请再次确认数据安全。 kubectl delete pvc <old-pvc-name> -n <namespace> kubectl delete pv <old-pv-name> 补充建议 预防胜于治疗:在Kubernetes命名空间中,可以通过创建ResourceQuota和LimitRange资源来预防单个PVC请求过大的存储空间,从源头控制存储使用。 理解回收策略:在删除旧PV时,务必了解其persistentVolumeReclaimPolicy(回收策略)。设置为Retain时,删除PVC后PV及相关后端存储资源会被保留;设置为Delete时,则可能会被自动清理。 K8s统计PV实际使用量 在PV卷缩容替代方案实施前,我们通常会统计Kubernetes集群中PV的总使用量,需要明白一点:kubectl get pv 命令本身无法直接提供PV的实际使用量,它主要显示的是PV的配置容量和状态信息。要获取PV的实际使用量,还需要借助其他方法。下面梳理几种可行的方案。 快速查看PV使用情况 如果想快速查看单个PV或PVC的实时使用情况,可以尝试以下方法: 方法操作说明 进入Pod查看1. 找到挂载目标PV的Pod:kubectl get pods -o wide --all-namespaces 2. 进入Pod并查看挂载点使用情况:kubectl exec -it <pod_name> -- df -h 或 `kubectl exec -it <pod_name> -- df -hgrep <mount_point>`优点:简单直接,无需额外工具。 局限:需要Pod处于运行状态,且只能查看单个Pod挂载的PV情况。 使用第三方工具安装 pvcpie 工具:pip install pvcpie,然后执行 pvcpie 命令。一个专门用于获取集群中PVC使用信息的Python工具。 搭建监控系统获取全面数据 对于生产环境或需要长期、全面监控的场景,建议搭建监控系统: 1.部署Metrics Server与监控工具: 确保集群已安装 Metrics Server。 部署 Prometheus 来收集和存储指标数据。 部署 Grafana 进行数据可视化。 2.利用Prometheus指标查询: Prometheus可以收集PV的实际使用量指标,例如 kubelet_volume_stats_used_bytes (卷中已使用的字节数) 和 kubelet_volume_stats_capacity_bytes (卷的总容量字节数)。可以使用PromQL查询语言进行统计。例如,查询所有PV的已用空间和容量: kubelet_volume_stats_used_bytes kubelet_volume_stats_capacity_bytes 3.配置Grafana仪表板: 在Grafana中,可以基于Prometheus的数据源创建仪表板,通过图表直观展示PV的总使用量、使用率趋势等。 云平台或特定发行版的集成监控 如果使用的是特定云平台的Kubernetes服务(如Azure AKS)或特定发行版(如Red Hat OpenShift),它们通常提供了集成的监控方案: Azure AKS:Azure Monitor的容器见解功能可以自动收集PV使用情况指标(如 pvUsedBytes),并提供了预配置的工作簿进行查看。 Red Hat OpenShift:集成了Prometheus,可以直接使用相关的PV监控指标。 最佳实践与建议 明确需求:根据需求(临时检查 vs. 长期监控)选择合适的方法。 数据准确性:请注意,某些监控工具(如 kubectl top)提供的指标可能专为Kubernetes自动扩缩容决策优化,与操作系统工具(如 top)的结果可能不完全一致。 配置告警:在监控系统基础上,为PV使用率配置告警,以便在空间不足时及时收到通知。 统计所有PV的配置容量总量,用kubectl get pv命令结合一些简单的文本处理工具就能做到。下面是一个汇总表格,列出了几种常用的方法: 方法命令特点 JSONPath提取kubectl get pv -o jsonpath='{.items[*].spec.capacity.storage}'直接提取容量值,但结果在同一行,需后续处理 JSONPath循环kubectl get pv -o jsonpath='{range .items[*]}{.spec.capacity.storage}{"\n"}{end}'每个PV容量单独一行,方便后续处理 自定义列kubectl get pv -o custom-columns=NAME:.metadata.name,CAPACITY:.spec.capacity.storage输出表格,直观显示每个PV的容量 自定义列(仅容量)kubectl get pv -o custom-columns=CAPACITY:.spec.capacity.storage --no-headers仅输出容量列,无表头,适合直接处理 重要提醒 配置容量 vs 实际使用量:kubectl get pv命令显示的是PV的配置容量(申请或初始设置的大小),而不是当前的实际数据使用量。要查看实际使用量,通常需要进入Pod内部使用df -h等命令,或借助集群监控系统(如Prometheus)。 单位一致性:确保所有PV的容量单位一致(例如,都是Gi或都是Mi),或者在处理时进行了适当的单位转换,否则求和结果可能不准确。 动态供应PV:对于通过StorageClass动态供应的PV,其配置容量通常由对应的PersistentVolumeClaim (PVC) 指定。 K8s统计PV配置容量 当PV的容量单位不一致时(比如同时存在Gi、Ki、Mi等),需要先统一单位再累加。以下是几种处理这种情况的方法: 方法一:使用awk进行单位转换和累加 kubectl get pv -o custom-columns=CAPACITY:.spec.capacity.storage --no-headers | awk ' {     # 提取数字和单位     if ($1 ~ /Gi$/) {         gsub(/Gi/, "", $1)         sum += $1 * 1024 * 1024  # Gi → Ki     }     else if ($1 ~ /Mi$/) {         gsub(/Mi/, "", $1)         sum += $1 * 1024  # Mi → Ki     }     else if ($1 ~ /Ki$/) {         gsub(/Ki/, "", $1)         sum += $1  # Ki保持不变     }     else if ($1 ~ /G$/) {         gsub(/G/, "", $1)         sum += $1 * 1000 * 1000  # G → K (十进制)     }     else if ($1 ~ /M$/) {         gsub(/M/, "", $1)         sum += $1 * 1000  # M → K (十进制)     }     else if ($1 ~ /K$/) {         gsub(/K/, "", $1)         sum += $1  # K保持不变     }     else {         # 没有单位,假设是字节         sum += $1 / 1024  # 字节 → Ki     } } END {     # 输出结果,可以选择合适的单位     if (sum >= 1024*1024*1024) {         printf "总容量: %.2f Ti\n", sum / (1024*1024*1024)     } else if (sum >= 1024*1024) {         printf "总容量: %.2f Gi\n", sum / (1024*1024)     } else if (sum >= 1024) {         printf "总容量: %.2f Mi\n", sum / 1024     } else {         printf "总容量: %.2f Ki\n", sum     } }' 方法二:使用jq进行更精确的处理 kubectl get pv -o json | jq ' [.items[].spec.capacity.storage |   capture("(?<value>[0-9.]+)(?<unit>[A-Za-z]+)") |  {    value: (.value | tonumber),    factor: (if .unit == "Ti" then 1024*1024*1024*1024            elif .unit == "Gi" then 1024*1024*1024            elif .unit == "Mi" then 1024*1024            elif .unit == "Ki" then 1024            elif .unit == "T" then 1000*1000*1000*1000            elif .unit == "G" then 1000*1000*1000            elif .unit == "M" then 1000*1000            elif .unit == "K" then 1000            else 1 end)  } | .value * .factor] | add' | awk ' {     total_ki = $1 / 1024  # 转换为KiB基数     if (total_ki >= 1024*1024*1024) {         printf "总容量: %.2f Ti\n", total_ki / (1024*1024*1024)     } else if (total_ki >= 1024*1024) {         printf "总容量: %.2f Gi\n", total_ki / (1024*1024)     } else if (total_ki >= 1024) {         printf "总容量: %.2f Mi\n", total_ki / 1024     } else {         printf "总容量: %.2f Ki\n", total_ki     } }' 方法三:简化的单位转换脚本 如果经常需要这个功能,可以创建一个可重用的脚本: #!/bin/bash # pv-total-capacity.sh calculate_pv_capacity() {     kubectl get pv -o custom-columns=CAPACITY:.spec.capacity.storage --no-headers | awk '     function to_kib(value, unit) {         switch(unit) {             case "Ti": return value * 1024 * 1024 * 1024 * 1024             case "Gi": return value * 1024 * 1024 * 1024             case "Mi": return value * 1024 * 1024             case "Ki": return value * 1024             case "T": return value * 1000 * 1000 * 1000 * 1000             case "G": return value * 1000 * 1000 * 1000             case "M": return value * 1000 * 1000             case "K": return value * 1000             default: return value  # 假设已经是字节         }     }          {         if (match($0, /([0-9.]+)([A-Za-z]*)/, parts)) {             value = parts[1]             unit = parts[2]             sum_kib += to_kib(value, unit) / 1024         }     }     END {         # 输出各种单位的表示         printf "总容量统计:\n"         printf "  %d Bytes\n", sum_kib * 1024         printf "  %.2f KiB\n", sum_kib         printf "  %.2f MiB\n", sum_kib / 1024         printf "  %.2f GiB\n", sum_kib / (1024*1024)         printf "  %.2f TiB\n", sum_kib / (1024*1024*1024)     }' } calculate_pv_capacity 使用建议 推荐使用方法一的awk脚本,它简单且功能完备 如果环境中有jq工具,方法二可以提供更精确的解析 对于生产环境,建议将方法三保存为脚本文件,方便重复使用 这些方法都能正确处理混合单位的情况,并输出易于理解的总容量统计结果。可以根据实际需求选择最适合的方法。 参考:Kubernetes 环境下华为云盘与 PV 数据目录缩容方案分析与实施

2026/2/6
articleCard.readMore

易经六十四卦Unicode符号表

Yijing Hexagram Symbols U+4DC0 – U+4DFF   (19904–19967) Character (decimal)DecimalCharacter (hex)HexName ䷀19904䷀4DC0HEXAGRAM FOR THE CREATIVE HEAVEN ䷁19905䷁4DC1HEXAGRAM FOR THE RECEPTIVE EARTH ䷂19906䷂4DC2HEXAGRAM FOR DIFFICULTY AT THE BEGINNING ䷃19907䷃4DC3HEXAGRAM FOR YOUTHFUL FOLLY ䷄19908䷄4DC4HEXAGRAM FOR WAITING ䷅19909䷅4DC5HEXAGRAM FOR CONFLICT ䷆19910䷆4DC6HEXAGRAM FOR THE ARMY ䷇19911䷇4DC7HEXAGRAM FOR HOLDING TOGETHER ䷈19912䷈4DC8HEXAGRAM FOR SMALL TAMING ䷉19913䷉4DC9HEXAGRAM FOR TREADING ䷊19914䷊4DCAHEXAGRAM FOR PEACE ䷋19915䷋4DCBHEXAGRAM FOR STANDSTILL ䷌19916䷌4DCCHEXAGRAM FOR FELLOWSHIP ䷍19917䷍4DCDHEXAGRAM FOR GREAT POSSESSION ䷎19918䷎4DCEHEXAGRAM FOR MODESTY ䷏19919䷏4DCFHEXAGRAM FOR ENTHUSIASM ䷐19920䷐4DD0HEXAGRAM FOR FOLLOWING ䷑19921䷑4DD1HEXAGRAM FOR WORK ON THE DECAYED ䷒19922䷒4DD2HEXAGRAM FOR APPROACH ䷓19923䷓4DD3HEXAGRAM FOR CONTEMPLATION ䷔19924䷔4DD4HEXAGRAM FOR BITING THROUGH ䷕19925䷕4DD5HEXAGRAM FOR GRACE ䷖19926䷖4DD6HEXAGRAM FOR SPLITTING APART ䷗19927䷗4DD7HEXAGRAM FOR RETURN ䷘19928䷘4DD8HEXAGRAM FOR INNOCENCE ䷙19929䷙4DD9HEXAGRAM FOR GREAT TAMING ䷚19930䷚4DDAHEXAGRAM FOR MOUTH CORNERS ䷛19931䷛4DDBHEXAGRAM FOR GREAT PREPONDERANCE ䷜19932䷜4DDCHEXAGRAM FOR THE ABYSMAL WATER ䷝19933䷝4DDDHEXAGRAM FOR THE CLINGING FIRE ䷞19934䷞4DDEHEXAGRAM FOR INFLUENCE ䷟19935䷟4DDFHEXAGRAM FOR DURATION ䷠19936䷠4DE0HEXAGRAM FOR RETREAT ䷡19937䷡4DE1HEXAGRAM FOR GREAT POWER ䷢19938䷢4DE2HEXAGRAM FOR PROGRESS ䷣19939䷣4DE3HEXAGRAM FOR DARKENING OF THE LIGHT ䷤19940䷤4DE4HEXAGRAM FOR THE FAMILY ䷥19941䷥4DE5HEXAGRAM FOR OPPOSITION ䷦19942䷦4DE6HEXAGRAM FOR OBSTRUCTION ䷧19943䷧4DE7HEXAGRAM FOR DELIVERANCE ䷨19944䷨4DE8HEXAGRAM FOR DECREASE ䷩19945䷩4DE9HEXAGRAM FOR INCREASE ䷪19946䷪4DEAHEXAGRAM FOR BREAKTHROUGH ䷫19947䷫4DEBHEXAGRAM FOR COMING TO MEET ䷬19948䷬4DECHEXAGRAM FOR GATHERING TOGETHER ䷭19949䷭4DEDHEXAGRAM FOR PUSHING UPWARD ䷮19950䷮4DEEHEXAGRAM FOR OPPRESSION ䷯19951䷯4DEFHEXAGRAM FOR THE WELL ䷰19952䷰4DF0HEXAGRAM FOR REVOLUTION ䷱19953䷱4DF1HEXAGRAM FOR THE CAULDRON ䷲19954䷲4DF2HEXAGRAM FOR THE AROUSING THUNDER ䷳19955䷳4DF3HEXAGRAM FOR THE KEEPING STILL MOUNTAIN ䷴19956䷴4DF4HEXAGRAM FOR DEVELOPMENT ䷵19957䷵4DF5HEXAGRAM FOR THE MARRYING MAIDEN ䷶19958䷶4DF6HEXAGRAM FOR ABUNDANCE ䷷19959䷷4DF7HEXAGRAM FOR THE WANDERER ䷸19960䷸4DF8HEXAGRAM FOR THE GENTLE WIND ䷹19961䷹4DF9HEXAGRAM FOR THE JOYOUS LAKE ䷺19962䷺4DFAHEXAGRAM FOR DISPERSION ䷻19963䷻4DFBHEXAGRAM FOR LIMITATION ䷼19964䷼4DFCHEXAGRAM FOR INNER TRUTH ䷽19965䷽4DFDHEXAGRAM FOR SMALL PREPONDERANCE ䷾19966䷾4DFEHEXAGRAM FOR AFTER COMPLETION ䷿19967䷿4DFFHEXAGRAM FOR BEFORE COMPLETION 在 CMD 或 PowerShell 中输出 六十四卦的 Unicode 范围是 U+4DC0 到 U+4DFF (1)使用 Python 输出 # 打印所有六十四卦 for i in range(0x4DC0, 0x4E00):     print(chr(i), end=" ") # 使用ipython                                                                                                      $> ipython In [1]: # 打印所有六十四卦    ...: for i in range(0x4DC0, 0x4E00):    ...:     print(chr(i), end=" ")    ...: ䷀ ䷁ ䷂ ䷃ ䷄ ䷅ ䷆ ䷇ ䷈ ䷉ ䷊ ䷋ ䷌ ䷍ ䷎ ䷏ ䷐ ䷑ ䷒ ䷓ ䷔ ䷕ ䷖ ䷗ ䷘ ䷙ ䷚ ䷛ ䷜ ䷝ ䷞ ䷟ ䷠ ䷡ ䷢ ䷣ ䷤ ䷥ ䷦ ䷧ ䷨ ䷩ ䷪ ䷫ ䷬ ䷭ ䷮ ䷯ ䷰ ䷱ ䷲ ䷳ ䷴ ䷵ ䷶ ䷷ ䷸ ䷹ ䷺ ䷻ ䷼ ䷽ ䷾ ䷿ 运行方式: 打开 CMD 或 PowerShell。 输入 python(需提前安装 Python),然后粘贴代码运行。 输入 ipython(需提前安装 ipython),然后粘贴代码运行。 (2)使用 PowerShell 输出 # 输出前10个卦象 1..10 | ForEach-Object { [char](0x4DBF + $_) } 运行方式: 打开 PowerShell。 粘贴代码并回车。 (3)使用 JavaScript 输出 在 JavaScript 中动态生成: <!DOCTYPE html> <html> <head>     <meta charset="UTF-8">     <title></title> </head> <body>     <script> let hexagrams = ""; for (let i = 0x4DC0; i <= 0x4DFF; i++) { hexagrams += String.fromCodePoint(i) + " "; } alert(hexagrams); // 输出所有六十四卦 //console.log(hexagrams);     </script> </body> </html> 参考: http://www.unicode.org/charts/PDF/U4DC0.pdf. Yijing Hexagram Symbols – Test for Unicode support in Web browsers Appendix:Unicode/Yijing Hexagram Symbols - Wiktionary, the free dictionary Unicode Block “Yijing Hexagram Symbols” Yijing Hexagram Symbols — Unicode Character Table 六十四卦图

2026/1/30
articleCard.readMore

从0开始学微服务

单体应用 单体应用: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实例可以部署一个微服务的代码。 服务独立维护。每个微服务都可以交由一个小团队甚至个人来开发、测试、发布和运维,并对整个生命周期负责。 服务治理能力要求高。因为拆分为微服务之后,服务的数量变多,因此需要有统一的服务治理平台,来对各个服务进行管理。 微服务架构 首先服务提供者(就是提供服务的一方)按照一定格式的服务描述,向注册中心注册服务,声明自己能够提供哪些服务以及服务的地址是什么,完成服务发布。 接下来服务消费者(就是调用服务的一方)请求注册中心,查询所需要调用服务的地址,然后以约定的通信协议向服务提供者发起请求,得到请求结果后再按照约定的协议解析结果。 而且在服务的调用过程中,服务的请求耗时、调用量以及成功率等指标都会被记录下来用作监控,调用经过的链路信息会被记录下来,用于故障定位和问题追踪。在这期间,如果调用失败,可以通过重试等服务治理手段来保证成功率。 总结一下,微服务架构下,服务调用主要依赖下面几个基本组件: 服务描述 注册中心 服务框架 服务监控 服务追踪 服务治理 不管是采用开源方案还是自行研发,都必须吃透每个组件的工作原理并能在此基础上进行二次开发。 采用微服务架构,可以帮助我们很好的解决实际业务场景中的问题: 从组织层面讲,系统的分工更加明确,责任更加清晰。 按照业务来划分服务,单个服务代码量小,业务单一,更易于维护。 服务能力更专一,代码重复度降低,可复用性高,使服务调用更加简便。 服务之间耦合性低,可随时加入服务,剔除过时服务。 单个服务也可以弹性扩展,容错能力更强。 更贴合云时代背景的软件开发运维环境,高效自动化构建部署。 数据存储解耦,不必将所有数据集中在一个大库中,不易拆分扩容。 整理自:从 0 开始学微服务

2026/1/21
articleCard.readMore

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

站在大语言模型外部看需要准备些什么样的训练数据,分什么阶段,怎样去训练大语言模型,把大语言模型看成一个黑盒。 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的过程如下图所示: 转载自:https://mp.weixin.qq.com/s/-lflK8HR_IkughslJ2s3_g 参考: AI领域核心术语技术总览表 认识大模型

2026/1/18
articleCard.readMore

JMeter混合场景压测

最近客户项目上线前小程序需要做性能压测,使用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 图形化模式适用于调试,不要进行压测。 图形化的压测方式会消耗较多的客户端性能,在压测过程中容易因为客户端问题导致内存溢出。官方不推荐我们使用图形化界面,推荐使用命令行方式执行测试脚本: 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 相关参考: Jmeter-性能测试详述 JMeter之压力测试——混合场景并发 JMeter多个线程组的使用说明 jmeter多业务混合场景如何设置各业务所占并发比例 Jmeter 性能压测 —— 混合场景 jmeter 压测解惑 Jmeter生成HTML性能测试报告

2026/1/13
articleCard.readMore