晚十二点,床上
富婆:这是给你的500元,辛苦了
小蝌蚪:不辛苦,主人
富婆:我老公要回来了,你走吧
小蝌蚪:是,主人
说完小蝌蚪从三楼别墅跳了下去
。。。
小蝌蚪是一名程序员,也是一个技师
白天敲代码,晚上捏脚
由于常年敲代码,所以指法高超
时常把客户按到上天日龙
。。。
富婆年轻、有钱,但是缺爱
因为嫁给了80岁老公
小蝌蚪听话活好不粘人
深得富婆的欢心
。。。
又一次见面
富婆:好无聊,赶紧讨好我
小蝌蚪:主人,我用手指在您腿上敲一段“Dijkstra最短路径算法”吧
富婆:这招你用过了
小蝌蚪:那我用胸毛在您背上默写《javascript高级程序设计》
富婆:不想
小蝌蚪:那我用舌头给您表演一段口技
富婆:舌头?口技?
小蝌蚪:不要多想,是正规表演
于是小蝌蚪表演了一段舌头碎大石
富婆大喜,掌声经久不衰
。。。
表演完毕,富婆面色泛红
富婆:我喜欢你
小蝌蚪:我也喜欢我自己
富婆:你爱我吗
小蝌蚪:我不能对客户动心
富婆:我要如何才能得到你的心
小蝌蚪:杀了我,把心掏出来,嘻嘻
富婆:嘻嘻
。。。
富婆:对了,下周来参加我的闺蜜派对
小蝌蚪:不带你老公去吗
富婆:他80岁了,带去丢脸
小蝌蚪:那我可要加钱噢
富婆:多少
小蝌蚪:200块钱两小时,物美价廉,服务一流
富婆:deal
。。。
闺蜜派对
晚八点,高级酒店,各种炫富
闺蜜A:我买了个包,才10万,好便宜哦~
闺蜜B:我包了三个小奶狗,让他们打了一晚上斗地主
闺蜜C:我老公养了好多小动物,比如宝马、路虎、捷豹
富婆默默上前:我在北京二环买了套四合院
其他闺蜜瞬间不说话了
。。。
小蝌蚪意识到
这不是一个普通的饭局
而是一场闺蜜之间的装逼盛宴
小蝌蚪:你需要我做什么
富婆:我需要你打败她们的男宠
。。。
酒后三巡
到了炫耀男宠的阶段
每个闺蜜都带来了自己包养的男宠
有肌肉男、高级鸭、男模等等
肌肉男上台就展示八块腹肌和沙包一样大的裤裆
男模直接亮出大长腿和帅到掉渣的脸
高级鸭把舌头伸出来,在台上发出“略略略略略...”的声音
男宠们各有所长
赢得了场下闺蜜们的掌声
。。。
轮到小蝌蚪上台
一身屎绿色格子衬衫
呆若木鸡
主持人问:辣鸡,你要展示什么特长
小蝌蚪:舌头碎大石
主持人:除了这个还有吗
小蝌蚪:胸毛碎大石
主持人暴怒:我怀疑你是来砸场的
小蝌蚪:是的,我就是来砸场的
主持人叫来20个保安
十秒钟后保安全部倒地抽搐
身上被小蝌蚪的胸毛刻满了神秘javascript代码
。。。
主持人:爸爸对不起,刚才是我冒犯了
主持人:爸爸,麻烦跟观众介绍一下自己
小蝌蚪从书包里掏出一千万美金(富婆送)
点燃后从98楼外抛出
燃烧的钱漫天飞舞
小蝌蚪介绍道:“我是颜色不一样的渣男”
台下所有女人狂轰乱叫
因过度兴奋而七窍流血
男宠们看到自己的主子疯狂跪舔小蝌蚪
深感自己被绿了
主持人:爸爸您还有什么温柔点的特长展示给大家吗
小蝌蚪:我要唱一首孙艳枝的《绿光》,送给在座的各位男辣鸡
“
《绿光》
爱就像一道绿光
洒在你头上
都怪嫂子太漂亮
。。。
不要怪爸爸无情
是你们太辣鸡
啊~绿光~呀~绿光
照亮你我他
”
男宠们:小蝌蚪你太过分了
小蝌蚪:各凭本事做人渣
男宠们:你到底想干什么
小蝌蚪:我想在你们坟头上蹦迪
男宠们心态爆炸,疯狂抓头
从98楼跳了下去
小蝌蚪赢得了比赛
成为了第一名男宠
。。。
酒店阳台,微风拂过
富婆:今晚谢谢你
小蝌蚪:为了钱,应该的
富婆:我喜欢你
小蝌蚪:我也喜欢我自己
富婆:你爱我吗
小蝌蚪:我不能对客户动心
富婆:我要如何才能得到你的心
小蝌蚪:杀了我,把心掏出来,嘻嘻
富婆:嘻嘻
。。。
黑帮大佬
屋内,捏脚
富婆:我要死了
小蝌蚪:怎么了
富婆:我老公在外面有女人了
富婆:他是个黑帮大佬,要杀我,跟小三在一起
小蝌蚪轻抚富婆的脸:傻瓜,在这之前,我会杀了他
小蝌蚪和富婆拥吻在了一起
两嘴之间拉出一条亮丝
吻毕,小蝌蚪消失在夜空中
。。。
刺杀大佬
深夜十二点
黑帮大佬酒店房间门外
塞入了一张小卡片
上面写着“包小姐”
和一张小蝌蚪穿泳裤的照片
。。。
黑帮大佬有着诡异的癖好
喜欢胸毛浓密的小奶狗
小蝌蚪满足了他对一切小奶狗的幻想
。。。
房间门果然打开了
小蝌蚪用一千根胸毛扎入了大佬体内
大佬:你。。。你是谁
小蝌蚪:我是你爸爸
大佬:为什么要杀我
小蝌蚪:因为我是你爸爸
大佬:能不能不提爸爸两个字
小蝌蚪:我和你老婆有一腿
黑帮大佬,猝,享年80
。。。
由于刺杀了黑帮大佬
当晚回去的路上
小蝌蚪被1000名杀手全城追杀
用尽了最后一根胸毛
小蝌蚪寡不敌众
倒在了血泊里,奄奄一息
将死之际,富婆从杀手中出现
她微笑的捅了小蝌蚪一刀
把心掏了出来
小蝌蚪惊恐的望着她
富婆:我终于得到了你的心,嘻嘻
(完)
作者:第一名的小蝌蚪微信公众号:前端屌丝
github: https://github.com/airuikun/blog
《nodejs线上模块热部署原理与实现》
背景
大家都知道,nodejs启的后端服务,如果有代码变动,要重启进程,代码才能生效。
nodejs的进程在重启的时候,爸爸们去访问服务,就会出现短暂的 502 bad gateway
,爸爸们会不高兴
如果你的服务器加上了watch机制
当服务器上的代码频繁发生变动,或者短时间内发生高频变动,那就会一直 502 bad gateway
所谓“重启一时爽,一直重启一直爽”
近段时间在做云编译相关需求的时候,就出现了短时间内线上服务代码高频变动,代码功能模块高频更新,在不能重启服务的情况下,让更新的代码生效的场景。
这就涉及到一个热部署的概念,在不重启服务的情况下,让新部署的代码生效。
接下来我来给爸爸们讲解热部署的原理和实现方案
代码没法实时生效的原因
当我们通过require('xx/xx.js')
去加载一个功能模块的时候,node会把require('xx/xx.js')
得到的结果缓存在require.cache('xx/xx.js')
中
当我们多次调用require('xx/xx.js')
,node就不再重新加载,而是直接从require.cache('xx/xx.js')
读取缓存
所以当爸爸在服务器上修改xx/xx.js
这个路径下的文件时,node只会去读取缓存,不会去加载爸爸的最新代码
源码地址和使用
为了实现这个热部署机制,在网上到处查资料,到处踩坑才弄好
以下代码是提炼出来、完整可运行的热部署基础原理代码,大家可以基于这个代码去自行拓展:smart-node-reload
注意最新版本12版本的node运行会报错,官方对require.cache做了调整,已经上报问题给官方,建议使用nodejs版本:v10.5.0
git clone
下来以后,无需安装,直接运行
npm start
这时候就开启了热部署变动监听
如何看到效果呢
爸爸请看/hots/hot.js
文件
const hot = 1
module.exports = hot
将第一行代码改为const hot = 111
const hot = 111
module.exports = hot
这时候就能看到终端里监听到代码变动,然后动态加载你的最新代码并得到执行结果,输出为:
热部署文件: hot.js ,执行结果: { 'hot.js': 111 }
热部署服务监听到代码变动,并重新加载了代码,爸爸们就可以实时拿到最新代码的执行结果了,整个过程都在线上环境运行,node进程也没有重启
源码解析
loadHandlers主函数
const handlerMap = {};// 缓存
const hotsPath = path.join(__dirname, "hots");
// 加载文件代码 并 监听指定文件夹目录文件内容变动
const loadHandlers = async () => {
// 遍历出指定文件夹下的所有文件
const files = await new Promise((resolve, reject) => {
fs.readdir(hotsPath, (err, files) => {
if (err) {
reject(err);
} else {
resolve(files);
}
});
});
// 初始化加载所有文件 把每个文件结果缓存到handlerMap变量当中
for (let f in files) {
handlerMap[files[f]] = await loadHandler(path.join(hotsPath, files[f]));
}
// 监听指定文件夹的文件内容变动
await watchHandlers();
};
loadHandlers
是整个热部署服务的主函数,我们指定了服务器根目录下的hots
文件夹是用来监听变动和热部署的文件夹
用fs.readdir
扫描hots
文件夹下的所有文件,通过loadHandler
方法去加载和运行每一个扫描到的文件,将结果缓存到handlerMap
里
然后用watchHandlers
方法开启文件变动监听
watchHandlers监听文件变动
// 监视指定文件夹下的文件变动
const watchHandlers = async () => {
// 这里建议用chokidar的npm包代替文件夹监听
fs.watch(hotsPath, { recursive: true }, async (eventType, filename) => {
// 获取到每个文件的绝对路径
// 包一层require.resolve的原因,拼接好路径以后,它会主动去帮你判断这个路径下的文件是否存在
const targetFile = require.resolve(path.join(hotsPath, filename));
// 当你适应require加载一个模块后,模块的数据就会缓存到require.cache中,下次再加载相同模块,就会直接走require.cache
// 所以我们热加载部署,首要做的就是清除require.cache中对应文件的缓存
const cacheModule = require.cache[targetFile];
// 去除掉在require.cache缓存中parent对当前模块的引用,否则会引起内存泄露,具体解释可以看下面的文章
//《记录一次由一行代码引发的“血案”》https://cnodejs.org/topic/5aaba2dc19b2e3db18959e63
//《一行 delete require.cache 引发的内存泄漏血案》https://zhuanlan.zhihu.com/p/34702356
if (cacheModule.parent) {
cacheModule.parent.children.splice(cacheModule.parent.children.indexOf(cacheModule), 1);
}
// 清除指定路径对应模块的require.cache缓存
require.cache[targetFile] = null;
// 重新加载发生变动后的模块文件,实现热加载部署效果,并将重新加载后的结果,更新到handlerMap变量当中
const code = await loadHandler(targetFile)
handlerMap[filename] = code;
console.log("热部署文件:", filename, ",执行结果:", handlerMap);
});
};
watchHandlers
函数是用来监听指定文件夹下的文件变动、清理缓存更新缓存用的。
用fs.watch
原生函数监听hots
文件夹下文件变动,当文件发生变动,就算出文件的绝对路径targetFile
而require.cache[targetFile]
就是require
对targetFile
原文件的缓存,清除缓存用require.cache[targetFile] = null;
坑爹的地方来了,仅仅只是将缓存置为null,会发生内存泄露,我们还需要清除缓存父级的引用require.cache[targetFile].parent
,就是下面这段代码
if (cacheModule.parent) {
cacheModule.parent.children.splice(cacheModule.parent.children.indexOf(cacheModule), 1);
}
loadHandler加载文件
// 加载指定文件的代码
const loadHandler = filename => {
return new Promise((resolve, reject) => {
fs.readFile(filename, (err, data) => {
if (err) {
resolve(null);
} else {
try {
// 使用vm模块的Script方法来预编译发生变化后的文件代码,检查语法错误,提前发现是否存在语法错误等报错
new vm.Script(data);
} catch (e) {
// 语法错误,编译失败
reject(e);
return;
}
// 编译通过后,重新require加载最新的代码
resolve(require(filename));
}
});
});
};
loadHandler函数的作用是加载指定文件,并校验新文件代码语法等。
通过fs.readFile读取文件内容
用node原生vm模块vm.Script方法去预编译发生变化后的文件代码,检查语法错误,提前发现是否存在语法错误等报错
检验通过后,通过resolve(require(filename))方法重新将文件require加载,并自动加入到require.cache缓存中
结尾:
以上就是热部署的所有内容了,代码地址是:smart-node-reload
这个代码是我经过极简后的代码,方便大家阅读和理解,感兴趣的小伙伴可以通过这个代码去进行深一步拓展
哎。。。。写完这篇文章,已经是凌晨两点了,写文章真不容易,麻烦各位爸爸star、follow、点赞和关注,辛苦爸爸了
作者:第一名的小蝌蚪微信公众号:前端屌丝
github: https://github.com/airuikun/blog
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。