高性能web网关之openresty
- 一、openresty 简介
- 二、openresty 安装
- 三、openresty开发实践 —— content_by_lua 阶段
- 四、openresty开发实践 —— rewrite_by_lua 阶段
- 五、openresty开发实践 —— body_filter_by_lua 阶段
- 六、openresty开发实践 —— 黑名单
- 6.1、基础版
- 6.2、进阶版
- 6.3、高阶版
- 七、openresty开发实践 —— 反向代理
- 总结
- 后言
一、openresty 简介
-
openresty 是一个基于 nginx 与 lua 的高性能 web 平台,其内部集成了大量精良的 lua 库、第三方模块以及大数的依赖项。用于方便搭建能够处理超高并发、扩展性极高的动态 web 应用、web 服务和动态网关。
-
openresty 通过汇聚各种设计精良的 nginx 模块,从而将 nginx有效地变成一个强大的通用 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 安装
(1)下载源压缩包:
wget https://openresty.org/download/openresty-1.21.4.1.tar.gz
(2)安装依赖:
sudo apt-get install libpcre3-dev libssl-dev perl make build-essential curl
(3)解压源码:
tar -xzvf openresty-1.21.4.1.tar.gz
(4)配置:默认, --prefix=/usr/local/openresty 程序会被安装到/usr/local/openresty目录。
cd openresty-1.21.4.1
./configure
(5)编译和安装:
make -j2
sudo make install
(6)设置环境:
cd ~
export PATH=/usr/local/openresty/bin:$PATH
(7)测试:
~$ openresty -v
nginx version: openresty/1.21.4.1
启动、关闭、重启 openresty:
# 指定配置启动 openresty
# 需要指定工作目录,示例中的 . 表示当前目录为工作目录。
openresty -p . -c conf/nginx.conf
# 优雅退出
openresty -p . -s quit
# 重启 openresty
openresty -p . -s reload
三、openresty开发实践 —— content_by_lua 阶段
(1)新建一个项目文件夹,项目文件夹新建三个子文件夹,分别是app、conf、logs,分别用来存放编写的应用程序、配置文件、日志文件。
mkdir my_openresty
cd my_openresty
mkdir app
mkdir conf
mkdir logs
(2)在conf下创建nginx.conf文件,输入以下内容:
worker_processes 2;
events {
worker_connections 10240;
}
#######################
# 以下为配置块
#######################
# http 协议
http {
#虚拟主机
server {
listen 8989;
# 处理http请求
# 捕获和处理
location / {
# 内容处理,在配置中写代码
content_by_lua_block {
ngx.say("hello","\t",ngx.var.remote_addr)
}
}
}
}
#
# tcp 使用stream
#
# stream{
#
#}
(3)openresty启动nginx:
openresty -p . -c conf/nginx.conf
(4)查看nginx启动状态:
ps aux | grep nginx
执行结果:
fly 15341 0.0 0.0 33264 1272 ? Ss 17:23 0:00 nginx: master process openresty -p . -c conf/nginx.conf
fly 15342 0.0 0.3 37516 7276 ? S 17:23 0:00 nginx: worker process
fly 15343 0.0 0.3 37516 7276 ? S 17:23 0:00 nginx: worker process
fly 15345 0.0 0.0 15984 968 pts/2 S+ 17:23 0:00 grep --color=auto nginx
(5)在浏览器输入服务器IP和端口,可以看到如下的结果:
四、openresty开发实践 —— rewrite_by_lua 阶段
nginx.conf文件,输入以下内容:
worker_processes 2;
events {
worker_connections 10240;
}
#######################
# 以下为nginx配置块
#######################
# http 协议
http {
#虚拟主机
server {
listen 8989;
# 处理http请求
# 捕获和处理
location / {
# 初始化数据,可以在此阶段加载耗时模块、设置全局变量
# init_by_lua_block {
# gloabl_a=100
# }
# 用于执行内部url重写或外部重定向
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)
}
}
# rewrite_by_lua不止能跳转到外部,也可以内部跳转
location /jump_here {
# 内容处理,在配置中写代码
content_by_lua_block {
ngx.say("hello, jump_here","\t",ngx.var.remote_addr)
}
}
}
}
#
# tcp 使用stream
#
# stream{
#
#}
没有启动openresty,则输入如下命令启动:
openresty -p . -c conf/nginx.conf
如果是之前已经启动了,只需要reload即可:
openresty -p . -s reload
五、openresty开发实践 —— body_filter_by_lua 阶段
nginx.conf文件,输入以下内容:
worker_processes 2;
events {
worker_connections 10240;
}
#######################
# 以下为nginx配置块
#######################
# http 协议
http {
#虚拟主机
server {
listen 8989;
# 处理http请求
# 捕获和处理
location / {
# 用于执行内部url重写或外部重定向
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)
}
}
# rewrite_by_lua不止能跳转到外部,也可以内部跳转
location /jump_here {
# 内容处理,在配置中写代码
content_by_lua_block {
ngx.say("hello, jump_here","\t",ngx.var.remote_addr)
}
# 用于修改应答body的内容
body_filter_by_lua_block {
local chunk=ngx.arg[1]
ngx.arg[1]=chunk:gsub("hello","FLY.")
}
}
}
}
#
# tcp 使用stream
#
# stream{
#
#}
执行效果:
六、openresty开发实践 —— 黑名单
黑名单功能一般在access_by_lua阶段实现,实现访问控制。
6.1、基础版
新建nginx_new.conf文件,输入以下内容:
worker_processes 2;
events {
worker_connections 10240;
}
#######################
# 以下为nginx配置块
#######################
# http 协议
http {
#虚拟主机
server {
listen 8989;
location / {
access_by_lua_block {
local block_list={
["192.168.0.105"]=true
}
if block_list[ngx.var.remote_addr] then
return ngx.exit(403)
end
}
# 内容处理,在配置中写代码
content_by_lua_block {
ngx.say("hello","\t",ngx.var.remote_addr)
}
}
}
}
没有启动openresty,则输入如下命令启动:
openresty -p . -c conf/nginx_new.conf
如果是之前已经启动了,只需要reload即可:
openresty -p . -s reload
执行效果:
注意,示例中的IP是写死在代码中的,在实际使用中不会这样来,一般存储在其他地方,比如redis。
6.2、进阶版
修改nginx_new.conf文件内容:
worker_processes 2;
events {
worker_connections 10240;
}
#######################
# 以下为nginx配置块
#######################
# http 协议
http {
#虚拟主机
server {
listen 8989;
location / {
# 用于访问控制
access_by_lua_block {
local block_list={
["192.168.0.105"]=true
}
if block_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)
}
}
}
}
black_v1.lua文件内容为:
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
只需要reload即可:
openresty -p . -s reload
注意,要记得先运行redis,同时添加IP地址到KEY中。
127.0.0.1:6379> SADD black_list 192.168.0.105
(integer) 1
127.0.0.1:6379> keys *
1) "black_list"
127.0.0.1:6379> SMEMBERS black_list
1) "192.168.0.105"
127.0.0.1:6379>
执行效果:
如果不知道有哪些接口可以使用,可以通过如下命令查询:
restydoc resty.redis
虽然把IP存储在了redis中,没有写死在代码里,但是每一次请求都要访问redis,这会导致整个系统的吞吐量降低;可以将这些数据写到共享内存中。
6.3、高阶版
redis+共享内存方式。而且为了保证数据有效性,需要定期将redis中的数据拉取到共享内存中;那么就需要在init_worker_by_lua阶段添加定时器。
修改nginx_new.conf文件内容:
worker_processes 2;
events {
worker_connections 10240;
}
#######################
# 以下为nginx配置块
#######################
# http 协议
http {
# 创建共享内存
lua_shared_dict bklist 1m;
# 初始化数据,定时器
init_worker_by_lua_file ./app/init_worker.lua;
#虚拟主机
server {
listen 8989;
location / {
# 用于访问控制
access_by_lua_block {
local block_list={
["192.168.0.105"]=true
}
if block_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_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文件内容为:
-- 只需要一个进程拉取数据即可。
if ngx.worker.id() ~=0 then
return
end
-- 获取共享内存
local bklist =ngx.shared.bklist
local redis=require "resty.redis"
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)
只需要reload即可:
openresty -p . -s reload
注意,要记得先运行redis,同时添加IP地址到KEY中。
127.0.0.1:6379> SADD black_list 192.168.0.105
(integer) 1
127.0.0.1:6379> keys *
1) "black_list"
127.0.0.1:6379> SMEMBERS black_list
1) "192.168.0.105"
127.0.0.1:6379>
执行效果:
七、openresty开发实践 —— 反向代理
nginx.conf文件内容:
worker_processes 4;
events {
worker_connections 10240;
}
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;
}
}
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
没有启动openresty,则输入如下命令启动:
openresty -p . -c conf/nginx.conf
如果是之前已经启动了,只需要reload即可:
openresty -p . -s reload
总结
-
nginx基础上嵌入lua产生openresty,在openresty基础上封装一个框架产生kong。直接在nginx上开发是比较复杂的,需要熟悉nginx的配置、nginx数据结构、nginx模块构建等,很难进行业务开发;nginx基础上嵌入lua产生的openresty可以进行业务的开发;openresty基础上封装一个框架并添加一些分布式特性就产生kong,即分布式api网关。
-
openresty中引用了luajit,性能上接近c语言。
-
nginx是多进程架构,master进程会fork出n个(CPU核心数)worker进程;worker进程间通过共享内存通信;worker进程负责处理请求,通过锁解决惊群问题和控制新的连接接入(负载均衡);请求沿着责任链进行处理。
-
openresty中的lua是嵌入在责任链的最后一层,即openresty是对nginx功能的补充,不会影响原来在nginx上实现的功能。
-
责任链的打断:ngx.exit()或者ngx.redirect()可以打断责任链。restydoc -s func() 可以查看命令查看接口描述。
-
nginx中直接反向代理可能造成后端服务器(upstream)无法获取到客户端的IP(客户端IP丢失),这是比较严重的问题,我们需要再转发的时候也要把IP地址转发到后端服务器(使用 proxy protocol v1 或 proxy protocol v2)。
stream {
upstream ups {
server 127.0.0.1:8888;
}
server {
listen 9999;
proxy_pass ups;
proxy_protocol on;
}
}
后言
本专栏知识点是通过<零声教育>的系统学习,进行梳理总结写下文章,对c/c++linux系统提升感兴趣的读者,可以点击链接查看详细的服务:C/C++服务器开发 。