java
主页 > 软件编程 > java >

redis深入浅出分布式锁实现下篇

2022-08-30 | 佚名 | 点击:

优化之UUID防误删

问题:删除操作缺乏原子性。

场景:

index1执行删除时,查询到的lock值确实和uuid相等

uuid=v1

set(lock,uuid);

index1执行删除前,lock刚好过期时间已到,被redis自动释放,在redis中没有了lock,没有了锁。

index2获取了lock

index2线程获取到了cpu的资源,开始执行方法

uuid=v2

set(lock,uuid);

index1执行删除,此时会把index2的lock删除

index1 因为已经在方法中了,所以不需要重新上锁。index1有执行的权限。index1已经比较完成了,这个时候,开始执行

删除的index2的锁!

优化之LUA脚本保证删除的原子性

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

@GetMapping("testLockLua")

public void testLockLua() {

    //1 声明一个uuid ,将做为一个value 放入我们的key所对应的值中

    String uuid = UUID.randomUUID().toString();

    //2 定义一个锁:lua 脚本可以使用同一把锁,来实现删除!

    String skuId = "25"; // 访问skuId 为25号的商品 100008348542

    String locKey = "lock:" + skuId; // 锁住的是每个商品的数据

   // 3 获取锁

    Boolean lock = redisTemplate.opsForValue().setIfAbsent(locKey, uuid, 3, TimeUnit.SECONDS);

    // 第一种: lock 与过期时间中间不写任何的代码。

    // redisTemplate.expire("lock",10, TimeUnit.SECONDS);//设置过期时间

    // 如果true

    if (lock) {

        // 执行的业务逻辑开始

        // 获取缓存中的num 数据

        Object value = redisTemplate.opsForValue().get("num");

        // 如果是空直接返回

        if (StringUtils.isEmpty(value)) {

            return;

        }

        // 不是空 如果说在这出现了异常! 那么delete 就删除失败! 也就是说锁永远存在!

        int num = Integer.parseInt(value + "");

        // 使num 每次+1 放入缓存

        redisTemplate.opsForValue().set("num", String.valueOf(++num));

        /*使用lua脚本来锁*/

        // 定义lua 脚本

        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

        // 使用redis执行lua执行

        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();

        redisScript.setScriptText(script);

        // 设置一下返回值类型 为Long

        // 因为删除判断的时候,返回的0,给其封装为数据类型。如果不封装那么默认返回String 类型,

        // 那么返回字符串与0 会有发生错误。

        redisScript.setResultType(Long.class);

        // 第一个要是script 脚本 ,第二个需要判断的key,第三个就是key所对应的值。

        redisTemplate.execute(redisScript, Arrays.asList(locKey), uuid);

    } else {

        // 其他线程等待

        try {

            // 睡眠

            Thread.sleep(1000);

            // 睡醒了之后,调用方法。

            testLockLua();

        } catch (InterruptedException e) {

            e.printStackTrace();

        }

    }

}

Lua 脚本详解:

项目中正确使用

定义key,key应该是为每个sku定义的,也就是每个sku有一把锁。

1

2

String locKey ="lock:"+skuId; // 锁住的是每个商品的数据

Boolean lock = redisTemplate.opsForValue().setIfAbsent(locKey, uuid,3,TimeUnit.SECONDS);

总结

加锁

使用lua释放锁

重试

为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件:

- 互斥性。在任意时刻,只有一个客户端能持有锁。

- 不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。

- 解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。

- 加锁和解锁必须具有原子性

原文链接:https://blog.csdn.net/m0_62436868/article/details/126547871
相关文章
最新更新