2026-02-06 16:06:40
在 Kubernetes 中,直接对 Persistent Volume (PV) 进行容量缩容(减小容量)通常是不被支持的。这主要是出于数据安全的考虑,因为贸然缩小底层存储设备可能会破坏数据。
Kubernetes 及其存储生态系统在设计上就偏向于扩容,对缩容则有严格限制,主要原因如下:
数据安全风险:缩容操作可能导致数据被截断或丢失。如果新的容量小于已存储的数据量,后果不堪设想。
底层存储限制:许多底层存储系统(如 AWS EBS、Longhorn 等)并不支持在线缩小卷容量。例如,LVM 的 pvresize 命令也会拒绝缩小已分配空间的物理卷。
Kubernetes 机制限制:Kubernetes 的 allowVolumeExpansion 配置项仅控制扩容,并不会开启缩容功能。
虽然不能直接缩容,但是可以考虑以下替代方案来管理存储资源:
总而言之,在 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时,则可能会被自动清理。
在PV卷缩容替代方案实施前,我们通常会统计Kubernetes集群中PV的总使用量,需要明白一点:kubectl get pv 命令本身无法直接提供PV的实际使用量,它主要显示的是PV的配置容量和状态信息。要获取PV的实际使用量,还需要借助其他方法。下面梳理几种可行的方案。
如果想快速查看单个PV或PVC的实时使用情况,可以尝试以下方法:
对于生产环境或需要长期、全面监控的场景,建议搭建监控系统:
1.部署Metrics Server与监控工具:
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),并提供了预配置的工作簿进行查看。
明确需求:根据需求(临时检查 vs. 长期监控)选择合适的方法。
数据准确性:请注意,某些监控工具(如 kubectl top)提供的指标可能专为Kubernetes自动扩缩容决策优化,与操作系统工具(如 top)的结果可能不完全一致。
配置告警:在监控系统基础上,为PV使用率配置告警,以便在空间不足时及时收到通知。
统计所有PV的配置容量总量,用kubectl get pv命令结合一些简单的文本处理工具就能做到。下面是一个汇总表格,列出了几种常用的方法:
配置容量 vs 实际使用量:kubectl get pv命令显示的是PV的配置容量(申请或初始设置的大小),而不是当前的实际数据使用量。要查看实际使用量,通常需要进入Pod内部使用df -h等命令,或借助集群监控系统(如Prometheus)。
单位一致性:确保所有PV的容量单位一致(例如,都是Gi或都是Mi),或者在处理时进行了适当的单位转换,否则求和结果可能不准确。
动态供应PV:对于通过StorageClass动态供应的PV,其配置容量通常由对应的PersistentVolumeClaim (PVC) 指定。
当PV的容量单位不一致时(比如同时存在Gi、Ki、Mi等),需要先统一单位再累加。以下是几种处理这种情况的方法:
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
}
}'
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工具,方法二可以提供更精确的解析
对于生产环境,建议将方法三保存为脚本文件,方便重复使用
这些方法都能正确处理混合单位的情况,并输出易于理解的总容量统计结果。可以根据实际需求选择最适合的方法。
2026-01-30 09:35:53
| Character (decimal) |
Decimal | Character (hex) |
Hex | Name |
|---|---|---|---|---|
| ䷀ | 19904 | ䷀ | 4DC0 | HEXAGRAM FOR THE CREATIVE HEAVEN |
| ䷁ | 19905 | ䷁ | 4DC1 | HEXAGRAM FOR THE RECEPTIVE EARTH |
| ䷂ | 19906 | ䷂ | 4DC2 | HEXAGRAM FOR DIFFICULTY AT THE BEGINNING |
| ䷃ | 19907 | ䷃ | 4DC3 | HEXAGRAM FOR YOUTHFUL FOLLY |
| ䷄ | 19908 | ䷄ | 4DC4 | HEXAGRAM FOR WAITING |
| ䷅ | 19909 | ䷅ | 4DC5 | HEXAGRAM FOR CONFLICT |
| ䷆ | 19910 | ䷆ | 4DC6 | HEXAGRAM FOR THE ARMY |
| ䷇ | 19911 | ䷇ | 4DC7 | HEXAGRAM FOR HOLDING TOGETHER |
| ䷈ | 19912 | ䷈ | 4DC8 | HEXAGRAM FOR SMALL TAMING |
| ䷉ | 19913 | ䷉ | 4DC9 | HEXAGRAM FOR TREADING |
| ䷊ | 19914 | ䷊ | 4DCA | HEXAGRAM FOR PEACE |
| ䷋ | 19915 | ䷋ | 4DCB | HEXAGRAM FOR STANDSTILL |
| ䷌ | 19916 | ䷌ | 4DCC | HEXAGRAM FOR FELLOWSHIP |
| ䷍ | 19917 | ䷍ | 4DCD | HEXAGRAM FOR GREAT POSSESSION |
| ䷎ | 19918 | ䷎ | 4DCE | HEXAGRAM FOR MODESTY |
| ䷏ | 19919 | ䷏ | 4DCF | HEXAGRAM FOR ENTHUSIASM |
| ䷐ | 19920 | ䷐ | 4DD0 | HEXAGRAM FOR FOLLOWING |
| ䷑ | 19921 | ䷑ | 4DD1 | HEXAGRAM FOR WORK ON THE DECAYED |
| ䷒ | 19922 | ䷒ | 4DD2 | HEXAGRAM FOR APPROACH |
| ䷓ | 19923 | ䷓ | 4DD3 | HEXAGRAM FOR CONTEMPLATION |
| ䷔ | 19924 | ䷔ | 4DD4 | HEXAGRAM FOR BITING THROUGH |
| ䷕ | 19925 | ䷕ | 4DD5 | HEXAGRAM FOR GRACE |
| ䷖ | 19926 | ䷖ | 4DD6 | HEXAGRAM FOR SPLITTING APART |
| ䷗ | 19927 | ䷗ | 4DD7 | HEXAGRAM FOR RETURN |
| ䷘ | 19928 | ䷘ | 4DD8 | HEXAGRAM FOR INNOCENCE |
| ䷙ | 19929 | ䷙ | 4DD9 | HEXAGRAM FOR GREAT TAMING |
| ䷚ | 19930 | ䷚ | 4DDA | HEXAGRAM FOR MOUTH CORNERS |
| ䷛ | 19931 | ䷛ | 4DDB | HEXAGRAM FOR GREAT PREPONDERANCE |
| ䷜ | 19932 | ䷜ | 4DDC | HEXAGRAM FOR THE ABYSMAL WATER |
| ䷝ | 19933 | ䷝ | 4DDD | HEXAGRAM FOR THE CLINGING FIRE |
| ䷞ | 19934 | ䷞ | 4DDE | HEXAGRAM FOR INFLUENCE |
| ䷟ | 19935 | ䷟ | 4DDF | HEXAGRAM FOR DURATION |
| ䷠ | 19936 | ䷠ | 4DE0 | HEXAGRAM FOR RETREAT |
| ䷡ | 19937 | ䷡ | 4DE1 | HEXAGRAM FOR GREAT POWER |
| ䷢ | 19938 | ䷢ | 4DE2 | HEXAGRAM FOR PROGRESS |
| ䷣ | 19939 | ䷣ | 4DE3 | HEXAGRAM FOR DARKENING OF THE LIGHT |
| ䷤ | 19940 | ䷤ | 4DE4 | HEXAGRAM FOR THE FAMILY |
| ䷥ | 19941 | ䷥ | 4DE5 | HEXAGRAM FOR OPPOSITION |
| ䷦ | 19942 | ䷦ | 4DE6 | HEXAGRAM FOR OBSTRUCTION |
| ䷧ | 19943 | ䷧ | 4DE7 | HEXAGRAM FOR DELIVERANCE |
| ䷨ | 19944 | ䷨ | 4DE8 | HEXAGRAM FOR DECREASE |
| ䷩ | 19945 | ䷩ | 4DE9 | HEXAGRAM FOR INCREASE |
| ䷪ | 19946 | ䷪ | 4DEA | HEXAGRAM FOR BREAKTHROUGH |
| ䷫ | 19947 | ䷫ | 4DEB | HEXAGRAM FOR COMING TO MEET |
| ䷬ | 19948 | ䷬ | 4DEC | HEXAGRAM FOR GATHERING TOGETHER |
| ䷭ | 19949 | ䷭ | 4DED | HEXAGRAM FOR PUSHING UPWARD |
| ䷮ | 19950 | ䷮ | 4DEE | HEXAGRAM FOR OPPRESSION |
| ䷯ | 19951 | ䷯ | 4DEF | HEXAGRAM FOR THE WELL |
| ䷰ | 19952 | ䷰ | 4DF0 | HEXAGRAM FOR REVOLUTION |
| ䷱ | 19953 | ䷱ | 4DF1 | HEXAGRAM FOR THE CAULDRON |
| ䷲ | 19954 | ䷲ | 4DF2 | HEXAGRAM FOR THE AROUSING THUNDER |
| ䷳ | 19955 | ䷳ | 4DF3 | HEXAGRAM FOR THE KEEPING STILL MOUNTAIN |
| ䷴ | 19956 | ䷴ | 4DF4 | HEXAGRAM FOR DEVELOPMENT |
| ䷵ | 19957 | ䷵ | 4DF5 | HEXAGRAM FOR THE MARRYING MAIDEN |
| ䷶ | 19958 | ䷶ | 4DF6 | HEXAGRAM FOR ABUNDANCE |
| ䷷ | 19959 | ䷷ | 4DF7 | HEXAGRAM FOR THE WANDERER |
| ䷸ | 19960 | ䷸ | 4DF8 | HEXAGRAM FOR THE GENTLE WIND |
| ䷹ | 19961 | ䷹ | 4DF9 | HEXAGRAM FOR THE JOYOUS LAKE |
| ䷺ | 19962 | ䷺ | 4DFA | HEXAGRAM FOR DISPERSION |
| ䷻ | 19963 | ䷻ | 4DFB | HEXAGRAM FOR LIMITATION |
| ䷼ | 19964 | ䷼ | 4DFC | HEXAGRAM FOR INNER TRUTH |
| ䷽ | 19965 | ䷽ | 4DFD | HEXAGRAM FOR SMALL PREPONDERANCE |
| ䷾ | 19966 | ䷾ | 4DFE | HEXAGRAM FOR AFTER COMPLETION |
| ䷿ | 19967 | ䷿ | 4DFF | HEXAGRAM FOR BEFORE COMPLETION |
六十四卦的 Unicode 范围是 U+4DC0 到 U+4DFF
# 打印所有六十四卦 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),然后粘贴代码运行。
# 输出前10个卦象
1..10 | ForEach-Object { [char](0x4DBF + $_) }

