1. 概述

在分布式系统中,确保某一时刻只有一个实例执行关键任务(如定时任务、状态协调等)是常见需求。本文将介绍如何借助 Consul 的会话管理与 KV 存储机制实现领导选举(Leadership Election),从而保障数据一致性与系统稳定性。

我们不会从零造轮子,而是通过一个轻量级 Java 库演示整个流程,并结合实际日志和配置,带你踩一遍常见的坑。✅


2. 什么是 Consul?

Consul 是 HashiCorp 开源的服务发现与配置管理工具,核心功能包括:

  • ✅ 服务注册与发现
  • ✅ 健康检查
  • ✅ KV 存储
  • ✅ 分布式会话(Session)管理
  • ✅ Web GUI 界面,便于可视化操作

本文重点关注的是最后两项:Session + KV Store。它们组合起来可以实现分布式锁和领导选举,这正是我们要用到的核心机制。

⚠️ 注意:虽然 Consul 内部集群也依赖 Raft 协议进行 leader 选举,但我们这里讲的是“应用层”的 leader 选举 —— 即多个服务实例之间选出一个“主节点”来执行特定任务。


3. Consul 核心概念

3.1 Agent 模式:Server 与 Client

Consul 集群中的每个节点都运行一个 Agent,它可以处于两种模式:

模式 角色说明
Server 负责一致性协议(Raft)、处理请求、选举 leader,通常奇数个(3/5/7)
Client(Agent) 轻量级代理,负责健康检查、本地服务注册、转发请求到 Server

生产环境一般部署 3~5 个 Server 节点,其余为 Client 节点。

3.2 KV 存储与 Session 机制

  • KV Store:键值对存储,支持带 Session 的原子操作(如 PUT 加锁)
  • Session:代表一个逻辑会话,可设置 TTL(超时时间),支持自动过期或主动销毁

关键点在于:只有持有有效 Session 的实例才能在 KV 中写入特定 key,这就构成了分布式锁的基础。

3.3 Consul 集群示意图

consul cluster

图中展示了 Consul Server 节点通过 Raft 协议达成共识,Client 节点负责本地服务健康检查并上报。


4. 使用 Consul 实现领导选举

在多实例部署场景下,若多个节点同时执行同一个定时任务,可能导致数据错乱。解决方案就是:只允许一个 leader 实例运行该任务

Consul 提供了一套简单粗暴但可靠的机制来实现这一点。

4.1 领导竞争流程

所有实例启动后,会进行如下步骤的竞争:

  1. ✅ 所有实例约定同一个 KV key(例如 services/myapp/leader
  2. ✅ 各自创建一个 Session
  3. ✅ 尝试以该 Session 对 key 执行 acquire 操作
    • 成功 → 当前实例成为 leader ✅
    • 失败 → 当前实例为 follower ❌
  4. ✅ leader 定期刷新 Session(防止过期)
  5. ✅ follower 持续监听 key 变化,一旦 leader 失效则重新竞争

这个过程本质上是一个基于 Session 的分布式锁抢占。

📌 关键机制:Consul 的 PUT /v1/kv/path?acquire=session_id 接口是原子操作,只有当 key 未被锁定或锁已失效时才会成功。


4.2 实战示例:Java 实现领导选举

我们使用开源库 Kinguin Digital Limited Leadership Consul 来快速实现。

添加依赖

<dependency>
   <groupId>com.github.kinguinltdhk</groupId>
   <artifactId>leadership-consul</artifactId>
   <version>1.0.5</version>
   <exclusions>
       <exclusion>
           <groupId>com.ecwid.consul</groupId> 
           <artifactId>consul-api</artifactId>
       </exclusion>
   </exclusions>
</dependency>

⚠️ 注意排除 consul-api,避免版本冲突(尤其是与 Spring Cloud Consul 共存时)。

共享 Key 模板

我们约定所有实例使用如下格式的 key:

services/%s/leader

其中 %s 替换为服务名(如 cluster-app)。

启动集群测试

new SimpleConsulClusterFactory()
    .mode(SimpleConsulClusterFactory.MODE_MULTI)
    .debug(true)
    .build()
    .asObservable()
    .subscribe(i -> System.out.println(i));

这段代码模拟多个实例启动,通过 asObservable() 监听选举事件流。

配置文件示例

cluster:
  leader:
    serviceName: cluster-app
    serviceId: node-1
    consul:
      host: localhost
      port: 8500
      discovery:
        enabled: false
    session:
      ttl: 15s        # Session 超时时间
      refresh: 7s     # 刷新间隔,必须小于 ttl
    election:
      envelopeTemplate: services/%s/leader
  • ttl=15s:Session 最长存活 15 秒
  • refresh=7s:每 7 秒主动刷新一次,防止过期
  • envelopeTemplate:选举使用的 key 模板

4.3 如何本地测试?

推荐使用 Docker 快速启动 Consul:

docker run -d --name consul -p 8500:8500 -e CONSUL_BIND_INTERFACE=eth0 consul

启动后访问 http://localhost:8500/ui 查看 Web 控制台。

执行结果日志

运行上述 Java 示例后,输出类似:

INFO: multi mode active
INFO: Session created e11b6ace-9dc7-4e51-b673-033f8134a7d4
INFO: Session refresh scheduled on 7 seconds frequency 
INFO: Vote frequency setup on 10 seconds frequency 
ElectionMessage(status=elected, vote=Vote{sessionId='e11b6ace-9dc7-4e51-b673-033f8134a7d4', serviceName='cluster-app', serviceId='node-1'}, error=null)
ElectionMessage(status=elected.first, vote=Vote{sessionId='e11b6ace-9dc7-4e51-b673-033f8134a7d4', serviceName='cluster-app', serviceId='node-1'}, error=null)

表示 node-1 成功当选为第一个 leader。

查看 Consul Web UI

进入 Key/Value 页面,可以看到生成的 key:

consul leadership election

key 为 services/cluster-app/leader,其 Value 包含 Session ID,说明已被锁定。

💡 此时其他实例无法获取该 key 的锁,直到当前 leader 主动释放或 Session 超时。


5. 总结

通过本文你应该掌握了:

  • ✅ Consul 如何利用 Session + KV Store 实现分布式锁
  • ✅ 多实例应用中如何安全地选出唯一 leader
  • ✅ 使用开源库快速集成领导选举能力
  • ✅ 本地通过 Docker 快速验证方案可行性

这套机制广泛应用于:

  • 分布式定时任务调度(避免重复执行)
  • 配置变更广播协调
  • 数据迁移、批量处理等幂等性要求高的场景

🔗 示例代码已托管至 GitHub:https://github.com/eugenp/tutorials/tree/master/spring-cloud-modules/spring-cloud-consul

如果你正在设计高可用系统,Consul 的领导选举是一个简单、可靠、值得信赖的选择。✅


原始标题:Leadership Election With Consul