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

Nginx Lua编程基础(nginx使用lua)

liuian 2025-03-20 16:22 24 浏览

Lua是一门脚本动态语言,并不太适合做复杂业务逻辑的程序开发,但是,在高并发场景下,Nginx Lua编程是解决性能问题的利器。

Nginx Lua编程主要的应用场景如下:

  • API网关:实现数据校验前置、请求过滤、API请求聚合、AB测试、灰度发布、降级、监控等功能,著名的开源网关Kong就是基于Nginx Lua开发的。
  • 高速缓存:可以对响应内容进行缓存,减少到后端的请求,从而提升性能。比如,Nginx Lua可以和Java容器、Redis整合,由Java容器进行业务处理和数据缓存,而Nginx负责读缓存并进行响应,从而解决Java容器的性能瓶颈
  • 简单的动态Web应用:可以完成一些业务逻辑处理较少但耗费CPU的简单应用,比如模板页面的渲染。一般的Nginx Lua页面渲染处理流程为:从Redis获取业务处理结果数据,从本地加载XML/HTML页面模板,然后进行页面渲染。
  • 网关限流:缓存、降级、限流是解决高并发的三大利器,Nginx内置了令牌限流的算法,但是对于分布式的限流场景,可以通过Nginx Lua编程定制自己的限流机制

ngx_lua是Nginx的一个扩展模块,将Lua VM嵌入Nginx,请求时创建一个VM,请求结束时回收VM,这样就可以在Nginx内部运行Lua脚本,使得Nginx变成一个Web容器。以OpenResty为例,其提供了一些常用的ngx_lua开发模块:

  • lua-resty-memcached:通过Lua操作memcache
  • lua-resty-mysql:通过Lua操作MySQL
  • lua-resty-redis:通过Lua操作Redis缓存
  • lua-resty-dns:通过Lua操作DNS域名服务器
  • lua-resty-limit-traffic:通过Lua进行限流
  • lua-resty-template:通过Lua进行模板渲染
  • lua-resty-jwt:通过Lua生成jwt
  • lua-resty-kafka:通过Lua操作kafka

Lua脚本需要通过Lua解释器来解释执行,除了Lua官方的默认解释器外,目前使用广泛的Lua解释器叫做LuaJIT。LuaJIT采用C语言编写,被设计成全兼容标准Lua 5.1,因此LuaJIT代码的语法和标准Lua的语法没多大区别。但是LuaJIT的运行速度比标准Lua快数十倍。

Nginx Lua的执行原理

在OpenResty中,每个Worker进程使用一个Lua VM,当请求被分配到Worker时,将在这个Lua VM中创建一个协程,协程之间数据隔离,每个协程都具有独立的全局变量。

ngx_lua是将Lua VM嵌入Nginx,让Nginx执行Lua脚本,并且高并发、非阻塞地处理各种请求Lua内置协程可以很好地将异步回调转换成顺序调用的形式。ngx_lua在Lua中进行的IO操作都会委托给Nginx的事件模型,从而实现非阻塞调用。开发者可以采用串行的方式编写程序,ngx_lua会在进行阻塞的IO操作时自动中断,保存上下文,然后将IO操作委托给Nginx事件处理机制,在IO操作完成后,ngx_lua会恢复上下文,程序继续执行,这些操作对用户程序都是透明的。

每个Worker进程都持有一个Lua解释器或LuaJIT实例,被这个Worker处理的所有请求共享这个实例。每个请求的context上下文会被Lua轻量级的协程分隔,从而保证每个请求是独立的。

ngx_lua采用one-coroutine-per-request的处理模型,对于每个用户请求,ngx_lua会唤醒一个协程用于执行用户代码处理请求,当请求处理完成后,这个协程会被销毁。每个协程都有一个独立的全局环境,继承于全局共享的、只读的公共数据。所以,被用户代码注入全局空间的任何变量都不会影响其他请求的处理,并且这些变量在请求处理完成后会被释放,这样就保证所有的用户代码都运行在一个sandbox(沙箱)中,这个沙箱与请求具有相同的生命周期。

得益于Lua协程的支持,ngx_lua在处理10000个并发请求时,只需要很少的内存。根据测试,ngx_lua处理每个请求只需要2KB的内存,如果使用LuaJIT就会更少

Nginx Lua配置指令

