redis 第5章 常见使用场景下PHP实现 redis 第5章 常见使用场景下PHP实现

2021-06-10

一、基于redis字符串string类型的简单缓存实现

$redis = new \Redis();
$redis->connect("127.0.0.1", 6379);
$redis->set("cache-key", json_encode([
    "data-list" => "这是个缓存数据~",
    "data-list-en" => "This a data of cache~",
]), JSON_UNESCAPED_UNICODE);
echo "字符串缓存成功~ \n\n";
//获取缓存数据
$data = $redis->get("cache-key");
echo "读取缓存数据为: \n";
print_r(json_decode($data,true));

二、基于redis列表list类型的简单队列实现

$redis = new \Redis();
$redis->connect("127.0.0.1", 6379);
// 进队列
$userId = mt_rand(000000, 999999);
$redis->rpush("QUEUE_NAME",json_encode(["user_id" => $userId]));
$userId = mt_rand(000000, 999999);
$redis->rpush("QUEUE_NAME",json_encode(["user_id" => $userId]));
$userId = mt_rand(000000, 999999);
$redis->rpush("QUEUE_NAME",json_encode(["user_id" => $userId]));
echo "数据进队列成功 \n";
// 查看队列
$res = $redis->lrange("QUEUE_NAME", 0, 1000);
echo "当前队列数据为: \n";
print_r($res);
echo "----------------------------- \n";
// 出队列
$redis->lpop("QUEUE_NAME");
echo "数据出队列成功 \n";
// 查看队列
$res = $redis->lrange("QUEUE_NAME", 0, 1000);
echo "当前队列数据为: \n";
print_r($res);

三、基于redis字符串setnx的悲观锁实现

解释:悲观锁(Pessimistic Lock), 顾名思义,就是很悲观。

每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁。

场景:如果项目中使用了缓存且对缓存设置了超时时间。

当并发量比较大的时候,如果没有锁机制,那么缓存过期的瞬间,

大量并发请求会穿透缓存直接查询数据库,造成雪崩效应。

$timeout = 5000;
$redis = new \Redis();
$redis->connect("127.0.0.1", 6379);
do {
    $microtime = microtime(true) * 1000;
    $microtimeout = $microtime+$timeout+1;
    // 上锁
    $isLock = $redis->setnx("lock.count", $microtimeout);
    if (!$isLock) {
        $getTime = $redis->get("lock.count");
        if ($getTime > $microtime) {
            // 睡眠 降低抢锁频率 缓解redis压力
            usleep(5000);
            // 未超时继续等待
            continue;
        }
        // 超时,抢锁,可能有几毫秒级时间差可忽略
        $previousTime = $redis->getset("lock.count", $microtimeout);
        if ((int)$previousTime < $microtime) {
            break;
        }
    }
} while (!$isLock);
$count = $redis->get("count")? : 0;
// file_put_contents("/var/log/count.log.1", ($count+1));
// 业务逻辑
echo "执行count加1操作~ \n\n";
$redis->set("count", $count+1);
// 删除锁
$redis->del("lock.count");
// 打印count值
echo $redis->get("count");

四、基于redis事务的乐观锁实现

解释:乐观锁(Optimistic Lock), 顾名思义,就是很乐观。

每次去拿数据的时候都认为别人不会修改,所以不会上锁。

watch命令会监视给定的key,当exec时候如果监视的key从调用watch后发生过变化,则整个事务会失败。

也可以调用watch多次监视多个key。这样就可以对指定的key加乐观锁了。

注意watch的key是对整个连接有效的,事务也一样。

如果连接断开,监视和事务都会被自动清除。

当然了exec,discard,unwatch命令都会清除连接中的所有监视。

$redis = new \Redis();
$redis->connect("127.0.0.1", 6379);
// 监视 count 值
$redis->watch("count");
// 开启事务
$redis->multi();
// 操作count
$time = time();
$redis->set("count", $time);
/**
 * 模拟并发下其他进程进行set count操作 请执行下面操作
 * redis-cli 执行 $redis->set("count", "is simulate"); 模拟其他终端
 */
sleep(10);
// 提交事务
$res = $redis->exec();
if ($res) {
    // 成功...
    echo "success:" . $time . "\n";
    return;
}
// 失败...
echo "fail:" . $time . "\n";

五、基于redis的发布订阅实现

①、发布

//发布
$redis = new \Redis();
$redis->connect("127.0.0.1", 6379);
$redis->publish("msg", "来自msg频道的推送");
echo "msg频道消息推送成功~ \n";
$redis->close();

②、订阅

// ini_set("default_socket_timeout", -1);
$redis = new \Redis();
$redis->pconnect("127.0.0.1", 6379);
//订阅
echo "订阅msg这个频道,等待消息推送... \n";
$redis->subscribe(["msg"], "callfun");
function callfun($redis, $channel, $msg)
{
    print_r([
        "redis"   => $redis,
        "channel" => $channel,
        "msg"     => $msg
    ]);
}

六、redis解决高并发问题,如商品秒杀

redis真的是一个很好的技术,它可以很好的在一定程度上解决网站一瞬间的并发量,例如商品抢购秒杀等活动。。。

redis之所以能解决高并发的原因是它可以直接访问内存,而以往我们用的是数据库(硬盘),提高了访问效率,解决了数据库服务器压力。

为什么redis的地位越来越高,我们为何不选择memcache,这是因为memcache只能存储字符串,而redis存储类型很丰富(例如有字符串、LIST、SET等),memcache每个值最大只能存储1M,存储资源非常有限,十分消耗内存资源,而redis可以存储1G,最重要的是memcache它不如redis安全,当服务器发生故障或者意外关机等情况时,redsi会把内存中的数据备份到硬盘中,而memcache所存储的东西全部丢失;这也说明了memcache不适合做数据库来用,可以用来做缓存。

下面用redis解决瞬间秒杀活动来说明:

下面这个程序模拟了20人一瞬间涌入这个页面进行秒杀,能够秒杀成功的只有10人,我们把先进来的用户放入redis队列中,当队列中的用户达到10时,后来用户就转到秒杀结束页面。这里用随机数来表示不同的用户。

connect("127.0.0.1",6379);
$redis_name = "miaoshala";
$num = 10;
$len = $redis->llen($redis_name);
if($len < $num){
    $uuid = mt_rand(100000,999999);
    $redis->rpush($redis_name,$uuid);
    echo "秒杀成功!";
}else{
    echo "对不起,来晚了!";
}

jmeter 模拟并发请求

https://file.lulublog.cn/images/3/2022/08/yhA5uU4AjQMM27w6MmA6MHjnMLNUnf.png

阅读 846