基于 Redis SETNX 实现分布式锁手记

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: 基于 Redis SETNX 实现分布式锁手记

环境与配置

* 

Redis 任意版本即可

* 

SpringBoot 任意版本即可,但是需要依赖 spring-boot-starter-data-redis


org.springframework.boot
spring-boot-starter-data-redis

看一看 Redis 社区对 SETNX 的解释

Redis SETNX

Set key to hold string value if key does not exist. In that case, it is equal to SET. When key already holds a value, no operation is performed. SETNX is short for “SET if Not eXists”.

Return value: Integer reply, specifically:

* 

1 if the key was set

* 

0 if the key was not set

由于当某个 key 不存在的时候,SETNX 才会设置该 key。且由于 Redis 采用单进程单线程模型,所以,不需要担心并发的问题。那么,就可以利用 SETNX 的特性维护一个 key,存在的时候,即锁被某个线程持有;不存在的时候,没有线程持有锁。
关于实现的解释

由于只涉及到 Redis 的操作,所以,代码实现比较简单。只对外提供两个接口:获取锁、释放锁。

* 

IDistributedLock: 操作接口定义

* 

RedisLock: IDistributedLock 的实现类

* 

DistributedLockUtil: 分布式锁工具类

* 

SpringContextUtil: 获取当前 classpath 中的 Bean

SETNX 命令对应到 StringRedisTemplate 的 api 是 setIfAbsent,如下所示

/**

  • Set {@code key} to hold the string {@code value} if {@code key} is absent.

*

*/
Boolean setIfAbsent(K key, V value);
源码和注释信息

/**

  • 分布式锁接口

  • 只需要两个接口: 获取锁与释放锁

*/
public interface IDistributedLock {
/**

  • 获取锁

  • */

boolean acquire();
/**

  • 释放锁

  • */

void release();
}

import SpringContextUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;

/**

  • 基于 Redis 实现的分布式锁

*/
@Slf4j
public class RedisLock implements IDistributedLock {

/* redis client /
private static StringRedisTemplate redisTemplate;

private String lockKey; // 锁的键值
private int expireMsecs = 15 * 1000; // 锁超时, 防止线程得到锁之后, 不去释放锁
private int timeoutMsecs = 15 * 1000; // 锁等待, 防止线程饥饿
private boolean locked = false; // 是否已经获取锁

RedisLock(String lockKey) {
this.lockKey = lockKey;
}

RedisLock(String lockKey, int timeoutMsecs) {
this.lockKey = lockKey;
this.timeoutMsecs = timeoutMsecs;
}

RedisLock(String lockKey, int expireMsecs, int timeoutMsecs) {
this.lockKey = lockKey;
this.expireMsecs = expireMsecs;
this.timeoutMsecs = timeoutMsecs;
}

public String getLockKey() {
return this.lockKey;
}

@Override
public synchronized boolean acquire() {

int timeout = timeoutMsecs;

if (redisTemplate == null) {
redisTemplate = SpringContextUtil.getBean(StringRedisTemplate.class);
}

try {

while (timeout >= 0) {

long expires = System.currentTimeMillis() + expireMsecs + 1;
String expiresStr = String.valueOf(expires); // 锁到期时间

if (redisTemplate.opsForValue().setIfAbsent(lockKey, expiresStr)) {
locked = true;
log.info("[1] 成功获取分布式锁!");
return true;
}
String currentValueStr = redisTemplate.opsForValue().get(lockKey); // redis里的时间

// 判断是否为空, 不为空的情况下, 如果被其他线程设置了值, 则第二个条件判断是过不去的
if (currentValueStr != null && Long.parseLong(currentValueStr) < System.currentTimeMillis()) {

String oldValueStr = redisTemplate.opsForValue().getAndSet(lockKey, expiresStr);

// 获取上一个锁到期时间, 并设置现在的锁到期时间
// 只有一个线程才能获取上一个线程的设置时间
// 如果这个时候, 多个线程恰好都到了这里, 但是只有一个线程的设置值和当前值相同, 它才有权利获取锁
if (oldValueStr != null && oldValueStr.equals(currentValueStr)) {
locked = true;
log.info("[2] 成功获取分布式锁!");
return true;
}
}

timeout -= 100;
Thread.sleep(100);
}
} catch (Exception e) {
log.error("获取锁出现异常, 必须释放: {}", e.getMessage());
}

return false;
}

@Override
public synchronized void release() {

if (redisTemplate == null) {
redisTemplate = SpringContextUtil.getBean(StringRedisTemplate.class);
}

try {
if (locked) {

String currentValueStr = redisTemplate.opsForValue().get(lockKey); // redis里的时间

// 校验是否超过有效期, 如果不在有效期内, 那说明当前锁已经失效, 不能进行删除锁操作
if (currentValueStr != null && Long.parseLong(currentValueStr) > System.currentTimeMillis()) {
redisTemplate.delete(lockKey);
locked = false;
log.info("[3] 成功释放分布式锁!");
}
}
} catch (Exception e) {
log.error("释放锁出现异常, 必须释放: {}", e.getMessage());
}
}
}

/**

  • 分布式锁工具类

*/
public class DistributedLockUtil {

/**

  • 获取分布式锁
  • 默认获取锁15s超时, 锁过期时间15s

*/
public static IDistributedLock getDistributedLock(String lockKey) {
lockKey = assembleKey(lockKey);
return new RedisLock(lockKey);
}

/**

  • 获取分布式锁

*/
public static IDistributedLock getDistributedLock(String lockKey, int timeoutMsecs) {
lockKey = assembleKey(lockKey);
return new RedisLock(lockKey, timeoutMsecs);
}

/**

  • 获取分布式锁

*/
public static IDistributedLock getDistributedLock(String lockKey, int timeoutMsecs, int expireMsecs) {
lockKey = assembleKey(lockKey);
return new RedisLock(lockKey, expireMsecs, timeoutMsecs);
}

/**

  • 对 key 进行拼接

*/
private static String assembleKey(String lockKey) {
return String.format("imooc_analyze_%s", lockKey);
}
}

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

/**

  • 获取当前 classpath 中的 Bean

*/
@Component
public class SpringContextUtil implements ApplicationContextAware {

private static ApplicationContext applicationContext;

@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SpringContextUtil.applicationContext = applicationContext;
}

public static ApplicationContext getApplicationContext() {
return applicationContext;
}

@SuppressWarnings("unchecked")
public static T getBean(Class c) throws BeansException {
return (T) applicationContext.getBean(c);
}
}