ngx_lua定义的Nginx配置指令大致如下:

  • lua_package_path:配置Lua外部库的搜索路径,搜索的文件类型为.lua。
  • lua_package_cpath:配置Lua外部搜索库的搜索路径,搜索C语言编写的外部库文件。
  • init_by_lua:Master进程启动时挂载的Lua代码块,常用于导入公共模块。
  • init_by_lua_file:Master进程启动时挂载的Lua脚本文件。
  • init_worker_by_lua:Worker进程启动时挂载的Lua代码块,常用于执行一些定时任务
  • init_worker_by_lua_file:Worker进程启动时挂载的Lua文件,常用于执行一些定时任务
  • set_by_lua:类似于rewrite模块的set指令,将Lua代码块的返回结果设置在Nginx的变量中。
  • set_by_lua_file:同上,执行的是脚本Lua脚本文件。
  • rewrite_by_lua:执行在rewrite阶段的Lua代码块,完成转发、重定向、缓存等功能。
  • rewrite_by_lua_file:同上,执行的是Lua脚本文件。
  • access_by_lua:执行在access阶段的Lua代码块,完成IP准入、接口权限等功能。
  • access_by_lua_file:同上,执行的是Lua脚本文件。
  • content_by_lua:执行在content阶段的Lua代码块,执行结果将作为请求响应的内容。
  • content_by_lua_file:同上,执行的是Lua脚本文件。
  • content_by_lua_block:content_by_lua的升级款,在一对花括号中编写Lua代码,而不需要做特殊字符转译。
  • header_filter_by_lua:响应头部过滤处理的Lua代码块,可以用于添加设置响应头部信息,如Cookie相关属性。
  • body_filter_by_lua:响应体过滤处理的Lua代码块,例如加密响应体。
  • log_by_lua:异步完成日志记录的Lua代码块,例如既可以在本地记录日志,也可以记录到ETL集群。

ngx_lua配置指令在Nginx的HTTP请求处理阶段所处的位置如图:

常用配置指令

  • lua_package_path指令:用于设置".lua"外部库的搜索路径,此指令的上下文为http配置块,默认值为LUA_PATH环境变量内容或者lua编译的默认值。
    • 格式:lua_package_path lua-style-path-str。
    • lua_package_cpath指令:用于设置Lua的C语言块外部库".so"(Linux)或".dll"(Windows)的搜索路径,此指令的上下文为http配置块。
    • 格式:lua_package_cpath lua-style-cpath-str
http {
  ...
  #设置“.lua”外部库的搜索路径,此指令的上下文为http配置块
	#";;"常用于表示原始的搜索路径
	lua_package_path	"/foo/bar/?.lua;/blah/?.lua;;";
	lua_package_cpath	"/usr/local/openresty/lualib/?/?.so;/usr/local/openresty/lualib/?.so;;";
}

对于以上两个指令,OpenResty可以在搜索路径中使用插值变量。例如,可以使用插值变量$prefix或${prefix}获取虚拟服务器server的前缀路径,server的前缀路径通常在Nginx服务器启动时通过-p PATH命令在指定。

  • init_by_lua指令:只能用于http上下文,运行在配置加载阶段。当Nginx的master进程在加载Nginx配置文件时,在全局Lua VM级别上运行由参数lua-script-str指定的Lua脚本块。若使用init_by_lua_file指令,后面跟lua文件的路径( lua_file_path),则在全局Lua VM 级别上运行lua_file_path文件指定的lua脚本。如果Lua脚本的缓存是关闭的,那么每一次请求都运行一次init_by_lua处理程序。

格式为:init_by_lua lua-script-str。

  • lua_load_cache指令:用于启用或禁止Lua脚本缓存。可以使用的上下文为http、server、location配置块。默认开启。

格式为:lua_code_cache on | off

http {
  ...
	#项目初始化
  init_by_lua_file	conf/luaScript/initial/loading_config.lua;
  	
  #调试模式,关闭lua脚本缓存
  lua_code_cache on;
  ...
}

在缓存关闭的时,set_by_lua_file、content_by_lua_file、access_by_lua_file、content_by_lua_file等指令中引用的Lua脚本都将不会被缓存,所有的Lua脚本都将从头开始加载。

  • set_by_lua指令:将Lua脚本块的返回结果设置在Nginx变量中。

格式为:set_by_lua $destVar lua-script-str params

location /set_by_lua_demo {
	#set 指令定义两个Nginx变量
  set $foo 1;
  set $bar 2;
  			
  #调用Lua内联代码,将结果放入Nginx变量$sum
  #Lua脚本的含义是,将两个输入参数$foo、$bar累积起来,然后相加的结果设置Nginx变量$sum中
  set_by_lua $sum 'return tonumber(ngx.arg[1]) + tonumber(ngx.arg[2])' $foo $bar;
  
  echo "$foo + $bar = $sum";
}

运行结果:

  work curl http://localhost/set_by_lua_demo
1 + 2 = 3
  • access_by_lua指令:执行在HTTP请求处理11个阶段的access阶段,使用Lua脚本进行访问控制。运行于access阶段的末尾,总是在allow和deny这样的指令之后运行。

格式为:access_by_lua $destVar lua-script-str

