百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

Lua在Nginx的应用(lua+nginx)

nanshan 2024-10-22 12:58 9 浏览 0 评论

首发于公众号 后端搬运工《Lua在Nginx的应用

当 Nginx 标准模块和配置不能灵活地适应系统要求时,就可以考虑使用 Lua 扩展和定制 Nginx 服务。OpenResty 集成了大量精良的 Lua 库、第三方模块,可以方便地搭建能够处理超高并发、扩展性极高的 Web 服务,所以这里选择 OpenResty 提供的 lua-nginx-module 方案。

安装Lua环境

lua-nginx-module 依赖于 LuaJIT 和 ngx_devel_kit。LuaJIT 需要安装,ngx_devel_kit 只需下载源码包,在 Nginx 编译时指定 ngx_devel_kit 目录。

系统依赖库

首先确保系统已安装如下依赖库。

$ yum install readline-devel pcre-devel openssl-devel gcc

安装LuaJIT

首先,安装 LuaJIT 环境,如下所示:

$ wget http://luajit.org/download/LuaJIT-2.0.5.tar.gz
$ tar zxvf LuaJIT-2.0.5.tar.gz
$ cd LuaJIT-2.0.5
$ make install
# 安装成功
==== Successfully installed LuaJIT 2.0.5 to /usr/local ====

设置 LuaJIT 有关的环境变量。

$ export LUAJIT_LIB=/usr/local/lib
$ export LUAJIT_INC=/usr/local/include/luajit-2.0
$ echo "/usr/local/lib" > /etc/ld.so.conf.d/usr_local_lib.conf
$ ldconfig

下载相关模块

下载 ngx_devel_kit 源码包,如下:

$ wget https://github.com/simpl/ngx_devel_kit/archive/v0.3.0.tar.gz
$ tar zxvf v0.3.0.tar.gz
# 解压缩后目录名
ngx_devel_kit-0.3.0

接下来,下载 Lua 模块 lua-nginx-module 源码包,为 Nginx 编译作准备。

$ wget https://github.com/openresty/lua-nginx-module/archive/v0.10.10.tar.gz
$ tar zxvf v0.10.10.tar.gz
# 解压缩后目录名
lua-nginx-module-0.10.10

加载Lua模块

Nginx 1.9 版本后可以动态加载模块,但这里由于版本太低只能重新编译安装 Nginx。下载 Nginx 源码包并解压:

$ wget http://nginx.org/download/nginx-1.13.5.tar.gz
$ tar zxvf nginx-1.13.5.tar.gz

编译并重新安装 Nginx:

$ cd nginx-1.13.5
# 增加--add-module=/usr/src/lua-nginx-module-0.10.10 --add-module=/usr/src/ngx_devel_kit-0.3.0
$ ./configure --prefix=/usr/local/nginx --with-http_ssl_module --with-http_v2_module --with-http_stub_status_module --with-pcre --add-module=/usr/src/lua-nginx-module-0.10.10 --add-module=/usr/src/ngx_devel_kit-0.3.0
$ make
$ make install
# 查看是否安装成功
$ nginx -v

配置Nginx环境

现在只需配置 Nginx,即可嵌入 Lua 脚本。首先,在 http 部分配置 Lua 模块和第三方库路径:

# 第三方库(cjson)地址luajit-2.0/lib
lua_package_path '/home/www/lua/?.lua;;';
lua_package_cpath '/usr/local/include/luajit-2.0/lib/?.so;;';

接着,配置一个 Lua 脚本服务:

# hello world测试
server {
    location /lua_content {
        # 定义MIME类型
        default_type 'text/plain';
        content_by_lua_block {
            ngx.say('Hello,world!')
        }
    }
}

测试安装和配置是否正常:

$ service nginx test
$ service nginx reload
# 访问地址/lua_content输出
Hello,world!

Lua调用Nginx

lua-nginx-module 模块中已经为 Lua 提供了丰富的 Nginx 调用 API,每个 API 都有各自的作用环境,详细描述见 Nginx API for Lua。这里只列举基本 API 的使用 。

先配一个 Lua 脚本服务,配置文件如下:

location ~ /lua_api {  
    # 示例用的Nginx变量  
    set $name $host;
    default_type "text/html";  
    # 通过Lua文件进行内容处理
    content_by_lua_file /home/www/nginx-api.lua;  
}

请求部分

  • ngx.var

可以通过ngx.var.var_name形式获取或设置 Nginx 变量值,例如 request_uri、host、request 等。

-- ngx.say打印内容
ngx.say(ngx.var.request_uri)
ngx.var.name = 'www.fanhaobai.com'
  • ngx.req.get_headers()

该方法会以表的形式返回当前请求的头信息。查看请求的头信息:

ngx.say('Host : ', ngx.req.get_headers().host, '<br>')
for k,v in pairs(ngx.req.get_headers()) do
    if type(v) == "table" then
        ngx.say(k, " : ", table.concat(v, ","), '<br>')
    else
        ngx.say(k," : ", v, '<br>')
    end
end

当然,通过 ngx.req.set_header() 也可以设置头信息。

ngx.req.set_header("Content-Type", "text/html")
  • ngx.req.get_uri_args()

该方法以表形式返回当前请求的所有 GET 参数。查看请求 query 为?name=fhb的 GET 参数:

ngx.say('name : ', ngx.req.get_uri_args().name, '<br>')
for k,v in pairs(ngx.req.get_uri_args()) do
    if type(v) == "table" then
        ngx.say(k, " : ", table.concat(v, ","), '<br>')
    else
        ngx.say(k," : ", v, '<br>')
    end
end

同样,可以通过 ngx.req.set_uri_args() 设置请求的所有 GET 参数。

ngx.req.set_uri_args({name='fhb'}) --{name='fhb'}可以为query形式name=fhb
  • get_post_args()

该方法以表形式返回当前请求的所有 POST 参数,POST 数据必须是 application/x-www-form-urlencoded 类型。查看请求curl --data 'name=fhb' localhost/lua_api的 POST 参数:

--必须先读取body体
ngx.req.read_body()
ngx.say('name : ', ngx.req.get_post_args().name, '<br>')
for k,v in pairs(ngx.req.get_post_args()) do
    if type(v) == "table" then
        ngx.say(k, " : ", table.concat(v, ","), '<br>')
    else
        ngx.say(k," : ", v, '<br>')
    end
end

通过 ngx.req.get_body_data() 方法可以获取未解析的请求 body 体内容字符串。

  • ngx.req.get_method()

获取请求的大写字母形式的请求方式,通过 ngx.req.set_method() 可以设置请求方式。例如:

ngx.say(ngx.req.get_method())

响应部分

  • ngx.header

通过ngx.header.header_name的形式获取或设置响应头信息。如下:

ngx.say(ngx.header.content_type)
ngx.header.content_type = 'text/plain'
  • ngx.print()

ngx.print() 方法会填充指定内容到响应 body 中。如下所示:

ngx.print(ngx.header.content_type)
  • ngx.say()

如上述使用,ngx.say() 方法同 ngx.print() 方法,只是会在后追加一个换行符。

  • ngx.exit()

以某个状态码返回响应内容,状态码常量对应关系见 HTTP status constants 部分,也支持数字形式的状态码。

ngx.exit(403)
  • ngx.redirect()

重定向当前请求到新的 url,响应状态码可选列表为 301、302(默认)、303、307。

ngx.redirect('http://www.fanhaobai.com')

其他

  • ngx.re.match

该方法提供了正则表达式匹配方法。请求?name=fhb&age=24匹配 GET 参数中的数字:

local m, err = ngx.re.match(ngx.req.set_uri_args, "[0-9]+")
if m then
    ngx.say(m[0])
else
    ngx.say("match not found")
end
  • ngx.log()

通过该方法可以将内容写入 Nginx 日志文件,日志文件级别需同 log 级别一致。

  • ngx.md5() | ngx.encode_base64() | ngx.decode_base64()

它们都是字符串编码方式。ngx.md5() 可以对字符串进行 md5 加密处理,而 ngx.encode_base64() 是对字符串 base64 编码, ngx.decode_base64() 为 base64 解码。

Nginx中嵌入Lua

上面讲述了怎么在 Lua 中调用 Nginx 的 API 来扩展或定制 Nginx 的功能,那么编写好的 Lua 脚本怎么在 Nginx 中得到执行呢?其实,Nginx 是通过模块指令形式在其 11 个处理阶段做插入式处理,指令覆盖 http、server、server if、location、location if 这几个范围。

模块指令列表

这里只列举基本的 Lua 模块指令,更多信息参考 Directives 部分。

指令

所在阶段

使用范围

说明

init_by_lua
init_by_lua_file

加载配置文件

http

可以用于初始化全局配置

set_by_lua
set_by_lua_file

rewrite

server
location
location if

复杂逻辑的变量赋值,注意是阻塞的

rewrite_by_lua
rewrite_by_lua_file

rewrite

http
server
location
location if

实现复杂逻辑的转发或重定向

content_by_lua
content_by_lua_file

content

location
location if

