OpenResty 深度解析:构建高性能 Web 服务的终极方案

引言

openresty是什么?在我个人对它的理解来看相当于嵌入了lua的nginx;

我们在nginx中嵌入lua是为了不需要再重新编译,我们只需要重新修改lua脚本,随后重启即可;

一.lua指令序列

我们分别从初始化阶段,重写/访问阶段,内容阶段,日志阶段来介绍上述图中的信息

1.初始化阶段

**init_by_lua*:**Nginx 启动时,在 master 进程中仅执行一次,用于全局初始化,比如加载配置文件、初始化共享字典等。

**init_worker_by_lua* :**Nginx 启动时,每个 worker 进程初始化时执行,可用于 worker 进程级别的初始化,像设置特定于 worker 的变量等 。

2.重写/访问阶段

**ssl_certificate_by_lua* :**当请求是安全请求(涉及 SSL/TLS)时执行,可用于动态选择 SSL 证书等操作。

set_by_lua* :用于设置 Nginx 变量,可在请求处理早期计算变量值。

**rewrite_by_lua* :**用于 URL 重写相关操作,可修改请求的 URI 等。

access_by_lua* :用于访问控制,比如认证、鉴权、IP 黑白名单判断等,决定请求是否能继续处理。

3.内容阶段

balancer_by_lua* :在负载均衡阶段执行,可自定义负载均衡算法,选择合适的上游服务器。

content_by_lua* :用于生成响应内容,是核心的内容生成阶段,可查询数据库、组装数据并输出响应。

header_filter_by_lua* :用于过滤和修改响应头,比如添加自定义响应头、修改缓存相关头信息等。

**body_filter_by_lua* :**用于过滤和修改响应体,可对响应内容进行二次处理,如压缩、加密等。

4.日志阶段

log_by_lua* :请求处理完成后,在记录日志时执行,可自定义日志记录逻辑,比如将日志发送到特定存储或进行格式化处理。

5.conf代码案例

我们可以直接在conf文件里面写lua代码块从而实现一些功能

Lua 复制代码
worker_processes 8;

events {
    worker_connections 10240;
}

http {
    error_log ./logs/error.log info;
    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_other")
                end
            }
            content_by_lua_block {
                ngx.say("hello","\t",ngx.var.remote_addr)
            }
        }
        location /jump_other {
            content_by_lua_block{
                ngx.say("jump other","\t",ngx.var.remote_addr)
            }
            body_filter_by_lua_block{
                local chunk =  ngx.arg[1]
                ngx.arg[1]=string.gsub(chunk,"other","lion")
            }
            log_by_lua_block{
                local request_method = ngx.var.request_method
                local request_uri = ngx.var.request_uri

                local status = ngx.var.status
                local response_time = ngx.var.request_time

                local msg  = string.fomat("[%s] %s -Status:%d,response time = %.2fms",
                os.data("%Y-%m-%d %H:%M:%S"),request_uri,status,response_time)
                ngx.log(ngx.INFO,msg)
            }
        }
    }
}

上述conf代码块起了8个工作线程 每个工作线程的最大连接数为10240
我们sever的监听端口为8989,当我们页面路由到location /时候会调用lua嵌入的**rewrite_by_lua_block(重写url)**接口;
当jump=1时会直接重定向到百度

当jump=2时会重定向到location /jumpother
当我们跳转到location /jump_other时 我们会调用content_by_lua_block(生成内容输出http响应) 接口 后面执行到body_filter_by_lua_block(对响应内容进行二次处理) 接口
后面执行到日志输出接口 log_by_lua_block(打印对应的日志信息)
++执行结果展示++

默认输出 不输入jump

输入jump=1 重定向到百度

输入jump=2 重定向到jump_other

二.openresty 中 嵌入原理和 责任链模式
1.OpenResty 嵌入原理

OpenResty 本质是将 LuaJIT 虚拟机嵌入到 Nginx 的管理进程(master 进程)和工作进程(worker 进程)中 。具体表现为:

进程内虚拟机共享:每个 worker 进程使用一个 LuaVM ,同一个进程内的所有协程共享该虚拟机。当请求分配到 worker 进程时,会在 LuaVM 中创建一个 coroutine 协程来处理请求。比如,在处理 HTTP 请求时,不同请求的协程都在所属 worker 进程的 LuaVM 里运行 Lua 代码。

与 Nginx 事件模型结合:ngx_lua 模块使 Lua 内建协程能和 Nginx 的事件驱动模型深度协作。Lua 代码中的 IO 操作委托给 Nginx 事件模型,实现非阻塞调用。像网络请求等 IO 操作时,Lua 协程挂起,Nginx 事件处理机制接管,操作完成后恢复协程上下文继续执行,对开发者透明。

模块与 API 注入 :Nginx 的 I/O 原语等功能经封装后注入 Lua 虚拟机,让 Lua 代码能直接访问。例如,通过ngx.reqngx.resp等 API 可在 Lua 代码里方便操作 Nginx 的请求和响应相关功能 。

2.OpenResty 责任链模式

在 OpenResty 中,责任链模式是一种用于请求处理的设计模式 :

特点:解耦合和中断

节点构建:把请求处理逻辑拆分成一个个独立节点,每个节点完成单一功能,如鉴权、限流、日志记录等。例如在 API 网关场景,鉴权节点验证用户身份合法性,限流节点控制请求频率。

链式调用:节点按特定顺序组成责任链,请求到达后依次流经各节点。前一节点处理完决定是否将请求传递给下一节点。比如鉴权通过才将请求传给限流节点,否则直接返回错误响应。

