Home  >  Article  >  php教程  >  浅谈对CACHE操作的封装及最佳实践

浅谈对CACHE操作的封装及最佳实践

WBOY
WBOYOriginal
2016-06-06 20:14:091090browse

作为WEB开发者,CACHE对我们来说是再熟悉不过了。但是,你真的有研究如何把它用得更“优雅”吗?下面以较常见的Memcache为例,谈谈对CACHE操作的几种常见封装方法,并推荐一种我认为最佳的实践。如果你有更好的解决方案,请不吝赐教:) 为什么要封装? ?php

作为WEB开发者,CACHE对我们来说是再熟悉不过了。但是,你真的有研究如何把它用得更“优雅”吗?下面以较常见的Memcache为例,谈谈对CACHE操作的几种常见封装方法,并推荐一种我认为最佳的实践。如果你有更好的解决方案,请不吝赐教:)

为什么要封装?

<?php $mc = new Memcached();
$mc->addServers('127.0.0.1', 11211);
$key = 'test';
$duration = 3600;
$value = $mc->get($key);
if ($mc->getResultCode() == Memcached::RES_NOTFOUND) {
    $value = getValueFromDB();
    $mc->set($key, $value, $duration);
}

第一次使用CACHE的同学,往往会写出上面的代码。简陋且有效,拿来写个Hello World是再合适不过了,但是真正在项目中使用CACHE的时候,还这么写就太low了。没有异常处理和重试机制、不能做负载均衡,大量的重复代码……当你受不了维护之繁琐时,就会想办法来解决这些问题,那就是封装。

封装的好处很明显:
1、减少代码量,提升可读性和可复用性;
2、可以在封装层内部增加负载均衡、数据压缩、键值HASH、异常处理、重试机制等等各种功能,提升可维护性和鲁棒性。

那么问题来了,该怎么封装呢?

实例句柄封装

<?php // 封装类
class MemCacheHandle {
? ? const MC_IP = '127.0.0.1';
    const MC_PORT = '11211';
? ? private $_MC;
? ? public function __construct() {
? ? ? ? $this->_MC = new Memcached();
? ? ? ? $this->_MC->addServers(self::MC_IP, self::MC_PORT);
? ? }
? ? public function get($key) {
? ? ? ? return $this->_MC->get($key);
? ? }
? ? public function set($key, $value, $duration) {
? ? ? ? return $this->_MC->set($key, $value, $duration);
? ? }
? ? public function missed() {
? ? ? ? return $this->_MC->getResultCode() == Memcached::RES_NOTFOUND;
? ? }
}
// 调用代码
$key = 'test';
$duration = 3600;
$mcHandle = new MemCacheHandle();
$value = $mcHandle->get($key);?
if ($mcHandle->missed()) {
    $value = getValueFromDB();
    $mcHandle->set($key, $value, $duration);
}

你可能会说了,这看上去和不封装区别不大,只是少了一句addServers嘛。但是让我们看看有了这么一层封装,能做些什么事情:

1、将服务器配置信息和业务代码隔离,并可以在构造方法中做负载均衡;
2、可以在get方法中对键值进行唯一性HASH,统一键值长度,避免超长的KEY;
3、可以在set方法中对存入的数据进行压缩,减少服务器的资源占用;
4、可以在get和set方法中做异常处理,记录LOG,或加上重试机制;
5、将Memcache的相关细节隐藏了起来,你可以换成redis之类,对用户是完全透明的。

虽然成果喜人,但是还是有点问题:在每个使用CACHE的地方都要实例化一个MemCacheHandle,多次使用就要多次实例化。虽然你可以在业务代码中对实例做持有,但是跨业务线和跨代码库的多次使用呢?

静态方法封装

<?php // 封装类
class MCache {
    private static $_INSTANCE;
    public static function get($key) {
        return self::_getInstance()->get($key);
    }
    public static function set($key, $value, $duration) {
        return self::_getInstance()->set($key, $value, $duration);
    }
    public static function missed() {
        return self::_getInstance()->missed();
    }
    private static function _getInstance() {
        if (!isset(self::$_INSTANCE)) {
            self::$_INSTANCE = new MemCacheHandle();
        }
        return self::$_INSTANCE;
    }
}
// 调用代码
$key = 'test';
$duration = 3600;?
$value = MCache::get($key);
if (MCache::missed()) {
    $value = getValueFromDB();
    MCache::set($key, $value, $duration);
}?

你可能会问了:这一点也不面向对象嘛,用静态方法封装有什么好处呢?

1、隐藏了实例化的过程,少了一句代码,少使用一个变量,这是看得见的实惠;
2、内部使用单例模式(后续还可改成连接池),避免了重复的实例化,让业务端调用无后顾之忧,这点很重要

似乎到这里调用端的代码就已经简化到头了,读取、判断、写入这三句是肯定少不了的。但真的是这样吗?

最佳实践:静态方法+闭包封装

<?php // 封装类
class MC extends MCache {
    public static function access($key, $duration, $getter, $params = array()) {
        $value = self::get($key);
        if (!self::missed()) return $value;
        $value = call_user_func_array($getter, $params);
        self::set($key, $value, $duration);
        return $value;
    }
}
// 调用代码
$value = MC::access('test', 3600, function() {
    return getValueFromDB();
});

如果你从没用过PHP的闭包(5.3版本才开始提供),看到这里我想你已经说不出话来了。

调用端只知道要在从DB取值前要过一层Cache,指定一下使用的键值和生命周期就好了。那么为什么要关心具体的逻辑呢?什么key、duration这样的变量都可以不用,就连最基本的读取、判断、写入这三句,我们也封装起来了。

没有一个临时变量,一句代码就搞定,这就是最完美的封装

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn