Lua+OpenResty应用开发

Posted zJ的架构笔记

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Lua+OpenResty应用开发相关的知识,希望对你有一定的参考价值。

openresty中使用json模块

web开发过程中,经常用的数据结构为json,openresty中封装了json模块,我们看如何使用


一)如何引入cjson模块,需要使用require

local json = require("cjson")

json.encode 将表格数据编码为 JSON 字符串

格式:

jsonString = json.encode(表格对象)

用法示例:

table 包含哈希键值对 和 数组键值对


test.lua

table包含哈希键值对时,数组键值将被转换为字符串键值

local json = require("cjson")

ngx.say(json.encode(t));



table所有键为数组型键值对时,会当作数组看待,空位将转化为null

local str = json.encode({[3]=1,[5]=2,[6]="3",[7]=4})
ngx.say(str); ---- [null,null,1,null,2,"3",4]
ngx.say("<br/>");

json.decode 将JSON 字符串解码为表格对象

格式:

table = json.decode(string)

用法示例:


local str  = [[ {"a":"v","b":2,"c":{"c1":1,"c2":2},"d":[10,11],"1":100} ]]

local t    = json.decode(str)

ngx.say(" --> ", type(t))


openresty中发起http请求

有些场景是需要nginx在进行请求转发

用户浏览器请求url访问到nginx服务器,但此请求业务需要再次请求其他业务;

如用户请求订单服务获取订单详情,可订单详情中需要返回商品信息,也就需要再请求商品服务获取商品信息;

这样就需要nginx需要有发起http请求的能力,而不是让用户浏览器再次请求商品信息nginx服务发起http请求区分内部请求 和 外部请求


resty.http ,从可实现外部请求,而且使用很方便

local uri_args = ngx.req.get_uri_args()

local wd = uri_args["wd"]

local http = require("resty.http")

local httpc = http.new()

local resp = httpc:request_uri("http://t.weather.sojson.com/api/weather/city/"..wd,{

method = "GET",

keepalive=false

})

local val = resp.body

ngx.say(val)

获取POST请求参数

ngx.req.read_body()

local arg = ngx.req.get_post_args()

name=nil

pwd=nil

for k,v in pairs(arg) do

    if k=="name" then

       name=v

    end

    if k=="pwd" then

       pwd = v

    end

end

ngx.say("name : ", name)

ngx.say("pwd : ", pwd

Lua+OpenResty应用开发

openresty中使用redis模块

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

local function close_redis(red)

    if not red then

        return

    end

    -- 释放连接(连接池实现),毫秒

    local pool_max_idle_time = 10000

    -- 连接池大小

    local pool_size = 100

    local ok, err = red:set_keepalive(pool_max_idle_time, pool_size)

    local log = ngx_log

    if not ok then

        log(ngx_ERR, "set redis keepalive error : ", err)

    end

end

 

-- 连接redis

local redis = require('resty.redis')

local red = redis.new()

red:set_timeout(1000)

 

local ip = "127.0.0.1"

local port = "6379"

local pwd= "ok"

ok = red:connect(ip,port)

 

if not ok then

    ngx.say("failed to auth: ", err)

    return close_redis(red)

end

if not ok then

    return close_redis(red)

end

ok = red:auth(pwd)

if not ok then

    ngx.say("failed to auth: ", err)

    return close_redis(red)

end

 

red:select('0')

red:set("msg","test ngx hello")

local resp = red:get("msg")  

if not resp then  

    ngx.say("get msg error : ", err)  

    return close_redis(red)  

end

ngx.say("msg : ", resp)

close_redis(red)

openresty中操作mysql

Lua+OpenResty应用开发

获取页面参数完成数据库添加

Lua+OpenResty应用开发

--请求参数

ngx.req.read_body()

local arg = ngx.req.get_post_args()

deptname=nil

--获取数据

for k,v in pairs(arg) do

if k=="deptname" then

deptname = v

end

end

 

ngx.say("deptname : ", deptname)

 

--创建连接mysql模块

local mysql = require "resty.mysql"

-- connect to mysql;

local db, err = mysql:new()

if not db then

return false

end

db:set_timeout(1000)

--设置连接信息

local ok, err, errno, sqlstate = db:connect{

host = "127.0.0.1",

port = 3306,

database = "bye",

user = "root",

password = "ok"

}

--验证是否连接上

if not ok then

ngx.say("connect mysql failed")

return false

end

 

if db == false then

    ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)

    return

end

 

ngx.say("----------插入数据部门----------------","<br/>")

--执行添加

res, err, errcode, sqlstate =

    db:query("insert into dept values (null,'"..deptname.."')")

if not res then

    ngx.say("insert failed")

    return

end

ngx.say("insert rows :", res.affected_rows,", id", res.insert_id, "<br/>"

查询数据并返回json

Lua+OpenResty应用开发


--请求参数

ngx.req.read_body()

local arg = ngx.req.get_post_args()

deptname=nil

--获取数据

for k,v in pairs(arg) do

if k=="deptname" then

deptname = v

end

end

ngx.say("deptname : ", deptname)

--创建连接mysql模块

local mysql = require "resty.mysql"

-- connect to mysql;

local db, err = mysql:new()

if not db then

return false

end

db:set_timeout(1000)

--设置连接信息

local ok= db:connect{

host = "127.0.0.1",

port = 3306,

database = "bye",

user = "root",

password = "ok",

max_packet_size = 1024 * 1024

}

--验证是否连接上

if not ok then

ngx.say("connect mysql failed")

return false

end

 

if db == false then

    ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)

    return

end

 

ngx.say("----------根据名称查询数据部门----------------","<br/>")

 

res =

    db:query("SELECT * FROM `dept` WHERE deptname LIKE '%"..deptname.."%'")

if not res then

    ngx.say("bad result: ", err, ": ", errcode, ": ", sqlstate, ".")

    return

end

 

local cjson = require "cjson"

ngx.say("result: ", cjson.encode(res))


广告缓存的载入与读取

需求分析

需要在页面上显示广告的信息。

Lua+Nginx配置

(1)实现思路-查询数据放入redis中

实现思路:

定义请求:用于查询数据库中的数据更新到redis中。

a.连接mysql ,按照广告分类ID读取广告列表,转换为json字符串。

b.连接redis,将广告列表json字符串存入redis 。

定义请求:

请求:

/update_content

参数:

id  --指定广告分类的id

返回值:

json

创建/root/lua目录,在该目录下创建update_content.lua:目的就是连接mysql 查询数据 并存储到redis中。

代码如下:

ngx.header.content_type="application/json;charset=utf8"

local cjson = require("cjson")

local mysql = require("resty.mysql")

local uri_args = ngx.req.get_uri_args()

local id = uri_args["id"]


local db = mysql:new()

db:set_timeout(1000)

local props = {

    host = "127.0.0.1",

    port = 3306,

    database = "shop_content",

    user = "root",

    password = "ok"

}


local res = db:connect(props)

local select_sql = "select url,pic from tb_content where status ='1' and category_id="..id.." order by sort_order"

res = db:query(select_sql)

db:close()


local redis = require("resty.redis")

local red = redis:new()

red:set_timeout(2000)


local ip ="127.0.0.1"

local port = 6379

red:connect(ip,port)

red:auth("ok")

red:set("content_"..id,cjson.encode(res))

red:close()


ngx.say("{flag:true}")


(2)实现思路-从redis中获取数据

实现思路:

加入openresty本地缓存

但是如果请求都到redis,redis压力也很大,所以我们一般采用多级缓存的方式来减少下游系统的服务压力。参考基本思路图的实现。

先查询openresty本地缓存 如果 没有

再查询redis中的数据,如果没有

再查询mysql中的数据,但凡有数据 则返回即可。

定义请求:

请求:/read_content

参数:id

返回值:json

定义lua缓存命名空间,修改nginx.conf,添加如下代码即可:

