The process of implementing the lock mechanism of Redis under Yii2.0
1. Set the lockout time: the current UNIX timestamp + Redis lock timeout time, in seconds (3), edit the file: \common\config\params.php, as shown in Figure 1
'lock' => [
'keyPrefix' => 'lock:', //Redis锁定 key 前缀
'timeOut' => 3, //Redis锁定超时时间,单位为秒
],
2. Get the relevant setting parameters and edit the file: \api\models\redis\gamecategory.php, as shown in Figure 2
// 设置锁定的过期时间
$time = time();
$lockKey = Yii::$app->params['lock']['keyPrefix'] . 'game_category';
$lockExpire = $time + Yii::$app->params['lock']['timeOut'];
3. Get the Redis connection to execute the relevant commands, edit the file: \API\Models\Redis\GameCategory.php, as shown in Figure 3
// 获取 Redis 连接,以执行相关命令
$redis = Yii::$app->redis;
4. Get the lock, as shown in Figure 4
// 获取锁定
$executeCommandResult = $redis->setnx($lockKey, $lockExpire);
Note: SETNX key value
Set the value to value, if the key does not exist, this case is equivalent to the set command. When the key exists, nothing is done. SETNX is the abbreviation of “Set if not exists”.
5, return 0, indicating that it has been locked by other clients, as shown in Figure 5, 6, 7
// 返回0,表示已经被其他客户端锁定
if ($executeCommandResult == 0) {
// $fileName = microtime(true);
// file_put_contents('./../runtime/0-' . $fileName . '.txt', '1');
// 防止死锁,获取当前锁的过期时间
$lockCurrentExpire = $redis->get($lockKey);
// $fileName = microtime(true);
// file_put_contents('./../runtime/6-' . $fileName . $lockCurrentExpire . '.txt', '1');
// 判断锁是否过期,如果已经过期
if ($time > $lockCurrentExpire) {
// $fileName = microtime(true);
// file_put_contents('./../runtime/1-' . $fileName . '.txt', '1');
// 释放锁定
// $redis->del($lockKey);
// 获取锁定
// $executeCommandResult = $redis->setnx($lockKey, $lockExpire);
// 防止并发锁定,检查存储在 key 的旧值是否仍然是过期的时间戳,如果是,则获取锁定,否则返回假
$executeCommandResult = $redis->getset($lockKey, $lockExpire);
if ($lockCurrentExpire != $executeCommandResult) {
// $fileName = microtime(true);
// file_put_contents('./../runtime/2-' . $fileName . '.txt', '1');
return ['status' => false, 'code' => 0, 'message' => ''];
}
// $fileName = microtime(true);
// file_put_contents('./../runtime/3-' . $fileName . '.txt', '1');
}
// 返回0,表示已经被其他客户端锁定,且不存在死锁,返回假
if ($executeCommandResult == 0) {
// $fileName = microtime(true);
// file_put_contents('./../runtime/4-' . $fileName . '.txt', '1');
return ['status' => false, 'code' => 0, 'message' => ''];
}
}
// $fileName = microtime(true);
// file_put_contents('./../runtime/5-' . $fileName . '.txt', '1');
6. Release the lock, as shown in Figure 8
Note: del key[key …]
If the deleted key does not exist, it is ignored directly.
7. For point 5, judge whether the lock has expired, if it has expired, comment out the release lock and obtain the lock, in order to prevent concurrent locking, the following test process can be done to verify
8. First comment out the release lock to simulate: what happens if the client fails, crashes or cannot release the lock? problem, as shown in Figure 9
9. In judging whether the lock has expired, if it has expired, use the first algorithm in this code segment, and uncomment all file_put_contents, as shown in Figure 10
10. In redis, execute the command: flushdb, clear all keys, as shown in Figure 11
11. Execute the concurrent request test and set the number of threads to 10, as shown in Figure 12 and 13
12. Check the files generated in the \api\runtime directory. The number of files starting with 0, 4, and 6 is all 9, and the number of files starting with 5 is 1. It is normal, as shown in Figure 14
13. In Redis, delete all business-related keys except lock:game_category, that is, the key starting with game_category, to simulate: the lock has expired, as shown in Figure 15
14. Delete the files generated in the \api\runtime directory, as shown in Figure 16
15. Execute the concurrent request test and set the number of threads to 10, as shown in Figure 17 and 18
16. Check the files generated in the \api\runtime directory. The number of files starting with 0 and 6 is 10, the number of files starting with 1 and 4 is 8, and the number of files starting with 5 is 2, as shown in Figure 19
Note 1: The number of files starting with 5 is 2, only 1, if it is greater than 1, it means that the lock is unsuccessful
Note 2: Determine whether the lock is expired, if it has expired, when this happens, you cannot just call del to release the lock, and then obtain the Take the lock, because there is a competitive relationship here, when multiple clients detect an expired lock, and then release the lock, and then obtain it, they all get the lock.
17. In judging whether the lock has expired, if it has expired, use the second algorithm in this code segment, and uncomment all file_put_contents, as shown in Figure 20
18. Repeat the 3 steps of 13, 14, 15 and other 3 steps to view the files generated in the \api\runtime directory. The number of files starting with 0 and 6 is 10, starting with 1 The number of files starting with 2 is 6, the number of files starting with 3 is 1, the number of files starting with 4 is 3, and the number of files starting with 5 is 1, as shown in Figure 21
Note 1: The number of files starting with 1 is 7, the number of files starting with 2 is 6, the number of files starting with 3 is 1, and the latter two are added together It is exactly the same as the former, which means that among the 7 threads that have expired, only one has obtained a lock, and finally the number of files starting with 5 is also 1
Note 2: Due to the properties of GetSet, you can check whether the old value stored in the key is still the expired timestamp, if so, get the lock, otherwise return false
19. Clean up all code that is convenient for testing during development, such as file_put_contents, etc., as shown in Figure 22
// 设置锁定的过期时间,获取相关锁定参数
$time = time();
$lockKey = Yii::$app->params['lock']['keyPrefix'] . 'game_category';
$lockExpire = $time + Yii::$app->params['lock']['timeOut'];
// 获取 Redis 连接,以执行相关命令
$redis = Yii::$app->redis;
// 获取锁定
$executeCommandResult = $redis->setnx($lockKey, $lockExpire);
// 返回0,表示已经被其他客户端锁定
if ($executeCommandResult == 0) {
// 防止死锁,获取当前锁的过期时间
$lockCurrentExpire = $redis->get($lockKey);
// 判断锁是否过期,如果已经过期
if ($time > $lockCurrentExpire) {
// 防止并发锁定,检查存储在 key 的旧值是否仍然是过期的时间戳,如果是,则获取锁定,否则返回假
$executeCommandResult = $redis->getset($lockKey, $lockExpire);
if ($lockCurrentExpire != $executeCommandResult) {
return ['status' => false, 'code' => 0, 'message' => ''];
}
}
// 返回0,表示已经被其他客户端锁定,且不存在死锁,返回假
if ($executeCommandResult == 0) {
return ['status' => false, 'code' => 0, 'message' => ''];
}
}
20. Abstract the acquisition lock and release lock into a class file, \common\models\redis\lock.php, as shown in Figure 23 and 24
true //状态
* ]
*
* 或者
*
* [
* 'status' => false, //状态
* 'code' => 0, //返回码
* 'message' => '', //说明
* ]
*
*/
public function lock($lockKeyName)
{
// 设置锁定的过期时间,获取相关锁定参数
$time = time();
$lockKey = Yii::$app->params['lock']['keyPrefix'] . $lockKeyName;
$lockExpire = $time + Yii::$app->params['lock']['timeOut'];
// 获取 Redis 连接,以执行相关命令
$redis = Yii::$app->redis;
// 获取锁定
$executeCommandResult = $redis->setnx($lockKey, $lockExpire);
// 返回0,表示已经被其他客户端锁定
if ($executeCommandResult == 0) {
// 防止死锁,获取当前锁的过期时间
$lockCurrentExpire = $redis->get($lockKey);
// 判断锁是否过期,如果已经过期
if ($time > $lockCurrentExpire) {
// 防止并发锁定,检查存储在 key 的旧值是否仍然是过期的时间戳,如果是,则获取锁定,否则返回假
$executeCommandResult = $redis->getset($lockKey, $lockExpire);
if ($lockCurrentExpire != $executeCommandResult) {
return ['status' => false, 'code' => 0, 'message' => ''];
}
}
// 返回0,表示已经被其他客户端锁定,且不存在死锁,返回假
if ($executeCommandResult == 0) {
return ['status' => false, 'code' => 0, 'message' => ''];
}
}
}
/**
* Redis模型的释放锁定实现
* @param string $lockKeyName 锁定键名
* 格式如下:
*
* 'game_category' //锁定键名,如比赛分类
*
* @return integer 被删除的keys的数量
* 格式如下:
*
* 1 //被删除的keys的数量
*
* 或者
*
* 0 //被删除的keys的数量
*
*/
public function unlock($lockKeyName)
{
// 获取相关锁定参数
$lockKey = Yii::$app->params['lock']['keyPrefix'] . $lockKeyName;
// 获取 Redis 连接,以执行相关命令
$redis = Yii::$app->redis;
// 释放锁定
return $redis->del($lockKey);
}
}
21. Edit the file: \api\models\redis\gamecategory.php, as shown in Figure 25, 26, 27
/* Redis模型的锁定实现 */
$lockKeyName = 'game_category';
$lock = new Lock();
$lockResult = $lock->lock($lockKeyName);
// 返回 false,表示已经被其他客户端锁定
if ($lockResult['status'] === false) {
return ['status' => false, 'code' => 0, 'message' => ''];
}


























