使用memcache处理缓存的三种方案

这篇文章主要讨论的问题是:如何为项目设计一个完整而简洁的缓存系统。只讲做法,不讲原理。在我们项目中,使用到了三种方法,来保证了缓存系统的有效简洁。

1) 第一种,最常见的方式 读取数据的主要步骤如下:

1)先从缓存中获取数据(如果在缓存中获取到,则直接返回已获取的数据)

2)如果获取不到,再从数据库里面读取相应的数据

3)  把获取到的数据加入缓存中

注意:这种方式是在Model层,也就是业务处理层加入的。

实例代码如下:

    public static function getCombatPowerRank()
    {
        $cacheKey = ‘Rank:CombatPower‘;

        // 先从缓存中读取
        if ($list = F(‘Memcache‘)->get($cacheKey)) {
            return $list;
        }

        $list = array();

        // 遍历所有用户分库,执行清理
        for ($i = 1; $i <= DIST_USER_DB_NUM; $i++) {
            if ($distList = Dao(‘Dist_User‘)->setDs($i)->getCombatPowerTopUsers(self::RANK_LIMIT)) {
                $list = array_merge($list, $distList);
            }
        }

        // 保存到缓存中
        F(‘Memcache‘)->set($cacheKey, $list, C(‘RANK_CACHE_TIME‘));

        return $list;
    }

这种方式确实很好理解,有一个弊端就是,所有的缓存都需要手动的加上以上缓存的代码,需要修改函数的内部代码。请注意,我们在项目中加入缓存的时间是项目完成的差不多了,也就是说需要有很多这样的“读取类”函数加入缓存,如果全是以上这种加入缓存方式的话,需要修改很多函数的内部代码,那绝对是一个复杂而容易遗漏的苦力活。如果一不小心,就会出现错误。有没有好的方式可以集中的给某些函数加入这样的缓存系统呢(如果有的话,绝对是一个福音,哈哈)

2)第二种方式 ,在DAO层集中处理。在解释这种方法之前,我先简要说明一下我们的需求,便于更好理解为什么我可以这么做。

在我们的游戏项目中,有一部分数据时静态资源数据,这种数据时配置好的,不会经常变动,每个用户需要的都一样。例如各种角色类的基础的属性,船只的基础属性等。这类数据涉及到的操作一般是读:把一张表全部读出来,获取根据某个条件读取相应的内容。既然操作单一,我们就直接在DAO层处理这类方法的缓存。做法就是给每一个Dao类里面的函数加入缓存。

不改变方法的内部代码,却可以给每个方法加入缓存,PHP魔术方法__call()就可以实现,如果对象调用某个方法,而这个方法又不存在,那么就会调用到这个魔术方法了,具体实现代码如下:

    /**
     * 调用魔术方法
     *
     * @param string $method
     * @param mixed $args
     * @return mixed
     */
    public function __call($method, $args)
    {
        if (! method_exists($this, ‘__CACHE__‘ . $method)) {

            // 这里是实现数据库链式查询的,这里可以忽略
            return parent::__call($method, $args);
        }

        $cacheKey = md5($this->_dbName . ‘:‘ . $this->_tableName . ‘:‘ . $method . ‘:‘ . serialize($args));

        $data = $this->_cache->get($cacheKey);

        if ($data === false) {
            // 调用类里面的方法
            $data = call_user_func_array(array($this, ‘__CACHE__‘ . $method), $args);
            $this->_cache->set($cacheKey, $data);
        }

        return $data;
    }

代码运行机制: 比如说有这样的一个调用关系:Dao(‘Static_Ship‘)->get(),但是在Static_Ship这个类中没有get()这个方法,于是程序就会执行__call(),在这个类中,有一个这样的方法__CACHE__get()这样的一个方法,于是我就执行了这个方法,并且把这个函数的数据缓存起来了。这样就达到了我们的目的,不改变函数内部的代码,把函数的结果缓存起来。

3)集中处理和用户有关的数据的缓存。如果大家细心的话,可以发现方法2中缓存的键值设计并不针对某一个用户。

$cacheKey = md5($this->_dbName . ‘:‘ . $this->_tableName . ‘:‘ . $method . ‘:‘ . serialize($args)); 注意,这个键值的设计主要由库名,表名,方法名,参数,需要注意的是库名,因为如果数据库涉及到分布式处理,就需要定位到相应的库名中。如果需要缓存的数据和用户有关系,我该如何设计呢。

这个处理方式还是需要结合需求,在我们项目中,需要读取“我的船”相应的数据。比如

1)我需要读取我的船的攻击力:getShipFieldByUserShipId($uid, $shipId, attack)

2) 我需要读取船的防御力 :getShipFieldByUserShipId($uid, $shipId, defence)

3) 读取我的船的航海速度:getShipFieldByUserShipId($uid, $shipId, speed)

这个时候,有两种SQL查询方法:

1) uid = $uid AND shipId = $shipId AND field=$field

2)   $data = " uid = $uid AND shipId = $shipId " 然后再这个$data数组中,返回相应的$data[$field].

你可能会觉得第二种方法会获取到一些无用的数据,不好。但是,事实上,第二种方法比第一种方法好,因为他可以使用索引查询,这个属于SQL优化的,暂且不讨论,第二个原因是便于方法可以加入缓存,查询条件越“统一”,越容易加入缓存。第三种做法也是在DAO层中实现,缓存方式正是基于查询条件高度统一的原则:

    public function getField($pk, $field)
    {
        // 禁用缓存时
        if (! $this->_isCached) {
            return $this->field($field)
                        ->where($this->_getPkCondition($pk))
                        ->fetchOne();
        }

        $data = $this->get($pk);
        return isset($data[$field]) ? $data[$field] : null;
    }

get方法的主要代码如下:

    /**
     * 根据主键 fetchRow
     *
     * @param mixed $pk
     * @return array
     */
    public function get($pk)
    {
        // 禁用缓存时
        if (! $this->_isCached) {
            return $this->where($this->_getPkCondition($pk))->fetchRow();
        }

        $cacheKey = $this->_getRowCacheKey($pk);

        // 保证相同的静态记录只读取一遍
        if (isset($this->_rowDatas[$cacheKey])) {
            return $this->_rowDatas[$cacheKey];
        }

        $row = $this->_cache->get($cacheKey);

        if ($row === false) {
            $row = $this->where($this->_getPkCondition($pk))->fetchRow() ?: array();
            $this->_cache->set($cacheKey, $row, $this->_cacheTTL);
            $this->_rowDatas[$cacheKey] = $row;
        }

        return $row;
    }

获取缓存键值的方法_getRowCacheKey()实现方式如下:

    // 获取单条记录缓存key
    protected function _getRowCacheKey($pk)
    {
        if (is_array($pk)) {
            $pkString = implode(‘:‘, $pk);
        }
        else {
            $pkString = $pk;
        }

        return md5($this->_dbName . ‘:‘ . $this->_tableName . ‘:get:‘ . $pkString);
    }

保证查询条件的高度统一,根据查询的条件设置缓存,就是第三中做法的精髓了。

缓存系统需要注意的几点:

1) 注意缓存系统的关联性,如果数据发生了变化,一定要更新缓存

2)如果被缓存的数据和用户有关,一定要把$cacheKey处理好,保证每个用户数据不会被其它用户串改。特别需要注意的是分库的时候uid=1可不止一个哦

3)如果有必要的话,可以做一个缓存命中率的统计,统计哪些库的那些表被哪些函数操作的次数

4) 如果某些表的数据频繁的被修改,可以不需要缓存,如果用户的行文记录表,_isCached 这个属性就是用来控制是否需要缓存。

见如下代码:

    /**
     * 删除(根据主键)
     *
     * @param mixed $pk
     * @param array $extraWhere 格外的WHERE条件
     * @return bool
     */
    public function deleteByPk($pk, array $extraWhere = array())
    {
        $where = $this->_getPkCondition($pk);

        if ($extraWhere) {
            $where = array_merge($where, $extraWhere);
        }

        if (! $result = $this->where($where)->delete()) {
            return $result;
        }

        // 清理缓存
        if ($this->_isCached) {
            $this->_deleteRowCache($pk);
        }

        // 统计Memcache读写次数
        Dao(‘Massive_MemcacheRecord‘)->mark($this->_dbName, $this->_tableName, __METHOD__, 1);

        return $result;
    }
时间: 2024-10-11 16:52:05

使用memcache处理缓存的三种方案的相关文章

三种方案实现日志记录功能

三种方案实现日志记录功能 方案一.使用拦截器实现日志记录功能 步骤一.首先需要我们自定义一个注解类 package cn.hmy.Util; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 自定义操作日志注解接口类

【Win 10 应用开发】文件读写的三种方案

本文老周就跟伙伴们探讨一下关于文件读写的方法.总得来说嘛,有三种方案可以用,而且每种方案都各有特色,也说不上哪种较好.反正你得记住老祖宗留给我们的大智慧——事无定法,灵活运用者为上. OK,咱们开始吧. 先说第一个方案:使用 FileIO类. 这个类属于RT库API,它公开了一堆静态方法,可以直接调用,快捷方便,就像.net里面的File类一样.在使用FileIo类的时候,需要一个引用已知文件的StorageFile实例,而且FileIo只能操作已经存在的文件,它不会自动创建文件,这一点要注意.

MySQL冗余数据的三种方案

一,为什么要冗余数据 互联网数据量很大的业务场景,往往数据库需要进行水平切分来降低单库数据量. 水平切分会有一个patition key,通过patition key的查询能够直接定位到库,但是非patition key上的查询可能就需要扫描多个库了. 此时常见的架构设计方案,是使用数据冗余这种反范式设计来满足分库后不同维度的查询需求. 例如:订单业务,对用户和商家都有订单查询需求: Order(oid, info_detail); T(buyer_id, seller_id, oid); 如果

三种方案在Windows系统下安装ubuntu双系统

一.虚拟机安装(不推荐) 使用工具:Vmware 如果不是因为迫不得已,比如Mac OS对硬件不兼容,Federa安装频繁出错,各种驱动不全等等,不推荐使用虚拟机安装. 个人感觉这是一种对操作系统的亵渎,因为你得的到它的人,却没有得到它的心. 但是虚拟机的安装也并不是毫无优点,不用担心网卡显卡声卡的驱动问题,不用担心硬件不兼容之类的. 下面来简单说一下操作步骤. 1.下载ubuntu的镜像文件 这个直接去官网就可以下载到: http://www.ubuntu.com/download/deskt

zz存储系统中缓存的三种类型

三种基本类型的缓存:绕写式(write-around),直写式(write-through)和回写式(write-back). 绕写式(write-around)缓存意味着所有数据首先被写入到硬盘区,当它成为合格的后,再基于读操作将它复制(注意不是移动)到闪存区.这意味着数据总是驻留在硬盘层,通常由RAID或镜像功能进行数据保护.其结果是,缓存区的闪存并不需要像上述分层模型中所要求的高可靠性.此外,由于所有数据首先被写入硬盘,闪存的使用寿命应该会延长.只有真正值得闪存“缓存”的数据才会被写到闪存

hibernate(二)一级缓存和三种状态解析

序言 前一篇文章知道了什么是hibernate,并且创建了第一个hibernate工程,今天就来先谈谈hibernate的一级缓存和它的三种状态,先要对着两个有一个深刻的了解,才能对后面我要讲解的一对多,一对一.多对多这种映射关系更好的理 --WH 一.一级缓存和快照 什么是一级缓存呢? 很简单,每次hibernate跟数据库打交道时,都是通过session来对要操作的对象取得关联,然后在进行操作,那么具体的过程是什么样的呢? 1.首先session将一个对象加入自己的管理范围内,其实也就是把该

分布式缓存中三种负载均衡的方法

本文主要是比较三种分布缓存负载均衡的方法,第一种是最简单的将 key的hash值对机器数取模算法,第二种是一致性哈希算法,第三种是淘宝开源的缓存解决方案tair的均衡算法.下面来分析下这三种算法的优缺点. 第一种:传统的数据分布方法,将key的hash值对机器数取模 这个算法的实现非常简单,计算hash(key)/n,n为机器数,得到的值就是该key需要路由到的服务器编号了. 优点:实现简单 缺点:在服务器数量发生变化的时候,缓存会大量失效. 第二种:一致性hash 试想下如果使用传统取模算法.

react传参的三种方案

可以直接: https://jsfiddle.net/u0no1t2z/ class LoggingButton extends React.Component { // 此语法确保 `handleClick` 内的 `this` 已被绑定. // 所以就不需要bind this了,但是没有办法传自定义参数,所以只能通过data-参数 读取 handleClick = (e) => { console.log('this is 1:',this); console.log('this is 1:

前端组件化的三种方案:

1.预编译语言在开发阶段,用更完善的第三方程序语言,生成html,css,js代码.缺点:css有sass,less:js有CoffeeScript,TypeScripty:HTML呢?优点:如果语言有缺陷,那我就再套一层语言来隐藏这些缺陷.将来:据说hex语言... 2.css和html写在html文件里 然后以 { type:"get", dataType:"text", url:"module.html", async:true, cach