eggjs框架源码解读

news2024/11/28 4:49:39

文章目录

    • 前言
    • Egg进程模型
    • Egg应用程序结构
    • egg运行启动的内幕
      • 加载插件
      • 扩展内置对象
      • 加载中间件
      • 加载service
      • 加载路由
      • 加载配置
      • 设置应用信息
      • 执行业务逻辑
      • 文件加载机制
    • 结语

前言

eggjs 是阿里在 Nodejs 技术上的一大杰作,也是对开源世界的一大贡献。里面包含了很多技术结晶,值得我们学习。

Egg进程模型

  • 多进程机制
  • 应用结构规范
  • 文件加载机制
  • 多进程机制

大家都知道,nodejs 本身是单线程的,单线程意味着一个错误抛出未捕获就会导致整个应用挂掉。且单线程只能跑在一个核上,无法有效利用机器资源,对机器是一种浪费。

在 egg 中,为了解决上述问题,引入了 Master - Agent - Worker 机制,有多少个 CPU 启多少个 Worker ,榨干了机器的资源;对于每一个 Worker,挂了之后 Mater 进程会重启一个,保证了应用的健壮行。

他们的关系是:Mater 启动 Agent, Agent 启动后会告诉 Mater ,Master 收到消息后会启动 Worker ,Worker 启动完毕告诉 Master ,这个时候 Master 会处于 Ready 状态。这个实现在 egg-cluster 模块。
在这里插入图片描述

可以看到egg处于的是一个中间层的角色,基于koa,不同于koa以middleware为主要生态,egg根据不同的业务需求和场景,加入了plugin,extends等这些功能,可以让开发者摆脱在使用middleware功能时无法控制使用顺序的被动状态,而且还可以增加一些请求无关的一些功能。除此之外,egg还有很多其他优秀的功能。

Master 是主进程,只处理子进程的重启及通信;Agent 是 Master 的子进程,主要处理一些通用的服务,比如公共资源访问、文件监听,这些东西让一个 Agent 来做就好了,不需要每个 Worker 去关心。Worker 只需要处理好应用的逻辑就行。

Agent 是以 child_process fork 出来的,它属于 Master 的子进程。Worker 是 cluster fork 出来的(详见 cfork 模块),因为 Worker 是主要负责干活的,所以需要通过 cluster 内置的负载均衡机制合理的分配给他们。

Master Agent Worker 三者间 IPC 通讯实现是通过 Messenger 对象来实现的,Messenger 是 egg 实现的消息转发机制。另外由于Agent Worker 属于不同的进程,所以 Agent 与 Worker 不能直接通讯,不过它们有同一个“爹” Master ,所以它们的通讯是通过 Master 来中转的。
在这里插入图片描述

Egg应用程序结构

应用结构规范化是 egg 一个核心思想。约定目录是指约束基于 egg 的上层框架,如 begg 、beidou ,及应用的目录规范。这样做的好处:为 egg 的文件加载机制做准备(config 文件加载、plugin 加载等)。一个基于 egg 的应用应该符合以下目录结构:

├── package.json
├── app.js (可选)
├── agent.js (可选)
├── app
|   ├── router.js
│   ├── controller
│   │   └── home.js
|   ├── extend (可选,对 egg 的扩展)|   ├── helper.js (可选)|   ├── filter.js (可选)|   ├── request.js (可选)|   ├── response.js (可选)|   ├── context.js (可选)|   ├── application.js (可选)|   └── agent.js (可选)
│   ├── proxy (可选,由 hsf/tr 插件规范,建议统一为 proxy)
│   ├── service (可选)
|   ├── public (可选)
|   │   ├── favicon.ico
|   |   └── ...
│   ├── middleware (可选)
│   │   └── response_time.js
│   └── views (可选,由 view 插件规范,建议统一为 views)
|       ├── layout.html
│       └── home.html
├── config
|   ├── config.default.js
│   ├── config.prod.js
|   ├── config.test.js (可选)
|   ├── config.local.js (可选)
|   ├── config.unittest.js (可选)
│   ├── plugin.js
│   └── role.js (可选,以 role 插件举例,插件特殊配置也放在 config 目录下)
└── test
    ├── middleware
    |   └── response_time.test.js
    └── controller
        └── home.test.js

egg运行启动的内幕

下面通过追踪源码来讲解一下egg究竟是如何运行起来的。

查看egg-init脚手架生成的项目文件,可以看到整个项目文件是没有严格意义上的入口文件的,根据package.json中的script命令,可以看到执行的直接是egg-bin dev的命令。找到egg-bin文件夹中的dev.js,会看到里面会去执行start-cluster文件:

//dev.js构造函数中

this.serverBin = path.join(__dirname, '../start-cluster');
// run成员函数
* run(context) {
    //省略
    yield this.helper.forkNode(this.serverBin, devArgs, options);
}

移步到start-cluster.js文件,可以看到关键的一行代码:

require(options.framework).startCluster(options);

其中options.framework打印信息为:

/Users/wyf/Project/egg-example/node_modules/egg

找到对应的egg目录中的index.js文件:

exports.startCluster = require(‘egg-cluster’).startCluster;
继续追踪可以看到最后运行的其实就是egg-cluster中的startCluster,并且会fork出agentWorker和appWorks,官方文档对于不同进程的fork顺序以及不同进程之间的IPC有比较清晰的说明,
主要的顺序如下:

Master 启动后先 fork Agent 进程
Agent 初始化成功后,通过 IPC 通道通知 Master
Master 再 fork 多个 App Worker
App Worker 初始化成功,通知 Master
所有的进程初始化成功后,Master 通知 Agent 和 Worker 应用启动成功
通过代码逻辑也可以看出它的顺序:

//在egg-ready状态的时候就会执行进程之间的通信

this.ready(() => {
  //省略代码
  const action = 'egg-ready';
  this.messenger.send({ action, to: 'parent' });
  this.messenger.send({ action, to: 'app', data: this.options });
  this.messenger.send({ action, to: 'agent', data: this.options });
});
 
this.on('agent-exit', this.onAgentExit.bind(this));
this.on('agent-start', this.onAgentStart.bind(this));
this.on('app-exit', this.onAppExit.bind(this));
this.on('app-start', this.onAppStart.bind(this));
this.on('reload-worker', this.onReload.bind(this));
 
// fork app workers after agent started
this.once('agent-start', this.forkAppWorkers.bind(this));

通过上面的代码可以看出,master进程会去监听当前的状态,比如在检测到agent-start的时候才去fork AppWorkers,在当前状态为egg-ready的时候,会去执行如下的进程之间的通信:

master—> parent
master —> agent
master —> app
fork出了appWorker之后,每一个进程就开始干活了,在app_worker.js文件中,可以看到进程启动了服务,具体代码:

// 省略代码
function startServer() {
  let server;
  if (options.https) {
    server = require('https').createServer({
      key: fs.readFileSync(options.key),
      cert: fs.readFileSync(options.cert),
    }, app.callback());
  } else {
    server = require('http').createServer(app.callback());
  }
 //省略代码
}

然后就回归到koa中的入口文件干的事情了。

除此之外,每一个appWorker还实例化了一个Application:

const Application = require(options.framework).Application;
const app = new Application(options);

在实例化application(options)时,就会去执行node_modules->egg模块下面loader目录下面的逻辑,也就是agentWorker进程和多个appWorkers进程要去执行的加载逻辑,具体可以看到app_worker_loader.js文件中的load():

load() {
    // app > plugin > core
    this.loadApplicationExtend();
    this.loadRequestExtend();
    this.loadResponseExtend();
    this.loadContextExtend();
    this.loadHelperExtend();
 
    // app > plugin
    this.loadCustomApp();
    // app > plugin
    this.loadService();
    // app > plugin > core
    this.loadMiddleware();
    // app
    this.loadController();
    // app
    this.loadRouter(); // 依赖 controller
  }
}

在真正执行业务代码之前,egg会先去干下面一些事情。

加载插件

egg中内置了如下一系列插件:

  • onerror 统一异常处理
  • Session Session 实现
  • i18n 多语言
  • watcher 文件和文件夹监控
  • multipart 文件流式上传
  • security 安全
  • development 开发环境配置
  • logrotator 日志切分
  • schedule 定时任务
  • static 静态服务器
  • jsonp jsonp 支持
  • view 模板引擎

加载插件的逻辑是在egg-core里面的plugin.js文件,先看代码:

loadPlugin() {
 
    //省略代码
    //把本地插件,egg内置的插件以及app的框架全部集成到allplugin中
    this._extendPlugins(this.allPlugins, eggPlugins);
    this._extendPlugins(this.allPlugins, appPlugins);
    this._extendPlugins(this.allPlugins, customPlugins);
 
    //省略代码
    //遍历操作
    for (const name in this.allPlugins) {
      const plugin = this.allPlugins[name];
 
      //对插件名称进行一些校验
      this.mergePluginConfig(plugin);
      //省略代码
      }
      if (plugin.enable) {
        //整合所有开启的插件
        enabledPluginNames.push(name);
      }
 }

如上代码(只是贴出了比较关键的地方),这段代码主要是将本地插件、egg中内置的插件以及应用的插件进行了整合。其中this.allPlugins的结果如下:
images

可以看出,this.allPlugins包含了所有内置的插件以及本地开发者自定义的插件。现货区所有插件的相关信息,然后将所有插件进行遍历,执行this.mergePluginConfig()函数,这个函数主要是对插件名称进行一些校验。之后还对项目中已经开启的插件进行整合。plugin.js文件还做了一些其他事情,比如获取插件路径,读取插件配置等等,这里不一一讲解。

扩展内置对象

包括插件里面定义的扩展以及开发者自己写的扩展,这也是这里讲的内容。

在对内置对象进行扩展的时候,实质上执行的是extend.js文件,扩展的对象包括如下几个:

  • Application
  • Context
  • Request
  • Response
  • Helper
    通过阅读extend.js文件可以知道,其实最后每个对象的扩展都是直接调用的loadExtends这个函数。拿Application这个内置对象进行举例:
loadExtend(name, proto) {
    // All extend files
    const filepaths = this.getExtendFilePaths(name);
    // if use mm.env and serverEnv is not unittest
    const isAddUnittest = 'EGG_MOCK_SERVER_ENV' in process.env && this.serverEnv !== 'unittest';
    for (let i = 0, l = filepaths.length; i < l; i++) {
      const filepath = filepaths[i];
      filepaths.push(filepath + `.${this.serverEnv}.js`);
      if (isAddUnittest) filepaths.push(filepath + '.unittest.js');
    }
 
    const mergeRecord = new Map();
    for (let filepath of filepaths) {
      filepath = utils.resolveModule(filepath);
      if (!filepath) {
        continue;
      } else if (filepath.endsWith('/index.js')) {
        // TODO: remove support at next version
        deprecate(`app/extend/${name}/index.js is deprecated, use app/extend/${name}.js instead`);
      }
      const ext = utils.loadFile(filepath);
 
      //获取内置对象的原有属性
      const properties = Object.getOwnPropertyNames(ext)
        .concat(Object.getOwnPropertySymbols(ext));
 
        //对属性进行遍历
      for (const property of properties) {
        if (mergeRecord.has(property)) {
          debug('Property: "%s" already exists in "%s",it will be redefined by "%s"',
            property, mergeRecord.get(property), filepath);
        }
 
        // Copy descriptor
        let descriptor = Object.getOwnPropertyDescriptor(ext, property);
        let originalDescriptor = Object.getOwnPropertyDescriptor(proto, property);
        if (!originalDescriptor) {
          // try to get descriptor from originalPrototypes
          const originalProto = originalPrototypes[name];
          if (originalProto) {
            originalDescriptor = Object.getOwnPropertyDescriptor(originalProto, property);
          }
        }
       //省略代码
       //将扩展属性进行合并
        Object.defineProperty(proto, property, descriptor);
        mergeRecord.set(property, filepath);
      }
      debug('merge %j to %s from %s', Object.keys(ext), name, filepath);
    }
},

将filepaths进行打印,如下图:

images

可以看出,filepaths包含所有的对application扩展的文件路径,这里会首先将所有插件中扩展或者开发者自己自定义的扩展文件的路径获取到,然后进行遍历,并且对内置对象的一些原有属性和扩展属性进行合并,此时对内置对象扩展的一些属性就会添加到内置对象中。所以在执行业务代码的时候,就可以直接通过访问application.属性(或方法)进行调用。

加载中间件

对中间件的加载主要是执行的egg-core中的middleware.js文件,里面的代码思想也是和上面加载内置对象是一样的,也是将插件中的中间件和应用中的中间件路径全部获取到,然后进行遍历。

遍历完成之后执行中间件就和koa一样了,调用co进行包裹遍历。

加载控制器
对控制器的加载主要是执行的egg-core中的controller.js文件
egg的官方文档中,插件的开发这一节提到:

插件没有独立的 router 和 controller

所以在加载controller的时候,主要是load应用里面的controller即可。详见代码;

loadController(opt) {
    opt = Object.assign({
      caseStyle: 'lower',
      directory: path.join(this.options.baseDir, 'app/controller'),
      initializer: (obj, opt) => {
        if (is.function(obj) && !is.generatorFunction(obj) && !is.class(obj)) {
          obj = obj(this.app);
        }
        if (is.promise(obj)) {
          const displayPath = path.relative(this.app.baseDir, opt.path);
          throw new Error(`${displayPath} cannot be async function`);
        }
        if (is.class(obj)) {
          obj.prototype.pathName = opt.pathName;
          obj.prototype.fullPath = opt.path;
          return wrapClass(obj);
        }
        if (is.object(obj)) {
          return wrapObject(obj, opt.path);
        }
        if (is.generatorFunction(obj)) {
          return wrapObject({ 'module.exports': obj }, opt.path)['module.exports'];
        }
        return obj;
      },
    }, opt);
    const controllerBase = opt.directory;
 
    this.loadToApp(controllerBase, 'controller', opt);
    this.options.logger.info('[egg:loader] Controller loaded: %s', controllerBase);
},

这里主要是针对controller的类型进行判断(是否是Object,class,promise,generator),然后分别进行处理

加载service

加载service的逻辑是egg-core中的service.js,service.js这个文件比较简单,代码如下:

loadService(opt) {
    // 载入到 app.serviceClasses
    opt = Object.assign({
      call: true,
      caseStyle: 'lower',
      fieldClass: 'serviceClasses',
      directory: this.getLoadUnits().map(unit => path.join(unit.path, 'app/service')),
    }, opt);
    const servicePaths = opt.directory;
    this.loadToContext(servicePaths, 'service', opt);
  },

首先也是先获取所有插件和应用中声明的service.js文件目录,然后执行this.loadToContext()

loadToContext()定义在egg-loader.js文件中,继续追踪,可以看到在loadToContext()函数中实例化了ContextLoader并执行了load(),其中ContextLoader继承自FileLoader,而且load()是声明在FileLoader类中的。
通过查看load()代码可以发现里面的逻辑也是将属性添加到上下文(ctx)对象中的。也就是说加载context对象是在加载service的时候完成的。

而且值得一提的是:在每次刷新页面重新加载或者有新的请求的时候,都会去执行context_loader.js里面的逻辑,也就是说ctx上下文对象的内容会随着每次请求而发生改变,而且service对象是挂载在ctx对象下面的,对于service的更新,这里有一段代码:

// define ctx.service
Object.defineProperty(app.context, property, {
  get() {
    // distinguish property cache,
    // cache's lifecycle is the same with this context instance
    // e.x. ctx.service1 and ctx.service2 have different cache
    if (!this[CLASSLOADER]) {
      this[CLASSLOADER] = new Map();
    }
    const classLoader = this[CLASSLOADER];
 
     //先判断是否有使用
    let instance = classLoader.get(property);
    if (!instance) {
      instance = getInstance(target, this);
      classLoader.set(property, instance);
    }
    return instance;
  },
});

在更新service的时候,首先会去获取service是否挂载在ctx中,如果没有,则直接返回,否则实例化service,这也就是service模块中的延迟实例化

加载路由

加载路由的逻辑主要是egg-core中的router.js文件

loadRouter() {
    // 加载 router.js
    this.loadFile(path.join(this.options.baseDir, 'app/router.js'));
},

可以看出很简单,只是加载应用文件下的router.js文件

加载配置

直接加载配置文件并提供可配置的方法。

设置应用信息

对egg应用信息的设置逻辑是对应的egg-core中的egg-loader.js,里面主要是提供一些方法获取整个app的信息,包括appinfo,name,path等,比较简单,这里不一一列出

执行业务逻辑

然后就会去执行如渲染页面等的逻辑

文件加载机制

文件加载机制是 egg 里面非常亮点的地方,其中 plugin 插件无缝集成,更让 egg 实现高可拓展性,为 egg 的繁荣生态打下基础。文件加载机制实现于 egg-loader 模块。

MasterLoader 、 AgentWorkerLoader 、 AppWorkerLoader 都是继承于 @ali/egg-loader 下的 BaseLoader ,而在 BaseLoader 中更是把 antx/config/extend/middleware/plugin/proxy/service 加载方式都集成了。以下是各进程所需要加载的目录文件:

MasterLoader(Mater进程)
loadAntx()

加载antx配置:会加载应用、egg 上层框架、egg 下面的config/antx.*.properties(*根据环境而定)

loadConfig()

加载应用、 egg 上层框架、egg 下面的config/config.*.js(*根据环境而定)

AgentLoader(Agent 进程)
loadPlugin()

会读取app/config/plugin.js 、egg 上层框架/lib/core/config/plugin.js 、egg/lib/core/plugin.js,并根据环境剔除一些未开启的插件。

loadAntx()

除了 masterLoader 加载的那些 antx 外,还加载了插件里的 antx 配置。

loadConfig()

除了 masterLoader 加载的那些 config 外,还加载了插件里的 config 配置。

AppWorkerLoader(Worker进程)
appWorkerLoader 除了加载 AgentLoader 相同的文件之外,还需要加载整个应用逻辑文件:

loadRequest()
loadResponse()
loadContext()
loadHelper()

加载 app/extend 目录下的文件,拓展 Koa 实例。包括 context.js request.js response.js application.js helper.js

loadCustomApp()

加载自定义的 app.js,包含应用下的, egg 上层框架,egg

loadProxy()

主要是加载一些 hsf 相关配置,加载应用及插件下里的 app/proxy 目录下的文件,需要依赖 hsf。

loadService()

加载应用及插件下里的 app/service 目录下的文件。通常是业务逻辑通用的抽象服务,比如 A 接口需要用户信息, B 接口也需要用户信息,那么用户信息就可以抽出来做一个单独的 service 服务。

loadMiddleware()

有关 koa 的中间件。包含应用下的、egg lib/core 及 插件下的 app/middleware 目录文件。

loadController()

加载 app/controller 目录下的文件,里面包含应用对路由的逻辑处理。

loadRouter()

加载 app/router.js,应用路由入口,依赖 controller。

结语

egg 在除了上述内容之外,还为了保证应用健壮性这方面做了不少事情,比如 保证进程的优雅退出,日志记录等。构建与它之上的框架可以根据插件系统非常好的进行扩展,egg 本身的代码设计实现也比较优雅,值得阅读源码学习。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/166675.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux---进程概念

