分布式锁之Redis实现

简介: 一.分布式锁简介    在分布式系统之前,系统中的锁还是单服务器上的锁,比如锁住一个进程中的多线程访问同一资源。如使用synchronized来实现。随着系统的发展,到后来分布式应用,有可能同一资源被多台服务器上的不同进程竞争,这种情况下,出现了今天讨论的分布式锁。

一.分布式锁简介

    在分布式系统之前,系统中的锁还是单服务器上的锁,比如锁住一个进程中的多线程访问同一资源。如使用synchronized来实现。随着系统的发展,到后来分布式应用,有可能同一资源被多台服务器上的不同进程竞争,这种情况下,出现了今天讨论的分布式锁

    目前主流的分布式锁实现方式主要有下面三种:

  • 基于数据库的索引和行锁,数据库乐观锁
  • 基于Redis的单线程原子操作:setNX
  • 基于Zookeeper的临时有序节点

    这里主要介绍Redis的实现。锁需要满足下面的条件:

  • 互斥性,只有一个客户端能占有锁,具有排他
  • 无死锁,即使发生有客户端无法解锁,也能保证后续其他客户端能加锁,应该有超时或者异常情况下,释放锁

二.Redis实现

1.使用setnx实现

    先上代码:

long now = System.currentTimeMillis();
// 使用 setNx 加锁,保证操作的原子性
boolean result = SUCCESS.equals(jedis.setnx(lockName,String.valueOf(now + expire*1000)));
// 下面的if主要是解决死锁问题
if(!result){
    String timestamp = jedis.get(lockName);
    // 如果设置过期时间失败的话,再通过value的时间戳来和当前时间戳比较,防止出现死锁
    if(timestamp!=null && Long.parseLong(timestamp)<now){
        // 通过 getSet 在发现锁过期未被释放的情况下,避免删除了在这个过程中有可能被其余的线程获取到了锁
        //锁已过期,获取上一个锁的过期时间,并设置现在锁的过期时间
        String oldValue = jedis.getSet(lockName,String.valueOf(now + expire*1000));
        if(oldValue!=null && oldValue.equals(timestamp)){
            result = true;
            jedis.expire(lockName,expire);
        }
    }
}
if(result){
    jedis.expire(lockName,expire);
}
return result;

    使用 setnx 实现加锁,其中key是锁,value是锁的过期时间。加了时间戳比较,防止出现死锁情况。
    细心的朋友可能也会发现还是存在下面的问题:

  • 时间戳同步问题。客户端自己生成过期时间,所以需要强制要求分布式下每个客户端的时间必须同步
  • 当锁过期的时候,如果多个客户端同时执行 getSet ,最终只有一个客户端可以加锁,但是这个客户端的锁的过期时间可能被其他客户端覆盖
  • 锁不具备拥有者标识,即任何客户端都可以解锁

    所以就有了第2种实现方式。

2.使用set "NX" "PX"实现

    实现如下:

public static final String SET_IF_NOT_EXIST = "NX";
public static final String SET_WITH_EXPIRE_TIME = "PX";
// "NX" 当key不存在时,我们进行set操作;若key已经存在,则不做任何操作
//"PX" 给key加一个过期时间
String result = jedis.set(lockName, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expire*1000);
if(LOCK_SUCCESS.equals(result)){
    return true;
}
return false;

3.解锁实现

    解锁的时候,主要思想就是在redis里面执行一段Lua脚本。

    为什么使用Lua脚本?

    保证操作的原子性。eval命令执行Lua代码的时候,Lua代码将被当成一个命令去执行,并且直到eval命令执行完成,Redis才会执行其他命令。另外,开源Redisson中的分布式锁就是Lua实现,我后面会有一篇文章分析Redisson分布式锁的实现。

// KEYS[1] lockName 锁名称 ARGV[1] requestId
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Object result = jedis.eval(script, Collections.singletonList(lockName), Collections.singletonList(requestId));
if (SUCCESS.equals(result)) {
    return true;
}
return false;

三.其他Redis实现

    Redisson提供了一种可重入的分布式锁的实现--RedissonLock。

    使用示例:

RLock dlock = client.getLock(lockName);
boolean result = false;
try {
    // expire 等待时间 leaseTime 超时时间
    result = dlock.tryLock(expire, 20, TimeUnit.SECONDS);
} catch (InterruptedException e) {
    e.printStackTrace();
} finally {
    if (dlock.isLocked()) {
        dlock.unlock();
    }
}
return result;

    相关的实现和示例代码放到了Github上,redis-usage.

