互斥锁常见使用场景: 缓存穿透
看下面这两个用户其实都请求了数据库,假设用户量大,会导致大量请求打到数据库导致宕机
用户A 查询缓存 没有缓存 请求数据库 添加缓存并设置过期时间
用户B 查询缓存 没有缓存 请求数据库 添加缓存并设置过期时间
我的这个demo是苹果授权的服务层代码 , 这里获取的数据是可以重复使用的,但是所有用户都请求会浪费服务器资源,所以添加一个互斥锁减少服务器的消耗
<?php
/**
* 苹果验证类
* Date: 2019/9/11
*/
namespace app\api\controller\v1;
use Firebase\JWT\JWK;
use Firebase\JWT\JWT;
use think\facade\Cache;
class AppleInterface
{
/**
* @Description:redis加锁
* @param $key
* @param $random
* @return void
* @author: Msy
* @Created-Time: 2022/7/11 15:21
*/
public function lock($key ,$random)
{
$redis = Cache::store('redis')->handler();
// return Cache::store('redis')->set($key ,$random,["NX","EX"=>60]); // tp不支持,只能原生写, 原生key有问题,有配置项前缀的
$key = config("cache.redis.prefix").$key;
return $redis->set($key ,$random,["NX","EX"=>60]);
}
/**
* @Description:释放redis锁
* @param $key
* @return void
* @author: Msy
* @Created-Time: 2022/7/11 15:21
*/
public function unlock($key,$random)
{
$lock_value=Cache::store("redis")->get($key);
if ($lock_value == $random){
Cache::store("redis")->rm($key);
}
}
/**
* @Description:
* @return void
* @author: Msy
* @Created-Time: 2022/7/11 15:20
*/
public function getJWKSInCache()
{
$key = ':apple:JWKS';
$ret = Cache::store("redis")->get($key);
if (!$ret) {
$lockKey = $key . '_lock';
$random = mt_rand();
//拿到互斥锁
if ($this->lock($lockKey, $random)) {
$value = file_get_contents('https://appleid.apple.com/auth/keys');
Cache::store("redis")->set($key, $value, 86400);
//释放锁
$this->unlock($lockKey, $random);
return $value;
} else {
//等待200毫秒,然后重新获取缓存值,让其他获取到锁的进程取得数据并设置缓存
usleep(200);
getJWKSInCache();
}
} else {
return $ret;
}
}
/**
* 验证token是否正常
* 验证准确性:通过Apple公钥在线(https://8gwifi.org/jwkconvertfunctions.jsp)得到用于解密的pem公钥字符串
* @param string $identityToken 前端获取的token
* @return bool|object
* @throws \Firebase\JWT\InvalidArgumentException
*/
public function apple_jwt_verify($identityToken = '' ,$i=0)
{
if($i>1) return false;
// 调用苹果接口获取JWKS
// $apiResponse = file_get_contents('https://appleid.apple.com/auth/keys');
$apiResponse = $this->getJWKSInCache();
$jwkSet = json_decode($apiResponse, true);
if (!is_array($jwkSet)) {
exception("Get JWK failed");
}
// 将JWKS转换为公钥
try {
$pubKeys = JWk::parseKeySet($jwkSet);
} catch (\Exception $e) {
Cache::store("redis")->rm(':apple:JWKS');
$i++;
$this->apple_jwt_verify($identityToken,$i);
exception("Parse key set failed:" . $e->getMessage());
}
// 使用公钥对JWT进行检验
try {
$payload = JWT::decode($identityToken, $pubKeys, ['RS256']);
} catch (\Exception $e) {
exception("Decode JWT failed:" . $e->getMessage());
}
// 若检验通过,会得到JWT里的payload信息
return json_decode((json_encode($payload)), true);
}
}