memcache应对高并发量的优化

我们都知道memcahce是解决大并发量的很好一个工具,但是有一个问题值得注意,当突然有很多用户请求我们的某个页面,请求的参数相同,但对应的值却在memcahce中没有,此时这些请求都将会去查询数据库操作,瞬间对数据库照成了很大的压力。

下面我做了一个小例子来模拟这种情况。

原页面代码:

 connect("127.0.0.1",11211) or die("connect failed");

$key = $_GET['key'];
if($result = $memcache->get($key)) {
    $s = "get result form memcache: ".$key."/".$result."\n";
    file_put_contents("/var/www/html/log1.txt",$s,FILE_APPEND);
    echo $s;
}else {
    //模拟查询数据库
    sleep(5); //休眠5秒
    $result = mt_rand(1,100);
    $memcache -> set($key,$result,0,3600);
    $s = "get result not from memcache: ".$key."/".$result."\n";
    file_put_contents("/var/www/html/log2.txt",$s,FILE_APPEND);
    echo $s;
}
?>

此代码的意思是,我通过某个参数key来访问这个页面,如果此key在memcahce中存在,则在log1.txt中写入一条数据,如果不存在则休眠5秒(模拟查询数据库),在log2.txt中写入一条数据,同时将此key得到的结果放入memcahce中。

这里值得注意的是我在连接memcahce时候加了一句“or die(“connect failed”);”,即当连接报错时退出,因为有可能当memcahce的连接数超出了它本身限制的次数后,导致连接不上,从而会执行会执行else中的语句(而非memcahce中无值)。

测试并发

1)   使用ab命令测试

# ab -c 200 -n 30000 http://192.168.1.78/complicateTest.php?key=abcde

以上命令表示200个并发请求30000次

可以看到在log2文件中有许多数据,说明查询了很多次数据库:

2

可以使用对数据库查询加锁来处理,即在查询数据的时候,先在缓存中存一个值K,其他客户端需要查询数据库的时候要先判断K是否存在,若存在则休眠,如下:

 connect("127.0.0.1",11211) or die("connect failed");

$key = $_GET['key'];
getResult($memcache,$key);

function getResult($memcache,$key) {
    if($result = $memcache->get($key)) {
        $s = "get result form memcache: ".$key."/".$result."\n";
        file_put_contents("/var/www/html/log1.txt",$s,FILE_APPEND);
        echo $s;
    }else {
        $tmp_key = "_temp_".$key;
        if($memcache -> add($tmp_key, "temp key",0,60)) {
            sleep(5);
            $result = mt_rand(1,100);
            $memcache -> set($key,$result,0,3600);
            $s = "get result not from memcache: ".$key."/".$result."\n";
            file_put_contents("/var/www/html/log2.txt",$s,FILE_APPEND);
            echo $s;
            $memcache -> delete($tmp_key);
        }else {
            sleep(3);
            file_put_contents("/var/www/html/log3.txt","sleep...\n",FILE_APPEND);
            getResult($memcache, $key);
       
        }
    }
}
?>

此时我们再可以观察log2.txt中的日志,发现只有一条的,即只有一条是从数据库中查询的数据。

但如果sleep的线程如果太多的话,也可能造成进程阻塞,

另一种方法是,将数据设置在memcache的时间为永久性的,在数据中设置一个过期时间,每次获取数据时判断是否过期,若过期则加锁重新查询数据库。加锁失败则返回旧数据,但是这样做的前提是要确保memcache有这个数据,若没有则还是会返回空。所以事先需要先将用户可以查询到的数据都放到内存中,但遇到内存突然重启就会出现返回空数据的情况

 connect("127.0.0.1",11211) or die("connect failed");

$key = $_GET['key'];
print_r(getResult($memcache,$key));

function getResult($memcache,$key) {
    $result = array();
    if($result = $memcache->get($key)) {
        if($result["expired"] > strtotime(date("Y-m-d H:i:s"))) {
            //memcache中有数据,且未过期,返回原数据
            return $result;
        }else {
            return lockData($result,$memcache,$key);
        }
    }else {
        //memcache中无数据
        return lockData($result,$memcache,$key);
    }
}

//加锁,查询数据库
function lockData($result,$memcache,$key) {
    $tmp_key = "_temp_".$key;
    if($memcache -> add($tmp_key, "temp key",0,60)) {
        sleep(5);
        $result["data"] = mt_rand(1,100);
        $result["expired"] = strtotime(date("Y-m-d H:i:s")." +1 hour");
        $memcache -> set($key,$result,0,0);
        $memcache -> delete($tmp_key);
    }else {
        //若memcache中没有数据则会返回空,若有值则是返回旧数据
    }
    return $result;
}

?>

 

比较好的方式是使用gearmand来处理并发,gearmand可以确保相同的请求只执行一次,gearmand的介绍可以参考这里

1)  gearmand安装

#yum install libdrizzle libdrizzle-devel

#rpm -ivh http://dl.iuscommunity.org/pub/ius/stable/Redhat/6/x86_64/epel-release-6-5.noarch.rpm

#yum install gearmand

2)  安装php的gearmand扩展

首先要先确定有phpize,如果没有可以使用:

#yum install php-devel

#wget http://pecl.php.net/get/gearman-1.1.0.tgz

#tar zxf gearman-1.1.0.tgz

#yum install libgrearman-devel.x86_64

#yum install re2c

#cd gearman-1.1.0

#/usr/bin/phpize

#./configure

#make && make install

修改php.ini文件,加入

extension=gearman.so

3)  启动gearman

#/etc/init.d/gearman start

#service httpd restart

代码示例:

服务端gearmanWorker.php:

addServer("127.0.0.1", 4730);
  $worker->addFunction("querydb","querydb");
  while ($worker->work());


function querydb($job) {
    $key = $job -> workload();
    sleep(5);
    $result = mt_rand(1,100);
    $s = "get result not form memcache: ".$key."/".$result."\n";
    file_put_contents("/var/www/html/log2.txt",$s,FILE_APPEND);
    return $result;
}

?>

客户端complicateTest.php:

 connect("127.0.0.1",11211) or die("connect failed");

$client= new GearmanClient();
$client->addServer("127.0.0.1", 4730);

$key = $_GET['key'];
echo getResult($memcache,$key,$client);

function getResult($memcache,$key,$client) {
    if($result = $memcache->get($key)) {
       //从缓存中取
       $s = "get result form memcache: ".$key."/".$result."\n";
       file_put_contents("/var/www/html/log1.txt",$s,FILE_APPEND);
    }else {
        //memcache中无数据
        $result = $client -> doNormal("querydb",$key,$key);
        $memcache -> set($key, $result,0,3600);
    }
    return $result;
}

?>

首先启动服务端

#nohup /usr/bin/php gearmanWorker.php > log.txt 2>&1 &

使用ab命令测试:

ab -c 200 -n 10000 http://192.168.1.78/complicateTest3.php?key=sc842da

可以发现log2.txt中也只有一条记录,即只有一次从数据库中取,其他的都是从缓存中获取。
 

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注