背景
Orange
属于流量网关(Api Geteway
),项目托管在Github
(https://github.com/orlabs/orange)上,目前活跃度较弱(2年未更新)。通常将`orange`和另一个流行的网关项目`Kong`进行比较,其实`orange`大部分组件都是参考`Kong`实现的,但是活跃度远不及`Kong`。
orange
和Kong
本质上都是OpenResty
应用,Lua
语言编写,Lua
在葡萄牙语里代表美丽的月亮。Openresty
和Lua
语言的关系类似JVM
虚拟机和Java
语言的关系。OpenResty
可以理解为一个集成了很多模块的定制版nginx
。
本文以orange 0.7.0
版本介绍项目的整体架构和原理,最后通过案例介绍如何研发实现一个orange
插件。
第一部分 知识准备
1.1 Openresty
介绍
Nginx
组件已经被广泛应用在web应用架构中,例如:负载均衡、反向代理、代理缓存以及流量限流等场景。Nginx
开发则需要C/C++语言,还需要了解底层接口,学习门槛较高。2009年,淘宝的章亦春和王晓哲一起设计了第二代的 OpenResty
。在王晓哲的提议下,选择了小而美的脚本语言 lua
进行开发。这就是ngx_lua
模块,并且将Nginx
核心、LuaJIT
、ngx_lua
模块、许多有用的Lua
库和常用的第三方Nginx
模块组合在一起成为OpenResty
。
Lua
脚本语言,不需要编译就可以执行,更加灵活。而且 OpenResty
还把 Lua
自身的协程与 Nginx
的事件机制完美结合在一起,优雅地实现了许多其他语言所没有的同步非阻塞编程范式,能够轻松开发出高性能的 Web 应用。
我们知道Nginx
的配置文件是文件存储,修改后需要reload
。而Lua
有代码热加载特性,不需要重启进程,就能够从磁盘、Redis
或者任何其他地方加载数据,随时替换内存里的代码片段。这就带来了“动态配置”,让 OpenResty
能够永不停机,在微秒、毫秒级别实现配置和业务逻辑的实时更新,比起 Nginx
秒级的重启是一个极大的进步。
OpenResty
还选用了LuaJIT
作为 Lua
语言的运行时(Runtime
),提升效率。LuaJIT
是一个高效的 Lua
虚拟机,支持 JIT(Just In Time)
技术,可以把 Lua
代码即时编译成“本地机器码”,这样就消除了脚本语言解释运行的劣势,让Lua
脚本跑得和原生 C 代码一样快。
1.2 Openresty
阶段式处理
Nginx
处理请求的过程一共划分为 11 个阶段,按照执行顺序依次是 post-read、server-rewrite、find-config、rewrite、post-rewrite、preaccess、access、post-access、try-files、content
以及 log
。
基于Nginx
底层的阶段处理,OpenResty
也使用“流水线”来处理 HTTP
请求。Nginx
的处理流水线是由一个个 C 模块组成的,只能在静态文件里配置,开发困难,配置麻烦(相对而言)。而 OpenResty
的处理流水线则是由一个个的 Lua
脚本组成的,不仅可以从磁盘上加载,也可以从Redis
、MySQL
里加载,而且编写、调试的过程非常方便快捷。
Nginx 把一个请求分成了很多阶段,这样第三方模块就可以根据自己行为,挂载到不同阶段进行处理达到目的。OpenResty 也应用了同样的特性。所不同的是,OpenResty 挂载的是我们编写的 Lua 代码。OpenResty 的阶段,比起 Nginx,OpenResty 的阶段更注重对 HTTP 请求响应报文的加工和处理。
OpenResty 里有几个阶段与 Nginx 是相同的,比如 rewrite、access、content、filter,这些都是标准的 HTTP 处理。
在这几个阶段里可以用“xxx_by_lua”指令嵌入 Lua 代码,执行重定向跳转、访问控制、产生响应、负载均衡、过滤报文等功能。因为 Lua 的脚本语言特性,不用考虑内存分配、资源回收释放等底层的细节问题,可以专注于编写非常复杂的业务逻辑,比 C 模块的开发效率高很多,即易于扩展又易于维护。
第二部分
版本是一个重构版本, 着重为了解决之前版本在有大量规则配置时性能损耗的问题。 基本的设计思路是将原来的规则细分成两层, 第一层叫做selector, 用于将流量进行第一步划分, 在进入某个selector后才按照之前的设计进行规则匹配, 匹配到后进行相关处理。
https://lengrongfu.github.io/2019/05/21/orange-%E5%8E%9F%E7%90%86/
https://book.aikaiyuan.com/openresty/understanding-orange.html
https://zhuanlan.zhihu.com/p/67481992
网关优化项目
https://github.com/starjiang/xorange
orange
运行后根据nginx.conf
文件中location
配置来进行顺序匹配流量。
1 | location / { |
第一部分 orange 中设计概念
Nginx 的请求处理阶段共有 11 个之多,我们先介绍其中 3 个比较常见的。按照它们执行时的先后顺序,依次是 rewrite
阶段、access
阶段以及 content
阶段(后面我们还有机会见到其他更多的处理阶段)。
orange 缓存
在nginx.conf
文件中:
1 | lua_code_cache on; |
这些配置是插件缓存数据。
orange中数据持久化
orange中插件基本构成
选择器(selector)
规则(rule)
第二部分 orange的启动过程
2.1
1 | init_by_lua_block { |
第三部分 orange中的插件
第四部分 如何开发一个orange
插件
通常一个插件的代码分为两个部分:后端和前端。
需求分析
业务逻辑介绍
插件对过滤后的目标流量访问的接口进行验签认证(SignatureAuth)。
- 插件支持多用户,在实际线上环境,对于一个借口,通常是多用户需要访问该接口。就需要我们针对不同用户分配密钥(accessKey);
- 插件支持用户将认证字段放在header还是query中;
- 插件支持用户自由添加字段;
客户端包装认证参数
- 传入参数:accessKey,accessSecret
- 生成参数
- paramStr:method=akauth&client=$UUID&rand=Math.rand()
- timeStr:String.valueOf(System.currentTimeMillis()/1000)
- sginStr:accessKey+timeStr+paramStr
- 签名计算:signature=base64(HmacSHA1.init(accessSecret).doFinal(signStr))
- 传递参数:accessKey,timeStr,paramStr,signature
参数放在header中。新增三个字段,分别是:timestamp、sign、appName
其中timestamp 作为判断密钥的有效期。
服务端转发认证参数
- 接收参数:accessKey,timeStr,paramStr,signature
- 请求 认证服务接口
后端
后端代码位于:orange/pligins
中,在源码中创建新增插件名称命名的子目录,例如:signature_auth_header
。
每个插件的子目录中约定必须有两个文件(api.lua
、handler.lua
):
1 | signature_auth_header/ |
其中
下面的函数可以从流量请求中提取header表。
1 | local headers = ngx.req.get_headers() |
前端
插件前后端使用经典MVC模式。前端代码文件在dashboad
中,目录结构如下:
1 | routes |
在views
中主页中增加插件:
1 | dashboard\views\common\left_nav.html |
代码:
1 | <li id="nav-signature-auth-header"> |
最后提交git的清单如下:
1 |
参考文献及资料
[1] Orange
官网,链接:http://orange.sumory.com/
[2] Orange
网关官网docker
,链接:https://hub.docker.com/r/syhily/orange
[3] agentzh
的 Nginx
教程(版本 2020.03.19),链接:http://openresty.org/download/agentzh-nginx-tutorials-zhcn.html
[4] OpenResty
最佳实践,链接:https://moonbingbing.gitbooks.io/openresty-best-practices/content/