目录
相关文章
|
1月前
|
存储 负载均衡 NoSQL
【赵渝强老师】Redis Cluster分布式集群
Redis Cluster是Redis的分布式存储解决方案,通过哈希槽(slot)实现数据分片,支持水平扩展,具备高可用性和负载均衡能力,适用于大规模数据场景。
167 2
|
2月前
|
存储 缓存 NoSQL
Redis核心数据结构与分布式锁实现详解
Redis 是高性能键值数据库,支持多种数据结构,如字符串、列表、集合、哈希、有序集合等,广泛用于缓存、消息队列和实时数据处理。本文详解其核心数据结构及分布式锁实现,帮助开发者提升系统性能与并发控制能力。
|
5天前
|
缓存 NoSQL 关系型数据库
Redis缓存和分布式锁
Redis 是一种高性能的键值存储系统,广泛用于缓存、消息队列和内存数据库。其典型应用包括缓解关系型数据库压力,通过缓存热点数据提高查询效率,支持高并发访问。此外,Redis 还可用于实现分布式锁,解决分布式系统中的资源竞争问题。文章还探讨了缓存的更新策略、缓存穿透与雪崩的解决方案,以及 Redlock 算法等关键技术。
|
2月前
|
NoSQL Redis
Lua脚本协助Redis分布式锁实现命令的原子性
利用Lua脚本确保Redis操作的原子性是分布式锁安全性的关键所在,可以大幅减少由于网络分区、客户端故障等导致的锁无法正确释放的情况,从而在分布式系统中保证数据操作的安全性和一致性。在将这些概念应用于生产环境前,建议深入理解Redis事务与Lua脚本的工作原理以及分布式锁的可能问题和解决方案。
120 8
|
3月前
|
缓存 NoSQL 算法
高并发秒杀系统实战(Redis+Lua分布式锁防超卖与库存扣减优化)
秒杀系统面临瞬时高并发、资源竞争和数据一致性挑战。传统方案如数据库锁或应用层锁存在性能瓶颈或分布式问题,而基于Redis的分布式锁与Lua脚本原子操作成为高效解决方案。通过Redis的`SETNX`实现分布式锁,结合Lua脚本完成库存扣减,确保操作原子性并大幅提升性能(QPS从120提升至8,200)。此外,分段库存策略、多级限流及服务降级机制进一步优化系统稳定性。最佳实践包括分层防控、黄金扣减法则与容灾设计,强调根据业务特性灵活组合技术手段以应对高并发场景。
976 7
|
4月前
|
NoSQL 算法 安全
redis分布式锁在高并发场景下的方案设计与性能提升
本文探讨了Redis分布式锁在主从架构下失效的问题及其解决方案。首先通过CAP理论分析,Redis遵循AP原则,导致锁可能失效。针对此问题,提出两种解决方案:Zookeeper分布式锁(追求CP一致性)和Redlock算法(基于多个Redis实例提升可靠性)。文章还讨论了可能遇到的“坑”,如加从节点引发超卖问题、建议Redis节点数为奇数以及持久化策略对锁的影响。最后,从性能优化角度出发,介绍了减少锁粒度和分段锁的策略,并结合实际场景(如下单重复提交、支付与取消订单冲突)展示了分布式锁的应用方法。
336 3
|
NoSQL Redis 数据库
用redis实现分布式锁时容易踩的5个坑
云栖号资讯:【点击查看更多行业资讯】在这里您可以找到不同行业的第一手的上云资讯,还在等什么,快来! 近有不少小伙伴投入短视频赛道,也出现不少第三方数据商,为大家提供抖音爬虫数据。 小伙伴们有没有好奇过,这些数据是如何获取的,普通技术小白能否也拥有自己的抖音爬虫呢? 本文会全面解密抖音爬虫的幕后原理,不需要任何编程知识,还请耐心阅读。
用redis实现分布式锁时容易踩的5个坑
|
NoSQL Java 关系型数据库
浅谈Redis实现分布式锁
浅谈Redis实现分布式锁
|
存储 canal 缓存
|
NoSQL PHP Redis
redis实现分布式锁
redis实现分布式锁
277 0
redis实现分布式锁