PHP模拟高并发异步请求测试+redis的setnx处理并发和防止死锁处理
比如说:某个查询数据库的接口,因为调用量比较大,所以加了缓存,并设定缓存过期后刷新,问题是当并发量比较大的时候,如果没有锁机制,那么缓存过期的瞬间,大量并发请求会穿透缓存直接查询数据库,造成雪崩效应,如果有锁机制,那么就可以控制只有一个请求去更新缓存,其它的请求视情况要么等待,要么使用过期的缓存。在 Redis 里,所谓 SETNX,是「SET if Not eXists」的缩写,也就是只有不存在
·
/** PHP并发异步请求测试 * /test/curlMulti */ public function curlMultiAction(){ $urls = [ "http://localhost:801/api/order/create", "http://localhost:801/api/order/create", "http://localhost:801/api/order/create", "http://localhost:801/api/order/create", "http://localhost:801/api/order/create", "http://localhost:801/api/order/create", ]; //并发测试 $url = 'http://localhost:801/api/order/create'; $data=[ 'customerId'=>'10000', 'customerOrderNo'=>'Test530051-'.uniqid(), 'goodsId'=>'1001273', 'account'=>'18857850025x', 'num'=>1, 'sign'=>'abDebug', ]; // 创建批处理cURL句柄 $multi_handle = curl_multi_init(); $curls = []; foreach ($urls as $url) { $ch = curl_init(); $data['customerOrderNo'] = uniqid('Test-'); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data)); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_multi_add_handle($multi_handle, $ch); $curls[] = $ch; } $running = null; do { curl_multi_exec($multi_handle, $running); } while ($running > 0); foreach ($curls as $ch) { $response = curl_multi_getcontent($ch); echo "Response: " . $response . PHP_EOL; curl_multi_remove_handle($multi_handle, $ch); curl_close($ch); } curl_multi_close($multi_handle); die; } 在 Redis 里,所谓 SETNX,是「SET if Not eXists」的缩写,也就是只有不存在的时候才设置,可以利用它来实现锁的效果,不过很多人没有意识到 SETNX 有陷阱! 比如说:某个查询数据库的接口,因为调用量比较大,所以加了缓存,并设定缓存过期后刷新,问题是当并发量比较大的时候,如果没有锁机制,那么缓存过期的瞬间,大量并发请求会穿透缓存直接查询数据库,造成雪崩效应,如果有锁机制,那么就可以控制只有一个请求去更新缓存,其它的请求视情况要么等待,要么使用过期的缓存。 下面以目前 PHP 社区里最流行的 PHPRedis 扩展为例,实现一段演示代码: <?php $ok = $redis->setNX($key, $value); if ($ok) { $cache->update(); $redis->del($key); } ?> 缓存过期时,通过 SetNX 获取锁,如果成功了,那么更新缓存,然后删除锁。看上去逻辑非常简单,可惜有问题:如果请求执行因为某些原因意外退出了,导致创建了锁但是没有删除锁,那么这个锁将一直存在,以至于以后缓存再也得不到更新。于是乎我们需要给锁加一个过期时间以防不测: <?php $redis->multi(); $redis->setNX($key, $value); $redis->expire($key, $ttl); $redis->exec(); ?>
<?php $ok = $redis->set($key, $random, array('nx', 'ex' => $ttl)); if ($ok) { $cache->update(); if ($redis->get($key) == $random) { $redis->del($key); } } ?>
Redis防死锁处理: $lock_key = 'locks_' . $userid; $is_lock = $redis->setnx($lock_key , 1); // 加锁 if($is_lock == true){ // 获取锁权限 // 程序逻辑处理: if ...... // 释放锁 $redis->del($lock_key); }else{ // 防止死锁 if($redis->ttl($lock_key) == -1){ $redis->expire($lock_key, 5); } return true; // 获取不到锁权限,直接返回 }
$redis->ttl设置过期时间。当 key 不存在时,返回 -2 。 当 key 存在但没有设置剩余生存时间时,返回 -1 。 否则,以秒为单位,返回 key 的剩余生存时间。
注意:在 Redis 2.8 以前,当 key 不存在,或者 key 没有设置剩余生存时间时,命令都返回 -1 。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
已为社区贡献4条内容
所有评论(0)