近期项目中遇到接口并发量比较高的情况,采用的是redis先缓存再进行数据同步的逻辑,但是实际运行中发现redis数据缺失或者溢出的情况,因此今天抽空代码实测下,以便最终来选择一种最可靠的数据结构(最终终于测试成功!!!)
需求:每个id次请求时成功或失败次数+1,每秒可能多个id都会发起请求
数据类型: {success:1,fail:1}
1.先用普通的key-value数据类型,以下是代码(使用thinkphp5.0框架)
public function index()
{
Redis::Instance();
$id=$this->request->param('id','1');
$plus=$this->request->param('plus','success');
$cachekey='test:'.$id;
$data=Redis::get($cachekey);
if(!$data){
$data=['success'=>0,'fail'=>0];
}else{
$data=json_decode($data,true);
}
$data[$plus]=$data[$plus]+1;
Redis::set($cachekey,json_encode($data));
}
使用apipost 100并发压测结果如下(实际项目也打不到100,按100测试也足够):
image.png
理论上redis中 success值应为667,但实际结果如下:
image.png
分析: 100并发下跟实际预期数值有偏差,且偏差量不小,实际项目的话用此方式可行性很低,因为会出现数据不准确的情况
2.再用hash类型测试,以下是代码(使用thinkphp5.0框架)
public function index2()
{
Redis::Instance();
$id=$this->request->param('id','1');
$plus=$this->request->param('plus','success');
$cachekey='test:'.$id;
$data=Redis::hget($cachekey);
if(!$data){
$data=['success'=>0,'fail'=>0];
}
$data[$plus]=$data[$plus]+1;
Redis::hsets($cachekey,$data);
}
使用apipost 100并发压测结果如下
image.png
理论上redis中 success值应为663,但实际结果如下:
image.png
分析: 跟上面的方法结果基本一样,可行性不高
3.经网络搜索到了setnx事务锁的方法,我们实际在测试下,代码如下:
public function index3()
{
Redis::Instance();
$id=$this->request->param('id','1');
$plus=$this->request->param('plus','success');
$cachekey='test:'.$id;
$lockkey='cando';
if(Redis::setnx($lockkey,1,10)){
$data=Redis::hget($cachekey);
if(!$data){
$data=['success'=>0,'fail'=>0];
}
$data[$plus]=$data[$plus]+1;
Redis::hsets($cachekey,$data);
Redis::del($lockkey);
}
}
理论上此方法可行,但是实际测试结果跟上面情况大差不差,因为有些请求触发了锁机制,数据就没有被记录下来,最终也会导致数据记录缺失的情况
4.list类型也是常用的,我们再实际测试下,代码如下
public function index4()
{
Redis::Instance();
$id=$this->request->param('id','1');
$plus=$this->request->param('plus','success');
$cachekey='test:'.$id;
Redis::lpush($cachekey,$plus);
}
apipost1000压测结果如下(上强度了,请求失败率请忽略):#####
image.png
理论上redis中 success值应为1176,实际结果如下:
image.png
分析: list类型可用性较高,再后续几次测试中都做到了 0 误差,异步同步数据时将数量统计方法修改下即可