跳到主要内容

node-worker-farm 多进程函数执行库源码解读和应用

· 阅读需 10 分钟

node-worker-farm 主要是用来将特定的逻辑,作为子进程的方式执行,由 worker-farm 来统一调度,分发调用任务。

模块构造函数

https://github.com/rvagg/node-worker-farm/blob/master/lib/index.js

可以看到构造函数主要是初始化 farm 容器,将给定的脚本文件路径加载进来等待调用的动作的分发。

可以看到入口有一个 farms 数组,用于存放 farm 容器,当你初始化多个 farm 实例的时候,都会将实例存储在这里,当你需要结束进程时,直接调用 node-worker-farm 暴露的 end 方法,即会销毁所有的 farm 实例。

不过这个特性也许是个坑吧,有人可能会不小心销毁掉不需要销毁的实例。

Farm

Farm 即是整个模块的核心了,它负责的功能有:

  1. 控制子进程的创建/销毁

  2. 新进来的函数执行任务的分发

  3. 控制调用的并发

各个参数的作用:

参数名说明默认值
maxCallsPerWorker每个子进程最多可以处理多少调用Infinity
maxConcurrentWorkers最大并发子进程数CPU 核数
maxConcurrentCallsPerWorker每个子进程最大的并发处理数10
maxConcurrentCalls全局最大并发处理数Infinity
maxCallTime单个 worker 最大处理时间Infinity
maxRetries最大重试次数Infinity
forcedKillTime在进程退出时,如果子进程未正常退出,则会强制退出100ms
autoStart初始化时,就自动最大数量的子进程false
onChild创建子进程时触发的函数空函数

函数调用

每次函数的执行是,构造了一个方法、参数的对象,发送给子进程排队处理。如果有限制最大全局并发数时,达到并发会抛出错误。

https://github.com/rvagg/node-worker-farm/blob/master/lib/farm.js#L312

每次调用函数在内部会执行 addCall 方法,这时就是构造了一个调用的对象信息。将调用信息存入全局队列中,执行 processQueue ,进行分发。

当前活跃的子进程个数小于最大并发子进程数时,创建一个子进程。这时,检查所有子进程正在处理的调用是否小于子进程的并发处理数。小于则将任务分配给这些子进程。

当发送任务时,如果有配置超时时间,则会注册一个超时回调,若超时,则整个子进程会被杀死,如果你的子进程中有多个调用正在执行,那么都会被干掉

处理完之后,通过 receive 方法进行后续的处理。

应用

现在默认的情况下,是最调用效率最优的方案,因为子进程被创建后,有调用的情况下,一直不会被销毁,省去了冷启动的时间。

但是,如果你需要在超时的情况下,cancel 掉正在处理的逻辑,maxCallTime 可以实现这个需求,但有个问题是,子进程被杀死时,所有其他的调用都会被终止,这不符合我们的期望,因此我们需要将 maxCallsPerWorker设为 1 ,让每个子进程同时只处理一个任务,处理完就退出,这样如果任务超时,不会对其他的任务造成影响。

思考

这样做的执行时间将会大幅增加,还有什么办法能够保证任务互不影响的情况,尽可能不降低执行效率呢。

目前想到的思路是,每次虽然只执行一个,但是每次执行完后不将子进程销毁,知道超时的时候才销毁一次,这样的话也可以减少每次创建子进程带来的开销。

你还有更好的方案吗?欢迎在下面留言讨论。

如何在 Console 中获取当前 redux 的 store 值

· 阅读需 2 分钟

如何在 Console 中获取当前 redux 的 store 值

如果你安装了 react developer tools ,你可以通过 $r.store.getState(); 来获取,不用改别的代码.

Note: 你需要先打开 react developer tool 之后,让他可用, 不然你回遇到 $r is not defined error


image.png


  1. 开打 developer tools
  2. 点击 「React」 tab
  3. 确认 provider 节点 (or topmost 节点) 现在是被选中的
  4. 然后输入 $r.store.getState(); or $r.store.dispatch({type:"MY_ACTION"}) 到你的浏览器 console

https://stackoverflow.com/questions/34373462/while-debugging-can-i-have-access-to-the-redux-store-from-the-browser-console

用户态和内核态

· 阅读需 3 分钟

印象

这两个名词可能我们曾经在一些文章中见过或者听说过,但是我们可能不知道它到底代表着什么意思。

只知道可能用户态可以做我们代码中编写的一些逻辑的事情,但是具体涉及到什么系统调用之类的操作就需要在内核态才能完成了。

这可能都只是初步印象。

初识

在 《许式伟的架构课》,我学习到,保护模式下,CPU 引入了“保护环 (Protecting Rings)”的概念,用来对 CPU 指令做权限划分。

如 Intel CPU 通常把权限划分为 Ring 0-3 四个等级。

操作系统内核在 Ring 0 (内核态),常规应用软件在 Ring 3 (用户态)。一次中断调用,不只是 “函数调用”,重要的是改变了执行权限,从用户态到了内核态。

