Scalability 系统的可扩展性是指系统可以平稳的支持增长的业务量。

扩展性很容易理解,比如开发团队的扩展性:一个程序员扩展为 7 人团队,7 人团队扩展为 20 人团队。比如公司业务的扩展性,从几十万的交易额扩展多几百万几千万的交易额。大家都很推崇互联网商业项目的主要原因即是它有很强的扩展性,同样的业务可以从几万的营业额扩展到几亿的营业额,目标客户可以从一个城市扩展到全国或者全世界,这是传统商业无法快速实现的。

WEB 系统的扩展包括:

纵向扩展

硬件方面可以更换更强劲的服务器,增加 CPU ,内存, 使用高速磁盘。

软件方面可以对现有代码的优化,重构。使用 Non-Blocking 非阻塞 IO 模式,或者异步 IO 模式,使用线程模式或者改用 事件驱动形模式。

目标是提高单机 qps ,连接数,来支持更多的连接,更多的用户,更多的业务量。

横向扩展

使用集群,更多的服务器代替单一服务器。

可扩展系统设计模式

负载均衡

这种模式将请求分发到并行的处理机上。要求程序 无状态 ,请求对于每个处理机都应该是相同的。比如常见的 REST 模式既是 无状态 模式。

分散聚集模式

这种模式将请求分发到多个处理机上,然后收集每个处理机返回的结果,得到整合的结果。比如Map-reduce也可以看做这种模式。

结果缓存

这种模式,首先查看请求是不是发生过,如果发生过则直接返回之前的结果,来节约处理时间。这种模式非常常见比如静态页面的 反向代理 ,各层级的 CACHE。

共享空间

所有的处理机监控共享空间中的未处理信息,处理完后将结果提交到共享空间,直到整个任务完成。

MAP REDUCE

这种模式通常用来解决 IO 密集型的业务,通过并行化的分布式系统来解决 IO 瓶颈。

队列模式

每个处理机从待处理队列提取任务进行处理然后将结果保存到已处理队列,多个处理机可以并行处理队列任务。

可扩展系统设计的要点

1. 快速失效并返回错误

2. 将复杂的大的请求分解成多个小请求

3. 利用 超时

4. 利用 缓存

5. 用队列来做缓冲

6. 精确测量每个步骤,记录详细日志

JSON JSON-RPC 和 JSONP

虽然用文本格式来通信比较昂贵,但是如今这方面的费用越来越便宜。应该在游戏这样数据量很大的应用中使用二进制数据进行通信,而在普通 WEB 应用中使用文本格式的数据来通信。在架构中应该尽量使用 JSON 而不是 XML 作为通信协议。

用 JSON 作为文本通信格式有很多好处:

JSON 就是 Javascript 中的一个对象,很容易把一个 JS 对象转换成 JSON 格式的数据

在JS 中进行对象和 JSON 的转换:

var myJSONText = JSON.stringify(myObject, replacer);
var myObject = eval('(' + myJSONtext + ')');

在 jQuery中进行对象和 JSON 的转换:

var obj = jQuery.parseJSON('{"name":"John"}');
alert( obj.name === "John" );

用 JSON-RPC 替代 XML-RPC 通常会把数据减小到 30% 左右

JSON-RPC 请求的例子

Client: { "method": "echo", "params": ["blog.eood.cn", "Hello JSON-RPC"], "id": 1}
Server: { "result": "Hello JSON-RPC", "error": null, "id": 1}

nodeJS 中对 JSON-RPC 做了支持

见 https://github.com/ericflo/node-jsonrpc

在 nodeJS 中创建 JSON-RPC 的服务端

var rpc = require('jsonrpc');
function add(first, second) {
    return first + second;
}
rpc.expose('add', add);
rpc.listen(8000, 'localhost');

在 nodeJS 中创建 JSON-RPC 的客户端

var rpc = require('jsonrpc');
var sys = require('sys');
var client = rpc.getClient(8000, 'localhost');
client.call('add', [1, 2], function(result) {
sys.puts('1 + 2 = ' + result);
});

PHP 中也有支持 JSON-RPC 的类库

见 http://www.jsonrpcphp.org/?page=example&lang=en

关于 JSONP

JSONP 是跨域 AJAX 通信的方式

jQuery 做 JSONP 请求和普通 AJAX 请求几乎一致

$.ajax({
    url: "http://blog.eood.cn/jsonp.json",
    dataType: 'jsonp',
    success: function(results){
       ...
    }
});

 