处理请求并输出响应

header_filter_by_lua
header_filter_by_lua_file

响应头信息过滤

http
server
location
location if

设置响应头信息

body_filter_by_lua
body_filter_by_lua_file

输出过滤

http
server
location
location if

对输出进行过滤或修改

使用指令

注意到,每个指令都会有*_lua*_lua_file两个指令,*_lua指令后为 Lua 代码块,而*_lua_file指令后为 Lua 脚本文件路径。下面将只对*_lua指令进行说明。

  • init_by_lua

该指令会在 Nginx 的 Master 进程加载配置时执行,所以可以完成 Lua 模块初始化工作,Worker 进程同样会继承这些。

nginx.conf配置文件中的 http 部分添加如下代码:

-- 所有worker共享的全局变量
lua_shared_dict shared_data 1m;  
init_by_lua_file /usr/example/lua/init.lua;

init.lua初始化脚本为:

local cjson = require 'cjson'
local redis = require 'resty.redis'
local shared_data = ngx.shared.shared_data
  • set_by_lua

我们直接使用 set 指令很难实现很复杂的变量赋值逻辑,而 set_by_lua 模块指令就可以解决这个问题。

nginx.conf配置文件 location 部分内容为:

location /lua {
    set_by_lua_file $num /home/www/set.lua;
    default_type 'text/html';
    echo $num;
}

set.lua脚本内容为:

local uri_args = ngx.req.get_uri_args()
local i = uri_args.a or 0
local j = uri_args.b or 0
return i + j

上述赋值逻辑,请求 query 为?a=10&b=2时响应内容为 12。

  • rewrite_by_lua

可以实现内部 URL 重写或者外部重定向。nginx.conf配置如下:

location /lua {
    default_type "text/html";
    rewrite_by_lua_file /home/www/rewrite.lua;
}

rewrite.lua脚本内容:

if ngx.req.get_uri_args()["type"] == "app" then
    ngx.req.set_uri("/m_h5", false);
end
  • access_by_lua

用于访问权限控制。例如,只允许带有身份标识用户访问,nginx.conf配置为:

location /lua {  
    default_type "text/html";
    access_by_lua_file /home/www/access.lua;
}  

access.lua脚本内容为:

if ngx.req.get_uri_args()["token"] == "fanhb" then
    return ngx.exit(403)
end
  • content_by_lua

该指令在 Lua 调用 Nginx 部分已经使用过了,用于输出响应内容。

案例

访问权限控制

使用 Lua 模块对本站的 ES 服务做受信操作控制,即非受信 IP 只能查询操作。nginx.conf配置如下:

location / {
    set $allowed '115.171.226.212';
    access_by_lua_block {
        if ngx.re.match(ngx.req.get_method(), "PUT|POST|DELETE") and not ngx.re.match(ngx.var.request_uri, "_search") then
	    start, _ = string.find(ngx.var.allowed, ngx.var.remote_addr)
	    if not start then
		ngx.exit(403)
	    end
	end
    }
	
    proxy_pass http://127.0.0.1:9200$request_uri;
}

访问频率控制

在 Nginx 配置文件的 location 部分配置 Lua 脚本基本参数,并配置 Lua 模块指令:

default_type "text/html";
set rate_per 300
access_by_lua_file /home/www/access.lua;

Lua 脚本实现频率控制逻辑,使用 Redis 对单位时间内的访问次数做缓存,key 为访问 uri 拼接 token 后的 md5 值。具体内容如下:

local redis = require "resty.redis"
local red = redis:new()

local limit = tonumber(ngx.var.rate_per) or 200
local expire_time = tonumber(ngx.var.rate_expire) or 1000
local key = "rate.limit:string:"

red:set_timeout(500)
local ok, err = red:connect("www.fanhaobai.com", 6379)
if not ok then
    ngx.log(ngx.ERR, "failed to connect redis: " .. err)
    return
end

key = key .. ngx.md5(ngx.var.request_uri .. (ngx.req.get_uri_args()['token'] or ngx.req.get_post_args()['token']))
local times, err = red:incr(key)
if not times then
    ngx.log(ngx.ERR, "failed to exec incr: " .. err)
    return
elseif times == 1 then
    ok, err = red:expire(key, expire_time)
    if not ok then
        ngx.log(ngx.ERR, "failed to exec expire: " .. err)
        return
    end
end

if times > limit then
    return ngx.exit(403)
end

return

相关推荐

实战派 | Java项目中玩转Redis6.0客户端缓存

铺垫首先介绍一下今天要使用到的工具Lettuce,它是一个可伸缩线程安全的redis客户端。多个线程可以共享同一个RedisConnection,利用nio框架Netty来高效地管理多个连接。放眼望向...

轻松掌握redis缓存穿透、击穿、雪崩问题解决方案(20230529版)

1、缓存穿透所谓缓存穿透就是非法传输了一个在数据库中不存在的条件,导致查询redis和数据库中都没有,并且有大量的请求进来,就会导致对数据库产生压力,解决这一问题的方法如下:1、使用空缓存解决对查询到...

Redis与本地缓存联手:多级缓存架构的奥秘

多级缓存(如Redis+本地缓存)是一种在系统架构中广泛应用的提高系统性能和响应速度的技术手段,它综合利用了不同类型缓存的优势,以下为你详细介绍:基本概念本地缓存:指的是在应用程序所在的服务器内...

腾讯云国际站:腾讯云服务器如何配置Redis缓存?

本文由【云老大】TG@yunlaoda360撰写一、安装Redis使用包管理器安装(推荐)在CentOS系统中,可以通过yum包管理器安装Redis:sudoyumupdate-...

Spring Boot3 整合 Redis 实现数据缓存,你做对了吗?

你是否在开发互联网大厂后端项目时,遇到过系统响应速度慢的问题?当高并发请求涌入,数据库压力剧增,响应时间拉长,用户体验直线下降。相信不少后端开发同行都被这个问题困扰过。其实,通过在SpringBo...

【Redis】Redis应用问题-缓存穿透缓存击穿、缓存雪崩及解决方案

在我们使用redis时,也会存在一些问题,导致请求直接打到数据库上,导致数据库挂掉。下面我们来说说这些问题及解决方案。1、缓存穿透1.1场景一个请求进来后,先去redis进行查找,redis存在,则...

Spring boot 整合Redis缓存你了解多少

在前一篇里面讲到了Redis缓存击穿、缓存穿透、缓存雪崩这三者区别,接下来我们讲解Springboot整合Redis中的一些知识点:之前遇到过,有的了四五年,甚至更长时间的后端Java开发,并且...

揭秘!Redis 缓存与数据库一致性问题的终极解决方案

在现代软件开发中,Redis作为一款高性能的缓存数据库,被广泛应用于提升系统的响应速度和吞吐量。然而,缓存与数据库之间的数据一致性问题,一直是开发者们面临的一大挑战。本文将深入探讨Redis缓存...

高并发下Spring Cache缓存穿透?我用Caffeine+Redis破局

一、什么是缓存穿透?缓存穿透是指查询一个根本不存在的数据,导致请求直接穿透缓存层到达数据库,可能压垮数据库的现象。在高并发场景下,这尤其危险。典型场景:恶意攻击:故意查询不存在的ID(如负数或超大数值...

Redis缓存三剑客:穿透、雪崩、击穿—手把手教你解决

缓存穿透菜小弟:我先问问什么是缓存穿透?我听说是缓存查不到,直接去查数据库了。表哥:没错。缓存穿透是指查询一个缓存中不存在且数据库中也不存在的数据,导致每次请求都直接访问数据库的行为。这种行为会让缓存...

Redis中缓存穿透问题与解决方法

缓存穿透问题概述在Redis作为缓存使用时,缓存穿透是常见问题。正常查询流程是先从Redis缓存获取数据,若有则直接使用;若没有则去数据库查询,查到后存入缓存。但当请求的数据在缓存和数据库中都...

Redis客户端缓存的几种实现方式

前言:Redis作为当今最流行的内存数据库和缓存系统,被广泛应用于各类应用场景。然而,即使Redis本身性能卓越,在高并发场景下,应用于Redis服务器之间的网络通信仍可能成为性能瓶颈。所以客户端缓存...

Nginx合集-常用功能指导

1)启动、重启以及停止nginx进入sbin目录之后,输入以下命令#启动nginx./nginx#指定配置文件启动nginx./nginx-c/usr/local/nginx/conf/n...

腾讯云国际站:腾讯云怎么提升服务器速度?

本文由【云老大】TG@yunlaoda360撰写升级服务器规格选择更高性能的CPU、内存和带宽,以提供更好的处理能力和网络性能。优化网络配置调整网络接口卡(NIC)驱动,优化TCP/IP参数...

雷霆一击服务器管理员教程

本文转载莱卡云游戏服务器雷霆一击管理员教程(搜索莱卡云面版可搜到)首先你需要给服务器设置管理员密码,默认是空的管理员密码在启动页面进行设置设置完成后你需要重启服务器才可生效加入游戏后,点击键盘左上角E...

取消回复欢迎 发表评论: