Nginx Lua编程-实战案例 nginx luci
liebian365 2024-10-29 15:54 7 浏览 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篇介绍完毕,后面会介绍综合实战~
相关推荐
- 快递查询教程,批量查询物流,一键管理快递
-
作为商家,每天需要查询许许多多的快递单号,面对不同的快递公司,有没有简单一点的物流查询方法呢?小编的回答当然是有的,下面随小编一起来试试这个新技巧。需要哪些工具?安装一个快递批量查询高手快递单号怎么快...
- 一键自动查询所有快递的物流信息 支持圆通、韵达等多家快递
-
对于各位商家来说拥有一个好的快递软件,能够有效的提高自己的工作效率,在管理快递单号的时候都需要对单号进行表格整理,那怎么样能够快速的查询所有单号信息,并自动生成表格呢?1、其实方法很简单,我们不需要一...
- 快递查询单号查询,怎么查物流到哪了
-
输入单号怎么查快递到哪里去了呢?今天小编给大家分享一个新的技巧,它支持多家快递,一次能查询多个单号物流,还可对查询到的物流进行分析、筛选以及导出,下面一起来试试。需要哪些工具?安装一个快递批量查询高手...
- 3分钟查询物流,教你一键批量查询全部物流信息
-
很多朋友在问,如何在短时间内把单号的物流信息查询出来,查询完成后筛选已签收件、筛选未签收件,今天小编就分享一款物流查询神器,感兴趣的朋友接着往下看。第一步,运行【快递批量查询高手】在主界面中点击【添...
- 快递单号查询,一次性查询全部物流信息
-
现在各种快递的查询方式,各有各的好,各有各的劣,总的来说,还是有比较方便的。今天小编就给大家分享一个新的技巧,支持多家快递,一次能查询多个单号的物流,还能对查询到的物流进行分析、筛选以及导出,下面一起...
- 快递查询工具,批量查询多个快递快递单号的物流状态、签收时间
-
最近有朋友在问,怎么快速查询单号的物流信息呢?除了官网,还有没有更简单的方法呢?小编的回答当然是有的,下面一起来看看。需要哪些工具?安装一个快递批量查询高手多个京东的快递单号怎么快速查询?进入快递批量...
- 快递查询软件,自动识别查询快递单号查询方法
-
当你拥有多个快递单号的时候,该如何快速查询物流信息?比如单号没有快递公司时,又该如何自动识别再去查询呢?不知道如何操作的宝贝们,下面随小编一起来试试。需要哪些工具?安装一个快递批量查询高手快递单号若干...
- 教你怎样查询快递查询单号并保存物流信息
-
商家发货,快递揽收后,一般会直接手动复制到官网上一个个查询物流,那么久而久之,就会觉得查询变得特别繁琐,今天小编给大家分享一个新的技巧,下面一起来试试。教程之前,我们来预览一下用快递批量查询高手...
- 简单几步骤查询所有快递物流信息
-
在高峰期订单量大的时候,可能需要一双手当十双手去查询快递物流,但是由于逐一去查询,效率极低,追踪困难。那么今天小编给大家分享一个新的技巧,一次能查询多个快递单号的物流,下面一起来学习一下,希望能给大家...
- 物流单号查询,如何查询快递信息,按最后更新时间搜索需要的单号
-
最近有很多朋友在问,如何通过快递单号查询物流信息,并按最后更新时间搜索出需要的单号呢?下面随小编一起来试试吧。需要哪些工具?安装一个快递批量查询高手快递单号若干怎么快速查询?运行【快递批量查询高手】...
- 连续保存新单号功能解析,导入单号查询并自动识别批量查快递信息
-
快递查询已经成为我们日常生活中不可或缺的一部分。然而,面对海量的快递单号,如何高效、准确地查询每一个快递的物流信息,成为了许多人头疼的问题。幸运的是,随着科技的进步,一款名为“快递批量查询高手”的软件...
- 快递查询教程,快递单号查询,筛选更新量为1的单号
-
最近有很多朋友在问,怎么快速查询快递单号的物流,并筛选出更新量为1的单号呢?今天小编给大家分享一个新方法,一起来试试吧。需要哪些工具?安装一个快递批量查询高手多个快递单号怎么快速查询?运行【快递批量查...
- 掌握批量查询快递动态的技巧,一键查找无信息记录的两种方法解析
-
在快节奏的商业环境中,高效的物流查询是确保业务顺畅运行的关键。作为快递查询达人,我深知时间的宝贵,因此,今天我将向大家介绍一款强大的工具——快递批量查询高手软件。这款软件能够帮助你批量查询快递动态,一...
- 从复杂到简单的单号查询,一键清除单号中的符号并批量查快递信息
-
在繁忙的商务与日常生活中,快递查询已成为不可或缺的一环。然而,面对海量的单号,逐一查询不仅耗时费力,还容易出错。现在,有了快递批量查询高手软件,一切变得简单明了。只需一键,即可搞定单号查询,一键处理单...
- 物流单号查询,在哪里查询快递
-
如果在快递单号多的情况,你还在一个个复制粘贴到官网上手动查询,是一件非常麻烦的事情。于是乎今天小编给大家分享一个新的技巧,下面一起来试试。需要哪些工具?安装一个快递批量查询高手快递单号怎么快速查询?...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- 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)