本文目录导读:

在分布式系统中,传统的 Java 单例模式(如双重检查锁、静态内部类)无法保证全局唯一性,因为它们仅能在单个 JVM 进程内生效。
要在分布式环境下实现“单例”(即全局唯一实例),核心思路是引入第三方协调组件,将实例的创建权交给分布式锁或注册中心。
以下是分布式环境下实现“分布式单例”的 4 种主流方案:
基于分布式锁(推荐)
这是最直接的方式:在创建单例对象时,先获取一个全局锁,获取成功才创建,创建后释放锁。
实现思路(以 Redis 为例):
public class DistributedSingleton {
private static volatile DistributedSingleton instance;
private static final String LOCK_KEY = "singleton:lock";
private DistributedSingleton() {}
public static DistributedSingleton getInstance() {
if (instance == null) { // 本地快速检查
String token = UUID.randomUUID().toString();
try {
// 尝试获取分布式锁(setnx + expire原子操作)
boolean locked = RedisUtil.setIfAbsent(LOCK_KEY, token, 10, TimeUnit.SECONDS);
if (locked) {
if (instance == null) { // 双重检查
instance = new DistributedSingleton();
}
} else {
// 获取不到锁,可以选择等待、重试或抛出异常
Thread.sleep(100);
return getInstance(); // 递归重试(注意防死循环)
}
} finally {
// 释放锁:必须验证是自己的锁(通过token)
if (token.equals(RedisUtil.get(LOCK_KEY))) {
RedisUtil.delete(LOCK_KEY);
}
}
}
return instance;
}
}
💡 注意:实际生产中用 Redisson 等成熟库,避免自己实现锁导致的死锁、锁过期等问题。
基于数据库唯一约束
利用数据库的唯一索引或主键约束,确保全局只有一个记录。
实现方式:
- 在数据库中建一张表
global_singleton,字段id、instance_data。 - 将
id设为唯一主键(如固定值1)。 - 所有节点尝试
INSERT该记录,只有第一个插入成功的节点才能创建实例。 - 后续节点
INSERT会失败(违反唯一约束),直接返回已有实例。
核心代码片段:
public class DistributedSingleton {
private static DistributedSingleton instance;
public static DistributedSingleton getInstance() {
if (instance == null) {
try {
// 尝试插入唯一记录
jdbcTemplate.update("INSERT INTO global_singleton (id, data) VALUES (1, ?)", createData());
instance = new DistributedSingleton();
} catch (DuplicateKeyException e) {
// 已有节点创建,读取实例数据
String data = jdbcTemplate.queryForObject("SELECT data FROM global_singleton WHERE id = 1", String.class);
// 反序列化或直接复用
}
}
return instance;
}
}
基于注册中心(ZooKeeper / etcd)
利用选举机制:只有 Leader 节点才负责创建单例实例。
ZooKeeper 实现步骤:
- 所有节点在 ZK 上创建临时顺序节点
/singleton/instance-0001。 - 编号最小的节点(
/singleton/instance-0001)成为 Leader。 - Leader 节点创建实例,其他节点监听该节点,实例销毁时重新选举。
优点:天然具备高可用和自动切换能力。
基于全局序列化 ID(特殊场景)
如果单例不是“对象”,而是一个“服务实例”本身(如某个定时任务只在一个节点执行),可以用幂等 ID:
- 每个请求携带全局唯一 ID
- 第一个处理该 ID 的节点执行,后续节点检测到已执行则跳过
核心冲突:单例模式定义 vs 分布式环境
传统的单例模式要求:
只创建一次,全局只有一个实例
在分布式系统中:
- Java 对象的单例:只在单进程内唯一,分布式要求多进程共享同一个对象,但不同 JVM 进程中的对象地址不同,无法通过语言特性做到。
- 实际业务含义:分布式单例的核心是“保证对某资源的独占访问”或“只初始化一次”,分布式任务调度中只允许一个节点执行任务、全局唯一的配置加载器等。
选型建议
| 场景 | 推荐方案 |
|---|---|
| 高并发、低延迟 | Redis 分布式锁(Redisson) |
| 已有 ZK 集群 | ZooKeeper 选举模式 |
| 简单且无额外中间件 | 数据库唯一约束 |
| 定时任务、离线处理 | 无状态加锁 |
常见陷阱
- 锁过期导致重复创建:确保锁持有时间 >> 实例创建时间,或使用 Redisson 看门狗自动续期。
- 死锁:务必加锁释放逻辑(try/finally)。
- 性能问题:所有节点抢锁,单点瓶颈,考虑本地缓存 + 定期刷新。
版权声明:足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播