redis锁实现

前言

当今的站点,大部分都会使用缓存,无论是用Memcache又或Redis。

其中一种用法:去拿热数据时,我们一般会加一层缓存,并且设定过期时间。但是,这里如果不注意,容易导致一个问题的产生:当并发很大时,在缓存过期的瞬间,如果有大量的请求,那么这些请求会直接打到数据库,去拿数据库的数据,这就是所谓的"缓存雪崩"。

例如,本博客中的php代码实现Memcache缓存的实例的代码,在高并发下,就会存在这种问题。

下面,就通过一些简单的代码来演示一下。

<?php

    /*

    测试没有使用锁,高并发下会产生的问题
     */

    $redis = new Redis();
    $redis->connect('127.0.0.1', 6379);

    $value = $redis->get('a');

    //判断有无,如果有,不保存数据。
    //任何情况a都是1,才是正确的值。如果在多个客户端请求时,如果a不是1,说明出问题了
    if (!$value) {

        //进到if,查询数据库
        $id = $redis->incr('id');//模拟查询数据库
        $redis->setEx('a', 100, $id);
    }

上面的代码,当并发200时,发现a键的值是10。这就说明,并发200时,其中有10个请求同时去查询了数据库。这就好像,同时200个人去抢一个厕所,结果又10个人抢到了。那结果就很悲剧了,因为我们不能让10个人同时上一个厕所。

加锁

  • 方式一

      $result = $redis->setnx($key, $val);//1行
      if ($result == 1) {        
          $redis->expire($key, 100);//2行
      }
    

但是这种方式也是有问题的,1行与2行是两次对Redis的操作,不具备原子性。有可能在expire时,redis挂了或者expire失败了,这样的结果就是$key没有成功设置时间。

  • 方式二

      $redis->set($key, $value, Array('nx', 'ex'=>10));
    

SET命令从2.6.12就包含了设置过期时间功能。

nx表示当$key不存在时我们才进行set操作,ex是有效期。10单位是秒

正确写法(仅供参考)

<?php
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);

$value = $redis->get('aa3');
if (!$value) {

    //这是模拟数据库查询,给数据库查询上锁,保证同时只能一个查询在进行
    if (getLock($redis, 'test', uniqid(), 10)) {
        $id = $redis->incr('idid3');//这行代码模拟的是数据库查询
        $redis->setEx('aa3', 10, $id);//将数据库中查询出的数据保存到Redis的aa3键
        $redis->setTimeout('idid3', 10);
    }
}

//加锁
function getLock($redis, $lockName, $lockValue, $expire)
{  
    //px 为毫秒
    return $redis->set('lock:'.$lockName, $lockValue, array('nx', 'px' => $expire));
}

参考

  1. https://redis.io/topics/distlock
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值