最强性能分布式锁实现方案

说明

源码地址:github.com/gaojindeng/...

在RPC-Lock中,利用Dubbo进行RPC调用时,根据特定的key进行哈希路由,将请求路由到同一个节点,并在该节点内部使用ReentrantLock来实现分布式锁的功能。

在服务提供者节点扩容时,可能会导致相同key路由到多个节点。为了应对这种情况,我设计了一种机制:在扩容时会对锁进行升级,从最初的ReentrantLock本地锁升级到Redis的分布式锁。升级的具体步骤是先获取本地ReentrantLock锁,然后再获取Redis分布式锁,从而确保在锁升级过程中保持资源访问的安全性。

为了避免各个消费节点在服务提供者新节点上线时可能出现的并发问题,扩容阶段的路由策略并不会立即将请求路由到新节点,而是继续将请求路由到旧节点,并同时升级锁的级别为ReentrantLock与Redis分布式锁的组合。只有当经过一定时间的阀值后,才会将请求路由到新节点,这样确保了对服务提供者新节点的监听过程不会引发并发问题。

这种设计保障了在RPC-Lock中服务提供者扩容时的安全性,通过在扩容阶段暂时维持旧节点的路由与锁升级方式,来避免潜在的并发问题,最终实现了对新节点的平稳监听和过渡。

扩容时路由切换示意图:
  1. 初始时,消费者针对同一个key消息都是路由到01节点使用轻量级本地锁
  1. 生产者新增一个节点02,hash路由虽然路由到新的节点,但是新节点还处于一个保护期,进行锁升级继续路由到旧的节点
  1. 旧节点的保护期结束后,则使用重量级锁路由到新的节点
  1. 新节点的保护期结束后,则进行锁降级为轻量级锁进行路由到新节点

测试demo

消费者:

java 复制代码
@DubboReference
private ProviderService providerService;

public String sayHello(String name) {
    //添加lockKey
    LockContext.setLockKey(name);
    return providerService.sayHello(name);
}

生产者:

java 复制代码
@Override
public String sayHello(String name) {
    Lock lock = LockContext.getLock();

    //加锁
    lock.lock();
    try {
        //业务逻辑处理
        System.out.println("Hello " + name + ", request from consumer: " + RpcContext.getContext().getRemoteAddress());
        return "Hello " + name;
    } finally {
        //释放锁
        lock.unlock();
    }

}

源码实现

代码实现主要有三个难点:

  1. 如何刷新和保存provider节点信息,要确保在一定时间内,所有的消费者拿到的远程服务列表是一致的。
  2. 路由选择,要维护新旧两套远程服务列表,且需要告诉服务提供者要不要进行锁升级
  3. 服务提供者provider收到请求后,如何针对同一个key创建同一个RpcLock对象,且没有其他线程在使用时如何销毁

我这里只讲下第三个点的实现逻辑,其他的可以去看源码。

java 复制代码
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
    // 1.获取需要加锁的key
    String lockKey = LockContext.getLockKey();
    if (lockKey == null || lockKey.isEmpty()) {
        return invoker.invoke(invocation);
    }

    // 2.根据key获取对应的RpcLock
    RpcLock rpcLock = getLock(lockKey, LockContext.isRedisLock());
    try {
        // 3.具体接口调用
        return invoker.invoke(invocation);
    } finally {
        // 4.释放线程占用,如果都没有线程占用,则从map中移除该rpcLock对象
        rpcLock.releaseWorker();
    }
}

private RpcLock getLock(String lockKey, boolean isRedisLock) {
    // 从map中获取锁对象,如果不存在则创建
    RpcLock rpcLock = lockMap.computeIfAbsent(lockKey, k -> createLock(lockKey, isRedisLock));
    
    // addWorker就是添加线程栈用,如果添加失败,说明被其他线程移除了,需要重新创建
    while ((rpcLock = (RpcLock) rpcLock.addWorker()) == null) {
        rpcLock = lockMap.computeIfAbsent(lockKey, k -> createLock(lockKey, isRedisLock));
        rpcLock.setRedisLockSwitch(isRedisLock);
    }
    return rpcLock;
}

使用读锁去添加线程占用;移除占用时如果发现需要删除rpcLock对象则会使用写锁

java 复制代码
public Lock addWorker() {
    ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();
    readLock.lock();
    try {
        if (active) {
            workerQueue.add(Thread.currentThread().getName());
            return this;
        } else {
            //说明已经被移除了
            return null;
        }
    } finally {
        readLock.unlock();
    }
}

public void releaseWorker() {
    workerQueue.remove(Thread.currentThread().getName());
    if (workerQueue.size() > 0) {
        return;
    }
    ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();
    writeLock.lock();

    try {
        if (workerQueue.size() < 1) {
            active = false;
            //移除自己
            RpcLockConsumerFilter.lockMap.remove(lockKey);
        }
    } finally {
        writeLock.unlock();
    }
}

风险点

  • 由单个节点扩容到多个节点会有问题,dubbo源码写死了只有1个服务提供者时,不会执行自定义的balance的逻辑,这个只能去修改源码。
  • 如果不同的消费者监听到服务提供者上线/下线时间长时间都不一致时,就会有并发问题。
  • 新节点保护期的时间不好去设定,控制的不好会有并发问题,特别是所有服务雪崩时,dubbo线程被占满。
相关推荐
武子康1 小时前
Java-72 深入浅出 RPC Dubbo 上手 生产者模块详解
java·spring boot·分布式·后端·rpc·dubbo·nio
橘子在努力4 小时前
【橘子分布式】Thrift RPC(理论篇)
分布式·网络协议·rpc
lifallen6 小时前
Kafka 时间轮深度解析:如何O(1)处理定时任务
java·数据结构·分布式·后端·算法·kafka
沈健_算法小生9 小时前
基于SpringBoot3集成Kafka集群
分布式·kafka·linq
Swift社区9 小时前
ELK、Loki、Kafka 三种日志告警联动方案全解析(附实战 Demo)
分布式·elk·kafka
chanalbert18 小时前
Nacos 技术研究文档(基于 Nacos 3)
spring boot·分布式·spring cloud
线条120 小时前
Spark 单机模式安装与测试全攻略
大数据·分布式·spark
C182981825751 天前
分布式ID 与自增区别
分布式
码字的字节1 天前
深入解析Hadoop架构设计:原理、组件与应用
大数据·hadoop·分布式·hadoop架构设计
悟能不能悟1 天前
Dubbo跨越分布式事务的最终一致性陷阱
分布式·wpf·dubbo