一、index知多少
先看阻塞查询Consul KV的例子:
curl -H 'X-Consul-Token: 93d4dc25-fb4 -486b-a216-16f8fab52fcd' 'http://127.0.0.1:8500/1/kv/test/data?index=245706000\&wait=10s' -v
返回结果:
> GET /kv/test/data HTTP/1.1
> Host: 15.56.57.200:8500
> User-Agent curl/8.19.0
> Accept:*/*
> X-Consul-Token: 93d4dc25-fbb4-486b-a216-f6f8fab52fcd
< HTTP/1.1 200 OK
< Content-Type: application/json
< Vary: Accept-Encoding
< X-Consul-Default-Acl-Policy: deny
< X-Consul-Index: 245706000
< X-Consul-Knownleader: true
< X-Consul-Lastcontact:
< X-Consul-Query-Backend: blocking-query
< Date: Mon, 21 Sep 2026 03:27:53 GMT
< Content-Length:114
("LockIndex":0, Key":"test/data","Flags":0,"Value":"MzlzMjiMyMz=","Createlindex":9356026,"Modiflndex :245706000\]
Consul查询主要条件参数如下:
?raw:直接返回原始值(不经过Base64编码),但仅适用于单个键查询
?recurse:递归查询所有子键
?keys:只返回键名,不返回值
?dc=<数据中心>:指定数据中心(多数据中心场景)
?index=<ModifyIndex>&wait=<等待时间>:index和wait必须同时使用,否则wait无效。index为0,表示从当前最新状态开始查询(不等待)。wait最大限制:默认10分钟(600s),可通过Consul配置max_query_time调整。
上面看到有index、X-Consul-Index、Modifylndex、LockIndex、Createlndex,下面介绍这些index的含义:
LockIndex:表示该KV键值对当前被锁定的次数(即锁的版本号)。每次对该键执行锁操作(如通过Consu的Session锁定)时,LockIindex会递增。如果值为0,表示该键未被锁定。它主要用于分布式锁机制,帮助检测锁是否被释放或更新。
CreateIndex:表示该KV键值对在Consul集群中被创建时的全局唯一索引号。
ModifyIndex:资源级索引,具有资源独立性,每个具体资源(KV键,服务、健康检查等)的最后ModifyIndex值,等于该资源最后一次变更时全局唯一索引号的值。存储位置:资源元数据(如KV存储的键值对)
X-Consul-Index:是存放在HTTP响应头中的ModifyIndex
index:Consul客户端通过?index=<Modifylndex>监听某个盗源的变化。Consul 比较传入的index 与资源当前的ModifyIndex:若传入index<当前ModifyIndex,立即返回新数据。
若传入index>=当前 Modifyndex,阻塞等待,直到该资源的Modifyndex超过传入值。
applyndex:也就是上面提到的全局唯一索引号,具有全局唯一性。Consul集群使用Raft协议维护一个全局的raft:applyindex,每次写入操作(创建、更新、删除)都会使该索引递增。存储位置:Raft日志(内存+持久化)
其实平时用的最多的还是Modifyndex。在下面文章中讲到的index,如果没有特殊说明,一律等同于ModifyIndex。
二、目录的index
下面再说一下Consul中比较特殊的资源----目录的ModifyIndex
目录(即KV存储中的前缀路径,或称为资源列表)的 ModifyIndex等于该目录下所有键中最大的 Modifylndex。
资源的 ModifyIndex = 该资源最后一次被修改时的全局applylIndex目录的 ModifyIndex = 目录中任意资源最后一次被修改时的全局applyndex
根据上面的描述,如果对目录下的KV进行操作,会有什么结果,可以分析一下:
1、当目录下某个KV修改了,这个KV的ModifyIndex将会变成这个目录下的最大ModifyIndex,这个ModifyIndex的值也同时成为这个目录的ModifyIndex;
2、如果目录下某个KV被删除了,目录的ModifyIndex将会如何变化,需要分3种场景来分析:
-
场景一:被删除的健是最大值ModifyIndex变小
回退到次大值
-
场景二:被删除的键不是最大值一ModifyIndex不变
最大值仍在
-
场景三:被删除的键是唯一最大值ModifyIndex变0
空目录
-
结论:删除目录下的键不会导致目录ModifyIndex变大,只会不变或变小。
但是,实际使用Consul客户端监听目录时,如果目录下的某个KV被删除了,这个目录的ModifyIndex还是变大了,这又是怎么回事?
三、墓碑机制
这里就不得不介绍一下Consul的墓碑机制(Tombstone Mechanism)
工作原理
1、删除操作
删除一个键值对时,Consul不会立即从存储中物理移除该条目。相反,它会将该键标记为"已删除",并记录一个墓碑标记。
这个标记通常包含:
- 被删除键的完整路径
- 一个唯一的、递增的修改索引(ModifyIndex),用于标识删除操作的时序。
- 一个TTL(生存时间),用于控制墓碑的保留时长。
Consul为什么采用运种处理方式也非常好理解,Consul客户端在监听这个止录时,如果目录下的资源在删除时没有生成递增的ModifyIndex,Consul客户端就监听不到变化(前面已经分析过,ModifyIndex可能不变),从而不会触发Consul客户端立即进行处理。
2、传播与同步
墓碑标记会像普通数据一样,通过Raft协议复制到集群中的所有节点。这样,即使某个Consul服务节点之前持有该键的旧值,在收到墓碑后也会将其标记为已删除。
3、垃圾回收
墓碑不会永久保留。Consul 会定期执行垃圾回收(Garbage Collection)任务。当满足以下条件时,墓碑会被物理删除:
- 该墓碑的TTL已过期(通常为几十分钟到几小时,取决于配置 tombstone_ttl 的值,默认值:15m)
- 集群中的所有节点都已确认收到该墓碑(通过Raft日志提交确认)。
- 没有其他未完成的、依赖于该键的会话(Session)或锁
虽然目录的ModifyIndex此时会变小,也会触发Consul客户端进行处理,但因为内容没有变化,一般也不会影响Consul客户端的数据。
应用场景
- KV Store:这是墓碑机制最直接的应用。删除一个KV条目后,其墓碑会阻止该键在集群中重新出现。
- 服务注册与健康检查:当一个服务实例被注销(deregister)时,Consul会为其创建一个墓碑。这可以防止因网络迟或节点故障导致的旧服务信息"复活",确保服务发现目录的准确性。
- 会话(Session):当会话失效或被销毁时,与该会话关联的锁或临时键值对会被删除。墓碑机制确保这些锁不会在会话恢复后意外重新生效。
四、目录index的变化
结合上面对Consul墓碑机制介绍,就能知道目录下的某个KV被删除后,这个目录的index会先变大,然后过一段时间后再变小。
过程说明
- 目录下的某个KV被删除后,会创建一个墓碑(tombstone)记录,标记该健已被删除,墓碑记录包含一个递增的 ModifyIndex 从而导致目录的ModifyIndex也递增了;
- 此时这个KV已经删除了,普通的查询API(如 GET /v1/kv/)确实不会返回这个已删除的键,因为墓碑记录使其对普通查询不可见;
- 过了一段时间,Consul执行垃圾回收,垃圾回收只删除墓碑本身,释放元数据空间,再查询目录index时,会发现目录index变小了。
举例说明
再举个例子对这个过程说明一下:
- 通过查询API查某个目录的ModifyIndex(比如值为N);
- 删除了这个目录下的一个KV,再查这个目录的ModifyIndex(比如值为M),会发现ModifylIndex变大(M>N);
- 过一段时间Consul执行垃圾回收后,再查这个目录的index(比如值为S,S<M);
- 当被删除的KV的ModifyIndex是这个目录下所有KV的ModifyIndex中最大的时,S<N;
- 当被删除的KV的ModifyIndex不是这个目录下所有KV的ModifyIndex中最大的时,S=N;
- 当被删除的KV是这个目录下唯一的KV时,S=0<N
五、ModifyIndex变小的其它场景
通过以上内容的介绍,可以发现删除目录下资源可能会使目录的ModifyIndex变小。其实还有其它情况会造成ModifyIndex变小:
-
场景一:Consul 集群重启或Leader 切换
现象:当Consul集群所有节点重启,或发生Leader切换且新Leader的Raft日志被截断时,全局索引可能从较大的值回退到较小的值。
原因:Raft 日志在Leader 切换后可能被截断(如旧Leader 的未提交目志被丢弃),新Leader的applylndex从日持久化的日志中恢复。
-
场景二:Consul数据被手动重置或恢复
现象:通过consul snapshot restore 恢复快照,或手动清空Consul 数据目录后重启。
原因:快照中的Index是备份时的值,恢复后Index会从备份值开始重新递增,可能小于恢复前的Index。
客户端如果使用旧的Index发起阻塞查询,Consul 会认为该Index大于当前值,立即返回当前数据(而非等待变更),导致客户端误以为资源发生了变更。