nodejs模块系统源码分析

概述

Node.js的出现使得前端工程师可以跨端工作在服务器上,当然,一个新的运行环境的诞生亦会带来新的模块、功能、抑或是思想上的革新,本文将带领读者领略 Node.js(以下简称 Node) 的模块设计思想以及剖析部分核心源码实现。

CommonJS 规范

Node 最初遵循 CommonJS 规范来实现自己的模块系统,同时做了一部分区别于规范的定制。CommonJS 规范是为了解决JavaScript的作用域问题而定义的模块形式,它可以使每个模块在它自身的命名空间中执行。

该规范强调模块必须通过module.exports导出对外的变量或函数,通过require()来导入其他模块的输出到当前模块作用域中,同时,遵循以下约定:

  • 在模块中,必须暴露一个 require 变量,它是一个函数,require 函数接受一个模块标识符,require 返回外部模块的导出的 API。如果要求的模块不能被返回则 require 必须抛出一个错误。
  • 在模块中,必须有一个自由变量叫做 exports,它是一个对象,模块在执行时可以在 exports 上挂载模块的属性。模块必须使用 exports 对象作为唯一的导出方式。
  • 在模块中,必须有一个自由变量 module,它也是一个对象。module 对象必须有一个 id 属性,它是这个模块的顶层 id。id 属性必须是这样的,require(module.id)会从源出module.id的那个模块返回 exports 对象(就是说 module.id 可以被传递到另一个模块,而且在要求它时必须返回最初的模块)。

Node 对 CommonJS 规范的实现

定义了模块内部的 module.require 函数和全局的 require 函数,用来加载模块。

在 Node 模块系统中,每个文件都被视为一个独立的模块。模块被加载时,都会初始化为 Module 对象的实例,Module 对象的基本实现和属性如下所示:

function Module(id = "", parent) {
  // 模块 id,通常为模块的绝对路径
  this.id = id;
  this.path = path.dirname(id);
  this.exports = {};
  // 当前模块调用者
  this.parent = parent;
  updateChildren(parent, this, false);
  this.filename = null;
  // 模块是否加载完成
  this.loaded = false;
  // 当前模块所引用的模块
  this.children = [];
}

每一个模块都对外暴露自己的 exports 属性作为使用接口。

模块导出以及引用

在 Node 中,可使用 module.exports 对象整体导出一个变量或者函数,也可将需要导出的变量或函数挂载到 exports 对象的属性上,代码如下所示:

// 1. 使用 exports: 笔者习惯通常用作对工具库函数或常量的导出
exports.name = 'xiaoxiang';
exports.add = (a, b) => a + b;
// 2. 使用 module.exports:导出一整个对象或者单一函数
...
module.exports = {
  add,
  minus
}

通过全局 require 函数引用模块,可传入模块名称、相对路径或者绝对路径,当模块文件后缀为 js / json / node 时,可省略后缀,如下代码所示:

// 引用模块
const { add, minus } = require('./module');
const a = require('/usr/app/module');
const http = require('http');

注意事项:

exports变量是在模块的文件级作用域内可用的,且在模块执行之前赋值给module.exports。

exports.name = 'test';
console.log(module.exports.name); // test
module.export.name = 'test';
console.log(exports.name); // test

如果为exports赋予了新值,则它将不再绑定到module.exports,反之亦然:

exports = { name: 'test' };
console.log(module.exports.name, exports.name); // undefined, test

]当module.exports属性被新对象完全替换时,通常也需要重新赋值exports:

module.exports = exports = { name: 'test' };
console.log(module.exports.name, exports.name) // test, test

模块系统实现分析模块定位

以下是require函数的代码实现:

// require 入口函数
Module.prototype.require = function(id) {
  //...
  requireDepth++;
  try {
    return Module._load(id, this, /* isMain */ false); // 加载模块
  } finally {
    requireDepth--;
  }
};

上述代码接收给定的模块路径,其中的 requireDepth 用来记载模块加载的深度。其中 Module 的类方法_load实现了 Node 加载模块的主要逻辑,下面我们来解析Module._load函数的源码实现,为了方便大家理解,我把注释加在了文中。

