Java分布式数据单例模式等怎么单例

wen java案例 30

本文目录导读:

Java分布式数据单例模式等怎么单例

  1. 基于分布式锁(推荐)
  2. 基于数据库唯一约束
  3. 基于注册中心(ZooKeeper / etcd)
  4. 基于全局序列化 ID(特殊场景)
  5. 核心冲突:单例模式定义 vs 分布式环境
  6. 选型建议
  7. 常见陷阱

在分布式系统中,传统的 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 等成熟库,避免自己实现锁导致的死锁、锁过期等问题。

基于数据库唯一约束

利用数据库的唯一索引或主键约束,确保全局只有一个记录。

实现方式:

  1. 在数据库中建一张表 global_singleton,字段 idinstance_data
  2. id 设为唯一主键(如固定值 1)。
  3. 所有节点尝试 INSERT 该记录,只有第一个插入成功的节点才能创建实例。
  4. 后续节点 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 实现步骤:

  1. 所有节点在 ZK 上创建临时顺序节点 /singleton/instance-0001
  2. 编号最小的节点(/singleton/instance-0001)成为 Leader。
  3. Leader 节点创建实例,其他节点监听该节点,实例销毁时重新选举。

优点:天然具备高可用和自动切换能力。

基于全局序列化 ID(特殊场景)

如果单例不是“对象”,而是一个“服务实例”本身(如某个定时任务只在一个节点执行),可以用幂等 ID:

  • 每个请求携带全局唯一 ID
  • 第一个处理该 ID 的节点执行,后续节点检测到已执行则跳过

核心冲突:单例模式定义 vs 分布式环境

传统的单例模式要求:

只创建一次,全局只有一个实例

在分布式系统中:

  1. Java 对象的单例:只在单进程内唯一,分布式要求多进程共享同一个对象,但不同 JVM 进程中的对象地址不同,无法通过语言特性做到。
  2. 实际业务含义:分布式单例的核心是“保证对某资源的独占访问”“只初始化一次”,分布式任务调度中只允许一个节点执行任务、全局唯一的配置加载器等。

选型建议

场景 推荐方案
高并发、低延迟 Redis 分布式锁(Redisson)
已有 ZK 集群 ZooKeeper 选举模式
简单且无额外中间件 数据库唯一约束
定时任务、离线处理 无状态加锁

常见陷阱

  1. 锁过期导致重复创建:确保锁持有时间 >> 实例创建时间,或使用 Redisson 看门狗自动续期。
  2. 死锁:务必加锁释放逻辑(try/finally)。
  3. 性能问题:所有节点抢锁,单点瓶颈,考虑本地缓存 + 定期刷新。

抱歉,评论功能暂时关闭!