New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reliability test #130

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

Comments

Projects
None yet
2 participants
@lifesinger
Member

lifesinger commented Nov 17, 2011

可靠性测试

  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

This comment has been minimized.

Show comment
Hide comment
@lifesinger

lifesinger Nov 17, 2011

Member

SeaJS 依赖的假设

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

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

Member

lifesinger commented Nov 17, 2011

SeaJS 依赖的假设

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

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

@lifesinger

This comment has been minimized.

Show comment
Hide comment
@lifesinger

lifesinger Nov 17, 2011

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

Member

lifesinger commented Nov 17, 2011

二次幂延迟优化

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

This comment has been minimized.

Show comment
Hide comment
@lifesinger

lifesinger Nov 17, 2011

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 服务,要慎重考虑。

Member

lifesinger commented Nov 17, 2011

不依赖 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

This comment has been minimized.

Show comment
Hide comment
@lifesinger

lifesinger Nov 17, 2011

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

Member

lifesinger commented Nov 17, 2011

延迟算法不太支持动态 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

This comment has been minimized.

Show comment
Hide comment
@lifesinger

lifesinger Nov 17, 2011

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%, 关键看出错率能否降低到可接受的范围内。

Member

lifesinger commented Nov 17, 2011

或许是最终方案

结合上面两条评论中的考虑,可以进一步优化,省略掉 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

This comment has been minimized.

Show comment
Hide comment
@lifesinger

lifesinger Nov 17, 2011

Member

杂思

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

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

Member

lifesinger commented Nov 17, 2011

杂思

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

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

@lifesinger

This comment has been minimized.

Show comment
Hide comment
@lifesinger

lifesinger Nov 30, 2011

Member

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

要好好设计下测试策略。

Member

lifesinger commented Nov 30, 2011

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

要好好设计下测试策略。

@gumutianqi

This comment has been minimized.

Show comment
Hide comment
@gumutianqi

gumutianqi Dec 23, 2011

相当不错啊

gumutianqi commented Dec 23, 2011

相当不错啊

@lifesinger

This comment has been minimized.

Show comment
Hide comment
@lifesinger

lifesinger Jun 23, 2012

Member

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

Member

lifesinger commented Jun 23, 2012

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment