ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Nginx高级应用:lua + OpenResty

Nginx高级应用:lua + OpenResty

Nginx高级应用

应用场景:

该技术不仅仅是应用于, 广告缓存只是常用于做广告的缓存!

广告缓存载入与读取

对于一个应用来多,每天都会有很多的用户来访问, 那么访问最多的就是首页了!

而对于首页这种,高访问,且页面数据并不是,经常的变化!
为了减轻服务器的压力,直接将其制作成一个静态的页面进行展示!

基本流程:

  • Nginx都学习过了:可以通过反向代理实现,网关负载均衡的 服务器!
    用户发送请求,首先通过 nginx , 通过nginx代理,负载均衡,请求对应的网关…模块。
  • 本次就是在用户登录/常用的操作, 请求时经过 nginx请求,在nginx中完成 调用redis数据操作,直接返回结果!
    但,niginx并不具备编程语言的特性if else逻辑判断,访问数据库 redis; 它只是一个负载均衡器;
    所以,需要通过lua 嵌入式语言来完成
  • 首先访问nginx ,我们可以采用缓存的方式,先从nginx本地缓存中获取,获取到直接响应
  • 如果没有获取到,再次访问redis,我们可以从redis中获取数据,如果有 则返回,并缓存到nginx中
  • 如果没有获取到,再次访问mysql
    我们从mysql中获取数据,再将数据存储到redis中。
  • 而这里面,我们都可以使用LUA脚本嵌入到程序中执行这些查询相关的业务。

Lua

简介:

  • Lua 是一种轻量小巧的脚本语言,用标准C语言编写并以源代码形式开放c 作为底层语言,写的东西都快!
    其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。

特性

  • 轻量级:
    使用 C语言编写并以源代码形式开放,编译后仅仅一百余K,可以很方便的嵌入别的程序里。
  • 可扩展:
    ** Lua提供了非常易于使用的扩展接口和机制**内置了大量开发好的,脚本文件 mysql redis....
    (通常是C或C++)提供这些功能,Lua可以使用它们,就像是本来就内置的功能一样。
  • 其它特性:
    支持面向过程(procedure-oriented)编程和函数式编程(functional programming)
    自动内存管理;只提供了一种通用类型的表(table)实现数组,哈希表,集合,对象
    通过闭包和table可以很方便地支持面向对象编程所需要的一些关键机制,比如数据抽象,虚函数,继承和重载等

应用场景

游戏开发,独立应用脚本,Web 应用脚本,扩展和数据库插件如:MySQL Proxy 和 MySQL WorkBench

安装

