OpenResty Con 2017 见闻杂记

今年的 OpenResty Con 在北京举行,考虑到路途过于遥远,我决定看直播。虽然参与的方式变了,但我依旧跟去年一样,趁着记忆还算清晰写了篇文章,算是“路边社”的新闻稿吧。

第一个演讲者是来自于 Strikingly 的龚凌晖,讲的是 Strikingly 内部用 OpenResty 实现的缓存系统。Strikingly 通过 OpenResty 在应用服务器前面实现了一层代理。他们把缓存数据放置在 S3/OSS 上,元数据放在 Redis 上。请求先打到 OpenResty 集群上,如果对应的缓存可用,就直接返回缓存结果,否则再访问源站并更新缓存。这个有点像放大版的 Nginx proxy_cache。凌晖的演讲有一点挺有趣的,他提到在当初做技术选型时,比较过基于 OpenResty 开发和自己造轮子两个方案。OpenResty 相关的库少,而且剩下的库还是良莠不齐的;然而自己造轮子开发量比较大,需要重新实现不少功能。相比之下,用 OpenResty 就只用造相关的库,虽然轮子还是要造的,但是工作量少了不少。

之后演讲的是来自 Kong 的 Thibault Charbonnier。Kong 公司是 OpenResty 的重度使用者,经常能看到他们的工程师分享关于 OpenResty 的演讲/文章。Thibault 本人就给 OpenResty 项目贡献了不少代码。演讲的内容是 Kong 工程师用 OpenResty 重写过去由其他软件提供的功能,使得 Kong API Gateway 这个产品变得越来越“开箱即用”的过程。过去 Kong API Gateway 除了用到 OpenResty 之外,还要打包用于生成 uuid 的 libuuid、用于进程间通讯的 serf、用于 DNS 查询的 dnsmasq,等等。这一连串玩意无论是部署还是维护起来都挺麻烦,尤其考虑到 Kong 公司做的是 To B 的产品,这种麻烦可能还要乘个系数翻几番。后面他们在 OpenResty 里面实现了 uuid 的功能(lua-resty-jit-uuid),写了 lua-resty-dns-client 这个库来进行 DNS 查询,进程间通讯的逻辑也改成在 OpenResty 内部完成。这下就把其他乱七八糟的小软件都砍掉了,仅剩 OpenResty 和数据库两种组件。作为同样做 To B 商业软件的程序员,我对这种对简化工具栈的追求深有同感。如何适配客户花样百出的现场环境,一直是做 To B 产品的一大难题。既然无法“店大欺客”地要求客户提供特定的环境,就只能遵循 less is more 的原则,尽量把软件栈做简单来避免意料之外的问题。除了讲述 Kong API Gateway 开发过程中大刀阔斧精兵简政的故事外,Thibault 还提到了他们开源出来的 worker 通讯库 lua-resty-worker-events、双层缓存库 lua-resty-ml、测试辅助库 lua-resty-busted 等等。

上午最后一个演讲是春哥介绍 OpenResty Inc. 的商业产品,也算是之前他一直唠叨的小语言的首次公开亮相吧。春哥展示了用于描述网关规则的 edgelang,语法看上去像是 erlang,能够根据模式匹配执行对应的动作。还有一个类似于 test-nginx 语法的,用于驱动测试集 testml。(有趣的是,testml 是在线上节点运行的,让我想起那个“true programmer tests in production”的段子……)此外还有个 schemalang,用于生成接口部分的代码,类似于 Swagger Api Spec。这些小语言都是用 fanlang 这门小语言开发的,最后会被翻译成 Lua 代码和 Nginx 配置。春哥还提到他们的产品力求以最小的组件获取最多的功能。跟其他 CDN 公司的技术栈不同,OpenResty Inc. 只用到了 OpenResty 和 PostgreSQL,连消息总线和内存数据库都是在 OpenResty 内部实现的。看来尽量减少组件数,确实是 To B 的公司的共识。

下午的第一个演讲,是 OpenResty Inc. 技术合伙人孙大同做的,关于新的 stream-lua-nginx-module 的介绍。为什么说是新的呢?之前 Nginx 1.9 的时候,春哥曾经基于 lua-nginx-module 改过一个 stream-lua-nginx-module。但由于 Nginx 官方在 1.13 大幅修改了 Nginx stream 子系统,原来的 stream 系统已经无法继续维护下去了。所以目前的 stream-lua-nginx-module 是基于 1.13 版的 stream 子系统重写的。大同介绍了 stream 子系统的一些功能,当然更为重要的是相关的坑。

第一个坑是 stream 系统在处理 UDP 时,是逐个包逐个包转发,性能可能会不太理想。其次由于 stream 工作在较为底层的部分,所以 LuaJIT 的 GC 对性能的影响更为明显。

另外在处理 TCP 连接过程中,由于连接的生命周期较长,频繁的内存分配可能会导致内存占用居高不下。stream 子系统的代码我没有看过,http 子系统里面,每个请求分配的内存只有在请求体生命周期结束时才会回收,不知道内存占用是否也由相似的原因导致的?

还有一个坑,stream 操作需要避免在未读完 socket 的读缓冲区前调用 close,因为 OS 会发送一个 RST 包而不会跟预期一样发送响应(对该现象的详细解释)。这个问题在 http 子系统中没有暴露出来,但是如果你需要跟更低层级的协议打交道 -- 你需要看下他们为此新增的 shutdown 接口。

大同还提到 stream 子系统中的 ngx.print/say 和直接操作 ngx.req.socket 会有冲突,需要调用 ngx.flush 及时输出响应。

最后有一个重要的点,stream-lua-nginx-module 是由 meta-lua-nginx-module 里面的 tt2 模板文件编译出来的。用模板来生成代码有利于同时保证 http 版本和 stream 版本能够及时同步,不过如果有修改,那么需要先改 C 代码,测试通过后再反哺回模板文件,貌似会比较麻烦?

紧跟在大同之后的演讲,是 codedump 老师关于 Lua 5.1 GC 实现的介绍。LuaJIT 虽然跟 Lua 5.1 有天壤之别,但是 GC 这一块却是大同小异的。codedump 老师讲解了 Lua 之中的增量标记-清扫回收算法,里面用的是经典的三色标记(未标记对象为白、正在标记对象为灰、子对象全部标记完毕的对象为黑,垃圾回收时回收白色对象)。美中不足的是,可能由于时间关系,细节部分没有更好地说明,比如为什么 table 的屏障和其他对象不同等等。如果对这些细节感兴趣,可以看下 codedump 老师自己写的一篇文章:Lua GC。随便吐嘈下 LuaJIT 计划的新一代GC 什么时候能够实现呢?现行的 GC 实现跟其他高性能语言差距挺大的呢。也许新的 LuaJIT 维护者会带来一些改变。随便解答下 Q&A 阶段那个标记-清扫算法如何解决循环依赖的问题:如果循环依赖的多个对象中,每一个对象都没有被其他对象引用,则初始化阶段后这几个对象一直都是白色,于是就能被回收了。

下一个演讲者是李凯,快乐茄的后台研发工程师。因为他的演讲偏向于具体的业务,所以我也没什么记录的…… 印象里他问了个为什么用 ngx.timer.at(0, ...) 会比 ngx.thread.spawn 更占用资源的问题,然后孙大同回答,说是前者(timer 对象)会创建一个包含 Lua Thread 的 fake request 对象,而后者只创建 Lua Thread 相关的对象,所以会更加轻量。

