分布式锁在WEB编程中的应用相当广泛
以接口幂等性为例,未实现接口幂等性的注册接口,是这样的:
<?php
public function register(Request $request)
{
//一些验证逻辑
$user = new User();
$user->username = $request->post('phone');
$user->password = Hash::make($request->post('password'));
$user->save();
}
这样写有什么问题?明显,如果同一时间以相同的请求数据请求该接口,可能会导致数据库有两条同样的记录。诚然,我们可以为Users
表的username
字段设置唯一索引,再catch
异常来抛出友好错误提示。但,这里我想介绍另一种方法,用分布式锁,以username
作为分布式锁key
的构建。
public function register(Request $request)
{
//一些验证逻辑
$phone = $request->post('phone');
$lock = Cache::lock('register-user-' . $phone, 10);
if ($lock->get()) {
$user = new User();
$user->username = $phone;
$user->password = Hash::make($request->post('password'));
$user->save();
$lock->release();
} else {
//返回友好的错误信息
}
}
同样的,也可以编写一个限制访问频次的中间件,以客户端ip、路由和参数生成唯一指纹。
class AccessLimit
{
public function handle($request, Closure $next)
{
$fingerprint = md5($request->ip() . $request->route()->getName() . var_export($request->all(), true));
$lock = Cache::lock($fingerprint, 5);
if ($lock->get()) {
return $next($request);
}
throw new \Exception('访问频繁');
}
}
还有一些非电商场景下的秒杀交易,比如说虚拟币,挂单交易只允许被一个用户购买。
如果不处理并发问题,就会发生超卖的情况。
<?php
public function deal(Request $request)
{
$dealId = $request->post('id');
$deal = Deal::find($dealId);
//一些验证操作
if (is_null($deal->sell_time)) { //状态判断逻辑
DB::beginTransaction();
try {
//处理业务逻辑
$deal->sell_time = date('Y-m-d H:i:s');
$deal->save();
DB::commit();
} catch (\Exception $e) {
DB::rollback();
}
return; //返回交易成功响应
}
throw new \Exception('交易失败响应');
}
这样写在高并发场景下有明显的问题,两个用户同时抢单时,状态判断逻辑同时成功,并执行到接下来的业务逻辑。怎么改进?用分布式锁。
<?php
public function deal(Request $request)
{
$dealId = $request->post('id');
$lock = Cache::lock('deal-' . $dealId, 10);
if (!$lock->get()) {
throw new \Exception('该挂单已交易完成');
}
$deal = Deal::find($dealId);
//一些验证操作
DB::beginTransaction();
try {
//处理业务逻辑
if (!is_null($deal->sell_time)) { //状态判断逻辑
throw new \Exception('该挂单已被交易');
}
$deal->sell_time = date('Y-m-d H:i:s');
$deal->save();
DB::commit();
//返回成功响应
} catch (\Exception $e) {
DB::rollback();
//返回失败响应
} finally {
$lock->release();
}
}
这样处理,即可以避免超卖,也可以避免请求频繁落到DB层,提升整体的系统吞吐量。