转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具、解决方案和服务,赋能开发者。
在文章的开始我们需要了解什么是缓存?缓存是预先根据数据列表准备一些重要数据。
没有缓存的话,系统的吞吐量就取决于存储速度最慢的数据,因此保持应用程序高性能的一个重要优化就是缓存。
web应用程序中有两项很重要的工作,分别是文件和视频Blob的缓存和快速访问页面模板。而在NodeJS中,非异步功能操作的延迟会决定系统什么时候为其他客户端提供服务,尽管操作系统有自己的文件缓存机制,但是同一个服务器中有多个web应用程序同时运行,且其中一个应用正在传输大量视频数据的时候,其他应用的缓存内容就可能会频繁失效,此时程序效率会大幅降低。
而针对应用程序资源的LRU算法能有效解决这个问题,使应用程序不被同一服务器中的其他应用程序缓存所影响。考虑到存储速度最慢数据决系统吞吐量的这一点,LRU缓存的存在能将系统性能提高2倍至100倍;同时,异步LRU会隐藏全部高速缓存未命中的延迟。
接下来我们一起来看具体实现的内容。
'use strict';
let Lru = function(cacheSize,callbackBackingStoreLoad,elementLifeTimeMs=1000){
let me = this;
let maxWait = elementLifeTimeMs;
let size = parseInt(cacheSize,10);
let mapping = {};
let mappingInFlightMiss = {};
let buf = [];
for(let i=0;i<size;i++)
{
let rnd = Math.random();
mapping[rnd] = i;
buf.push({data:"",visited:false, key:rnd, time:0, locked:false});
}
let ctr = 0;
let ctrEvict = parseInt(cacheSize/2,10);
let loadData = callbackBackingStoreLoad;
this.get = function(key,callbackPrm){
let callback = callbackPrm;
if(key in mappingInFlightMiss)
{
setTimeout(function(){
me.get(key,function(newData){
callback(newData);
});
},0);
return;
}
if(key in mapping)
{
// RAM speed data
if((Date.now() - buf[mapping[key]].time) > maxWait)
{
if(buf[mapping[key]].locked)
{
setTimeout(function(){
me.get(key,function(newData){
callback(newData);
});
},0);
}
else
{
delete mapping[key];
me.get(key,function(newData){
callback(newData);
});
}
}
else
{
buf[mapping[key]].visited=true;
buf[mapping[key]].time = Date.now();
callback(buf[mapping[key]].data);
}
}
else
{
// datastore loading + cache eviction
let ctrFound = -1;
while(ctrFound===-1)
{
if(!buf[ctr].locked && buf[ctr].visited)
{
buf[ctr].visited=false;
}
ctr++;
if(ctr >= size)
{
ctr=0;
}
if(!buf[ctrEvict].locked && !buf[ctrEvict].visited)
{
// evict
buf[ctrEvict].locked = true;
ctrFound = ctrEvict;
}
ctrEvict++;
if(ctrEvict >= size)
{
ctrEvict=0;
}
}
mappingInFlightMiss[key]=true;
let f = function(res){
delete mapping[buf[ctrFound].key];
buf[ctrFound] =
{data: res, visited:false, key:key, time:Date.now(), locked:false};
mapping[key] = ctrFound;
callback(buf[ctrFound].data);
delete mappingInFlightMiss[key];
};
loadData(key,f);
}
};
};
exports.Lru = Lru;
let Lru = require("./lrucache.js").Lru;
let fs = require("fs");
let path = require("path");
let fileCache = new Lru(500, async function(key,callback){
// cache-miss data-load algorithm
fs.readFile(path.join(__dirname,key),function(err,data){
if(err) {
callback({stat:404, data:JSON.stringify(err)});
}
else
{
callback({stat:200, data:data});
}
});
},1000 /* cache element lifetime */);
使用LRU构造函数获取参数(高速缓存大小、高速缓存未命中的关键字和回调、高速缓存要素生命周期)来构造CLOCK高速缓存。
fileCache.get("./test.js",function(dat){
httpResponse.writeHead(dat.stat);
httpResponse.end(dat.data);
});
结果数据还有另一个回调,因此可以异步运行
let mapping = {};
在映射中找到一个(字符串/整数)键:
if(key in mapping)
{
// key found, get data from RAM
}
高效且简单
buf[mapping[key]].visited=true;
buf[mapping[key]].time = Date.now();
callback(buf[mapping[key]].data);
visited用来通知CLOCK指针(ctr和ctrEvict)保存该插槽,避免它被驱逐。time字段用来管理插槽的生命周期。只要访问到高速缓存命中都会更新time字段,把它保留在高速缓存中。
用户使用callback函数给get()函数提供用于检索高速缓存插槽的数据。
if((Date.now() - buf[mapping[key]].time) > maxWait)
{
delete mapping[key];
me.get(key,function(newData){
callback(newData);
});
}
删除映射后其他异步访问不会再影响其内部状态
let ctrFound = -1;
while(ctrFound===-1)
{
if(!buf[ctr].locked && buf[ctr].visited)
{
buf[ctr].visited=false;
}
ctr++;
if(ctr >= size)
{
ctr=0;
}
if(!buf[ctrEvict].locked && !buf[ctrEvict].visited)
{
// evict
buf[ctrEvict].locked = true;
ctrFound = ctrEvict;
}
ctrEvict++;
if(ctrEvict >= size)
{
ctrEvict=0;
}
}
第一个“ if”块检查“第二次机会”指针(ctr)指向的插槽状态,如果是未锁定并已访问会将其标记为未访问,而不是驱逐它。
第三“If”块检查由ctrEvict指针指向的插槽状态,如果是未锁定且未被访问,则将该插槽标记为“ locked”,防止异步访问get() 方法,并找到逐出插槽,然后循环结束。
对比可以发现ctr和ctrEvict的初始相位差为50%:
let ctr = 0;
let ctrEvict = parseInt(cacheSize/2,10);
并且在“ while”循环中二者均等递增。这意味着,这二者循环跟随另一方,互相检查。高速缓存插槽越多,对目标插槽搜索越有利。对每个键而言,每个键至少停留超过N / 2个时针运动才从从逐出中保存。
mappingInFlightMiss[key]=true;
let f = function(res){
delete mapping[buf[ctrFound].key];
buf[ctrFound] = {data: res, visited:false, key:key, time:Date.now(), locked:false};
mapping[key] = ctrFound;
callback(buf[ctrFound].data);
delete mappingInFlightMiss[key];
};
loadData(key,f);
由于用户提供的缓存缺失数据存储加载功能(loadData)可以异步进行,所以该缓存在运行中最多可以包含N个缓存缺失,最多可以隐藏N个缓存未命中延迟。隐藏延迟是影响吞吐量高低的重要因素,这一点在web应用中尤为明显。一旦应用中出现了超过N个异步缓存未命中/访问就会导致死锁,因此具有100个插槽的缓存可以异步服务多达100个用户,甚至可以将其限制为比N更低的值(M),并在多次(K)遍中进行计算(其中M x K =总访问次数)。
我们都知道高速缓存命中就是RAM的速度,但因为高速缓存未命中可以隐藏,所以对于命中和未命中而言,总体性能看起来的时间复杂度都是O(1)。当插槽很少时,每个访问可能有多个时钟指针迭代,但如果增加插槽数时,它接近O(1)。
在此loadData回调中,将新插槽数据的locked字段设置为false,可以使该插槽用于其他异步访问。
if(buf[mapping[key]].locked)
{
setTimeout(function(){
me.get(key,function(newData){
callback(newData);
});
},0);
}
if(key in mappingInFlightMiss)
{
setTimeout(function(){
me.get(key,function(newData){
callback(newData);
});
},0);
return;
}
这样,就可以避免数据的重复。
"use strict";
// number of asynchronous accessors(1000 here) need to be equal to or less than
// cache size(1000 here) or it makes dead-lock
let Lru = require("./lrucache.js").Lru;
let cache = new Lru(1000, async function(key,callback){
// cache-miss data-load algorithm
setTimeout(function(){
callback(key+" processed");
},1000);
},1000 /* cache element lifetime */);
let ctr = 0;
let t1 = Date.now();
for(let i=0;i<1000;i++)
{
cache.get(i,function(data){
console.log("data:"+data+" key:"+i);
if(i.toString()+" processed" !== data)
{
console.log("error: wrong key-data mapping.");
}
if(++ctr === 1000)
{
console.log("benchmark: "+(Date.now()-t1)+" miliseconds");
}
});
}
为了避免死锁的出现,可以将LRU大小选择为1000,或者for只允许循环迭代1000次。
输出:
benchmark: 1127 miliseconds
由于每个高速缓存未命中都有1000毫秒的延迟,因此同步加载1000个元素将花费15分钟,但是重叠的高速缓存未命中会更快。这在I / O繁重的工作负载(例如来自HDD或网络的流数据)中特别有用。
10%的命中率:
密钥生成:随机,可能有10000个不同的值
1000个插槽
"use strict";
// number of asynchronous accessors(1000 here) need to be equal to or less than
// cache size(1000 here) or it makes dead-lock
let Lru = require("./lrucache.js").Lru;
let cacheMiss = 0;
let cache = new Lru(1000, async function(key,callback){
cacheMiss++;
// cache-miss data-load algorithm
setTimeout(function(){
callback(key+" processed");
},100);
},100000000 /* cache element lifetime */);
let ctr = 0;
let t1 = Date.now();
let asynchronity = 500;
let benchRepeat = 100;
let access = 0;
function test()
{
ctr = 0;
for(let i=0;i<asynchronity;i++)
{
let key = parseInt(Math.random()*10000,10); // 10% hit ratio
cache.get(key.toString(),function(data){
access++;
if(key.toString()+" processed" !== data)
{
console.log("error: wrong key-data mapping.");
}
if(++ctr === asynchronity)
{
console.log("benchmark: "+(Date.now()-t1)+" miliseconds");
console.log("cache hit: "+(access - cacheMiss));
console.log("cache miss: "+(cacheMiss));
console.log("cache hit ratio: "+((access - cacheMiss)/access));
if(benchRepeat>0)
{
benchRepeat--;
test();
}
}
});
}
}
test();
benchmark: 10498 miliseconds
cache hit: 6151
cache miss: 44349
cache hit ratio: 0.1218019801980198
由于基准测试是按100个步骤进行的,每个缓存丢失的延迟时间为100毫秒,因此产生了10秒的时间(接近100 x 100毫秒)。命中率接近预期值10%。
let key = parseInt(Math.random()*2000,10); // 50% hit ratio
Result:
benchmark: 10418 miliseconds
cache hit: 27541
cache miss: 22959
cache hit ratio: 0.5453663366336634
let key = parseInt(Math.random()*1010,10); // 99% hit ratio
Result:
benchmark: 10199 miliseconds
cache hit: 49156
cache miss: 1344
cache hit ratio: 0.9733861386138614
结果产生了0.9733比率的键的随机性
let key = parseInt(Math.random()*999,10); // 100% hit ratio
benchmark: 1463 miliseconds
cache hit: 49501
cache miss: 999
cache hit ratio: 0.9802178217821782
基准测试的第一步(无法逃避缓存未命中)之后,所有内容都来自RAM,并大大减少了总延迟。
文本详细介绍了NodeJS中LRU算法缓存的实现,希望可以为大家提供新的思路,更好的在开发中提升系统性能。
Vue 是一套用于构建用户界面的渐进式框架,与其它 JS 框架不同,Vue 被设计为可以自底向上逐层应用,由于其核心库只关注视图层,因此 Vue 更易上手,且很容易与第三方库或既有项目整合,当其与现代化的工具链或各种支持类库相结合时,Vue 也能为复杂的单页应用提供驱动。
SpreadJS?是一款基于 HTML5 的纯前端表格控件,可以以原生的方式嵌入各类应用,并与前后端技术框架相结合。将 SpreadJS 与 Vue 集成,可在 Vue 框架中实现类似 Excel 的电子表格功能,包括对 450 多种计算公式的支持、在线导入导出 Excel 文档、数据透视表和可视化分析,使应用程序具备极高的处理性能和响应速度。
阅读了解Vue框架下的SpreadJS集成。
在使用jquery-multiselect(一个把下拉框改造成带checkbox的可以多选的控件)时...
Dreamweaver CS3设计网页的时候,图像域是网页中必不可少的成份。它能让浏览者展...
基本上快被这个问题搞疯了,症状如下 症状描述:在ie下(6或7,8没有试过)当出...
效果: 轮播图在向一个方向移动的同时,要对其每一个图片的大小,位置,透明度以...
base target=_blank是将基本链接的目标框架都改为新页打开,如果对HTML、CSS和JS...
最近参加公司组织的Node学习小组,每个人认领不同的知识点,并和组内同学分享。...
企业内部H5微应用开发 分为 服务端API和前端API的开发,主要涉及到进入应用免登...
阅读全文你将获得以下解决方案。 点击长文本编辑textarea,自动获得焦点 随着输...
以实例说明Dreamwaver CS 6版本的应用。即使现在用很多有关DreamwaverCS6版本的...
该方法主要用来做网站自适应的,同时可以实现撑起内容高度,避免图片加载后导致...