Tuesday, March 8, 2011

ngx_openresty系列之 ngx_lua vs. node.js

ngx_lua 是由 chaoslawful 和 agentzh 开发的用于 web 开发的 nginx 扩展,
其主要特点在于利用了 nginx 的非阻塞 IO 模型以及 lua VM 的灵活性。

node.js 是 ry 主导开发的基于强大高效的 v8 引擎,
提供了事件模型和各种基础设施的 web 开发平台,
其主要特点是内部各种 IO 操作是非阻塞的,为高并发提供了很好的基础。
同时,因为基于 js 语法,和 web 前端融合较紧密,
可以提供前后一致的编程体验(当然,客户端还比较难达到服务器端的爽)。

在传统的 php 编程中,当需要查询数据库时,当前的
apache 进程(或线程)(或 php-fpm 之类)在数据结果返回前一直处于等待状态,
如果大量的请求都在做数据库查询操作,那么服务器就没法处理更多请求。
因为 apache 能开启的进程(或线程)数是有限的(受内存限制)。

<code>
<?php
// 连接数据库时,进程是在等待的
$c = mysql_connect('mysql_host', 'user', '****');
mysql_select_db('my_database');

$query = 'SELECT col1 FROM my_table LIMIT 10';
// 查询时,进程是在等待的
$result = mysql_query($query);

while ($line = mysql_fetch_array($result, MYSQL_ASSOC)) {
    echo " * " . $line[0] . "\n";
}

mysql_free_result($result);
mysql_close($c);
?>
</code>

如上面的代码所示,在跟外部系统交互的时候,当前进程啥都没干,净等着了,
你还不能抱怨人家不干活,因为理由很充分:数据库忙着呢,我得等!
真的这样么?
我们看看 node.js 怎么做的。

<code>
// 仅仅给出部分代码,不能直接运行
// resp 是响应体对象

var c = new require('mysql').Client();
c.user = 'user';
c.password = '****';

// 开始连接数据库,同时注册了一个在连接成功后要执行的函数
// 注意,这时候 node.js 做了这件事情以后代码还在继续往下执行
c.connect(function (err, results) {
    if (err) {
        resp.end("ERROR: " + err.message);
        return;
    }

    // 这里发了一个数据库请求,并且注册了一个在请求完成后要
    // 执行的函数,注册完毕后就跟刚才一样干其他事情去了
    c.query(
        'SELECT col1 FROM my_table LIMIT 10',
        function (err, results, fields) {
            if (err) {
                resp.end("ERROR: " + err.message);
                return;
            }

            for (var i = 0, iM = results.length; i < iM; ++i) {
                resp.write(' * ' + results[i][0] + '\n');
            }

            resp.end();
            c.end();
        });
});
</code>

我们可以看到,node.js 对 IO 的做法是当要发生可能等待的事情时,
注册个函数在那里,然后继续做其他事情,当实际的数据到达或者事件完成时,
再调用之前注册的函数来处理。
整个 node.js 环境内漂浮着许多事件和函数,
在底层,有一些机制来保证这些事件的正确、准确触发。

在这种思路下,业务处理代码被切片,然后被注册到各种事件上面去,
这些由 node.js 统一管理,因此实现了非阻塞的处理。

对于 php 来说,一旦 apache 把控制权转交给 php 以后,
他们之间就很难转让控制权了,我们不可能把 php 代码切分到这样细致的地步,
因为即使切分了也没法注册给 apache ,而在 php 内部注册也是没有意义的:
一个 php 进程一般只处理一个客户端请求而已,在一个请求内添加事件的概念,
没法提升系统整体的并发能力。

node.js 还有一个很大的优势是 连接池。
在 php 里,连接可能仅仅是复用而已,连接池的意义应该不大。
而在 node.js 里,连接是可以在多个请求之间共享的,
只要 node.js 服务器不重启或关闭,那么这些连接便可以一直复用。

看到这里,你有没有激动或者兴奋呢?
是否要抛弃执着等待 IO 的 apache + php 这一对黄金搭档呢?
不过,我们稍等一下,如果为了非阻塞的特性,而要我人肉的拆分我的代码
成为很多函数(代码片段)然后注册到各种事件上去,会不会写起来很恶心?
或者,有没有从天上掉下来的什么东西,能够让我写像 php 那样从上到下的代码,
而又能够像 node.js 那样非阻塞呢?

ngx_lua 就是这样的东西。
那,ngx_lua 到底是神马东西呢?我们先看段代码。

