怎么理解Redis中的分布式锁

71次阅读
没有评论

共计 6093 个字符,预计需要花费 16 分钟才能阅读完成。

本篇内容介绍了“怎么理解 Redis 中的分布式锁”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让丸趣 TV 小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

Redis 分布式锁

大家项目中都会使用到分布式锁把,通常用来做数据的有序操作场景,比如一笔订单退款(如果可以退多次的情况)。或者用户多端下单。【相关推荐:Redis 视频教程】

Maven 依赖

我主要是基于 Spring-Boot 2.1.2 + Jedis 进行实现

?xml version= 1.0  encoding= UTF-8 ? 
 project xmlns= http://maven.apache.org/POM/4.0.0 
 xmlns:xsi= http://www.w3.org/2001/XMLSchema-instance 
 xsi:schemaLocation= http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd 
  modelVersion 4.0.0 /modelVersion 
  parent 
  groupId org.springframework.boot /groupId 
  artifactId spring-boot-starter-parent /artifactId 
  version 2.1.2.RELEASE /version 
  /parent 
  groupId cn.edu.cqvie /groupId 
  artifactId redis-lock /artifactId 
  version 1.0-SNAPSHOT /version 
  properties 
  project.build.sourceEncoding UTF-8 /project.build.sourceEncoding 
  java.version 1.8 /java.version 
  redis.version 2.9.0 /redis.version 
  spring-test.version 5.0.7 /spring-test.version 
  /properties 
  dependencies 
  dependency 
  groupId org.springframework.boot /groupId 
  artifactId spring-boot-autoconfigure /artifactId 
  /dependency 
  dependency 
  groupId org.springframework.data /groupId 
  artifactId spring-data-redis /artifactId 
  /dependency 
  dependency 
  groupId redis.clients /groupId 
  artifactId jedis /artifactId 
  version ${redis.version} /version 
  /dependency 
  dependency 
  groupId org.springframework.boot /groupId 
  artifactId spring-boot-starter-logging /artifactId 
  /dependency 
  dependency 
  groupId org.slf4j /groupId 
  artifactId log4j-over-slf4j /artifactId 
  /dependency 
  dependency 
  groupId org.springframework.boot /groupId 
  artifactId spring-boot-starter-test /artifactId 
  scope test /scope 
  /dependency 
  /dependencies 
  build 
  plugins 
  plugin 
  groupId org.springframework.boot /groupId 
  artifactId spring-boot-maven-plugin /artifactId 
  /plugin 
  /plugins 
  /build 
 /project

配置文件

application.properties 配置文件内容如下:

spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=
spring.redis.timeout=30000
spring.redis.jedis.pool.max-active=8
spring.redis.jedis.pool.min-idle=2
spring.redis.jedis.pool.max-idle=4

logging.level.root=INFO

接口定义

接口定义,对于锁我们核心其实就连个方法 lock 和 unlock.

public interface RedisLock {
 long TIMEOUT_MILLIS = 30000;
 int RETRY_MILLIS = 30000;
 long SLEEP_MILLIS = 10;
 boolean tryLock(String key);
 boolean lock(String key);
 boolean lock(String key, long expire);
 boolean lock(String key, long expire, long retryTimes);
 boolean unlock(String key);
}

分布式锁实现

我的实现方式是通过 setnx 方式实现了,如果存在 tryLock 逻辑的话,会通过 自旋 的方式重试

// AbstractRedisLock.java  抽象类
public abstract class AbstractRedisLock implements RedisLock {
 @Override
 public boolean lock(String key) { return lock(key, TIMEOUT_MILLIS);
 }
 @Override
 public boolean lock(String key, long expire) { return lock(key, TIMEOUT_MILLIS, RETRY_MILLIS);
 }
//  具体实现
@Component
public class RedisLockImpl extends AbstractRedisLock { private Logger logger = LoggerFactory.getLogger(getClass());
 @Autowired
 private RedisTemplate String, String  redisTemplate;
 private ThreadLocal String  threadLocal = new ThreadLocal String 
 private static final String UNLOCK_LUA;
 static { StringBuilder sb = new StringBuilder();
 sb.append(if redis.call(\ get\ ,KEYS[1]) == ARGV[1]  
 sb.append( then  
 sb.append( return redis.call(\ del\ ,KEYS[1])  
 sb.append( else  
 sb.append(  return 0  
 sb.append( end  
 UNLOCK_LUA = sb.toString();
 }
 @Override
 public boolean tryLock(String key) { return tryLock(key, TIMEOUT_MILLIS);
 }
 public boolean tryLock(String key, long expire) {
 try { return !StringUtils.isEmpty(redisTemplate.execute((RedisCallback String) connection -  { JedisCommands commands = (JedisCommands) connection.getNativeConnection();
 String uuid = UUID.randomUUID().toString();
 threadLocal.set(uuid);
 return commands.set(key, uuid,  NX ,  PX , expire);
 }));
 } catch (Throwable e) { logger.error( set redis occurred an exception , e);
 }
 return false;
 }
 @Override
 public boolean lock(String key, long expire, long retryTimes) { boolean result = tryLock(key, expire);
 while (!result   retryTimes--   0) {
 try { logger.debug( lock failed, retrying...{} , retryTimes);
 Thread.sleep(SLEEP_MILLIS);
 } catch (InterruptedException e) {
 return false;
 }
 result = tryLock(key, expire);
 }
 return result;
 }
 @Override
 public boolean unlock(String key) {
 try { List String  keys = Collections.singletonList(key);
 List String  args = Collections.singletonList(threadLocal.get());
 Long result = redisTemplate.execute((RedisCallback Long) connection -  { Object nativeConnection = connection.getNativeConnection();
 if (nativeConnection instanceof JedisCluster) { return (Long) ((JedisCluster) nativeConnection).eval(UNLOCK_LUA, keys, args);
 }
 if (nativeConnection instanceof Jedis) { return (Long) ((Jedis) nativeConnection).eval(UNLOCK_LUA, keys, args);
 }
 return 0L;
 });
 return result != null   result   0;
 } catch (Throwable e) { logger.error( unlock occurred an exception , e);
 }
 return false;
 }
}

测试代码

最后再来看看如何使用吧. (下面是一个模拟秒杀的场景)

@RunWith(SpringRunner.class)
@SpringBootTest
public class RedisLockImplTest { private Logger logger = LoggerFactory.getLogger(getClass());
 @Autowired
 private RedisLock redisLock;
 @Autowired
 private StringRedisTemplate redisTemplate;
 private ExecutorService executors = Executors.newScheduledThreadPool(8);
 @Test
 public void lock() {
 //  初始化库存
 redisTemplate.opsForValue().set( goods-seckill ,  10 
 List Future  futureList = new ArrayList ();
 for (int i = 0; i   100; i++) { futureList.add(executors.submit(this::seckill));
 try { Thread.sleep(100);
 } catch (InterruptedException e) { e.printStackTrace();
 }
 }
 //  等待结果,防止主线程退出
 futureList.forEach(action -  {
 try { action.get();
 } catch (InterruptedException | ExecutionException e) { e.printStackTrace();
 }
 });
 }
 public int seckill() {
 String key =  goods 
 try { redisLock.lock(key);
 int num = Integer.valueOf(Objects.requireNonNull(redisTemplate.opsForValue().get(goods-seckill)));
 if (num   0) { redisTemplate.opsForValue().set(goods-seckill , String.valueOf(--num));
 logger.info(秒杀成功,剩余库存:{} , num);
 } else { logger.error( 秒杀失败,剩余库存:{} , num);
 }
 return num;
 } catch (Throwable e) { logger.error( seckill exception , e);
 } finally { redisLock.unlock(key);
 }
 return 0;
 }
}

总结

本文是 Redis 锁的一种简单的实现方式,基于 jedis 实现了锁的重试操作。
但是缺点还是有的,不支持锁的自动续期,锁的重入,以及公平性(目前通过自旋的方式实现,相当于是非公平的方式)。

“怎么理解 Redis 中的分布式锁”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注丸趣 TV 网站,丸趣 TV 小编将为大家输出更多高质量的实用文章!

正文完
 
丸趣
版权声明:本站原创文章,由 丸趣 2023-07-27发表,共计6093字。
转载说明:除特殊说明外本站除技术相关以外文章皆由网络搜集发布,转载请注明出处。
评论(没有评论)