分布式锁的背景

用途:分布式环境下保证数据一致性,常见场景秒杀抢单。

常见的锁方案如下:

  • 基于数据库实现分布式锁
  • 基于缓存,实现分布式锁,如redis
  • 基于Zookeeper实现分布式锁

实现方式(三种)

mysql实现

基于MySQL的行锁(悲观锁)实现,查询语句加上for update

1
select * from order_table where id = 'xxx' for update

优点:理解起来简单,不需要维护额外的第三方中间件(比如Redis,Zk)。
缺点:虽然容易理解但是实现起来较为繁琐,需要自己考虑锁超时,加事务等等。性能局限于数据库,一般对比缓存来说性能较低。对于高并发的场景并不是很适合。

redis实现分布式锁

基于setnx实现

1
2
3
4
5
6
7
8
9
$lock = $redis->setnx($redis_key . $this->id, time());
if (!$lock) {
// 其他请求在占用锁
return false;
} else {
// 加超时
$redis->expire(redis_key . $this->id, 1);
}
// 做后面的处理

这个实现存在两个步骤,非原子性,很是问题。

基于nx和ex实现,对于单点redis是最完美的实现方式了,但是无法避免redis机器挂掉特殊情况。

1
2
3
4
5
6
7
8
9
10
11
$lock = $this->redis_cli->set($key, "value", ['nx', 'ex' => 100]);
// 对应的redis命令:set key value ex 100 nx

$script = '
if redis.call("get",KEYS[1]) == ARGV[1]
then
return redis.call("del",KEYS[1])
else
return 0
end';
return $redis->eval($script, $key, $value);

解决单点问题可使用redis的redlock锁,可详看这两篇(多看几遍):
http://zhangtielei.com/posts/blog-redlock-reasoning.html
http://zhangtielei.com/posts/blog-redlock-reasoning-part2.html

优点:对于Redis实现简单,性能对比ZK和Mysql较好。如果不需要特别复杂的要求,那么自己就可以利用setNx进行实现,如果自己需要复杂的需求的话那么可以利用或者借鉴Redission。对于一些要求比较严格的场景来说的话可以使用RedLock。
缺点:需要维护Redis集群,如果要实现RedLock那么需要维护更多的集群。

更新日志

20191225更新,圣诞节快乐,@all