Test::Nginx 模块介绍

先说句题外话,Perl的测试模块那真是相当的爽,不仅可以爽到无与伦比的正则,还可以对测试用例自由组合、乱序运行等等。

Perl测试模块大概有如下:

  • Test::Simple
  • Test::More
  • Test::Base 及衍生(这个我还没搞透)

一般Perl的测试用例,会在一个叫t/的目录下,一堆叫*.t的文件,其实就是普通的Perl脚本。

来段最简单的测试代码:

$ cat 1.t
use Test::Simple tests=>2;
ok(1 + 1 == 2, "test1 true");
ok("10" == 10, "test2 true");
    
$ perl 1.t
1..2
ok 1 - test1 true
ok 2 - test2 true

Test::Simple模块只有一个ok()函数,需要复杂的一些的话就用Test::More吧。

跑偏题太远了,回到本文要介绍的Test::Nginx模块。个人觉得这个测试适用于以下场景:

  • Nginx测试,包括:自身、模块、配置等测试
  • 反向代理测试,比如后端的HTTP或者其他服务

安装可以用cpan或者直接clone一份Test::Nginx,库地址:https://github.com/agentzh/test-nginx

文档:

摘抄模块中的一段测试用例:

use Test::Nginx::Socket;
repeat_each(1);
plan tests => 2 * repeat_each() * blocks();
$ENV{TEST_NGINX_MEMCACHED_PORT} ||= 11211;  # make this env take a default value
run_tests();

__DATA__

=== TEST 1: sanity
--- config
location /foo {
    set $memc_cmd set;
    set $memc_key foo;
    set $memc_value bar;
    memc_pass 127.0.0.1:$TEST_NGINX_MEMCACHED_PORT;
}
--- request
GET /foo
--- response_body_like: STORED
--- error_code: 201

可以看出就是一个基础的Perl代码,外加一部分Nginx的配置。TEST部分扩展自Test::Base:

  • config,一段标准的location
  • request,发起HTTP请求
  • response_body_like,类似于匹配,这里可以用帅气的正则(例子)都可以

可以参考 @agentzh 很多跟Nginx有关的模块,都包含有大量的测试代码。

像我一样懒惰的人,直接copy一份,补一些业务代码就可以啦。

如果有一堆.t测试脚本,还用perl .t方式就不合适,Perl自带了一个工具:prove,常用参数:

-l 类似-Ilib路径,加载一些库。比如上面的Test::Nginx模块不在默认LIB下
-s 随机跑测试用例

prove命令默认会找当前目录下t/文件夹下所有*.t脚本。

END

Published: 2013-12-04 — Comments

PostgreSQL ARRAY 数据类型

刚好手头有一个需求,是涉及到数组类型的,懒的插入多条数据库记录,想起了ARRAY数据类型。

官方文档参考:

这里简单的介绍下和一些示例,完整的还是推荐参考官方吧。测试版本为:PostgreSQL 9.3

比如声明integer/varchar数组:

x integer[5]
y varchar[]
z varchar ARRAY

后两个声明等价,懒人的话就别指明ARRAY长度了,建一个表来玩玩吧。

CREATE TABLE test1 (
  x integer[5],
  y varchar[],
  z varchar ARRAY
);

smallfish=# \d test1
       Table "public.test1"
 Column |        Type         | Modifiers
--------+---------------------+-----------
 x      | integer[]           |
 y      | character varying[] |
 z      | character varying[] |
 
没骗你们吧,后两者完全一样。

写入ARRAY字段,有一下两种方式:

  • 显示声明,比如:ARRAY[11, 22]
  • 花括号,比如:'{aa, bb}'

插入一条记录:

INSERT INTO test1 VALUES (ARRAY[11, 22], '{aa, bb}', ARRAY['cc', 'dd']);

查询一下,看看表里出来是啥样子:

smallfish=# SELECT * FROM test1;
    x    |    y    |    z
---------+---------+---------
 {11,22} | {aa,bb} | {cc,dd}

