前言

详细 :

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

加锁

简单的一段伪代码

public boolean tryLock(...){
  if(SETNX key value 返回 1){
  	// ...
  	return true;
	}else{
  	// ...
  	return false;
	}
}

获取锁

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

死锁

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