盒子
盒子
文章目录
  1. 前言
  2. 分布式锁
    1. 需要解决问题
    2. 常见方案
    3. 基于 redis 的分布式锁实现
  3. SETNX
    1. 加锁
    2. 获取锁
  4. 继续
  5. 再继续
  6. Error
    1. 死锁

redis 的分布式锁实现

前言

详细 :

https://juejin.im/entry/5a502ac2518825732b19a595

这里只谈谈思路…

分布式锁

由于分布式系统的分布性,即多线程和多进程分布在不同的服务器中,通常的锁就没有用武之地了.

比如 Java 的 synchronized 和 lock,只能保证进程内线程间共享资源.

所以需要自己实现分布式锁…

需要解决问题

  • 互斥性
  • 安全性
  • 死锁
  • 容错

常见方案

  • 基于数据库实现分布式锁

    基于数据库表的增删

    基于数据库排它锁

  • 基于 zookeeper 实现分布式锁

  • 基于缓存实现分布式锁 如 redis

基于 redis 的分布式锁实现

需要自己实现锁!!!

想一想,实现一个锁应当如何做?

关键点是,同一个资源在同一时刻只能被一个线程访问.

如何做?

我们可以用一个标记来保存资源的状态(是否被线程占用)…

比如 jdk 的 Lock 的实现类里用到了 同步器

比如,我们可以假定有这个标记就代表资源被占用,没有这个标记,就代表资源尚未被占用.

另外,这个标记必须每个线程都能看到.(可见性)

redis 可以利用 SETNX 命令来做这个标记.

来看看 SETNX…

http://doc.redisfans.com/string/setnx.html

SETNX

SETNX key value

key 的值设为 value ,当且仅当 key 不存在。

若给定的 key 已经存在,则 SETNX 不做任何动作。

SETNX 是『SET if Not eXists』(如果不存在,则 SET)的简写。

  • 可用版本:

    = 1.0.0

  • 时间复杂度:

    O(1)

  • 返回值:

    设置成功,返回 1 。设置失败,返回 0

如上所说,当已存在 SETNX 设置的标记,说明已有线程获取锁.如果没有标记,则设置一个标记,代表当前线程获取到锁.

实际使用可能是这样 : 利用该命令设置一个 锁 与 锁的超时时间 的对应键值对.

SETNX lock.id lock.time

这里用 id 来区分锁.

如果设置成功,返回 1 .我们把这当作获取到锁.

如果设置失败,则说明别的线程先设置了,我们把这当作获取锁失败.

获取锁,可以用一个循环不断尝试…(在设置的超时时间内)

而解锁,就相应的删除该键值对即可.

DEL lock.id

加锁

简单的一段伪代码

1
2
3
4
5
6
7
8
9
public boolean tryLock(...){
if(SETNX key value 返回 1){
// ...
return true;
}else{
// ...
return false;
}
}

获取锁

1
2
3
4
5
6
7
8
9
10
11
12
public boolean(...){
while(true){
// 不断尝试获取锁
boolean hasLock = tryLock();
if(hasLock){
// 获取锁成功
return true;
}else{
...
}
}
}

为了避免死循环,得设置一个超时时间..

继续

上面通过 SETNX lock.id lock.time 来设置锁与超时时间.

这样,需要手动 DEL lock.id,还需要在获取到锁后进行超时判断.

也可以通过 expire key seconds 命令来设置超时时间,到时间 key 自动失效.

比如:

setnx locknx 1

expire locknx 2

2 秒后就可以重新对 locknx 重新设置值了.

不过这样的话,有可能出现这样的问题 : 执行 setnx 命令后,程序挂掉了,此时因为没有执行 expire 设置过期时间,locknx(锁) 就一直被占用!!

这是因为,这两个操作分开执行,不具备原子性!!

再继续

从 Redis 2.6.12 开始,可以使用 set 原子操作,将 setnx 与 expire 一起使用.

set key value [EX seconds][Px milliseconds] [NX|XX]

  • EX second

    设置键的过期时间为 second 秒

  • PX millisecond

    设置键的过期时间为 millisecond 毫秒

  • NX

    只在键不存在时,才对键进行设置操作

  • XX

    只在键存在时,才对键进行设置操作

  • SET 操作完成时,返回 OK,否则返回 nil

set locktarget 122 ex 10 nx

Error

死锁

分布式环境,如果一个线程获取了锁却断线了,其它试图获取锁的线程就会阻塞…