[website]

http://wo-auto-read.noradle.com:8016

[deploy]

# 从开发者机器同步软件到目标服务器
rsync -av -C -e "ssh -p 60222" ~/dev/project/node-queue-pool node@61.181.22.93:


和其他异步支持模块的比较
======================

尝试将 async code 改为 sync 模式(如tamejs) 其实是不必要的。

因为本来我们就是异步并不能等介于同步，因为异步的应用处理模式更为灵活多变，无法相当于一个同步模式。

即执行异步，阻塞，完成后继续。

比如说同时发起多个异步的情况，怎么去阻塞呢？

再有如果可以并发的请求要控制并发度(不要一起一窝蜂的发出)，使用 temperjs 怎么实现呢，因此最简单的方式还是适合 node-queue-pool


step/tampjs is just too magic, node-queue-pool/async-builder is just basic of js.


[tampjs/streamline]
* too magic
* IDE support
* development/deploy operation more complex
* debug
* lost for scope
* vulnerable, easy to break


【范例任务】

这些任务的特点都是不能一下子将全部任务都放入queue中，而是只放最少可能完成人数的数量，
然后 queue-pool 记录返回结果，并且调用检查过程，
如果通过，则结束队列；如果不通过，继续执行新请求

## 根据IP查找地理位置，知道有2个结果完全相同的出现才作为正式值返回，同时发出两个请求。

不是第一个返回时，马上发起第三个连接。而是要等待第二个完成后若和第一个结果不同才发起连接

## 找十个符合要求的东西

同时发出十个请求，当存在不符合的情况时，马上发出补充请求，直到有十个请求符合为止
每个并发返回 fin(err) 后，在 on(error) 中继续执行新请求，不必放到 queue-pool 中

设想
=====

## wrap this as a promise

放到 async-builder 中的函数都会有一个 this 指向一个对于的 promise
因此可以将 cb async API 都改造成返回 promise
这样

## generator

function wrapper(args) {
  var start = 1;
  var states;  multiple execution of this will maintain state in these variables
	return function next() {
	  ... do something
	  return start++;
	}
}
var fn = wrapper(...);
fn.next();
fn.next();

RateControl
=============

  比如控制每秒钟的数量。

方案一：使用 setInterval，每次只允许发送一条短信，每秒触发n次。
	此方案发送太分散，网络效率较低，虽然容易实现，但不推荐。

方案二：使用 setInterval 单位固定为秒，每次批量执行不超过指定条数。
  此方案先将所有待发送短信放到队列
，然后每秒批量取出n条(队列不足n条取所有)
，可以设定并发为每秒允许数
，然后一次性批量发出
。但是对于特定短信的发送
，就会存在延迟
，比如说，没赶上上一批次的话
，就要等下一秒才能发送
，即便上一批次根本就没有填满限量
。

方案三：不使用 setInterval，但是 SuperQueue 记录一旦超过指定速率，就自动暂停
  队列执行的并发数不限制，但是一旦超过速率，就要停止
。技术方法如下
，第一条短信到达时开始计数，并且同时记录下开始时间
，在开始时间内没有超过指定时间的话
，就放开执行，
，否则就停止执行，状态设定为超速状态
，然后定时到开始时间加时间间隔单位解除该状态
，并且重新记录开始时间
，并且将队列中内容继续发送
。采用此方案的好处时
，特定短信的时延较小（不会出现最大可能1s的机制时延）
，并发支持多种速率限制规则(没如说每s多少条加上每分钟多少条)
。因此采用方案3
。

   自然时段计数就是从系统启动时按照 interval 定时清除计数器，
而接受器在计数不超过指定单位时都直接执行，否则入队列。
自然时段计数对于 javascript 这样的异步语言比较易用。
但是对于基于线程的语言就比较麻烦，因为需要额外的定时器线程去做计数器清零。
实现方式非常简单，就是设定 interval 定时器，
定期调用 resume 和计数清0.


   对于 java 等线程语言，使用收到第一条后进行记录起始时间的方式可以在单线程完成上述控制。
因此目前短信网关很有可能采用的是上述机制。
当然，线程语言不记录第一条开始时间而直接将当前时间和固定的时间段比较也没有问题。


为什么需要 super-queue 和他的各个各样的 plugin
==========================================

  每当我们执行异步操作时，其实基本上就是执行 I/O 相关的操作，因为非 I/O 操作基本都只需要同步执行。
那么这些 I/O 操作无非是网络相关或文件相关，分别对应对端服务器和文件系统服务，
由于对端(网络服务或文件服务)的服务能力是有限的，因此需要控制所有到对端的限制器，比如：
1. 对方是基于进程和线程的服务，存在最大进程数或最大线程数的限制，而每个进程或线程同时只能处理一个请求。
这时，就需要向对方发出请求的操作应用并发控制限制器。
2. 对方能处理请求的速率是有明确限定的，比如每秒钟不超过 30 个请求
3. 需要记录所有操作的性能统计数据
4. 需要连接池，当能从连接池中取出空闲连接时才能发出请求
5. 暂时排队的请求必须确保能够在持有的 nodejs 进程失败重启后恢复，不能丢失
  获取 NodeJS 的 http client 基础模块应该基于 super-queue 来设计。


resource, limiter(resource), queue, cache 四元素架构
-----------------------------------------

异步调用并非直接可行，往往首先要建立到对端的连接，甚至将要进行认证等操作，然后才能对其发起异步请求。

比如：
   当文件系统剩余空间超过100M时，可以向其中写自动下载图片，否则就关掉。
同时，当特定文件系统的磁盘I/O利用率或等待I/O过多时，关闭该磁盘上各个文件系统的写权限，清除资源。

所以说，super-queue 是普遍需要的异步操作基础设施。


cache
--------------

option.cacheKey 存在的话就作为 ._cache[option.cacheKey] = result
cache 是否需要作为一种 plugin 呢？
答案是不是 plugin，而是同 queue 一样，可以 .setCache 指定每个 cache 类型帮助实现缓存提速。
cache 类必须实现 .check(option) 和 .save(option, result) 两个接口。
cache 可能会自动清除长期存储的的，或者LRU较短的项目，实现执行代价越大保留时间越长的策略
cache 会定时或在保存条目数量或总空间超过一定值后进行启动清除操作，
cache 可能会将条目保存到如 redis 等快速数据库中，这样node重启后依然可以从本地 redis 中恢复缓存，
cache 可能不采用hash object方式存储，可以会将 key 先进行 md5 hash 在存储，或者按 key 分层存储。
cache 行为和每个 option.cacheXXX 属性相关，属性名由 cache 类自行指定
因此需要做成插件，通过 super-queue.useCache(cache) 来指定

cache 检测会在 .exec 前时进行检测，包括在 .enqueue 和 ._resume 时，如果发现有缓存可用，
对于在 .enqueue 阶段就发现命中的，不进行 .check .count .actcb 操作。
对于在 ._resume 阶段发现命中的，不进行 .check .count .actcb 操作。
各个 limiter 不会 cache hit 的请求进行阻拦和跟踪统计，
会将 option.cacheResult 填写内容，并且直接执行 Action，
Action 自己需要看看 option.cacheResult 是否存在，存在就应该直接返回。
cache hit 统计应该由 cache 类完成。

各个 plugin 如限速等如何要和 cache 一起使用的话，最好进行调整，
当然，按理说 cache 执行应该不受任何 limiter 的限制才对。

