高性能web网关Openresty实践

一、openresty 简介

  1. openresty 是一个基于 nginx 与 lua 的高性能 web 平台,其内部集成了大量精良的 lua 库、第三方模块以及大数的依赖项。用于方便搭建能够处理超高并发、扩展性极高的动态 web 应用、web 服务和动态网关。

  2. openresty 通过汇聚各种设计精良的 nginx 模块,从而将 nginx有效地变成一个强大的通用 Web 应用平台。这样,可以使用 Lua 脚本语言调动 Nginx 支持的各种C 以及 Lua 模块,快速构造出足以胜任 10K 乃至 1000K 以上单机并发连接的高性能 Web 应用系统。

  3. 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_test_result

四、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{
#    
#}

执行效果:
body_filter

六、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

执行效果:
forbidden

注意,示例中的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> 

执行效果:
redis_black_list
如果不知道有哪些接口可以使用,可以通过如下命令查询:

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> 

执行效果:
black_list_v2

七、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

总结

  1. nginx基础上嵌入lua产生openresty,在openresty基础上封装一个框架产生kong。直接在nginx上开发是比较复杂的,需要熟悉nginx的配置、nginx数据结构、nginx模块构建等,很难进行业务开发;nginx基础上嵌入lua产生的openresty可以进行业务的开发;openresty基础上封装一个框架并添加一些分布式特性就产生kong,即分布式api网关。

  2. openresty中引用了luajit,性能上接近c语言。

  3. nginx是多进程架构,master进程会fork出n个(CPU核心数)worker进程;worker进程间通过共享内存通信;worker进程负责处理请求,通过锁解决惊群问题和控制新的连接接入(负载均衡);请求沿着责任链进行处理。

  4. openresty中的lua是嵌入在责任链的最后一层,即openresty是对nginx功能的补充,不会影响原来在nginx上实现的功能。

  5. 责任链的打断:ngx.exit()或者ngx.redirect()可以打断责任链。restydoc -s func() 可以查看命令查看接口描述。

  6. 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;
    	}
    }
    
  • 4
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Lion Long

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值