服务端需要一个新的分层 -“数据缓存层”
— 实现读取缓存在数据访问层中抽离策略 一. 背景 使用数据缓存在WEB工程中是一个非常有意义的策略,不仅仅可以减少数据库负载,而且当数据缓存在内存中,能大大提高了的读取速度。 在WEB设计中,我们往往忽略一个重要的信息 – Key,每当我们需要缓存的时候
— 实现读取缓存在数据访问层中抽离策略
一. 背景
使用数据缓存在WEB工程中是一个非常有意义的策略,不仅仅可以减少数据库负载,而且当数据缓存在内存中,能大大提高了的读取速度。
在WEB设计中,我们往往忽略一个重要的信息 – Key,每当我们需要缓存的时候,只是单纯的对一个Key进行SET或者GET操作,时间长了,也许就出现了以下的问题:
- 这个数据缓存的Key是什么;
- 这个数据缓存生命周期有多长;
- 如何不获取这个数据缓存,而得到实际的数据做测试;
- 在其他项目中同时可能用到这个数据缓存,如何重置这个缓存
- ……
这就造成了我们平时常见的一种场景,一位同事查到原因,高喊:
“Shit!为什么生产环境上的数据总是测试有问题,测试环境都好好的,线上一定是有缓存,但是我们怎么把它清除掉!“
二. 引发思考的根源
先来看一个简单的例子:获取服务器的数据列表
// 这是一个读取服务器相关信息的数据访问类 Class ServerStore { // 根据数量获取最新的测试服务器列表数据 public function fetchTestingList($limit); }
看到这个类基本所有人都知道fetchTestingList的方法体要这么写:
// 这是一个读取服务器相关信息的数据访问类 Class ServerStore { // 根据数量获取最新的测试服务器列表数据 public function fetchTestingList($limit) { // OK,开始找Cache $cache_key = "ServerTestingList:{$limit}"; if ( FALSE !== $data_from_cache = Cache::getInstance()->get($cache_key) ) { return $data_from_cache; } // Cache没找到,找数据表吧 $data = $this->db->select('id','name','time') ->from('server') ->limit($limit) ->fetchRow(); // 写缓存,给个600秒意思下吧 Cache::getInstance()->set($cache_key, $data, 600); return $data; } }
现在我们调用这个方法获取数据:
$ServerStore = new ServeStore(); $list = $ServerStore->fetchTestingList(4);
这个对象我们统一称之为Store(数据访问) 对象
看似习以为常的方法,却隐藏的几个重要的问题:
- 这个缓存周期TTL,600秒不是我需要的,那把这个作为一个参数吧。
- 这个方法我不希望得到缓存数据,想要返回实际数据表读取的数据,那么把是否需要缓存作为一个参数吧,到此已经三个参数。
- 有些方法体的参数个数比较多,而且参数还有数组格式,那么组合的Cache Key不是需要在这里构造规则?
- 我这个ServerStore有10个方法体,我难道要写10次Cache::getInstance?我还有5个Store层呢,这是在坑爹吗?
- 我的后台项目中有个需求需要清空这个缓存,要是前台被人偷偷修改了Key的规则,我怎么拿到这个Key!这是坑到家了吗?
这个例子确实是开了一个玩笑,但事实上我们的生产环境上经常出现这样的问题,并不是因为我们的程序逻辑出错了,而是由于我们在一开始设计上就没有考虑到扩展性。
三. 独立缓存层的设计
为什么我们将缓存写在Store对象内,如果写在Store对象外头可以吗?形如这般:
$ServerStore->ttl(600)->fetchTestingList(4); $ServerStore->nocache()->fetchTestingList(4);
那么这应该不是一个Store对象可以做到方式,我们不妨将它叫做Service(数据服务)对象:
$ServerService->ttl(600)->fetchTestingList(4); $ServerService->nocache()->fetchTestingList(4);
我们可以猜想到:
- Service对象中ttl()方法是应该用来返回一个Cache(数据缓存)对象,通过Cache对象的fetchTestingList()方法获取数据。
- Service对象中nocache()方法则是用来返回一个之前的Store对象,通过Store对象的fetchTestingList()方法获取数据。
- Service对象中必然存在两个属性不妨叫做$_db_handler, $_cache_handler用来切换对应操作访问对象。
- Cache对象中只用于返回Cache中的值。
- 当Service对象中使用ttl方法获取不到Cache时(TTL失效),必然还要自动获取Store对象进行读取最新的数据。
根据我们的猜想,画出UML图:
为了保证通用性我们将Cache和Service都继承一个抽象基类:
一切看似逻辑没什么异常了,可是我们Key的组合怎么处理呢?怎么创建这个Key?
先来处理第一个问题,从一开始的设计方案看,ServerStore中每个方法只会存在一个Key,也就是说这个Store中有多少个方法体,对应的就应该有多少个Key。
为此我们可以建立一个ServerCacheKey(数据缓存键)对象,里面的每个方法都应该匹配ServerStore中对应的方法。那么我们不妨设计一个接口iServer,然而ServerCacheKey,ServerCache以及ServerStore实现这个接口,通过这样的实现ServerCacheKey将也有fetchTodayTestingList这个方法,自然能通过对应的参数组合出需要的Key。
?????? 那么第二个问题,创建这个Key的工作可能出现的位置在:
- Cache对象:要通过创建Key来获取缓存中对应Key的Value;
- Service对象:当使用Cache对象获取缓存的数据失效时,会重新使用Store对象从数据表中查出新的值,需要重新通过创建Key的操作重新将新数据写入缓存。
有了接口的帮助,那么实现接口的类,它们的类名,方法,参数就被统一了,为此我们再建立一个CacheKeyCreater类通过这一些列的规则就可以创建指定的Key。
注:ServerCache可以继承抽象类Call魔术自动通过CacheKeyCreater实现的接口方法,进行读取数据缓存方法,因此我们这里移除ServerCache实现iServer的过程。
四. 程序实现
为了方便程序实现和调试,以下使用ThinkPHP框架模拟程序,将App下Lib目录结构做如下规划:
/Lib/Core:核心类文件,CacheKeyCreater, BaseService, BaseCache等放置处
/Lib/Interface: 接口类放置处
/Lib/Service: 服务类放置处
/Lib/Store: 数据访问类放置处
/Lib/Cache: 数据缓存类放置处
/Lib/CacheKey: 数据缓存键类放置处
使用ThinkPHP自带的D函数自动装载对应目录下类库,部分类库需要增加自定义的autoload功能。
- 接口类iServer:
// 服务器列表接口 Interface iServer { // 获取今日开服列表 public function fetchTodayOpeningList($limit); // 获取今日测试列表 public function fetchTodayTestingList($limit); }
Copy after login - CacheKeyCreater 创建Key类:
/** * APP Cache Key生成类 * * @category Core * @package Core * @author Lancer <lancer.he> * @since 2014-03-06 */ Final Class CacheKeyCreater { public static function create($class, $func, $args) { $cachekey_class = $class . 'CacheKey'; return call_user_func_array(array($cachekey_class, $func), $args); } }</lancer.he>
Copy after login - Cache抽象基类BaseCache:
/** * APP Cache层抽象基类 * * @category Core * @package Core * @author Lancer <lancer.he> * @since 2014-03-06 */ Abstract Class BaseCache { public function __call($func, $args) { $class = str_replace('Cache', '', get_class($this) ); $key = CacheKeyCreater::create($class, $func, $args); return Cache::getInstance()->get($key); } }</lancer.he>
Copy after login - Service抽象基类BaseService:
/** * APP Service层抽象基类,自动选择Cache层/Store层 * * @category Core * @package Core * @author Lancer <lancer.he> * @since 2014-03-06 */ Abstract Class BaseService { protected $_cache_handler; protected $_db_handler; protected $_class_name; protected $_layer_cache_tag = 'Cache';//Cache类放在Cache目录下 protected $_layer_store_tag = 'Store';//Store类放在Store对象下 public function __construct() { $this->_class_name = str_replace('Service', '', get_class($this) ); $this->_cache_handler = D($this->_class_name, $this->_layer_cache_tag); $this->_db_handler = D($this->_class_name, $this->_layer_store_tag); } public function ttl($ttl) { $this->_cache_handler->ttl = $ttl; return $this; } public function nocache() { return $this->_db_handler; } public function __call($func, $args) { $data_from_cache = call_user_func_array(array($this->_cache_handler, $func), $args); if ( FALSE !== $data_from_cache ) return $data_from_cache; $data = call_user_func_array(array($this->_db_handler, $func), $args); $key = CacheKeyCreater::create($this->_class_name, $func, $args); Cache::getInstance()->set($key, $data, $this->_cache_handler->ttl ); return $data; } }</lancer.he>
Copy after login - ServeStore:必须实现接口iServer的方法
/** * 服务器数据读取层 * * @category Cache * @package Core * @author Lancer <lancer.he> * @since 2014-03-02 */ Class ServerStore implements iServer{ public function fetchTodayOpeningList($limit=4) { $condition = array('status' => 1, 'audit' => 1 'opendate' => date('Y-m-d') ); return D('Server')->order('opentime DESC') ->where($condition) ->limit($limit); ->getField('id, game_id, name, opentime, statusval'); } public function fetchTodayTestingList($limit=4) { $condition = array('status' => 0, 'audit' => 1 'opendate' => date('Y-m-d') ); $pagination = array('limit' => 4); return D('Server')->order('opentime DESC') ->where($condition) ->limit($limit); ->getField('id, game_id, name, opentime, statusval'); } }</lancer.he>
Copy after login - ServerCacheKey:必须实现接口iServer的方法
/** * 服务器列表缓存键类 * * @category CacheKey * @package Extend * @author Lancer <lancer.he> * @since 2014-03-02 */ Class ServerCacheKey implements iServer { public function fetchTodayOpeningList($limit) { return 'ServerTodayOpeningList:' . $limit; } public function fetchTodayTestingList($limit) { return 'ServerTodayTestingList:' . $limit; } }</lancer.he>
Copy after login - ServerService / ServerCache 由于集成父类,目前暂时是空的:
Class ServerCache extends BaseCache {} Class ServerService extends BaseService {}
Copy after login
测试过程:将设计的程序,我们大胆的在Action下做一个测试:
Class TestAction extends BaseAction { public function cachetest() { D('Server', 'Service')->ttl(600)->fetchTodayTestingList(4); D('Server', 'Service')->nocache()->fetchTodayOpeningList(4); $this->show(' '); } }
测试结果:访问页面后在Memadmin查询:
测试结果:利用ThinkPHP自带的追踪SQL记录来查看下效果:
确实只有一条SQL查询,证明我们的设计和程序逻辑是没有问题。
五. 小结
通过设计模式分离出一个数据缓存层,解决了以下的问题:
- 其他关联模块中可以通过CacheKeyCreater的方式直接获取到Key进行操作处理;
- 单元测试时可以自主选择使用缓存层或是数据层进行测试;
- 通过接口的规数据访问层和数据缓存层的方法,一旦修改出错,必然抛出致命错误,提高开发人员在项目初期对接口进行规范设计的技能,减少对数据层的修改。
原文地址:服务端需要一个新的分层 -“数据缓存层”, 感谢原作者分享。

Hot AI Tools

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Undress AI Tool
Undress images for free

Clothoff.io
AI clothes remover

Video Face Swap
Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Article

Hot Tools

Notepad++7.3.1
Easy-to-use and free code editor

SublimeText3 Chinese version
Chinese version, very easy to use

Zend Studio 13.0.1
Powerful PHP integrated development environment

Dreamweaver CS6
Visual web development tools

SublimeText3 Mac version
God-level code editing software (SublimeText3)

Hot Topics



How to implement dual WeChat login on Huawei mobile phones? With the rise of social media, WeChat has become one of the indispensable communication tools in people's daily lives. However, many people may encounter a problem: logging into multiple WeChat accounts at the same time on the same mobile phone. For Huawei mobile phone users, it is not difficult to achieve dual WeChat login. This article will introduce how to achieve dual WeChat login on Huawei mobile phones. First of all, the EMUI system that comes with Huawei mobile phones provides a very convenient function - dual application opening. Through the application dual opening function, users can simultaneously

The programming language PHP is a powerful tool for web development, capable of supporting a variety of different programming logics and algorithms. Among them, implementing the Fibonacci sequence is a common and classic programming problem. In this article, we will introduce how to use the PHP programming language to implement the Fibonacci sequence, and attach specific code examples. The Fibonacci sequence is a mathematical sequence defined as follows: the first and second elements of the sequence are 1, and starting from the third element, the value of each element is equal to the sum of the previous two elements. The first few elements of the sequence

How to implement the WeChat clone function on Huawei mobile phones With the popularity of social software and people's increasing emphasis on privacy and security, the WeChat clone function has gradually become the focus of people's attention. The WeChat clone function can help users log in to multiple WeChat accounts on the same mobile phone at the same time, making it easier to manage and use. It is not difficult to implement the WeChat clone function on Huawei mobile phones. You only need to follow the following steps. Step 1: Make sure that the mobile phone system version and WeChat version meet the requirements. First, make sure that your Huawei mobile phone system version has been updated to the latest version, as well as the WeChat App.

What is the correct way to restart a service in Linux? When using a Linux system, we often encounter situations where we need to restart a certain service, but sometimes we may encounter some problems when restarting the service, such as the service not actually stopping or starting. Therefore, it is very important to master the correct way to restart services. In Linux, you can usually use the systemctl command to manage system services. The systemctl command is part of the systemd system manager

In today's software development field, Golang (Go language), as an efficient, concise and highly concurrency programming language, is increasingly favored by developers. Its rich standard library and efficient concurrency features make it a high-profile choice in the field of game development. This article will explore how to use Golang for game development and demonstrate its powerful possibilities through specific code examples. 1. Golang’s advantages in game development. As a statically typed language, Golang is used in building large-scale game systems.

Implementing exact division operations in Golang is a common need, especially in scenarios involving financial calculations or other scenarios that require high-precision calculations. Golang's built-in division operator "/" is calculated for floating point numbers, and sometimes there is a problem of precision loss. In order to solve this problem, we can use third-party libraries or custom functions to implement exact division operations. A common approach is to use the Rat type from the math/big package, which provides a representation of fractions and can be used to implement exact division operations.

PHP Game Requirements Implementation Guide With the popularity and development of the Internet, the web game market is becoming more and more popular. Many developers hope to use the PHP language to develop their own web games, and implementing game requirements is a key step. This article will introduce how to use PHP language to implement common game requirements and provide specific code examples. 1. Create game characters In web games, game characters are a very important element. We need to define the attributes of the game character, such as name, level, experience value, etc., and provide methods to operate these

Title: Methods and specific code examples to solve the problem that the PHP service cannot start normally under Ubuntu. When using Ubuntu to build a website or application, you often encounter the problem that the PHP service cannot start normally, which will cause the website to be unable to be accessed normally or the application to be unable to function normally. run. This article will introduce how to solve the problem that the PHP service cannot start normally under Ubuntu, and provide specific code examples to help readers quickly solve such failures. 1. Check the PHP configuration file First, we need to check the PHP configuration file