<code>
upstream db {
    drizzle_server mysql_host:3306 protocol=mysql
                   dbname=my_database user=user password=****;
}

http {
    server {
        location = /i-mysql {
            internal;

            drizzle_query $echo_request_body;
            drizzle_pass db;

            rds_json on;
        }

        location /test {
            content_by_lua '
                local yajl = require("yajl")

                local sql = "SELECT col1 FROM my_table LIMIT 10"
                local res = ngx.location.capture("/i-mysql",
                    { method = ngx.HTTP_POST, body = sql })

                if res.status ~= 200 then
                    ngx.say("error")
                    ngx.exit()
                end

                local result = yajl.to_value(res.body)

                for i, v in ipairs(result) do
                    ngx.say(" * " .. v)
                end
            ';
        }
    }
}
</code>

这是神马东西!!!!!!
好吧,其实这是一段 nginx 配置,
同时也是一段业务逻辑处理代码。

这里的写法跟 php 是类似的,从上到下,
但是呢,它对待 IO 的态度跟 node.js 是一样的,
当开始 IO 操作的时候,它会暂停当前代码的执行并发起数据请求,
当请求完成后,恢复之前暂停的代码,并把结果返回。

ngx_lua 的模型是一个 nginx 进程内可以同时处理不限数量(几乎)的请求,
他们按照上面描述的逻辑执行。

你可能会说,
那为什么要用 lua 这个没听说过的东西来搞,
而不用 php 或者 js 搞呢?

刚才说了,当发起请求的时候,需要暂停代码的执行,
暂时只有 lua 运行时支持这个特性(或者有其他什么主流语言支持,赶快说来听听)。
并且 lua 里面创建的进程(其实是协程)非常轻量,占用内存非常少,可以让服务器同时处理更多请求。

这篇文章是个开头,
后续会更多的介绍 ngx_lua 的周边和现状,
以及更多的使用示例。



Thursday, February 10, 2011

下雪了

早晨迷迷蒙蒙的醒来,
发现下雪了,
虽不至若狂,却很是欣喜。

转又发现很多铲雪的声音和身影,
遂想,为何这会带来烦恼的雪,
会让我觉得欣喜呢?

或者是太久低迷的心情,
因为这雪而释放了。

或者是听到广播里太多的话语,
抱怨这没有雪的冬天太不像样,
现在终于下雪了,连我也感受到了这像样的冬天了
——没有雪的冬天是不完整的冬天。

早早的来到公司,
一个人静静的看窗外细碎的雪花飘舞,
无论是发呆还是遐想,
都是很不错的。

忽然想起,
19、20交际的时候,
有帮文人开始积极的探索改革,
期望能够帮助当权政府改善,
然而他们失败了,
看来即使所谓的头头想要改变,
也是困难的,
为什么呢,
可能是牵涉到的利益群体太多,
而这些利益群体又是极其重要和关键的,
比如人家有一切维护和保障权力的资格和能力。
人家反对,
当然很难做出什么实质性的改变了。

所以就导致人们出来抗议,
干脆我们重新洗牌,
总能碰到一手好牌吧。

也许社会就是这样,
就像一场牌局。
当前执政的人,就像我们的牌。
也许初抓到一首烂牌,
但只要运气好,技术好,
慢慢的也会翻势。

只是社会这场是永没有结束的牌局,
可以重新抓,只是永远打不完。

而所有的民众就是打牌的人,
随着打的越久,
也许技巧就越高,
大家只是在每一局里面不断的调整手里的牌,
保持一个良好的状态,
可是,却永远没法赢。

好牌放在手里只是一种感觉,
因为你既不会输,也不会赢。
你要做的也许只是陪着大家打牌。

雪还在下着,
心情却不如刚才那般明亮了。
何苦呢。

Saturday, October 9, 2010

jquery 事件处理研究(1)

jquery 事件绑定很好用,
不过处理事件冒泡、后续事件执行逻辑很是纠结。
总结已知的逻辑如下。


jquery 的事件提供以下方法处理相关逻辑:

1. ev.preventDefault -> 阻止浏览器绑定到当前 node 的默认行为,比如 a 链接的 click 事件默认会跳转
2. ev.stopPropagation -> 阻止冒泡,即当前 node 的 parentNode s 绑定的同类型事件不会被触发
3. ev.stopImmediatePropagation -> 阻止当前 node 同事件类型 后续函数的触发,会自动调用 ev.stopPropagation() ,
    比如给 #a 绑定了 5 个 click ,当第 2 个函数调用了 ev.stopImmediatePropagation() 以后,后面的 3 个函数都不会被调用,
    并且这个 click 事件也不会再冒泡了。
    如果只想阻止后续函数的触发而不阻止冒泡(假如有这需求呢),那么可以设置
        ev.isImmediatePropagationStopped = function () {return true;}; 即可。
4. return false -> 如果事件处理函数返回 false ,那么 jquery 会自动调用 ev.stopPropagation() 和 ev.preventDefault() ;
    但是后续函数会继续执行 :(

以上描述在正常的 .bind .click 等方法中正常,在 .live 和 .delegate 稍有不同

1. 在 .live 的事件处理函数中 return false ,行为不仅仅是上面描述的,还会产生类似 ev.stopImmediatePropagation() 的效果,
    即后续的 live 事件都不执行了;
2. 在 .live 的事件处理函数中想阻止后续函数的执行,只能 return false ;
    但是这样有个副作用就是 jquery 会自动调用 ev.stopPropagation() 和 ev.preventDefault() ,这个行为没法改变!!!!



纠结的事情啊
望同样纠结、研究过的一起探讨


参考:


Saturday, September 25, 2010

mac osx 上安装git

很久很久以前,我在 mac 上装 git,从 source 。
结果发现没法 make doc

因为他需要 xmlto 这个东西,
而这个东西需要能支持 --long-opt 的 getopt ,
从 http://software.frodo.looijaard.name/getopt/download.php 下载的东西编译不能通过,
经查是因为 getopt 的 Makefile 有错误导致。

文件 line.36 

CPPFLAGS=-DLIBCGETOPT=$(LIBCGETOPT) -DWITHOUT_GETTEXT=$(WITHOUT_GETTEXT) -DLOCALEDIR=\"$(localedir)\" -DNOT_UTIL_LINUX

的 -DWITH_GETTEXT 是错误,按照上面的修改即可。

可能大多数人都有个 gettext 所有没问题。

OK, 无论如何,解决了就好。





PS: 最终还是没能 git - make man 成功,下次再战。

Monday, September 20, 2010

压缩好东东 lzo

这个世界上总有太多的好东西等着我们去发掘。

晚上看 kindle 的资料,无意中翻开下载的 kindle src ,发现里面有个 lzo 名字比较有趣,
打开看看就更有兴趣了,遂 go 了一把,觉得是个好东西,可以拿来替换掉 gzip 了。


Wednesday, August 18, 2010

vim 中同时滚动水平分割窗口


vim 中可以水平分割窗口, :vs file2
如果想保持滚动其中一个窗口,另一个也跟着滚动,可以这么来:

在两个窗口执行 :set scb
然后滚动其中任何一个,另一个也会滚动。

完整写法是 scrollbind ,简写为 scb

取消方法是  :set noscb

Sunday, July 18, 2010

周末小记

这个周末,看了几部电影。
《阿飞正传》
《空气人偶》
《人在冏途》

完了感觉,很庆幸自己四肢健全,而且是个人。不愁吃喝,当然啦,只是不愁基础的而已,但已足够。
人最怕的就是对比,但是,对比最能看出效果来。所以,对比完了我觉得,我还是很幸福的。

忙碌庸庸了这辈子的大部分时光,总想着能够做些改变,参加百淘让我感觉有了希望。

拥抱变化,拥有激情

这已经无敌了。

觉得,自己很空,需要多些读写。

Wednesday, June 23, 2010

ngx_lua 终于好了

终于。

无敌的晓哲和春哥,你们太强大了。
ngx_lua 在两位大侠的手里搞定了。
示例如下,看看他强大的威力吧。

location /lua {
    content_by_lua '
        local num = tonumber(ngx.var.arg_num) or 0;
        ngx.echo("num is: ", num, "\\n");

        if (num > 0) then
            res = ngx.location.capture("/lua?num="..tostring(num - 1));
            ngx.echo("status=", res.status, " ");
            ngx.echo("body=", res.body, "\\n");
        else
            ngx.echo("end");
        end 
        ';  
}

$ curl -i localhost/lua?num=5
HTTP/1.1 200 OK
Server: nginx/0.8.40
Date: Wed, 23 Jun 2010 15:28:27 GMT
Content-Type: application/octet-stream
Transfer-Encoding: chunked
Connection: keep-alive

0? is: 5
status=200 body=num is: 4
status=200 body=num is: 3
status=200 body=num is: 2
status=200 body=num is: 1
status=200 body=num is: 0
end



强大吧。
可以计算 阶乘了。
呵呵。