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 |
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
获取页面参数完成数据库添加
--请求参数 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
--请求参数 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应用开发的主要内容,如果未能解决你的问题,请参考以下文章
Nginx+Lua(OpenResty)开发高性能Web应用