Nginx框架之Lua拓展

目录

Lua脚本简述

Lua脚本简述

脚本特点

安装Lua

lua小例子

Nginx增加Lua执行模块

Nginx嵌入Lua脚本语言

Nginx嵌入Lua脚本语言

ngx_lua支持的指令

 在OpenResty中演示ngx_lua的指令

打造高性能后端接口

OpenResty Redis模块

 OpenResty mysql模块

 OpenResty http模块

Lua模板渲染器

使用

 Nginx非阻塞与Lua协程的绝配

Lua协程

Nginx的API生成页面


Lua脚本简述

nginx三大核心功能,包含静态资源、反向代理、api模块扩展,对于lua脚本的扩展,就是api模块扩展的一部分;并且nginx可以通过lua脚本直接调用redis服务器;

Lua脚本简述

lua官网:

The Programming Language Lua

Lua是一种功能强大,高效,轻量级,可嵌入的脚本语言,非常容易嵌入
到我们应用程序中。
Lua是动态类型的,通过使用基于寄存器的虚拟机解释字节码来运行,并
具有增量垃圾收集的自动内存管理,使其成为配置,脚本和快速原型设计
的理想选择。
Lua旨在成为一种轻量级可嵌入脚本语言。它用于各种应用程序,从游戏
到Web应用程序和图像处理。
Lua 将简单的程序语法与基于关联阵列和可扩展语义的强大数据描述构造相结合。Lua 采用动态键入,通过基于注册的虚拟计算机解释字形码进行运行,并具有自动内存管理,可增加垃圾收集,非常适合配置、脚本和快速原型制作。

应用在图像处理这些。

应用场景
  • 1. 许多工业应用 (例如, Adobe的Photoshop Lightroom)
  • 2. 重点是嵌入式系统(例如, 巴西的数字电视的 Ginga中间件)
  • 3. Lua目前 是游戏中领先的脚本语言,在游戏 (例如, 魔兽世界和愤怒的小鸟)中使用。

脚本特点

  • Lua是解释脚本语言领域中最快的语言。
  • Lua是便携式的,在具有标准C编译器的所有平台中构建成开箱即用,可运行各种Unix和Windows, 移动设备(运行Android,iOS,BREW,Symbian,Windows Phone),嵌入式微处理器(如ARM和Rabbit,适用于Lego MindStorms等应用程序),IBM大型机等。
  • 可嵌入,Lua是一种快速语言引擎,占用空间小,可以轻松嵌入到应用程序中。
  • 简单而强大,提供实现功能的*元机制*,而不是直接在语言中提供大量功能。Lua不是纯粹的面对 象语言,提供了实现类和继承的元机制。
  • ,源包含大约24000行C,包含源代码和文档的Lua 5.3.5的tar包需要297K压缩和1.2M未压缩。 免费,MIT许可证,可以用于任何目的,包括商业目的,完全免费。

比js语言更加广泛。轻量级语言不能做的事情,没有什么大的框架,例如java中spring,这也是它其中的一个缺点把

安装Lua

比较简单

curl -R -O http://www.lua.org/ftp/lua-5.3.5.tar.gz 
tar zxf lua-5.3.5.tar.gz 
cd lua-5.3.5 
make linux test

依赖不多,直接下载就行。

使用打印 hello world

$ lua
Lua 5.1.4 Copyright (C) 1994-2008 Lua.org, PUC-Rio
> print("hello, world")
hello, world

lua小例子

新建mydata.lua文件 
  • 定义一个外界可见的表
  • 受保护的自定义数据内容
  • function给_M定义一个方法,get_age,用来返回data中的数据
  • 向外界暴露表
-- 定义一个外界可见的表
local _M = {}

-- 受保护的自定义数据内容
local data = {
   dog = 5,
   cat = 3,
   pig = 1,
}

-- function给_M定义一个方法,get_age,用来返回data中的数据
function _M.get_age(name)
   return data[name]
end	-- end表示方法结束

-- 向外界暴露表
return _M

这个和js中的方法就很像了。

利用lua脚本实现 阶乘

-- defines a factorial function
function fact (n)
if n == 0 then
return 1
else
return n * fact(n-1)
end
end
print("enter a number:")
a = io.read("*number") -- read a number
print(fact(a))

要运行 ,直接 使用 lua 文件就能运行起来

 语法比Java要简略,缩进对齐,无大括号分号

Nginx增加Lua执行模块

Nginx嵌入Lua脚本语言

ngx_lua模块

不鼓励自己用Nginx构建这个模块,自己构建需要注意,Nginx,LuaJIT和OpenSSL官方发行版具有各种限制和长期
存在的错误,这些错误可能会导致某些模块的功能被禁用,无法正常运行或运行速度变慢。如何编译, 参考 《Nginx嵌入Lua脚本语言操作》 手册。
使用ngx_lua模块相当于使用Openresty,建议用官方版本OpenResty,它做了大量优化、集成了常用的第三方插件。

OpenResty
OpenResty内部集成了大量精良的 Lua 库、第三方模块以及 大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。OpenResty是一款基于
NGINX 和 LuaJIT 的Web 平台。OpenResty的安装参考《OpenResty安装操作》,比构建ngx_lua模块简单很多.

Nginx嵌入Lua脚本语言

安装编译Nginx

  • 安装依赖项
yum -y install gcc zlib zlib-devel pcre-devel openssl openssl-devel

# lua_jit
wget http://luajit.org/download/LuaJIT-2.0.5.tar.gz 
tar -xvf LuaJIT-2.0.5.tar.gz
cd LuaJIT-2.0.5
make install

下载安装nginx和依赖项

cd ~
# nginx
wget http://nginx.org/download/nginx-1.14.2.tar.gz
tar -xvf nginx-1.14.2.tar.gz
# ndk
wget https://github.com/simplresty/ngx_devel_kit/archive/v0.3.0.zip 
unzip v0.3.0.zip 
# ngx_lua 模块
wget https://github.com/openresty/lua-nginx-module/archive/v0.10.14.zip
unzip v0.10.14.zip

# 安装nginx
cd nginx-1.14.2
./configure --prefix=/usr/local/nginx-1.14.2 \
         --add-module=../ngx_devel_kit-0.3.0 \
         --add-module=../lua-nginx-module-0.10.14
  • 测试lua模块是否安装成功
  • 创建一个文件夹存储lua脚本
mkdir /usr/local/nginx-1.14.2/lua_scripts
  • 创建mydata.lua
-- mydata.lua
 local _M = {}

 local data = {
     dog = 3,
     cat = 4,
     pig = 5,
 }

 function _M.get_age(name)
     return data[name]
 end
 return _M
  • nginx.conf文件
 lua_package_path "/usr/local/nginx-1.14.2/lua_scripts/?.lua;;";
    server {
        ...
        location /lua {
            content_by_lua_block {
               local mydata = require "mydata"
               ngx.say(mydata.get_age("dog"))
            }
        }

可能的错误

# 1、动态库找不到
./sbin/nginx: error while loading shared libraries: libluajit-5.1.so.2: cannot open shared object file: No such file or directory
# 解决办法:
echo "/usr/local/lib" > /etc/ld.so.conf.d/usr_local_lib.conf
ldconfig

# 2、warn提示
nginx: [alert] detected a LuaJIT version which is not OpenResty's; many optimizations will be disabled and performance will be compromised (see https://github.com/openresty/luajit2 for OpenResty's LuaJIT or, even better, consider using the OpenResty releases from https://openresty.org/en/download.html)
# 告诉你,你不要用这个luajit版本,可以用openresty提供的luajit优化版本,或者干脆直接用openresty

安装lua_redis

wget https://codeload.github.com/openresty/lua-resty-redis/zip/master
unzip master
cd lua-resty-redis-master
make && make install

 都是lua集成的组件。

OpenResty 目录

包括nginx目录等。 

ngx_lua支持的指令

指令分为配置指令、控制指令。
控制指令分为两种方式:
lua脚本块*_by_lua_block
lua脚本文件*_by_lua_file
Lua脚本执行顺序,如右图从上至下:初始化、重写/访问、内容处理、日志输出四个阶段。
内容处理可以由上游服务或者Lua脚本语言来处理,用Lua脚本就大有可为了。

 在OpenResty中演示ngx_lua的指令

设置由set_by_lua,content_by_lua等指定的脚本使用的Lua模块搜索路径。类似java的classPath

预加载json模块

在location、location if上下文中使用。充当“内容处理程序”并执行指定的lua脚本内容,Lua代码可以进行API调用,并在独立的全局环境(即沙箱)中作为新的衍生协程执行

worker_processes  1;
events {
    worker_connections 1024;
}

http {
    # 设置由set_by_lua, content_by_lua等指定的脚本使用的Lua模块搜索路径。类似java的classPath
    # 路径字符串采用标准的Lua路径形式,;; 可用于代表原始搜索路径。
	lua_package_path "/usr/local/openresty/lua_scripts/?.lua;;";

    # 申请一个名为dogs的10m字典内存,共享在所有工作进程中
    lua_shared_dict dogs 10m;

    
    # 在服务器启动时预加载Lua模块,并利用现代操作系统的写时复制(COW)优化。
    init_by_lua_block {
        -- 预加载json模块
        require "cjson"

        -- 操作共享内存
        local dogs = ngx.shared.dogs;
        dogs:set("Tom", 56)
    }

    server {
        listen 8080;
        location /lua {
            default_type text/html;

            # 在location、location if上下文中使用。充当“内容处理程序”并执行指定的lua脚本内容,
            # Lua代码可以进行API调用,并在独立的全局环境(即沙箱)中作为新的衍生协程执行
            content_by_lua_block {
                local mydata = require "mydata"
                ngx.say(mydata.get_age("dog"))

                -- require关键字会返回init_by_lua_block中已经预加载了cjson模块
                ngx.say(require "cjson".encode{dog = 5, cat = 6})

                -- 读取共享内存中的信息
                local dogs = ngx.shared.dogs;
                ngx.say(dogs:get("Tom"))
            }
        }
    }
}

 在运行的时候,需要去掉 nginx目录在运行

打造高性能后端接口

OpenResty Redis模块

OpenResty中默认嵌入了下列模块,包含Redis,所以要使用Redis非常简单。

 要使用Redis非常简单

-- 引入Redis模块
local redis = require("resty.redis")
--创建实例
local red = redis:new() 
--建立连接
local ok, err = red:connect(“127.0.0.1”, 6379)
--调用API进行处理
ok, err = red:set("msg", "hello world")
local resp, err = red:get("msg")

这里 用local进行申请变量。 这是lua脚本 和js语言基本一致的地方。

 这个模块要保证在linux上客户端存在redis,然后运行lua脚本。

 在OpenResty  中 lualib /resty 中放在 这个目录下面的

  • 在写lua脚本时需要引用  默认就是 lualib
local redis = require("resty.redis")  
  • 创建实例  建立连接,通过new方式
--创建实例  
local red = redis:new()  
--设置超时(毫秒)  
red:set_timeout(1000)  
--建立连接  
local ip = "127.0.0.1"  
local port = 6379  
local ok, err = red:connect(ip, port)  
  •  发送数据并判断是否成功
if not ok then  
    ngx.say("connect to redis error : ", err)  
    return close_redis(red)  
end  
--调用API进行处理  
ok, err = red:set("msg", "hello world")  
if not ok then  
    ngx.say("set msg error : ", err)  
    return close_redis(red)  
end  
  • 关闭redis
local function close_redis(red)  
    if not red then  
        return  
    end  
	--释放连接(Redis连接池实现)
    local pool_max_idle_time = 10000 --毫秒  
    local pool_size = 100 --连接池大小  
    local ok, err = red:set_keepalive(pool_max_idle_time, pool_size)  
    if not ok then
        ngx.say("set keepalive error : ", err)
    end 
end 
  • 得到的数据为空处理
--得到的数据为空处理  
if resp == ngx.null then  
    resp = ''  --比如默认值
end  
ngx.say("msg : ", resp)  
  
close_redis(red) 

对于redis操作,lua脚本是没有事务的支持的。

在 nginx_lua_lib.conf配置上对应的location

请求得到数据

 使用场景 

降低tomcat的压力,非常有用的。根据不会走到web服务器上。 增大请求这些,是构建百万连接的重要一部分

 OpenResty mysql模块

也是一样的 加载 mysql的驱动

local mysql = require("resty.mysql")

创建实例

 local db, err = mysql:new()  
    if not db then  
        ngx.say("new mysql error : ", err)  
        return  
    end  

设置超时时间(毫秒) 

 db:set_timeout(1000)

配置建立连接

 local props = {  
        host = "127.0.0.1",  
        port = 3306,  
        database = "mysql",  
        user = "root",  
        password = "123456"  
    }  
      
    local res, err, errno, sqlstate = db:connect(props)  
      
    if not res then  
       ngx.say("connect to mysql error : ", err, " , errno : ", errno, " , sqlstate : ", sqlstate)  
       return close_db(db)  
    end  

删除表  

 local drop_table_sql = "drop table if exists test"  
    res, err, errno, sqlstate = db:query(drop_table_sql)  
    if not res then  
       ngx.say("drop table error : ", err, " , errno : ", errno, " , sqlstate : ", sqlstate)  
       return close_db(db)  
    end  

创建表  

 local create_table_sql = "create table test(id int primary key auto_increment, ch varchar(100))"  
    res, err, errno, sqlstate = db:query(create_table_sql)  
    if not res then  
       ngx.say("create table error : ", err, " , errno : ", errno, " , sqlstate : ", sqlstate)  
       return close_db(db)  
    end  

关闭连接

    local function close_db(db)  
        if not db then  
            return  
        end  
        db:close()  
    end  

这里有个点安全机制,防止sql注入

--防止sql注入  
    local ch_param = ngx.req.get_uri_args()["ch"] or ''  
    --使用ngx.quote_sql_str防止sql注入  
    local query_sql = "select id, ch from test where ch = " .. ngx.quote_sql_str(ch_param)  
    res, err, errno, sqlstate = db:query(query_sql)  
    if not res then  
       ngx.say("select error : ", err, " , errno : ", errno, " , sqlstate : ", sqlstate)  
       return close_db(db)  
    end  
      
    for i, row in ipairs(res) do  
       for name, value in pairs(row) do  
         ngx.say("select row ", i, " : ", name, " = ", value, "<br/>")  
       end  
    end  

 OpenResty http模块

OpenResty默认没有提供Http客户端,需要使用第三方提供,从github上搜索相应的客户端,比如lua-resty-http

cd /usr/local/openresty/lualib/resty/  
sudo wget https://raw.githubusercontent.com/pintsized/lua-resty-http/master/lib/resty/http_headers.lua 
sudo wget https://raw.githubusercontent.com/pintsized/lua-resty-http/master/lib/resty/http.lua

这个模块可以调用第三方的服务。

local http = require("resty.http")  
--创建http客户端实例
local httpc = http.new()
  
local resp, err = httpc:request_uri("http://www.baidu.com", {  
    method = "GET",  
    path = "/s?wd=123",
    headers = {
        ["User-Agent"] = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.111 Safari/537.36"  
    }  
})  
  
if not resp then  
    ngx.say("request error :", err)  
    return  
end  
  
--获取状态码  
ngx.status = resp.status  
  
--获取响应头  
for k, v in pairs(resp.headers) do  
    if k ~= "Transfer-Encoding" and k ~= "Connection" then  
        ngx.header[k] = v  
    end  
end  
--响应体  
ngx.say(resp.body)
  
httpc:close()  

Lua模板渲染器

动态web网页开发是Web开发中一个常见的场景,Lua中也有许多模板引擎,我们通过lua-resty-template来完成动态页面的渲染。

cd /usr/local/openresty/lualib/resty/
wget https://raw.githubusercontent.com/bungle/lua-resty-template/master/lib/resty/template.lua
mkdir /usr/local/openresty/lualib/resty/html
cd /usr/local/openresty/lualib/resty/html
wget https://raw.githubusercontent.com/bungle/lua-resty-template/master/lib/resty/template/html.lua

动态生成网页模板。

使用

nginx配置文件,添加模板解析路径

#首先匹配nginx下面的模板目录  
set $template_location "/templates";  
#然后匹配指定的模板根目录,我们这里是/usr/local/openresty/lua_scripts
set $template_root "/usr/local/openresty/lua_scripts";
#或者通过root指令来切换路径
root /usr/local/openresty/lua_scripts;

lua内容,html_template.lua

local template = require("resty.template")  

local context = {  
    title = "lua模板渲染",  
    name = "hash同学",  
    description = "<script>alert(1);</script>",  
    age = 20,  
    hobby = {"电影", "音乐", "阅读"},  
    score = {语文 = 90, 数学 = 80, 英语 = 70},  
    score2 = {  
        {name = "语文", score = 90},  
        {name = "数学", score = 80},  
        {name = "英语", score = 70},  
    }  
}
template.render("resty_template.html", context)

进行解析生成模板。

 Nginx非阻塞与Lua协程的绝配

服务器有个设计原则:永远不能阻塞。
nginx作为非常优秀的服务器,这点发挥的非常极致。在nginx里有很多的体现异步的地方。
Lua嵌入会阻塞吗?
-- foo本身是个协程,由nginx调用
function foo() 
-- 此处由nginx发起db的连接请求,因为异步这里先暂停协程
users = db.query('users', {name: name}); 
-- 当请求有响应得到处理时,继续执行协程,这些才运行
deal_with(users); 
end
通过nginx的异步机制和lua的协程,很容易实现这种同步写法,异步实现的模型,但对开发人
员而言,不用关心内部做了什么。

Lua协程

什么是协程?
Lua协程,称为 协作式多线程 。Lua中的协程代表一个独立的执行线程,具有自己的堆栈、自己的局部变量、PC计数器和其自
己的指令指针; 与java线程不同的是,协程只通过显式调用yield函数来暂停其执行。同一时间,多协程只运行其中一个,java多线程则可以运行多个。
协程可以处于三种不同状态之一: 暂停,运行和死亡
使用Lua协程
coroutine.create(f)
创建一个新的协同程序并返回一个类型为thread的对象,并不启动协程。
coroutine.resume(co)
开始或继续执行协同程序co
coroutine.yield
暂停执行调用协程,让出CPU
coroutine.wrap(f)
类似create方法,创建一个协程但它不是返回协程本身,而是返回一个函数,当被调用时,它恢复协程

Nginx的API生成页面

可以通过Nginx的API服务功能来实现商品页面功能设计
将需要的数据准备好,通过Lua访问Redis获取得,再由Lua生成json数据,交给Lua模板解析器,
通过Lua模板解析器,来完成模板数据的动态生成。

达到对一个商品页面的实现。

Guess you like

Origin blog.csdn.net/qq_33373609/article/details/121088124