OpenResty 最佳实践 (1)
此文已由作者汤晓静授权网易云社区发布。
欢迎访问网易云社区,了解更多网易技术产品运营经验。
OpenResty 发展起源
OpenResty(也称为 ngx_openresty)是一个全功能的 Web 应用服务器。它打包了标准的 nginx 核心,很多的常用的第三方模块,以及它们的大多数依赖项。 通过揉和众多设计良好的 nginx 模块,OpenResty 有效地把 nginx 服务器转变为一个强大的 Web 应用服务器,基于它开发人员可以使用 lua 编程语言对 nginx 核心以及现有的各种 nginx C 模块进行脚本编程,构建出可以处理一万以上并发请求的极端高性能的 Web 应用。
OpenResty 致力于将你的服务器端应用完全运行于 nginx 服务器中,充分利用 nginx 的事件模型来进行非阻塞 I/O 通信。不仅仅是和 HTTP 客户端间的网络通信是非阻塞的,与 MySQL、PostgreSQL、Memcached 以及 Redis 等众多后端之间的网络通信也是非阻塞的。 因为 OpenResty 软件包的维护者也是其中打包的许多 nginx 模块的作者,所以 OpenResty 可以确保所包含的所有组件可以可靠地协同工作。
OpenResty 最早是雅虎中国的一个公司项目,起步于 2007 年 10 月。当时兴起了 OpenAPI 的热潮,用于满足各种 Web Service 的需求,基于 Perl 和 Haskell 实现; 2009 章亦春在加入淘宝数据部门的量子团队,决定对 OpenResty 进行重新设计和彻底重写,并把应用重点放在支持像量子统计这样的 Web 产品上面,这是第二代的 OpenResty,基于 nginx 和 lua 进行开发。
为什么要取 OpenResty 这个名字呢?OpenResty 最早是顺应 OpenAPI 的潮流做的,所以 Open 取自“开放”之意,而 Resty 便是 REST 风格的意思。虽然后来也可以基于 ngx_openresty 实现任何形式的 Web service 或者传统的 Web 应用。
也就是说 nginx 不再是一个简单的静态网页服务器,也不再是一个简单的反向代理了,OpenResty 致力于通过一系列 nginx 模块,把 nginx 扩展为全功能的 Web 应用服务器,目前有两大应用目标:
通用目的的 Web 应用服务器。在这个目标下,现有的 Web 应用技术都可以算是和 OpenResty 或多或少有些类似,比如 Nodejs,PHP 等等,但 OpenResty 的性能更加出色。
nginx 的脚本扩展编程,为构建灵活的 Web 应用网关和 Web 应用防火墙等功能提供了极大的便利性。
OpenResty 特性概括如下:
基于 nginx 的 Web 服务器
打包 nginx 核心、常用的第三方模块及依赖项
使用 lua 对 nginx 进行脚本编程
充分利用 nginx 的事件模型进行非阻塞 I/O 通信
使用 lua 以同步方式进行异步编程
拓展后端通信方式
综合 OpenResty 的特性,它不仅具备 nginx 的负载均衡、反向代理及传统 http server 等功能,还可以利用 lua 脚本编程实现路由网关,实现访问认证、流量控制、路由控制及日志处理等多种功能;同时利用 cosocket 拓展和后端(mysql、redis、kafaka)通信后,更可开发通用的 restful api 程序。
OpenResty 之 lua 编程
lua 简介
1993 年在巴西里约热内卢天主教大学诞生了一门编程语言,他们给这门语言取了个浪漫的名字 — lua,在葡萄牙语里代表美丽的月亮。事实证明他们没有糟蹋这个优美的单词,lua 语言正如它名字所预示的那样成长为一门简洁、优雅且富有乐趣的语言。
lua 从一开始就是作为一门方便嵌入(其它应用程序)并可扩展的轻量级脚本语言来设计,因此她一直遵从着简单、小巧、可移植、快速的原则,官方实现完全采用 ANSI C 编写,能以 C 程序库的形式嵌入到宿主程序中。luaJIT 2 和标准 lua 5.1 解释器采用的是著名的 MIT 许可协议。正由于上述特点,所以 lua 在游戏开发、机器人控制、分布式应用、图像处理、生物信息学等各种各样的领域中得到了越来越广泛的应用。其中尤以游戏开发为最,许多著名的游戏,比如 World of Warcraft、大话西游,都采用了 lua 来配合引擎完成数据描述、配置管理和逻辑控制等任务。即使像 Redis 这样中性的内存键值数据库也提供了内嵌用户 lua 脚本的官方支持。
作为一门过程型动态语言,lua 有着如下的特性:
变量名没有类型,值才有类型,变量名在运行时可与任何类型的值绑定;
语言只提供唯一一种数据结构,称为表(table),它混合了数组、哈希,可以用任何类型的值作为 key 和 value。提供了一致且富有表达力的表构造语法,使得 lua 很适合描述复杂的数据;
函数是一等类型,支持匿名函数和正则尾递归(proper tail recursion);
支持词法定界(lexical scoping)和闭包(closure);
提供 thread 类型和结构化的协程(coroutine)机制,在此基础上可方便实现协作式多任务;
运行期能编译字符串形式的程序文本并载入虚拟机执行;
通过元表(metatable)和元方法(metamethod)提供动态元机制(dynamic meta-mechanism),从而允许程序运行时根据需要改变或扩充语法设施的内定语义;
能方便地利用表和动态元机制实现基于原型(prototype-based)的面向对象模型;
从 5.1 版开始提供了完善的模块机制,从而更好地支持开发大型的应用程序;
lua 基础数据类型
print(type("hello world")) --> output:stringprint(type(print)) --> output:functionprint(type(true)) --> output:booleanprint(type(360.0)) --> output:numberprint(type(nil)) --> output:nil
nil
nil 是一种类型,lua 将 nil 用于表示“无效值”。一个变量在第一次赋值前的默认值是 nil,将 nil 赋予给一个全局变量就等同于删除它。
local numprint(num) --> output:nil num = 100print(num) --> output:100
boolean (true/false)
布尔类型,可选值 true/false;lua 中 nil 和 false 为“假”,其它所有值均为“真”,比如 0 和空字符串就是“真”。
local a = truelocal b = 0local c = nilif a then
print("a") --> output:aelse
print("not a") -- 这个没有执行
endif b then
print("b") --> output:belse
print("not b") -- 这个没有执行
endif c then
print("c") -- 这个没有执行else
print("not c") --> output:not c
end
number
Number 类型用于表示实数,和 C/C++ 里面的 double 类型很类似。可以使用数学函数 math.floor(向下取整)和 math.ceil(向上取整)进行取整操作。
local order = 3.99local score = 98.01print(math.floor(order)) --> output:3print(math.ceil(score)) --> output:99
string
和其他语言 string 大同小异
local str1 = 'hello world'local str2 = "hello lua"local str3 = [["add\name",'hello']]
local str4 = [=[string have a [[]].]=]print(str1) --> output:hello worldprint(str2) --> output:hello luaprint(str3) --> output:"add\name",'hello'print(str4) --> output:string have a [[]].
table (数组、字典)
Table 类型实现了一种抽象的“关联数组”。“关联数组”是一种具有特殊索引方式的数组,索引通常是字符串(string)或者 number 类型,但也可以是除 nil 以外的任意类型的值。
local corp = {
web = "www.google.com", -- 索引为字符串,key = "web",
-- value = "www.google.com"
telephone = "12345678", -- 索引为字符串
staff = {"Jack", "Scott", "Gary"}, -- 索引为字符串,值也是一个表 100876, -- 相当于 [1] = 100876,此时索引为数字
-- key = 1, value = 100876
100191, -- 相当于 [2] = 100191,此时索引为数字
[10] = 360, -- 直接把数字索引给出
["city"] = "Beijing" -- 索引为字符串
}print(corp.web) --> output:www.google.comprint(corp["telephone"]) --> output:12345678print(corp[2]) --> output:100191print(corp["city"]) --> output:"Beijing"print(corp.staff[1]) --> output:Jackprint(corp[10]) --> output:360
在内部实现上,table 通常实现为一个哈希表、一个数组、或者两者的混合。具体的实现为何种形式,动态依赖于具体的 table 的键分布特点。
function
在 lua 中,函数也是一种数据类型,函数可以存储在变量中,可以通过参数传递给其他函数,还可以作为其他函数的返回值。
local function foo()
print("in the function")
-- dosomething()
local x = 10
local y = 20
return x + y
end local a = foo -- 把函数赋给变量 print(a()) -- output:in the function30
lua 表达式
| 算术运算符 | 说明 | 关系运算符 | 说明 | 逻辑运算符 | 说明 |
|---|---|---|---|---|---|
| + | 加法 | < | 小于 | and | 逻辑与 |
| - | 减法 | > | 大于 | or | 逻辑或 |
| * | 乘法 | <= | 小于等于 | not | 逻辑非 |
| / | 除法 | >= | 大于等于 | - | - |
| ^ | 指数 | ~= | 不等于 | - | - |
| % | 取模 | - | - | - | - |
note: lua 中的不等于用 ~= 表示, 和其他语言的 != 不一致
lua 流程控制
lua 的流程控制结构和 python 类似,有几个特例:
lua 中的 elseif 需要连写,中间不能有空行;python 中写法是 elif
lua 中没有 continue 流控
if/else/elseif
if a = 1 then print("1")elseif a == 2 then print("2")else
print("3")end
while
while a > 1 do if a == 5 then
break
end
a = a + 1end
repeat
local i = 0repeat print(i)
if i == 5 then break
end
until true
for/break
local t = { a = 1, b = 2}for k, v in pairs(t) do -- 遍历字典 print(k, v)end
local t = {1, 2}for k, v in ipairs(t) do -- 遍历整型数组 print(k, v)endfor i = 1, 10 do -- range 循环
print(i)
end
return
local function foo(arg) if arg == "" then
return nil
end return "bar"end
OpenResty 模块编写
编写一个 access.lua 模块,源码如下:
local _M = {}
_M.check = function() if ngx.var.http_host == "foo.bar.com" then
ngx.exit(403) endendreturn _M -- 注意 return _M,返回 table 表示的模块
在 access_by_lua 的 nginx hook 中调用 access 模块:
access_by_lua_block { local rule = require "access" -- require 中不需要加 `.lua` 后缀
rule.check()
}
OpenResty 核心原理
nginx 进程模型
nginx 是一个 master + 多个 worker 进程模型;master 进程负责管理和监控 worker 进程,如加载和解析配置文件,重启 worker 进程,更新二进制文件等。 worker 进程负责处理请求,每个 worker 地位和功能相同,内部按照 epoll + callback 方式实现并发连接处理;整体架构图如下: ![]()
nginx 请求处理流程
每个 worker 进程都分阶段处理 http 请求,简单概括为初始化请求 -> 处理请求行 -> 后端交互 -> 响应头处理 -> 响应包体处理 -> 打印日志等几个阶段。其中处理响应体阶段又可以挂载多个不同的 filter。具体的请求阶段可以参见 nginx Phase, nginx 请求处理流程如下图: ![]()
nginx 事件机制
nginx 的事件驱动机制是对 epoll 驱动的封装,但其本质还是 epoll + callback 方式: ![]()
lua 协程
| 函数 | 描述 |
|---|---|
| coroutine.create() | 创建 coroutine,返回 coroutine,参数是一个函数,当和 resume 配合使用的时候就唤醒函数调用 |
| coroutine.resume() | 重启 coroutine,和 create 配合使用 |
| coroutine.yield() | 挂起 coroutine,将 coroutine 设置为挂起状态,这个和 resume 配合使用能有很多有用的效果 |
| coroutine.status() | 查看 coroutine 的状态。注:coroutine 的状态有四种:dead,suspend,running,normal |
coroutine.create(f)
创建一个主体函数为 f 的新协程。f 必须是一个 lua 的函数。返回这个新协程,它是一个类型为 "thread" 的对象,创建后并不会启动该协程。
coroutine.resume(co, [, val1, ...])
开始或继续协程 co 的运行。当第一次执行一个协程时,他会从主函数处开始运行。val1, ... 这些值会以参数形式传入主体函数。如果该协程被挂起,resume 会重新启动它;val1, ... 这些参数会作为挂起点的返回值。如果协程运行起来没有错误,resume 返回 true 加上传给 yield 的所有值 (当协程挂起),或是主体函数的所有返回值(当协程中止)。
coroutine.yield(...)
挂起正在调用的协程的执行。 传递给 yield 的参数都会转为 resume 的额外返回值。
coroutine.status(co)
以字符串形式返回协程 co 的状态:
当协程正在运行(它就是调用 status 的那个) ,返回 "running";
如果协程调用 yield 挂起或是还没有开始运行,返回 "suspended";
如果协程是活动的,都并不在运行(即它正在延续其它协程),返回 "normal";
如果协程运行完主体函数或因错误停止,返回 "dead"。
协程实例(生产者消费者)
使用协程实现生产者消费者:
local function produce()
while true do
local x = io.read()
coroutine.yield(x) -- 挂起协程
endendlocal producer = coroutine.create(produce) -- 创建协程local function receive() local status, value = coroutine.resume(producer) -- 执行协程
return valueendlocal function consumer()
while true do
local x = receive()
io.write(x, "\n") endendconsumer() -- loop
lua 与 c 堆栈交互
lua 虚拟机常嵌入 C 程序中运行,对于 C 程序来说,lua 虚拟机就是一个子进程。lua 将所有状态都保存在 lua_State 类型中,所有的 C API 都要求传入一个指向该结构的指针。我们根据这个指针来获取 lua 虚拟机(也就是子进程)的状态。
虚拟机内部与外部的 C 程序发生数据交换主要是通过一个公用栈实现的,也就是说 lua 虚拟机和 C 程序公用一个栈,双方都可以压栈或读取数据。一方压入,另一方弹出就能实现数据的交换。
在 c 中,lua 堆栈就是一个 struct,堆栈索引方式可能是正数也可能是负数,区别是:正数索引 1 永远表示栈底,负数索引 -1 永远表示栈顶。 堆栈的默认大小是 20,可以用 lua_checkstack 修改,用 lua_gettop 则可以获得栈里的元素数目。
C 调用 lua
在 C 中创建 lua 虚拟机
lua_State *luaL_newstate (void)
加载 lua 的库函数
void luaL_openlibs (lua_State *L);
加载 lua 文件,使用接口
int luaL_dofile (lua_State *L, const char *filename);
开始交互,lua 定义一个函数
function test_func_add(a, b) return a + b end
如果你的 lua_State 是全局变量,那么每次对堆栈有新操作时务必使用lua_settop(lua_State, -1)将偏移重新置到栈顶
去lua文件中取得test_func_add方法
void lua_getglobal (lua_State *L, const char *name);
参数压栈
lua_pushnumber
通过 pcall 调用
int lua_pcall (lua_State *L, int nargs, int nresults, int msg);
完整示例,先编写一个 foo.lua 文件,在文件中实现 test_func_add 方法
function test_func_add(a, b)
return a + b
end
接下来在 C 代码中调用 foo.lua:
lua_State* init_lua()
{
lua_State* s_lua = luaL_newstate(); if (!s_lua) {
printf("luaL_newstate failed!\n");
exit(-1);
}
luaL_openlibs(s_lua); return s_lua;
}bool load_lua_file(lua_State* s_lua, const char* lua_file){ if (luaL_dofile(s_lua, lua_file) != 0) {
printf("LOAD LUA %s %s\n", lua_file, BOOT_FAIL); return false;
}
printf("LOAD LUA %s %s\n", lua_file, BOOT_OK); return true;
}int proc_add_operation(lua_State* s_lua, int a, int b){
lua_settop(s_lua, -1);
lua_getglobal(s_lua, "test_func_add");
lua_pushnumber(s_lua, a);
lua_pushnumber(s_lua, b); int val = lua_pcall(s_lua, 2, 1, 0); if (val) {
printf("lua_pcall_error %d\n", val);
} return (int)lua_tonumber(s_lua, -1);
}int main() {
lua_State* s_lua =init_lua(); if (!load_lua_file(s_lua, "foo")) { return -1;
} proc_add_operation(s_lua, 1, 2);
}
lua 调用 c
定义谁先实现 C 接口
#define target 300static int l_test_check_value(lua_State * l){ int num = lua_tointeger(l, -1); bool check = (num == target);
lua_pushboolean(l, check); return 1;
}lua 虚拟启动时候,注册加载 C 接口
lua_register(s_lua, "test_check_value", l_test_check_value);
在 lua 代码中调用注册的 C 接口
function test_func_check(a)
local val = test_check_value(a) return val
end
更多网易技术、产品、运营经验分享请点击。
相关文章:
【推荐】 负载均衡技术(二)———常用负载均衡服务介绍
OpenResty 最佳实践 (1)的更多相关文章
- OpenResty 最佳实践 lua与nginx的结合 --引用自https://moonbingbing.gitbooks.io/openresty-best-practices/content/
系统的说明了lua在nginx上的开发 请大家到源址查看 OpenResty最佳实践
- OpenResty 最佳实践 1
建议先搜索<OpenResty最佳实践.pdf> 到网上下载openresty-1.13.6.1-win32 考虑到操作方便性,建议建立个bin目录,放入系统目录中,生成 nginx-st ...
- OpenResty 最佳实践 (2)
此文已由作者汤晓静授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. lua 协程与 nginx 事件机制结合 文章前部分用大量篇幅阐述了 lua 和 nginx 的相关知识,包 ...
- OpenResty 最佳实践
OpenResty 最佳实践 https://moonbingbing.gitbooks.io/openresty-best-practices/content/index.html
- 《OpenResty 最佳实践》学习开篇
前言:对openresty学习中,收集了一些相关知识的参考网站,有兴趣的可以看看.另附网盘分享. lua菜鸟教程 openresty最佳实战 lua在线解析工具 Nginx Lua API Nginx ...
- 转:OpenResty最佳实践(推荐了解lua语法)
看点: 1. Lua 语法的说明, 排版清晰易懂. 转: https://moonbingbing.gitbooks.io/openresty-best-practices/content/lua/m ...
- 转: OpenResty最佳实践
https://moonbingbing.gitbooks.io/openresty-best-practices/content/ centOS安装另加内容 ln -sf luajit-2.1.0- ...
- OpenResty最佳实践
https://moonbingbing.gitbooks.io/openresty-best-practices/content/
- Openresty最佳案例 | 汇总
转载请标明出处: http://blog.csdn.net/forezp/article/details/78616856 本文出自方志朋的博客 目录 Openresty最佳案例 | 第1篇:Ngin ...
随机推荐
- 33_java之类加载器和反射
01类加载器 * A.类的加载 当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载,连接,初始化三步来实现对这个类进行初始化. * a 加载 * 就是指将class文件读入内存,并为之 ...
- [译]作为一个web开发人员,哪些技术细节是在发布站点前你需要考虑到的
前日在cnblogs上看到一遍文章<每个程序员都必读的12篇文章>,其中大多数是E文的. 先译其中一篇web相关的”每个程序员必知之WEB开发”. 原文: http://programme ...
- SQL中INNER JOIN的用法
SQL join 用于根据两个或多个表中的列之间的关系,从这些表中查询数据. Join 和 Key 有时为了得到完整的结果,我们需要从两个或更多的表中获取结果.我们就需要执行 join. 数据库中的表 ...
- vs2015 新特性
vs2015 新特性 自动属性的增强 http://www.kwstu.com/ArticleView/manong_201411200854239378
- Zabbix 监控 Cisco ASA5525 流量
简介: Zabbix 监控 Cisco ASA5525 网络接口流量 一.Zabbix 支持 SNMP.Cisco 开启 SNMP 二.测试 shell > snmpwalk -v 2c -c ...
- js中元素结点的引用
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8" ...
- jsp脚本语法
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"% ...
- LMAX系统架构
本文转载自:LMAX系统架构 ,(非常感谢作者yfx416分享好文) 很多架构师都面临这么一个问题:如何设计一个高吞吐量,低延时的系统?面对这个问题,各位都有自己的答案.但面对这个问题,大家似乎渐渐形 ...
- LevelDb日知录之五:MemTable详解
[LevelDb日知录之五:MemTable详解] LevelDb日知录前述小节大致讲述了磁盘文件相关的重要静态结构,本小节讲述内存中的数据结构Memtable,Memtable在整个体系中的重要地位 ...
- Python_08-常用模块
1 常用模块介绍 1.1 os模块 1.2 sys模块 1.3 built-in内置模块 1.4 time模块 1.5 re模块 2 ...