nodejs是一个轻量级的webserver框架,和python的bottle很类似,都是轻量级的web框架:写一个web server只需要一行代码就可以。
node.js平台的构建是基于Chrome's JavaScript runtime,即它是对GoogleV8引擎(应用于Google Chrome浏览器)进行了封装。V8引擎执行Javascript的速度非常快,性能非常好。Node对一些特殊用例进行了优化,提供了替代的API,使得V8在非浏览器环境下运行得更好。
Bottle的官网为:http://bottlepy.org/docs/dev/
二.安装说明
linux下的安装命令如下所示:
wget http://nodejs.org/dist/v0.10.5/node-v0.10.5.tar.gz
tar zxvf node-v0.10.5.tar.gz
cd node-v0.10.5.tar.gz
./configure --prefix=/home/zhaolincheung/local/nodejs
make && make intall
注:这里将node.js安装在/home/zhaolincheung/local/nodejs目录下。node.js的安装需要python2.6以上的支持,否则在执行./configure时会出错;node.js还需要gcc-c++的支持,所以系统需要实现安装gcc-c++。
通过 node -v来检查安装是否成功,如果返回:v.0.10.5,则说明安装成功。
至此node.js已经编译并安装完成。如需卸载,可以执行make uninstall进行卸载。
三.简单的hello world程序学习任何语言或者框架,首先要写的程序就是hello world程序。这里也是这样,我们来写一个简单的hello world程序。
首先,编写helloworld.js,内容如下:
var http = require('http'); http.createServer(function(req, res) { res.writeHead(200, {'Content-Type':'text/plain'}); res.end('Hello World\n'); }).listen(10001); console.log('Server running at http://127.0.0.1:10001/');其次,执行该文件:/home/zhaolincheung/local/nodejs/bin/node helloworld.js
最后,通过浏览器访问http://127.0.0.1:1337便得到了hello world的响应。
四.开发进阶
当我们在使用node.js进行开发时,由于node.js是单线程的,所以对于耗时的操作,我们将它封装成函数采用回调函数的方式进行调用,这样可以让代码继续往下执行。当把耗时操作放在回调函数中,当耗时操作执行完毕后,回调函数会继续执行其函数内剩余的代码,它不会耽误回调函数外其他方法(请求)的执行。这个就是传说中的 回调 。我们给某个方法传递了一个函数,这个方法在有相应事件发生时调用这个函数来进行回调。这就是事件驱动的异步服务器端JavaScript和它的回调啦!
作者在网上看到了一个很好的node.js的入门资料,分享给大家,参考链接为:http://www.nodebeginner.org/index-zh-cn.html
下面介绍该资料中的一个例子,对不同的请求(url)进行相应的处理(路由)。它包含4个文件,index.js、server.js、router.js和requestHandlers.js。其中index.js是定义了不同请求(url)的相应的处理(路由)方法,和启动webserver。
index.js的代码如下:
var server = require("./server"); var router = require("./router"); var requestHandlers = require("./requestHandlers"); var handle = {}; handle["/"] = requestHandlers.start; handle["/start"] = requestHandlers.start; handle["/upload"] = requestHandlers.upload; server.start(router.route, handle);
server.js是server的创建和启动的实现。server.js的代码如下:
var http = require("http"); var url = require("url"); function start(route, handle) { function onRequest(request, response) { var pathname = url.parse(request.url).pathname; console.log("Request for " + pathname + " received."); route(handle, pathname, response); } http.createServer(onRequest).listen(65535); console.log('Server has started.'); } exports.start = start;
上面的onRequest函数就是回调函数。
回调函数就是将一个函数f1作为函数f2的参数进行调用,这样f2就不会因为f1的耗时操作而影响下面代码的执行,f1会等待相应事件的触发,即f1和f2会并行执行。
router.js是对不同的url调用函数(即下面的handler[pathname](response))进行处理。router.js的代码如下:
function route(handle, pathname, response) { console.log("About to route a request for "+ pathname); if (typeof handle[pathname] == 'function') { handle[pathname](response); } else { console.log("No request handler found for " + pathname); response.writeHead(404,{'Content-Type': 'text/plain'}); response.write('node.js: 404 Not found'); response.end(); } } exports.route = route;requestHandlers.js是不同url调用(路由)的方法的具体实现。requestHandlers.js的代码如下:
function start(response) { console.log("Request handler 'start' was called."); response.writeHead(200, {'Content-Type': 'text/plain'}); response.write("node.js: hello,start"); response.end(); } function upload(response){ console.log("Request handler 'upload' was called."); response.writeHead(200, {'Content-Type': 'text/plain'}); response.write("node.js: hello,upload"); response.end(); } exports.start = start; exports.upload = upload;
运行这个例子的命令为:node index.js
当请求:http://127.0.0.1:65535/后的运行结果如下:
当请求:http://127.0.0.1:65535/upload后的运行结果如下:
代码链接:https://github.com/zhaolincheung/nodejs_demo
五.python的轻量级web框架bottle介绍编写一个文件bottle_example.py,内容如下:
from bottle import route, run, template, static_file @route('/hello') def hello(): return "Hello,world!" test_home = './resource/' @route('/rsrc/<p:path>') def foo(p): return static_file(p, test_home) run(host='localhost', port=8080)
执行这个文件,然后在浏览器中输入:http://localhost:8080/hello,页面会输出”hello,world”。
如下所示:
好,webserver已经跑起来了。接下来,我们研究webserver如何加载一个页面,首先我们在bottle_example.py的当前目录下新建一个文件夹resource,在resource目录下再建立一个docs文件夹,在docs目录下我们建一个index.html页面(即index.html存在在./resource/docs/目录下)。目录结构如下图所示:
因为bottle是基于路由进行响应处理的,我们该如何让bottle来加载这个index.html页面呢?
答案是:@route('/rsrc/<p:path>')这个路由能够实现。当用户在浏览器中打开http://localhost:8080/rsrc/docs/index.html页面时,就会触发这个路由,然后执行static_file这个方法来加载path路径指定的静态文件。这样便实现了加载静态页面的功能。
代理模式,那么什么是代理模式勒,我们可以这样来理解,我很忙,忙的没空理你,那你要找我呢就先找我的代理人吧,那代理人总要知道
被代理人能做哪些事情不能做哪些事情吧,那就是两个人具备同一个接口,代理人虽然不能干活,但是被
代理的人能干活呀。说起来很抽象,具体看实例还是来的实在!
比如西门庆找潘金莲,那潘金莲不好意思答复呀,咋办,找那个王婆做代理,表现在程序上时这样的:
先定义一种类型的女人
class KindWomen //指示这一类女人,王婆和潘金莲都属于这种女人 { public: virtual void HappyWithMan() = 0; //和男人开心,你懂的,王婆和潘金莲都会 virtual void MakeEyesWithMan() = 0; //给男人抛媚眼,王婆和潘金莲都会抛 }; //潘金莲出场 class Panjinlian:public KindWomen { public: virtual void HappyWithMan() { cout<<"潘金莲和男人开心.........."<<endl; } virtual void MakeEyesWithMan() { cout<<"潘金莲给男人抛媚眼."<<endl; } }; //王婆出场 class WangPo:public KindWomen { private: KindWomen *kindWomen; public: virtual void HappyWithMan() //王婆自知老了,happy不动了,所以叫别人去干 { kindWomen->HappyWithMan(); } virtual void MakeEyesWithMan() //王婆这么丑抛媚眼谁看啊,所以她又喊别人去抛 { kindWomen->MakeEyesWithMan(); } WangPo(KindWomen *m_pKindWomen) //设置王婆到底代理的是谁,王婆开始构造 { this->kindWomen = m_pKindWomen; } }; //西门庆控制主函数 int main() { //西门庆看上了潘金莲,但是潘金莲害羞啊,也不敢去抛媚眼,所以这时候就需要王婆帮他做一些事撒 WangPo wangpo = new WangPo(new Panjinlian() ); //王婆出场,代理的是潘金莲 wangpo.MakeEyesWithMan(); //王婆开始抛媚眼,但是实际上是潘金莲在抛媚眼 wangpo.HappyWithMan(); //王婆开始和男人happy..... 实际上王婆哪能和男人开心,幕后是潘金莲在happy system("pause"); return 0; }
假如到现在还有个卢俊义他老婆也想和西门庆玩玩,那么他也可以找到王婆做代理人,同样继承了抽象类并实现方法,传递给王婆就可以做代理了,有的时候如果需要代理的对象过多,也可以结合享元模式使用,享元模式以后再说
总结一下:代理模式就是利用接口和多态来实现,对需要代理的人进行了浅封装,直接出面做事的人是代理人,但是实际上在做的是被代理的人
我做的客户端自动升级系统需要在内存或缓存中维持一根可用客户端产品的列表。考虑到在内存中进行读取的速度更快,于是选择在内存中存储一份可用客户端产品的列表。这个列表用一个静态列表对象保存。当新增,修改,淘汰客户端产品时,会对从数据库向这个内存对象中进行同步。
这个设计是完全没有问题的。但是应用到多个tomcat部署这样的同一个实例的时候,问题就出来了。这也是今天上线的积累到的一个教训。上线之后发现,新增了两个客户端产品之后,刷客户端列表却时而能不显示任何信息,时而值显示一个客户端,有时候显示2个客户端,很不稳定。程序要做到的精确在这里变成了不确定因素。
和项目负责人沟通之后,发现问题的根源了。问题在于,我们同一个应用部署了6个tomcat,每个tomcat进程都有自己的进程空间,各自维护自己的内存变量。我通过Action进行可用客户端列表的更新操作只会触发一个tomcat下的应用进行数据同步。最终导致每次刷客户端列表的结果都不一样。
根据这个流程图可以发现,依赖Action进行数据操作,最终只会更新一个tomcat进程下的内存数据。这个问题最后通过设缓存的方式得以解决,即共享数据。如果一定要用将数据存在内存中的话,其实也是可以的,程序中增加一个轮询线程即可,用这个线程同步数据库中的数据。
其实一开始我是想到要用redis的方式缓存数据,但是我喜欢用新的方式解决问题。毕竟用内存存数据比用redis更快。虽然造成上线出问题,最后重新上线,但是并不后悔采用新的方式进行开发。