动态调整:可根据业务需求灵活添加、删除或调整节点顺序。像某些高安全要求业务,在责任链中增加更严格加密和多重认证节点;普通业务则简化节点链。

三.cosocket

cosocket指的是协程和socket的结合实现特定的功能

四.openresty总结
1.我们使用openresty的三板斧

①背靠nginx,嵌入到各个阶段的lua函数

②cosocket可同步非阻塞在多个阶段访问第三方库服务,在nginx上实现业务成为可能

③ngx,shared.dict共享内存可在多个worker进程共享数据,数据实时生效

2.conf和lua代码案例

conf文件

Lua 复制代码
worker_processes 8;

events {
    worker_connections 10240;
}

http {
    error_log ./logs/black.log info;
    lua_shared_dict blks 1m;
    init_worker_by_lua_file ./app/init_worker.lua;
    server {
        listen 8989;
        location / {
            access_by_lua_block {
                local black_list= {
                    ["192.168.217.2"]=true
                }
                if black_list[ngx.var.remote_addr] then
                return ngx.exit(404)
                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("black_v1","\t",ngx.var.remote_addr)
            }
        }
        location /black_v2 {
            access_by_lua_file ./app/black_v2.lua;
            content_by_lua_block {
                ngx.say("black_v2","\t",ngx.var.remote_addr)
            }
        }

    }
}

我们在worker线程初始化的时候通过lua_shared_dict接口实现一块1m的共享内存 后我们调用init_worker_by_lua_file接口 进入到 init_worker.lua 文件中

我们通过conf代码块中的

location /black_v2 {

access_by_lua_file ./app/black_v2.lua;

content_by_lua_block {

ngx.say("black_v2","\t",ngx.var.remote_addr)

}

}

进行详解

init_worker.lua文件

Lua 复制代码
--只在第一个worker进程里面初始化
if ngx.worker.id() ~= 0 then
    return
end

local redis = require("resty.redis")
local bklist = ngx.shared.blks

local function updata_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 _, value in pairs(black_list) do
        bklist:set(value, true)
    end
    ngx.timer.at(5, updata_blacklist)
end

ngx.timer.at(5, updata_blacklist) --每5s把redis黑名单里面的内容更新到共享内存里

我们在上述lua代码中实现了 对redis的连接以及实时把redis里面黑名单的内容更新到共享内存块中

black_v2.lua 文件

Lua 复制代码
local bklist = ngx.shared.blks

local ip = ngx.var.remote_addr

if bklist:get(ip) then
    return ngx.exit(404)
end

当我们路由到 location /black_v2时候 通过access_by_lua_file 接口 会执行black_v2.lua文件

从而拿到共享内存中的黑名单ip地址从而进行判断

实现黑白名单结果演示

启动openresty

启动redis

我们在balck_list中添加两个ip地址

http请求访问

把本地ip地址从 black_list中删除

五.kong在openresty进一层的封装
1.概念和接口

kong和konga-->web服务在开发中经常使用的

我们只介绍其中的反向代理来演示其功能

kong在反向代理的两个接口分别是

proxy_pass;

proxy_protocol on;

2.conf和lua代码案例

conf文件

Lua 复制代码
worker_processes 2;

events {
    worker_connections 1024;
}

# #http
##动态
http {
    error_log ./logs/proxy.log info;
    lua_shared_dict shm 1m;
    lua_shared_dict urls 1m;


    ################
    ###############
    ###########
    ########
    #####
    ##
    #
}


#静态---->问题--->添加删除需要重启生效
http {
    error_log ./logs/proxy.log info;
    upstream ups {
        server 192.168.217.148:7001;
        server 192.168.217.148:7002;
        server 192.168.217.148:7003;
    }
    server {
        listen 9001;
        location / {
            proxy_pass http://ups;
            proxy_protocol on;
        }
    }
}



#tcp
stream {
    upstream tcp_ups {
        server 192.168.217.148:8080;
    }
    server {
        listen 9002;
        proxy_pass tcp_ups;
        proxy_protocol on;
    }
    server {
        listen 9003;
        content_by_lua_file ./app/proxy.lua;
    }
}

proxy.lua

Lua 复制代码
local sock, err = ngx.req.socket()
if not sock then
    return
end

local upsock = ngx.socket.tcp()

local ok, err = upsock:connect("192.168.217.148", 8080)
if not ok then
    return
end

upsock:send(ngx.var.remote_addr .. "\n")
-- local function handler_upstream()

-- end
六.kong总结

Kong 是一款基于 NGINX 和 OpenResty 构建的开源 API 网关,支持 API 管理、流量控制、身份验证、监控等功能,可实现对 API 的全生命周期管理与流量治理。

相关推荐
WGS.3 小时前
Ubuntu 更改 Nginx 版本
nginx
泽济天下6 小时前
【工作记录】Kong Gateway入门篇之简介
gateway·kong
SHUIPING_YANG7 小时前
Nginx 返回 504 状态码表示 网关超时(Gateway Timeout)原因排查
运维·nginx·gateway
〆、风神9 小时前
面试真题 - 高并发场景下Nginx如何优化
java·nginx·面试
技术宝哥17 小时前
Redis(2):Redis + Lua为什么可以实现原子性
数据库·redis·lua
noravinsc20 小时前
国产化中间件 替换 nginx
运维·nginx·中间件
ZHOU_WUYI1 天前
使用 Docker 部署 React + Nginx 应用教程
nginx·react.js·docker
梦在深巷、1 天前
nginx配置之负载均衡
运维·nginx·负载均衡
iRayCheung1 天前
Kind方式部署k8s单节点集群并创建nginx服务对外访问
nginx·kubernetes·kind