Reliability test #130

Closed
lifesinger opened this Issue Nov 17, 2011 · 9 comments

Projects

None yet

2 participants

@lifesinger
Member

可靠性测试

  1. 等待 army 的测试结果数据
  2. 利用云梯,在淘宝上也测试一下
  3. 根据测试结果,优化代码
  4. 博文总结

利用云梯,进一步测试以下关键点:( A 表示 a.js 执行时间点,a 表示 a.js onload 时间点)

  1. A 比 a 先的可靠性
  2. Aa 紧相邻的可靠性
  3. IE 下,getCurrentScript 的可靠性
  4. ABC 与 abc 顺序一致的可靠性

细节测试

  • 测试 IE9+ 下,onload 和 onreadystatechange 同时触发的概率和影响
  • 测试 IE9+ 下,onload 和 onreadystatechange 哪种情况下,A 比 a 先的可靠性更高
@lifesinger
Member

SeaJS 依赖的假设

上线之后,理论上只存在具名模块。因此只依赖 A 先于 a 的假设,只要这个可靠性很高,就没问题。

开发时,存在匿名模块,需要自动获取 url. 这时非 IE 下依赖 Aa 紧相邻假设,IE 下依赖 getCurrentScript 的可靠性。这是开发时的依赖,出错率只要低于十万分之一就可以接受。(F5 刷新十万次,出现一次错误)

@lifesinger
Member

二次幂延迟优化

Army 的匿名模块获取依赖 ABC 与 abc 的顺序一致性,并使用了二次幂延迟算法来提高准确度。(需要有一个文件一个模块的前提,并在打包时通过 define.finish 等方式来处理。)

实际情况下:

  1. A 比 a 先占绝大多数
  2. a 比 A 先的情况也存在

在 a 比 A 先的情况中,又有:

2.1 ABC 和 abc 的顺序一致
2.2 ABC 和 abc 的顺序不一致

一份测试结果:https://github.com/seajs/seajs/blob/master/test/research/onload-order/result/stat.txt

通过二次幂延迟算法,可以解决 2.1 的问题。因此理论上可以提高可靠性。

二次幂延迟的前提是:

  1. 顺序一致
  2. 文件和模块一对一

核心是延迟,时间倒不一定需要是二次幂。

采用2次幂延迟算法的onload次序测试: http://army8735.org/2011/11/17/1028.html
army 的二次幂延迟代码:https://github.com/army8735/Ai/blob/master/base/amd.js

@lifesinger
Member

不依赖 A 先于 a 的方案

SeaJS 里,

开发时依赖 A 先于 a 假设、Aa 紧相邻假设 与 getCurrentScript() 方法,可靠性上已经很不错,特别是考虑在 Chrome/Firefox 下开发时。开发时的可靠性不再需要过多考虑。

打包部署上线后的可靠性,目前依赖 A 先于 a 的假设。为了提高可靠性,除了延迟策略,还可以采用以下方式:

  1. 开始请求 http://path/to/a.js 时,记录 callbackA
  2. 当 A 发生时,如果遇到 define('http://path/to/a.js', ...), 直接回调 callbackA
  3. 当 a 发生时,不进行回调

这样,理论上可以达到 100% 的可靠性。

以上优化需要满足的前提条件:

  1. 每个文件里,都应该包含自身的 define('http://path/to/a.js', ...)
  2. a.js 里的 define('http://path/to/a.js', ...),必须放在文件最后,这样才能保证所有依赖。

(注:这两个前提条件有违背 CommonJS 的 package 规范)

如果不满足以上两个前提条件,则打包时,需要在文件最后自动添加一行代码:

a.js:

define('http://path/to/a.js', {});

上面的方式理论上能把可靠性提高到 100%, 但也意味着对打包工具有了强依赖,会使得很难直接使用动态 combo 服务,要慎重考虑。

@lifesinger
Member

延迟算法不太支持动态 combo

比如:

a.js:

define('http://path/to/a.js', ...);
define.finish('http://path/to/a.js');

b.js:

define('http://path/to/b.js', ...);
define.finish('http://path/to/b.js');

当 combo 时,请求的 url 是:http://path/to/??a.js,b.js. 这时 onload 会进入 d2 分支,但永远等不到对应的 defQueue 项。要正常运行的话,需要:

修改 combo 服务,在合并后的文件末尾,自动追加

define('http://path/to/??a.js,b.js', {});
define.finish('http://path/to/??a.js,b.js');

修改 onload 中的逻辑,对 combo 字符串进行解析,得到合并的最后一个文件路径:http://path/to/b.js, 然后判断这个路径有无 script[url] === true

@lifesinger
Member

或许是最终方案

结合上面两条评论中的考虑,可以进一步优化,省略掉 define.finish. 改成只要是 define 具名模块,就不 push 到 defQueque, 直接回调注册到该路径的函数即可。这样,load 的逻辑里为:

var cbCache = {};

function load(url, callback) {

  // url = 'http://path/to/??a.js,b.js'
  // callback is a function

  var urls = unCombo(url); // 得到 ['http://path/to/a.js', 'http://path/to/b.js']
  var lastUrl = urls[urls.length - 1];

  if (!cbCache[lastUrl]) {
    cbCache[lastUrl] = [callback];
  } else {
    cbCache[lastUrl].push(callback);
  }

  getScript(url, function() {

    // 处理 defQueue 中的匿名模块

  });
}

在 define 函数中的逻辑是:

  define = function(id, ...) {
    ...

    // 在最后
    url = id2Url(id);
    if (url) {
      if (cbCache[url]) {
        cbCache[url].forEach(function(fn) {
          fn(); // 直接执行掉回调
        });
      }
    } else {
      defQueue.push(mod);
    }
  };

好处是:打包上线后,不再依赖 A 先于 a 的假设,正常情况下的可靠性理论上是 100%。

不足之处

  1. unCombo() 逻辑,需要写死或通过插件来实现,以适配不同的 combo 写法。(注:还得考虑去时间戳。)
  2. 将回调放在 A 里,意味着 404 等错误发生时,会 fall back 到 time out 时才触发回调,对需要及时响应的系统不利。
  3. 如果一个路径为 url 的模块中,不包含 define(url,...), 意味着回调也会被延迟到 time out 时。
  4. 当多个 combo 文件含有同一个文件且都在最后时,先下载的有可能将后下载的回调提前执行掉而导致错误。
  5. 好像还有些什么⋯⋯

纠结的选择

如果信任 A 先于 a 的假设,则以上不足之处都不存在,但缺点就是,可靠性上会降低。

真是纠结呀,还是得进一步测试。毕竟任何实现,可靠性实际上都无法 100%, 关键看出错率能否降低到可接受的范围内。

@lifesinger
Member

杂思

上面评论中,最终优化方案的核心思想是:权利转移。 将 callback 的回调权,从 a 中移交给 A.

突然想起马云的话,我们总得信任点什么。或许 A 先于 a,就是前端们应该选择信任的?

@lifesinger
Member

要测试一下 <script src> 正常情况下的失败率,以此作为基线。

要好好设计下测试策略。

@gumutianqi

相当不错啊

@lifesinger
Member

测试结果表明:A 先 a 的假设是值得信赖的,百万级别测试,只有十几次出现问题,考虑部分是 404 引起的,实际情况更小,可以忽略。

@lifesinger lifesinger closed this Jun 23, 2012
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment