diff --git a/docs/interview/experiences/13202307handwriting.md b/docs/interview/experiences/13202307handwriting.md index cb4f243..205d2b6 100644 --- a/docs/interview/experiences/13202307handwriting.md +++ b/docs/interview/experiences/13202307handwriting.md @@ -690,6 +690,12 @@ thousand('123456789'); var num2 = '123456789.1234'; num2.replace(/(?!^)(?=(\d{3})+\.)/g, ','); // '123,456,789.1234' + +function thousand(str) { + let ans = []; + str = str.split('').reverse(); + for (let i = 0; i < str.length; i++) {} +} ``` ## 23.实现一个 node 异步函数的 promisify diff --git a/docs/interview/experiences/15202309handwriting.md b/docs/interview/experiences/15202309handwriting.md index 0d20f5a..b8a8cde 100644 --- a/docs/interview/experiences/15202309handwriting.md +++ b/docs/interview/experiences/15202309handwriting.md @@ -61,12 +61,12 @@ nav: * 41.增加数组原型 group 方法 * 42.Vue 中的响应式机制是如何实现的?请手写代码来实现数据劫持(数据劫持即数据变化时触发回调函数)的简单示例 * 43.算法:实现一个函数,将给定的十进制数转换为 36 进制表示 - * 44.迭代/递归的方式实现二叉树的层次遍历 + * 44.useState hook * 45.判断一个二叉树是否对称,即左子树和右子树是否镜像对称 * 46.给定一组乱序的区间,合并重叠的区间并返回结果。 * 47.多叉树, 获取每一层的节点之和 - * 48. - * 49. + * 48.按照版本号由小到大排序 + * 49.【代码题】实现一个拼手气抢红包算法 * 50. */ ``` @@ -1286,7 +1286,21 @@ console.log(res); ``` -## 44.迭代/递归的方式实现二叉树的层次遍历 +## 44.useState hook + +```js +const useState = (defaultVal) => { + const val = useRef(defaultVal); + const setVal = (newVal) => { + if (typeof newVal === 'function') { + val.current = newVal(val.current); + } else { + val.current = newVal; + } + }; + return [val, setVal]; +}; +``` ## 45.判断一个二叉树是否对称,即左子树和右子树是否镜像对称 @@ -1465,6 +1479,31 @@ var levelOrderDFS = function (root) { }; ``` +## 48.按照版本号由小到大排序 + +```js +// 样例输入:versions = ['0.1.1', '2.3.3', '0.302.1', '4.2', '4.3.5', '4.3.4.5'] +// 输出:['0.1.1', '0.302.1', '2.3.3', '4.3.4.5', '4.3.5'] +function sortVersion(nums) { + return nums.sort((a, b) => { + a = a.split('.'); + b = b.split('.'); + let len = Math.max(a.length, b.length); + for (let i = 0; i < len; i++) { + let A = +a[i] || 0; + let B = +b[i] || 0; + if (A == B) continue; + console.log(A - B, 'ab', A, B); + return A - B; + } + return 0; + }); +} +sortVersion(['0.1.1', '2.3.3', '0.302.1', '4.2', '4.3.5', '4.3.4.5']); +``` + +## 49.【代码题】实现一个拼手气抢红包算法 + ## 参考 - [字节跳动前端面经(3 年工作经验附详细答案)](https://mp.weixin.qq.com/s/MYfuUSNS7xIAT4RgZIMv0g) diff --git a/docs/interview/experiences/21leetcodeA.md b/docs/interview/experiences/21leetcodeA.md index fcd7c8b..83cf666 100644 --- a/docs/interview/experiences/21leetcodeA.md +++ b/docs/interview/experiences/21leetcodeA.md @@ -410,6 +410,10 @@ function numIslands(grid) { ## [17.合并两个有序数组](https://leetcode.cn/problems/merge-sorted-array/) ```js +// 输入:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3 +// 输出:[1,2,2,3,5,6] +// 解释:需要合并 [1,2,3] 和 [2,5,6] 。 +// 合并结果是 [1,2,2,3,5,6] ,其中斜体加粗标注的为 nums1 中的元素。 function merge(num1, m, num2, n) { for (let i = m; i < m + n; i++) { num1[i] = nums2[i - m]; @@ -512,6 +516,36 @@ function water(nums) { ``` +## [33.合并区间](https://leetcode.cn/problems/merge-intervals/) + +```js +// 输入:intervals = [[1,3],[2,6],[8,10],[15,18]] +// 输出:[[1,6],[8,10],[15,18]] +// 解释:区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6]. +function mergeArr(nums) { + nums.sort((a, b) => a[0] - b[0]); + let ans = []; + let pre = nums[0]; + for (let i = 1; i < nums.length; i++) { + let cur = nums[i]; + if (cur[0] > pre[1]) { + ans.push(pre); + pre = cur; + } else { + pre[1] = Math.max(cur[1], pre[1]); + } + } + ans.push(pre); + return ans; +} +mergeArr([ + [1, 3], + [2, 6], + [8, 10], + [15, 18], +]); +``` + ## [38.最长公共子序列](https://leetcode.cn/problems/longest-common-subsequence/) ```js @@ -537,3 +571,9 @@ function generate(n) { return res; } ``` + +## 50.比较版本号 + +```js + +``` diff --git a/docs/interview/experiences/22leetcodeB.md b/docs/interview/experiences/22leetcodeB.md index 3bfe967..8b721b6 100644 --- a/docs/interview/experiences/22leetcodeB.md +++ b/docs/interview/experiences/22leetcodeB.md @@ -46,6 +46,8 @@ nav: * 22.二叉树的第K小的元素 * 23.将有序数组展开为二叉搜索树 * 24.背包问题 + * 25.多个数组交集 + * 26.删除有序数组中的重复项 */ ``` @@ -90,6 +92,46 @@ var pathSum = function (root, targetSum) { ## 3.重排链表 +## 4.最长公共前缀 + +```js +// 输入:strs = ["flower","flow","flight"] +// 输出:"fl" +// 横行扫描 +function longFind(strs) { + if (!strs.length) return ''; + let prefix = strs[0]; + for (let i = 0; i < strs.length; i++) { + prefix = findL(prefix, strs[i]); + if (prefix.length == 0) break; + } + return prefix; +} +function findL(a, b) { + let len = Math.min(a.length, b.length); + let index = 0; + while (a[index] == b[index] && index < len) { + index++; + } + return a.slice(0, index); +} +longFind(['flower', 'flow', 'flight']); +// 纵向扫描 +function longFindHor(strs) { + if (!strs.length) return []; + let prefix = strs[0]; + for (let i = 0; i < prefix.length; i++) { + for (let j = 1; j < strs.length; j++) { + if (strs[j][i] != prefix[i]) { + return prefix.slice(0, i); + } + } + } + return prefix; +} +longFindHor(['flower', 'flow', 'flight']); +``` + ## 6.阶乘(迭代/递归/缓存) ```js @@ -354,3 +396,138 @@ var diameterOfBinaryTree = function (root) { return total; }; ``` + +## 24.背包问题 + +```js +function weightBag(weight, value, size) { + let len = weight.length; + let dp = new Array(len).fill().map(() => new Array(size + 1).fill(0)); + // 首行设置0 + for (let i = weight[0]; i <= size; i++) { + dp[0][i] = value[0]; + } + // 注意遍历顺序 + for (let i = 1; i < len; i++) { + for (let j = 0; j <= size; j++) { + if (weight[i] > j) { + dp[i][j] = dp[i - 1][j]; + } else { + dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]); + } + } + } + console.table(dp); + return dp[len - 1][size]; +} +weightBag([1, 3, 4], [15, 20, 30], 6); +``` + +## 25.多个数组交集 + +```js +/** + * 多个数组交集 [[3,1,2,4,5],[1,2,3,4],[3,4,5,6]] + */ +// 横向扫描 +function intersect2(nums) { + if (!nums.length) return []; + let prefix = nums[0]; + for (let i = 1; i < nums.length; i++) { + prefix = findT(prefix, nums[i]); + if (prefix.length == 0) break; + } + return prefix; +} +function findT(a, b) { + let ans = []; + for (let item of a) { + let index = b.indexOf(item); + if (index > -1) { + ans.push(item); + b.splice(index, 1); + } + } + return ans; +} +intersect2([ + [3, 1, 2, 4, 5], + [1, 2, 3, 4], + [3, 4, 5, 6], +]); + +// hash +function intersect3(nums) { + let m = new Map(); + let ans = []; + for (let item of nums) { + for (let i of item) { + if (!m.has(i)) { + m.set(i, 0); + } + let count = m.get(i); + m.set(i, count + 1); + } + } + for (let [key, value] of m.entries()) { + if (value == nums.length) { + ans.push(key); + } + } + return ans; +} +intersect3([ + [3, 1, 2, 4, 5], + [1, 2, 3, 4], + [3, 4, 5, 6], +]); +``` + +## [26.删除有序数组中的重复项](https://leetcode.cn/problems/remove-duplicates-from-sorted-array/) + +```js +// 输入:nums = [0,0,1,1,1,2,2,3,3,4] +// 输出:5, nums = [0,1,2,3,4] +/** + * @param {number[]} nums + * @return {number} + */ +var removeDuplicates = function (nums) { + for (let i = 0; i < nums.length; i++) { + if (nums[i] == nums[i + 1]) { + const v = nums.splice(i, 1); + i--; + } + } + return nums.length; +}; +removeDuplicates([1, 1, 2]); +function removeDuplicates2(nums) { + if (nums.length == 0) return 0; + let slow = 0, + fast = 1; + while (fast < nums.length) { + if (nums[fast] != nums[slow]) { + slow = slow + 1; + nums[slow] = nums[fast]; + } + fast = fast + 1; + } + return slow + 1; +} +//[1,4,1,6] +function once(nums) { + nums.sort((a. b) => a-b) + let ans = [] + for(let i=0;i Node.js 中的 worker_threads[4] 模块是用于创建多线程应用程序的官方模块。它允许在 Node.js 程序中创建和管理真正的操作系统线程,以实现并行处理和利用多核 CPU 的能力。 + - 利用的是 Node.js 的**事件循环机制和异步非阻塞的 I/O**操作。Node.js 使用事件驱动的模型来处理请求,当有请求到达时,Node.js 将其放入事件队列中,然后通过事件循环来处理这些请求。在等待 I/O 操作的过程中,Node.js 不会阻塞其他请求的处理,而是继续处理其他请求。这样,即使 JavaScript 是单线程的,但在实际运行中,多个请求可以同时处理,充分利用了多核系统的能力。 -## 9.pm2 守护进程原理是什么 +## [9.pm2 守护进程原理是什么]() + +守护进程是什么:守护进程是一个在后台运行并且不受任何终端控制的进程,并且能够因为某个异常导致进程退出的时候重启子进程。守护进程一般在系统启动时开始运行,除非强行终止,否则直到系统关机都保持运行.使用命令 ps axj 查看当前守护进程。 + +### 实现监听子进程退出 + +利用 NodeJS 的事件机制,监听 exit 事件 + +在 master.js 中使用 fork 创建子进程监听 exit 事件,1s 之后创建一个新的子进程 + +```js +// master.js +const { fork } = require('child_process'); + +const forkChild = () => { + const child = fork('log.js'); + child.on('exit', () => { + setTimeout(() => { + forkChild(); + }, 1000); + }); +}; +forkChild(); + +// log.js +const fs = require('fs'); +const path = require('path'); + +fs.open(path.resolve(__dirname, 'log.txt'), 'w', function (err, fd) { + setInterval(() => { + fs.write(fd, process.pid + '\n', function () {}); + }, 2000); +}); +``` + +使用 node master 启动项目之后,使用 kill 命令杀掉对应的子进程,能够成功重启子进程,守护进程生效~ + +### 如何退出终端运行? + +使用 setsid + +在 Node 中如何调用 setsid 方法呢? Node 尚未对 setsid 进程封装,但是我们可以通过 child_process.spawn 来调用该方法。 spawn 中有一个配置项 detached,当其 **detached: true** 时,会调用 setsid 方法 + +> 在非 Windows 平台上,如果 options.detached 设置为 true,则子进程将成为新进程组和会话的领导者。 子进程可以在父进程退出后继续运行。 + +### 开始实现 + +- 在 command 中使用 child_process.spawn(master) 创建子进程 +- 对进程 master 执行 setsid 方法 +- 进程 command 退出,进程 master 由 init 进程接管,此时进程 master 为守护进程 + +创建 command,使用 spawn 方法衍生子进程 + +```js +//command.js +const { spawn } = require('child_process'); + +spawn('node', ['./master.js'], { + detached: true, +}); + +process.exit(0); +``` + +当我们执行 node command 之后,我们的主进程就会关闭,但是我们的子进程还在继续运行,且不受终端控制,该进程就是我们创建的守护进程 ## 10.Nodejs 中的 cluster 模式和 fork 模式区别 @@ -199,16 +266,65 @@ readFileAsync('test.js').then( ); ``` -## 说说 Nodejs 异步 IO 模型 - ## 13.Node 的日志和负载均衡怎么做的? ## 14.Node 开启子进程的方法有哪些? +> child_process / cluster 模块 + ## 15.在操作系统中,进程和线程如何进行通信?请列举几种常见的进程间通信方式。 -## cluster 模块为什么可以让多个子进程监听同一个端口呢 +## 16.cluster 模块为什么可以让多个子进程监听同一个端口呢 开启多进程时候端口疑问讲解:如果多个 Node 进程监听同一个端口时会出现 Error:listen EADDRIUNS 的错误,而 cluster 模块为什么可以让多个子进程监听同一个端口呢?原因是 master 进程内部启动了一个 TCP 服务器,而真正监听端口的只有这个服务器,当来自前端的请求触发服务器的 connection 事件后,master 会将对应的 socket 句柄发送给子进程。而 child_process 操作子进程时,创建多个 TCP 服务器, 无论是 child_process 模块还是 cluster 模块,为了解决 Node.js 实例单线程运行,无法利用多核 CPU 的问题而出现的。核心就是通过 fork()或者其他 API,创建了子进程之后,父进程(即 master 进程)负责监听端口,接收到新的请求后将其分发给下面的 worker 进程,父子进程之间才能通过 message 和 send()进行 IPC 通信(Inter-Process Communication)。 Node 中实现 IPC 通道是依赖于 **libuv**, + +## 17.说说 Nodejs 异步 IO 模型 + +## 18.孤儿进程 / 僵尸进程 + +### 孤儿进程 + +父进程已经退出,它的一个或者多个子进程还在运行,上述的子进程会成为孤儿进程孤儿进程会被 **init** 进程(pid 为 1)收养,并由 init 进程完成对它们的状态收集工作,init 进程会循环 wait 回收资源,孤儿进程无害 + +### 僵尸进程 + +主进程通过 fork 创建了子进程,如果子进程退出之后父进程没有调用 wait/waitpid 获取子进程的状态信息,那么子进程中保存的进程号/退出状态/运行时间等都不会被释放,进程号会一直被占用 + +```js +const fork = require('child_process').fork; + +zombie(); + +function zombie() { + const worker = fork('./worker.js'); + console.log(`Worker is created, pid: ${worker.pid}, ppid: ${process.pid}`); + while (1) {} // 主进程永久阻塞 +} +``` + +该示例之所以能够成为僵尸进程是因为 while(1){}吃满了父进程的 CPU,无法处理子进程的退出信号。 + +修改上述代码,子进程退出之后,父进程可以监听到,就不会有僵尸进程的产生。 + +```js +const fork = require('child_process').fork; + +zombie(); + +function zombie() { + const worker = fork('./worker.js'); + worker + .on('exit', () => { + console.log('exit'); + }) + .on('close', () => { + console.log('close'); + }); +} +``` + +## 参考 + +- [nodejs 线程和进程](https://mp.weixin.qq.com/s/huyn95OyOz45J93B3WGXdA) diff --git a/docs/interview/experiences/practise/202312/1221.js b/docs/interview/experiences/practise/202312/1221.js index 3f15df8..1d44a7e 100644 --- a/docs/interview/experiences/practise/202312/1221.js +++ b/docs/interview/experiences/practise/202312/1221.js @@ -3,4 +3,136 @@ * 2.两个字符串对比, 得出结论都做了什么操作, 比如插入或者删除 * 3.背包问题 * 4.多个数组合并/交集/并集(最优解) + * 5.比较版本号 */ + +function removeDep(arr) {} +removeDep([1, 1, 2, 3]); + +/** + * 多个数组交集 [[3,1,2,4,5],[1,2,3,4],[3,4,5,6]] + */ +// 横向扫描 +function intersect2(nums) { + if (!nums.length) return []; + let prefix = nums[0]; + for (let i = 1; i < nums.length; i++) { + prefix = findT(prefix, nums[i]); + if (prefix.length == 0) break; + } + return prefix; +} +function findT(a, b) { + let ans = []; + for (let item of a) { + let index = b.indexOf(item); + if (index > -1) { + ans.push(item); + b.splice(index, 1); + } + } + return ans; +} +intersect2([ + [3, 1, 2, 4, 5], + [1, 2, 3, 4], + [3, 4, 5, 6], +]); + +// hash +function intersect3(nums) { + let m = new Map(); + let ans = []; + for (let item of nums) { + for (let i of item) { + if (!m.has(i)) { + m.set(i, 0); + } + let count = m.get(i); + m.set(i, count + 1); + } + } + for (let [key, value] of m.entries()) { + if (value == nums.length) { + ans.push(key); + } + } + return ans; +} +intersect3([ + [3, 1, 2, 4, 5], + [1, 2, 3, 4], + [3, 4, 5, 6], +]); +var intersect1 = function (nums1, nums2) { + let ans = []; + for (let item of nums1) { + let index = nums2.indexOf(item); + if (index != -1) { + ans.push(item); + // nums2[index] = false; + nums2.splice(index, 1); + } + } + return ans; +}; +intersect1([2, 2], [1, 2]); + +// 双指针 +var intersect = function (nums1, nums2) { + nums1.sort((a, b) => a - b); + nums2.sort((a, b) => a - b); + let l = 0, + r = 0, + res = []; + while (l < nums1.length && r < nums1.length) { + if (nums1[l] == nums2[r]) { + res.push(nums1[l]); + l++; + r++; + } else { + if (nums1[l] < nums2[r]) { + l++; + } else { + r++; + } + } + } + return res; +}; + +// 两个数组交集 +var intersection = function (nums1, nums2) { + let a = [...new Set(nums1)]; + let b = [...new Set(nums2)]; + let arr = []; + for (let i of a) { + if (b.includes(i)) { + arr.push(i); + } + } + return arr; +}; + +// 双指针 +function reorderLink(root) { + if (!root) return; + let cur = root; + let map = []; + while (cur) { + map.push(cur); + cur = cur.next; + } + let i = 0; + let j = map.length - 1; + while (i < j) { + map[i].next = map[j]; + i++; + if (i === j) { + break; + } + map[j].next = map[i]; + j--; + } + map[j].next = null; +} diff --git a/docs/interview/experiences/practise/202312/1223.js b/docs/interview/experiences/practise/202312/1223.js new file mode 100644 index 0000000..62e1af3 --- /dev/null +++ b/docs/interview/experiences/practise/202312/1223.js @@ -0,0 +1,52 @@ +/** + * 1.多数组取交集 + * 2.排序多版本号 + * 3. + */ + +class RedPackage { + money = 0; + count = 0; + _remain = 0; + + constructor(money, count) { + this.money = money; + this.count = count; + this._remain = money; + } + + openRedPackge() { + // 已经抢完了 + if (this.count <= 0) { + console.log('红包已经被抢完啦~'); + return; + } + + // 只剩一个红包 + if (this.count === 1) { + this.count--; + console.log(this._remain); + return; + } + + const ratio = Math.random() * (this._remain / this.money); + // 这里会涉及到一个JS计算精度的问题 + // 正常应该用第三方库或者字符串算法实现一个精准的加减乘除 + // 这里为了简单就这么直接做了 + let youGet = (this.money * ratio).toFixed(2); + const tempRemain = +(this._remain - youGet).toFixed(2); + const allLeast = this.count * 0.01; + + // 如果剩余的金额不够每人一分钱,那么需要减少本次获得的金额 + if (tempRemain < allLeast) { + youGet = +(this._remain - allLeast).toFixed(2); + this._remain = allLeast; + } else { + this._remain = tempRemain; + } + console.log(youGet); + this.count--; + } +} +var t = new RedPackage(100, 10); +console.log(t, 'ttt'); diff --git a/docs/node/eventLoop.md b/docs/node/eventLoop.md index a0361d3..a49ffad 100644 --- a/docs/node/eventLoop.md +++ b/docs/node/eventLoop.md @@ -139,7 +139,7 @@ Node 中的 Event Loop 和浏览器中的是完全不相同的东西。 Node 的 - 开始执行宏任务,上面 6 个阶段。从第 1 个阶段开始,执行相应每一个阶段的宏任务队列中所有任务。(每个阶段的宏任务队列执行完毕后,开始执行微任务),然后在开始下一阶段的宏任务,依次构成事件循环。 - timers Queue -> 执行微任务 -> I/O Queue -> 执行微任务 -> Check Queue 执行微任务 -> Close Callback Queue -> 执行微任务 -在 node 环境下,**process.nextTick**的优先级高于 Promise.then,可以简单理解为在宏任务结束后会先执行微任务队列中的 nextTickQueue 部分,然后才会执行微任务中的 Promise.then 部分 +在 node 环境下,**process.nextTick**的优先级高于 **Promise.then**,可以简单理解为在宏任务结束后会先执行微任务队列中的 nextTickQueue 部分,然后才会执行微任务中的 Promise.then 部分 如此反复,无穷无尽…… @@ -214,3 +214,5 @@ setTimeout(function () { - [JS 运行机制](https://juejin.cn/post/6976927131095793678?searchId=20231201210755359B842682307A9F6668) - [「硬核 JS」一次搞懂 JS 运行机制](https://juejin.cn/post/6844904050543034376?searchId=20231201233712D5595BAB6E2502B738BB) + +- [node 端事件循环](https://mp.weixin.qq.com/s/huyn95OyOz45J93B3WGXdA) diff --git a/docs/node/study/2cluster.md b/docs/node/study/2cluster.md index 81dce1f..dbade98 100644 --- a/docs/node/study/2cluster.md +++ b/docs/node/study/2cluster.md @@ -111,7 +111,7 @@ Node.js 中的进程 Process 是一个全局对象,无需 require 直接使用 - 三个标准流:process.stdout 标准输出、process.stdin 标准输入、process.stderr 标准错误输出 - process.title 指定进程名称,有的时候需要给进程指定一个名称 -### 创建多进程 child_process、cluster 的应用 +#### 创建多进程 child_process、cluster 的应用 Node.js 提供了 child_process 模块,用于创建和管理子进程。通过**child_process**模块,我们可以在 Node.js 中创建新的进程,与其进行通信,并监视其状态。以下是一个简单的示例,演示了如何在 Node.js 中创建一个子进程并与主进程通信: 开启一个 http 服务,并通过 require('child_process').fork 创建一个子进程: @@ -145,7 +145,7 @@ server.listen(3000, () => { }); ``` -### 创建一个含有大量计算任务的处理逻辑 +#### 创建一个含有大量计算任务的处理逻辑 ```js // compute.js @@ -174,7 +174,7 @@ process.on('message', (msg) => { 在上面的示例中,我们创建了一个 http 服务,并在接口http://127.0.0.1:3000/compute接口中使用require('child_process').fork()创建了一个子进程,将大量的计算逻辑放在了子进程中,这样一来,当我们频繁请求http://127.0.0.1:3000/compute接口时,我们的node服务就会并发处理这些计算逻辑密集型的逻辑,从而让接口有更快的响应。 试想如果此时没有开启子进程,而是将大量计算逻辑放到主进程,当有大量请求时会发生什么? 答案:会变成每次请求都是同步的,前一个请求处理完毕,才会处理下一个,时间就会拉长,后面的请求响应就会变慢。 -### 使用 cluster 创建多进程 +#### 使用 cluster 创建多进程 ```js const http = require('http'); @@ -200,6 +200,8 @@ if (cluster.isMaster) { } ``` +cluster 模块调用**cluster.fork()**来创建子进程,该方法与 child_process 中的 fork 是同一个方法。 cluster 模块采用的是经典的主从模型 Master-worker 模式,Cluster 会创建一个 master,然后根据你指定的数量复制出多个子进程,可以使用**cluster.isMaster**属性判断当前进程是 master 还是 worker(工作进程)。由 master 进程来管理所有的子进程,主进程不负责具体的任务处理,**主要工作是负责调度和管理**。 cluster 模块同时实现了负载均衡调度算法,在类 unix 系统中,cluster 使用轮转调度(round-robin),node 中维护一个可用 worker 节点的队列 free,和一个任务队列 handles。当一个新的任务到来时,节点队列队首节点出队,处理该任务,并返回确认处理标识,依次调度执行。而在 win 系统中,Node 通过 Shared Handle 来处理负载,通过将文件描述符、端口等信息传递给子进程,子进程通过信息创建相应的 SocketHandle / ServerHandle,然后进行相应的端口绑定和监听,处理请求。 + ### Pm2 中的 cluster 模式和 fork 模式区别 fork 模式,**单实例多进程**,常用于多语言混编,比如 php、python 等,不支持端口复用,需要自己做应用的端口分配和负载均衡的子进程业务代码。缺点就是单服务器实例容易由于异常会导致服务器实例崩溃。 @@ -208,6 +210,10 @@ cluster 模式,**多实例多进程**,但是只支持 node,端口可以复 ## 多个进程为什么可以监听同一个端口? +开启多进程时候端口疑问讲解:如果多个 Node 进程监听同一个端口时会出现 Error:listen EADDRIUNS 的错误,而 cluster 模块为什么可以让多个子进程监听同一个端口呢? + +原因是**master 进程内部启动了一个 TCP 服务器,而真正监听端口的只有这个服务器**,当来自前端的请求触发服务器的 connection 事件后,master 会将对应的 socket 句柄发送给子进程。而 child_process 操作子进程时,创建多个 TCP 服务器, 无论是 child_process 模块还是 cluster 模块,为了解决 Node.js 实例单线程运行,无法利用多核 CPU 的问题而出现的。核心就是通过 fork()或者其他 API,创建了子进程之后,**父进程(即 master 进程)负责监听端口,接收到新的请求后将其分发给下面的 worker 进程**,父子进程之间才能通过 message 和 send()进行 IPC 通信(Inter-Process Communication)。 + ## 什么是 IPC 通信,如何建立 IPC 通信?什么场景下需要用到 IPC 通信? IPC (Inter-process communication) ,即**进程间通信技术**,由于每个进程创建之后都有自己的独立地址空间,实现 IPC 的目的就是为了进程之间资源共享访问,实现 IPC 的方式有多种:管道、消息队列、信号量、Domain Socket,Node.js 通过 **pipe** 来实现。看一下 Demo,未使用 IPC 的情况 @@ -254,6 +260,31 @@ I am worker, PID: 42474 第一个问题,Node.js 是单线程还是多线程?这个问题是个基本的问题,在以往面试中偶尔提到还是有不知道的,Javascript 是单线程的,但是作为其在服务端运行环境的 **Node.js** 并非是单线程的。 +```js +const http = require('http'); + +const server = http.createServer(); +server.listen(3000, () => { + process.title = '测试进程线程数量'; + console.log('进程id', process.pid); +}); +``` + +![Nodejs 7个线程](image.png) + +创建了 http 服务,开启了一个进程,都说了 Node.js 是单线程,所以大家可能认为 Node 启动后线程数应该为 1,让我们使用 Mac 自带的活动监视器搜索进程 pid 来查看一下具体是几个线程: + +可以看到线程数量是 7(macbook pro),但是为什么会开启 7 个线程呢?难道 Javascript 不是单线程不知道小伙伴们有没有这个疑问? 解释一下这个原因: Node 中最核心的是 v8 引擎,v8 是一个执行 JS 的引擎. 包括我们熟悉的编译优化, 垃圾回收等等.在 Node 启动后,会创建 v8 的实例,这个实例是多线程的。 + +- 主线程:编译、执行代码。 +- 编译/优化线程:在主线程执行的时候,可以优化代码。 +- 分析器线程:记录分析代码运行时间,为 Crankshaft 优化代码执行提供依据。 +- 垃圾回收的几个线程。所以大家常说的 Node 是单线程的指的是 JavaScript 的执行是单线程的(开发者编写的代码运行在单线程环境中),但 Javascript 的**宿主环境**,无论是 Node 还是浏览器都是多线程的 + +> js 执行是单线程,代码运行在单线程环境中 + +> js 的宿主环境是多线程 + 第二个问题,Javascript 为什么是单线程?这个问题需要从浏览器说起,在浏览器环境中对于 DOM 的操作,试想如果多个线程来对同一个 DOM 操作是不是就乱了呢,那也就意味着对于 DOM 的操作只能是单线程,避免 DOM 渲染冲突。在浏览器环境中 UI 渲染线程和 JS 执行引擎是互斥的,一方在执行时都会导致另一方被挂起,这是由 JS 引擎所决定的。 ## 关于守护进程,是什么、为什么、怎么编写? @@ -280,6 +311,10 @@ child.stdout.pipe(process.stdout); // 将子进程的输出做为当前进程的 - 在 **/usr/local/bin** 目录下创建一个软链文件 sudo ln -s /${dir}/hello.js /usr/local/bin/hello,文件名就是我们在终端使用的名字 - 终端执行 hello 相当于输入 **node hello.js** +## 消息队列 + +客户端同时产生大量网络请求,服务器承受能力有一定的限制 + ### 参考 - [Nodejs 进阶:解答 Cluster 模块的几个疑问](https://juejin.cn/post/6844904087771693070) diff --git a/docs/node/study/image.png b/docs/node/study/image.png new file mode 100644 index 0000000..7eae2dc Binary files /dev/null and b/docs/node/study/image.png differ