客户端计算能力的增强,使 WEB 技术发展的天枰又开始偏向客户端运算。比如 Facebook 将模板渲染的运算放到了浏览器,浏览器先将模板下载到本地,再按需从服务器请求 JSON 格式的数据,然后在浏览器中渲染。对于提供给搜索引擎抓取的内容仍然可以在服务器端拼接完成后再输出。为了提高开发效率,可以考虑在服务器端也用 Javascript 或者其他语言拼接渲染 JSON 格式的数据和与客户端一致的模板。Mustache 就提供了这样的一种机制,并且支持大部分 WEB 语言。但是比较高效的服务器端方案仍然是用 Javascript 来写,用 NodeJS 向浏览器输出 JSON 数据和重用浏览器模板渲染代码向搜索引擎输出完整 HTML 数据。

NodeJS 和倍受推崇的 WEB 服务器 Nginx 类似,都采用 Event-driven 模型,对于单核服务器,只需要开一个进程就可以同时 serve 成千上万的客户。

不仅仅是 NodeJS, 对于 RIA 来说,支持最完善的仍然是 Javascript,基于 XUL 的客户端方案也需要 Javascript 作为开发语言,并且 Javascript 支持常见的高级特性比如 lambda,map-reduce 。

所以 Javascript 在服务器端和客户端都有很大的前景。设计模式既是解决问题的template,也是代码的组织方式。略去常见模式,仅仅涉及几个在 Javascript 中比较常用的模式:

用 prototype 的构建模式:

function Car(model, year, miles){
   this.model = model;
   this.year    = year;
   this.miles  = miles;
}

/*
 Note here that we are using Object.prototype.newMethod rather than
 Object.prototype so as to avoid redefining the prototype object
*/
Car.prototype.toString = function(){
		return this.model + " has done " + this.miles + " miles";
};

var civic = new Car("Honda Civic", 2009, 20000);
var mondeo = new Car("Ford Mondeo", 2010, 5000);

console.log(civic.toString());

单件模式:

var Singleton =(function(){
	var instantiated;
	function init (){
		/*singleton code here*/
		return {
			publicMethod:function(){
				console.log('hello world')
			},
			publicProperty:'test'
		}
	}

	return {
		getInstance :function(){
			if (!instantiated){
				instantiated = init();
			}
			return instantiated;
		}
	}
})()

/*calling public methods is then as easy as:*/
Singleton.getInstance.publicMethod();

可以传递参数的单件模式:

var SingletonTester = (function(){

  //args: an object containing arguments for the singleton
  function Singleton(args) {

   //set args variable to args passed or empty object if none provided.
    var args = args || {};
    //set the name parameter
    this.name = 'SingletonTester';
    //set the value of pointX
    this.pointX = args.pointX || 6; //get parameter from arguments or set default
    //set the value of pointY
    this.pointY = args.pointY || 10;  

  }

 //this is our instance holder
  var instance;

 //this is an emulation of static variables and methods
  var _static = {
    name: 'SingletonTester',
   //This is a method for getting an instance

   //It returns a singleton instance of a singleton object
    getInstance: function (args){
      if (instance === undefined) {
        instance = new Singleton(args);
      }
      return instance;
    }
  };
  return _static;
})();

var singletonTest = SingletonTester.getInstance({pointX: 5});
console.log(singletonTest.pointX); // outputs 5

模块模式:

var someModule = (function(){

  //private attributes
  var privateVar = 5;

  //private methods
  var privateMethod = function(){
  return 'Private Test';
  };

  return {
        //public attributes
        publicVar    : 10,
        //public methods
        publicMethod : function(){
        return ' Followed By Public Test ';
         }, 

         //let's access the private members
          getData : function(){
          return privateMethod() + this.publicMethod() + privateVar;
         }
       }
    })(); //the parens here cause the anonymous function to execute and return

someModule.getData();

观察者模式:

它适合pub-sub进行信息分发

function Observer(){
    this.functions = [];
}

Observer.prototype = {
    subscribe : function(fn) {
        this.functions.push(fn);
    },

    unsubscribe : function(fn) {
        this.functions = this.functions.filter(
            function(el) {
                if ( el !== fn ) {
                    return el;
                }
            }
        );
    },

    update : function(o, thisObj) {
        var scope = thisObj || window;
        this.functions.forEach(
            function(el) {
                el.call(scope, o);
            }
        );
    }
};
/*
    * Publishers are in charge of "publishing" eg: Creating the Event
    * They're also in charge of "notifying" (firing the event)
*/
var obs = new Observer;
obs.update('here is some test information');

/*
    * Subscribers basically... "subscribe" (or listen)
    * And once they've been "notified" their callback functions are invoked
*/
var fn = function() {
    // my callback stuff
};
obs.subscribe(fn);

/*
    * Unsubscribe if you no longer wish to be notified
*/
obs.unsubscribe(fn);

JQuery 中的遍历:

  $.each(function(){});
  $('.items').each(function(){});

更多有用资源

http://www.hunlock.com/blogs/Functional_Javascript

http://www.addyosmani.com/resources/essentialjsdesignpatterns/book/#designpatternsjavascript