分布式锁,Redission,其它实现问题讲解,以及面试题回答案例

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
注册配置 MSE Nacos/ZooKeeper,118元/月
简介: 分布式锁,Redission,其它实现问题讲解,以及面试题回答案例

分布式锁,Redission,其它实现问题讲解,以及面试题回答案例


什么是分布式锁?


分布式锁是一种同步机制,用于控制多个进程或节点对共享资源的访问。其目标是在分布式系统中防止并发访问引起的数据不一致或竞争条件问题。当一个节点获得了分布式锁后,其他节点必须等待或被阻塞,直到锁被释放。


分布式锁的需求


在分布式环境中,有几个方面需要考虑:


  1. 原子性: 获取锁和释放锁的操作应该是原子的,以防止竞态条件。
  2. 可靠性: 即使在节点故障或网络分区的情况下,分布式锁也应该是可靠的,能够正确地保持锁的状态。
  3. 性能: 分布式锁应该是高性能的,以确保不会成为系统的瓶颈。


常见的分布式锁实现方式


  1. 基于数据库的实现: 使用数据库的事务特性来实现分布式锁,通过在数据库中创建一个锁表,将锁状态存储在数据库中。
  2. 基于ZooKeeper的实现: ZooKeeper是一个分布式协调服务,可以用来实现分布式锁。通过创建ZooKeeper节点来表示锁的状态,可以实现简单而可靠的分布式锁。
  3. 基于Redis的实现: Redis是一种内存数据库,提供了原子操作和分布式特性,因此可以用作分布式锁的存储介质。


Redis作为分布式锁的存储介质


Redis是一个快速且具有分布式特性的内存数据库,常被用作分布式锁的存储介质。在Redis中,我们可以使用两种方式实现分布式锁:


  1. 基于SETNX和EXPIRE指令的简单实现: 使用SETNX(Set if Not eXists)指令来尝试获取锁,如果成功则获得锁,然后通过EXPIRE指令设置锁的过期时间。释放锁时,通过删除对应的键来释放锁。
SET resource_name my_unique_identifier NX EX 10
  1. 基于Lua脚本的复杂实现: 使用Lua脚本在一次请求中执行多个指令,从而实现获取锁和设置过期时间的原子操作。


Redission作为分布式锁解决方案


Redission是一个基于Redis的Java驱动,提供了丰富的分布式锁功能。它封装了分布式锁的复杂性,提供了简单而强大的API。


使用Redission获取锁

RRedissionClient redisson = Redisson.create();
RLock lock = redisson.getLock("myLock");
lock.lock(); // 尝试获取锁
try {
    // 在这里执行需要加锁的代码
} finally {
    lock.unlock(); // 释放锁
}


锁的可重入性


Redission支持锁的可重入性,同一个线程可以多次获取同一把锁,每次获取都需要相应的释放。

RLock lock = redisson.getLock("myLock");
lock.lock();
try {
    // 执行加锁代码
    lock.lock(); // 可以再次获取锁
    try {
        // 执行嵌套的加锁代码
    } finally {
        lock.unlock(); // 释放嵌套的锁
    }
} finally {
    lock.unlock(); // 释放外层锁
}


基于数据库的分布式锁


使用关系型数据库实现分布式锁是一种常见的方式。通过在数据库中创建锁表,并使用事务来确保获取锁和释放锁的原子性,可以实现基于数据库的分布式锁。

-- 创建锁表
CREATE TABLE distributed_lock (
    lock_name VARCHAR(255) PRIMARY KEY,
    locked_by VARCHAR(255),
    expiration_time TIMESTAMP
);

-- 获取锁
INSERT INTO distributed_lock (lock_name, locked_by, expiration_time)
VALUES ('myLock', 'myIdentifier', NOW() + INTERVAL 10 SECOND)
ON DUPLICATE KEY UPDATE locked_by = VALUES(locked_by), expiration_time = VALUES(expiration_time);

-- 释放锁
DELETE FROM distributed_lock WHERE lock_name = 'myLock' AND locked_by = 'myIdentifier';


ZooKeeper分布式锁的实现


  1. 创建锁节点: 每个参与锁的节点在ZooKeeper上创建一个独特的顺序临时节点,表示它想要获取锁。
  2. 获取锁: 节点通过获取锁的过程,即检查是否是所有子节点中最小的节点。如果是最小节点,则它成功获取了锁。
  3. 监听前一个节点: 如果节点没有成功获取锁,它会监听它前面一个节点的删除事件。一旦前一个节点被删除,表示锁可用,当前节点再次尝试获取锁。
  4. 释放锁: 节点在使用锁完成后,将自己创建的节点删除,释放锁。


下面是一个使用ZooKeeper进行分布式锁实现的简单示例:

import org.apache.zookeeper.*;

public class ZooKeeperLock implements Watcher {

    private ZooKeeper zooKeeper;
    private String lockPath;

    public ZooKeeperLock(String connectionString, String lockPath) throws Exception {
        this.zooKeeper = new ZooKeeper(connectionString, 5000, this);
        this.lockPath = lockPath;
    }

    public void acquireLock() throws KeeperException, InterruptedException {
        String lockNode = zooKeeper.create(lockPath + "/lock_", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE,
                CreateMode.EPHEMERAL_SEQUENTIAL);

        // 获取所有子节点
        var children = zooKeeper.getChildren(lockPath, false);

        // 排序子节点
        children.sort(String::compareTo);

        // 判断当前节点是否是最小节点
        if (lockNode.endsWith(children.get(0))) {
            System.out.println("Lock acquired!");
            return;
        }

        // 如果不是最小节点,则监听前一个节点
        String predecessor = lockPath + "/" + children.get(children.indexOf(lockNode.substring(lockPath.length() + 1)) - 1);
        zooKeeper.exists(predecessor, true);
    }

    public void releaseLock() throws KeeperException, InterruptedException {
        zooKeeper.close();
    }

    @Override
    public void process(WatchedEvent event) {
        if (event.getType() == Event.EventType.NodeDeleted) {
            // 前一个节点被删除,重新尝试获取锁
            try {
                acquireLock();
            } catch (KeeperException | InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}


考虑因素和注意事项


  1. 锁的粒度: 锁的粒度应该尽量小,以减小锁的争用,提高系统的并发性能。
  2. 死锁: 在设计分布式锁时,要注意死锁的可能性。一些系统引入超时机制,以防止因故障或其他原因导致的死锁。
  3. 锁的持有时间: 锁的持有时间应该尽量短,以减小锁的争用时间,提高系统的响应性能。
  4. 锁的可重入性: 考虑是否需要支持锁的可重入性,以便同一个线程可以多次获取同一把锁。
  5. 容错性: 分布式锁的实现应该考虑系统的容错性,即使在节点故障或网络分区的情况下,锁仍然能够正确地工作。


实际应用中的分布式锁


在实际应用中,分布式锁常用于以下场景:


  • 防止重复操作: 通过锁来确保某个操作只能被执行一次,防止重复操作。
  • 控制资源访问: 限制对共享资源的并发访问,确保数据一致性。
  • 分布式事务: 在分布式事务中使用分布式锁来确保事务的一致性。


面试回答演示


分布式锁是一种同步机制,简单的说就是在分布式系统中当多个进程或节点共享资源的时候,用于解决他们因为并发访问引起的数据不一致问题,当一个节点获得分布式锁之后,其他节点必须等待直到说释放为止。一般情况下分布式锁的实现需要考虑三个方面,也就是原子性,可靠性和性能,要在高性能情况下,让分布式锁在获得和释放的过程中操作是原子的,同时在节点故障或者网络分区情况,分布式锁还是可靠的。


常见的实现方式有基于数据库的实现,基于zookeeper的实现,基于redis的实现,数据库实现的话就是通过数据库创建锁表,并且利用数据库的事物来确保锁的获取与释放是原子性的,redis的话就是可以使用set,setnx和expire指令来实现,setnx是用来获取锁的,如果成功获取锁,再用expire指令来设置锁的过期时间,再复杂的锁的实现可以使用lua脚本进行实现,lua脚本可以在一次请求的过程中执行过个指令。


还有的方法是使用redission这个基于redis的java驱动来实现,它里面提供了丰富的api来实现这个复杂的过程,比如最开始创建一个redisson对象,然后通过这个redisson对象可以获取锁,然后再使用try…finally在try中加入需要加锁的代码,在finally中进行锁的释放,redisson还支持锁的可重入性,也就是允许同一个线程多次获取同一把锁,也就是使用try…finally嵌套,这种方式可以解决一些死锁的情况,比如在我正常的业务没有问题,但是存在数据库发生异常的情况而导致的错误,这个时候我们使用try…catch嵌套来实现,里面的try…catch来处理正常业务,外面的try…catch来处理数据库异常的情况。


zookeeper也可以实现分布式锁,这个实现的话,一般是每个参与锁的节点在zookeeper上创建一个独特的顺序临时节点,然后节点获取锁的过程,当该节点是所有子节点中最小的节点他就可以获取锁,一个节点没有获取锁成功,他会监听前面一个节点是否发生删除事件,因为一个锁他的锁使用完了之后,他会把自己创建的节点删了,释放锁。


相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
4月前
|
存储 缓存 NoSQL
Redis常见面试题(二):redis分布式锁、redisson、主从一致性、Redlock红锁;Redis集群、主从复制,哨兵模式,分片集群;Redis为什么这么快,I/O多路复用模型
redis分布式锁、redisson、可重入、主从一致性、WatchDog、Redlock红锁、zookeeper;Redis集群、主从复制,全量同步、增量同步;哨兵,分片集群,Redis为什么这么快,I/O多路复用模型——用户空间和内核空间、阻塞IO、非阻塞IO、IO多路复用,Redis网络模型
Redis常见面试题(二):redis分布式锁、redisson、主从一致性、Redlock红锁;Redis集群、主从复制,哨兵模式,分片集群;Redis为什么这么快,I/O多路复用模型
|
3月前
|
存储 监控 固态存储
【vSAN分布式存储服务器数据恢复】VMware vSphere vSAN 分布式存储虚拟化平台VMDK文件1KB问题数据恢复案例
在一例vSAN分布式存储故障中,因替换故障闪存盘后磁盘组失效,一台采用RAID0策略且未使用置备的虚拟机VMDK文件受损,仅余1KB大小。经分析发现,该VMDK文件与内部虚拟对象关联失效导致。恢复方案包括定位虚拟对象及组件的具体物理位置,解析分配空间,并手动重组RAID0结构以恢复数据。此案例强调了深入理解vSAN分布式存储机制的重要性,以及定制化数据恢复方案的有效性。
92 5
|
3月前
|
算法 Go
[go 面试] 雪花算法与分布式ID生成
[go 面试] 雪花算法与分布式ID生成
|
21天前
|
程序员
后端|一个分布式锁「失效」的案例分析
小猿最近很苦恼:明明加了分布式锁,为什么并发还是会出问题呢?
30 2
|
1月前
|
Java 应用服务中间件 程序员
JVM知识体系学习八:OOM的案例(承接上篇博文,可以作为面试中的案例)
这篇文章通过多个案例深入探讨了Java虚拟机(JVM)中的内存溢出问题,涵盖了堆内存、方法区、直接内存和栈内存溢出的原因、诊断方法和解决方案,并讨论了不同JDK版本垃圾回收器的变化。
29 4
|
1月前
|
分布式计算 NoSQL Java
Hadoop-32 ZooKeeper 分布式锁问题 分布式锁Java实现 附带案例和实现思路代码
Hadoop-32 ZooKeeper 分布式锁问题 分布式锁Java实现 附带案例和实现思路代码
43 2
|
3月前
|
存储 固态存储 虚拟化
【vSAN分布式存储服务器数据恢复】VMware vSphere vSAN ESXi超融合HCI分布式存储数据恢复案例
近期,我司处理了一个由10台华为OceanStor存储组成的vSAN超融合架构,其中一台存储闪存盘出现故障,用户取下后用新的闪存盘代替,然后对该闪存盘所在的磁盘组进行重建,导致集群中一台使用0置备策略的虚拟机数据丢失。
77 6
|
4月前
|
canal 缓存 NoSQL
Redis常见面试题(一):Redis使用场景,缓存、分布式锁;缓存穿透、缓存击穿、缓存雪崩;双写一致,Canal,Redis持久化,数据过期策略,数据淘汰策略
Redis使用场景,缓存、分布式锁;缓存穿透、缓存击穿、缓存雪崩;先删除缓存还是先修改数据库,双写一致,Canal,Redis持久化,数据过期策略,数据淘汰策略
Redis常见面试题(一):Redis使用场景,缓存、分布式锁;缓存穿透、缓存击穿、缓存雪崩;双写一致,Canal,Redis持久化,数据过期策略,数据淘汰策略
|
3月前
|
Go API 数据库
[go 面试] 分布式事务框架选择与实践
[go 面试] 分布式事务框架选择与实践
|
4月前
|
NoSQL Java Redis
jedis 与 redission 实现分布式锁
jedis 与 redission 实现分布式锁
92 4