服务端需要一个新的分层 -“数据缓存层”
— 实现读取缓存在数据访问层中抽离策略 一. 背景 使用数据缓存在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); }
Nach dem Login kopieren - 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>
Nach dem Login kopieren - 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>
Nach dem Login kopieren - 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>
Nach dem Login kopieren - 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>
Nach dem Login kopieren - 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>
Nach dem Login kopieren - ServerService / ServerCache 由于集成父类,目前暂时是空的:
Class ServerCache extends BaseCache {} Class ServerService extends BaseService {}
Nach dem Login kopieren
测试过程:将设计的程序,我们大胆的在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进行操作处理;
- 单元测试时可以自主选择使用缓存层或是数据层进行测试;
- 通过接口的规数据访问层和数据缓存层的方法,一旦修改出错,必然抛出致命错误,提高开发人员在项目初期对接口进行规范设计的技能,减少对数据层的修改。
原文地址:服务端需要一个新的分层 -“数据缓存层”, 感谢原作者分享。

Heiße KI -Werkzeuge

Undresser.AI Undress
KI-gestützte App zum Erstellen realistischer Aktfotos

AI Clothes Remover
Online-KI-Tool zum Entfernen von Kleidung aus Fotos.

Undress AI Tool
Ausziehbilder kostenlos

Clothoff.io
KI-Kleiderentferner

Video Face Swap
Tauschen Sie Gesichter in jedem Video mühelos mit unserem völlig kostenlosen KI-Gesichtstausch-Tool aus!

Heißer Artikel

Heiße Werkzeuge

Notepad++7.3.1
Einfach zu bedienender und kostenloser Code-Editor

SublimeText3 chinesische Version
Chinesische Version, sehr einfach zu bedienen

Senden Sie Studio 13.0.1
Leistungsstarke integrierte PHP-Entwicklungsumgebung

Dreamweaver CS6
Visuelle Webentwicklungstools

SublimeText3 Mac-Version
Codebearbeitungssoftware auf Gottesniveau (SublimeText3)

Heiße Themen



Wie implementiert man die doppelte WeChat-Anmeldung auf Huawei-Mobiltelefonen? Mit dem Aufkommen der sozialen Medien ist WeChat zu einem unverzichtbaren Kommunikationsmittel im täglichen Leben der Menschen geworden. Viele Menschen können jedoch auf ein Problem stoßen: Sie können sich gleichzeitig auf demselben Mobiltelefon bei mehreren WeChat-Konten anmelden. Für Huawei-Mobiltelefonbenutzer ist es nicht schwierig, eine doppelte WeChat-Anmeldung zu erreichen. In diesem Artikel wird erläutert, wie eine doppelte WeChat-Anmeldung auf Huawei-Mobiltelefonen erreicht wird. Erstens bietet das EMUI-System, das mit Huawei-Mobiltelefonen geliefert wird, eine sehr praktische Funktion – das doppelte Öffnen von Anwendungen. Durch die doppelte Öffnungsfunktion der Anwendung können Benutzer gleichzeitig

Die Programmiersprache PHP ist ein leistungsstarkes Werkzeug für die Webentwicklung, das eine Vielzahl unterschiedlicher Programmierlogiken und Algorithmen unterstützen kann. Unter diesen ist die Implementierung der Fibonacci-Folge ein häufiges und klassisches Programmierproblem. In diesem Artikel stellen wir vor, wie Sie die Fibonacci-Folge mit der Programmiersprache PHP implementieren, und fügen spezifische Codebeispiele bei. Die Fibonacci-Folge ist eine mathematische Folge, die wie folgt definiert ist: Das erste und das zweite Element der Folge sind 1, und ab dem dritten Element ist der Wert jedes Elements gleich der Summe der beiden vorherigen Elemente. Die ersten paar Elemente der Sequenz

So implementieren Sie die WeChat-Klonfunktion auf Huawei-Mobiltelefonen Mit der Popularität sozialer Software und der zunehmenden Bedeutung von Datenschutz und Sicherheit rückt die WeChat-Klonfunktion allmählich in den Mittelpunkt der Aufmerksamkeit der Menschen. Die WeChat-Klonfunktion kann Benutzern helfen, sich gleichzeitig bei mehreren WeChat-Konten auf demselben Mobiltelefon anzumelden, was die Verwaltung und Nutzung erleichtert. Es ist nicht schwierig, die WeChat-Klonfunktion auf Huawei-Mobiltelefonen zu implementieren. Sie müssen lediglich die folgenden Schritte ausführen. Schritt 1: Stellen Sie sicher, dass die Version Ihres Mobiltelefonsystems und die WeChat-Version den Anforderungen entsprechen. Stellen Sie zunächst sicher, dass die Version Ihres Huawei-Mobiltelefonsystems sowie die WeChat-App auf die neueste Version aktualisiert wurden.

Was ist der richtige Weg, einen Dienst unter Linux neu zu starten? Wenn wir ein Linux-System verwenden, stoßen wir häufig auf Situationen, in denen wir einen bestimmten Dienst neu starten müssen, aber manchmal können beim Neustart des Dienstes Probleme auftreten, z. B. wenn der Dienst nicht tatsächlich gestoppt oder gestartet wird. Daher ist es sehr wichtig, die richtige Methode zum Neustarten von Diensten zu beherrschen. Unter Linux können Sie normalerweise den Befehl systemctl verwenden, um Systemdienste zu verwalten. Der Befehl systemctl ist Teil des systemd-Systemmanagers

Die Implementierung exakter Divisionsoperationen in Golang ist ein häufiger Bedarf, insbesondere in Szenarien mit Finanzberechnungen oder anderen Szenarien, die hochpräzise Berechnungen erfordern. Der in Golang integrierte Divisionsoperator „/“ wird für Gleitkommazahlen berechnet, und manchmal besteht das Problem eines Präzisionsverlusts. Um dieses Problem zu lösen, können wir Bibliotheken von Drittanbietern oder benutzerdefinierte Funktionen verwenden, um exakte Divisionsoperationen zu implementieren. Ein gängiger Ansatz ist die Verwendung des Rat-Typs aus dem Paket math/big, der eine Darstellung von Brüchen bereitstellt und zur Implementierung exakter Divisionsoperationen verwendet werden kann.

Im heutigen Bereich der Softwareentwicklung wird Golang (Go-Sprache) als effiziente, prägnante und hochgradig parallele Programmiersprache von Entwicklern zunehmend bevorzugt. Seine umfangreiche Standardbibliothek und die effizienten Parallelitätsfunktionen machen es zu einer hochkarätigen Wahl im Bereich der Spieleentwicklung. In diesem Artikel wird untersucht, wie man Golang für die Spieleentwicklung verwendet, und seine leistungsstarken Möglichkeiten anhand spezifischer Codebeispiele demonstriert. 1. Golangs Vorteile bei der Spieleentwicklung: Als statisch typisierte Sprache wird Golang beim Aufbau großer Spielsysteme verwendet.

Implementierungsleitfaden für PHP-Spielanforderungen Mit der Popularität und Entwicklung des Internets erfreut sich der Markt für Webspiele immer größerer Beliebtheit. Viele Entwickler hoffen, die PHP-Sprache zur Entwicklung ihrer eigenen Webspiele nutzen zu können, und die Umsetzung der Spielanforderungen ist ein wichtiger Schritt. In diesem Artikel wird erläutert, wie Sie mithilfe der PHP-Sprache allgemeine Spielanforderungen implementieren und spezifische Codebeispiele bereitstellen. 1. Spielfiguren erstellen In Webspielen sind Spielfiguren ein sehr wichtiges Element. Wir müssen die Attribute des Spielcharakters wie Name, Level, Erfahrungswert usw. definieren und Methoden für deren Bedienung bereitstellen

Titel: Methoden und spezifische Codebeispiele zur Lösung des Problems, dass der PHP-Dienst unter Ubuntu nicht normal starten kann. Wenn Sie Ubuntu zum Erstellen einer Website oder Anwendung verwenden, tritt häufig das Problem auf, dass der PHP-Dienst nicht normal starten kann, was dazu führt, dass die Website nicht normal startet Es kann nicht normal darauf zugegriffen werden oder die Anwendung kann nicht normal ausgeführt werden. In diesem Artikel wird erläutert, wie das Problem gelöst werden kann, dass der PHP-Dienst unter Ubuntu nicht normal gestartet werden kann, und es werden spezifische Codebeispiele bereitgestellt, die den Lesern helfen, solche Fehler schnell zu beheben. 1. Überprüfen Sie die PHP-Konfigurationsdatei. Zuerst müssen wir die PHP-Konfigurationsdatei überprüfen