Module._load = function(request, parent, isMain) {
  // 步骤一:解析出模块的全路径
  const filename = Module._resolveFilename(request, parent, isMain);

  // 步骤二:加载模块,具体分三种情况处理
  // 情况一:存在缓存的模块,直接返回模块的 exports 属性
  const cachedModule = Module._cache[filename];
  if (cachedModule !== undefined)
    return cachedModule.exports;
  // 情况二:加载内建模块
  const mod = loadNativeModule(filename, request);
  if (mod && mod.canBeRequiredByUsers) return mod.exports;
  // 情况三:构建模块加载
  const module = new Module(filename, parent);
  // 加载过之后就进行模块实例缓存
  Module._cache[filename] = module;

  // 步骤三:加载模块文件
  module.load(filename);

  // 步骤四:返回导出对象
  return module.exports;
};

加载策略

上面的代码信息量比较大,我们主要看以下几个问题:

模块的缓存策略是什么? 分析上述代码我们可以看到,_load加载函数针对三种情况给出了不同的加载策略,分别是:

  • 情况一:缓存命中,直接返回。
  • 情况二:内建模块,返回暴露出来的 exports 属性,也就是 module.exports 的别名。
  • 情况三:使用文件或第三方代码生成模块,最后返回,并且缓存,这样下次同样的访问就会去使用缓存而不是重新加载。

Module._resolveFilename(request, parent, isMain) 是怎么解析出文件名称的?

我们看如下定义的类方法:

Module._resolveFilename = function(request, parent, isMain, options) {
 if (NativeModule.canBeRequiredByUsers(request)) {
     // 优先加载内建模块
   return request;
 }
 let paths;

 // node require.resolve 函数使用的 options,options.paths 用于指定查找路径
 if (typeof options === "object" && options !== null) {
   if (ArrayIsArray(options.paths)) {
     const isRelative =
       request.startsWith("./") ||
       request.startsWith("../") ||
       (isWindows && request.startsWith(".\\")) ||
       request.startsWith("..\\");
     if (isRelative) {
       paths = options.paths;
     } else {
       const fakeParent = new Module("", null);
       paths = [];
       for (let i = 0; i < options.paths.length; i++) {
         const path = options.paths[i];
         fakeParent.paths = Module._nodeModulePaths(path);
         const lookupPaths = Module._resolveLookupPaths(request, fakeParent);
         for (let j = 0; j < lookupPaths.length; j++) {
           if (!paths.includes(lookupPaths[j])) paths.push(lookupPaths[j]);
         }
       }
     }
   } else if (options.paths === undefined) {
     paths = Module._resolveLookupPaths(request, parent);
   } else {
        //...
   }
 } else {
   // 查找模块存在路径
   paths = Module._resolveLookupPaths(request, parent);
 }
 // 依据给出的模块和遍历地址数组,以及是否为入口模块来查找模块路径
 const filename = Module._findPath(request, paths, isMain);
 if (!filename) {
   const requireStack = [];
   for (let cursor = parent; cursor; cursor = cursor.parent) {
     requireStack.push(cursor.filename || cursor.id);
   }
   // 未找到模块,抛出异常(是不是很熟悉的错误)
   let message = `Cannot find module '${request}'`;
   if (requireStack.length > 0) {
     message = message + "\nRequire stack:\n- " + requireStack.join("\n- ");
   }

   const err = new Error(message);
   err.code = "MODULE_NOT_FOUND";
   err.requireStack = requireStack;
   throw err;
 }
 // 最终返回包含文件名的完整路径
 return filename;
};

上面的代码中比较突出的是使用了_resolveLookupPaths和_findPath两个方法。

_resolveLookupPaths: 通过接受模块名称和模块调用者,返回提供_findPath使用的遍历范围数组。

// 模块文件寻址的地址数组方法
   Module._resolveLookupPaths = function(request, parent) {
    if (NativeModule.canBeRequiredByUsers(request)) {
      debug("looking for %j in []", request);
      return null;
    }

    // 如果不是相对路径
    if (
      request.charAt(0) !== "." ||
      (request.length > 1 &&
        request.charAt(1) !== "." &&
        request.charAt(1) !== "/" &&
        (!isWindows || request.charAt(1) !== "\\"))
    ) {
      /**
       * 检查 node_modules 文件夹
       * modulePaths 为用户目录,node_path 环境变量指定目录、全局 node 安装目录
       */
      let paths = modulePaths;

      if (parent != null && parent.paths && parent.paths.length) {
        // 父模块的 modulePath 也要加到子模块的 modulePath 里面,往上回溯查找
        paths = parent.paths.concat(paths);
      }

      return paths.length > 0 ? paths : null;
    }

    // 使用 repl 交互时,依次查找 ./ ./node_modules 以及 modulePaths
    if (!parent || !parent.id || !parent.filename) {
      const mainPaths = ["."].concat(Module._nodeModulePaths("."), modulePaths);

      return mainPaths;
    }

    // 如果是相对路径引入,则将父级文件夹路径加入查找路径
    const parentDir = [path.dirname(parent.filename)];
    return parentDir;
   };

_findPath: 依据目标模块和上述函数查找到的范围,找到对应的 filename 并返回。

// 依据给出的模块和遍历地址数组,以及是否顶层模块来寻找模块真实路径
Module._findPath = function(request, paths, isMain) {
 const absoluteRequest = path.isAbsolute(request);
 if (absoluteRequest) {
  // 绝对路径,直接定位到具体模块
   paths = [""];
 } else if (!paths || paths.length === 0) {
   return false;
 }
 const cacheKey =
   request + "\x00" + (paths.length === 1 ? paths[0] : paths.join("\x00"));
 // 缓存路径
 const entry = Module._pathCache[cacheKey];
 if (entry) return entry;
 let exts;
 let trailingSlash =
   request.length > 0 &&
   request.charCodeAt(request.length - 1) === CHAR_FORWARD_SLASH; // '/'
 if (!trailingSlash) {
   trailingSlash = /(?:^|\/)\.?\.$/.test(request);
 }
 // For each path
 for (let i = 0; i < paths.length; i++) {
   const curPath = paths[i];
   if (curPath && stat(curPath) < 1) continue;
   const basePath = resolveExports(curPath, request, absoluteRequest);
   let filename;
   const rc = stat(basePath);
   if (!trailingSlash) {
     if (rc === 0) { // stat 状态返回 0,则为文件
       // File.
       if (!isMain) {
         if (preserveSymlinks) {
           // 当解析和缓存模块时,命令模块加载器保持符号连接。
           filename = path.resolve(basePath);
         } else {
           // 不保持符号链接
           filename = toRealPath(basePath);
         }
       } else if (preserveSymlinksMain) {
         filename = path.resolve(basePath);
       } else {
         filename = toRealPath(basePath);
       }
     }
     if (!filename) {
       if (exts === undefined) exts = ObjectKeys(Module._extensions);
       // 解析后缀名
       filename = tryExtensions(basePath, exts, isMain);
     }
   }
   if (!filename && rc === 1) {
     /**
       *  stat 状态返回 1 且文件名不存在,则认为是文件夹
       * 如果文件后缀不存在,则尝试加载该目录下的 package.json 中 main 入口指定的文件
       * 如果不存在,然后尝试 index[.js, .node, .json] 文件
     */
     if (exts === undefined) exts = ObjectKeys(Module._extensions);
     filename = tryPackage(basePath, exts, isMain, request);
   }
   if (filename) { // 如果存在该文件,将文件名则加入缓存
     Module._pathCache[cacheKey] = filename;
     return filename;
   }
 }
 const selfFilename = trySelf(paths, exts, isMain, trailingSlash, request);
 if (selfFilename) {
   // 设置路径的缓存
   Module._pathCache[cacheKey] = selfFilename;
   return selfFilename;
 }
 return false;
};

模块加载

标准模块处理

阅读完上面的代码,我们发现,当遇到模块是一个文件夹的时候会执行tryPackage函数的逻辑,下面简要分析一下具体实现。

// 尝试加载标准模块
function tryPackage(requestPath, exts, isMain, originalPath) {
  const pkg = readPackageMain(requestPath);
  if (!pkg) {
    // 如果没有 package.json 这直接使用 index 作为默认入口文件
    return tryExtensions(path.resolve(requestPath, "index"), exts, isMain);
  }
  const filename = path.resolve(requestPath, pkg);
  let actual =
    tryFile(filename, isMain) ||
    tryExtensions(filename, exts, isMain) ||
    tryExtensions(path.resolve(filename, "index"), exts, isMain);
  //...
  return actual;
}
// 读取 package.json 中的 main 字段
function readPackageMain(requestPath) {
  const pkg = readPackage(requestPath);
  return pkg ? pkg.main : undefined;
}

readPackage 函数负责读取和解析 package.json 文件中的内容,具体描述如下:

function readPackage(requestPath) {
  const jsonPath = path.resolve(requestPath, "package.json");
  const existing = packageJsonCache.get(jsonPath);
  if (existing !== undefined) return existing;
  // 调用 libuv uv_fs_open 的执行逻辑,读取 package.json 文件,并且缓存
  const json = internalModuleReadJSON(path.toNamespacedPath(jsonPath));
  if (json === undefined) {
    // 接着缓存文件
    packageJsonCache.set(jsonPath, false);
    return false;
  }
  //...
  try {
    const parsed = JSONParse(json);
    const filtered = {
      name: parsed.name,
      main: parsed.main,
      exports: parsed.exports,
      type: parsed.type
    };
    packageJsonCache.set(jsonPath, filtered);
    return filtered;
  } catch (e) {
    //...
  }
}

上面的两段代码完美地解释 package.json 文件的作用,模块的配置入口( package.json 中的 main 字段)以及模块的默认文件为什么是 index,具体流程如下图所示:

模块文件处理

定位到对应模块之后,该如何加载和解析呢?以下是具体代码分析:

Module.prototype.load = function(filename) {
  // 保证模块没有加载过
  assert(!this.loaded);
  this.filename = filename;
  // 找到当前文件夹的 node_modules
  this.paths = Module._nodeModulePaths(path.dirname(filename));
  const extension = findLongestRegisteredExtension(filename);
  //...
  // 执行特定文件后缀名解析函数 如 js / json / node
  Module._extensions[extension](this, filename);
  // 表示该模块加载成功
  this.loaded = true;
  // ... 省略 esm 模块的支持
};

后缀处理

可以看出,针对不同的文件后缀,Node.js 的加载方式是不同的,以下针对.js, .json, .node简单进行分析。

.js 后缀 js 文件读取主要通过 Node 内置 APIfs.readFileSync实现。

Module._extensions[".js"] = function(module, filename) {

  // 读取文件内容
  const content = fs.readFileSync(filename, "utf8");
  // 编译执行代码
  module._compile(content, filename);
};

json 后缀 JSON 文件的处理逻辑比较简单,读取文件内容后执行JSONParse即可拿到结果。

Module._extensions[".json"] = function(module, filename) {
  // 直接按照 utf-8 格式加载文件
  const content = fs.readFileSync(filename, "utf8");
  //...
  try {
    // 以 JSON 对象格式导出文件内容
    module.exports = JSONParse(stripBOM(content));
  } catch (err) {
    //...
  }
};

.node 后缀 .node 文件是一种由 C / C++ 实现的原生模块,通过 process.dlopen 函数读取,而 process.dlopen 函数实际上调用了 C++ 代码中的 DLOpen 函数,而 DLOpen 中又调用了 uv_dlopen, 后者加载 .node 文件,类似 OS 加载系统类库文件。

Module._extensions[".node"] = function(module, filename) {
  //...
  return process.dlopen(module, path.toNamespacedPath(filename));
};

从上面的三段源码,我们看出来并且可以理解,只有 JS 后缀最后会执行实例方法_compile,我们去除一些实验特性和调试相关的逻辑来简要的分析一下这段代码。

编译执行

模块加载完成后,Node 使用 V8 引擎提供的方法构建运行沙箱,并执行函数代码,代码如下所示:

Module.prototype._compile = function(content, filename) {
  let moduleURL;
  let redirects;
  // 向模块内部注入公共变量 __dirname / __filename / module / exports / require,并且编译函数
  const compiledWrapper = wrapSafe(filename, content, this);
  const dirname = path.dirname(filename);
  const require = makeRequireFunction(this, redirects);
  let result;
  const exports = this.exports;
  const thisValue = exports;
  const module = this;
  if (requireDepth === 0) statCache = new Map();
      //...
   // 执行模块中的函数
    result = compiledWrapper.call(
      thisValue,
      exports,
      require,
      module,
      filename,
      dirname
    );
  hasLoadedAnyUserCJSModule = true;
  if (requireDepth === 0) statCache = null;
  return result;
};
// 注入变量的核心逻辑
function wrapSafe(filename, content, cjsModuleInstance) {
  if (patched) {
    const wrapper = Module.wrap(content);
    // vm 沙箱运行 ,直接返回运行结果,env -> SetProtoMethod(script_tmpl, "runInThisContext", RunInThisContext);
    return vm.runInThisContext(wrapper, {
      filename,
      lineOffset: 0,
      displayErrors: true,
      // 动态加载
      importModuleDynamically: async specifier => {
        const loader = asyncESM.ESMLoader;
        return loader.import(specifier, normalizeReferrerURL(filename));
      }
    });
  }
  let compiled;
  try {
    compiled = compileFunction(
      content,
      filename,
      0,
      0,
      undefined,
      false,
      undefined,
      [],
      ["exports", "require", "module", "__filename", "__dirname"]
    );
  } catch (err) {
    //...
  }
  const { callbackMap } = internalBinding("module_wrap");
  callbackMap.set(compiled.cacheKey, {
    importModuleDynamically: async specifier => {
      const loader = asyncESM.ESMLoader;
      return loader.import(specifier, normalizeReferrerURL(filename));
    }
  });
  return compiled.function;
}

上述代码中,我们可以看到在_compile函数中调用了wrapwrapSafe函数,执行了__dirname / __filename / module / exports / require公共变量的注入,并且调用了 C++ 的 runInThisContext 方法(位于 src/node_contextify.cc 文件)构建了模块代码运行的沙箱环境,并返回了compiledWrapper对象,最终通过compiledWrapper.call方法运行模块。

以上就是nodejs模块系统源码分析的详细内容,更多关于nodejs模块系统源码分析的资料请关注我们其它相关文章!

(0)

相关推荐

  • 谈谈node.js中的模块系统

    Node.js 的模块 JavaScript 做为一门为网页添加交互功能的简单脚本语言问世,在诞生时并不包含模块系统,随着 JavaScript 解决问题越来越复杂,把所有代码写在一个文件内,用 function 区分功能单元已经不能支撑复杂应用开发了,ES6 带来了大部分高级语言都有的 class 和 module,方便开发者组织代码 import _ from 'lodash'; class Fun {} export default Fun; 上面三行代码展示了一个模块系统最重要的两个要素

  • Node.js JSON模块用法实例分析

    本文实例讲述了Node.js JSON模块用法.分享给大家供大家参考,具体如下: 一.JSON.stringify语法: JSON.stringify(value [, replacer] [, space]) value:是必选字段.就是你输入的对象,比如数组,类等. replacer(可选参数):它又分为2种方式,一种是数组,第二种是方法. (1)replacer为数组时,表示的是Key,只有在类中有出现过的Key的键值对(Key-Value)才会在转化的结果中出现. replacer数组仅

  • 浅析node.js的模块加载机制

    在node.js中,模块使用CommonJS规范,一个文件是一个模块 node.js中的模块可分为三类 内部模块 - node.js提供的模块如 fs,http,path等 自定模块 - 我们自己写的模块 第三方模块 - 通过npm安装的模块 node.js提供了大量的模块供我们使用,比如 想解析一个文件的路径,可以使用path模块下的相应方法实现: const path = require('path'); //返回目标文件的绝对路径 console.log(path.resolve('./1

  • node.js核心模块有哪些

    全局对象 在浏览器JS中,通常window是全局对象,而nodejs中的全局对象是global,所有全局变量都是global对象的属性. 在nodejs中能够直接访问到的对象通常都是global的属性,如console. process等 全局对象与全局变量 global最根本的作用是作为全局变量的宿主. 全局变量的条件: 在最外层定义的变量:全局对象的属性:隐式定义的变量(未定义直接赋值的变量) 定义一个全局变量,同时也是全局对象的属性. 永远使用var定义变量以避免引入全局变量,因为全局变量

  • Node.js学习教程之Module模块

    前言 采用了 Commonjs 规范,通过 module.exports.require 来导出和导入模块.模块加载机制中,采用了延迟加载的策略.就是说在用到的情况下,系统模块才会被加载,等加载完成后会放到 binding_cache 中. 分类(模块类型) 系统模块 核心模块(native 模块),http.buffer.fs 等,底层调用的内建模块 (C/C++): C/C++ 模块(built-in 内建模块),供 native 模块调用: 第三方模块 第三方维护的模块,比如 expres

  • 通过实例了解Nodejs模块系统及require机制

    一.简介 Nodejs 有一个简单的模块加载系统.在 Nodejs 中,文件和模块是一一对应的(每个文件被视为一个独立的模块),这个文件可能是 JavaScript 代码,JSON 或编译过的C/C++ 扩展,例如: /** *foo.js *将这个js文件导出为模块 */ exports.hello = function() { console.log("hello Nodejs!"); } /** *main.js *main.js和foo.js在同一目录下 *在控制台中将会输出:

  • 详细分析Node.js 模块系统

    为了让Node.js的文件可以相互调用,Node.js提供了一个简单的模块系统. 模块是Node.js 应用程序的基本组成部分,文件和模块是一一对应的.换言之,一个 Node.js 文件就是一个模块,这个文件可能是JavaScript 代码.JSON 或者编译过的C/C++ 扩展. 创建模块 在 Node.js 中,创建一个模块非常简单,如下我们创建一个 main.js 文件,代码如下: var hello = require('./hello'); hello.world(); 以上实例中,代

  • NodeJS模块与ES6模块系统语法及注意点详解

    社区模块规范: 1.CommonJS规范 规范实现者: NodeJS 服务端 Browserify 浏览器 2.AMD规范 全称 异步模块定义 规范实现者: RequireJS 浏览器 3.CMD规范 通用模块定义 规范实现者: seaJS 服务端和浏览器通用 官方模块规范 1.ESM规范 就是ES6 Module 各浏览器和服务端 目前常用的就是浏览器端的RequireJS.NodeJS.以及ESM CommonJS语法分析 module.export 关键 1.module.exports实

  • node.js require() 源码解读

    2009年,Node.js 项目诞生,所有模块一律为 CommonJS 格式. 时至今日,Node.js 的模块仓库 npmjs.com ,已经存放了15万个模块,其中绝大部分都是 CommonJS 格式. 这种格式的核心就是 require 语句,模块通过它加载.学习 Node.js ,必学如何使用 require 语句.本文通过源码分析,详细介绍 require 语句的内部运行机制,帮你理解 Node.js 的模块机制. 一.require() 的基本用法 分析源码之前,先介绍 requir

  • nodejs模块系统源码分析

    概述 Node.js的出现使得前端工程师可以跨端工作在服务器上,当然,一个新的运行环境的诞生亦会带来新的模块.功能.抑或是思想上的革新,本文将带领读者领略 Node.js(以下简称 Node) 的模块设计思想以及剖析部分核心源码实现. CommonJS 规范 Node 最初遵循 CommonJS 规范来实现自己的模块系统,同时做了一部分区别于规范的定制.CommonJS 规范是为了解决JavaScript的作用域问题而定义的模块形式,它可以使每个模块在它自身的命名空间中执行. 该规范强调模块必须

  • 如何让Nodejs支持H5 History模式(connect-history-api-fallback源码分析)

    导读 本文主要是对connect-history-api-fallback库进行一次源码分析.connect-history-api-fallback是一个用于支持SPA History路由模式的nodejs库.阅读本文前,应对HTML5 History模式有一定程度的了解! 源码分析 /** * 前端需要开启history模式,而后端根据url并不知道前端在请求api还是在请求页面,如localhost:4200/home这种url,前端理所当然认为"我需要得到html,并跳转到首页"

  • Mybatis源码分析之插件模块

    Mybatis插件模块 插件这个东西一般用的比较少,就算用的多的插件也算是PageHelper分页插件: PageHelper官网:https://github.com/pagehelper/Mybatis-PageHelper/blob/master/README_zh.md 官网上这个也有谈到Mybatis的插件流程分析. 使用示例 插件类 记录SQL执行的时间, 1.在JDK8之前必须实现Interceptor接口中的三个方法,在JDK8之后只需要实现intercept方法即可: 2.加上

  • muduo源码分析之TcpServer模块详细介绍

    这次我们开始muduo源代码的实际编写,首先我们知道muduo是LT模式,Reactor模式,下图为Reactor模式的流程图[来源1] 然后我们来看下muduo的整体架构[来源1] 首先muduo有一个主反应堆mainReactor以及几个子反应堆subReactor,其中子反应堆的个数由用户使用setThreadNum函数设置,mainReactor中主要有一个Acceptor,当用户建立新的连接的时候,Acceptor会将connfd和对应的事件打包为一个channel然后采用轮询的算法,

  • python如何使用contextvars模块源码分析

    目录 前记 更新说明 1.有无上下文传变量的区别 2.如何使用contextvars模块 3.如何优雅的使用contextvars 4.contextvars的原理 4.1 ContextMeta,ContextVarMeta和TokenMeta 4.2 Token 4.3 全局唯一context 4.4contextvar自己封装的Context 4.5 ContextVar 5.contextvars asyncio 5.1在asyncio中获取context 5.2 对上下文的操作 5.2

  • rollup cli开发全面系统性rollup源码分析

    目录 引言 prefix symlink Executables(可执行文件) rollup 命令行的开发 打包生成 rollup 文件 引言 在学习 rollup CLI 之前我们需要了解 npm 中的 prefix,symlink,Executables 这三个概念. prefix 当我们使用 --global/-g 选项的时候会将包安装到 prefix 目录下,prefix 默认为 node 的安装位置.在大多数系统上,它是 /usr/local. 在 Windows 上,它是 %AppD

  • nginx源码分析configure脚本详解

    nginx源码分析--configure脚本 一.前言 在分析源码时,经常可以看到类似 #if (NGX_PCRE) .... #endif 这样的代码段,这样的设计可以在不改动源码的情况下,通过简单的定义宏的方式来实现功能的打开与关闭,但是在nginx/src目录下始终没有找到宏 NGX_PCRE 对应的 #define 语句. 在之前介绍event模块的时候,讲到init_cycle函数中对cycle进行了初始化,其中很重要一步操作就是讲包含所有module信息的数组拷贝到这个cycle对应

  • jQuery源码分析-03构造jQuery对象-工具函数

    作者:nuysoft/高云 QQ:47214707 EMail:nuysoft@gmail.com 声明:本文为原创文章,如需转载,请注明来源并保留原文链接. 读读写写,不对的地方请告诉我,多多交流共同进步,本章的的PDF等本章写完了发布. jQuery源码分析系列的目录请查看 http://nuysoft.iteye.com/blog/1177451,想系统的好好写写,目前还是从我感兴趣的部分开始,如果大家有对哪个模块感兴趣的,建议优先分析的,可以告诉我,一起学习. 3.4 其他静态工具函数

  • openstack云计算keystone架构源码分析

    目录 keystone架构 Keystone API Router Services (1) Identity Service (2) Resource Service (3) Assignment Service (4) Token Service (5) Catalog Service (6) Policy ServicePolicy Service Backend Driver keystone管理这些概念的方法 keystone-10.0.0代码结构展示 keystone服务启动 key

  • RateLimiter 源码分析

    俗话说得好,缓存,限流和降级是系统的三把利剑.刚好项目中每天早上导出数据时因调订单接口频率过高,订单系统担心会对用户侧的使用造成影响,让我们对调用限速一下,所以就正好用上了. 常用的限流算法有2种:漏桶算法和令牌桶算法. 漏桶算法 漏桶算法:请求先进入"桶"中,然后桶以一定的速率处理请求.如果请求的速率过快会导致桶溢出.根据描述可以知道,漏桶算法会强制限制请求处理的速度.任你请求的再快还是再慢,我都是以这种速率来处理. 但是对于很多情况下,除了要求能够限制平均处理速度外,还要求能允许一

随机推荐