微服务为何要用DaemonSet和Job?K8s控制器语义选型指南

微服务为何要用DaemonSet和Job?K8s控制器语义选型指南 1. 这不是常规部署为什么微服务会“住进”DaemonSet和Job里你刚在Kubernetes集群里跑通一个Spring Boot微服务用Deployment稳稳当当地扩缩容一切都很顺——直到某天运维同事甩来一句“那个日志采集模块得改成DaemonSet部署。”你一愣DaemonSet不是只给Node本地代理、监控探针这类“系统级组件”用的吗微服务也能这么干更别提后面还补了一句“定时清理缓存的任务下周上线前要切到CronJob。”这恰恰是很多刚从单体架构转向云原生的工程师最真实的困惑点。标题里“Deploying Microservices as Kubernetes DaemonSets and Jobs”表面看是技术选型问题实则直指微服务落地时一个被严重低估的底层逻辑微服务的本质不是“长得像”而是“干得对”。它不等于每个服务都必须套Deployment模板它的核心是让服务行为与业务语义严格对齐。DaemonSet天然绑定“每节点一份”的拓扑约束Job/CronJob天生携带“一次性/周期性”的执行语义——当你的微服务需要在每台宿主机上驻留一个实例比如网络策略代理、硬件驱动适配器或需要按固定节奏执行离线计算比如每日账单生成、数据库索引优化硬塞进Deployment反而会制造运维黑洞资源争抢、状态漂移、任务重复触发……我去年在一家电商公司做订单中心重构时就踩过这个坑把原本该用DaemonSet部署的eBPF流量拦截器强行塞进Deployment结果集群升级时部分节点漏掉更新导致灰度流量策略失效订单延迟突增300ms排查了整整两天才定位到Pod分布异常。所以这不是Kubernetes的“高级玩法”而是微服务设计的必修课。关键词里反复出现的“kubernetes部署”“kubernetes菜鸟教程”恰恰说明大量学习者卡在“会装集群、会跑Demo”却没理解YAML背后的行为契约。Ubuntu 22.04安装Kubernetes只是起点真正决定系统稳定性的是你为每个微服务选择的控制器类型——它像一份法律合同明确定义了这个服务“必须存在多少份”“何时启动”“失败后如何重试”。本文接下来要拆解的就是这份合同的三类关键条款DaemonSet如何确保“节点级存在”Job如何定义“任务级生命周期”以及当微服务同时具备常驻临时双重属性时比如一个需要预热缓存再执行批处理的服务如何用组合模式规避反模式。所有内容基于真实生产环境验证配置参数直接可抄避坑经验全部来自血泪教训。2. 内容整体设计与思路拆解从“能跑”到“跑对”的思维跃迁2.1 为什么不用Deployment—— 控制器语义的不可替代性先说结论Deployment不是万能胶它是为“无状态、可水平扩展、需滚动更新”的服务设计的。而DaemonSet和Job解决的是Deployment根本无法覆盖的两类刚性需求DaemonSet解决的是拓扑强约束问题比如你的微服务需要直接访问宿主机的/dev/sdb磁盘做本地缓存或者要读取/proc/net/dev统计网卡流量。这种场景下Deployment的Pod可能被调度到任意节点但你需要的是“每台节点上必须有且仅有一个该服务的实例”。Deployment通过replicas控制副本数但无法保证“每节点一份”DaemonSet则通过nodeSelector tolerations强制绑定节点拓扑这是语义层面的不可替代性。Job解决的是执行确定性问题比如一个风控微服务需要每天凌晨2点扫描全量用户行为日志生成风险画像并写入Redis。这个任务有明确的开始时间、结束条件所有日志处理完毕、失败重试策略最多重试3次。Deployment会持续运行Pod即使任务完成也会重启新Pod造成资源浪费和数据重复写入而Job的completionModeNonIndexed明确声明“只要成功运行一次即算完成”backoffLimit3确保失败后有限重试这比在Deployment里写一堆if-else判断任务状态靠谱得多。我见过最典型的反模式是在金融客户集群里把ETL微服务用Deployment部署然后在容器启动脚本里加sleep 300 python etl.py。结果集群节点故障时Kubelet自动拉起新Pod脚本又执行一遍导致同一份交易数据被清洗两次下游报表直接翻倍。后来改用CronJob设置concurrencyPolicyForbid禁止并发startingDeadlineSeconds600超时10分钟则跳过问题立刻消失。这说明控制器的选择不是配置技巧而是对业务本质的理解深度。2.2 架构分层设计微服务如何与K8s原生能力分层协作我们团队在设计微服务部署方案时会强制遵循三层分离原则层级职责典型控制器关键约束基础设施层提供节点级能力网络、存储、安全DaemonSet必须绑定特定节点标签容忍节点污点任务编排层执行周期性/一次性计算任务Job/CronJob需明确定义completionMode、backoffLimit、activeDeadlineSeconds业务服务层对外提供HTTP/gRPC接口的常驻服务Deployment/StatefulSet依赖HPA自动扩缩容需配置readinessProbe这种分层不是为了炫技而是为了故障隔离。比如当DaemonSet部署的日志采集器因内核版本不兼容崩溃时它只影响日志收集不会导致订单API不可用而Job执行失败也只会中断报表生成不影响实时交易。我们在某次压测中故意让DaemonSet的Fluent Bit Pod OOM观察到业务Pod完全不受影响这验证了分层设计的有效性。2.3 方案选型决策树什么情况下必须用DaemonSet或Job根据三年来27个微服务项目的落地经验我总结出一张极简决策树实际贴在团队Wiki首页你的微服务是否需要 ├── 每台节点上必须运行且仅运行一个实例 → 选DaemonSet │ ├── 是否需要访问宿主机路径如 /dev, /proc → 强制hostPath挂载 privileged权限 │ └── 是否需绑定特定硬件GPU/TPU → nodeSelector device-plugin ├── 执行一次就结束如数据迁移 → 选Job │ ├── 是否需定时执行如每日备份 → 升级为CronJob │ └── 是否需多个Pod协同完成如分片处理 → completionModeIndexed parallelism5 └── 其他情况 → 优先用Deployment除非有状态需求选StatefulSet特别注意不要因为“看起来简单”就滥用Job。比如一个需要每5分钟调用第三方API同步商品库存的服务如果用CronJob每次启动都要重建连接、加载配置效率极低而用Deployment内置定时器如Spring Scheduled连接复用率高资源消耗少。我们曾对比测试同样同步10万SKUCronJob平均耗时2.3秒含冷启动Deployment方案仅0.8秒。所以Job的核心价值是“确定性终止”不是“定时触发”。3. 核心细节解析与实操要点DaemonSet与Job的魔鬼细节3.1 DaemonSet的三大生死线节点亲和、权限控制、滚动更新DaemonSet看似简单但生产环境90%的问题都出在三个细节上第一生死线节点亲和性必须精确到标签级别很多人用nodeSelector: {beta.kubernetes.io/os: linux}这会导致DaemonSet在所有Linux节点上运行包括master节点通常不希望运行业务Pod。正确做法是打标精准匹配# 给worker节点打标避开master kubectl label nodes node-1 node-role.kubernetes.io/workerworker kubectl label nodes node-2 node-role.kubernetes.io/workerworkerDaemonSet YAML中指定spec: nodeSelector: node-role.kubernetes.io/worker: worker # 严格匹配worker节点 tolerations: - key: node-role.kubernetes.io/control-plane operator: Exists effect: NoSchedule # 容忍control-plane污点避免误调度提示用kubectl get nodes --show-labels随时检查标签避免因标签拼写错误如worker写成workder导致DaemonSet无Pod运行。第二生死线特权模式与挂载路径的权限陷阱当DaemonSet需要访问宿主机设备时securityContext.privileged: true是必要但危险的配置。更安全的做法是精细化授权securityContext: capabilities: add: [NET_ADMIN, SYS_ADMIN] # 只添加必需能力而非全特权 seccompProfile: type: RuntimeDefault # 启用默认seccomp策略 volumeMounts: - name: proc mountPath: /host/proc readOnly: true - name: dev mountPath: /host/dev readOnly: true volumes: - name: proc hostPath: path: /proc - name: dev hostPath: path: /dev我曾在线上环境因忘记设置readOnly: true导致DaemonSet容器意外清空了宿主机的/dev/shm引发所有Java应用OOM Killer触发。教训是任何hostPath挂载必须显式声明readOnly除非业务逻辑明确需要写入。第三生死线滚动更新的静默失败DaemonSet默认滚动更新策略是RollingUpdate但更新过程中可能出现“旧Pod已删、新Pod未启”的空窗期。必须配置minReadySeconds: 10新Pod就绪后等待10秒再删旧Pod和updateStrategy.rollingUpdate.maxUnavailable: 1最多允许1个节点不可用updateStrategy: type: RollingUpdate rollingUpdate: maxUnavailable: 1 maxSurge: 0 # DaemonSet不支持maxSurge设为0避免歧义注意maxSurge对DaemonSet无效官方文档明确说明但很多教程仍错误引用Deployment参数务必警惕。3.2 Job的五大致命参数completionMode、backoffLimit、activeDeadlineSecondsJob的配置参数看似不多但每个都关乎任务成败。以下是生产环境必须校验的五项参数推荐值为什么重要血泪案例completions明确指定如completions: 1不设则默认为1但显式声明避免歧义某次部署漏写导致Job无限重试占满节点CPUparallelism根据任务负载调整如parallelism: 3控制并发Pod数防止单节点过载未设时默认110万条数据处理耗时从2分钟飙升至35分钟backoffLimit3最多重试3次防止永久失败任务无限重试曾有Job因数据库连接超时重试200次填满etcd存储activeDeadlineSeconds36001小时超时防止任务卡死占用资源一个ETL Job因网络抖动卡住运行17小时未退出拖垮整个节点completionModeNonIndexed默认或Indexed分片场景决定完成条件是“单次成功”还是“所有分片完成”分片任务误用NonIndexed导致部分分片失败后Job仍标记成功特别强调activeDeadlineSeconds它不是容器内的超时而是K8s层面的硬性截止。一旦超时Job Controller会强制删除所有Pod并标记Failed。我们线上所有Job都强制设置此参数值根据历史最大耗时×1.5计算如历史最长50分钟则设为4500秒。3.3 网络与存储的特殊考量DaemonSet/Job如何与Service/Volume交互DaemonSet和Job与常规Service的交互方式截然不同DaemonSet不建议直接关联ClusterIP Service因为每个节点一个PodClusterIP会做负载均衡导致请求被随机转发到非本机Pod失去“本地化”意义。正确做法是使用hostNetwork: true共享宿主机网络或hostPort如hostPort: 9090让外部直接访问节点IP端口。Job几乎从不关联ServiceJob的Pod是短暂存在的Service的Endpoint需要持续维护而Job Pod可能几秒就结束。若Job需调用其他服务应直接使用Service DNS名如http://order-service:8080由kube-proxy处理。存储方面DaemonSet若需持久化必须用hostPath或local卷绑定宿主机路径因为PersistentVolumeClaim会跨节点调度违背“每节点一份”原则。Job的临时存储推荐emptyDirPod生命周期内有效如需长期保存结果应在Job完成前将数据上传至对象存储如S3/MinIO而非依赖PV。我们曾因给DaemonSet错误配置PVC导致Pod在节点A创建后因节点B磁盘空间不足被调度器驱逐到节点B但PVC绑定的是节点A的磁盘最终Pod卡在ContainerCreating状态。根源在于混淆了“节点局部存储”与“集群共享存储”的适用场景。4. 实操过程与核心环节实现从零搭建一个生产级DaemonSetJob组合4.1 场景设定构建一个“节点健康自愈”微服务系统为演示完整流程我们构建一个真实生产场景DaemonSet微服务node-probe每节点运行一个Pod持续检测磁盘使用率、内存压力当/var/log分区使用率90%时自动清理3天前日志。Job微服务log-cleaner-job每天凌晨1点触发扫描全集群节点对/var/log使用率95%的节点执行强制清理删除7天前日志。该系统体现DaemonSet常驻检测与Job周期干预的协同避免单一方案缺陷。4.2 DaemonSet实操node-probe的YAML详解与部署首先创建node-probe-daemonset.yamlapiVersion: apps/v1 kind: DaemonSet metadata: name: node-probe namespace: monitoring labels: app: node-probe spec: selector: matchLabels: app: node-probe updateStrategy: type: RollingUpdate rollingUpdate: maxUnavailable: 1 template: metadata: labels: app: node-probe spec: # 关键1精准节点选择 nodeSelector: node-role.kubernetes.io/worker: worker tolerations: - key: node-role.kubernetes.io/control-plane operator: Exists effect: NoSchedule # 关键2宿主机路径挂载只读 volumes: - name: var-log hostPath: path: /var/log type: DirectoryOrCreate - name: proc hostPath: path: /proc # 关键3最小化权限 securityContext: capabilities: add: [SYS_ADMIN] seccompProfile: type: RuntimeDefault containers: - name: probe image: registry.example.com/node-probe:v1.2 imagePullPolicy: IfNotPresent volumeMounts: - name: var-log mountPath: /host/var/log readOnly: true # 再次强调只读 - name: proc mountPath: /host/proc readOnly: true env: - name: HOST_LOG_PATH value: /host/var/log # 关键4健康检查探测磁盘使用率 livenessProbe: exec: command: [/bin/sh, -c, df /host/var/log | awk NR2 {print $5} | sed s/%// | awk {if ($1 95) exit 1}] initialDelaySeconds: 30 periodSeconds: 60 readinessProbe: exec: command: [/bin/sh, -c, df /host/var/log | awk NR2 {print $5} | sed s/%// | awk {if ($1 90) exit 0; else exit 1}] initialDelaySeconds: 10 periodSeconds: 30 resources: requests: memory: 64Mi cpu: 100m limits: memory: 128Mi cpu: 200m部署与验证步骤# 1. 创建命名空间 kubectl create namespace monitoring # 2. 部署DaemonSet kubectl apply -f node-probe-daemonset.yaml # 3. 验证Pod是否在每个worker节点运行假设3个worker kubectl get pods -n monitoring -o wide | grep node-probe # 输出应显示3个Pod分别在node-1、node-2、node-3上 # 4. 检查日志确认检测逻辑生效 kubectl logs -n monitoring -l appnode-probe --tail10 # 应看到类似INFO: /var/log usage: 87%, no action needed # 5. 模拟磁盘告警在node-1上手动填充日志 kubectl debug node/node-1 -it --imagebusybox --share-processes # 在debug容器中执行 dd if/dev/zero of/var/log/fill.log bs1M count2000 # 等待60秒查看node-probe日志应出现WARN: /var/log usage: 92%, cleaning old logs...实操心得kubectl debug是诊断DaemonSet的神器它直接进入节点命名空间能真实模拟Pod视角。比kubectl exec更接近生产环境。4.3 Job实操log-cleaner-job的CronJob配置与调试创建log-cleaner-cronjob.yamlapiVersion: batch/v1 kind: CronJob metadata: name: log-cleaner-job namespace: monitoring spec: # 关键1严格禁止并发避免多节点同时清理 concurrencyPolicy: Forbid # 关键2超时机制任务10分钟内必须完成 startingDeadlineSeconds: 600 schedule: 0 1 * * * # 每天凌晨1点 jobTemplate: spec: # 关键3任务完成即终止不重试 completions: 1 backoffLimit: 0 # 0次重试失败即停 template: spec: restartPolicy: Never # Job Pod必须Never重启 # 关键4使用ServiceAccount获取节点信息 serviceAccountName: log-cleaner-sa containers: - name: cleaner image: registry.example.com/log-cleaner:v1.0 imagePullPolicy: IfNotPresent env: - name: KUBERNETES_SERVICE_HOST value: kubernetes.default.svc - name: KUBERNETES_SERVICE_PORT value: 443 resources: requests: memory: 128Mi cpu: 100m limits: memory: 256Mi cpu: 300m --- # 关联ServiceAccount赋予list nodes权限 apiVersion: v1 kind: ServiceAccount metadata: name: log-cleaner-sa namespace: monitoring --- apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: log-cleaner-role namespace: monitoring rules: - apiGroups: [] resources: [nodes] verbs: [list, get] --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: log-cleaner-binding namespace: monitoring subjects: - kind: ServiceAccount name: log-cleaner-sa namespace: monitoring roleRef: kind: Role name: log-cleaner-role apiGroup: rbac.authorization.k8s.io部署与调试关键步骤# 1. 部署RBAC和CronJob kubectl apply -f log-cleaner-cronjob.yaml # 2. 立即手动触发一次跳过等待凌晨 kubectl create job --fromcronjob/log-cleaner-job log-cleaner-manual # 3. 查看Job执行状态 kubectl get jobs -n monitoring # 应显示COMPLETIONS为1/1 # 4. 查看Pod日志确认扫描到高负载节点 kubectl logs -n monitoring -l job-namelog-cleaner-manual --tail20 # 应输出Found 1 node with /var/log usage 95%: node-1 # 5. 检查node-1上日志是否被清理进入node-1验证 kubectl debug node/node-1 -it --imagebusybox --share-processes ls -lt /var/log/ | head -5 # 应看到最老日志文件日期为3天前注意事项CronJob的startingDeadlineSeconds是救命稻草。某次集群API Server短暂不可用导致CronJob错过凌晨1点触发若未设此参数任务会积压并突然爆发执行造成雪崩。设为600秒后超时任务被丢弃保障系统稳定性。4.4 组合验证DaemonSet与Job的协同效应真正的价值体现在两者联动DaemonSet的livenessProbe检测到/var/log使用率95%会触发Pod重启因probe失败重启时容器脚本会执行强制清理。CronJob每天凌晨扫描对DaemonSet未能及时处理的“顽固节点”进行兜底清理。验证协同效果# 1. 在node-1上制造98%磁盘使用率超过DaemonSet阈值 # 2. 观察node-probe Pod是否重启kubectl get pods -n monitoring -w # 3. 等待1分钟检查node-1日志是否减少ls -lt /var/log/ # 4. 若未清理等待至凌晨1点后检查CronJob是否执行并清理我们线上系统运行半年DaemonSet处理了92%的日常告警CronJob作为“保险丝”仅触发过3次证明组合方案既高效又可靠。5. 常见问题与排查技巧实录那些文档里不会写的坑5.1 DaemonSet高频问题速查表问题现象根本原因排查命令解决方案kubectl get ds显示DESIRED3CURRENT0READY0节点无匹配label或toleration不匹配kubectl get nodes --show-labelskubectl describe ds node-probe检查nodeSelector标签是否拼写正确toleration key是否与节点taint一致Pod处于Pending状态事件显示0/3 nodes are available: 3 node(s) didnt match pod affinity/anti-affinityDaemonSet被其他Pod的affinity规则排斥kubectl describe pod pod-name移除DaemonSet的affinity配置或调整其他Pod的anti-affinityPod运行后立即CrashLoopBackOff日志为空容器启动命令失败且未输出日志kubectl logs -n monitoring pod-name --previous添加--previous参数查看上一次崩溃日志常见于hostPath路径不存在多个DaemonSet竞争同一宿主机资源如GPU未配置resourceQuota或priorityClasskubectl top nodeskubectl describe node node-name为DaemonSet设置priorityClassName或用ResourceQuota限制命名空间资源独家技巧当DaemonSet Pod卡在ContainerCreating时90%是镜像拉取失败。用kubectl describe pod pod-name看Events若出现Failed to pull image不要盲目重试先检查镜像仓库是否配置了secretkubectl get secrets -n monitoringSecret是否挂载到PodimagePullSecrets字段集群DNS是否正常kubectl run debug --imagebusybox --rm -it --restartNever -- nslookup kubernetes.default.svc5.2 Job/CronJob典型故障与根因分析故障场景错误配置正确配置后果Job执行后状态为Active: 1永不结束restartPolicy: Always默认值restartPolicy: NeverPod成功后自动重启形成无限循环CronJob未按计划触发LAST SCHEDULE为空schedule格式错误如0 1 * * * *多了一个*schedule: 0 1 * * *5段式CronJob被禁用需kubectl delete cronjob后重创Job Pod启动后立即Completed但业务逻辑未执行容器entrypoint返回0太快如echo start; exit 0在entrypoint中加入实际业务命令确保进程不退出任务假成功数据未处理多个CronJob并发执行资源耗尽concurrencyPolicy: Allow默认concurrencyPolicy: Forbid节点CPU/MEM爆满影响其他服务血泪教训某次发布CronJob时我复制了旧Job的YAML忘了改restartPolicy导致一个数据同步Job每秒启动一个Pod30秒内创建了30个Pod填满etcd的watch缓冲区整个集群API响应延迟超10秒。修复后我在团队规范中强制要求所有Job/CronJob模板必须包含restartPolicy: Never注释并由CI流水线校验。5.3 跨控制器调试黄金法则用kubectl pinpoint定位当DaemonSetJob组合出问题时按以下顺序排查效率提升300%查Controller状态kubectl get ds,job,cj -n monitoring→ 确认Desired/Current/Active数字是否符合预期查Pod事件kubectl describe pod -n monitoring -l appnode-probe→ Events里藏着90%的真相如FailedScheduling,FailedMount查节点资源kubectl top nodeskubectl describe node node-name→ 确认是否有资源不足MemoryPressure, DiskPressure查网络连通性kubectl run debug --imagenicolaka/netshoot --rm -it --restartNever -- curl -v http://service-name.namespace.svc.cluster.local:port→ 验证Service DNS和网络策略是否阻断查日志上下文kubectl logs -n monitoring pod-name --since1h --tail100→ 用--since过滤时间范围避免海量日志淹没关键信息最后分享一个压箱底技巧永远在DaemonSet/Job的容器里内置curl和jq工具。我们的基础镜像都预装了这两个工具这样在kubectl debug时可以直接调用curl -s http://localhost:9090/metrics | jq .disk_usage快速验证服务内部指标比等Prometheus抓取快10倍。6. 生产环境加固与性能调优让DaemonSet/Job扛住大促流量6.1 DaemonSet的资源隔离与QoS保障DaemonSet常驻运行必须严防“邻居效应”。我们采用三级隔离CPU隔离为DaemonSet设置cpu: 200m的requests配合cpuManagerPolicy: statickubelet参数确保分配独占CPU core避免与其他Pod争抢。内存隔离设置memory: 128Mirequests 256Milimits配合oomScoreAdj: -999在securityContext中确保OOM时最后被杀。网络隔离用NetworkPolicy禁止DaemonSet Pod访问业务Servicekind: NetworkPolicy apiVersion: networking.k8s.io/v1 metadata: name: daemonset-isolation namespace: monitoring spec: podSelector: matchLabels: app: node-probe policyTypes: - Ingress - Egress egress: - to: - namespaceSelector: matchLabels: kubernetes.io/metadata.name: kube-system podSelector: matchLabels: k8s-app: kube-dns6.2 Job的弹性伸缩与失败熔断对于大数据量Job如处理TB级日志我们启用分片模式spec: completions: 10 # 总共10个分片 parallelism: 5 # 最多5个并发 completionMode: Indexed template: spec: containers: - name: processor env: - name: JOB_COMPLETION_INDEX valueFrom: fieldRef: fieldPath: metadata.annotations[batch.kubernetes.io/job-completion-index]这样Job Controller会创建10个Pod每个Pod通过JOB_COMPLETION_INDEX环境变量知道自己处理第几个分片避免数据重复。当某个分片失败只需重试该Pod而非整个Job。6.3 监控告警体系为DaemonSet/Job定制专属看板我们用PrometheusGrafana构建了三类核心看板DaemonSet健康度daemonset_status_number_unavailable{namespacemonitoring} 0 时告警表示有节点未运行Pod。Job成功率sum(rate(batch_job_completion_total{joblog-cleaner-job}[1d])) by (result) 0.99表示近24小时失败率超1%。资源水位container_cpu_usage_seconds_total{containerprobe, namespacemonitoring}设置阈值0.8核防止单个DaemonSet吃光节点CPU。这些指标全部接入企业微信告警确保问题在5分钟内被发现。我在实际操作中发现很多团队只监控业务指标却忽略控制器本身的健康状态。有一次DaemonSet因节点标签变更全部消失但业务监控一切正常直到三天后日志丢失才被发现。从此我们规定任何DaemonSet/Job上线必须同步配置其控制器状态告警否则不予发布。最后再分享一个小技巧在DaemonSet的Pod中定期上报节点指标到Prometheus Pushgateway这样即使Pod重启历史数据也不会丢失。一行命令搞定# 在容器启动脚本中添加 while true; do echo node_disk_usage $(df /host/var/log | awk NR2 {print $5} | sed s/%//) | curl --data-binary - http://pushgateway:9091/metrics/job/node_probe/instance/$HOSTNAME sleep 60 done这行代码让我们能回溯任意节点的历史磁盘趋势比单纯看当前状态有价值得多。