zl程序教程

您现在的位置是:首页 >  其他

当前栏目

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

2023-02-26 12:28:08 时间

环境与配置

* 

Redis 任意版本即可

(福利推荐:阿里云、腾讯云、华为云服务器最新限时优惠活动,云服务器1核2G仅88元/年、2核4G仅698元/3年,点击这里立即抢购>>>

* 

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 [email protected] key} to hold the string [email protected] value} if [email protected] key} is absent.

*

  • @param key must not be [email protected] null}.
  • @param value
  • @see Redis Documentation: SETNX

*/
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);
}
}

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


本站部分内容转载自网络,版权属于原作者所有,如有异议请联系QQ153890879修改或删除,谢谢!
转载请注明原文链接:基于 Redis SETNX 实现分布式锁手记

你还在原价购买阿里云、腾讯云、华为云、天翼云产品?那就亏大啦!现在申请成为四大品牌云厂商VIP用户,可以3折优惠价购买云服务器等云产品,并且可享四大云服务商产品终身VIP优惠价,还等什么?赶紧点击下面对应链接免费申请VIP客户吧:

1、点击这里立即申请成为腾讯云VIP客户

2、点击这里立即注册成为天翼云VIP客户

3、点击这里立即申请成为华为云VIP客户

4、点击这里立享阿里云产品终身VIP优惠价

喜欢 (0)
[[email protected]]
分享 (0)