并不需要安装,windows 只需要配置一下系统的环境变量即可:环境变量也只是为了可以在系统的任意对方可以使用!
Path:D:\WSMwork\lua(本人的安装路径
**lua53 或 lua53 -i **都可以启动lua

入门Lua

创建hello.lua文件

打印:print("输出内容!");

  • 创建文件,输入print("hello");
  • 直接 cmd 运行:

执行命令:lua53 文件名.lua

LUA的基本语法(了解)

介绍:

  • lua有交互式编程和脚本式编程:

  • 交互式编程就是 启动lua直接输入语法,就能直接执行。

  • 脚本式编程需要编写脚本,然后再执行命令 执行脚本才可以。
    一般采用脚本式编程。(例如:编写一个hello.lua的文件,输入文件内容,并执行lua53 hell.lua即可)
    lua53 不同版本的lua 执行要指定版本号!

  • 语法都是一样的,只是操作方式不同…下面了解lua 基本语法!

脚本式编程创建 .lua文件, 进行执行...

注释:

hello.lua

-- 单行注释--[[ 多行注释 多行注释 ]]--

定义变量

同Java 一样有,全局变量和局部变量:

  • 全局整个文件可以使用!
  • 局部指定方法内可以使用!
    hello.lua
-- 全局变量赋值a=1-- 局部变量赋值localb=2-- ..拼接print(a..b)print(a..':'..b)

a='张三'字符可以使用,单引号 或 双引号来表示,utf-8可能会乱码,建议文件编码 ANSl
如果变量没有初始化:则 它的值为nil 这和java中的null不同。

案例


数值运算还是通过+ - * /

Lua中的数据类型

  • Lua 是动态类型语言,变量不要类型定义,只需要为变量赋值。
  • 值可以存储在变量中,作为参数传递或结果返回。
  • Lua 中有 8 个基本类型分别为:
    nil、boolean、number、string、userdata、function、thread 和 table。

    type( 值 ) 返回值的类型
print(type("Hello world"))--> stringprint(type(10.4*3))--> number 包含double intprint(type(print))--> functionprint(type(type))--> functionprint(type(true))--> booleanprint(type(nil))--> nil

流程控制

if语句类似于Orcal的存储过程!

  • if 语句 由一个布尔表达式作为条件判断,其后紧跟其他语句组成。
-- 条件括号,可选:加或不加if(布尔表达式)then--[ 在布尔表达式为 true 时执行的语句 --]end

if…elseif…eles…end语句

  • if 语句可以与 elseif 语句, else搭配使用, 在 if 条件表达式为 false 时执行 else 语句代码块。

if——else

if(布尔表达式)then--[ 布尔表达式为 true 时执行该语句块 --]else--[ 布尔表达式为 false 时执行该语句块 --]end

if——esleif——elseif——else——end

localage=15if(age>=18)thenprint('成年人')elseifage<18thenprint('未成年')elseprint('未出生')end
测试:

循环

while循环[满足条件就循环] do … end

  • while 循环语句在判断条件为 true 时会重复执行循环体语句。
while(条件true执行)doprint(‘执行循环内容…’)end

for循环

  • for 循环语句可以重复执行指定语句,重复次数可在 for 语句中控制。
forvar=exp1,exp2,exp3doprint(‘执行循环内容…’)end--[[ var 从 exp1 变化到 exp2 每次变化以 exp3 为步长递增 var 并执行一次 “执行体”。 exp3 是可选的,如果不指定,默认为1。 ]]--

repeat…until语句[满足条件结束]

  • repeat…until 循环语句不同于 for 和 while循环
  • for 和 while 循环的条件语句在当前循环执行开始时判断repeat 无论如何都会执行一次until 条件成立true则结束循环!
repeatprint(‘执行循环内容…’)until(true退出循环)

函数

  • lua中也可以定义函数,类似于java中的方法。
--[[ 函数返回两个值的最大值 --]]functionmax(num1,num2)if(num1>num2)thenresult=num1;elseresult=num2;endreturnresult;end-- 调用函数print("两值比较最大值为 ",max(10,4))print("两值比较最大值为 ",max(5,6))

执行之后的结果:

表table

  • table 是 Lua 的一种数据结构用来帮助我们创建不同的数据类型,如:数组、字典、对象等。
-- 初始化表mytable = {}-- 指定值mytable[1]= "Lua"-- 移除引用mytable = nil

测试Demo

table={}table[0]="张三1"table[1]="张三2"table[2]="张三3"--table 是一个对象print(table)--通过下标来获取值print(table[0])--table = nil 来清空表/对象table=nuiprint(table)print(table[0])

模块

模块定义

  • 模块类似于一个封装库
    Lua 5.1 开始,Lua 加入了标准的模块管理机制,可以把一些公用的代码放在一个文件里;
    以 API 接口的形式在其他地方调用,有利于代码的重用和降低代码耦合度。

创建一个文件叫module.lua

别忘了中文乱码,文件格式设置ASCII码
module.lua

-- 文件名为 module.lua-- 定义一个名为 module 的模块,和文件名一样!module={}-- 定义一个常量module.constant="这是一个常量"-- 定义一个函数 module.func1functionmodule.func1()print("这是一个公有函数")end-- 定义一个局部(私有) 方法-- 因此是不能从外部访问模块里的这个私有函数,必须通过模块里的公有函数来调用localfunctionfunc2()print("这是一个私有函数!")end-- 模拟调用私有方法...functionmodule.func3()func2()endreturnmodule
  • 模块的结构就是一个 table 的结构,
  • 因此可以像操作调用 table 里的元素那样来操作调用模块里的常量或函数。

require 函数

require用于引入其他的模块,类似于java中的类要引用别的类的效果。
要引入的模块,要放在一个文件夹下,可以是子文件!不然找不到!

require("<模块名>")-- 或require"<模块名>"

OpenResty

简介

  • OpenResty® 是一个基于 Nginx 与 Lua 的高性能 Web 平台
    内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。
    用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。
  • 简单来说: OpenResty 就是 Niginx,集成了Lua的Niginx 使其具备了编程特性
    由 OpenResty 团队自主开发)
    OpenResty下载

