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

说明

源码地址: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线程被占满。
相关推荐
与遨游于天地14 分钟前
分布式锁从Redis到Redisson的演进
数据库·redis·分布式
Francek Chen3 小时前
【大数据存储与管理】实验3:熟悉常用的HBase操作
大数据·数据库·分布式·hbase
七夜zippoe4 小时前
DolphinDB分布式表:创建与管理
数据库·分布式·维度·dolphindb·数据写入
KmSH8umpK4 小时前
Redis分布式锁进阶第十七篇
数据库·redis·分布式
fengxin_rou4 小时前
JVM 内存结构与内存溢出 / 泄漏问题全解析
java·开发语言·jvm·分布式·rabbitmq
gQ85v10Db18 小时前
Redis分布式锁进阶第十七篇:微服务分布式锁全局治理 + 跨团队统一规范落地 + 全链路稳定性提升方案
redis·分布式·微服务
gQ85v10Db1 天前
Redis分布式锁进阶第十八篇:本地缓存+分布式锁双锁架构 + 高并发削峰兜底 + 极致性能无损优化实战
redis·分布式·缓存
小江的记录本1 天前
【Kafka核心】Kafka高性能的四大核心支柱:零拷贝、批量发送、页缓存、压缩
java·数据库·分布式·后端·缓存·kafka·rabbitmq
gQ85v10Db1 天前
Redis分布式锁进阶第十四篇:全系列终局架构复盘 + 锁体系统一规范 + 线上全年零事故收官方案
redis·分布式·架构
KmSH8umpK1 天前
Redis分布式锁进阶第十二篇
数据库·redis·分布式