因此,我们上面提到的一些情况,应该是由于我们在用户态时,执行权限较低,而某些需要在内核态才能调用的操作只能交给操作系统内核来完成了。

npm 升级版本号技巧

· 阅读需 3 分钟

较新的 npm 版本可以使用一个新特性来自动给你的 package.json 升级版本号。

为什么需要这个

有很多时候,我们都要去手动改这个版本号,然后再提交一个 commit ,感觉比较麻烦,而且可能有的开发者没有遵循语义化版本 semver 的规则,倒是别人在引用的时候会出现奇奇怪怪的问题。

这个命令怎么用呢

// 升级一个小版本号    1.1.1 -> 1.1.2
npm version patch -m " 升级到 %s ,因为修复了 xxx bug "

// 升级一个中版本号 1.1.1 -> 1.2.0
npm version minor -m " 升级到 %s ,因为添加了 xxxx 特性 "

// 升级一个大版本号 1.1.1 -> 2.0.0
npm version major -m " 升级到 %s ,因为有 xxxx 不兼容的特性 "

会自动修改 package.json 中的版本号,并添加一个 commit 。

git revert 一次 revert 多个 commit

· 阅读需 2 分钟

假如git commit 链是

A -> B -> C -> D

如果想把B,C,D都给revert,除了一个一个revert之外,还可以使用range revert

git revert B^..D 

这样就把B,C,D都给revert了,变成:

A-> B ->C -> D -> D'-> C' -> B'

用法

git revert OLDER_COMMIT^..NEWER_COMMIT

如果我们想把这三个revert不自动生成三个新的commit,而是用一个commit完成,可以这样:

git revert -n OLDER_COMMIT^..NEWER_COMMIT
git commit -m "revert OLDER_COMMIT to NEWER_COMMIT"

自然语言处理的一个库 nlp.js

· 阅读需 2 分钟

https://github.com/axa-group/nlp.js

"NLP.js" is a general natural language utilities for nodejs. Currently supporting:

  • Guess the language of a phrase
  • Fast levenshtein distance of two strings
  • Search the best substring of a string with less levenshtein distance to a given pattern.
  • Get stemmers and tokenizers for several languages.
  • Sentiment Analysis for phrases (with negation support).
  • Named Entity Recognition and management, multilanguage, and accepting similar strings, so the introduced text does not need to be exact.
  • Natural Language Processing Classifier, to classify utterance into intents.
  • Natural Language Generation Manager, so from intents and conditions it can generate an answer.
  • NLP Manager: a tool able to manage several languages, the Named Entities for each language, the utterance and intents for the training of the classifier, and for a given utterance return the entity extraction, the intent classification and the sentiment analysis. Also, it is able to maintain a Natural Language Generation Manager for the answers.

Node.js 中监听 redis key 过期事件

· 阅读需 2 分钟

It is in fact possible to listen to the "expired" type keyevent notification using a subscribed client to the specific channel ( and listening to its message event.

通过 subscribe client 可以监听 __keyevent@{db}__:expired ( db 是你传入的配置 ) 频道来接收过期的事件 ,

const redis = require('redis')
const CONF = {db:3}

let pub, sub

// Activate "notify-keyspace-events" for expired type events
pub = redis.createClient(CONF)
pub.send_command('config', ['set','notify-keyspace-events','Ex'], subscribeExpired)

// Subscribe to the "notify-keyspace-events" channel used for expired type events
function subscribeExpired(e, r){
sub = redis.createClient(CONF)
const expired_subKey = `__keyevent@${CONF.db}__:expired`

sub.subscribe(expired_subKey, function () {
console.log(' [i] Subscribed to "'+expired_subKey+'" event channel : '+r)
sub.on('message', function (chan, msg){
console.log('[expired]', msg)}
)
TestKey()
})
}

//例如,设置一个 key 并设置 10s 超时
function TestKey(){
pub.set('testing', 'redis notify-keyspace-events : expired')
pub.expire('testing', 10)
}

MongoDB 聚合查询 - 按时间分组统计

· 阅读需 1 分钟

DB search sql:

  1. 筛选出拥有 openId 的用户,对这些用户进行按年月日分组,并统计每天新增的用户数
db.user.aggregate(
[
{
$match: { "openId": { $exists: true } }
},
{
$group : {
_id : { month: { $month: "$createDate" }, day: { $dayOfMonth: "$createDate" }, year: { $year: "$createDate" } },
count: { $sum: 1 }
}
}
]
)

node.js 苹果 apn 推送 p12 证书使用方式

· 阅读需 1 分钟
// "apn": "^2.1.2",
var apn = require('apn');

var service = new apn.Connection(
"passphrase":"secret",
"pfx":"/path/to/cert.p12",
"production":false
});

var apnVoipProvider = new apn.Provider(voipOptions);

apnNotification = new apn.Notification();
apnNotification.badge = 1;
apnNotification.sound = 'msg.mp3';
apnNotification.alert = 'hello';
apnNotification.payload = {
title: notification.title, id: notificationID, path: notification.path,
};

apnVoipProvider.send(apnNotification, token);