专栏首页烟草的香味基于redis的分布式锁

基于redis的分布式锁

概述

在之前, 我也使用redis做过分布式锁, 当时的做法是这样的:

  1. setnx: 向 redis中创建一个过期时间为1s的key, 若创建失败, 则锁获取失败
  2. expire: 获取锁成功后, 给锁增加过期时间
  3. del: 处理后释放锁

当时觉得貌似没什么问题. 是我太天真了, 今天突然想到, 恩, 有问题.

问题

1.如果在第一步之后, 程序崩了, 没有给锁设置过期时间, 导致所有后续操作都无法正常获取到锁. 怎么破?

2.在A成功上锁后, 因为IO阻塞等原因, 执行时间有点长, 锁已经过期了, 这时B过来成功上锁, A在释放锁的时候释放的就是B的锁.

3.redis突然挂了. 如果redis突然挂了, 怎么办? 当然, 可以增加redis节点, 主节点挂了, 从节点立刻补上. 但是, 主节点的数据同步到从节点也是需要时间的吧. 假设一个场景:

  1. A在主节点设置锁
  2. 主节点还没有同步数据的时候, 挂了
  3. 从节点接替成为主节点
  4. B在主节点也成功设置了锁

这个时候, 分布式锁就失效了.

解决

那么有没有办法解决上面的问题呢? 我到万能的谷歌上找了一下, 恩, 真的有.

上面的问题一个一个解决.

问题一

如何避免没有给锁设置过期时间的问题?

其实看看就知道了, 问题出在设置key和设置value分成两条命令执行, 所以导致如果在 setnx命令执行过后, 程序崩溃, expire命令没有正常执行, 将其合并为一条命令就好啦.

set key value NX PX 5000

其中NX表示存在则不设置, PX表示过期时间.

如此, 至少可以保证不会出现没有过期时间的锁了

问题二

如何避免A释放了B的锁.

如何避免释放了其他人的锁呢? 换个问题, 如何保证这个锁是你加的呢? so easy, 加锁的时候, 讲value值设置成一个只有我知道的随机数字, 释放的时候看看值是不是我的就行了.

如此在释放的时候需要两步操作:

  1. 获取redis锁的值
  2. 若值是我的, 释放锁

当然, 为了保证释放锁操作的原子性, 这两步操作最好也能合并为一步操作. 那redis如何实现值是否相同的判断呢? Lua脚本.

简单介绍一下

eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 argv1 argv2
# 看懂了吧, 哈哈
# eval 是redis内置的命令
# 第一个参数是运行的脚本逻辑
# 第二个参数表示后面有几个key
# 第五个参数开始就是附加参数, 在脚本逻辑中使用的

所以, 脚本内容如下:

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

如此, 至少可以保证不会出现A释放了B锁的情况了

问题三

如何保证在主节点挂掉的时候, 从节点接替后, 不会重复获得锁?

官网上提供了一个方法, 从多个redis实例同时获取锁. 因为我没看太明白, 之后看懂了在说吧. 过...

其实, 如果不是处理金钱这种不容出错的业务, 这种小概率事件个人觉得还是可以容忍的.


总结

最终, 在redis单机下实现的分布式锁操作如下:

# 获取分布式锁,过期时间可调
set lock_key random_value NX PX 5000
# ...do something
# 释放分布式锁
eval "if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end" 1 lock_key random_value

本文分享自微信公众号 - 烟草的香味(hujing-bc),作者:胡靖哥哥

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-03-15

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 罗马数字

    今天再看罗马数字,才突然发现其完全是一种不同寻常的计数方法。我们平常用的是进制计数法(二进制、十进制等),而罗马数字完全不同。简单列几个罗马数字:

    烟草的香味
  • Java同步和异步,阻塞和非阻塞

    同步是指: 发送方发出数据后, 等待接收方发回响应后才发下一个数据包的通讯方式. 就是在发出一个调用时, 在没有得到结果之前, 该调用就不返回, 但是一旦调用返...

    烟草的香味
  • css中的单位

    前端开发中, 做适配是少不了的, 即页面在各种尺寸的机型中显示效果一样, 这就用到了css中的各种长度单位, 做一下总结

    烟草的香味
  • redis-sentinel主从复制高可用

    本实验是在测试环境下,考虑到学生机器较弱,因此只准备一台linux服务器用作环境!!

    超蛋lhy
  • C语言第一个字符串Hello,C语言基础教程之字符串

    C 语言中,字符串实际上是使用 null 字符 '' 终止的一维字符数组。因此,一个以 null 结尾的字符串,包含了组成字符串的字符。

    猫咪爱分享
  • 《你不知道的JavaScript》:js常见值类型的坑

    常见的值类型有数组(array)、字符串(string)、数字(number)等。

    前端_AWhile
  • 10个有关String的面试问题

    下面是面试中最容易问到的有关String的问题。

    Java后端工程师
  • Python转义字符

    有时我们并不想让转义字符生效,我们只想显示字符串原来的意思,这就要用r和R来定义原始字符串。如:

    于小勇
  • R语言之系统进化树的美化

    百度百科对进化树的定义是:在生物学中,用来表示物种之间的进化关系。生物分类学家和进化论者根据各类生物间的亲缘关系的远近,把各类生物安置在有分枝的树状的图表上,简...

    一粒沙
  • function implemented in Scala - compiled java code - some closure example

    Jerry Wang

扫码关注云+社区

领取腾讯云代金券