Nginx Lua编程-实战案例 nginx luci
liebian365 2024-10-29 15:54 25 浏览 0 评论
案例一:Nginx+Redis进行分布式访问统计
得益于Nginx的高并发性能和Redis的高速缓存,基于Nginx+Redis实现的受访统计架构设计比纯Java实现的受访统计架构设计,在性能上高处很多。
参考案例:统计【http://localhost/visitCount】接口在10秒内的访问次数。
在Nginx的配置文件中,定义一个location匹配 /visitCount,并定义一个变量count,用于接收Lua脚本返回的从Redis中获取的访问次数。
location /visitCount {
#访问次数
set $count 0;
access_by_lua_file luaScripe/redis/RedisVisitCount.lua;
echo "10s内访问次数:" $count;
}
RedisVisitCount.lua脚本中的逻辑如下:
- 根据接口划分存入Redis中的key为demo:visitCount;
- 每次访问时,访问次数加+1,使用Redis的incr(key)实现;
- 每次访问时,判断Redis中demo:visitCount的value是否为1,若为1,则设置demo:visitCount10秒自动过期
-- 引入自定义Redis操作模块
local redisOpe = require("luaScript.redis.RedisOperator");
-- 创建自定义的Redis操作对象
local red = redisOpe:new();
-- 获取Redis连接
red:open();
-- 获取访问次数
local visitCount = red:incrValue("demo:visitCount");
if visitCount == 1 then
-- 设置10秒过期
red:expire("demo:visitCount", 10);
end
-- 将访问次数设置到Nginx变量中
ngx.var.count = visitCount;
-- 连接归还到连接池
red:clost();
重启Nginx后,浏览器访问http://localhost/visitCount,显示访问的统计次数,10秒内多次刷新,发现统计次数累加,10秒后重新开始计算
案例二:Nginx+Redis+Java容器实现高并发访问
在不需要高速访问的场景下,运行在后端的Java容器会直接从DB数据库中查询数据,然后返回给客户端。但受限于数据库连接数限制、网络传输延迟、数据库的IO频繁等多方面原因,后端Java容器(Tomcat、)直接查询DB的性能会很低,此时对架构进行调整,采用"Java容器+Redis+DB"的查询架构,对数据一致性要求不是特别高但访问频繁 的API接口,可以将DB数据放入Redis缓存中,Java API优先查询Redis,若缓存未命中,则回源到DB查询,最后将成功的查询结果更新到Redis缓存中,此种架构利用Redis分流了大量的查询请求,极大提升了API接口的处理性能,请求架构如下:
但是,常用的后端Java容器的性能其实不高,QPS性能指标一般在1000以内,而Nginx的性能是Java容器的10倍左右,且性能更稳定、还不存在FullGC卡顿。为了应对高并发场景,可以将"Java容器+Redis+DB"架构调整为"Nginx+Redis+Java容器"查询架构。新架构将后端Java容器的缓存查询、缓存判断前移到反向代理Nginx,通过Nginx直接进行Redis缓存查询、缓存判断。如此,不仅为Java容器减少了很多请求,而且充分发挥了Nginx的高并发优势和稳定性优势,请求处理架构如下
下面实现一个"Nginx+Redis+Java"查询架构的案例:可以用于秒杀系统
首先定义两个接口
- 模拟Java容器的商品查询接口:/java/goods/detail
- 模拟供外部调用的商品查询接口:/goods/detail
然后提供一个Lua操作缓存的类RedisCacheOpe,定义三个方法:
- getCache(self, goodsId):根据商品ID获取商品信息;
- goUpstream(self):通过capture内部请求访问上游接口获取商品数据
- setCache(self, goodsId, goodsStr):设置商品缓存,此方法用于模拟后台Java代码。
缓存操作类RedisCacheOpe核心代码如下:
---
--- Generated by EmmyLua(https://github.com/EmmyLua)
--- Created by xx
--- DateTime: 2022/2/19 下午12:50
---
--导入自定义的基础块
local basic = require("luaScript.module.common.basic");
local redisOp = require("luaScript.redis.RedisOperator");
local PREFIX = "GOODS_CACHE_";
local _RedisCacheOpe = { };
_RedisCacheOpe.__index = _RedisCacheOpe;
-- 类的方法
function _RedisCacheOpe.new(self)
local object = {};
setmetatable(object, self);
return object;
end
-- Nginx服务器中使用Lua获取get或post请求参数
function _RedisCacheOpe.getParam(self, paramName)
local request_method = ngx.var.request_method;
local args = nil;
if "GET" == request_method then
args = ngx.req.get_uri_args();
elseif "POST" == request_method then
ngx.req.read_body();
args = ngx.req.get_post_args();
end
if not args then
return nil;
end
return args[paramName];
end
-- 获取缓存数据
function _RedisCacheOpe.getCache(self, goodsId)
local red = redisOp:new();
--打开连接
if not red:open() then
basic:error("Redis连接失败");
return nil;
end
local json = red:getValue(PREFIX..goodsId);
red:close();
if not json or json == ngx.null then
basic:log("商品【"..goodsId.."】没有命中缓存");
return nil;
end
basic.log("商品【"..goodsId.."】成功命中缓存")
return json;
end
-- 设置缓存数据,模拟Java后台操作
function _RedisCacheOpe.setCache(self, goodsId, goodsStr)
local red = redisOp:new();
if not red:open() then
basic.error("Redis连接失败")
return nil;
end
red:setValue(PREFIX..goodsId, goodsStr);
red:expire(PREFIX..goodsId, 60);
basic:log(goodsId.."缓存设置成功");
-- 归还Redis连接
red:close();
return json;
end
-- 优先从缓存获取,否则访问上游接口
function _RedisCacheOpe.goUpstream(self)
local request_method = ngx.var.request_method;
local args = nil;
if "GET" == request_method then
args = ngx.req.get_uri_args();
elseif "POST" == request_method then
ngx.req.read_body();
args = ngx.req.get_post_args();
end
-- 回源上游接口,
local res = ngx.location.capture("/java/goods/detail", {
method = ngx.HTTP_GET,
args = args
});
basic:log("上游数据获取成功");
--返回上游接口的响应体
return res.body;
end
return _RedisCacheOpe;
Nginx配置文件通过location配置块,来使用上述Lua脚本:
# Nginx+Redis+Java架构案例
location = /goods/detail {
content_by_lua_block {
local goodsId = ngx.var.arg_goodsId;
if not goodsId then
ngx.say("请输入goodsId");
return;
end
local RedisCacheOpe = require("luaScript.redis.RedisCacheOpe");
local redisCache = RedisCacheOpe:new();
local json = redisCache:getCache(goodsId);
-- 判断缓存是否被命中
if not json then
ngx.say("未命中缓存,回源到上游接口", "<br>");
json = redisCache.goUpstream();
else
ngx.say("缓存已被命中!", "<br>");
end
ngx.say("商品信息:", json);
}
}
location = /java/goods/detail {
internal;
content_by_lua_block {
local RedisCacheOpe = require "luaScript.redis.RedisCacheOpe";
-- 模拟Java后台从DB中查询的数据
local json = "{goodsId:商品ID, goodsName:商品名称}";
-- 将商品信息缓存到Redis
local redisCache = RedisCacheOpe:new();
redisCache:setCache(ngx.var.arg_goodsId, json);
-- 返回商品到下游网关
ngx.say(json);
}
}
浏览器中第一次访问http://localhost/goods/detail?goodsId=33,显示缓存未命中
再次刷新浏览器:
案例三:Nginx+Redis实现黑名单拦截
实现IP黑名单拦截可以有以下途径:
- 操作系统层,配置iptables防火墙规则,拒绝黑名单中IP的网络请求;
- 使用Nginx网关的deny配置指令,拒绝黑名单中IP的网络请求;
- 在Nginx网关的access阶段,通过Lua脚本检查客户端IP是否在黑名单中;
- 在Spring Cloud的内部网关(zuul)的过滤器中检查客户端IP是否在黑名单中
以上检查方式都是基于静态的、提前准备好的黑名单进行的。在系统实际运行中,黑名单往往需要动态计算,系统需要动态识别出大量发起请求的恶意爬虫或者恶意用户,并且将这些恶意请求的IP放入一个动态的IP黑名单中。
Nginx网关可以依据动态黑名单内的IP进行请求拦截并拒绝服务,这里结合Nginx+Redis提供一个基于动态IP黑名单进行请求拦截的实现。假设黑名单IP已经生成并且定期更新到Redis中,Nginx网关可以直接从Redis获取计算好的IP黑名单,单位了提升黑名单的读取速度,并不是每一次请求都从Redis读取IP黑名单,而是从本地的共享内存black_ip_list中获取,
以下为"Nginx+Redis"实现黑名单拦截的参考实现:定义一个black_ip_filter.lua
---
--- Generated by EmmyLua(https://github.com/EmmyLua)
--- Created by xx.
--- DateTime: 2022/2/19 下午2:54
---
-- 导入自定义模块
local basic = require("luaScript.module.common.basic")
local redisOp = require("luaScript.redis.RedisOperator")
local ip = basic.getClientIP();
basic.log("获取到客户端IP: "..ip);
basic.log("首先获取Nginx共享内存中的黑名单列表,并判断...")
-- 此处获取Nginx配置文件中定义的共享内存内存变量:black_ip_list
local black_ip_list = ngx.shared.black_ip_list;
basic.log("Nginx共享内存中的黑名单列表【black_ip_list】 type is "..type(black_ip_list))
basic.log("Nginx共享内存中的黑名单列表【black_ip_list】: "..basic.tableToStr(black_ip_list));
-- 获取本地缓存的刷新时间
local last_update_time = black_ip_list:get("last_update_time");
basic.log("Nginx共享内存中的黑名单列表last_update_time type is "..type(last_update_time))
basic.log("Nginx共享内存中的黑名单列表last_update_time: "..basic.toStringEx(last_update_time))
if last_update_time ~= nil then -- last_update_time不等于nil
basic.log("last_update_time不等于nil...")
local now = ngx.now();
basic.log("now() result type is ".. type(now)..", and now = "..now);
local dif_time = ngx.now() - last_update_time;
basic.log("dif_time = "..dif_time);
if dif_time < 60 then --缓存1分钟,未过期
if black_ip_list:get(ip) then -- 命中Nginx本地缓存的黑名单
basic.log("IP: "..ip.."命中Nginx本地缓存的黑名单!")
return ngx.exit(ngx.HTTP_FORBIDDEN);
end
return;
end
end
basic.log("未命中Nginx本地缓存黑名单,继续判断是否命中Redis中的黑名单...")
local KEY = "limit:ip:blacklist";
local red = redisOp:new();
red:open();
local ip_blacklist = red:getSmembers(KEY);
red:close();
basic.log("Redis缓存中的黑名单列表【ip_blacklist】 type is "..type(ip_blacklist))
basic.log("Redis缓存中的黑名单列表【ip_blacklist】: "..basic.tableToStr(ip_blacklist));
basic.log("Redis缓存中的黑名单列表【ip_blacklist】长度为: "..basic.table_length(ip_blacklist))
if basic.table_length(ip_blacklist) == 0 then
--此处这么写有问题,ip_blacklist是table,not ip_blacklist 不能判断出table是空的,改为使用table的长度来获得
--但是使用table.getn(tableName)方法只能获得有序table的大小,对于无序table,需要遍历累加获得长度
--if not ip_blacklist then
basic.log("Redis缓存中的黑名单列表为空,不拦截...")
basic.log("black ip set is null");
return;
else
basic.log("Redis缓存中的黑名单列表不为空,刷新本地缓存...")
--刷新本地缓存
black_ip_list:flush_all();
basic.log("同步Redis黑名单到本地缓存...")
for i, ip in ipairs(ip_blacklist) do
basic.log("第"..i.."个IP:"..ip.."同步成功");
black_ip_list:set(ip, true);
end
basic.log("设置本地缓存的最新更新时间")
black_ip_list:set("last_update_time", ngx.now());
end
if black_ip_list:get(ip) then
return ngx.exit(ngx.HTTP_FORBIDDEN); --直接返回403
end
Nginx配置文件中,通过location配置块,来使用上述脚本:
#Nginx+Redis实现IP黑名单拦截示例
location /black_ip_demo {
access_by_lua_file luaScript/redis/black_ip_filter.lua;
echo "恭喜您,没有被拦截!";
}
当在浏览器访问http://localhost/black_ip_demo时,当未从Redis中获取到黑名单时,请求是放行的:
请求一次后,Redis中的黑名单列表会同步到Nginx本地共享变量中,再次访问会拦截
好啦,Nginx篇介绍完毕,后面会介绍综合实战~
相关推荐
- 4万多吨豪华游轮遇险 竟是因为这个原因……
-
(观察者网讯)4.7万吨豪华游轮搁浅,竟是因为油量太低?据观察者网此前报道,挪威游轮“维京天空”号上周六(23日)在挪威近海发生引擎故障搁浅。船上载有1300多人,其中28人受伤住院。经过数天的调...
- “菜鸟黑客”必用兵器之“渗透测试篇二”
-
"菜鸟黑客"必用兵器之"渗透测试篇二"上篇文章主要针对伙伴们对"渗透测试"应该如何学习?"渗透测试"的基本流程?本篇文章继续上次的分享,接着介绍一下黑客们常用的渗透测试工具有哪些?以及用实验环境让大家...
- 科幻春晚丨《震动羽翼说“Hello”》两万年星间飞行,探测器对地球的最终告白
-
作者|藤井太洋译者|祝力新【编者按】2021年科幻春晚的最后一篇小说,来自大家喜爱的日本科幻作家藤井太洋。小说将视角放在一颗太空探测器上,延续了他一贯的浪漫风格。...
- 麦子陪你做作业(二):KEGG通路数据库的正确打开姿势
-
作者:麦子KEGG是通路数据库中最庞大的,涵盖基因组网络信息,主要注释基因的功能和调控关系。当我们选到了合适的候选分子,单变量研究也已做完,接着研究机制的时便可使用到它。你需要了解你的分子目前已有哪些...
- 知存科技王绍迪:突破存储墙瓶颈,详解存算一体架构优势
-
智东西(公众号:zhidxcom)编辑|韦世玮智东西6月5日消息,近日,在落幕不久的GTIC2021嵌入式AI创新峰会上,知存科技CEO王绍迪博士以《存算一体AI芯片:AIoT设备的算力新选择》...
- 每日新闻播报(September 14)_每日新闻播报英文
-
AnOscarstatuestandscoveredwithplasticduringpreparationsleadinguptothe87thAcademyAward...
- 香港新巴城巴开放实时到站数据 供科技界研发使用
-
中新网3月22日电据香港《明报》报道,香港特区政府致力推动智慧城市,鼓励公私营机构开放数据,以便科技界研发使用。香港运输署21日与新巴及城巴(两巴)公司签署谅解备忘录,两巴将于2019年第3季度,开...
- 5款不容错过的APP: Red Bull Alert,Flipagram,WifiMapper
-
本周有不少非常出色的app推出,鸵鸟电台做了一个小合集。亮相本周榜单的有WifiMapper's安卓版的app,其中包含了RedBull的一款新型闹钟,还有一款可爱的怪物主题益智游戏。一起来看看我...
- Qt动画效果展示_qt显示图片
-
今天在这篇博文中,主要实践Qt动画,做一个实例来讲解Qt动画使用,其界面如下图所示(由于没有录制为gif动画图片,所以请各位下载查看效果):该程序使用应用程序单窗口,主窗口继承于QMainWindow...
- 如何从0到1设计实现一门自己的脚本语言
-
作者:dong...
- 三年级语文上册 仿写句子 需要的直接下载打印吧
-
描写秋天的好句好段1.秋天来了,山野变成了美丽的图画。苹果露出红红的脸庞,梨树挂起金黄的灯笼,高粱举起了燃烧的火把。大雁在天空一会儿写“人”字,一会儿写“一”字。2.花园里,菊花争奇斗艳,红的似火,粉...
- C++|那些一看就很简洁、优雅、经典的小代码段
-
目录0等概率随机洗牌:1大小写转换2字符串复制...
- 二年级上册语文必考句子仿写,家长打印,孩子照着练
-
二年级上册语文必考句子仿写,家长打印,孩子照着练。具体如下:...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- wireshark怎么抓包 (75)
- qt sleep (64)
- cs1.6指令代码大全 (55)
- factory-method (60)
- sqlite3_bind_blob (52)
- hibernate update (63)
- c++ base64 (70)
- nc 命令 (52)
- wm_close (51)
- epollin (51)
- sqlca.sqlcode (57)
- lua ipairs (60)
- tv_usec (64)
- 命令行进入文件夹 (53)
- postgresql array (57)
- statfs函数 (57)
- .project文件 (54)
- lua require (56)
- for_each (67)
- c#工厂模式 (57)
- wxsqlite3 (66)
- dmesg -c (58)
- fopen参数 (53)
- tar -zxvf -c (55)
- 速递查询 (52)