openresty 简介
openresty 是一个基于 nginx 与 lua 的高性能 web 平台,其内部 集成了大量精良的 lua 库、第三方模块以及大数的依赖项。用于 方便搭建能够处理超高并发、扩展性极高的动态 web 应用、 web 服务和动态网关。
openresty 通过汇聚各种设计精良的 nginx 模块,从而将 nginx 有效地变成一个强大的通用 Web 应用平台。这样,Web 开发人 员和系统工程师可以使用 Lua 脚本语言调动 Nginx 支持的各种 C 以及 Lua 模块,快速构造出足以胜任 10K 乃至 1000K 以上单 机并发连接的高性能 Web 应用系统。
openresty 的目标是让你的 Web 服务直接跑在 Nginx 服务内部, 充分利用 Nginx 的非阻塞 I/O 模型(多reactor 模型),不仅仅 对 HTTP 客户端请求(stream),甚至于对远程后端诸如 MySQL、PostgreSQL、Memcached 以及 Redis etcd kafka grpc 等都进行一致的高性能响应(upstream)。
openresty 安装
官网: http://openresty.org/cn/
下载页面:http://openresty.org/cn/download.html
# 安装依赖
apt-get install libpcre3-dev \
libssl-dev perl make build-essential curl
# 解压源码
tar -xzvf openresty-VERSION.tar.gz
# 配置:默认, --prefix=/usr/local/openresty 程序会被
安装到/usr/local/openresty目录。
./configure
make -j2
sudo make install
cd ~
export PATH=/usr/local/openresty/bin:$PATH
启动、关闭、重启 openresty
# 指定配置启动 openresty
openresty -p . -c conf/nginx.conf
# 优雅退出
openresty -p . -s quit
# 重启 openresty
openresty -p . -s reload
openresty 应用场景
奇虎360的所有服务端团队都在使用,京东、百度、魅族、知 乎、优酷、新浪这些互联网公司都在使用。有用来写 WAF (web application firewall)、有做 CDN 调度、有做广告系统、消息推送系统,API server 的。还有用在非常关键的业务上,比如高可用架构分享的京东商品详情页。
1 在请求真正到达上游服务之前,Lua 可以随心所欲的做复杂的访问控制和安全检测
2 随心所欲的操控响应头里面的信息
3 从外部存储服务(比如 Redis,Memcached,MySQL, Postgres)中获取后端信息,并用这些信息来实时选择哪一个后端来完成业务访问
4 在内容 handler 中随意编写复杂的 Web 应用,使用同步但依然非阻塞的方式,访问后端数据库和其他存储
5 在 rewrite 阶段,通过 Lua 完成非常复杂的 URL dispatch
6 用 Lua 可以为 nginx 子请求和任意 location,实现高级缓存机制
lua-nginx-module
nginx 采用模块化设计,使得每一个 http 模块可以仅专注于完 成一个独立的、简单的功能,而一个请求的完整处理过程可以 由无数个 http 模块共同合作完成。为了灵活有效地指定下一个 http 处理模块是哪一个;http 框架依据常见的的处理流程将处 理阶段划分为 11 个阶段,其中每一个阶段都可以由任意多个 http 模块流水式地处理请求。
openresty 将 lua 脚本嵌入到 nginx 阶段处理的末尾模块下;这样以来并不会影响 nginx 原有的功能,而是在 nginx 基础上丰富它的功能;
嵌入 lua 的优点是:使用 openresty 开发,不需要重新编译, 直接修改 lua 脚本,重新启动即可;
lua 模块指令顺序
问题:访问某个页面,先验证是否用户权限是否合法,否则跳到用户验证界面;
问题:黑白名单在哪个阶段实现?
init_by_lua
在 nginx 重新加载配置文件时,运行里面 lua 脚本,常用于 全局变量的申请。例如 lua_shared_dict 共享内存的申请,只 有当 nginx 重启后,共享内存数据才清空,这常用于统计。
set_by_lua
设置一个变量,常用与计算一个逻辑,然后返回结果,该阶 段不能运行Output API、Control API、Subrequest API、 Cosocket API
rewrite_by_lua
在 access 阶段前运行,主要用于 rewrite url;
access_by_lua
主要用于访问控制,这条指令运行于 nginx access 阶段的末 尾,因此总是在 allow 和 deny 这样的指令之后运行,它们 同属 access 阶段。可用来判断请求是否具备访问权限;
content_by_lua
阶段是所有请求处理阶段中最为重要的一个,运行在这个阶 段的配置指令一般都肩负着生成内容(content)并输出 HTTP 响应。
header_filter_by_lua
一般只用于设置 Cookie 和 Headers 等。
body_filter_by_lua
一般会在一次请求中被调用多次,因为这是实现基于 HTTP 1.1 chunked 编码的所谓“流式输出”的。
log_by_lua
该阶段总是运行在请求结束的时候,用于请求的后续操作, 如在共享内存中进行统计数据,如果要高精确的数据统计, 应该使用 body_filter_by_lua
嵌入原理
openresty 是在nginx处理的阶段末尾加上我们的方法,补充功能,原来实现的功能不受影响。
责任链模式
ngx.exit(status)
如果status == 0 只打断当前责任链,如果status>=200 则打断整个责任链,直接退出。
ngx.redirect()
cosocket
openresty 为 nginx 添加的最核心的功能就是 cosocket;自 cosocket 加入,可以在 http 请求处理中访问第三方服务; cosocket 主要依据 nginx 中的事件机制和 lua 的协程结合后实现了非阻塞网络 io;在业务逻辑使用层面上可以通过同步非阻塞的方式来写代码;
引入 cosocket 后,nginx 中相当于有了多条并行同步逻辑线 (lua 协程),nginx 中单线程负责唤醒或让出其中 lua 协程; 唤醒或让出依据来源于协程运行的条件是否得到满足;
问题:比较 openresty 、skynet、zvnet 的 lua 虚拟机抽象和 lua 协程抽象?
黑名单用户 nginx.conf
worker_processes 8;
events {
worker_connections 10240;
}
http {
lua_shared_dict bklist 1m;
init_worker_by_lua_file ./app/init_worker.lua;
server {
listen 9000;
location / {
access_by_lua_block {
local black_list = {
["192.168.44.1"] = true
}
if black_list[ngx.var.remote_addr] then
return ngx.exit(403)
end
}
content_by_lua_block {
ngx.say("hello" , "\t" , ngx.var.remote_addr)
}
}
location /black_v1 {
access_by_lua_file ./app/black_v1.lua;
content_by_lua_block {
ngx.say("hello" , "\t" , ngx.var.remote_addr)
}
}
location /black_v2 {
access_by_lua_file ./app/black_v2.lua;
content_by_lua_block {
ngx.say("hello" , "\t" , ngx.var.remote_addr)
}
}
}
}
black_v1.lua (将黑名单ip放入redis)
local redis = require "resty.redis"
local red = redis:new()
local ok , err = red:connect("127.0.0.1" , 6379)
if not ok then
return ngx.exit(301)
end
local ip = ngx.var.remote_addr
local exists , err = red:sismember("black_list" , ip)
if exists == 1 then
return ngx.exit(403)
end
black_v2.lua
local bklist = ngx.shared.bklist
local ip = ngx.var.remote_addr
if bklist:get(ip) then
return ngx.exit(403)
end
init_worker.lua 一个worker定时往共享内存中刷数据,(黑名单ip)
if ngx.worker.id() ~= 0 then
return
end
local redis = require "resty.redis"
local bklist = ngx.shared.bklist
local function update_blacklist()
local red = redis:new()
local ok , err = red:connect("127.0.0.1" , 6379)
if not ok then
return
end
local black_list,err = red:smembers("black_list")
bklist:flush_all()
for _,v in pairs(black_list) do
bklist:set(v , true)
end
ngx.timer.at(5 , update_blacklist)
end
ngx.timer.at(5 , update_blacklist)
反向代理
worker_processes 8;
events {
worker_connections 10240;
}
# http
http {
server {
listen 8989;
location / {
rewrite_by_lua_block {
local args = ngx.req.get_uri_args()
if args["jump"] == "1" then
return ngx.redirect("http://baidu.com")
elseif args["jump"] == "2" then
return ngx.redirect("/jump_here")
end
}
content_by_lua_block {
ngx.say("hello", "\t", ngx.var.remote_addr)
}
}
location /jump_here {
content_by_lua_block {
ngx.say("jump_here hello", "\t", ngx.var.remote_addr)
}
body_filter_by_lua_block {
local chunk = ngx.arg[1]
ngx.arg[1] = chunk:gsub("hello", "mark")
}
}
}
}
stream {
upstream ups {
server 127.0.0.1:8888;
}
server {
listen 9999;
proxy_pass ups;
proxy_protocol on;
}
server {
listen 9000;
content_by_lua_file ./app/proxy.lua;
}
}
cosocket proxy.lua
local sock, err = ngx.req.socket()
if err then
ngx.log(ngx.INFO, err)
end
local upsock, ok
upsock = ngx.socket.tcp()
ok, err = upsock:connect("127.0.0.1", 8989)
if not ok then
ngx.log(ngx.INFO, "connect error:"..err)
end
upsock:send(ngx.var.remote_addr .. '\n')
local function handle_upstream()
local data
for i=1, 1000 do
local reader = upsock:receiveuntil("\n", {inclusive = true})
data, err, _ = reader()
if err then
sock:close()
upsock:close()
return
end
sock:send(data)
end
end
ngx.thread.spawn(handle_upstream)
local data
while true do
local reader = sock:receiveuntil("\n", {inclusive = true})
data, err, _ = reader()
if err then
sock:close()
upsock:close()
return
end
upsock:send(data)
end