ngx.header.content_type="application/json;charset=utf8"

local uri_args = ngx.req.get_uri_args();

local id = uri_args["id"];

--获取本地缓存

local cache_ngx = ngx.shared.my_cache;

--根据ID 获取本地缓存数据

local contentCache = cache_ngx:get('content_cache_'..id);

 

if contentCache == "" or contentCache == nil then

    local redis = require("resty.redis");

    local red = redis:new()

    red:set_timeout(2000)

    red:connect("127.0.0.1", 6379)

red:auth("ok")

    local rescontent=red:get("content_"..id);

    if ngx.null == rescontent then

        local cjson = require("cjson");

        local mysql = require("resty.mysql");

        local db = mysql:new();

        db:set_timeout(2000)

        local props = {

            host = "127.0.0.1",

            port = 3306,

            database = "changgou_content",

            user = "root",

            password = "ok"

        }

        local res = db:connect(props);

        local select_sql = "select url,pic from tb_content where status ='1' and category_id="..id.." order by sort_order";

        res = db:query(select_sql);

        local responsejson = cjson.encode(res);

        red:set("content_"..id,responsejson);

        ngx.say(responsejson);

        db:close()

    else

        cache_ngx:set('content_cache_'..id, rescontent, 10*60);

        ngx.say(rescontent)

    end

    red:close()

else

    ngx.say(contentCache)

end

nginx限流

一般情况下,首页的并发量是比较大的,即使 有了多级缓存,当用户不停的刷新页面的时候,也是没有必要的,另外如果有恶意的请求 大量达到,也会对系统造成影响。而限流就是保护措施之一。

生活中限流对比

  • 水坝泄洪,通过闸口限制洪水流量(控制流量速度)。

  • 办理银行业务:所有人先领号,各窗口叫号处理。每个窗口处理速度根据客户具体业务而定,所有人排队等待叫号即可。若快下班时,告知客户明日再来(拒绝流量)

  • 火车站排队买票安检,通过排队 的方式依次放入。(缓存带处理任务)

 nginx的限流

nginx提供两种限流的方式:

  • 一是控制速率

  • 二是控制并发连接数

local function close_redis(red)

    if not red then

        return

    end

    -- 释放连接(连接池实现),毫秒

    local pool_max_idle_time = 10000

    -- 连接池大小

    local pool_size = 100

    local ok, err = red:set_keepalive(pool_max_idle_time, pool_size)

    local log = ngx_log

    if not ok then

        log(ngx_ERR, "set redis keepalive error : ", err)

    end

end

 

-- 连接redis

local redis = require('resty.redis')

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

    return close_redis(red)

end

red:select('0')

 

local clientIP = ngx.req.get_headers()["X-Real-IP"]

if clientIP == nil then

   clientIP = ngx.req.get_headers()["x_forwarded_for"]

end

if clientIP == nil then

   clientIP = ngx.var.remote_addr

end

 

local incrKey = "user:"..clientIP..":freq"

local blockKey = "user:"..clientIP..":block"

 

local is_block,err = red:get(blockKey) -- check if ip is blocked

if tonumber(is_block) == 1 then

    ngx.exit(403)

    close_redis(red)

end

 

inc  = red:incr(incrKey)

 

if inc < 10 then

   inc = red:expire(incrKey,1)

end

-- 每秒10次以上访问即视为非法,会阻止1分钟的访问

if inc > 10 then

    --设置block 为 True 为1

    red:set(blockKey,1)

    red:expire(blockKey,60)

end

 

close_redis(red


以上是关于Lua+OpenResty应用开发的主要内容,如果未能解决你的问题,请参考以下文章

安装OpenResty(Nginx+Lua)开发环境

OpenResty(Nginx+Lua)开发入门

Nginx+Lua(OpenResty)开发高性能Web应用

openresty开发系列13--lua基础语法2常用数据类型介绍

OpenResty与nginx结合执行lua脚本

openresty开发系列15--lua基础语法4表table和运算符