redis hash结构 遍历某一个key下所有的(field,values)的方法

朋友们可以关注下我的公众号,获得最及时的更新:

或者关注我的知乎账号 : https://www.zhihu.com/people/zhangyachen


redis的hash结构中存储了如下的数据:

$input = array(
    "key" => $key,      //唯一的key值
    "qid" => $qid,      //问题id
    "value" => $startTime_$endTime,     //开始时间_结束时间
)

需求:每天凌晨跑定时脚本,跑出一个key下的所有qid,判断当前时间与value,当$endTime<time()时,即为过期qid,需要删除它。

所以我们怎么找出所有的(field,values)呢,我想到了如下的方法:

  • 通过redis的HGETALL命令。
  • 在DB中存储一份key=>qid的集合。
  • 通过redis的HSCAN命令。

通过redis的HGETALL命令

$input = array(
    "key" => $key,
);

$ret = $objRedis->HGETALL($input);
var_dump($ret);

result:

array(3) {
  ["ret"]=>
  array(1) {
    ["business_AdvancedPackageOne_2"]=>
    array(16) {
      [0]=>
      array(2) {
        ["field"]=>
        string(2) "58"
        ["value"]=>
        string(3) "1_1"
      }
      [1]=>
      array(2) {
        ["field"]=>
        string(2) "56"
        ["value"]=>
        string(5) "56_57"
      }
      [2]=>
      array(2) {
        ["field"]=>
        string(2) "57"
        ["value"]=>
        string(5) "57_58"
      }
      ...
    }
  }
  ["err_no"]=>
  int(0)
  ["err_msg"]=>
  string(2) "OK"
}

但是此方法有2个缺点:

  • 公司的redis中间层对返回数据做了64KB大小的限制,如果返回包的大小超过64KB,就会返回错误。
  • HGETALL会在大数据集下表现的很低效,复杂度为O(n),n为hash表的大小。

弃用。

在DB中存储一份key=>qid的集合

简答来说,就是在DB中也存储一份key=>qid的映射。首先先查找key对应的qid有哪些,在去redis中查找,所需要的操作只是HGET操作:

$input = array(
    "key" => $key,      //唯一的key值
    "qid" => $qid,      //问题id
)
$ret = $objRedis->HGET($input);

此方法的缺点在于:从DB中取出qid,需要循环遍历qid集合,一次次去读redis,不像HGETALL那样一次可以全部读出来。但是循环遍历操作我认为可以通过如下两个方法去解决:

  • 将循环遍历操作变为批量读取:
$input = array(
    'reqs' => array(
        $input1,
        $input2,
        ...,
    ),
);
$rpc->$cmd($input);
  • 在数据库中增加startTime和endTime两个字段,直接在数据库层判断哪些qid过期,将过期的qid取出来,直接组装成数据,调用hdel一次性全部删除(以下是我脚本中的一段代码):
/**
 * @desc 获取当前机构行家过期的增值包1的qid集合
 * @param $objBusinessDB
 * @param $businessId
 * @param $currentTime
 * @return array
 */
function getMergeQid($objBusinessDB,$businessId,$currentTime){
    //过期的qid集合
    $outDateQid = array();
    $outDateId = array();

    //查询过期的包信息
    $sql = "select * from busines_package where businessId={$businessId} and endTime<{$currentTime} and deleted=0";
    $dbRet = $objBusinessDB->query($sql);
    if($dbRet === false){
        Bd_Log::warning("select table fail.sql[".$sql."]");
        return $outDateQid;
    }

    foreach($dbRet as $value){
        $extpack = mc_pack_pack2array($value['extpack']);
        //合并qid信息
        if(isset($extpack['qid']) && !empty($extpack['qid'])){
            $outDateQid = array_merge($outDateQid,$extpack['qid']);
        }
        $outDateId[] = intval($value['id']);
    }

    $sql = sprintf("update business_package set deleted=2 where id in (%s)",implode($outDateId));
    $dbRet = $objBusinessDB->query($sql);
    if($dbRet === false){
        Bd_Log::warning("update table fail.sql[".$sql."]");
    }

    return $outDateQid;
}
    //获取过期的qid集合
    $outDateQid = getMergeQid($objBusinessDB,$businessId,$currentTime);
    //删除redis中的qid记录
    $redisRet = $objDaoRedis->HDEL($redisKey,$outDateQid);
    if($redisRet['err_no'] != 0){
        Bd_Log::warning("delete advancedPackage fail.businessId[$businessId] currentTime[$currentTime]");
    }

为了防止过期的qid在第二天重复被读取,在发现有过期的qid集合时,将对应的deleted字段设置为2。这样的话就能防止被重复读取(where deleted=0),提高效率。

采用。

通过redis的HSCAN命令

关于HSCAN命令的介绍:SCAN

基本思想是:通过redis自身的HSCAN命令,循环读取一个key下的所有qid。优点很明显:

  • 相比于HGETALL,都能读取出一个key下所有的(field,value)。但是由于是循环读取,占用redis服务器的资源和耗时都较少。
  • 不用在DB中冗余存储一份key=>qid的映射,操作也相对于简单了许多。

但是悲催的是,公司的redis服务层不支持hscan命令:

array(2) {
  ["err_no"]=>
  int(405)
  ["err_msg"]=>
  string(37) "Unknown Method: unknown method(HSCAN)"
}

放弃。

综上,采用第二种方法:在DB中存储一份key=>qid的集合。

posted @ 2017-12-13 15:53  张雅宸  阅读(4192)  评论(0编辑  收藏  举报