Redis分布式锁

为什么需要分布式锁

在多线程环境中,如果多个线程同时访问共享资源(例如商品库存等秒杀场景下),会发生数据竞争,可能会导致出现脏数据或者系统问题,威胁到程序的正常运行。

我的项目中为了保证共享资源被安全的访问,使用了互斥锁对资源进行了保护,即同一时刻只有一个线程访问并修改资源,其他线程需要等到该线程释放才能去访问。

我们传统的单机多线程情况下:

Synchronized锁是锁本地的,属于JVM级别的(只能解决同一个jvm下多线程间的互斥),如果多个 JVM 进程共享同一份资源的话,使用本地锁就没办法实现资源的互斥访问了。因此我们引进了分布式锁。

分布式锁的定义

满足分布式系统下多进程可见并且互斥的锁。多个服务器(tomcat)之间可见的锁。

使用场景:同一个数据只能被修改一次,或者秒杀【数据的变化是需要被感知】

分布式锁的核心思想就是让大家都使用同一把锁,只要大家使用的是同一把锁,那么我们就能锁住线程,不让线程一起进行,让程序串行执行。

分布式锁底层主要用了redis的setnx命令:由于Redis是单线程的,用了setnx命令之后,只有一个客户端对某一个key设置值,在没有过期或删除key的时候其他客户端是不能设置这个key的。

setnx

Java代码中为setIfAbsent。这个命令在Redis中用于仅在指定的键不存在的时候设置键的值。如果键已存在,则不会执行任何操作,并且返回0表示设置失败。如果键不存在,则会设置新的值,并返回1表示设置成功。,

①防止误删其他锁,需要加个判断的逻辑,同时为了保证原子性

为了防止误删到其他的锁,这里我们建议使用 Lua 脚本通过 key 对应的 value(唯一值)来判断。

选用 Lua 脚本是为了保证解锁操作的原子性。因为 Redis 在执行 Lua 脚本时,可以以原子性的方式执行,从而保证了锁释放操作的原子性。

②为了防止在删除锁的时候,服务器突然宕机,导致锁一直不存在,我们需要加个过期时间

具体怎么实现的?

我们通过UUID来区分不同JVM发来的请求,这样将来在删除锁之前进行判断的时候就不会误删

private static final String ID_PREFIX = UUID.randomUUID().toString(true) + "-";
@Override
public boolean tryLock(long timeoutSec) {
   // 获取线程标示
   String threadId = ID_PREFIX + Thread.currentThread().getId();
   // 获取锁
   Boolean success = stringRedisTemplate.opsForValue()
                .setIfAbsent(KEY_PREFIX + name, threadId, timeoutSec, TimeUnit.SECONDS);
   return Boolean.TRUE.equals(success);
}

删除锁

public void unlock() {
    // 获取线程标示
    String threadId = ID_PREFIX + Thread.currentThread().getId();
    // 获取锁中的标示
    String id = stringRedisTemplate.opsForValue().get(KEY_PREFIX + name);
    // 判断标示是否一致
    if(threadId.equals(id)) {
        // 释放锁
        stringRedisTemplate.delete(KEY_PREFIX + name);
    }
}

setnx底层实现过程

Redis使用一个哈希表来存储键值对,当执行setnx命令时,Redis会去哈希表中查找指定的键。如果键存在,则返回0,如果键不存在则Redis会创建一个新的键值对,并将值与键关联起来。


通过添加过期时间做为兜底方案还不够保险,如果操作共享资源的时间大于过期时间,就会出现锁提前过期的问题,进而导致分布式锁直接失效。但如果锁的超时时间设置过长,又会影响到性能。


引出了Redisson框架

分布式锁中如何合理控制锁的有效时长

的确,这个时间是通过setnx直接指定的。不能过长也不能过短。

因此我们当时采用的是redis的一个框架redisson实现的 给锁续期(看门狗机制)

尽可能保证锁是业务完成之后自己释放的,而不是因为过期而释放的。

当锁住的一个业务还没有执行完成的时候,在redisson中引入一个看门狗机制。每隔一段时间就检查当前业务是否还持有锁,如果持有则增加当前业务持有锁的时间,当业务执行完成之后则释放锁就可以了。

同时,在高并发条件下,一个业务有可能会执行很快,客户1持有锁的时候,客户2来了也不会马上拒绝,它会不断尝试获取锁,如果客户1释放之后,客户2也能马上持有锁,性能也能得到提升。

相关推荐

  1. redis笔记】分布式

    2024-05-13 11:30:05       36 阅读
  2. Redis - 分布式、Redisson

    2024-05-13 11:30:05       34 阅读
  3. redis——分布式

    2024-05-13 11:30:05       38 阅读
  4. Redis分布式

    2024-05-13 11:30:05       44 阅读
  5. Redis分布式

    2024-05-13 11:30:05       34 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-05-13 11:30:05       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-05-13 11:30:05       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-05-13 11:30:05       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-05-13 11:30:05       18 阅读

热门阅读

  1. Vue和Vue3前端面试频率较高的面试题和答案

    2024-05-13 11:30:05       15 阅读
  2. Python中的绝对路径与相对路径详解

    2024-05-13 11:30:05       10 阅读
  3. react 逻辑 AND 运算符 (&&)

    2024-05-13 11:30:05       13 阅读
  4. SpringMVC 4.3 nacos1.4 mock2.0 junit4.13 测试Controller

    2024-05-13 11:30:05       12 阅读
  5. sql-行转列2(转置)

    2024-05-13 11:30:05       13 阅读
  6. sql-行转列(转置)

    2024-05-13 11:30:05       11 阅读
  7. 母亲节祝福html源码示例

    2024-05-13 11:30:05       10 阅读