收敛是设计目标
幂等任务的本质是收敛:任务不关心当前处于什么状态,只负责把目标状态对齐。实现路径通常是「读取现状 → 与期望状态比对 → 只修改有差异的部分 → 再次确认」。这样任务被重复执行时,第二轮比对会发现没有差异,直接结束并返回未变更。收敛设计让自动化天然适配重试、断点续跑与并发触发,也让执行结果从「跑了什么」变成「改了什么」。
基本信息
| 核心思路 | 声明期望状态,由任务负责比对与收敛 |
|---|---|
| 执行前提 | 任务能读取现状,且读取结果可信 |
| 正常输出 | 无变化(未变更)与已变更都是成功 |
| 配套机制 | 唯一键、状态标记、加锁、超时与批次控制 |
设计要点
- 先查后写:执行任何变更动作前先判断目标是否已经是期望状态。
- 整文件渲染:配置文件整体按模板渲染,避免在已有文件上做追加或行替换。
- 唯一键约束:创建资源时带上业务唯一标识,让重复创建直接被拒绝或忽略。
- 状态标记:任务开始前写入执行标记,中断后可判断上次执行到哪一步。
- 单实例保护:对同一目标加分布式锁或串行执行,避免并发写同一份配置。
先查后写的两种写法
先查后写的两种写法
# 写法一:判断存在再创建,避免报「已存在」
if [ ! -d /opt/app/releases/1.4.2 ]; then
mkdir -p /opt/app/releases/1.4.2 # 只有缺失时才建目录
fi
# 写法二:用幂等语义的命令替代一次性命令
grep -qxF 'net.ipv4.ip_forward=1' /etc/sysctl.d/99-app.conf \
|| echo 'net.ipv4.ip_forward=1' >> /etc/sysctl.d/99-app.conf # 已在则跳过
sysctl --system # 最后统一加载,重复执行结果一致声明式的写法
声明式的写法
# Ansible:确保状态,而不是执行动作
- name: 确保应用目录存在
ansible.builtin.file:
path: /opt/app
state: directory # 声明目标状态:存在
mode: '0755' # 属性也一并收敛
# 重复执行该任务会返回 ok(未变更),而不是 changed收敛设计的要素
| 要素 | 作用 | 缺失后果 |
|---|---|---|
| 现状读取 | 判断是否已达成 | 每轮都盲改,重复执行出错 |
| 差异比对 | 只改必要部分 | 全量重写导致服务被反复重启 |
| 唯一键 | 识别同一目标 | 重复创建出多份资源 |
| 状态标记 | 支撑断点续跑 | 中断后只能从头再做一遍 |
| 执行锁 | 防止并发写同一目标 | 配置文件被并发写坏 |
「未变更」应该是可观测的正常结果。如果一条任务每次执行都报告已变更,通常说明现状读取方式失效或比对条件写得太宽松,例如把时间戳或随机数写进了模板。