初学redis分页缓存方法实现

6,970 阅读5分钟

1. 直接设置缓存,如果数据量大,操作增删改,更新缓存频率高和效率低。

2. 分页设置缓存,通过页码设置缓存。

1.新增-删除所有缓存(倒叙排序,第一页插入数据,后续页列表都改变),
2.修改-更新当前页缓存,
3.删除-更新当前页以及当前页以后的页面的缓存。
<?php
class ArticleClass
{
    private $pageCount = 10;//每页显示
    /**
     * 获取列表
     * @param $page_no
     * @return array
     */
    public function getList($page_no){
        $cache = getRedis();
        $cache_key = ArticleList;
        $list = $cache->get($cache_key);
        $count = $this->getCount();    //计算总数
        $page = new Page($count,$this->pageCount);
        $show = $page->show();
        if(!$list){
            $list = M('Articles')->limit($page->firstRow . ',' . $page->listRows)->order('id DESC')->select();
            $ids = [];
            foreach($list as $value){
                $ids[] = $value['id'];
                unset($value);
            }
            $oldids = $cache->get(ArticleId);
            $cache->set($cache_key,$list,3600);//缓存1小时
            $cache->set(ArticleId,$ids);
        }
        return ['page'=>$show,'list'=>$list];
    }

    /**
     * 获取页码总数
     * @return mixed
     */
    public function getCount(){
        return M('Articles')->count();
    }

    /**
     * 获取最后页码
     * @return float
     */
    public function getLastPage(){
        $count = $this->getCount();
        return ceil($count/$this->pageCount);
    }

    /**
     * 获取某一条记录信息
     * @param $id
     * @return mixed
     */
    public function getInfo($id){
        $info = M('Articles')->where('id = '.$id)->find();
        return $info;
    }

    /**
     * 保存信息
     * @param $data
     * @return mixed
     */
    public function create($data){
        return M('Articles')->add($data);
    }

    /**
     * 更新信息
     * @param $data
     * @param $id
     * @return mixed
     */
    public function updateById($data,$id){
        return M('Articles')->where('id = '.$id)->save($data);
    }

    /**
     * 删除信息
     * @param $id
     * @return mixed
     */
    public function deleteById($id){
        return M('Articles')->where('id = '.$id)->delete();
    }

    /**
     * 删除/修改 缓存
     * @param $type
     * @param null $page
     */
    public function delCache($type,$page=null){
        $cache = getRedis();
        $cache_key = ArticleList;
        if($type == 'add'){
            for($i=1;$i<=$this->getLastPage();$i++){
                $cache->rm($cache_key.$i);
            }
        }elseif($type == 'update'){
            $cache->rm($cache_key.$page);
        }elseif($type == 'delete'){
            for($i=$page;$i<=$this->getLastPage();$i++){
                $cache->rm($cache_key.$i);
            }
        }
    }
}

问题:①传页码,可能人为修改。②修改一条数据,改一批数据。

3. 一条数据一个缓存

一开始的误区:把所有数据先丢在缓存中??不,是把所有数据的id先存到一个ids缓存,获取数据的时候,根据这个ids去找,缓存中找不到的在通过某个id去数据库查这个数据并保存缓存。

1.用set通过key+id存储内容信息,并存储所有数据的id到另外一个缓存ids,通过区间获取ids,通过for用get key+id获取数据。

引出的问题:
第一个访问的人,在缓存丢失了,去查找所有记录丢进缓存这个过程中,必定很慢!!
解决方法:
所有记录ids缓存先永久保存,各条记录缓存加上expire时间,每个记录缓存丢失在查找指定的数据写入缓存。
【这个说法是有问题的!!永久缓存 x,还是要设置有效时长。】
【第一个人访问慢的问题,后续通过list实现,redis列表一般不设失效时间】
public function getList(){
    $cache_ids = $this->Cache->get(ArticleId);
    $count = $this->getCount();//计算总数
    $page = new Page($count,$this->pageCount);
    $show = $page->show();
    if(!$cache_ids){//这个在后台跑一次,永久记录缓存
        $lists = M('Articles')->order('id DESC')->select();//tp3.2不能直接
查找某个字段所有数据
        $ids = [];
        foreach($lists as $value){
            $ids[] = $value['id'];
            unset($value);
        };
        unset($lists);
        $this->Cache->set(ArticleId,$ids);//记录所有id到一个缓存
        //当前需要获取的数据
        $list = M('Articles')->limit($page->firstRow.','.$page->listRows)->order('id DESC')->select();
        foreach($list as $value){
            $this->Cache->set($this->code_key.$value['id'],$value,3600);//
每条记录一个缓存
            unset($value);
        }
    }else{
        $start = $page->firstRow;
        $end = $start+$page->listRows;
        for($i=$start;$i<$end;$i++){
            if(empty($cache_ids[$i])){
                break;
            }
            $temp = $this->Cache->get(ArticleList.$cache_ids[$i]);
            if(!$temp){//某个缓存不存在,从库里拿
                $temp = $this->getInfo($cache_ids[$i]);
                $this->Cache->set($this->code_key.$cache_ids[$i],$temp,3600);//记录丢失的缓存
            }
            $list[] = $temp;
            unset($temp);
        }
    }
    return ['page'=>$show,'list'=>$list];
}
问题2:
获取所有id的时候通过foreach获取??
解决方法:
1.通过数组函数array_column获取数组某一列的值,返回一个一维数组。
2.其实当时查代码的时候只是要id,可以加个filed(‘id’),然后存就存一个二维数组,子数组只有一个字段id,拿的时候通过array_slice获取一段要的数据。
温馨提示:不要通过foreach获取缓存!!查每个缓存,不存在,查数据库by id,再存缓存。
public function getList(){
    $cache_ids = $this->Cache->get(ArticleId);
    $count = $this->getCount();//计算总数
    $page = new Page($count,$this->pageCount);
    $show = $page->show();
    if(!$cache_ids){//这个在后台跑一次,永久记录缓存
        $lists = M('Articles')->order('id DESC')->select();//tp3.2不能直接
查找返回一维数组为某个字段的所有数据
        $ids = [];
        $ids = array_column($lists,'id');
        unset($lists);
        $this->Cache->set(ArticleId,$ids);//记录所有id到一个缓存
        $cache_ids = $this->Cache->get(ArticleId);
    }
    $start = $page->firstRow;
    $end = $start+$page->listRows;
    for($i=$start;$i<$end;$i++){
        if(empty($cache_ids[$i])){
            break;
        }
        $temp = $this->Cache->get(ArticleList.$cache_ids[$i]);
        if(!$temp){//某个缓存不存在,从库里拿
            $temp = $this->getInfo($cache_ids[$i]);
            $this->Cache->set($this->code_key.$cache_ids[$i],$temp,3600);//记录丢失的缓存
        }
        $list[] = $temp;
        unset($temp);
    }
    
    return ['page'=>$show,'list'=>$list];
}
  • 增删改时,更新缓存
以下代码存在的问题:
1. 新增和修改没有考虑ids缓存不存在的情况!!
2. 且存在隐患:当用户新增数据并发量大,插入数据库先的人存入缓存慢!!
导致列表排序有问题!!如:当前数据1,2,3;a先插入4,但是还没来得及保存到缓存的那一个节点!
b插入了5,且插入了缓存,导致原本应该1,2,3,4,5的缓存列表变成1,2,3,5,4
一劳永逸的解决方案:增删改涉及到的缓存直接删除!!因为获取的时候找不到缓存时会到数据库查并存入缓存!!
/**
 * 新增一个缓存
 * @param $id
 */
public function addCache($id){
    $info = $this->getInfo($id);
    $this->Cache->set($this->code_key.$id,$info,3600);//记录一条的缓存
    $ids = $this->Cache->get($this->code_ids);
    array_unshift($ids,$id);
    $this->Cache->set($this->code_ids,$ids);
}

/**
 * 修改一个缓存
 * @param $id
 */
public function updateCache($id){
    $info = $this->getInfo($id);
    $this->Cache->set($this->code_key.$id,$info,3600);//记录一条的缓存
}

/**
 * 删除一个缓存
 * @param $id
 */
public function delCache($id){
    $this->Cache->rm($this->code_key.$id);//记录一条的缓存
    $ids = $this->Cache->get($this->code_ids);
    array_splice($ids,array_search($id,$ids),1);
    $this->Cache->set($this->code_ids,$ids);
}
  • 最终代码
