PHP中nosql缓存更新策略

1.why

在后端由于性能要求,经常会进行nosql(Memcache、Redis等)缓存,缓解来自磁盘(关系型数据库、文件等)数据的瓶颈。但是缓存也会带来弊端,数据不能实时更新。更新策略如下:

1)主动更新。对一些关键的、业务量大的业务进行定时主动更新数据,防止过期失效后,直接穿透缓存,进入磁盘,带来雪崩。

2)被动更新。对一些实时性不是很强、业务量小的需求,可以采用被动更新。当缓存失效后,直接读写磁盘,然后再放入缓存,下个用户在缓存失效前就可直接读取缓存数据。

以下讲的是在数据变化情况下,被动更新缓存策略。

 

2.how

建立缓存版本号。

当有数据更新或者添加时,更新全局或者某个模块的缓存版本号,使缓存键值找不到,重新创建新的缓存键值,然后读取磁盘数据。缺点:当更新或者添加频繁,缓存的键值会越来越多,直至它们自动给失效,所以建议缓存过期时间不要设置太长,防止缓存占用太多内存(如果内存足够大,你就随意啦)。

 

3.what

以下是建立的数据库模型基类:

myBaseModel.php:

<?php
/**
 * Created by PhpStorm.
 * User: Alvin Tang
 * Date: 2017/2/20
 * Time: 11:38
 * Author: 442353346@qq.com
 * Desc: 封装好的基类数据库模型
 */

class myBaseModel extends model{
    /***
     * Memcache缓存
     * @var null
     */

    protected $mem = null;

    /***
     * 构造函数
     * myBaseModel constructor.
     */

    function __construct(){
        $this->mem = MemClass::getInstance();
    }

    /***
     * 获取模块的MEM版本号
     * @param string $module
     * @return array|string
     */

    public function getMemCacheVersion( $module = '' ){
        $key = MEM_CACHE_GLOBAL_VERSION_PRE . (empty( $module ) ? $this->tableNameNew : $module);
        return $this->mem->get($key);
    }

    /***
     * 更新Mem某个模块的缓存版本号
     * @param string $module
     * @param bool $version
     * @param int $expire
     */

    public function updateMemCacheVersion( $module = '', $version = false, $expire = 864000){
        $value = empty( $version ) ? time() : $version;
        $key = MEM_CACHE_GLOBAL_VERSION_PRE . (empty( $module ) ? $this->tableNameNew : $module);
        $this->mem->set($key, $value, $expire);
    }
    /***
     * 获取列表
     * @param string $fields 字段
     * @param bool $condition 条件
     * @param bool $order 排序
     * @param bool $limit 限制条件
     * @param array $bind PDO绑定参数
     * @param bool $cache 是否启用缓存
     * @param string $cacheKeyPrex 缓存的键值前缀
     * @param int $expire 过期时间
     * @param bool $recordCount 是否获取记录总数
     * @return array|mixed|string
     */

    public function getLists($fields = '*', $condition = false, $order = false, $limit = false, $bind = array(), $cache = true, $cacheKeyPrex = '', $expire = 300, &$recordCount = false){
        //是否启用缓存
        if($cache){
            //全局版本号,删除缓存时需要控制版本号,尽量根据表或者模块来
            $moduleCacheVersion = $this->getMemCacheVersion();

            //是否读取总数
            if($recordCount){
                //总数键值
                $cacheCountKey = md5( serialize( array( $cacheKeyPrex, $this->tableNameNew, $recordCount, $condition, $bind, $moduleCacheVersion) ) );

                $cacheCountData = $this->mem->get( $cacheCountKey );

                if(!empty($cacheCountData)){
                    $recordCount = $cacheCountData;
                }

            }
            //缓存键值
            $cacheKey = md5( serialize( array( $cacheKeyPrex, $this->tableNameNew, $fields, $condition, $order, $limit, $bind, $moduleCacheVersion) ) );

            $cacheData = $this->mem->get( $cacheKey );

            if(!empty($cacheData)){
                return $cacheData;
            }
        }
        /*************获取总数START***************/
        //是否读取总数
        if($recordCount){
            $sqlCountData = $this->select('COUNT(1) my_count', $condition, false, false, $this->tableNameNew, $bind);
            $sqlCountData = empty( $sqlCountData ) ? 0 : $sqlCountData[0]['my_count'];
            $recordCount = $sqlCountData;
            if($cache){
                $this->mem->set($cacheCountKey, $sqlCountData, $expire);
            }
        }
        /*************获取总数END***************/
        //数据库数据
        $sqlData = $this->select($fields, $condition, $order, $limit, $this->tableNameNew, $bind);

        if($cache){
            $this->mem->set($cacheKey, $sqlData, $expire);
        }

        return $sqlData;
    }

    /***
     * 获取单条信息
     * @param string $fields 字段
     * @param bool $condition 条件
     * @param array $bind PDO绑定参数
     * @param bool $cache 是否启用缓存
     * @param string $cacheKeyPrex 缓存的键值前缀
     * @param int $expire 过期时间
     * @return bool|mixed
     */

    public function getInfo($fields = '*', $condition = false, $bind = array(), $cache = true, $cacheKeyPrex = '', $expire = 300){
        $result = $this->getLists($fields, $condition, false, '1', $bind, $cache, $cacheKeyPrex, $expire);

        return empty($result) ? false : $result[0];
    }

    /***
     * 更新或者减少某个字段的数据
     * @param $condition
     * @param array $data
     * 如:array('view_num' => 2, 'reply_num' => 1)
     * @param bool $plus
     * @return mixed
     */

    private function inOrDecrease($condition, $data = array(), $plus = true){
        $condition = $this->getCondition( $condition );
        $tempData = array();
        foreach ($data as $key => $value){
            $tempData[] = "`{$key}` = `{$key}` " . ($plus ? '+' : '-') . $value;
        }
        $sql = 'UPDATE ' . $this->tableNameNew . ' SET ' . implode(',', $tempData) . ' WHERE ' . $condition;
        return $this->execute($sql);
    }
    /***
     * 递增更新数据,如某个字段数据+1等
     * @param $condition
     * @param array $data
     * 如:array('view_num' => 2, 'reply_num' => 1)
     * @return mixed
     */

    public function increase($condition, $data = array()){
        return $this->inOrDecrease($condition, $data);
    }

    /***
     * 减少某个字段的数据
     * @param $condition
     * @param array $data
     * 如:array('view_num' => 2, 'reply_num' => 1)
     * @return mixed
     */

    public function decrease($condition, $data = array()){
        return $this->inOrDecrease($condition, $data, false);
    }

    /***
     * 添加或者更新单条信息,必须有唯一主键
     * @param $params
     * @return mixed
     * @throws Exception
     */

    public function duplicateInfo( $params ){
        //操作语句
        $sql = 'INSERT INTO ' . $this->tableNameNew;
        //字段
        $fields = array_keys( $params );

        $sql .= '(`' . implode('`,`', $fields) . '`)';

        //插入时绑定的字段
        $bindFields = array_map(function ($v){
            return ':' . $v;
        }, $fields);
        //更新的预处理键值
        $bindFields2 = array_map(function ($v){
            return ':' . $v . '_m';
        }, $fields);

        //更新的绑定
        $updateBindFields = array_map(function ($v){
            return '`'. $v .'`' . ' = :' . $v . '_m';
        }, $fields);

        $sql .= ' VALUES(' . implode(',', $bindFields) .')';

        $sql .= ' ON DUPLICATE KEY UPDATE ' . implode(',', $updateBindFields);
        //绑定的数据
        $bindData = array_combine($bindFields, array_values( $params ));
        //
        $bindData2 = array_combine($bindFields2, array_values( $params ));

        //数据合并
        $bindData = array_merge($bindData, $bindData2);

        try{
            $result = $this->execute($sql, $bindData);
        }catch (Exception $ex){
            throw new Exception($ex->getMessage());
        }

        return $result;
    }
}

 

数据库模型继承:bannersModel.php

<?php
/**
 * Created by PhpStorm.
 * User: Alvin Tang
 * Date: 2017/2/20
 * Time: 11:36
 * Author: 442353346@qq.com
 * Desc: 首页轮播图模型
 */

class bannersModel extends myBaseModel {
    public static $db_group = 'bbs';
    protected $tableNameNew = 'b_banners';
}

调用方法:

/***
 * 获取首页轮播图
 */

protected function getBanners(){
    //轮播图模型
    $myBannersModel = new bannersModel();
    //获取列表数据
    $list = $myBannersModel->getLists();
    //do something
   
    //更新缓存
    $myBannersModel->updateMemCacheVersion();
}

Leave a Comment