• modulejs的导入
  • Require函数详解
  • module路径解析

module.js的导入

module.js是由node.js在Nodejs程序启动的时候导入的。module.js中使用的require函数是在node.js文件中定义的。 具体的代码在node.js中,代码分为两段:

      // 程序启动时候,会利用NativeModule去require我们的module.js函数
// 下面会详细介绍NativeModule
var Module = NativeModule.require('module');
.............(省去)
} else {
// Main entry point into most programs:
// 这个就是文档中所说的主模块问题,也就是利用node app.js启动程序时候,
// app.js就是主模块,也就是module.js里面定义的runMain函数。
Module.runMain();
}

下面详细说明NativeModule的使用

    // 对象构造函数
function NativeModule(id) {
// 文件名,自动加上了js后缀,说明其仅仅解析js的module文件
this.filename = id + '.js';
// 用于记录本模块的标志符
this.id = id;
// 模块的导出对象
this.exports = {};
// 是否导出标志符
this.loaded = false;
}
// 内部的模块, 具体可以查看 process.binding的用法
NativeModule._source = process.binding('natives');
// 用于做NativeModule的内部缓存
NativeModule._cache = {};
// 这个是在module.js中使用的require函数
NativeModule.require = function(id) {
// 特殊处理,如果是native_module,直接返回
if (id == 'native_module') {
return NativeModule;
}
// 查看是否有缓存,
var cached = NativeModule.getCached(id);
if (cached) {
// 如果有,直接返回导出的对象
return cached.exports;
}
// 是否在NativeModule中存在
if (!NativeModule.exists(id)) {
throw new Error('No such native module ' + id);
}
// 这个应该是和C/C++的系统模块交互
process.moduleLoadList.push('NativeModule ' + id);
// 生成一个新的NativeModule对象
var nativeModule = new NativeModule(id);
// 做缓存
nativeModule.cache();
// 编译模块
nativeModule.compile();
// 导出模块的对象
return nativeModule.exports;
};
// 查找是否有缓存
NativeModule.getCached = function(id) {
return NativeModule._cache[id];
}
// 查找id是否存在,从代码上可以看出NativeModule要求在c/c++代码中有体现
NativeModule.exists = function(id) {
return NativeModule._source.hasOwnProperty(id);
}
// 获取source
NativeModule.getSource = function(id) {
return NativeModule._source[id];
}
// 对于script的封装,这个是后续理解module,exports等的关键。任何的require
//一个文件或者模块其实就是将文件里面的内容,封装成一个函数
NativeModule.wrap = function(script) {
return NativeModule.wrapper[0] + script + NativeModule.wrapper[1];
};
// 具体的函数头和尾
NativeModule.wrapper = [
'(function (exports, require, module, __filename, __dirname) { ',
'\n});'
];
//编译
NativeModule.prototype.compile = function() {
var source = NativeModule.getSource(this.id);
source = NativeModule.wrap(source);
// 这个是javascript虚拟机的功能 后续会有详细介绍
var fn = runInThisContext(source, { filename: this.filename });
// 查看上述的wrap 函数定义,也就是说在module.js中使用的module
// 其实是NativeModule对象
// 使用的require函数,其实是NativeModule.require
fn(this.exports, NativeModule.require, this, this.filename);
// 模块已经导入
this.loaded = true;
};
// 增加cache
NativeModule.prototype.cache = function() {
NativeModule._cache[this.id] = this;
};

由于module模块的内容比较多,分如下重点函数进行源码分析,然后再分析帮助函数

Require函数详解

下面是require函数的源码,也就是我们通常用require(./test)时候,这里需要强调的是,require不是关键字,而是一个函数

// Loads a module at the given file path. Returns that module's
// `exports` property.
// 导入模块,并且返回模块的exports属性。
Module.prototype.require = function(path) {
// 参数检查,确保path不为空
assert(path, 'missing path');
// 参数检查,确保path为string类型
assert(util.isString(path), 'path must be a string');
// 直接调用_load函数返回,注意参数多了一个this,也就是本module的对象
return Module._load(path, this);
}; // Check the cache for the requested file.
// 1. If a module already exists in the cache: return its exports object.
// 2. If the module is native: call `NativeModule.require()` with the
// filename and return the result.
// 3. Otherwise, create a new module for the file and save it to the cache.
// Then have it load the file contents before returning its exports
// object.
// 上述的介绍翻译。
// 对要求的文件检查缓存,
// @request就是对应的要导入的file或者目录,
// @parent其实是谁导入了该模块,从上述的源码可以看出,其实是this。
// @isMain 标志是否为主文件,这里只有从node.js 调用的才是,其他都不是。
// 对于缓存,会做下面的事情:
// 1. 如果模块以及存在在缓存中,直接返回。
// 2. 如果模块是native的,直接调用NativeModule.require()并且返回。
// 3. 否则,创建一个新的module对象,保存在缓存中,并且导入文件内容,然后返回exports对象。
Module._load = function(request, parent, isMain) {
// 添加log,看谁导入了当前的模块
if (parent) {
debug('Module._load REQUEST ' + (request) + ' parent: ' + parent.id);
}
//找到当前的需要解析的文件名,以后会详解_resolveFilename
var filename = Module._resolveFilename(request, parent);
// 步骤1:如果已经有的缓存,直接返回缓存的exports
var cachedModule = Module._cache[filename];
if (cachedModule) {
return cachedModule.exports;
}
// 步骤2:如果在自然模块中存在,直接使用自然模块进行解析
if (NativeModule.exists(filename)) {
// REPL is a special case, because it needs the real require.
if (filename == 'repl') {
var replModule = new Module('repl');
replModule._compile(NativeModule.getSource('repl'), 'repl.js');
NativeModule._cache.repl = replModule;
return replModule.exports;
} debug('load native module ' + request);
return NativeModule.require(filename);
}
// 创建Module对象。
var module = new Module(filename, parent);
// 是否为主模块,
if (isMain) {
// 主模块的话,需要将当前的module赋值给process.mainModule
process.mainModule = module;
// 主模块的id特殊的赋值为"."
module.id = '.';
}
// 将创建的模块cache起来
Module._cache[filename] = module;
// 确保是否有异常
var hadException = true;
try {
// 做真正的导入模块的操作,下面会详解该函数
module.load(filename);
hadException = false;
} finally {
// 如果有异常,直接删除上述的缓存
if (hadException) {
delete Module._cache[filename];
}
}
// 返回新创建模块的exports
return module.exports;
}; // Given a file name, pass it to the proper extension handler.
// 指定一个文件名,导入模块,调用适当扩展处理函数,当前主要是js,json,和node
Module.prototype.load = function(filename) {
//增加log,当前导入什么文件,id是什么
debug('load ' + JSON.stringify(filename) +
' for module ' + JSON.stringify(this.id));
// 确保当前模块没有被载入
assert(!this.loaded);
// 赋值当前模块的文件名
this.filename = filename;
// 当前的path
this.paths = Module._nodeModulePaths(path.dirname(filename));
// 当前文件的后缀
var extension = path.extname(filename) || '.js';
// 确认默认的后缀都*.js
if (!Module._extensions[extension]) extension = '.js';
// 根据后缀的解析函数来做解析
Module._extensions[extension](this, filename);
this.loaded = true;
}; 下面是nodejs支持的三种后缀: // Native extension for .js
// js后缀的处理
Module._extensions['.js'] = function(module, filename) {
//直接同步的读入文件的内容。
var content = fs.readFileSync(filename, 'utf8');
// 然后调用_compile进行编译。下面会分析该函数
module._compile(stripBOM(content), filename);
}; // Native extension for .json
// 对于json文件的处理
Module._extensions['.json'] = function(module, filename) {
//直接同步的读入文件的内容。
var content = fs.readFileSync(filename, 'utf8');
try {
// 直接将模块的exports赋值为json文件的内容
module.exports = JSON.parse(stripBOM(content));
} catch (err) {
// 异常处理
err.message = filename + ': ' + err.message;
throw err;
}
};
//node文件的打开处理,通常为C/C++文件。
Module._extensions['.node'] = process.dlopen; 下面就分析最后的一个函数_compile:
// Run the file contents in the correct scope or sandbox. Expose
// the correct helper variables (require, module, exports) to
// the file.
// Returns exception, if any.
// 这个函数会给出require, module, exports等帮助变量给文件
// @content 主要是js文件的主要内容
// @filename 是js文件的文件名
Module.prototype._compile = function(content, filename) {
// self就是一个帮助变量,代表的this
var self = this;
// remove shebang
// 去掉一些注释(shebang)
content = content.replace(/^\#\!.*/, '');
// 其实模块中的require就是这个函数
// 其仅仅是一个对module中的require函数的封装。
function require(path) {
return self.require(path);
}
//resolve 函数,这个会解释文档中的module导入的路径问题,是单个文件,目录还是模块
require.resolve = function(request) {
return Module._resolveFilename(request, self);
};
// 禁止使用require中的paths路径
Object.defineProperty(require, 'paths', { get: function() {
throw new Error('require.paths is removed. Use ' +
'node_modules folders, or the NODE_PATH ' +
'environment variable instead.');
}});
//注意require.main就是主模块
require.main = process.mainModule; // Enable support to add extra extension types
// 将Module._extensions赋值给require
require.extensions = Module._extensions;
require.registerExtension = function() {
throw new Error('require.registerExtension() removed. Use ' +
'require.extensions instead.');
};
//将缓存也赋值给require
// require一会是函数,一会又像是对象,其实都是对象:)
require.cache = Module._cache; // 获取当前的文件的路径
var dirname = path.dirname(filename);
// 当NODE_MODULE_CONTEXTS为1的时候才可以调用,也就是说,所有的模块都在一个环境中,无需
// 模块来,这个好像通常情况下不会被执行。
// Module._contextLoad = (+process.env['NODE_MODULE_CONTEXTS'] > 0);
if (Module._contextLoad) {
if (self.id !== '.') {
debug('load submodule');
// not root module
var sandbox = {};
for (var k in global) {
sandbox[k] = global[k];
}
sandbox.require = require;
sandbox.exports = self.exports;
sandbox.__filename = filename;
sandbox.__dirname = dirname;
sandbox.module = self;
sandbox.global = sandbox;
sandbox.root = root; return runInNewContext(content, sandbox, { filename: filename });
} debug('load root module');
// root module
global.require = require;
global.exports = self.exports;
global.__filename = filename;
global.__dirname = dirname;
global.module = self; return runInThisContext(content, { filename: filename });
} // create wrapper function
// 这里的wrap函数就是node.js中的函数,会将文件中的内容封装成一个函数。
var wrapper = Module.wrap(content);
// 编译内容,返回函数
var compiledWrapper = runInThisContext(wrapper, { filename: filename });
// 处理debug模式,
if (global.v8debug) {
if (!resolvedArgv) {
// we enter the repl if we're not given a filename argument.
if (process.argv[1]) {
resolvedArgv = Module._resolveFilename(process.argv[1], null);
} else {
resolvedArgv = 'repl';
}
} // Set breakpoint on module start
if (filename === resolvedArgv) {
global.v8debug.Debug.setBreakPoint(compiledWrapper, 0, 0);
}
}
// 直接调用wrapper函数,将module模块中的exports,本函数中的require,
//self也就是新创建的module作为参数传递给模块,进行执行。
// filename, dirname作为参数传递过去
// 这就是为什么我们可以直接在module文件中,直接访问exports, module, require函数的原因
var args = [self.exports, require, self, filename, dirname];
return compiledWrapper.apply(self.exports, args);
};

所以,从上面的源码分析中,我们知道了缓存是如何实现的, module中的变量如exports,id,filename是如何能得到访问的。

module路径解析

这里先可以看一下官方文章中的内容:

require(X) from module at path Y
1. If X is a core module, // 如果是核心模块,
a. return the core module // 直接返回核心模块,这里的核心模块是指nodejs下lib的内容
b. STOP 返回
2. If X begins with './' or '/' or '../' // 如果文件以 "./", "/",或者 "../"形式,
a. LOAD_AS_FILE(Y + X) // 导入一个文件 返回,如何处理导入文件
b. LOAD_AS_DIRECTORY(Y + X) // 导入一个目录,返回 如何导入目录,看后面
3. LOAD_NODE_MODULES(X, dirname(Y)) // 导入一个NODE_MODULE,返回。
4. THROW "not found" // 上述都没找到,直接排出没找到的异常。 LOAD_AS_FILE(X) // 导入一个文件按
1. If X is a file, load X as JavaScript text. STOP // 如果X是一个文件,作为js文件导入,直接返回。直接停止
2. If X.js is a file, load X.js as JavaScript text. STOP //加上js后缀为一个文件,直接作为js文件导入 直接停止
3. If X.json is a file, parse X.json to a JavaScript Object. STOP//加上json后缀为一个文件,直接作为json文件导入 直接停止
4. If X.node is a file, load X.node as binary addon. STOP//加上node后缀为一个文件,直接作为c/c++ addon文件导入 直接停止 LOAD_AS_DIRECTORY(X) 如何导入一个目录的处理方式
1. If X/package.json is a file, // 查找X/package.json是否存在
a. Parse X/package.json, and look for "main" field. //查找json file中是否有main
b. let M = X + (json main field) // 生成新的文件
c. LOAD_AS_FILE(M) // 作为文件导入。
2. If X/index.js is a file, load X/index.js as JavaScript text. STOP // 查看是否存在 X/index.js,如果存在,作为js文件导入
3. If X/index.json is a file, parse X/index.json to a JavaScript object. STOP // 查看是否存在 X/index.json,如果存在,导入json文件作为js对象
4. If X/index.node is a file, load X/index.node as binary addon. STOP// 查看是否存在 X/index.node,如果存在,导入c/c++的 addon导入 LOAD_NODE_MODULES(X, START) // 导入node_module的步骤
1. let DIRS=NODE_MODULES_PATHS(START) // 返回一个文件目录内容
2. for each DIR in DIRS: // 对于目录里的内容
a. LOAD_AS_FILE(DIR/X) // 作为文件导入。 作为文件导入,查看LOAD_AS_FILE
b. LOAD_AS_DIRECTORY(DIR/X) // 或者作为一个目录导入, 查看LOAD_AS_DIRECTORY // 请注意,这里仅仅是说还是作为一个具体的文件或者目录,如果有一个找到了,就需要停止,而不是真的要导入文件文件里面的所有内容 NODE_MODULES_PATHS(START) // 具体NODE_MODULES文件目录算法
1. let PARTS = path split(START)
2. let I = count of PARTS - 1
3. let DIRS = []
4. while I >= 0,
a. if PARTS[I] = "node_modules" CONTINUE
c. DIR = path join(PARTS[0 .. I] + "node_modules")
b. DIRS = DIRS + DIR
c. let I = I - 1
5. return DIRS

基本的思想都在文档中做了详细的说明, 那么下面分析源代码是如何实现的。废话少说,直接源代码分析:

// 这个函数就是在load的时候调用的,也就是说其负责具体filename的文件查找。
// 所以,从这里可以看出,无论怎么处理,require仅仅能导入的是一个文件模块,不能是一个目录,
// 有目录的说法,仅仅是因为可以在目录上查找具体的文件。这也就是文档中所说的文件和目录的一一对应关系
Module._resolveFilename = function(request, parent) {
//如果是NativeModule中已经存在,直接用,这个其实就是core 模块,文档中的第一种情况
if (NativeModule.exists(request)) {
return request;
}
// 根据文件名称,调用_resolveLookupPaths来查找具体的文件模块
var resolvedModule = Module._resolveLookupPaths(request, parent);
// 上述返回的id
var id = resolvedModule[0];
// 上述返回的具体的目录文件夹
var paths = resolvedModule[1]; // look up the filename first, since that's the cache key.
// 输出具体的id和paths
debug('looking for ' + JSON.stringify(id) +
' in ' + JSON.stringify(paths));
// 找到具体的文件名称,以返回。
var filename = Module._findPath(request, paths);
// 处理文件找不到的情况
if (!filename) {
var err = new Error("Cannot find module '" + request + "'");
err.code = 'MODULE_NOT_FOUND';
throw err;
}
// 返回具体的文件
return filename;
};
// 从上面的文档可以看出,在处理核心模块后,其是依靠_resolveLookupPaths和_findPath这两个
// 帮助函数来查找具体的文件模块。
// _resolveLookupPaths: 该函数用于查找当前文件“可能的路径”
// _findPath: 根据上述的路径,根据优先级选择一个具体的文件,如官方文档中的文件 Module._resolveLookupPaths = function(request, parent) {
//依然先检查核心模块
if (NativeModule.exists(request)) {
return [request, []];
}
// 查找request的标志符,如果不以"./" 或者'..'开头
var start = request.substring(0, 2);
if (start !== './' && start !== '..') {
// 这种情况直接返回nodejs系统路径modulePaths,这个是在Module._initPaths 函数中设置的,
// 有兴趣可以自己分析一下,很简单的函数
var paths = modulePaths;
if (parent) {
// 设置一下父亲的路径,其实就是谁导入了当前模块
if (!parent.paths) parent.paths = [];
paths = parent.paths.concat(paths);
}
//直接返回
return [request, paths];
} // with --eval, parent.id is not set and parent.filename is null
// 处理父亲模块为空的情况,这种情况我认为一般就是主模块
if (!parent || !parent.id || !parent.filename) {
// make require('./path/to/foo') work - normally the path is taken
// from realpath(__filename) but with eval there is no filename
// 生成新的目录, 在系统目录modulePaths,当前目录和"node_modules"作为候选的路径
// 对于node_modules,可以参考_nodeModulePaths函数。
var mainPaths = ['.'].concat(modulePaths);
mainPaths = Module._nodeModulePaths('.').concat(mainPaths);
return [request, mainPaths];
} // Is the parent an index module?
// We can assume the parent has a valid extension,
// as it already has been accepted as a module.
// 处理父亲模块是否为index模块,
var isIndex = /^index\.\w+?$/.test(path.basename(parent.filename));
var parentIdPath = isIndex ? parent.id : path.dirname(parent.id);
// 返回request中的具体的id
var id = path.resolve(parentIdPath, request); // make sure require('./path') and require('path') get distinct ids, even
// when called from the toplevel js file
// 确保require('./path') and require('path') 的id不一样。应该会做不同的缓存,所以,我们
if (parentIdPath === '.' && id.indexOf('/') === -1) {
id = './' + id;
} debug('RELATIVE: requested:' + request +
' set ID to: ' + id + ' from ' + parent.id);
//返回id和具体的父亲模块的文件夹
//注意: 也就是说,当我们以"./" 等方式require是,都是以当前父模块为对象路径的
return [id, [path.dirname(parent.filename)]];
}; 接下来分析一下:_findPath // 从候选路径中选择
Module._findPath = function(request, paths) {
// 等到具体的文件扩展,现在有js,json 和node
var exts = Object.keys(Module._extensions);
//如果以绝对路径开头,直接重置可选路径,所以这个windows下和linux下应该不一样,
//这点应该是对linux来说的,所以,我们最好不要以/开头导入模块
if (request.charAt(0) === '/') {
paths = [''];
} var trailingSlash = (request.slice(-1) === '/');
// 这里是做文件扫描的cache,防止重复查找
var cacheKey = JSON.stringify({request: request, paths: paths});
if (Module._pathCache[cacheKey]) {
// 如果cache已经有了,直接返回
return Module._pathCache[cacheKey];
} // For each path
// 从每个候选路径中查找文件
for (var i = 0, PL = paths.length; i < PL; i++) {
var basePath = path.resolve(paths[i], request);
var filename; if (!trailingSlash) {
// try to join the request to the path
// 尝试一下当前文件是否存在
filename = tryFile(basePath);
// 尝试一下当前文件加上后缀文件是否存在
if (!filename && !trailingSlash) {
// try it with each of the extensions
filename = tryExtensions(basePath, exts);
}
}
// 尝试一下当前的package.json文件是否存在
if (!filename) {
filename = tryPackage(basePath, exts);
}
// 尝试一下index文件加上后缀是否存在
if (!filename) {
// try it with each of the extensions at "index"
filename = tryExtensions(path.resolve(basePath, 'index'), exts);
} if (filename) {
// 增加到文件缓存中
Module._pathCache[cacheKey] = filename;
return filename;
}
}
return false;
}; // 所以从这里可以看出,对于具体的文件的优先级:
// 1. 具体文件。
// 2. 加上后缀。
// 3. package.json
// 4 index加上后缀
// 候选路径以当前文件夹,nodejs系统文件夹和node_module中的文件夹为候选,以上述顺序找到任意一个,
// 就直接返回

还有一些帮助函数,如readPackage, tryPackage,tryFile,_initPaths没有做详细的说明。

上述就是module的全部重要的源码说明。

Nodejs源码解析之module的更多相关文章

  1. seajs1.3.0源码解析之module依赖有序加载

    /** * The core of loader */ ;(function(seajs, util, config) { // 模块缓存 var cachedModules = {} // 接口修改 ...

  2. jQuery2.x源码解析(设计篇)

    jQuery2.x源码解析(构建篇) jQuery2.x源码解析(设计篇) jQuery2.x源码解析(回调篇) jQuery2.x源码解析(缓存篇) 这一篇笔者主要以设计的角度探索jQuery的源代 ...

  3. JQuery源码解析(一)

    写在前面:本<JQuery源码解析>系列是基于一些前辈们的文章进行进一步的分析.细化.修改而写出来的,在这边感谢那些慷慨提供科普文档的技术大拿们. 要查阅JQ的源文件请下载开发版的JQ.j ...

  4. gulp源码解析(一)—— Stream详解

    作为前端,我们常常会和 Stream 有着频繁的接触.比如使用 gulp 对项目进行构建的时候,我们会使用 gulp.src 接口将匹配到的文件转为 stream(流)的形式,再通过 .pipe() ...

  5. jQuery2.x源码解析(构建篇)

    jQuery2.x源码解析(构建篇) jQuery2.x源码解析(设计篇) jQuery2.x源码解析(回调篇) jQuery2.x源码解析(缓存篇) 笔者阅读了园友艾伦 Aaron的系列博客< ...

  6. Koa2 源码解析(1)

    Koa2 源码解析 其实本来不想写这个系列文章的,因为Koa本身很精简,一共就4个文件,千十来行代码. 但是因为想写 egg[1] 的源码解析,而egg是基于Koa2的,所以就先写个Koa2的吧,用作 ...

  7. iview源码解析(1)

    概述 公司技术栈开始用vue主导开发,但因为公司前端会vue的不多所以在项目中用到vue的技术不是很深,之前出去面试被接连打击,而且本来打算开始为公司vue的项目构建自己的组件库所以去下载了iview ...

  8. 异步任务spring @Async注解源码解析

    1.引子 开启异步任务使用方法: 1).方法上加@Async注解 2).启动类或者配置类上@EnableAsync 2.源码解析 虽然spring5已经出来了,但是我们还是使用的spring4,本文就 ...

  9. Redux系列x:源码解析

    写在前面 redux的源码很简洁,除了applyMiddleware比较绕难以理解外,大部分还是 这里假设读者对redux有一定了解,就不科普redux的概念和API啥的啦,这部分建议直接看官方文档. ...

随机推荐

  1. windows端口占用处理方法

    (1)输入命令:netstat -ano,列出所有端口的情况.在列表中我们观察被占用的端口,比如是8081,首先找到它.C:\Users\Administrator>netstat -ano活动 ...

  2. css书写规范 & 页面布局规范 &常用案例经验总结

    CSS 属性书写顺序(重点) 建议遵循以下顺序: 布局定位属性:display / position / float / clear / visibility / overflow(建议 displa ...

  3. Spring Cloud调用接口过程

    Spring Cloud 在接口调用上,大致会经过如下几个组件配合: Feign== >Hystrix ==>Ribbon ==>Http Client(apache http co ...

  4. VIM处理工具与正则表达式

    *本文中/data目录为训练目录 1.在vim中设置TAB缩进为四个字符 打开vim 输入:set tabstop=4 2.复制/etc/rc.d/init.d/functions文件至/tmp/,替 ...

  5. .NET Conf 2021 正在进行中,带你看一看微软带来了什么内容

    今年最大的.NET活动正在进行, 可以通过Channel9 https://channel9.msdn.com/Events/dotnetConf/2021 看具体的Session .微软和社区一直在 ...

  6. maven中的distributionManagement的作用

    mvn install  会将项目生成的构件安装到本地Maven仓库,mvn deploy 用来将项目生成的构件分发到远程Maven仓库. 本地Maven仓库的构件只能供当前用户使用,在分发到远程Ma ...

  7. Part 29 AngularJS intellisense in visual studio

    In the previous videos if you have noticed as we were typing the angular code in Script.js file we w ...

  8. 后台大哥请进一步:使用Visual Studio编译scss和souce map实现前后端的完美结合

    title: 后台大哥请进一步:使用Visual Studio编译scss和souce map实现前后端的完美结合 date: 2020-06-28 sidebarDepth: 2 tags: win ...

  9. 面试官:咱们来聊一聊mysql主从延迟

    背景 前段时间遇到一个线上问题,后来排查好久发现是因为主从同步延迟导致的,所以今天写一篇文章总结一下这个问题希望对你有用.如果觉得还不错,记得加个关注点个赞哦 思维导图 思维导图 常见的主从架构 随着 ...

  10. inux 下配置网卡的别名即网卡子IP的配置 转

    what 什么是ip别名?用windows的话说,就是为一个网卡配置多个ip.when 什么场合增加ip别名能派上用场?布网需要.多ip访问测试.特定软件对多ip的需要...and so on. ho ...