目录
相关文章
|
7月前
|
存储 负载均衡 NoSQL
【赵渝强老师】Redis Cluster分布式集群
Redis Cluster是Redis的分布式存储解决方案,通过哈希槽(slot)实现数据分片,支持水平扩展,具备高可用性和负载均衡能力,适用于大规模数据场景。
499 2
|
7月前
|
存储 缓存 NoSQL
【📕分布式锁通关指南 12】源码剖析redisson如何利用Redis数据结构实现Semaphore和CountDownLatch
本文解析 Redisson 如何通过 Redis 实现分布式信号量(RSemaphore)与倒数闩(RCountDownLatch),利用 Lua 脚本与原子操作保障分布式环境下的同步控制,帮助开发者更好地理解其原理与应用。
452 6
|
8月前
|
存储 缓存 NoSQL
Redis核心数据结构与分布式锁实现详解
Redis 是高性能键值数据库,支持多种数据结构,如字符串、列表、集合、哈希、有序集合等,广泛用于缓存、消息队列和实时数据处理。本文详解其核心数据结构及分布式锁实现,帮助开发者提升系统性能与并发控制能力。
|
6月前
|
NoSQL Java 调度
分布式锁与分布式锁使用 Redis 和 Spring Boot 进行调度锁(不带 ShedLock)
分布式锁是分布式系统中用于同步多节点访问共享资源的机制,防止并发操作带来的冲突。本文介绍了基于Spring Boot和Redis实现分布式锁的技术方案,涵盖锁的获取与释放、Redis配置、服务调度及多实例运行等内容,通过Docker Compose搭建环境,验证了锁的有效性与互斥特性。
543 0
分布式锁与分布式锁使用 Redis 和 Spring Boot 进行调度锁(不带 ShedLock)
|
6月前
|
缓存 NoSQL 关系型数据库
Redis缓存和分布式锁
Redis 是一种高性能的键值存储系统,广泛用于缓存、消息队列和内存数据库。其典型应用包括缓解关系型数据库压力,通过缓存热点数据提高查询效率,支持高并发访问。此外,Redis 还可用于实现分布式锁,解决分布式系统中的资源竞争问题。文章还探讨了缓存的更新策略、缓存穿透与雪崩的解决方案,以及 Redlock 算法等关键技术。
|
12月前
|
数据采集 存储 数据可视化
分布式爬虫框架Scrapy-Redis实战指南
本文介绍如何使用Scrapy-Redis构建分布式爬虫系统,采集携程平台上热门城市的酒店价格与评价信息。通过代理IP、Cookie和User-Agent设置规避反爬策略,实现高效数据抓取。结合价格动态趋势分析,助力酒店业优化市场策略、提升服务质量。技术架构涵盖Scrapy-Redis核心调度、代理中间件及数据解析存储,提供完整的技术路线图与代码示例。
1398 0
分布式爬虫框架Scrapy-Redis实战指南
|
NoSQL Java 中间件
【📕分布式锁通关指南 02】基于Redis实现的分布式锁
本文介绍了从单机锁到分布式锁的演变,重点探讨了使用Redis实现分布式锁的方法。分布式锁用于控制分布式系统中多个实例对共享资源的同步访问,需满足互斥性、可重入性、锁超时防死锁和锁释放正确防误删等特性。文章通过具体示例展示了如何利用Redis的`setnx`命令实现加锁,并分析了简化版分布式锁存在的问题,如锁超时和误删。为了解决这些问题,文中提出了设置锁过期时间和在解锁前验证持有锁的线程身份的优化方案。最后指出,尽管当前设计已解决部分问题,但仍存在进一步优化的空间,将在后续章节继续探讨。
1519 131
【📕分布式锁通关指南 02】基于Redis实现的分布式锁
|
10月前
|
数据采集 存储 NoSQL
基于Scrapy-Redis的分布式景点数据爬取与热力图生成
基于Scrapy-Redis的分布式景点数据爬取与热力图生成
679 67
|
8月前
|
NoSQL Redis
Lua脚本协助Redis分布式锁实现命令的原子性
利用Lua脚本确保Redis操作的原子性是分布式锁安全性的关键所在,可以大幅减少由于网络分区、客户端故障等导致的锁无法正确释放的情况,从而在分布式系统中保证数据操作的安全性和一致性。在将这些概念应用于生产环境前,建议深入理解Redis事务与Lua脚本的工作原理以及分布式锁的可能问题和解决方案。
299 8
|
9月前
|
缓存 NoSQL 算法
高并发秒杀系统实战(Redis+Lua分布式锁防超卖与库存扣减优化)
秒杀系统面临瞬时高并发、资源竞争和数据一致性挑战。传统方案如数据库锁或应用层锁存在性能瓶颈或分布式问题,而基于Redis的分布式锁与Lua脚本原子操作成为高效解决方案。通过Redis的`SETNX`实现分布式锁,结合Lua脚本完成库存扣减,确保操作原子性并大幅提升性能(QPS从120提升至8,200)。此外,分段库存策略、多级限流及服务降级机制进一步优化系统稳定性。最佳实践包括分层防控、黄金扣减法则与容灾设计,强调根据业务特性灵活组合技术手段以应对高并发场景。
2506 7