location /access_demo {
  access_by_lua	'ngx.log(ngx.DEBUG, "remote_addr = "..ngx.var.remote_addr);
  if ngx.var.remote_addr == "192.168.56.121" then
  	return;
  end
  ngx.exit(ngx.HTTP_UNAUTHORIZED);
  ';
  echo "hello world";
}
  		
location /access_demo_2 {
  allow "192.168.56.121";
  deny all;
  echo "hello world";
}

运行结果:

  work curl http://localhost/access_demo

401 Authorization Required

401 Authorization Required


openresty/1.13.6.2
#上述案例运行日志: 2022/02/15 10:32:17 [debug] 26293#0: *17 [lua] access_by_lua(nginx-lua-demo.conf:85):1: remote_addr = 127.0.0.1 2022/02/15 10:32:17 [info] 26293#0: *17 kevent() reported that client 127.0.0.1 closed keepalive connection work curl http://localhost/access_demo_2 403 Forbidden

403 Forbidden


openresty/1.13.6.2
#上述案例运行日志 2022/02/15 10:33:11 [error] 26293#0: *18 access forbidden by rule, client: 127.0.0.1, server: localhost, request: "GET /access_demo_2 HTTP/1.1", host: "localhost" 2022/02/15 10:33:11 [info] 26293#0: *18 kevent() reported that client 127.0.0.1 closed keepalive connection
  • content_by_lua/content_by_lua_block指令:用于设置执行在content阶段的Lua代码块,执行结果将作为请求响应的内容。该指令用于location上下文。

格式为:content_by_lua lua-script-str

location /errorLog {
  content_by_lua '
    ngx.log(ngx.ERR, "this is an error log ");
  	ngx.say("错误日志调用成功");
  ';
}
  		
location /infoLog {
	content_by_lua '
		ngx.log(ngx.ERR, "this is an info log ");
  	ngx.say("业务日志调用成功");
  ';
}

location /debugLog {
  content_by_lua '
    ngx.log(ngx.ERR, "this is an debug log ");
  	ngx.say("调试日志调用成功");
  ';
}

OpenResty v0.9.17版本以后,使用content_by_lua_block指令代替content_by_lua指令,避免对代码块中的字符串进行转译。

运行结果:

  work curl http://localhost/errorLog
错误日志调用成功
  work curl http://localhost/infoLog 
业务日志调用成功
  work curl http://localhost/debugLog
调试日志调用成功

Nginx Lua的内置常量和变量

内置变量

  • ngx.arg:类型为Lua table,ngx.arg.VARIABLE用于获取ngx_lua配置指令后面的调用参数。
  • ngx.var:类型为Lua table,ngx.var.VARIABLE用于引用某个Nginx变量。前提是Nginx变量必须提前声明
  • ngx.ctx:类型为Lua table,可以用来访问当前请求的Lua上下文数据,其生存周期与当前请求相同
  • ngx.header:类型为Lua table,用于访问HTTP响应头,可以通过ngx.header.HEADER形式引用某个头
  • ngx.status:用于设置当前请求的HTTP响应码

内置常量

内置常量基本是见名知意的,可以根据后面的实战案例,加深理解。

核心常量

    • ngx.OK(0)
    • ngx.ERROR(-1)
    • ngx.AGAIN(-2)
    • ngx.DONE(-4)
    • ngx.DECLINED(-5)
    • ngx.nil

HTTP方法常量

    • ngx.HTTP.GET
    • ngx.HTTP.HEAD
    • ngx.HTTP.PUT
    • ngx.HTTP.POST
    • ngx.HTTP.DELETE
    • ngx.HTTP.OPTIONS
    • ngx.HTTP.MKCOL
    • ngx.HTTP.MOVE
    • ngx.HTTP.PROPFIND
    • ngx.HTTP.PROPPATCH
    • ngx.HTTP.LOCK
    • ngx.HTTP.UNLOCK
    • ngx.HTTP.PATH
    • ngx.HTTP.TRACE

HTTP状态码常量

    • ngx.HTTP_OK(200)
    • ngx.HTTP_CREATED(201)
    • ngx.HTTP_SPECIAL_RESPONSE(300)
    • ngx.HTTP_MOVED_PERMANENTLY(301)
    • ngx.HTTP_MOVER_TEMPORARILY(302)
    • ngx.HTTP_SEE_OTHER(303)
    • ngx.HTTP_NOT_MODIFIED(304)
    • ngx.HTTP_BAD_REQUEST(400)
    • ngx.HTTP_UNAUTHORIZED(401)
    • ngx.HTTP_FORBIDDEN(403)
    • ngx.HTTP_NOT_FOUND(404)
    • ngx.HTTP_NOT_ALLOWED(405)
    • ngx.HTTP_GONE(410)
    • ngx.HTTP_INTERNAL_SERVER_ERROR(500)

日志类型常量

    • ngx.STDERR
    • ngx.EMERG
    • ngx.ALERT
    • ngx.CRIT
    • ngx.ERR
    • ngx.WARE
    • ngx.NOTICE
    • ngx.INFO
    • ngx.DEBUG

Nginx+LUA基础到此结束,下一篇开始实战!并在实战中掌握基础。

相关推荐

教你把多个视频合并成一个视频的方法

一.情况介绍当你有一个m3u8文件和一个目录,目录中有连续的视频片段,这些片段可以连成一段完整的视频。m3u8文件打开后像这样:m3u8文件,可以理解为播放列表,里面是播放视频片段的顺序。视频片段像这...

零代码编程:用kimichat合并一个文件夹下的多个文件

一个文件夹里面有很多个srt字幕文件,如何借助kimichat来自动批量合并呢?在kimichat对话框中输入提示词:你是一个Python编程专家,完成如下的编程任务:这个文件夹:D:\downloa...

Java APT_java APT 生成代码

JavaAPT(AnnotationProcessingTool)是一种在Java编译阶段处理注解的工具。APT会在编译阶段扫描源代码中的注解,并根据这些注解生成代码、资源文件或其他输出,...

Unit Runtime:一键运行 AI 生成的代码,或许将成为你的复制 + 粘贴神器

在我们构建了UnitMesh架构之后,以及对应的demo之后,便着手于实现UnitMesh架构。于是,我们就继续开始UnitRuntime,以用于直接运行AI生成的代码。PS:...

挣脱臃肿的枷锁:为什么说Vert.x是Java开发者手中的一柄利剑?

如果你是一名Java开发者,那么你的职业生涯几乎无法避开Spring。它如同一位德高望重的老国王,统治着企业级应用开发的大片疆土。SpringBoot的约定大于配置、SpringCloud的微服务...

五年后,谷歌还在全力以赴发展 Kotlin

作者|FredericLardinois译者|Sambodhi策划|Tina自2017年谷歌I/O全球开发者大会上,谷歌首次宣布将Kotlin(JetBrains开发的Ja...

kotlin和java开发哪个好,优缺点对比

Kotlin和Java都是常见的编程语言,它们有各自的优缺点。Kotlin的优点:简洁:Kotlin程序相对于Java程序更简洁,可以减少代码量。安全:Kotlin在类型系统和空值安全...

移动端架构模式全景解析:从MVC到MVVM,如何选择最佳设计方案?

掌握不同架构模式的精髓,是构建可维护、可测试且高效移动应用的关键。在移动应用开发中,选择合适的软件架构模式对项目的可维护性、可测试性和团队协作效率至关重要。随着应用复杂度的增加,一个良好的架构能够帮助...

颜值非常高的XShell替代工具Termora,不一样的使用体验!

Termora是一款面向开发者和运维人员的跨平台SSH终端与文件管理工具,支持Windows、macOS及Linux系统,通过一体化界面简化远程服务器管理流程。其核心定位是解决多平台环境下远程连接、文...

预处理的底层原理和预处理编译运行异常的解决方案

若文章对您有帮助,欢迎关注程序员小迷。助您在编程路上越走越好![Mac-10.7.1LionIntel-based]Q:预处理到底干了什么事情?A:预处理,顾名思义,预先做的处理。源代码中...

为“架构”再建个模:如何用代码描述软件架构?

在架构治理平台ArchGuard中,为了实现对架构的治理,我们需要代码+模型描述所要处理的内容和数据。所以,在ArchGuard中,我们有了代码的模型、依赖的模型、变更的模型等,剩下的两个...

深度解析:Google Gemma 3n —— 移动优先的轻量多模态大模型

2025年6月,Google正式发布了Gemma3n,这是一款能够在2GB内存环境下运行的轻量级多模态大模型。它延续了Gemma家族的开源基因,同时在架构设计上大幅优化,目标是让...

比分网开发技术栈与功能详解_比分网有哪些

一、核心功能模块一个基本的比分网通常包含以下模块:首页/总览实时比分看板:滚动展示所有正在进行的比赛,包含比分、比赛时间、红黄牌等关键信息。热门赛事/焦点战:突出显示重要的、关注度高的比赛。赛事导航...

设计模式之-生成器_一键生成设计

一、【概念定义】——“分步构建复杂对象,隐藏创建细节”生成器模式(BuilderPattern):一种“分步构建型”创建型设计模式,它将一个复杂对象的构建与其表示分离,使得同样的构建过程可以创建...

构建第一个 Kotlin Android 应用_kotlin简介

第一步:安装AndroidStudio(推荐IDE)AndroidStudio是官方推荐的Android开发集成开发环境(IDE),内置对Kotlin的完整支持。1.下载And...