function() {
  var result = cache.isHit(key);
  if(result)
  call_async(key, function(err, result) {
    ...
  }
}

async(key,callback(err,result));

cache 的另外一个作用还是，将该请求拦截并忽略，防止进入无限循环，
比如说对于网站连接覆盖测试来说，要模拟点击所有的网页链接，
但是点过的连接就不要再点击了，因此需要记录是否点击过了。
最简单的方式就是返回 result 为 true 代表执行过了，
然后在act中首先检查 option.cacheResult 是否为 true，如果是那么就不要再重复执行了。

resource pool
------------------

enqueue 时必须确保资源池

general throttler
==================

TQ.enqueue(act,options) 执行一个动作，带有相关属性
act(fin(err,options)) 执行完动作，可以调用 fin，报告错误和其他 option 信息

TQ.setXXX, TQ.adXXX
TQ.setMaxOnWay or TQ.setMaxConcurrency(maxConcurrency,ifUseSlot)
TQ.setRate(span,maxSpanLimit)
TQ.setTimeout(timeout, ifRetry)

TQ.addThrottler(a instance of throttoler)
TQ 会记录下所有的 Throttler，然后应用他们。
Throttler 可能是速率控制，可能是并发，各种各样。

todos
--------

### core

* limiter call sq.resume, when? by what way?
* urgent call will always be executed immediately (may be unnecessary)
* timeout set, how timeout affect limiters, how timeout and retry
* how to control by channel
* discard action when check throw error

### queue behavior

* priority queue, de-queue by lower priority first
* persist call arguments in outside database instead of in-process memory
  , like redis, so we gain the power of reliable execution.
* pending execution then node crash
* if there are onway executions, node can not be allowed to quit,
  and must wait for reply

Design
===========

1. versatile queue types: any queue

  .shift .push .length .head [0]

2. plugins: any limiter/controller, any logger/tracer/stater

	.count .check .onActCB

	请将最严厉的limiter在最前加入，以避免执行无谓的其他limiter.check操作。

3. result synthesize


什么时候应该 resume
-----------------

* 对于速率控制来说，在当前控制时段已过时，就应该要求执行 resume
* 对于并发控制来说，在执行的请求完成后，释放出一个slot，就应该要求执行 resume
，因此并发控制监听动作结束的成员过程应该触发队列执行 resume
* 对于允许时段的控制来说，当允许时段到来时，就应该执行 resume
* 对于攒批量再处理的控制来说，当一批的量到达后，就应该执行 resume
，这时批处理需要了解积压情况
，如果自己记录，那么是不可能的事情，因为它只能在 .check .count 中作为
，因此只能依赖于查其所控制的 SuperQueue


什么叫做完
---------------

因为只有当执行指定数量的动作后才有所谓做完的概念，因此 SuperQueue 不内置 drain/end 事件，而是通过 plugin 实现。
目前设计在 Stater 中实现。
当 drain 时，就是当 resume 时发现队列空，也就是执行计数等于执行完计数。
end 就是当设置完 end 后，收到 drain 的事件。


.suspending 需不需要，要不要内置手动 resume 和 suspend
------------------------------------------------------------

可以，因为实际上各个 plugin 都可能标明目前不能执行请求，
比如说自动阅读含有以下 limiters
1. 手动的控制，内部含有是否暂停的标志
2. 自动时段控制，不能在晚上睡觉时间模拟阅读，因为太假了。内部用定时器设置开关标志，.check 看该标志
3. 每批次 n 条，记录累计到 n 条，设置内部开关标志，重新登录后手动调用 .resume() 继续
4. 每日 n 条，同上；但是到了次日，使用定时器自动将技术器归零，并恢复，它使用 schedule 包
5. 阅读书内容要间隔5s，但是阅读其他的页面不控制间隔

除了在 .check 时判定当前一条动作不能执行外，还可能判定后续也不能执行。
那么他们应该可以直接在 SuperQueue 上设置 .suspending 字段。
这样，SuperQueue 就不用对每个请求都执行一遍 check，从而提高了效率。
但是，什么时候取消 .suspending 呢，需要 plugin。


问题：
但是多个 limiters 存在时，其中一个解除了限制时，其他的可能还未解除，而 .suspending 只有一个标志。
如果谁都可以解除 .suspending，那么也无所谓，因为依然执行每个 limiter.check。

不用，因为 .queue.length>0 就可以代表当前存在阻塞，所以用不着 使用 .suspending


任何 limiter 都可以标明 .suspending=true

关于超时 timeout
---------------

* 超时是否记录一次执行
* 超时门限应该在哪里设置
，可以通过 plugin 设置
，在 count 中计时
，并在 onActCB 中解除计时
。计时就是将
* 超时是否应该是内在机制，而不是 plugin 的行为
，应该是内在机制，不是 plugin 的行为
。

* 开始执行，没有在指定的时间内执行完，要发生 execTimeout 事件
，对应于 psp.web 就是 oracle 侧执行时间过长，系统将会取消该任务
，当然 oracle 服务进程可能不能马上退出并重新连接 nodejs
。
* 指派任务，但是一直放到队列中，超过指定时长，要发生 queueTimeout 事件
，对于等待超过一定时间的任务，产生超时事件
，对于 psp.web 来说，就是废弃该任务
。
* 队列长度超长，当等待队列太长时，超过一定量的请求将被废弃


 For psp.web reversed connection.
---------------------------------------------

Oracle will establish min server process and reversed connections to nodejs.
When nodejs need more spare server above min spare server setting,
nodejs will call oracle to establish more server process and reversed connections.
when node find too many spare reversed connections,
node will tell oracle to kill some server processes.

oracle reverse connection pool may be mix of active pool and passive pool, that's too complex.

ResourceFactory
.create return rc {
  rc.on('end...', rc.emit('destroy'));
}

* 如果资源自己会失效或销毁，那么必须要指定该事件的名称，用于及时从资源池中清除 rc.on('name',...)
* 如果需要根据 minSpare 在 .count 时请求创建新资源，那么就需要给出创建函数 createrc()
* 如果需要根据 maxSpare 在 .actcb 时释放新资源，那么就需要给出清除函数 delrc(rc)
* 如果指定 minTotal，就同时要指定 createrc()
* 如果指定 maxTotal，那么在追加资源时，不能超过该值

对于 reversed connection，服务端只要给出最大连接数即可，没有必要给出。
或者 psp.web reversed connection 在 oracle 侧实现控制策略，如下：
每当进入 gateway 和执行完当前循环时，发出 pipe 消息通知 k_pmon 出现新的忙闲事件，
k_pmon 根据 minSpare, maxSpare 进行动态增加进程数。
在 node 端，只需要按照 passive pool 来操作就好了。
因为如果在 node 端控制，node 一样要调用 oracle 来具体进行控制，
那么与其间接不如直接在 oracle 侧控制好了。



Memo
===============
* 从原先的一大统设计过渡到微核心加大量内置和定制的插件的架构，极大的增加的灵活性。
* 和 mixer 分离，即 mixer 专门用于


各种异步库的分类
===============

* 并行的执行，如 async array async 类，互相之间没有关系
* 时间规划的，如 crontab 类的
* 相互之间依赖的，也就是从简单串行到复杂依赖的路径
- step 类的，每个 async callback 使用 helper 来接受
-
* 队列，不能马上执行请求的动作，而需要排队、跟踪记录等监控逻辑