运行方式:
打开 PowerShell。
粘贴代码并回车。
在 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>

参考:
Yijing Hexagram Symbols – Test for Unicode support in Web browsers
Appendix:Unicode/Yijing Hexagram Symbols - Wiktionary, the free dictionary
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实例可以部署一个微服务的代码。
服务独立维护。每个微服务都可以交由一个小团队甚至个人来开发、测试、发布和运维,并对整个生命周期负责。
服务治理能力要求高。因为拆分为微服务之后,服务的数量变多,因此需要有统一的服务治理平台,来对各个服务进行管理。

首先服务提供者(就是提供服务的一方)按照一定格式的服务描述,向注册中心注册服务,声明自己能够提供哪些服务以及服务的地址是什么,完成服务发布。
接下来服务消费者(就是调用服务的一方)请求注册中心,查询所需要调用服务的地址,然后以约定的通信协议向服务提供者发起请求,得到请求结果后再按照约定的协议解析结果。
而且在服务的调用过程中,服务的请求耗时、调用量以及成功率等指标都会被记录下来用作监控,调用经过的链路信息会被记录下来,用于故障定位和问题追踪。在这期间,如果调用失败,可以通过重试等服务治理手段来保证成功率。
总结一下,微服务架构下,服务调用主要依赖下面几个基本组件:
服务描述
注册中心
服务框架
服务监控
服务追踪
服务治理
不管是采用开源方案还是自行研发,都必须吃透每个组件的工作原理并能在此基础上进行二次开发。
采用微服务架构,可以帮助我们很好的解决实际业务场景中的问题:
从组织层面讲,系统的分工更加明确,责任更加清晰。
按照业务来划分服务,单个服务代码量小,业务单一,更易于维护。
服务能力更专一,代码重复度降低,可复用性高,使服务调用更加简便。
服务之间耦合性低,可随时加入服务,剔除过时服务。
单个服务也可以弹性扩展,容错能力更强。
更贴合云时代背景的软件开发运维环境,高效自动化构建部署。
数据存储解耦,不必将所有数据集中在一个大库中,不易拆分扩容。
整理自:从 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的三个核心步骤:
研究人员把互联网上能找到的几乎所有高质量文本——百科全书、小说、新闻、代码库、论文——全部打包,喂给这个模型。
量级: 这相当于让一个人读了几千万辈子的书。
模型在读这些书时,主要在做一件事:猜下一个字是什么。
例子: 看到“床前明月__”,模型要猜出是“光”。
原理: 如果猜对了,给予奖励(参数调整);如果猜错了(比如猜成了“饼”),就狠狠修正。
结果: 通过千亿次的猜测和修正,模型学会了语言的语法、词语的搭配,以及人类世界的逻辑和常识(比如知道“水是流动的”、“1+1=2”)。
预训练结束后,我们就得到了一个基座模型。
它的状态: 它博学多才,什么都知道一点,懂中文也懂英文。
它的缺点: 它还很“野”,不懂礼貌,也不知道怎么听指令。如果你问它“怎么做番茄炒蛋?”,它可能会给你续写一段关于番茄的历史,而不是给你食谱。因为它只会“接着往下写”,还不会“回答问题”。
总之,Pretrain(预训练) = 通过海量阅读,学会语言规律和世界知识的过程。它是大模型最昂贵、最耗时的阶段(通常需要几千张显卡跑几个月),是模型能力的地基。这个阶段的大模型说起话来非常像“接话茬”,并不是在“做任务”。
如果说 Pretrain(预训练) 是让模型在图书馆里泛读万卷书,成了一个博学的“书呆子”。
那么 SFT(Supervised Fine-Tuning,有监督微调) 就是给这个书呆子进行“职业技能培训”,或者是找了个“金牌导师”手把手教它怎么说话。
在这个阶段,我们的目标不再是让它“学知识”,而是让它“懂规矩”。
以下是 SFT 的三个核心变化:
在预训练阶段,我们给它看的是并没有标注重点的海量文本。而在 SFT 阶段,我们给它看的是高质量的问答对(Q&A)。
人类老师(标注员) 会编写成千上万个例子,告诉模型:“当用户问这个问题时,标准的回答应该是这样的。”
例子:
输入: “请把这段话翻译成英文。”
标准答案: “Please translate this sentence into English.”
量级: 相比预训练的数据海,SFT 的数据量要小得多(通常是几万到几十万条),但质量要求极高。
Pretrain 时: 模型看到“番茄炒蛋”,可能会接着写“是一道中国名菜,起源于……”(因为它在做文本接龙)。
SFT 时: 老师拿着戒尺站在旁边。模型看到“番茄炒蛋怎么做?”,如果它还想背历史课文,老师会敲它一下,指着标准答案说:“不对!这时候你应该列出步骤:第一步打蛋,第二步切番茄……”
效果: 模型开始模仿人类说话的语气、格式和逻辑。它学会了:当人类用问句开头时,我应该提供答案,而不是接着编故事。
经过 SFT 之后,这个模型就发生了质的飞跃:
听懂指令: 你让它缩写文章,它就不会扩写;你让它写代码,它就不会写诗。
格式规范: 它学会了分点作答、使用礼貌用语。
ChatGPT 的诞生: GPT-3 只是一个预训练模型(书呆子),而 ChatGPT 则是经过了 SFT(以及后续步骤)后的产物(智能助手)。
小结,SFT(指令微调) = 通过高质量的问答范例,教模型如何正确地使用它学到的知识来服务人类。它让模型从“懂语言”变成了“懂人话”。
如果说 Pretrain(预训练) 造就了博学的“书呆子”,SFT(微调) 把它变成了能干活的“职场新人”;
那么 RLHF(Reinforcement Learning from Human Feedback,人类反馈强化学习) 就是给这个新人上的“思想品德课”和“情商培训班”。
在 SFT 阶段,模型虽然学会了回答问题,但它有个大毛病:它不知道哪个答案更好,甚至为了“回答”而撒谎或干坏事。 比如你问“怎么偷东西?”,SFT 模型可能会很详细地教你(因为它觉得回答就是它的工作)。
RLHF 就是为了解决这个问题,给模型“树立三观”。以下是通俗易懂的两个关键环节:
我们不再让老师手把手写标准答案了(太累了),而是让模型针对同一个问题生成两个不同的回答,让老师来打分(或者二选一)。
场景: 用户问“怎么制作炸弹?”
回答 A: 详细列出化学配方(虽然准确,由于危险,人类老师判0分)。
回答 B: “制作炸弹是违法的,我不能帮你,但我可以介绍炸药的历史。”(虽然拒绝了,但符合安全规范,人类老师判100分)。
回答 C: 胡言乱语(50分)。
结果: 我们训练出了一个专门的“判卷老师”(Reward Model,奖励模型)。这个判卷老师并不生产内容,它唯一的任务就是:判断这句话人类爱不爱听,符不符合人类的价值观。
现在,让大模型开始大量做题,然后让刚才训练好的“判卷老师”给它打分。
机制(类似训狗):
如果模型生成了一段话,判卷老师给了高分(比如态度谦逊、内容安全),系统就给模型发一个“糖果”(正向反馈),告诉它:“做得好,以后多这样说话!”
如果判卷老师给了低分(比如脏话、撒谎、种族歧视),系统就给它一个“惩罚”(负向反馈),告诉它:“错了,以后这种话少说!”
进化: 模型为了以此获得更多的“糖果”,会拼命调整自己的说话方式,去迎合“判卷老师”的喜好。
经过 RLHF,模型不仅仅是“能说话”,它变成了符合 3H 原则 的模范生:
Helpful(有帮助): 甚至能从你模糊的问题中猜到你想问什么。
Honest(诚实): 不懂装懂的情况变少了(虽然还不能完全根除)。
Harmless(无害): 拒绝回答违法、暴力、色情的问题,变得非常有“求生欲”。
SFT及RLHF的过程如下图所示:

转载自:https://mp.weixin.qq.com/s/-lflK8HR_IkughslJ2s3_g
参考:
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

图形化模式适用于调试,不要进行压测。
图形化的压测方式会消耗较多的客户端性能,在压测过程中容易因为客户端问题导致内存溢出。官方不推荐我们使用图形化界面,推荐使用命令行方式执行测试脚本:
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
相关参考: