webpack热更新原理解析

news2024/11/16 11:58:25

热更新原理

1. webpack-dev-server启动本地服务

这里首先会启动webpack并生成compiler实例(compiler实例通过各种事件钩子可以实现监听编译无效、编译结束等功能);

然后会通过express启动一个本地服务,用于服务浏览器对打包资源的请求;
同时,server启动后会启动一个websocket服务,用于服务端与浏览器之间的全双工通信(比如本地资源更新并打包结束后通知客户端请求新的资源);

webpack-dev-server/client/index.js目录下onSocketMessage函数如下:


var onSocketMessage = {
  hot: function hot() {
    if (parsedResourceQuery.hot === "false") {
      return;
    }
    options.hot = true;
  },
  liveReload: function liveReload() {
    if (parsedResourceQuery["live-reload"] === "false") {
      return;
    }
    options.liveReload = true;
  },
  /**
   * @param {string} hash
   */
  hash: function hash(_hash) {
    status.previousHash = status.currentHash;
    status.currentHash = _hash;
  },
  logging: setAllLogLevel,
  /**
   * @param {boolean} value
   */
  overlay: function overlay(value) {
    if (typeof document === "undefined") {
      return;
    }
    options.overlay = value;
  },
  /**
   * @param {number} value
   */
  reconnect: function reconnect(value) {
    if (parsedResourceQuery.reconnect === "false") {
      return;
    }
    options.reconnect = value;
  },
  "still-ok": function stillOk() {
    log.info("Nothing changed.");
    if (options.overlay) {
      hide();
    }
    sendMessage("StillOk");
  },
  ok: function ok() {
    sendMessage("Ok");
    if (options.overlay) {
      hide();
    }
    reloadApp(options, status);
  },
  close: function close() {
    log.info("Disconnected!");
    if (options.overlay) {
      hide();
    }
    sendMessage("Close");
  }
};

2. 修改webpack.config.js的entry配置

启动本地服务后,会在入口动态新增两个文件入口并一同打包到bundle文件中,如下:

// 修改后的entry入口
{ entry:
    { index: 
        [
            // socket客户端代码,onSocketMessge,处理ok/hash等消息
            'xxx/node_modules/webpack-dev-server/client/index.js?http://localhost:8080',
            // 监听'webpackHotUpdate热更新检查
            'xxx/node_modules/webpack/hot/dev-server.js',
            // 开发配置的入口
            './src/index.js'
    	],
    },
}  

这里需要说明下两个文件的作用:

  1. webpack/hot/dev-server.js:该函数主要用于处理检测更新,将其注入到客户端代码中,然后当接收到服务端发送的webpackHotUpdate消息后调用module.hot.check()方法检测更新;有更新时通过module.hot.apply()方法应用更新
  2. webpack-dev-server/client/index.js:动态注入socket客户端代码,通过onSocketMessage函数处理socket服务端的消息;用于更新hash及热模块检测和替换;
// webpack/hot/dev-server.js核心代码
var hotEmitter = require("./emitter");
hotEmitter.on("webpackHotUpdate", function (currentHash) {
	lastHash = currentHash;
	if (!upToDate() && module.hot.status() === "idle") {
		log("info", "[HMR] Checking for updates on the server...");
		check(); // module.hot.check()
	}
});

3. webpack监听文件变化

监听文件变化主要通过setupDevMiddleware方法,底层主要是通过webpack-dev-middleware,调用了compiler.watch方法监听文件的变化;

当文件变化时,通过memory-fs库将打包后的文件写入内存,而不是\dist目录;

其实就是因为webpack-dev-server只负责启动服务和前置准备工作所有文件相关的操作都抽离到webpack-dev-middleware库了,主要是本地文件的编译和输出以及监听;

Compiler 支持可以监控文件系统的 监听(watching) 机制,并且在文件修改时重新编译。 当处于监听模式(watch mode)时, compiler 会触发诸如 watchRun, watchCloseinvalid 等额外的事件。

4. 监听webpack编译结束

通过webpack-dev-server/lib/Server.js中的setupHooks()方法监听webpack编译完成;主要是通过done钩子监听到当次compilation编译完成时,触发done回调并调用sendStats发送socket消息okhash事件

5. 浏览器接收到热更新的通知

上面讲到,当次compilation结束后会通过websocket发送消息通知到客户端,客户端检测是否需要热更新;客户端根据消息类型(ok/hash/hot/ovelay/invalid等)做对应的处理

客户端接收websocket的代码在启动webpack服务后会动态加入到entry入口中并打包到bundle.js中,因此可以正常接收socket服务端消息

在这里插入图片描述

以下是部分socketMessge的处理函数,这里hash可以看到用于更新previousHashcurrentHashok事件主要用于进行热更新检查,主要通过reloadApp实现,其内部则是通过node的EventEmitter发送了webpackHotUpdate事件触发热更新检查;而真正的热更新检查是由HotModuleReplacementPluginmodule.hot.check()实现的;

  /**
   * @param {string} hash
   */
  hash: function hash(_hash) {
    status.previousHash = status.currentHash;
    status.currentHash = _hash;
  },
  ok: function ok() {
    sendMessage("Ok");

    if (options.overlay) {
      hide();
    }

    reloadApp(options, status);
  },
  /**
   * @param {boolean} value
   */
  overlay: function overlay(value) {
    if (typeof document === "undefined") {
      return;
    }

    options.overlay = value;
  },
  invalid: function invalid() {
    log.info("App updated. Recompiling..."); // Fixes #1042. overlay doesn't clear if errors are fixed but warnings remain.

    if (options.overlay) {
      hide();
    }

    sendMessage("Invalid");
  },
	

module.hot.checkmodule.hot.apply方法与HotModuleReplacementPlugin相关,接下来我们看看其作用

6. HotModuleReplacementPlugin

如下所示,我们可以看到module.hot的定义由createModuleHotObject决定,内部的hot对象中定义了check: hotChekapply: hotApply等;具体实现需要借助setStatus函数及对应status

由于这些代码需要在HMR中使用,也是运行时代码,所以同样会被开始就注入到入口文件中

// *\node_modules\webpack\lib\hmr\HotModuleReplacement.runtime.js
$interceptModuleExecution$.push(function (options) {
	var module = options.module;
	var require = createRequire(options.require, options.id);
	module.hot = createModuleHotObject(options.id, module);
	module.parents = currentParents;
	module.children = [];
	currentParents = [];
	options.require = require;
});

createModuleHotObject的实现如下:

	function createModuleHotObject(moduleId, me) {
		var _main = currentChildModule !== moduleId;
		var hot = {
			// private stuff
			_acceptedDependencies: {},
			_acceptedErrorHandlers: {},
			_declinedDependencies: {},
			_selfAccepted: false,
			_selfDeclined: false,
			_selfInvalidated: false,
			_disposeHandlers: [],
			_main: _main,
			_requireSelf: function () {
				currentParents = me.parents.slice();
				currentChildModule = _main ? undefined : moduleId;
				__webpack_require__(moduleId);
			},

			// Module API
			active: true,
			accept: function (dep, callback, errorHandler) {
				if (dep === undefined) hot._selfAccepted = true;
				else if (typeof dep === "function") hot._selfAccepted = dep;
				else if (typeof dep === "object" && dep !== null) {
					for (var i = 0; i < dep.length; i++) {
						hot._acceptedDependencies[dep[i]] = callback || function () {};
						hot._acceptedErrorHandlers[dep[i]] = errorHandler;
					}
				} else {
					hot._acceptedDependencies[dep] = callback || function () {};
					hot._acceptedErrorHandlers[dep] = errorHandler;
				}
			},
			decline: function (dep) {
				if (dep === undefined) hot._selfDeclined = true;
				else if (typeof dep === "object" && dep !== null)
					for (var i = 0; i < dep.length; i++)
						hot._declinedDependencies[dep[i]] = true;
				else hot._declinedDependencies[dep] = true;
			},
			dispose: function (callback) {
				hot._disposeHandlers.push(callback);
			},
			addDisposeHandler: function (callback) {
				hot._disposeHandlers.push(callback);
			},
			removeDisposeHandler: function (callback) {
				var idx = hot._disposeHandlers.indexOf(callback);
				if (idx >= 0) hot._disposeHandlers.splice(idx, 1);
			},
			invalidate: function () {
				this._selfInvalidated = true;
				switch (currentStatus) {
					case "idle":
						currentUpdateApplyHandlers = [];
						Object.keys($hmrInvalidateModuleHandlers$).forEach(function (key) {
							$hmrInvalidateModuleHandlers$[key](
								moduleId,
								currentUpdateApplyHandlers
							);
						});
						setStatus("ready");
						break;
					case "ready":
						Object.keys($hmrInvalidateModuleHandlers$).forEach(function (key) {
							$hmrInvalidateModuleHandlers$[key](
								moduleId,
								currentUpdateApplyHandlers
							);
						});
						break;
					case "prepare":
					case "check":
					case "dispose":
					case "apply":
						(queuedInvalidatedModules = queuedInvalidatedModules || []).push(
							moduleId
						);
						break;
					default:
						// ignore requests in error states
						break;
				}
			},

			// Management API
			check: hotCheck,
			apply: hotApply,
			status: function (l) {
				if (!l) return currentStatus;
				registeredStatusHandlers.push(l);
			},
			addStatusHandler: function (l) {
				registeredStatusHandlers.push(l);
			},
			removeStatusHandler: function (l) {
				var idx = registeredStatusHandlers.indexOf(l);
				if (idx >= 0) registeredStatusHandlers.splice(idx, 1);
			},

			//inherit from previous dispose call
			data: currentModuleData[moduleId]
		};
		currentChildModule = undefined;
		return hot;
	}

7. module.hot.check 开始热更新

hotCheck的实现如下:

	function hotCheck(applyOnUpdate) {
		if (currentStatus !== "idle") {
			throw new Error("check() is only allowed in idle status");
		}
		return setStatus("check")
			.then($hmrDownloadManifest$)
			.then(function (update) {
				if (!update) {
					return setStatus(applyInvalidatedModules() ? "ready" : "idle").then(
						function () {
							return null;
						}
					);
				}

				return setStatus("prepare").then(function () {
					var updatedModules = [];
					currentUpdateApplyHandlers = [];

					return Promise.all(
						Object.keys($hmrDownloadUpdateHandlers$).reduce(function (
							promises,
							key
						) {
							$hmrDownloadUpdateHandlers$[key](
								update.c,
								update.r,
								update.m,
								promises,
								currentUpdateApplyHandlers,
								updatedModules
							);
							return promises;
						},
						[])
					).then(function () {
						return waitForBlockingPromises(function () {
							if (applyOnUpdate) {
								return internalApply(applyOnUpdate);
							} else {
								return setStatus("ready").then(function () {
									return updatedModules;
								});
							}
						});
					});
				});
			});
	}

可以看到check状态成功后会进入prepare状态,成功后会返回一个promise对象;

$hmrDownloadUpdateHandlers$.$key$ = function (
		chunkIds,
		removedChunks,
		removedModules,
		promises,
		applyHandlers,
		updatedModulesList
	) {
		applyHandlers.push(applyHandler);
		currentUpdateChunks = {};
		currentUpdateRemovedChunks = removedChunks;
		currentUpdate = removedModules.reduce(function (obj, key) {
			obj[key] = false;
			return obj;
		}, {});
		currentUpdateRuntime = [];
		chunkIds.forEach(function (chunkId) {
			if (
				$hasOwnProperty$($installedChunks$, chunkId) &&
				$installedChunks$[chunkId] !== undefined
			) {
				promises.push($loadUpdateChunk$(chunkId, updatedModulesList));
				currentUpdateChunks[chunkId] = true;
			} else {
				currentUpdateChunks[chunkId] = false;
			}
		});
		if ($ensureChunkHandlers$) {
			$ensureChunkHandlers$.$key$Hmr = function (chunkId, promises) {
				if (
					currentUpdateChunks &&
					$hasOwnProperty$(currentUpdateChunks, chunkId) &&
					!currentUpdateChunks[chunkId]
				) {
					promises.push($loadUpdateChunk$(chunkId));
					currentUpdateChunks[chunkId] = true;
				}
			};
		}
	};

以下面代码作为例子:

// src/index.js
import { addByBit, add } from './add';

export default function () {
    console.log('rm console loader test==', addByBit(1,2));
    return 'hello index file.......';
}


// src/foo.js
export default () => {
    console.log('hello webpack demos!')
    return 'hello webpack'
}


// src/add.js
// add function
export function add(a, b) {
    console.log('a + b===', a + b);
    return a + b;
}

// add by bit-operation
export function addByBit(a, b) {
    if (b === 0) return a;
    let c = a ^ b,
        d = (a & b) << 1;

    return addByBit(c, d);
}

  1. 我们更改src/index.js,将return 'hello index file.......';更改为return 'hello index file.';

触发更新时首先会发送ajax请求http://localhost:8081/index.3b102885936c5d7de6d5.hot-update.json3b102885936c5d7de6d5为oldhash,对应的返回为:

// 20221205151344
// http://localhost:8080/index.455e1dda0f8e3cbf9ba0.hot-update.json

{
  "c": [
    "index"
  ],
  "r": [],
  "m": []
}

c: chunkIds,
r: removedChunks,
m: removedModules

  1. 我们更改src/index.js,移除add.js文件引用

    export default function () {
        console.log('rm console loader test==');
        return 'hello index file.......';
    }
    

    本次更新后得到的[id]-[hash].hot-update.json为:

    // 20221205152831
    // http://localhost:8080/index.a75a200ec8e959f6ed40.hot-update.json
    
    {
      "c": [
        "index"
      ],
      "r": [
        
      ],
      "m": [
        "./src/add.js"
      ]
    }
    

另外,webpack还会通过JSONP方式请求http://localhost:8080/index.455e1dda0f8e3cbf9ba0.hot-update.js3b102885936c5d7de6d5为oldhash,对应的返回为待更新模块的更新后的chunk代码;

self["webpackHotUpdatedemo"]("index",{

/***/ "./src/index.js":
/*!**********************!*\
  !*** ./src/index.js ***!
  \**********************/
/***/ ((__unused_webpack_module, __unused_webpack___webpack_exports__, __webpack_require__) => {

eval("/* harmony import */ var _add__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./add */ \"./src/add.js\");\n\r\n\r\n/* harmony default export */ function __WEBPACK_DEFAULT_EXPORT__() {\r\n    console.log('rm console loader test==', (0,_add__WEBPACK_IMPORTED_MODULE_0__.addByBit)(1,2));\r\n    return 'hello index file.';\r\n}\n\n//# sourceURL=webpack://demo/./src/index.js?");

/***/ })

},
/******/ function(__webpack_require__) { // webpackRuntimeModules
/******/ /* webpack/runtime/getFullHash */
/******/ (() => {
/******/ 	__webpack_require__.h = () => ("8be9bd00cb51bd4d68f1")
/******/ })();
/******/ 
/******/ }
);

可以看到其内部调用了函数self["webpackHotUpdatedemo"],其定义如下,入参分别为chunkIdmoreModulesruntimeruntime用于更新最新的文件hash,

webpack的输出产物除了业务代码外,还有包括支持webpack模块化、异步模块加载、热更新等特性的支撑性代码,这些代码称为runtime

self["webpackHotUpdatedemo"] = (chunkId, moreModules, runtime) => {
/******/ 			for(var moduleId in moreModules) {
/******/ 				if(__webpack_require__.o(moreModules, moduleId)) {
/******/ 					currentUpdate[moduleId] = moreModules[moduleId];
/******/ 					if(currentUpdatedModulesList) currentUpdatedModulesList.push(moduleId);
/******/ 				}
/******/ 			}
/******/ 			if(runtime) currentUpdateRuntime.push(runtime);
/******/ 			if(waitingUpdateResolves[chunkId]) {
/******/ 				waitingUpdateResolves[chunkId]();
/******/ 				waitingUpdateResolves[chunkId] = undefined;
/******/ 			}
/******/ 		};

8. module.hot.apply

通过webpack/lib/hmr/JavascriptHotModuleReplacement.runtime.js中的相关逻辑做热更新;

  1. 首先需要删除过期的模块
    具体通过webpack/lib/hmr/JavascriptHotModuleReplacement.runtime.js中的dispose方法实现

  2. 将新的模块添加到modules中,并更新模块代码

    具体通过webpack/lib/hmr/JavascriptHotModuleReplacement.runtime.js中的apply方法实现。

    • insert new code
    • run new runtime modules
    • call accept handlers
    • Load self accepted modules

参考文献

  1. 轻松理解webpack热更新原理

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

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

相关文章

前端中的身份认证

一.Cookie 1.1.HTTP协议的无状态性 HTTP协议的无状态&#xff1a; 客户端的每次HTTP请求都是独立的&#xff0c;之间没有直接关系 服务器不会主动保留每次HTTP请求的状态 1.2.任何突破HTTP无状态的限制 发会员卡 注意&#xff1a; ​ 现实中,会员卡身份认证方式&#xff0c;在…

03 - 调试环境的搭建(Bochs)

---- 整理自狄泰软件唐佐林老师课程 1. Bochs&#xff08;另一款优秀的虚拟机软件&#xff09; 专业模拟x86架构的虚拟机 开源且高度可移植&#xff0c;由C编写完成 支持操作系统开发过程中的断点调试 通过简单配置就能运行绝大多数主流的操作系统 2. Bochs的安装与配置 下载…

[附源码]计算机毕业设计社区人员信息管理系统设计与实现Springboot程序

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

HRB系列直流隔离可调电源模块:用20K电位器和200K电位器区别

今天俞霖科技小编简谈如何合理地选用DC-DC模块电源&#xff0c;本文将从DC-DC模块电源开发设计的角度来简谈以上问题&#xff0c;以供广大技术设计人员参考。 DC-DC模块电源的众多优点是大家众所周知的&#xff0c;DC-DC模块电源以其体积小巧、性能卓异、使用方便的显著特点&a…

ArcGIS API For JavaScript(8)之使用动态图层dynamicLayers实现多图层合并截图

场景还原&#xff1a; 定位某个矢量图斑范围面&#xff0c;过滤展示该图斑&#xff0c;以图斑为中心&#xff0c;截图图斑周边并附带影像底图的截图。 在前端要实现地图截图&#xff0c;首先想到的是使用arcgis rest api中的export接口&#xff0c;这是没问题的&#xff0c;exp…

工业物联网关-modbus数据采集程序(1-程序设计)

写代码之前 最近代码写慢了&#xff0c;磨了好久都没开始动手写代码。考虑的东西越多越多&#xff0c;甚至自己都认为过虑了。就像这个程序&#xff0c;写代码之前估计花了大半天或者一天在思考怎么写&#xff0c;不知道是好事还是年纪大了。所以专门写篇文章&#xff0c;把自…

为什么要选择 Redis?

文章目录前言一、选型二、协议三、客户端1、常见 java 客户端2、常见可视化工具&#xff1a;四、Redis 生态1、模块2、代理3、其他前言 Redis&#xff08;Remote Dictionary Server&#xff09;&#xff0c;即「远程字典服务」是一个使用 ANSI C 编写的、开源的、支持网络的、…

【电力系统】基于YALMIP 的微网(光伏+风电+蓄电池+微电网+柴油机)优化调度模型附matlab代码

✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;修心和技术同步精进&#xff0c;matlab项目合作可私信。 &#x1f34e;个人主页&#xff1a;Matlab科研工作室 &#x1f34a;个人信条&#xff1a;格物致知。 更多Matlab仿真内容点击&#x1f447; 智能优化算法 …

Unity—UGUI

每日一句&#xff1a;读数、学习 去更远的地方&#xff0c;才能摆脱那些你不屑一顾的圈子 目录 InputFiled输入框 例&#xff1a;用户名和密码 Toggle组件 案例&#xff1a;冷却效果 InputFiled输入框 Text Component 输入文本组件 Text输入内容 Character Limit 输入字符…

宝塔后渗透-添加用户_反弹shell

更新时间&#xff1a;2022年11月21日 1. 背景介绍 对于想拿到bt后台来说&#xff0c;非常的艰难&#xff1a;无非是通过bypass之后提权&#xff0c;直接拿到服务器的root权限&#xff0c;然后再去宝塔后台。 当然&#xff0c;还有一种运气十分爆棚的方法&#xff1a;发现了b…

Qt的Q_UNUSED()函数的功能

目录Qt Assistant&#xff08;Qt 助手&#xff09;构建场景其他一些平替方法参考Qt Assistant&#xff08;Qt 助手&#xff09; 函数名直译过来是【不用的&#xff1b;从未用过的】。 碰到陌生的函数不要慌&#xff0c;直接Qt Assistant查一哈。 Q_UNUSED(name) Indicates to …

负载均衡器 OpenELB ARP 欺骗技术解析

作者&#xff1a;大飞哥&#xff0c;视源电子运维工程师&#xff0c;KubeSphere 用户委员会广州站站长&#xff0c;KubeSphere Ambassador。 K8S 对集群外暴露服务有三种方式&#xff1a;NodePort&#xff0c;Ingress 和 Loadbalancer。NodePort 用于暴露 TCP 服务(4 层)&#…

基于5G智能网关的水泵远程监控系统方案

方案背景 水泵作为一种常见的水务设备&#xff0c;在日常的生产、生活中发挥重要的作用。为了保证生产、生活用水&#xff0c;也为了预防异常天气带来的过度降水&#xff0c;水泵具备的供水、排水作用都是不可忽视的。然而&#xff0c;很多地区的水泵管理模式依然停留在专人看…

毕业设计-基于机器视觉的手写字识别系统

目录 前言 课题背景和意义 实现技术思路 实现效果图样例 前言 &#x1f4c5;大四是整个大学期间最忙碌的时光,一边要忙着备考或实习为毕业后面临的就业升学做准备,一边要为毕业设计耗费大量精力。近几年各个学校要求的毕设项目越来越难,有不少课题是研究生级别难度的,对本科…

变焦镜头内参数如何获得?

很多时候,我们是使用相应的棋盘格标定进行相机内参数的获取,但是对于相机而言,如果要是焦距范围是测量比较远的物体,那么我们要进行注意相应的内参数就是不能够使用棋盘格标定法进行获取,因为不准. 由于项目的需要,这里我就是需要进行探究一下,如何通过自己调试直接设定内参数…

CSS 实现卡片边框渐变动画

前言 &#x1f44f;CSS实现卡片边框渐变动画&#xff0c;速速来Get吧~ &#x1f947;文末分享源代码。记得点赞关注收藏&#xff01; 1.实现效果 2.实现步骤 父容器添加背景渐变色 <div class"card"></div>.card {background: linear-gradient(0deg, …

Tensorboader图片和模型可视化

非常简单&#xff0c;10分钟搞懂1 Tensorboader介绍2 进行写入和运行&#xff08;共2步&#xff09;3 图像显示3.1 显示图片3.2 显示模型3.3 动态显示学习率等参考文献1 Tensorboader介绍 它就是1个可视化工具&#xff0c;需要用pip进行下载&#xff1b; 下载以后进行显示&a…

SpringCloud框架(三):微服务优化,Feign的最佳实现方案

SpringCloud环境搭建&#xff1a;生产和消费 RestTemplate Spring章节复习已经过去&#xff0c;新的章节SpringCloud开始了&#xff0c;这个章节中将会回顾微服务相关 主要依照以下几个原则 每一个组件的Demo和Coding上传到我的代码仓库在原有基础上加入一些设计模式&#xf…

数据结构与算法之图的应用

一.树之习题选讲-Tree Traversals Again 树习题-TTA.1 题意理解 非递归中序遍历的过程 1. Push的顺序为先序遍历(pre)2. Pop的顺序给出中序遍历(in) 树习题-TTA.2 核心算法 上图分别是先序、中序、后序遍历通过规律我们可以看到他们之间的位置分配 //伪代码 void solve(int …

Zabbix在X86服务器上的部署流程

服务器资源:Centos7、X86架构 部署zabbix服务端 #设置SELinux 成为permissive模式临时关闭selinux防火墙 setenforce 0 #获取zabbix的下载源和更换阿里源 https://mirrors.aliyun.com/zabbix/zabbix/5.0/rhel/7/x86_64/zabbix-release-5.0-1.el7.noarch.rpm #解压zabbix包 …