是不是看上去还挺帅的样子,这里的字符串没有特殊符号,比如空格斜杠之类的,可以双引号或用E来包一下。

ARRAY索引是从1开始的,这点稍微区别下一般语言的数组,还支持类似切片的查询哦。

smallfish=# SELECT x[1], y, z FROM test1 WHERE x[2]>10;
 x  |    y    |    z
----+---------+---------
 11 | {aa,bb} | {cc,dd}
 
smallfish=# SELECT x[1:2] FROM test1;
    x
---------
 {11,22}

说了读,写,还有更新的例子:

smallfish=# UPDATE test1 SET x[1] = 100;
smallfish=# SELECT * FROM test1;
    x     |    y    |    z
----------+---------+---------
 {100,22} | {aa,bb} | {cc,dd}

给数组添加新元素:

smallfish=# UPDATE test1 SET x[3] = 999;
smallfish=# SELECT * FROM test1;
      x       |    y    |    z
--------------+---------+---------
 {100,22,999} | {aa,bb} | {cc,dd}

按照索引删除的功能似乎要写函数来解决了,官方有一个array_remove的函数,不过这个是按照value来移除的,如:

smallfish=# UPDATE test1 SET x = array_remove(x, 22);
smallfish=# SELECT x FROM test1;
     x
-----------
 {100,999}

这年头能用上Pg真心不容易,用的还是9.x版本。谢天谢地~

END

Published: 2013-11-28 — Comments

我的第一次马拉松(近期跑步总结)

今年博客写的相当少,今天再来一篇非技术内容吧,源于今天刚好跑完了杭州马拉松(半马)。今年7月份开始计划跑步,从最初的4圈就气喘吁吁快跪的样子,一步一步的熬到现在可以轻松跑完10公里,当然仅仅是慢跑的水平。

凭着一时冲动报名了半马(21km),虽然在规定的时间(3小时)内跑完,还是觉得跟自己最初的目标有点差距来着,原计划2小时跑完,结果跑完已经超过20多分钟。不过也是有原因的,简单总结一下吧。

首先,说到准备。今天算起来今天准备阶段做的太差了,浪费了太多时间和体力。

  1. 应该提前半小时到达赛场,换好和存放好衣服,然后做一些热身和拉伸动作,今天完全没热身就上场了。
  2. 马拉松由于人数众多,起跑阶段尽量选择道路边缘,可以避免被人来回挤和超出,影响自己。当然要注意路道情况,别崴脚什么的。
  3. 上下坡的速度调整,下坡虽然感觉快和轻松,其实是相对的,收不住的话,上坡会发觉很累。
  4. 轻装上阵,能不带的尽量不带,建议就带一个电子表,能看时间即可。手机啊,GPS啊什么的,虽然方便,跑到最后发现这些个玩意都是累赘,尤其是长距离跑。
  5. 小装备要准备充足,比如:创可贴(避免x处摩擦)、凡士林(同摩擦)、腰包(装点小东西)、面纸(防止中途意外xx)、吃喝能量(运动饮料,巧克力之类)补充。
  6. 合理安排跑步和休息计划,比如:大致距离的时速,休息时间等。
  7. 跑步节奏,身体和呼吸的调整。千万要稳住,不要和身边的人做比较,自己的身体只有自己知道。跑不动咱就快走来放松一下。

说了这么多,基本都是今天碰到的问题。有兴趣的朋友可以参考 @跑步指南 的一些微博,里面有备战马拉松的一些计划,写的相当不错。