<?php
/**
 * Created by PhpStorm.
 * User: Administrator
 * Date: 2018/4/10
 * Time: 14:41
 */
namespace Api\Controller;
use Think\Controller;
use Think\Page;

class ArticleClass
{
    private $pageCount = 10;//每页显示
    private $Cache;//存储缓存对象
    public function __construct()
    {
        $this->Cache = getRedis();
    }

    /**
     * 获取列表
     * @return array
     */
    public function getList(){
        $cache_ids = $this->getIdList();//获取数据
        //分页
        $count = count($cache_ids);//计算总数
        $page = new Page($count,$this->pageCount);
        $show = $page->show();
        $start = $page->firstRow;
        $end = $start+$page->listRows;
        $list = [];
        for($i=$start;$i<$end;$i++){
            if(empty($cache_ids[$i])){
                break;
            }
            $temp = $this->getInfo($cache_ids[$i]);
            $list[] = $temp;
            unset($temp);
        }
        return ['page'=>$show,'list'=>$list];
    }

    /**
     * 获取ID列表
     * @return mixed
     */
    public function getIdList(){
        $cache_ids = $this->Cache->get(ArticleId);
        if(!$cache_ids){//id缓存不存在,进来
            $lists = M('Articles')->order('id DESC')->select();//tp3.2不能直接返回一维数组为某个字段所有数据
            $ids = [];
            $ids = array_column($lists,'id');
            $this->Cache->set(ArticleId,$ids,24*3600);//记录所有id到一个缓存
            $cache_ids = $this->Cache->get(ArticleId);
        }
        return $cache_ids;
    }

    /**
     * 获取某一条记录信息
     * @param $id
     * @return mixed
     */
    public function getInfo($id){
        $info = $this->Cache->get(ArticleList.$id);
        if(!$info){//某个缓存不存在,从库里拿
            $info = M('Articles')->where('id = '.$id)->find();
            $this->Cache->set(ArticleList.$id,$info,3600);//记录丢失的缓存
        }
        return $info;
    }

    /**
     * 新增 by id
     * @param $data
     * @return mixed
     */
    public function create($data){
        $id = M('Articles')->add($data);
        $this->killActicleIdsCache();
        return $id;
    }

    /**
     * 更新by id
     * @param $data
     * @param $id
     * @return mixed
     */
    public function updateById($data,$id){
        $res = M('Articles')->where('id = '.$id)->save($data);
        $this->kellActicleInfoCache($id);
        return $res;
    }

    /**
     * 删除by id
     * @param $id
     * @return mixed
     */
    public function deleteById($id){
        $res = M('Articles')->where('id = '.$id)->delete();
        $this->kellActicleInfoCache($id);
        $this->killActicleIdsCache();
        return $res;
    }

    /**
     * 删除内容缓存
     * @param $id
     */
    public function kellActicleInfoCache($id){
        $this->Cache->rm(ArticleList.$id);//删除一条的缓存
    }

    /**
     * 清除ids缓存
     */
    public function killActicleIdsCache(){
        $ids = $this->Cache->get(ArticleId);
        if($ids){
            $this->Cache->rm(ArticleId);
        }
    }
}

2)list mysql查询好,排好序,都丢在一个列表里面。通过区间获取数据。

弊端:

a.不能指定修改、删除某个key,增删改都要重写(先删除,后for循环插入)整个list.

b.并发的写缓存,可能导致数据重复(数据不是唯一性,集合)

上面是想岔了,①.list可以通过lre指定删除每个值的,而且list也是存id,信息数据还是一条一个缓存。②.数据重复主要是在翻页的时候刚好有人写入数据(通过id倒序排),如果通过page当前页码和pageSize每页显示个数分页,就会出现原本在第一页的数据,下拉加载第二页的时候发现也在!!因为在第一页头插入了数据把第一页最后的数据顶到第二页或者更后面的页码。。。

解决思路:

首先,把数据通过id顺序rpush从尾查到头,获取数据时就从尾拿到头。分页通过最后一条记录的索引值得到下一页end值,然后通过pageSize计算出start值,最后通过lrange(ids,start,end)获取分页区间。索引值key传给前端,前端再传回来;如果不存在,则获取数据总数llen(),得到$end,再处理。新增通过rpush()插入尾部,删除通过lrem()删除指定值。更新和修改删除对应的info缓存。
例如:
1-----5-----10-----15 有15条数据,每页5条数据分页。

lrange(ids,14,14-5+1) 即索引为10,11,12,13,14的数据。(索引从0开始)

完整代码如下

class ArticleClass
{
    private $pageCount = 5;//每页显示
    private $Cache;//存储缓存对象
    public function __construct()
    {
        $this->Cache = getRedis();
    }

    /**
     * 获取列表
     * @param null $key 列表索引
     * @return array
     */
    public function getList($key=null){
        //分页
        if($key == null){
            $end = $this->getArticleIdLen();
        }else{
            $end = $key;
        }
        $end = $end - 1;
        if($end < 0){
            return ['list'=>[],'key'=>0];
        }
        $start = $end - $this->pageCount + 1;
        if($start <= 0){
            $start = 0;
        }
        $cache_ids = $this->getRangeId($start,$end);//获取数据
        $list = [];
        foreach($cache_ids as $id){
            $info = $this->getInfo($id);
            $list[] = $info;
        }
        rsort($list);
        return ['list'=>$list,'key'=>$start];
    }

    /**
     * 获取ids缓存长度
     * @return mixed
     */
    public function getArticleIdLen(){
        if(!$this->Cache->exists(ArticleId)){//缓存不存在,获取
            $this->setIdList();
        }
        $count = $this->Cache->llen(ArticleId);
        return $count;
    }

    /**
     * 获取ID区间缓存
     * @param $start
     * @param $end
     * @return mixed
     */
    public function getRangeId($start,$end){
        if(!$this->Cache->exists(ArticleId)){//缓存不存在,获取
            $this->setIdList();
        }
        $cache_ids = $this->Cache->lrange(ArticleId,$start,$end);
        return $cache_ids;
    }

    /**
     * 设置ID列表缓存
     * @return mixed
     */
    public function setIdList(){
        $lists = M('Articles')->order('id ASC')->select();
        $ids = [];
        $ids = array_column($lists,'id');
        foreach($ids as $value){
            $this->Cache->rpush(ArticleId,$value);
        }
    }

    /**
     * 获取某一条记录信息
     * @param $id
     * @return mixed
     */
    public function getInfo($id){
        $info = $this->Cache->get(ArticleList.$id);
        if(!$info){//某个缓存不存在,从库里拿
            $info = M('Articles')->where('id = '.$id)->find();
            $this->Cache->set(ArticleList.$id,$info,3600);//记录丢失的缓存
        }
        return $info;
    }

    /**
     * 新增 by id
     * @param $data
     * @return mixed
     */
    public function create($data){
        $id = M('Articles')->add($data);
        $this->Cache->rpush(ArticleId,$id);//插到列表尾巴
        return $id;
    }

    /**
     * 更新by id
     * @param $data
     * @param $id
     * @return mixed
     */
    public function updateById($data,$id){
        $res = M('Articles')->where('id = '.$id)->save($data);
        $this->kellActicleInfoCache($id);
        return $res;
    }

    /**
     * 删除by id
     * @param $id
     * @return mixed
     */
    public function deleteById($id){
        $res = M('Articles')->where('id = '.$id)->delete();
        $this->kellActicleInfoCache($id);
        $this->Cache->lrem(ArticleId,$id);//删除id列表缓存
        return $res;
    }

    /**
     * 删除内容缓存
     * @param $id
     */
    public function kellActicleInfoCache($id){
        $this->Cache->rm(ArticleList.$id);//删除一条的缓存
    }
}

扩展问题:数据量太大,通过时间分多个redis存储,每个月存一个reids列表,怎么拿完这个月的值再拿上一个月的,并且要数据衔接好?

解决思路:获取值时先获取当前月份的数据,若数据不满足当前页码要显示的个数,则获取下一个缓存补充,一个缓存获取完,获取下一个缓存。前端传多一个缓存块id。

代码如下:

class ArticleClass
{
    private $pageCount = 5;//每页显示
    private $Cache;//存储缓存对象
    private $cache_block = ['2018-04','2018-03'];
    public function __construct()
    {
        $this->Cache = getRedis();
    }

    /**
     * 获取列表
     * @param null $key 列表索引
     * @return array
     */
    public function getList($key=null,$block=0){
        //分页
        if($key == null){
            $end = $this->getArticleIdLen($this->cache_block[$block]);
        }else{
            $end = $key;
        }
        $end = $end - 1;
        if($end < 0){//如果没有值,拿它下一个缓存
            $block++;
            $end = $this->getArticleIdLen($this->cache_block[$block]);
            $end = $end - 1;
            if($end < 0){
                $block--;
                return ['list'=>[],'key'=>0,'block'=>$block];
            }
        }
        $start = $end - $this->pageCount + 1;
        if($start <= 0){
            $start = 0;
        }
        $cache_ids = $this->getRangeId($start,$end,$this->cache_block[$block]);//获取数据

        if(count($cache_ids) < $this->pageCount){//最后返回数量不足,拿下一个补充
            $block++;
            $end = $this->getArticleIdLen($this->cache_block[$block]);
            $end = $end - 1;
            if($end > 0){
                $start = $end - $this->pageCount + 1 + count($cache_ids);
                $block_ids = $this->getRangeId($start,$end,$this->cache_block[$block]);
                $cache_ids = array_merge($cache_ids,$block_ids);
            }else{
                $block--;
            }
        }
        $list = [];
        foreach($cache_ids as $id){
            $info = $this->getInfo($id);
            $list[] = $info;
            unset($id);
        }
        rsort($list);
        return ['list'=>$list,'key'=>$start,'block'=>$block];
    }

    /**
     * 获取ids缓存长度
     * @return mixed
     */
    public function getArticleIdLen($block){
        if(!$this->Cache->exists(ArticleId.$block)){
            $this->setIdList($block);
        }
        $count = $this->Cache->llen(ArticleId.$block);
        return $count;
    }

    /**
     * 获取ID区间缓存
     * @param $start
     * @param $end
     * @return mixed
     */
    public function getRangeId($start,$end,$block){
        if(!$this->Cache->exists(ArticleId.$block)){
            $this->setIdList($block);
        }
        $cache_ids = $this->Cache->lrange(ArticleId.$block,$start,$end);
        return $cache_ids;
    }

    /**
     * 设置ID列表缓存
     * @return mixed
     */
    public function setIdList($block){
        $lists = M('Articles')->where("DATE_FORMAT(created_time,'%Y-%m') = '".$block."'")->order('id ASC')->select();
        $ids = [];
        if(count($lists)){
            $ids = array_column($lists,'id');
            foreach($ids as $value){
                $this->Cache->rpush(ArticleId.$block,$value);
            }
        }
    }

    /**
     * 获取某一条记录信息
     * @param $id
     * @return mixed
     */
    public function getInfo($id){
        $info = $this->Cache->get(ArticleList.$id);
        if(!$info){//某个缓存不存在,从库里拿
            $info = M('Articles')->where('id = '.$id)->find();
            $this->Cache->set(ArticleList.$id,$info,3600);//记录丢失的缓存
        }
        return $info;
    }

    /**
     * 新增 by id
     * @param $data
     * @return mixed
     */
    public function create($data){
        $id = M('Articles')->add($data);
        $this->Cache->rpush(ArticleId.substr($data['created_time'],0,7),$id);//插到列表尾巴
        return $id;
    }

    /**
     * 更新by id
     * @param $data
     * @param $id
     * @return mixed
     */
    public function updateById($data,$id){
        $res = M('Articles')->where('id = '.$id)->save($data);
        $this->kellActicleInfoCache($id);
        return $res;
    }

    /**
     * 删除by id
     * @param $id
     * @return mixed
     */
    public function deleteById($id){
        $data = M('Articles')->field('created_time')->where('id = '.$id)->find();
        $res = M('Articles')->where('id = '.$id)->delete();
        $this->kellActicleInfoCache($id);
        $this->Cache->lrem(ArticleId.substr($data['created_time'],0,7),$id);//删除id列表缓存
        return $res;
    }

    /**
     * 删除内容缓存
     * @param $id
     */
    public function kellActicleInfoCache($id){
        $this->Cache->rm(ArticleList.$id);//删除一条的缓存
    }
}

3)zset