什么是分布式锁
定义:在分布式环境下,一个共享的可见的公共资源,各个线程通过对这个公共资源的抢占,能够使得一个代码块在同一时间只能被一个机器的一个线程执行,那这个公共资源就是分布式锁,或者说这整个机制就是分布式锁。
或者从使用场景定义:分布式锁主要用于在分布式环境中保护跨进程、跨主机、跨网络的共享资源实现互斥访问,以达到保证数据的一致性
分布式锁实现方式
锁的实现方式有多种,只要能满足所有线程都能看得到这个锁标记即可。
常见的方式是使用数据库、缓存或者zookeeper来实现分布式锁,除了这些,其实一个网络中的共享可见的可读写的资源就可以用作实现锁。
锁的操作主要有两个,即lock()和unlock()。
分布式特性
- 这把锁要是一把可重入锁 (一般是指设置过期时间,避免死锁)
- 这把锁最好是一把阻塞锁(阻塞锁是指不断循环请求获取锁资源,一般根据业务需求需不需要阻塞)
- 有高可用的获取锁和释放锁的功能
- 获取锁和释放锁的性能要好
PHP+REDIS实现
- 在多进程或者分布式执行同一份代码去处理同一份业务,在未加锁的情况下,并发下会出现重复执行。
- 例如:多进程查询数据库库存去处理业务,可能多个任务全部拿到凭证,导致库存变成负数。这种情况下需要引入分布式锁,避免数据库修改未完成比其他任务获取,下面使用php+redis进行实现
<?php
class RedisLock
{
public $redis;
public $lockedNames = [];
public function __construct()
{
$this->redis = new \Redis();
$this->redis->connect('127.0.0.1', '6379');
}
public function lock($name, $timeout = 10, $expire = 15, $waitIntervalUs = 100000)
{
if ($name == null) return false;
$now = time();
$timeoutAt = $now + $timeout;
$expireAt = $now + $expire;
$redisKey = "Lock:{$name}";
while (true) {
$result = $this->redis->setnx($redisKey, $expireAt);
if ($result) {
$this->redis->expire($redisKey, $expire);
$this->lockedNames[$name] = $expireAt;
$ttl = $this->redis->ttl($redisKey);
if ($ttl < 0) {
$this->redis->set($redisKey, $expireAt);
$this->lockedNames[$name] = $expireAt;
return true;
}
return true;
}
if ($timeout <= 0 || $timeoutAt < microtime(true)) break;
usleep($waitIntervalUs);
}
return false;
}
public function isLocking($name)
{
return key_exists($name, $this->lockedNames);
}
public function unlock($name)
{
if ($this->isLocking($name)) {
if ($this->redis->del("Lock:$name")) {
unset($this->lockedNames[$name]);
return true;
}
}
return false;
}
}
分布式锁应用
这里简单使用workerman开启多进程进行使用
<?php
require 'RedisLock.php';
class PageLock
{
public static function plock($name, $timeout = 10, $exp = 15)
{
return RedisLock::onLock($name, function () {
$aa = time();
return $aa;
}, $timeout, $exp);
}
public static function run($name)
{
$time = self::plock($name);
sleep(1);
echo $time . PHP_EOL;
}
}
在上面的代码中 onLock是获取到锁执行代码然后释放锁,这里只是打印时间,在调用run里面进行睡眠,这里多进程都获取到了锁进行了时间打印
上面的代码onLock只是简单做了打印时间,咋实际业务中onLock中进行的业务会阻塞,其他进程无法获取到锁,打印的时间将不一样,能够清晰的看到使用锁的过程
但是这样做,多进程将变得没有意义,加锁只是为了多进程不执行同一个任务,这里需要引入redis incr进行区分性处理,每个进程获取到锁,将redis加1,根据序号执行下面的业务 如 四个进程全部获取到锁,得到的数字是1,2,3,4那么根据需要同步执行不续等待, 如对应数据库id1234,则能同时查询出4条不重复的数据
|