当天收官的演讲者是来自于新浪微博的周晶,OpenResty 社区的老朋友。周晶讲的是当 Motan RPC 框架遇见 OpenResty,两者碰撞出的火花。可惜我对 RPC 框架没什么了解,所以听得云里雾里的。不过周晶提到一个有趣的思路,就是像 PHP 这一类的语言只实现 RPC 的 client,server 部分可以由 OpenResty 负责协议的卸载,然后再以 CGI 的方式转调背后的实现。如果有用 OpenResty 介入 RPC 的需求的同行,建议听下这个演讲。(IT大咖说上有视频回放)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Lua-Resty-Checkups是一个基于lua的upstream管理和健康检查模块,由又拍云开源。特点:支持周期性upstream服务管理操作支持管理和健康检查支持upstream动态更新有利于加权轮询或哈希平衡支持 Nginx C upstream同步操作可使用级别和键值实现集群使用简介:-- config.lua _M = {} _M.global = {     checkup_timer_interval = 15,     checkup_shd_sync_enable = true,     shd_config_timer_interval = 1, } _M.ups1 = {     cluster = {         {             servers = {                 {host="127.0.0.1", port=4444, weight=10, max_fails=3, fail_timeout=10},             }         },     }, }lua_package_path "/path/to/lua-resty-checkups/lib/checkups/?.lua;/path/to/config.lua;;"; lua_shared_dict state 10m; lua_shared_dict mutex 1m; lua_shared_dict locks 1m; lua_shared_dict config 10m; server {     listen 12350;     return 200 12350; } server {     listen 12351;     return 200 12351; } init_worker_by_lua_block {     local config = require "config"     local checkups = require "resty.checkups.api"     checkups.prepare_checker(config)     checkups.create_checker() } server {     location = /12350 {         proxy_pass http://127.0.0.1:12350/;     }     location = /12351 {         proxy_pass http://127.0.0.1:12351/;     }     location = /t {         content_by_lua_block {             local checkups = require "resty.checkups.api"             local callback = function(host, port)                 local res = ngx.location.capture("/" .. port)                 ngx.say(res.body)                 return 1             end             local ok, err             -- connect to a dead server, no upstream available             ok, err = checkups.ready_ok("ups1", callback)             if err then ngx.say(err) end             -- add server to ups1             ok, err = checkups.update_upstream("ups1", {                     {                         servers = {                             {host="127.0.0.1", port=12350, weight=10, max_fails=3, fail_timeout=10},                         }                     },                 })             if err then ngx.say(err) end             ngx.sleep(1)             ok, err = checkups.ready_ok("ups1", callback)             if err then ngx.say(err) end             ok, err = checkups.ready_ok("ups1", callback)             if err then ngx.say(err) end             -- add server to new upstream             ok, err = checkups.update_upstream("ups2", {                     {                         servers = {                             {host="127.0.0.1", port=12351},                         }                     },                 })             if err then ngx.say(err) end             ngx.sleep(1)             ok, err = checkups.ready_ok("ups2", callback)             if err then ngx.say(err) end             -- add server to ups2, reset rr state             ok, err = checkups.update_upstream("ups2", {                     {                         servers = {                             {host="127.0.0.1", port=12350, weight=10, max_fails=3, fail_timeout=10},                             {host="127.0.0.1", port=12351, weight=10, max_fails=3, fail_timeout=10},                         }                     },                 })             if err then ngx.say(err) end             ngx.sleep(1)             ok, err = checkups.ready_ok("ups2", callback)             if err then ngx.say(err) end             ok, err = checkups.ready_ok("ups2", callback)             if err then ngx.say(err) end     } }Lua 配置示例:_M = {} -- Here is the global part _M.global = {     checkup_timer_interval = 15,     checkup_timer_overtime = 60,     default_heartbeat_enable = true,     checkup_shd_sync_enable = true,     shd_config_timer_interval = 1, } -- The rests parts are cluster configurations _M.redis = {     enable = true,     typ = "redis",     timeout = 2,     read_timeout = 15,     send_timeout = 15,     protected = true,     cluster = {         {   -- level 1             try = 2,             servers = {                 { host = "192.168.0.1", port = 6379, weight=10, max_fails=3, fail_timeout=10 },                 { host = "192.168.0.2", port = 6379, weight=10, max_fails=3, fail_timeout=10 },             }         },         {   -- level 2             servers = {                 { host = "192.168.0.3", port = 6379, weight=10, max_fails=3, fail_timeout=10 },             }         },     }, } _M.api = {     enable = false,     typ = "http",     http_opts = {         query = "GET /status HTTP/1.1\r\nHost: localhost\r\n\r\n",         statuses = {             [500] = false,             [502] = false,             [503] = false,             [504] = false,         },     },     mode = "hash",     cluster = {         dc1 = {             servers = {                 { host = "192.168.1.1", port = 1234, weight=10, max_fails=3, fail_timeout=10 },             }         },         dc2 = {             servers = {                 { host = "192.168.1.2", port = 1234, weight=10, max_fails=3, fail_timeout=10 },             }         }     } } _M.ups_from_nginx = {     timeout = 2,     cluster = {         {   -- level 1             upstream = "api.com",         },         {   -- level 2             upstream = "api.com",             upstream_only_backup = true,         },     }, } return _M
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值