系统配置

目录结构:

D:\WSMwork\openresty-1.15.8.2-win64\lualib\resty 路径下包含了很多的lua模块

配置:

配置lua库位置:修改openresty-1.15.8.2-win64\conf\nginx.conf文件

接下来的大部分操作都是在: 自定义的lua.conf文件中执行

lua.conf

自定义的lua.conf

#给nginx 分内存 128m 兆 缓存大小!用于存放热点数据(频繁使用的数据!)lua_shared_dict my_cache 128m;#nginx服务server{listen9090;#指定端口9090,默认80server_name _;#静态模板地址设置...set$template_location"/templates";set$template_root"D:/templates";#填写对应的地址...#nginx分流的请求路径...location /mylua{default_type text/html;#通过 content_by_lua_block{ lua的执行代码 } ngx.say('是lua的浏览器输出方法!')content_by_lua_block{localage=20ifage>=18thenngx.say('>=18');elsengx.say('<18');end}}}
  • 这就是一个正常的 nginx的配置代码:
  • content_by_lua_block{ }lua的执行操作就写在这里是nginx拥有了编程语言的特性!
    OpenResty1.9.3.2以前是content_by_lua{ }可通过nginx -v查看版本号!
  • ngx.say(‘是lua的浏览器输出方法!’)就像Java的 Out打印流一样!

启动/刷新 nginx

启动命令

OpenResty目录下cmd

  • nginx.exe没有异常就是执行成功!

重启刷新命令

当修改了文件,频繁调用 nginx.exe是启动,开启进行造成堵塞!正确的方法是刷新,重启
OpenResty目录下cmd

  • nginx.exe -s reload

测试

nginx端口占用,启动报错:bind() to 0.0.0.0:80 failed (10013: An attempt was made to access a …)

  • nginx.conf文件下还有默认的 80端口启动.. 可能会与电脑里某个端口冲突, 直接更改即可!
    或者:停止进程Windows操作!
    netstat -aon | findstr :8080表示端口
    tasklist|findstr "进程号"

OpenResty引入lua 文件:

在nginx 配置中写lua 代码不方便书写,可以直接在外面写好,调用即可!

location /mylua2{default_type text/html;content_by_lua_file D:/WSMwork/myweb.lua;}#content_by_lua_file 引入外部的lua文件, 分号结尾;

content_by_lua_file

引入外部的.lua 文件

定义一个文件 myweb.lua外部文件~刷新nginx

openresty中发起http请求

  • 有些场景是需要nginx在进行请求转发这样就需要nginx需要有发起http请求的能力
  • nginx服务发起http请求区分内部请求 和 外部请求

为了方便测试,启动一个微服来查看

lua 脚本

myweb.lua

--设置编码格式!ngx.header.content_type="text/html;charset=utf8"--调用ngx 模块中的方法,返回请求urllocaluri_args=ngx.req.get_uri_args()--获取请求参数!localzz=uri_args["zz"]--获取http 模块,require( );localhttp=require("resty.http")--http模块 new( ) 一个httpc对象localhttpc=http.new()--..拼接参数 wdlocalresp=httpc:request_uri("http://127.0.0.1:9002/info/hello/"..zz,{method="GET",--get请求;keepalive=false--无论如何都请求;})--返回参结果,打印结果!localval=resp.body ngx.say(val)

测试

注意这里的参数是在 nginx,9090端口的请求参数!

require(“resty.http”)

就是这个lua模块负载发送请求,
OpenResty 中存在着很多的,lua脚本模块…

获取POST请求参数

跟get 非常类似…

ngx.req.read_body()--获取请求urllocalarg=ngx.req.get_post_args()--参数集合 Map类型!--声明几个用于存储的空对象id=nildeptname=nil--循环参数集合!fork,vinpairs(arg)do--判断参数,key 进行参数赋值!ifk=="id"thenid=vendifk=="deptname"thendeptname=vendend--页面输出!ngx.say("deptname: ",deptname)ngx.say("id: ",id)

使用postman 进行Post请求:

openresty中使用redis模块

  • 在一些高并发的场景中,我们常常会用到缓存技术
  • 现在我们常用的分布式缓存redis是最知名的
    我们需要引入redis模块 require “resty.redis”

lua 脚本

myweb.lua

-- 定义私有方法localfunctionclose_redis(red)ifnotredthenreturnend-- 释放连接(连接池实现),毫秒localpool_max_idle_time=10000-- 连接池大小localpool_size=100localok,err=red:set_keepalive(pool_max_idle_time,pool_size)locallog=ngx_logifnotokthenlog(ngx_ERR,"set redis keepalive error : ",err)endend-- 连接redislocalredis=require('resty.redis')--引入模块localred=redis.new()red:set_timeout(1000)localip="127.0.0.1"localport="6379"-- local pwd= "ok" 本人没有密码直接注释!-- 连接测试 ip 端口是否正确...ok,err=red:connect(ip,port)ifnotokthenngx.say("failed to auth: ",err)returnclose_redis(red)end-- 密码是否正确 没有密码直接注释!--[[ ok, err = red:auth(pwd) if not ok then ngx.say("failed to auth: ", err) return close_redis(red) end ]]----获取redis第一个库,redis一共16给库red:select('0')red:set("msg","test ngx hello")--set存入一个msg key,值:test ngx hello-- get根据key查找... 并处理异常localresp,err=red:get("msg")ifnotrespthenngx.say("get msg error : ",err)returnclose_redis(red)end--打印关闭资源!ngx.say("msg : ",resp)close_redis(red)

记得要启动redis 哦!

安装目录下:redis-server.exe

查看所以key

测试:

直接网关请求!

openresty中操作mysql

数据库

既然要数据库操作,那么就搞个数据库吧!mysql

lua 脚本

myweb.lua

--创建连接mysql模块localmysql=require"resty.mysql"-- connect to mysql;localdb,err=mysql:new()ifnotdbthenreturnfalse--没连上关闭!end--超时时间db:set_timeout(1000)--设置连接信息localok,err,errno,sqlstate=db:connect{host="127.0.0.1",--ipport=3306,--端口database="book",--连接的数据库user="root",--用户password="ok",--密码max_packet_size=1024*1024--字节大小 1m}--验证是否连接上ifnotokthenngx.say("connect mysql failed")returnfalse--没连上关闭endifdb==falsethenngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)returnendngx.say("----------查询数据----------------","<br/>")res,err,errcode,sqlstate=db:query("SELECT * FROM bookinfo")--实际情况,可能还需要拼接参数....--没有数据,返回...异常!ifnotresthenngx.say("bad result: ",err,": ",errcode,": ",sqlstate,".")returnend--获取JSON 模块localcjson=require"cjson"--JSON翻译,页面输出ngx.say("result: ",cjson.encode(res))--如果是新增...--[[ --如果没有返回结果,新增异常提示! if not res then ngx.say("insert failed") return end --返回影响行数 ngx.say("insert rows :", res.affected_rows,", id", res.insert_id, "<br/>") ]]--

结果测试:

扩展:

  • 推荐使用 nginx 引用外部的lua使用
    方便排错,如果出现错误,直接控制台运行lua 脚本即可!
    结构清晰…
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进