/** 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 。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