目录 1. 什么是进程&#xff1f; 2. 描述进程---PCB task_struct---PCB的一种 task_ struct内容分类 3. 组织进程 4. 查看进程 通过系统调用获取进程标示符 通过系统调用创建进程---fork初识 1. 什么是进程&#xff1f; 其实&#xff0c;我们启动一个软件&#xf…

SLAM本质剖析番外-李群李代数的微分和导数

0. 简介 这几个月&#xff0c;博主已经从SLAM算法的使用向着算法的数学推导进行了记录和分享&#xff0c;之前也分享了李群李代数关注核心一文&#xff0c;从现象中解释了李群和李代数表达的含义。但是这还不够&#xff0c;所以这次作者作为SLAM本质剖析的番外&#xff0c;来介…

基础数字(一)位运算 哈希(数组中元素出现次数)

目录 力扣剑指 Offer II 070. 排序数组中只出现一次的数字 数组中只出现一次的数&#xff08;其它数出现k次&#xff09;_牛客题霸 数组中只出现一次的两个数字_牛客题霸_牛客网 数组中出现次数超过一半的数字_牛客题霸_牛客网 缺失的第一个正整数_牛客题霸_牛客网 力扣剑指…

[杂记]算法:前缀和与差分数组

这篇讲一下前缀和与差分数组的关系 1. 前缀和 1.1 一维数组前缀和 前缀和在处理数组中的连续子数组的某一段加和的问题中很有用, 因为是拿空间换时间, 可以将线性复杂度降低为常数时间复杂度. 前缀和的道理很简单, 对于数组arr[i],i0,...,n−1arr[i], i 0, ..., n - 1arr[i…

《Linux Shell脚本攻略》学习笔记-第四章

4.1 简介 本章主要介绍sed、awk、grep、cut等命令&#xff0c;这些工具可以相互结合以满足文本处理需求。 正则表达式是一种基础的模式匹配技术。 4.2 使用正则表达式 正则表达式是由字面文本和具有特殊意义的符号组成的。 1&#xff09;位置标记 位置标记锚点是标识字符串位置…

Anaconda安装、opencv环境配置、jupyter notebook使用虚拟环境

目录一、Anaconda 的安装二、opencv 3.4.1.15版本安装三、jupyter notebook使用虚拟环境四、运行报错-缺库一、Anaconda 的安装 Anaconda官网&#xff1a;Anaconda Installers Anaconda历史版本&#xff1a;Anaconda Index of 这边建议和我装一样anaconda3 python3.7&#xf…

opencv的图像基本操作(基于jupyter Notebook)

opencv的基本操作cv2是opencv在python中的缩写&#xff0c;函数开头用cv2cv2.imread(cat.jpg) #读入图片cat.jpgcv2.imwrite(mycat.png,img) #图片img保存为mycat.pngcv2.imshow(image,img) #创建窗口&#xff0c;显示图像cv2.waitKey(10000) #等待时间&#xff0c;以 毫秒为单…

整数分解

问题描述 将 3 分解成两个正整数的和, 有两种分解方法, 分别是 312312 和 321321 。注意顺序不同算不同的方法。 将 5 分解成三个正整数的和, 有 6 种分解方法, 它们是 113122113122 131212221311131212221311 。 请问, 将 2021 分解成五个正整数的和, 有多少种分解方法? …

Android大厂面试100题,涵盖测试技术、环境搭建、人力资源

测试技术面试题 1、什么是兼容性测试&#xff1f;兼容性测试侧重哪些方面&#xff1f; 2、我现在有个程序&#xff0c;发现在Windows上运行得很慢&#xff0c;怎么判别是程序存在问题还是软硬件系统存在问题&#xff1f; 3、测试的策略有哪些&#xff1f; 4、正交表测试用例…

Sinutrain下载安装与开启OPC UA---kalrry

Sinumerik下载安装与开启OPC UA---kalrry前言一、安装前准备二、Win7安装1、软件安装2、开启授权3、文件配置4、客户端连接三、Win10/11安装四、启动后使用前言 本教程只适用于 Sinutrain-v4.7 版本&#xff0c;其他版本配置目录有所改变建议安装到默认路径&#xff0c;否则后…

【云原生】k8s安全机制

内容预知 前言 1. 认证&#xff08;Authentication&#xff09; 1.1 k8s集群内的三种认证方式 1.2 k8s集群内的认证说明 &#xff08;1&#xff09;需要被认证的访问类型 &#xff08;2&#xff09;安全性说明 &#xff08;3&#xff09;证书颁发的方式 &#xff08;4&a…

Qt中使用qt自带的函数实现各种进制间的相互转换,easy.

文章目录一.十进制转各种进制第一种&#xff1a;使用QString的静态函数number第二种&#xff1a;使用QString的拼接函数arg二.各种进制相互转换一.十进制转各种进制 第一种&#xff1a;使用QString的静态函数number ①使用QString的静态函数number即可&#xff0c;如我把字符…

嵌入式linux-进程状态与进程关系

1. 进程状态 1.1什么是进程状态 Linux 系统下进程通常存在 6 种不同的状态&#xff0c;分为&#xff1a;就绪态、运行态、僵尸态、可中断睡眠状态&#xff08;浅度 睡眠&#xff09;、不可中断睡眠状态&#xff08;深度睡眠&#xff09;以及暂停态。 下面我们来一一总结一下&…

数据湖之Hudi基础:入门介绍和编译部署

主要记录下Hudi的概述和打包编译等内容&#xff0c;方便参考 文章目录简介官网发展历史Hudi特性使用场景安装部署编译环境准备编译hudi1.源码包上传到服务器2.修改pom文件3.修改源码兼容hadoop34.手动安装kafka依赖&#xff08;非必须&#xff09;5.解决spark模块依赖冲突6.执行…

【基础篇】4 # 链表(上):如何实现LRU缓存淘汰算法?

说明 【数据结构与算法之美】专栏学习笔记 链表结构 数组需要一块连续的内存空间来存储&#xff0c;对内存的要求比较高&#xff0c; 而链表并不需要一块连续的内存空间&#xff0c;它通过指针将一组零散的内存块串联起来使用。 结点&#xff1a;指的是内存块后继指针 next…

Postgresql源码(98)lex与yacc的定制交互方式

1 背景知识一&#xff1a;LEX %option prefix Postgresql中使用%option prefix"core_yy"&#xff0c;影响范围&#xff1a;yy_create_buffer,yy_delete_buffer,yy_flex_debug,yy_init_buffer,yy_flush_buffer,yy_load_buffer_state,yy_switch_to_buffer,yyin,yyleng…

【并发编程十一】c++线程同步——future

【并发编程十一】c线程同步——future一、互斥二、条件变量三、future1、promise1.1、子线程设值&#xff0c;主线程获取1.2、主线程设置值&#xff0c;子线程获取1.3、shared_future2、async2.1、不开新线程的async2.2、开新线程的async3、packaged_task3.1、不使用bind3.2、提…

Kafka-概述

一、Kafka是什么 1.定义 Apache Kafka 是一款开源的消息引擎系统。 消息引擎系统是一组规范。企业利用这组规范在不同系统之间传递语义准确的消息&#xff0c;实现松耦合的异步式数据传递。 二、消息队列的使用场景 传统消息队列的应用场景包括 缓存/削峰、解耦、异步通信 …

vue(透传属性,$attrs)

官方文档 https://cn.vuejs.org/guide/components/attrs.html 案例 <FirstLevel class"attr-test-class" name"zs" age"18"></FirstLevel>FirstLevel组件没有用props去申明name和age&#xff0c;所以这两个属性会透视传递。 <…

RT-Thread系列--组件初始化

一、目的RT-Thread里面有个特别有意思的软件设计叫做组件自动初始化。有些小伙伴可能是第一次听说&#xff0c;所以这边我解释一下&#xff0c;请看下面的代码片段static void clock_init() {// 时钟初始化 } static void uart_init() {// 串口初始化 } static void i2c_init()…