再说一下,关于跑步我自己的一些感受吧。

  1. 装备

    1. 鞋子,最最重要了。平时公路跑或者跑圈,推荐缓冲避震型的跑鞋。Asics有好多入门级的跑鞋,避免对膝盖造成过大的冲击,膝盖歇了,就可以回家躺着了。。。
    2. 衣服,速干衣,主要也是为了不吸汗。不要选择太紧或者太松的衣服,长距离时候要么摩擦太大,要么紧身勒的很累(光好看没P用啊)。
    3. 其他配件,袜子和护膝什么的。袜子选择稍微厚一些的运动袜,护膝不要买太紧,以免影响腿部肌肉和血液循环。
  2. 计划

    1. 跑步间隔,比如跑一休一,这个根据身体自己和运动程度来调节,一周跑个3-4次。“科学证明”跑步间隔不宜超过3天,碰到长假什么的,自己安排个短距离的跑步,找找状态。
    2. 运动量,初期可以选择5-10km的匀速跑。
    3. LSD,这个就跟自己的目标有关,每周尽量来一次“长距离慢跑”,速度慢于平时,追求持久。距离大于平时距离的50%左右,初期不能太大,别翻倍。。。。
    4. 贵在坚持啊。。。。。三天打鱼两天晒网,有可能前面白跑了。
    5. 变速跑,如果只是一味的匀速跑,距离是远了。但是你有可能只是长距离慢跑者。。

半马跑完了,突然对两月后的厦门全马忧心忡忡啊。。亚历山大,看来要安排科学的计划了。

END

Published: 2013-11-03 — Comments

Python Testing

代码写多了越发觉得测试的重要性,之前一直喜欢“目测”的做法已经不值得推荐了。当然,这只是一个玩笑。

在Python代码里测试大概有这么几种:doctestunittestnose(第三方工具)。

个人推荐nose,简单的话doctest也已经足够了。

首先:

  • 代码要易于测试,代码写完对应的测试应该配套跟上。
  • 测试要简单,轻便。

先说最简单的doctest吧,顾名思义,doctest就是在文档(docstring)里完成测试。看以下例子(/tmp/1.py):

def add(a, b):
    """
    >>> add(10, 20)
    30
    """
    return a + b

if __name__ == '__main__':
    import doctest
    doctest.testmod()

可以看出doctest是不是很简单?是不是想起了普通解释器(>>>),运行一下测试(注意-v选项):

smallfish@debian:~$ python /tmp/1.py -v
Trying:
    add(10, 20)
Expecting:
    30
ok
1 items had no tests:
    __main__
1 items passed all tests:
   1 tests in __main__.add
1 tests in 2 items.
1 passed and 0 failed.
Test passed.

作为简单的模块或者工具,main里实现测试已经足够了。但是稍大一些工程或应用,doctest太多的话,看上去会觉得有点臃肿。

下面介绍下nose,偷偷的说下,nose是支持doctest、unittest测试的,所以嘛。先安装一下:

smallfish@debian:~$ pip install nose

继续跑一下上面的例子:

smallfish@debian:~$ nosetests /tmp/1.py --with-doctest -v
Doctest: 1.add ... ok

----------------------------------------------------------------------
Ran 1 test in 0.006s

OK

好吧,写点常见的例子,目录结构如下(foo模块以及foo的测试代码):

smallfish@debian:~$ tree /tmp/foomodule/
/tmp/foomodule/
|-- foo
|   |-- a.py
|   |-- b.py
|   `-- __init__.py
`-- tests
    |-- test_a.py
    `-- test_b.py

模块代码如下:

# /tmp/foomodule/foo/a.py 
def add(a, b):
    return a + b

def double(a):
    return a * 2
    
# /tmp/foomodule/foo/b.py 
import memcache

class Cache:

    def __init__(self, server):
        self.cache = memcache.Client([server])

    def get(self, name):
        return self.cache.get(name)

    def set(self, name, value):
        return self.cache.set(name, value)

    def delete(self, name):
        return self.cache.delete(name)

    def close(self):
        self.cache.disconnect_all()

对应的测试代码:

# /tmp/foomodule/tests/test_a.py 
from foo.a import add, double

def test_add():
    v = add(10, 20)
    assert v == 30

def test_double():
    v = double(10)
    assert v == 20

# /tmp/foomodule/tests/test_b.py 
from foo.b import Cache

class TestCache:

    def setUp(self):
        self.cache = Cache("127.0.0.1:11211")
        self.key   = "name"
        self.value = "smallfish"

    def tearDown(self):
        self.cache.close()

    def test_00_get(self):
        v = self.cache.get(self.key)
        assert v == None

    def test_01_set(self):
        v = self.cache.set(self.key, self.value)
        assert v == True
        v = self.cache.get(self.key)
        assert v == self.value

    def test_02_delete(self):
        v = self.cache.delete(self.key)
        assert v == True

先不解释,跑一跑(-v -s选项,针对当前路径下tests目录测试):

smallfish@debian:/tmp/foomodule$ nosetests -s -v
test_a.test_add ... ok
test_a.test_double ... ok
test_b.TestCache.test_00_get ... ok
test_b.TestCache.test_01_set ... ok
test_b.TestCache.test_02_delete ... ok

----------------------------------------------------------------------
Ran 5 tests in 0.024s

OK

简单吧?nose默认是当前目录下tests目录进行测试,默认规则:文件(含有test),函数(test_开头),类(Test开头)。

nose完整文档请参考:http://nose.readthedocs.org/en/latest/

END

Published: 2013-09-12 — Comments

Python Profile 工具性能分析

最近碰到“程序速度大大降低”的说法,还是直接用数据说明比较有信服力,以及可以找出真正问题所在。

Python自带了几个性能分析的模块:profile、cProfile和hotshot,使用方法基本都差不多,无非模块是纯Python还是用C写的。

官网文档参考:http://docs.python.org/2/library/profile.html

本文示例基于cProfile模块,先写点测试代码(test1.py):

import time

def func1():
    sum = 0
    for i in range(1000000):
        sum += i

def func2():
    time.sleep(10)

func1()
func2()

运行cProfile命令如下:

$ python -m cProfile -o test1.out test1.py

这里是以模块方式直接保存profile结果,当然也可以在程序中引入cProfile模块。

好了,上面的测试程序大概运行10秒左右,可以看下最终的性能分析数据。

$ python -c "import pstats; p=pstats.Stats('test1.out'); p.print_stats()"

Wed Aug 28 22:19:45 2013    test1.out

         6 function calls in 10.163 seconds

   Random listing order was used

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.024    0.024   10.163   10.163 test1.py:1(<module>)
        1   10.001   10.001   10.001   10.001 {time.sleep}
        1    0.092    0.092    0.138    0.138 test1.py:3(func1)
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
        1    0.000    0.000   10.001   10.001 test1.py:8(func2)
        1    0.046    0.046    0.046    0.046 {range}

分析的数据还很清晰和简单的吧,大概能看出几列数据的意思,重点关注下时间和函数调用次数,再来排个序:

$ python -c "import pstats; p=pstats.Stats('test1.out'); p.sort_stats('time').print_stats()"

Wed Aug 28 22:19:45 2013    test1.out

         6 function calls in 10.163 seconds

   Ordered by: internal time

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1   10.001   10.001   10.001   10.001 {time.sleep}
        1    0.092    0.092    0.138    0.138 test1.py:3(func1)
        1    0.046    0.046    0.046    0.046 {range}
        1    0.024    0.024   10.163   10.163 test1.py:1(<module>)
        1    0.000    0.000   10.001   10.001 test1.py:8(func2)
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}

可以看出测试程序中最耗费时间点是:time.sleep和range。

sort_stats支持一下参数:

calls, cumulative, file, line, module, name, nfl, pcalls, stdname, time

pstats模块还支持交互式:

$ python -m pstats test1.out
Welcome to the profile statistics browser.
test1.out% ### help 或者输入字母按下tab,可以补全的
test1.out% stats 10

这里似乎告一段落了,只是输出式的性能分析似乎有点乏味,开源世界这么美好,肯定会有些更美观和帅气的方法吧。

一步步来,先安装依赖项(请选择对应系统平台):

PS:Ubuntu系统请注意请注意,pstats模块因为某些原因不在Python包中,需要单独安装:python-profiler包

装好依赖后,如此这番:

$ ./gprof2dot.py -f pstats test1.out | dot -Tpng -o test1.png

看看test1.png,一切都了然于心了吧。。。

python profile

END

Published: 2013-08-28 — Comments

RabbitMQ REST API

最近造了一个轮子:rabbitmq-http,源于内部项目的一个翻版。基于 Go 语言实现。

先说说为什么要写 HTTP API,在使用 RabbitMQ 过程中碰到了以下几个问题:

  • 多语言,这样就要求每个语言都需要安装对应的 amqp 库。
  • 版本,“历史遗留”原因,对应的 amqp 库比较古老,升级不易。
  • 协议实现不全,这个也是跟版本有点关系。
  • 切分和扩展,想自己逻辑里按照用户或 vhost 等来做 hash 切分有点麻烦。
  • TCP,调试和跟踪不易啊。
  • 功能复杂,对于使用者来说曲线较高,大都数情况只是生产者消费者模型。

其实也不算是问题,就是不爽的地方。也就是想找一个自由度略大的方案,然后使用起来也简单方便。

就整出了一个 HTTP 的方案,刚好最近手痒,想用 Go 语言写点东西,遂趁着周末和晚上整了一下。

仓库地址:https://github.com/smallfish/rabbitmq-http

如何安装依赖、编译和启动这些操作,这里就不再赘述,直接参考项目里的 README.md

目前封装的接口大致分为一下几类:

  • Exchange
    • 新建
    • 删除
  • Queue
    • 新建
    • 删除
    • 绑定/取消绑定
    • 读取消息
  • Message
    • 发布新消息

上面的新建、删除、发送或者读取的行为对应 HTTP 请求中的几种方法,比如:POST、DELETE、GET等。

整个 API 的返回值(HTTP状态码)有一下三类:

  • 200,操作 OK,返回的 Body 体中有简单描述字符。
  • 405,不支持的请求方式,比如接口尚未实现。
  • 500,服务器端错误,Body 体中会有错误的描述。比如队列不存在、删除错误等。

好了,还是来一点实际的例子吧。完全是 curl 操作:

新建一个 Exchange:

$ curl -i -X POST http://127.0.0.1:8080/exchange -d \
'{"name": "e1", "type": "topic", "durable": true, "autodelete": false}'

新建一个 Queue:

$ curl -i -X POST http://127.0.0.1:8080/queue -d \
'{"name": "q1"}'

绑定 Queue 到 Exchange 上:

$ curl -i -X POST http://127.0.0.1:8080/queue/bind -d \
'{"queue": "q1", "exchange": "e1", "keys": ["aa", "bb", "cc"]}'

发布消息到 Exchange,指定 Routing-key:

$ curl -i -X POST http://127.0.0.1:8080/publish -d \
'{"exchange": "e1", "key": "bb", "body": "hahaha"}'

读取一下刚才发送的消息,即消费某个 Queue:

$ curl -i "http://127.0.0.1:8080/queue?name=q1"
        
PS:消费的接口输出为 Chunked 模式,可以用类似文件的行读取方式,接口是 HTTP 长连接。
同时这个接口也支持多个 Queue 一起消费,类似:“/queue?name=q1&name=q2” 即可。

基本常用的接口已经暴露出来, 后续还会封装一些其他语言的 SDK,其实都是普通的 HTTP 的请求。

欢迎 fork,欢迎当小白鼠, ^_^

END

Published: 2013-03-28 — Comments

RabbitMQ trace 日志调试

RabbitMQ 默认日志里只有类似客户端“accpet/close”等信息,对于有异常或者跟踪消息内部结构就比较麻烦了。

翻阅官方教程意外发现了 rabbitmq_tracing 插件和 firehose

注意:打开 trace 会影响消息写入功能,适当打开后请关闭。

自己顺手写了一个封装脚本,参考:https://github.com/smallfish/rabbitmq-trace

安装上面的插件并开启 trace_on 之后,会发现多了两个 exchange:amq.rabbitmq.trace 和 amq.rabbitmq.log,类型均为:topic。

懂了吧,只要订阅这两个主题,就能收到:客户端连接、消息发收等具体信息了。

下面开始测试吧,先安装插件,并打开 trace_on:

$ sudo rabbitmq-plugins enable rabbitmq_tracing
$ (略去重启RabbitMQ命令)
$ sudo rabbitmqctl trace_on
Starting tracing for vhost "/" ...
...done.

测试代码采用 Python 的 pika 库,片段如下:

import pika

def _on_message(ch, method, properties, body):
    ret = {}
    ret['routing_key'] = method.routing_key
    ret['headers'] = properties.headers
    ret['body'] = body
    print ret

conn = pika.BlockingConnection(pika.ConnectionParameters())
chan.queue_declare(exclusive=False, auto_delete=True) # 临时队列
queue = ret.method.queue
chan.queue_bind(exchange='amq.rabbitmq.trace', queue=queue, routing_key='#')
chan.queue_bind(exchange='amq.rabbitmq.log', queue=queue, routing_key='#')
chan.basic_consume(_on_message, queue=queue, no_ack=True)
chan.start_consuming()

运行结果大致如下:

{'body': 'accepting AMQP connection <0.28967.1> (127.0.0.1:52930 -> 127.0.0.1:5672)\n', 
 'headers': None, 'routing_key': 'info'}
{'body': 'accepting AMQP connection <0.28967.1> (127.0.0.1:52930 -> 127.0.0.1:5672)\n', 
 'headers': {'node': 'rabbit@mac', 'exchange_name ': 'amq.rabbitmq.log', 'redelivered': 0, 
 'routing_keys': ['info'], 'properties': {'timestamp': 1355906883, 
 'content_type': 'text/plain'}}, 
 'routing_key': 'deliver.amq.gen-dJNnBwGUuigsfAzoUD9Zlw'}
{'body': '内容略去...', 'headers': {'node': 'rabbit@mac', 'exchange_name': 'amq.direct',
 'routing_keys': ['test2'], 'properties': {}}, 'routing_key': 'publish.amq.direct'}
{'body': 'closing AMQP connection <0.28967.1> (127.0.0.1:52930 -> 127.0.0.1:5672)\n', 
 'headers': None, 'routing_key': 'info'}
{'body': 'closing AMQP connection <0.28967.1> (127.0.0.1:52930 -> 127.0.0.1:5672)\n',
 'headers': {'node': 'rabbit@mac', 'exchange_name': 'amq.rabbitmq.log', 'redelivered': 0, 
 'routing_keys': ['info'], 'properties': {'timestamp': 1355906884, 
 'content_type': 'text/plain'}}, 
 'routing_key': 'deliver.amq.gen-dJNnBwGUuigsfAzoUD9Zlw'}

END

Published: 2012-12-19 — Comments

Go 模块测试

Go 很多地方都透露着“约定大于配置”的理论,比如测试、可见性、语法等等。

本文示例模块为:foo.go,则对应的测试模块为:foo_test.go,测试版本为:go v1.0.3。

先写好示例代码: foo.go

package foo

func Add(a, b int) int {
    return a + b
}

对应的测试代码:foo_test.go

package foo

import "testing"

func TestAdd(t *testing.T) {
    if (Add(1, 2) != 3) {
        t.Error("test foo:Addr failed")
    } else {
        t.Log("test foo:Addr pass")
    }
}   

到这里可以运行测试了:

$ go test
PASS
ok      _/Users/smallfish/test/go/foo   0.080s

或者详细一点的输出:

$ go test -v
=== RUN TestAdd
--- PASS: TestAdd (0.00 seconds)
foo_test.go:9:  test foo:Addr pass
                PASS
ok      _/Users/smallfish/test/go/foo   0.013s

默认测试函数是以“Test”开头,比如:TestXXX。而性能测试代码是以“Benchmark”开头。

下面来简单一段性能测试代码:

func BenchmarkAdd(b *testing.B) {
    // 如果需要初始化,比较耗时的操作可以这样:
    // b.StopTimer()
    // .... 一堆操作
    // b.StartTimer()
    for i := 0; i < b.N; i++ {
        Add(1, 2)
    }
}

跑一下性能测试:

$ go test -test.bench=".*"
PASS
BenchmarkAdd    2000000000               1.27 ns/op
ok      _/Users/smallfish/test/go/foo   2.702s

更多请参考:“go help test” 命令和 testing 模块

END

Published: 2012-12-07 — Comments

lua-resty-beanstalkd 模块教程

本文涉及几个名词:

下面是读写的示例:

  1. 生产者(发布消息),主要那应用了两个协议:use/put,代码如下:
location /t {
    content_by_lua '
        local beanstalkd = require "resty.beanstalkd"
        
        local bean, err = beanstalkd:new()

        local ok, err = bean:connect("127.0.0.1", 11300)
        if not ok then
            ngx.say("1: failed to connect: ", err)
            return
        end

        local ok, err = bean:use("default")
        if not ok then
            ngx.say("2: failed to use tube: ", err)
            return
        end
   
        local id, err = bean:put("hello")
        if not id then
            ngx.say("3: failed to put: ", err)
            return
        end

        ngx.say("put: ", id)

        bean:close()
    ';
}
  1. 消费者(消费消息),主要用了三个协议:watch/reserve/delete,代码如下:
location /t {
    content_by_lua '
        local beanstalkd = require "resty.beanstalkd"

        local bean, err = beanstalkd:new()

        local ok, err = bean:connect("127.0.0.1", 11300)
        if not ok then
            ngx.say("1: failed to connect: ", err)
            return
        end

        local ok, err = bean:watch("default")
        if not ok then
            ngx.say("2: failed to watch: ", err)
            return
        end

        local id, data = bean:reserve()
        if not id then
            ngx.say("3: failed to reserve: ", id)
            return
        else
            ngx.say("1: reserve: ", id)
            local ok, err = bean:delete(id)
            if not ok then
                ngx.say("4: failed to delete: ", id)
                return
            end
            ngx.say("2: delete: ", id)
        end

        bean:close()
    ';
}

为了提升性能,可以调用 set_keepalive(提升50%以上性能),需要注意:

  1. 代码中不要调用 xx:close 主动关闭连接。
  2. 在对象使用后调用 xx:set_keepalive,而不是初始化 new/connect 时候调用此方法。

END

Published: 2012-11-25 — Comments

LuaJIT FFI 调用 Curl 示例

LuaJIT 是一个好东西,比官方 Lua 解释器性能上提升很多。ngx_lua/ngx_openresty 都推荐用 LuaJIT 来加速 Lua 代码。

除去性能和速度上的优势,LuaJIT 还提供了 C Binding 模块:FFI,可以理解为类似 Python 中的 ctypes 模块,不过更加小巧和直观。意味着可以很方便的调用动态库啦。

下面示例如何在 FFI 调用 Curl 中的相关函数,环境如下:

操作系统:   Mac OS X 10.8.2
LuaJIT版本:LuaJIT-2.0.0-beta10
Curl版本:  7.24.0

测试代码如下:

local ffi = require 'ffi'

ffi.cdef[[
    void *curl_easy_init();
    int curl_easy_setopt(void *curl, int option, ...);
    int curl_easy_perform(void *curl);
    void curl_easy_cleanup(void *curl);
    char *curl_easy_strerror(int code);
]]

local libcurl = ffi.load('libcurl')

local curl = libcurl.curl_easy_init()
local CURLOPT_URL = 10002 -- 参考 curl/curl.h 中定义

if curl then
    libcurl.curl_easy_setopt(curl, CURLOPT_URL, 'http://example.com')
    res = libcurl.curl_easy_perform(curl)
    if res ~= 0 then
        print(ffi.string(libcurl.curl_easy_strerror(res)))
    end
    libcurl.curl_easy_cleanup(curl)
end

参考:http://develcuy.com/en/playing-luajit-ffi(被此文坑了一下,示例中 curl_easy_setopt 函数 option 类型为:char,正确的应该是:int)

报错信息如下(可以辅助设置 VERBOSE(值为41) 选项调试):

URL using bad/illegal format or missing URL

END

Published: 2012-10-11 — Comments