分布式锁实战-偶遇 etcd 后就想抛弃 Redis ?

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
容器服务 Serverless 版 ACK Serverless,317元额度 多规格
简介: 分布式锁实战-偶遇 etcd 后就想抛弃 Redis ?

篇幅太长看着也累,尝试分成多个小章节,每天进步一点点。

前情回顾

分布式锁系列内容规划如下,本篇是第 3 篇:

  1. 《分布式锁上-初探
  2. 《分布式锁中-基于 Zookeeper 的实现是怎样》
  3. 《分布式锁中-偶遇 etcd 后就想抛弃 Redis ?》(本篇)
  4. 《分布式锁中-基于 Redis 的实现需避坑 - Jedis 篇》
  5. 《分布式锁中-基于 Redis 的实现很多样 - Redission 篇》(写作中)
  6. 《分布式锁中-基于 MySQL 的实现 - 有得用就好》(写作中)
  7. 《分布式锁中-多维度的对比各种分布式锁实现》(写作中)
  8. 《分布式锁下-分布式锁客户端的抽象、适配与加固》(写作中)

一、etcd 简介

1. etcd 的背景

虽然Kubernetes 给云原生时代带来了颠覆性的新气象,但却很少人了解被钦定作为其后端存储的 etcd ,本篇从分布式锁视角梳理etcd的各种机制,探索基于etcd的锁实现是怎样。

etcd 能被Kubernetes 如此青睐,是因为它一直在聆听社区的声音并快速改进,积极配合 Kubernetes 项目向前推进,解决社区反馈的痛点;发起 V2 到 V3 的重大版本更新,尤其是 19 年由 Google、Alibaba 等公司联合打造的 3.4 版本,满足了 Kubernetes 在超大型公司大规模使用中严苛的可用性、扩展性和性能等要求。

2Zmh5D.gif

上图描述了 etcd 名字的由来,其寓意是为大规模分布式系统提供存储配置信息;华为 Kubernetes 专家杜军老师专门为 etcd 著书《云原生分布式存储基石-etcd》,称它基石足见何等重要,另一位大咖etcd 的作者李响老师曾讲:“etcd 就是用来存储云上最重要的数据的”;相信随着云原生架构的演进,etcd 会担任越来越多重要的角色;技术人员在云原生时代,除了拥抱 Kubernetes,也要拥抱 etcd。

2.etcd vs ZooKeeper

很长一段时间 ZooKeeper(后文简称 ZK) 被作为默认首选项,用于解决分布式系统的协同和元数据存储,但其太复杂、迭代慢、难维护、性能缺陷等问题逐渐成为槽点;而 etcd 吸取了 ZK 的教训,从设计和实现上具有后见之明,提供了更好的工程和运维体验,其主要改进在于如下几个方面:

  • 动态的集群节点关系重配置
  • 高负载条件下的稳定读写
  • 多版本并发控制的数据模型
  • 持久、稳定的监听机制
  • 租约 (lease) 原语实现了连接与会话的解耦
  • 安全的分布式共享锁 API
  • 普适的 HTTP 、GRPC 通信协议

据说在一个由 3 台 8 核节点组成的云服务器上, etcd v3 版本可以做到每秒数万次的写操作和数十万次的读操作(ZK:😲);后续会有其他篇章结合QA的测试结果来探讨etcd服务端的设计和性能情况。

3.etcd 特性介绍

为满足本篇目标所需,也考虑到对 etcd 熟悉的读者不多,这里着重介绍以下几个关键特性:

  • 数据组织:etcd 在 V2 版本时跟 ZK 一样以树形目录结构来组织数据,V3 版本则通过半开区间( Key range)取代 V2 中的目录结构,优化成扁平的 Key-Value 结构,Key 仍采用之前的格式如/lock/lock1/uuid1、/lock/lock1/uuid2,感官跟树形目录形式保持一致,但实际是一个完整独立的 key;可以基于 前缀(Prefix)机制通过/lock/lock1/查询一批拥有相同前缀的数据,但其语义是前缀匹配查询而非目录子节点查询;这个改造也是为了支撑更多的特性和达到更好的性能。

2Zmh5D.gif

  • 集群模式:通常是由 3、5 个基数实例组成集群,当超过半数服务实例正常工作就能对外提供服务,既能避免单点故障,又尽量高可用,每个服务实例都有一个数据备份,通过 raft 协议实现数据全局一致。

2Zmh5D.gif

顺序更新:每个 etcd 节点都可接收读写请求,但变更类(增删改)请求会在集群内转给 leader 执行,来所有客户端的变更请求将按照 leader 接收的顺序被处理,在 ZK 中插入同名节点,ZK 会自动为同名节点名后添加递增序号,即避免冲突又具有顺序管控的意义;但 etcd 中不能插入同名 Key,它是采用 revision 机制来管控更新类请求的顺序,使用一个全局计数器,单调递增,每当有数据变更,revision 就会加一(具有全局唯一性),而每个 revision 都关联对应修改的数据,可以通过 revision 的大小推断数据变更的顺序,利用这个特性可以实现高级协调服务。

2Zmh5D.gif

租约(Lease)机制:常见的 TTL(Time To Live)机制是给单个 Key-Value 设置存活时间,超过时间后自动删除这个 Key-Value,V3 中加入的租约机制更高级一些,针对每个 Lease 设置了一个 TTL 时间,存储 Key-Value 时可指定一个 Lease,多个拥有相同 TTL 的 key 绑定到同一个 Lease,实现 Lease 的复用。同时也支持续约,可以通过客户端在 Lease 到期之前续约,以避免 Key-Value 过期后失效,也可以通过客户端主动解约,这比 ZK 中基于 session 的状态保持更灵活。

2Zmh5D.gif

  • 监听机制:客户端可以注册对某一个 Key 或一批相同前缀(前缀机制,似 ZK 中的父节点)Key 的监听,当被监听的这些 Key 发生变更,客户端将收到通知,感知到变更。

2Zmh5D.gif

etcd 的分布式锁正是基于以上特性来实现的,简单来说是:

  • 租约机制:用于支撑异常情况下的锁自动释放能力
  • 前缀和 Revision 机制:用于支撑公平获取锁和排队等待的能力
  • 监听机制:用于支撑抢锁能力
  • 集群模式:用于支撑锁服务的高可用

二、加解锁的流程描述

2Zmh5D.gif

1.准备客户端和 Key
  • 客户端连接 Etcd,获取 LockClient,获取 LeaseClient
  • 以/lock/lock1 为前缀 + / + uuid 创建全局唯一的 key,如:
  • client-a 线程 1 的 key 为"/lock/lock1/uuid1",申请 lock1
  • client-a 线程 2 的 key 为"/lock/lock1/uuid2",申请 lock1
  • client-c 线程 1 的 key 为"/lock/lock2/uuid3",申请 lock2
  • client-b 线程 1 的 key 为"/lock/lock2/uuid4",申请 lock2
  • 客户端 a、b、c 分别创建租约,租约的时长由业务输入确定
2.创建租约并保持续租
  • 方案 1-创建定时任务定时续租,无论客户端是持锁状态还是等待锁状态 Key 都必须存在,而 Key 是否释放由租约管控,所以都需要保持租约的活性
  • 持锁状态:当业务未完成时,不能让租约到期,需定时续租;当业务完成时可主动解除租约,持锁 Key 会被删除;若客户端异常,租约到期后持锁 Key 也会被删除;等锁的客户端监听到持锁 Key 被删除后,可开始抢锁。
  • 等锁状态:等锁超时会主动解除租约,或客户端异常时等锁 key 被删除,后边排队的就前进一步,尝试抢锁。
  • 方案 2-使用自动续约
3.绑定租约写 key
  • 每个客户端在执行 put 操作时,将第 1 步中准备的具有唯一性的 Key 绑定租约写入 etcd
4.获取竞争锁的 key-Value 列表
  • 以 lock1 为例,客户端以前缀"/lock/lock1" 读取所有匹配此前缀的的 key-Value 列表(key-Value 中带有 key 对应的 Revision)
5.对所获取的 Key-Value 列表按 revision 从小到大排序
6.判断自己是不是第一个(revision 最小),若是,则成功获取锁
7.若不是,则监听自己的前一个 Key-Value 的删除事件
  • 删除事件由主动释放租约或者因租约过期失效而触发
  • 这种只监听前一个 Key-Value 的方式避免了惊群效应问题
8.若是阻塞申请锁,则申请锁的操作可增加阻塞等待
9.若监听事件生效,则回到第 4 步重新进行判断,直到获取到锁
10解锁时,将第一个 Key-Value 的租约释放

三、etcd 分布式锁的能力

可能读者是单篇阅读,这里引入第一篇《分布式锁上-初探》中的一些内容,一个分布式锁应具备这样一些功能特点:

  • 互斥性:在同一时刻,只有一个客户端能持有锁
  • 安全性:避免死锁,如果某个客户端获得锁之后处理时间超过最大约定时间,或者持锁期间发生了故障导致无法主动释放锁,其持有的锁也能够被其他机制正确释放,并保证后续其它客户端也能加锁,整个处理流程继续正常执行
  • 可用性:也被称作容错性,分布式锁需要有高可用能力,避免单点故障,当提供锁的服务节点故障(宕机)时不影响服务运行,这里有两种模式:一种是分布式锁服务自身具备集群模式,遇到故障能自动切换恢复工作;另一种是客户端向多个独立的锁服务发起请求,当某个锁服务故障时仍然可以从其他锁服务读取到锁信息(Redlock)
  • 可重入性:对同一个锁,加锁和解锁必须是同一个线程,即不能把其他线程持有的锁给释放了
  • 高效灵活:加锁、解锁的速度要快;支持阻塞和非阻塞;支持公平锁和非公平锁

基于上边对 etcd 分布式锁的介绍,这里简单总结一下 etcd 的能力矩阵,ZK 的情况请看《分布式锁中-基于 Zookeeper 的实现》,redis锁的情况会在后续文章中补充

能力 ZK etcd Redis 原生 Redlock
互斥
安全 链接异常时,session 丢失自动释放锁 基于租约,租约过期后自动释放锁,不用像ZK那样释放链接
可用性 相对可用性还好
可重入 服务端非可重入,本地线程可重入 服务端非可重入,本地线程可重入需自研
加解锁速度 速度不算快 速度快,如GRPC 协议优势、服务端性能的优势
阻塞非阻塞 客户端两种能力都提供 jetcd-core 中,阻塞非阻塞由 Future#get 的超时控制能力支撑
公平非公平 公平锁 公平锁
可续租 天然支持,基于session 天然支持,基于Lease

四、jetcd 库实现分布式锁

etcd 针对 java 语言的客户端有官方的 jetcd-core 还有 IBM 的 etcd-java,下文使用 jetcd-core 做示例。

jetcd-core 中提供了高阶的 Lock API(无需使用者准备唯一 key、前缀查询 Key-Value 列表、排序判断自己是否 revision 最小的、监听前一个 Key-Value 的删除),使用者只需关注最原始的诉求:申请的锁是什么名称、用多久,申请不到就等多久;使用高阶 API 实现分布式锁的流程会比第 2 部分原生的流程要简单许多,流程如下:

  • 连接 etcd 集群
  • 获取 LockClient,获取 LeaseClient,不需要准备 key
  • 创建租约并保持续租, 你觉得哪一种方案会更好呢?
  • 方案 1-创建定时任务定时续租
  • 方案 2-使用自动续约
  • 绑定租约抢锁
  • 若抢到锁,则返回锁路径信息
  • 若没抢到锁,则阻塞等待(可设置超时返回)
  • 解锁时,传入锁信息解锁;根据业务场景决定如何解约

1.pom 依赖

<dependency>
  <groupId>io.etcd</groupId>
  <artifactId>jetcd-core</artifactId>
  <version>0.7.3</version>
</dependency>
复制代码

2.Lease 相关的 API 介绍

public interface Lease extends CloseableClient {
     //创建一个租约,过期时间是ttl,单位秒;没有请求超时控制
    CompletableFuture<LeaseGrantResponse> grant(long ttl);
     //创建一个租约,过期时间是ttl,单位秒;后两个参数是请求超时控制
    CompletableFuture<LeaseGrantResponse> grant(long ttl, long timeout, TimeUnit unit);
     //解约
    CompletableFuture<LeaseRevokeResponse> revoke(long leaseId);
    //主动续约1次
    CompletableFuture<LeaseKeepAliveResponse> keepAliveOnce(long leaseId);
    //查询租约信息
    CompletableFuture<LeaseTimeToLiveResponse> timeToLive(long leaseId, LeaseOption leaseOption);
    //自动续约
    CloseableClient keepAlive(long leaseId, StreamObserver<LeaseKeepAliveResponse> observer);
}
复制代码

3.lock 相关的 API 介绍

public interface Lock extends CloseableClient {
    //绑定租约申请指定名称的锁,抢锁成功返回锁秘钥,
    CompletableFuture<LockResponse> lock(ByteSequence name, long leaseId);
    //持锁秘钥解锁
    CompletableFuture<UnlockResponse> unlock(ByteSequence lockKey);
}
复制代码

4.分布式锁示例

package com.rock.dlock;
import io.etcd.jetcd.ByteSequence;
import io.etcd.jetcd.Client;
import io.etcd.jetcd.Lease;
import io.etcd.jetcd.Lock;
import io.etcd.jetcd.lease.LeaseKeepAliveResponse;
import io.etcd.jetcd.lock.LockResponse;
import io.grpc.stub.StreamObserver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
/**
 * @author zs
 * @date 2022/11/6 12:23 PM
 */
public class DemoEtcdLock {
    private final static Logger log = LoggerFactory.getLogger(DemoEtcdLock.class);
    private Client client;
    private Lock lockClient;
    private Lease leaseClient;
    private LockState lockState;
    class LockState{
        private String lockKey;
        private String lockPath;
        private String errorMsg;
        private long leaseTTL;
        private long leaseId;
        private boolean lockSuccess;
        public LockState(String lockKey, long leaseTTL) {
            this.lockKey = lockKey;
            this.leaseTTL = leaseTTL;
        }
        public String getLockKey() {
            return lockKey;
        }
        public void setLockKey(String lockKey) {
            this.lockKey = lockKey;
        }
        public String getLockPath() {
            return lockPath;
        }
        public void setLockPath(String lockPath) {
            this.lockPath = lockPath;
        }
        public String getErrorMsg() {
            return errorMsg;
        }
        public void setErrorMsg(String errorMsg) {
            this.errorMsg = errorMsg;
        }
        public long getLeaseId() {
            return leaseId;
        }
        public void setLeaseId(long leaseId) {
            this.leaseId = leaseId;
        }
        public boolean isLockSuccess() {
            return lockSuccess;
        }
        public void setLockSuccess(boolean lockSuccess) {
            this.lockSuccess = lockSuccess;
        }
        public long getLeaseTTL() {
            return leaseTTL;
        }
        public void setLeaseTTL(long leaseTTL) {
            this.leaseTTL = leaseTTL;
        }
    }
    public DemoEtcdLock(Client client, String lockKey, Long leaseTTL, TimeUnit unit) {
        this.client = client;
        //1.准备客户端
        this.lockClient = client.getLockClient();
        this.leaseClient = client.getLeaseClient();
        this.lockState = new LockState(lockKey,unit.toSeconds(leaseTTL));
    }
    public boolean lock()  {
        try {
            //2.创建租约,并自动续约
            createLease();
            //3.执行加锁,并为锁对应的Key绑定租约
            createLock();
        }catch (InterruptedException | ExecutionException e) {
            //todo:异常处理
        }
        return lockState.isLockSuccess();
    }
    public void unlock()  {
        try {
        //正常释放锁
            if (this.lockState.getLockPath() != null) {
                lockClient.unlock(ByteSequence.from(lockState.getLockPath().getBytes())).get();
            }
            //如果是主动续约,则关闭续约的定时任务
            //删除租约
            if (lockState.getLeaseId() != 0L) {
                leaseClient.revoke(lockState.getLeaseId());
            }
        } catch (InterruptedException | ExecutionException e) {
            //todo:异常处理
        }
        log.info("线程:{} 释放锁", Thread.currentThread().getName());
    }
    // 创建一个租约
    private void createLease() throws ExecutionException, InterruptedException {
        log.debug("[etcd-lock]: start to createLease." + this.lockState.getLockKey() + Thread.currentThread().getName());
        try {
            long leaseId = leaseClient.grant(this.lockState.getLeaseTTL()).get().getID();
            lockState.setLeaseId(leaseId);
            //自动续约
            StreamObserver<LeaseKeepAliveResponse> observer = new StreamObserver<LeaseKeepAliveResponse>() {
                @Override
                public void onNext(LeaseKeepAliveResponse value) {
                    log.trace("cluster node lease remaining ttl: {}, lease id: {}", value.getTTL(), value.getID());
                }
                @Override
                public void onError(Throwable t) {
                    log.error("cluster node lease keep alive failed. exception info: {}", t);
                }
                @Override
                public void onCompleted() {
                    log.trace("cluster node lease completed");
                }
            };
            // 设置自动续约
            leaseClient.keepAlive(leaseId, observer);
        }catch (InterruptedException | ExecutionException e) {
            log.error("[etcd-lock] Create lease failed:" + e);
            lockState.setErrorMsg("Create lease failed:" + e);
            throw e;
        }
    }
    private void createLock() throws ExecutionException, InterruptedException {
        String lockKey = this.lockState.getLockKey();
        log.debug("[etcd-lock]: start to createLock." + lockKey + Thread.currentThread().getName());
        try {
            LockResponse lockResponse = lockClient.lock(ByteSequence.from(lockKey.getBytes()), lockState.getLeaseId()).get();
            if (lockResponse != null) {
                String lockPath = lockResponse.getKey().toString(StandardCharsets.UTF_8);
                this.lockState.setLockPath(lockPath);
                log.info("线程:{} 加锁成功,锁路径:{}", Thread.currentThread().getName(), lockPath);
                this.lockState.setLockSuccess(true);
            }
        }
        catch (InterruptedException | ExecutionException e) {
            log.error("[etcd-lock] lock failed:" + e);
            lockState.setErrorMsg("[etcd-lock] lock failed:" + e);
            leaseClient.revoke(this.lockState.getLeaseId());
            throw e;
        }
    }
}
复制代码

5.测试锁

package com.rock.dlock;
import io.etcd.jetcd.Client;
import java.util.concurrent.TimeUnit;
/**
 * @author zs
 * @date 2022/11/6 12:23 PM
 */
public class TestEtcdLock {
    public static void main(String[] args) {
        Client client = Client.builder().endpoints("http://localhost:2379").build();
        DemoEtcdLock demoEtcdLock1 = new DemoEtcdLock(client,"rock",30L, TimeUnit.SECONDS);
        DemoEtcdLock demoEtcdLock2 = new DemoEtcdLock(client,"rock",30L, TimeUnit.SECONDS);
        boolean lock1 = demoEtcdLock1.lock();
        if(lock1) {
            try {
                System.out.printf("do something");
            } finally {
                demoEtcdLock1.unlock();
            }
        }
        demoEtcdLock1.lock();//demoEtcdLock1 持锁未释放
        demoEtcdLock2.lock();//demoEtcdLock2 客户端无可重入设计,这里将会阻塞等待demoEtcdLock1释放锁
    }
}
复制代码

五、总结

本篇从 etcd V3 版本的 Lease、Prefix 、Watch 等关键特性切入,介绍了如何基于这些特性来实现一个分布式锁,并基于jetcd-core库提供了一个分布式锁的示例,呈现了其关键API的用法;此示例尚未达到生产级可用,如异常、可重入、可重试、超时控制等功能都未补全,计划在下一篇介绍完redis之后,再介绍一个健壮的分布式锁客户端要如何抽象设计,如何适配 ZK 、Redis 、etcd 。

六、最后说一句(请关注,莫错过)

如果这篇文章对您有所帮助,或者有所启发的话,可以关注公众号:【 架构染色 】,进行交流和学习。您的支持是我坚持写作最大的动力。

求一键三连:点赞、收藏、转发。

Reference:


