[date: 2021-09-02 20:30] [visits: 15]

OpenResty

最近笔者写了两篇在OpenResty上进行插件开发的文章,但可能忽略了一个关键点,即读者也许不知道OpenResty是什么,能做什么,因此对于怎么做并不感兴趣。本文尝试从是什么、能做什么、怎么做三个角度对OpenResty进行更详细的说明,争取让读者理解并对OpenResty有兴趣。

Nginx & Lua

Nginx

OpenResty最精简的描述是等于Nginx + Lua。

Nginx是HTTP反向代理杰出的代表、最佳实践,主要可完成如下工作:

PS: 笔者曾有疑问为何请求从client -> nginx -> backend server,却将backend server称为Upstream,因为HTTP资源存在服务器,Upstream描述的是资源流向而非数据流向

Nginx虽强大优秀,但随技术更新迭代所体现出美中不足的点是扩展能力不灵活,需要用C/C++编写插件模块,这对当下开发者不友好,门槛高且开发周期长。

OpenResty的出现完美解决了Nginx上述缺点,赋予开发者在Nginx上使用Lua开发扩展的能力,扩展实现灵活高效且Nginx的高性能也继续得以保持。

Lua

Lua是葡萄牙语月亮的意思,总结一下主要有如下特点:

通过学习与实践Lua基础语法、数据类型、模块机制这些内容,整体感觉较简单,稍高阶的内容有模式匹配、协程、元表元方法、C API等。在OpenResty上使用Lua开发扩展,基础能力正常够用,一般不会过于依赖高阶内容。

是什么?

OpenResty是什么?笼统描述是Nginx原生能力 + Lua扩展能力,在不失去Nginx能力的同时可利用Lua实现各种扩展。

Nginx支持按照既定约束集成C/C++编写的模块,在流量处理中与Nginx交互以满足可扩展需求。OpenResty是开发者agentzh利用Lua与C API交互方便,在Nginx可扩展的“位置”植入了Lua代码执行能力,并提供了与Nginx交互的Lua核心模块实现。

以JavaScript举例,假设利用适配器模式(Adapter Pattern)为程序留下了一个自定义行为入口,使用者在运行时可提供一个文件实现适配器,动作发生时将调用使用者所提供的适配器方法。程序设计之初,认为所有人都会写JavaScript,因此代码可能长这样:

// 运行者通过env提供adapterPath
const adapter = require(`${process.env.adapterPath}`); 

function run() {
    let result = {};
    
    // some code
    
    // 如果adapter提供transformResult方法,程序将在返回结果之前对结果调用transformResult方法
    if (adapter.transformResult) {
        return adapter.transformResult(result);
    }
    
    return result;
}

上述所举例的Adapter,其隐含了一个限制:“所提供的adapterPath必须是Node.js require所支持的”,因此无法使用其他语言实现Adapter。

假设上述run方法是Nginx内部请求Upstream并将返回的result发送给Downstream的实现,则提供的Adapter可以在最后返回前进行一些定制处理(扩展能力)。但Nginx使用C语言实现,代码需被编译为机器码执行,因此按照原生扩展方式开发,将要用C/C++实现并与Nginx一起编译为最终可执行文件。这个门槛与成本绝大多数开发者难以接受,而OpenResty提供了完善支持,让开发者能够使用Lua语言进行上述扩展能力的实现,且继续保持高性能。

能做什么?

上图是OpenResty从配置->启动->处理请求->响应的过程描述,其中*_by_lua是能够提供Lua代码进行能力扩展的位置,每个位置能做什么下文将具体说明。

*_by_lua,*_by_lua_block,*_by_lua_file,一般推荐使用后两者,极短的代码或调试使用block,其他使用*_by_lua_file

init_by_lua

Nginx是Master-Worker进程模型,Master负责协调Worker但不处理实际工作。在启动时Master需要先进行初始化,init_by_lua可在此阶段初始化全局配置或模块,如初始化lua_shared_dict shm(进程间共享内存)。

init_worker_by_lua

Master启动以及初始化完成后,Fork Worker进程,init_worker_by_lua在Worker进程初始化过程中执行,可以初始化进程专用功能。

ssl_certificate_by_lua

建立HTTPS连接的SSL握手阶段执行,此处可以动态设置证书与私钥,如同一个server中配置多个Host,可以利用该指令实现不同Host的SSL握手过程使用不同证书与私钥。

set_by_lua

Rewrite阶段执行,用于修改Nginx变量。

rewrite_by_lua

在Rewrite阶段最后执行,可以进行改写URL或重定向到外部URL。同一个域名接入多个不同的Upstream,Upstream之间的URL可能冲突,此时可为每个Upstream定义命名空间前缀,转发时rewrite去除。比如client /scope/api/xxx => upstream /api/xxx(这种简单rewrite规则推荐用Nginx原生指令)。

access_by_lua

已确认匹配的Location准备正式“产生”内容时执行,此处可进行拦截确认是否继续向前,比如限流限频检查,权限检查,人机检查等等,不通过返回错误或者其他内容中断后续处理。

content_by_lua

提供Lua代码动态生成响应数据的能力,用于Lua进行业务开发场景,当下很少这么做,一般选择将请求转发到Upstream。

balancer_by_lua

在将请求准备转发给Upstream时,balancer_by_lua提供了决定转发到哪台server的能力。在微服务快速发展的今天,借助此能力可以实现服务发现,智能负载均衡等功能。

header_filter_by_lua

返回响应头部时执行,此处可以加工处理响应头,如实现跨域。

body_filter_by_lua

返回响应体时执行,此处可以加工处理响应体,比如统一响应格式。笔者盲猜需要在这一阶段介入处理场景较少,性能影响以及其他需要考虑的因素会比较多。

log_by_lua

响应完成后,日志阶段、收尾工作,如上报APM系统、资源清理等。

怎么做?

怎么做,将在后续逐步补充更多功能的实践记录。

历史

在进行OpenResty相关实践时,感觉相关生态停留在几年之前,似乎没怎么发展,而云原生能力在不断增强,场景解决方案中OpenResty的身影越来越少,但有另外一个新起之秀Kong(OpenResty + 插件生态?),笔者准备详细了解一番。

摘录描写一段OpenResty历史,以表敬意:

2015年笔者与OpenResty有过一面之缘,由于当时工作不久技术积累过少,因而错过,直到5年后的2020年,得以重拾与它的缘分,感叹一句相见恨晚

参考资料