V8使用JIT去解析JS,在JS代码执行之前,首先要消耗大量的时间在解析和编译中,这会拖慢JS的总执行时间,在2015年V8支持了code cache方案来解决这个问题。
简单来说,使用了code caching后,当JS第一次被编译后,将生成的代码序列化后存储在磁盘上,后续再次加载时,会优先读取磁盘中的缓存,反序列化后执行,避免JS被从头编译造成重复开销。
实质是通过V8提供的几个方法实现的,在node中通过 vm.Script 调用。
v8::ScriptCompiler::kProduceCodeCache 生成code cache
v8::ScriptCompiler::Source::GetCachedData 获取code cache
v8::ScriptCompiler::kConsumeCodeCache 消费生成的code cache
通常浏览器进行资源缓存,存在两种方式,第一种是内存缓存,经过编译之后生成的代码存储在内存当中,
可以在同一个v8实例中被反复使用。第二种就是code cache,对编译之后生成的代码持久化到磁盘。这种方式缓存的结果不会被特定的个v8实例独占,可以在多个实例中使用。但是缺点也相对比较明显,就是会一直占用磁盘空间。所以并不是所有JS都会使用第二个缓存方案,一般初次运行的JS会被存储在内存中,在指定时间被重复多次访问的JS才会被存储到磁盘上。
这个功能首次被介绍是在Node v5.7.0中,可以通过vm.Script使用V8code cache功能,通过加快实例化时间来减少node的启动时间,经典实现可以参照v8-compile-cache。
require('v8-compile-cache')
它的适用方法非常简单,只需要引入即可。让我们来看下v8-compile-cache中做了什么。
if (!process.env.DISABLE_V8_COMPILE_CACHE && supportsCachedData()) {
const cacheDir = getCacheDir();
const prefix = getParentName();
const blobStore = new FileSystemBlobStore(cacheDir, prefix);
const nativeCompileCache = new NativeCompileCache();
nativeCompileCache.setCacheStore(blobStore);
nativeCompileCache.install();
process.once('exit', code => {
if (blobStore.isDirty()) {
blobStore.save();
}
nativeCompileCache.uninstall();
});
}
首先检测环境变量,看用户是否禁用此功能并且判断当前版本是否支持此功能
function getCacheDir() {
// Avoid cache ownership issues on POSIX systems.
const dirname = typeof process.getuid === 'function'
? 'v8-compile-cache-' + process.getuid()
: 'v8-compile-cache';
const version = typeof process.versions.v8 === 'string'
? process.versions.v8
: typeof process.versions.chakracore === 'string'
? 'chakracore-' + process.versions.chakracore
: 'node-' + process.version;
const cacheDir = path.join(os.tmpdir(), dirname, version);
return cacheDir;
}
接着获取写入位置,一般是在/private/var/folders 或者/var/folders路径下,
const blobStore = new FileSystemBlobStore(cacheDir, prefix);
接着建立一个blobStore
class NativeCompileCache {
...
install() {
const self = this;
const hasRequireResolvePaths = typeof require.resolve.paths === 'function';
// 保留原生模块的_compile 方法
this._previousModuleCompile = Module.prototype._compile;
Module.prototype._compile = function(content, filename) {
const mod = this;
function require(id) {
return mod.require(id);
}
// https://github.com/nodejs/node/blob/v10.15.3/lib/internal/modules/cjs/helpers.js#L28
function resolve(request, options) {
return Module._resolveFilename(request, mod, false, options);
}
require.resolve = resolve;
// https://github.com/nodejs/node/blob/v10.15.3/lib/internal/modules/cjs/helpers.js#L37
// resolve.resolve.paths was added in v8.9.0
if (hasRequireResolvePaths) {
resolve.paths = function paths(request) {
return Module._resolveLookupPaths(request, mod, true);
};
}
require.main = process.mainModule;
// Enable support to add extra extension types
require.extensions = Module._extensions;
require.cache = Module._cache;
const dirname = path.dirname(filename);
const compiledWrapper = self._moduleCompile(filename, content);
// We skip the debugger setup because by the time we run, node has already
// done that itself.
const args = [mod.exports, require, mod, filename, dirname, process, global];
return compiledWrapper.apply(mod.exports, args);
};
}
...
}
接着在NativeCompileCache类中,重写了compile方法,提供了新的require方法,其中调用内部moduleCompile方法
_moduleCompile(filename, content) {
...
// create wrapper function
var wrapper = Module.wrap(content);
var invalidationKey = crypto
.createHash('sha1')
.update(content, 'utf8')
.digest('hex');
var buffer = this._cacheStore.get(filename, invalidationKey);
var script = new vm.Script(wrapper, {
filename: filename,
lineOffset: 0,
displayErrors: true,
cachedData: buffer,
produceCachedData: true,
});
if (script.cachedDataProduced) {
// 写入到内存中
this._cacheStore.set(filename, invalidationKey, script.cachedData);
} else if (script.cachedDataRejected) {
this._cacheStore.delete(filename);
}
var compiledWrapper = script.runInThisContext({
filename: filename,
lineOffset: 0,
columnOffset: 0,
displayErrors: true,
});
return compiledWrapper;
}
moduleCompile中主要做了这几件事,创建 wrapper function,然后在_cacheStore中获取内存或磁盘中的存储内容,调用new vm.Script进行编译(并不执行),第一个参数是code string,是我们包装过的代码 wrapper,options为配置项,接着从内存中取已经生成cachedData,cachedData是编译好的code cache,如果没有传入的话,会通过配置produceCachedData:true来生成code cache,并保存到内存中。
var compiledWrapper = script.runInThisContext({
filename: filename,
lineOffset: 0,
columnOffset: 0,
displayErrors: true,
});
const compiledWrapper = self._moduleCompile(filename, content);
const args = [mod.exports, require, mod, filename, dirname, process, global];
return compiledWrapper.apply(mod.exports, args);
接着运行其中的代码,返回compiledWrapper,执行 compiledWrapper.apply(mod.exports, args)时, 对 mod 重新赋值,应用了新的 require 生成了新的 module.exports,通过Module.prototype._load将结果返回给用户。
process.once('exit', code => {
if (blobStore.isDirty()) {
blobStore.save();
}
nativeCompileCache.uninstall();
});
最后在进程退出的时候,如果检测内存中有更新,则持久化到磁盘中。
未开启code cache
开启code cache
以上是babel-core.js和rxjs-bundle.js在开启code cache和关闭code cache的加载数据,相比有较大幅度提升。
code cache通过加快实例化时间来减少node的启动时间,在faas场景下也有重要使用场景,自动缩扩容是K8S的重要特性,面对忽然到来的大流量,需要在极短的时间对函数进行扩容并可以正常相应请求,这就对Node.js 函数的启动时间有了较高的要求,可以在构建阶段直接将代码转换为字节码并打包到镜像中,通过减少编译时间,来提升faas函数的启动时间。
作者:Node地下铁
来自:http://www.sohu.com/a/331337841_657169
浏览器缓存主要分为强强缓存(也称本地缓存)和协商缓存(也称弱缓存),强缓存是利用http头中的Expires和Cache-Control两个字段来控制的,用来表示资源的缓存时间。协商缓存就是由服务器来确定缓存资源是否可用.
一个缓存就是一个组件,它可以透明地存储数据,以便未来可以更快地服务于请求。缓存能够服务的请求越多,整体系统性能就提升得越多。
浏览器缓存就是把一个已经请求过的Web资源(如html页面,图片,js,数据等)拷贝一份副本储存在浏览器中,为什么使用缓存:减少网络带宽消耗,降低服务器压力,减少网络延迟,加快页面打开速度
一个H5页面在APP端,如果勾选已读状态,则下次打开该链接,会跳过此页面。用到了HTML5 的本地存储 API 中的 localStorage作为解决方案,回顾了下Web缓存的知识
在描述CDN的实现原理之前,让我们先看传统的未加缓存服务的访问过程,以便了解CDN缓存访问方式与未加缓存访问方式的差别,用户访问未使用CDN缓存网站的过程为:用户向浏览器提供要访问的域名;
页面打开时,由于缓存的存在,刚刚更新的数据有时无法在页面得到刷新,当这个页面作为模式窗口被打开时问题更为明显, 如何将缓存清掉?
通过在Response Header设置Cache-Control head 信息可以控制浏览器的缓存行为。我们先来看一下Cache-Control可以设置哪些值:缓存头Cache-Control只能在服务端设置,在客户端是由浏览器设置的,自己不能修改它的值。
增量更新是目前大部分团队采用的缓存更新方案,能让用户在无感知的情况获取最新内容。具体实现方式通常是(一般我们通过构建工具来实现,比如webpack):
浏览器会默认缓存网站的静态资源文件,如:js文件、css文件、图片等。缓存带来网站性能提升的同时也带来了一些困扰,最常见的问题就是不能及时更新静态资源,造成新版本发布时用户无法及时看到新版本的变化,严重影响了用户体验。
一个后台管理系统,一个列表页A路由配置需要缓存,另一个页面B里面有多个跳转到A路由的链接。问题描述:首先访问/A?id=1页面,然后到B页面再点击访问A?id=2的页面,发现由于页面A设置了缓存,数据还是id=1的数据,并没有更新。
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!