本文所指TwoQueues缓存模型,是说数据在内存中的缓存模型。
无论何种语言,都可能需要把一部分数据放在内存中,避免重复运算、读取。最常见的场景就是JQuery选择器,有些Dom元素的选取是非常耗时的,我们希望能把这些数据缓存起来,不必每次调用都去重新遍历Dom树。
存就存吧,但总得有个量吧!总不能把所有的历史数据都放在内存中,毕竟目前内存的容量还是相当可怜的,就算内存够大,理论上每个线程分配的内存也是有限制的。
那么问题来了,如何才能高效的把真正有用的数据缓存起来呢?这就涉及到淘汰算法,需要把垃圾数据淘汰掉,才能保住有用的数据。
比较常用的思路有以下几种:
FIFO:就是一个先进先出的队列,最先缓存的数据,最早被淘汰,著名的JQuery框架内部就是用的这种模型。
LRU:双链表结构,每次有新数据存入,直接放在链表头;每次被访问的数据,也转移到链表头,这样一来,链表尾部的数据即是最近没被使用过的,淘汰之。
TwoQueues:FIFO+ LRU,FIFO主要存放初次存入的数据,LRU中存放至少使用过两次的热点数据,此算法命中率高,适应性强,复杂度低。
其他淘汰算法还有很多很多,但实际用的比较多的也就这两种。因为他们本身算法不复杂,容易实现,执行效率高,缓存的命中率在大多数场合也还可以接受。毕竟缓存算法也是需要消耗CPU的,如果太过复杂,虽然命中率有所提高,但得不偿失。试想一下,如果从缓存中取数据,比从原始位置取还消耗时间,要缓存何用?
具体理论就不多说了,网上有的是,我也不怎么懂,今天给大家分享的是JavaScript版的TwoQueues缓存模型。
还是先说说使用方法,很简单。
基本使用方法如下:
[/code]
var tq = initTwoQueues(10);
tq.set("key", "value");
tq.get("key");
[/code]
初始化的时候,指定一下缓存容量即可。需要注意的是,由于内部采用FIFO+LRU实现,所以实际容量是指定容量的两倍,上例指定的是10个(键值对),实际上可以存放20个。
容量大小需要根据实际应用场景而定,太小命中率低,太大效率低,物极必反,需要自己衡量。
在开发过程中,为了审查缓存效果如何,可以将缓存池初始化成开发版:
var tq = initTwoQueues(10, true); tq.hitRatio();
就是在后边加一个参数,直接true就可以了。这样初始化的缓存池,会自动统计命中率,可以通过hitRatio方法获取命中率。如果不加这个参数,hitRatio方法获取的命中率永远为0。
统计命中率肯定要消耗资源,所以生产环境下不建议开启。
是时候分享代码了:
(function(exports){ /** * 继承用的纯净类 * @constructor */ function Fn(){} Fn.prototype = Elimination.prototype; /** * 基于链表的缓存淘汰算法父类 * @param maxLength 缓存容量 * @constructor */ function Elimination(maxLength){ this.container = {}; this.length = 0; this.maxLength = maxLength || 30; this.linkHead = this.buildNode("", ""); this.linkHead.head = true; this.linkTail = this.buildNode("", ""); this.linkTail.tail = true; this.linkHead.next = this.linkTail; this.linkTail.prev = this.linkHead; } Elimination.prototype.get = function(key){ throw new Error("This method must be override!"); }; Elimination.prototype.set = function(key, value){ throw new Error("This method must be override!"); }; /** * 创建链表中的节点 * @param data 节点包含的数据,即缓存数据值 * @param key 节点的唯一标示符,即缓存的键 * @returns {{}} */ Elimination.prototype.buildNode = function(data, key){ var node = {}; node.data = data; node.key = key; node.use = 0; return node; }; /** * 从链表头弹出一个节点 * @returns {*} */ Elimination.prototype.shift = function(){ var node = null; if(!this.linkHead.next.tail){ node = this.linkHead.next; this.linkHead.next = node.next; node.next.prev = this.linkHead; delete this.container[node.key]; this.length--; } return node; }; /** * 从链表头插入一个节点 * @param node 节点对象 * @returns {*} */ Elimination.prototype.unshift = function(node){ node.next = this.linkHead.next; this.linkHead.next.prev = node; this.linkHead.next = node; node.prev = this.linkHead; this.container[node.key] = node; this.length++; return node; }; /** * 从链表尾插入一个节点 * @param node 节点对象 * @returns {*} */ Elimination.prototype.append = function(node){ this.linkTail.prev.next = node; node.prev = this.linkTail.prev; node.next = this.linkTail; this.linkTail.prev = node; this.container[node.key] = node; this.length++; return node; }; /** * 从链表尾弹出一个节点 * @returns {*} */ Elimination.prototype.pop = function(){ var node = null; if(!this.linkTail.prev.head){ node = this.linkTail.prev; node.prev.next = this.linkTail; this.linkTail.prev = node.prev; delete this.container[node.key]; this.length--; } return node; }; /** * 从链表中移除指定节点 * @param node 节点对象 * @returns {*} */ Elimination.prototype.remove = function(node){ node.prev.next = node.next; node.next.prev = node.prev; delete this.container[node.key]; this.length--; return node; }; /** * 节点被访问需要做的处理,具体是把该节点移动到链表头 * @param node */ Elimination.prototype.use = function(node){ this.remove(node); this.unshift(node); }; /** * LRU缓存淘汰算法实现 * @constructor */ function LRU(){ Elimination.apply(this, arguments); } LRU.prototype = new Fn(); LRU.prototype.get = function(key){ var node = undefined; node = this.container[key]; if(node){ this.use(node); } return node; }; LRU.prototype.set = function(key, value){ var node = this.buildNode(value, key); if(this.length === this.maxLength){ this.pop(); } this.unshift(node); }; /** * FIFO缓存淘汰算法实现 * @constructor */ function FIFO(){ Elimination.apply(this, arguments); } FIFO.prototype = new Fn(); FIFO.prototype.get = function(key){ var node = undefined; node = this.container[key]; return node; }; FIFO.prototype.set = function(key, value){ var node = this.buildNode(value, key); if(this.length === this.maxLength){ this.shift(); } this.append(node); }; /** * LRU、FIFO算法封装,成为新的twoqueues缓存淘汰算法 * @param maxLength * @constructor */ function Agent(maxLength){ this.getCount = 0; this.hitCount = 0; this.lir = new FIFO(maxLength); this.hir = new LRU(maxLength); } Agent.prototype.get = function(key){ var node = undefined; node = this.lir.get(key); if(node){ node.use++; if(node.use >= 2){ this.lir.remove(node); this.hir.set(node.key, node.data); } }else{ node = this.hir.get(key); } return node; }; Agent.prototype.getx = function(key){ var node = undefined; this.getCount++; node = this.get(key); if(node){ this.hitCount++; } return node; }; Agent.prototype.set = function(key, value){ var node = null; node = this.lir.container[key] || this.hir.container[key]; if(node){ node.data = value; }else{ this.lir.set(key, value); } }; /** * 获取命中率 * @returns {*} */ Agent.prototype.hitRatio = function(){ var ret = this.getCount; if(ret){ ret = this.hitCount / this.getCount; } return ret; }; /** * 对外接口 * @param maxLength 缓存容量 * @param dev 是否为开发环境,开发环境会统计命中率,反之不会 * @returns {{get, set: Function, hitRatio: Function}} */ exports.initTwoQueues = function(maxLength, dev){ var api = new Agent(maxLength); return { get: (function(){ if(dev){ return function(key){ var ret = api.getx(key); return ret && ret.data; }; }else{ return function(key){ var ret = api.get(key); return ret && ret.data; }; } }()), set: function(){ api.set.apply(api, arguments); }, hitRatio: function(){ return api.hitRatio.apply(api, arguments); } }; }; }(this));
最后,再次提醒,缓存算法需要和实际应用场景相结合,没有万能算法,合适的才是最好的!
在高频的业务场景下,我们可能会频繁的查询数据库获取业务数据,虽然有主键索引的加持,但也不可避免的对数据库性能造成了极大的考验。而对于这种 kv 的查询方式,我们可以很方便的通过使用 模型缓存 来减缓数据库的压力。本组件实现了 Model 数据自动缓存的功能,且当删除和修改模型数据时,自动删除和修改对应的缓存。执行累加、累减操作时,缓存数据自动进行对应累加、累减变更。 模型缓存暂时只支持 Redis
是一个用sqlite查询实现的缓存接口,FlowQueryList, FlowCursorList,或者其他你想使用它的任何地方。 只要增加 cachingEnabled = true在你得@Table注解中就可以启用表的高速缓存。要启用类缓存多列@PrimaryKey,你必须定义一个@MultiCacheField对象(下文解释)。 当查询在数据库运行时,它将在缓存中存储模型的实例,并且缓存是一
问题内容: 哪种方法能让浏览器使用js文件的缓存版本(从服务器端)? 问题答案: 或.htaccess文件中
beego 的 cache 模块是用来做数据缓存的,设计思路来自于 database/sql,目前支持 file、memcache、memory 和 redis 四种引擎,安装方式如下: go get github.com/astaxie/beego/cache 如果你使用memcache 或者 redis 驱动就需要手工安装引入包 go get -u github.com/astaxie/be
7.11. 模板缓存 代码中有一个低效率的地方:每次显示一个页面,renderTemplate都要调用ParseFile。更好的做法是在程序初始化的时候对每个模板调用ParseFile一次,将结果保存为*Template类型的值,在以后使用。 首先,我们创建一个全局map,命名为templates。templates用于储存*Template类型的值,使用string索引。 然后,我们创建一个in
主要内容:1. 概述,2. Cache,3. CacheKey1. 概述 在优化系统性能时,优化数据库性能是非常重要的一个环节,而添加缓存则是优化数据库时最有效的手段之一。正确、合理地使用缓存可以将一部分数据库请求拦截在缓存这一层。 MyBatis 中提供了一级缓存和二级缓存,而这两级缓存都是依赖于基础支持层中的缓 存模块实现的。这里需要读者注意的是,MyBatis 中自带的这两级缓存与 MyBatis 以及整个应用是运行在同一个 JVM 中的,共享同一块堆