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

本文涉及的产品
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
简介: 分布式锁,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
相关文章
|
1月前
|
监控 负载均衡 Cloud Native
ZooKeeper分布式协调服务详解:面试经验与必备知识点解析
【4月更文挑战第9天】本文深入剖析ZooKeeper分布式协调服务原理,涵盖核心概念如Server、Client、ZNode、ACL、Watcher,以及ZAB协议在一致性、会话管理、Leader选举中的作用。讨论ZooKeeper数据模型、操作、会话管理、集群部署与管理、性能调优和监控。同时,文章探讨了ZooKeeper在分布式锁、队列、服务注册与发现等场景的应用,并在面试方面分析了与其它服务的区别、实战挑战及解决方案。附带Java客户端实现分布式锁的代码示例,助力提升面试表现。
124 2
|
1月前
|
存储 分布式计算 大数据
HBase分布式数据库关键技术与实战:面试经验与必备知识点解析
【4月更文挑战第9天】本文深入剖析了HBase的核心技术,包括数据模型、分布式架构、访问模式和一致性保证,并探讨了其实战应用,如大规模数据存储、实时数据分析及与Hadoop、Spark集成。同时,分享了面试经验,对比了HBase与其他数据库的差异,提出了应对挑战的解决方案,展望了HBase的未来趋势。通过Java API代码示例,帮助读者巩固理解。全面了解和掌握HBase,能为面试和实际工作中的大数据处理提供坚实基础。
50 3
|
4月前
|
存储 缓存 固态存储
【vsan数据恢复】vsan分布式存储架构数据恢复案例
VSAN数据恢复环境: 一套有三台服务器节点的VSAN超融合基础架构,每台服务器节点上配置2块SSD硬盘和4块机械硬盘。 每个服务器节点上配置有两个磁盘组,每个磁盘组使用1个SSD硬盘作为缓存盘,2个机械硬盘作为容量盘。三台服务器节点上共配置6个磁盘组,共同组成VSAN存储空间,存放虚拟机文件。 需要恢复服务器节点上的数据库数据。 VSAN故障: 非正常关机导致VSAN逻辑架构出现故障,部分虚拟机磁盘组件出现问题,磁盘文件丢失。
|
5月前
|
Java 关系型数据库 数据库连接
BATJ高频面试249道题:微服务+多线程+分布式+MyBatis +Spring
本文收集整理了各大厂常见面试题N道,你想要的这里都有内容涵盖:Java、MyBatis、ZooKeeper、Dubbo、Elasticsearch、Memcached、Redis、MySQL、Spring、Spring Boot、Spring Cloud、RabbitMQ、Kafka、Linux 等技术栈,希望大家都能找到适合自己的公司,开开心心的撸代码。
|
1月前
|
缓存 NoSQL 数据库
关于高并发下缓存失效的问题(本地锁 && 分布式锁 && Redission 详解)
关于高并发下缓存失效的问题(本地锁 && 分布式锁 && Redission 详解)
152 0
|
2月前
|
存储 Java 应用服务中间件
【分布式技术专题】「架构实践于案例分析」盘点互联网应用服务中常用分布式事务(刚性事务和柔性事务)的原理和方案
【分布式技术专题】「架构实践于案例分析」盘点互联网应用服务中常用分布式事务(刚性事务和柔性事务)的原理和方案
62 0
|
2月前
|
消息中间件 缓存 负载均衡
这些年背过的面试题——分布式篇
本文是技术人面试系列分布式篇,面试中关于分布式都需要了解哪些基础?一文带你详细了解,欢迎收藏!
271 1
这些年背过的面试题——分布式篇
|
4月前
|
设计模式 Java 关系型数据库
BAT等大厂年薪30W+面试清单:JVM\MySQL\设计模式\分布式\微服务
疫情影响下招聘名额缩减不少,但阿里、腾讯、抖音、快手等互联网公司却加快了人才招聘的节奏。这里根据自身的实际经历,整理了一份面试这些大厂的清单,希望能帮助到大家查漏补缺,攻克面试难关。
|
4月前
|
Dubbo Java 应用服务中间件
Spring Boot + Dubbo + Zookpeer分布式案例
Spring Boot + Dubbo + Zookpeer分布式案例
37 0
|
4月前
|
缓存 NoSQL Java
毕业季-Java分布式开发面试题
毕业季-Java分布式开发面试题