相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
1月前
|
NoSQL Java Redis
太惨痛: Redis 分布式锁 5个大坑,又大又深, 如何才能 避开 ?
Redis分布式锁在高并发场景下是重要的技术手段,但其实现过程中常遇到五大深坑:**原子性问题**、**连接耗尽问题**、**锁过期问题**、**锁失效问题**以及**锁分段问题**。这些问题不仅影响系统的稳定性和性能,还可能导致数据不一致。尼恩在实际项目中总结了这些坑,并提供了详细的解决方案,包括使用Lua脚本保证原子性、设置合理的锁过期时间和使用看门狗机制、以及通过锁分段提升性能。这些经验和技巧对面试和实际开发都有很大帮助,值得深入学习和实践。
太惨痛: Redis 分布式锁 5个大坑,又大又深, 如何才能 避开 ?
|
7天前
|
NoSQL Redis
Redis分布式锁如何实现 ?
Redis分布式锁通过SETNX指令实现,确保仅在键不存在时设置值。此机制用于控制多个线程对共享资源的访问,避免并发冲突。然而,实际应用中需解决死锁、锁超时、归一化、可重入及阻塞等问题,以确保系统的稳定性和可靠性。解决方案包括设置锁超时、引入Watch Dog机制、使用ThreadLocal绑定加解锁操作、实现计数器支持可重入锁以及采用自旋锁思想处理阻塞请求。
41 16
|
22天前
|
NoSQL 关系型数据库 MySQL
MySQL与Redis协同作战:优化百万数据查询的实战经验
【10月更文挑战第13天】 在处理大规模数据集时,传统的关系型数据库如MySQL可能会遇到性能瓶颈。为了提升数据处理的效率,我们可以结合使用MySQL和Redis,利用两者的优势来优化数据查询。本文将分享一次实战经验,探讨如何通过MySQL与Redis的协同工作来优化百万级数据统计。
48 5
|
28天前
|
缓存 NoSQL Java
Spring Boot与Redis:整合与实战
【10月更文挑战第15天】本文介绍了如何在Spring Boot项目中整合Redis,通过一个电商商品推荐系统的案例,详细展示了从添加依赖、配置连接信息到创建配置类的具体步骤。实战部分演示了如何利用Redis缓存提高系统响应速度,减少数据库访问压力,从而提升用户体验。
69 2
|
1月前
|
NoSQL Java Redis
开发实战:使用Redisson实现分布式延时消息,订单30分钟关闭的另外一种实现!
本文详细介绍了 Redisson 延迟队列(DelayedQueue)的实现原理,包括基本使用、内部数据结构、基本流程、发送和获取延时消息以及初始化延时队列等内容。文章通过代码示例和流程图,逐步解析了延迟消息的发送、接收及处理机制,帮助读者深入了解 Redisson 延迟队列的工作原理。
|
1月前
|
NoSQL Redis 数据库
计数器 分布式锁 redis实现
【10月更文挑战第5天】
47 1
|
1月前
|
NoSQL 算法 关系型数据库
Redis分布式锁
【10月更文挑战第1天】分布式锁用于在多进程环境中保护共享资源,防止并发冲突。通常借助外部系统如Redis或Zookeeper实现。通过`SETNX`命令加锁,并设置过期时间防止死锁。为避免误删他人锁,加锁时附带唯一标识,解锁前验证。面对锁提前过期的问题,可使用守护线程自动续期。在Redis集群中,需考虑主从同步延迟导致的锁丢失问题,Redlock算法可提高锁的可靠性。
73 4
|
3月前
|
NoSQL Redis
基于Redis的高可用分布式锁——RedLock
这篇文章介绍了基于Redis的高可用分布式锁RedLock的概念、工作流程、获取和释放锁的方法,以及RedLock相比单机锁在高可用性上的优势,同时指出了其在某些特殊场景下的不足,并提到了ZooKeeper作为另一种实现分布式锁的方案。
110 2
基于Redis的高可用分布式锁——RedLock
|
3月前
|
缓存 NoSQL Java
SpringBoot整合Redis、以及缓存穿透、缓存雪崩、缓存击穿的理解分布式情况下如何添加分布式锁 【续篇】
这篇文章是关于如何在SpringBoot应用中整合Redis并处理分布式场景下的缓存问题,包括缓存穿透、缓存雪崩和缓存击穿。文章详细讨论了在分布式情况下如何添加分布式锁来解决缓存击穿问题,提供了加锁和解锁的实现过程,并展示了使用JMeter进行压力测试来验证锁机制有效性的方法。
SpringBoot整合Redis、以及缓存穿透、缓存雪崩、缓存击穿的理解分布式情况下如何添加分布式锁 【续篇】
|
1月前
|
缓存 NoSQL Java
大数据-50 Redis 分布式锁 乐观锁 Watch SETNX Lua Redisson分布式锁 Java实现分布式锁
大数据-50 Redis 分布式锁 乐观锁 Watch SETNX Lua Redisson分布式锁 Java实现分布式锁
59 3
大数据-50 Redis 分布式锁 乐观锁 Watch SETNX Lua Redisson分布式锁 Java实现分布式锁