本文将带领大家精读3个Nginx Lua编程实战案例,学不会就来砍我
off999 2025-01-07 14:56 33 浏览 0 评论
Nginx Lua编程实战案例
本节介绍如下3个Nginx Lua编程实战案例:
(1)一个基于Nginx+Redis分布式架构的访问统计实战案例。
(2)一个基于Nginx+Redis+Java容器架构的高并发访问实战案例。
(3)一个基于Nginx+Redis架构的黑名单拦截实战案例。
Nginx+Redis进行分布式访问统计
接口(或者页面)的访问统计是网站运营和优化的一个重要参考数据,对于分布式接口可以通过Nginx+Redis架构来简单实现分布式受访统计。
得益于Nginx的高并发性能和Redis的高速缓存,基于Nginx+Redis的受访统计的架构设计比纯Java实现受访统计的架构设计在性能上高出很多。
作为参考案例,这里使用前面定义的RedisOperator基础操作类编写了一个简单的受访统计类,具体的代码如下:
---启动调试,正式环境请注释
local mobdebug = require("luaScript.initial.mobdebug");
mobdebug.start();
--导入自定义的RedisOperator模块
local redisOp = require("luaScript.redis.RedisOperator");
--创建自定义的redis操作对象
local red = redisOp:new();
--打开连接
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:close();在nginx-redis-demo.conf配置文件中编写一个location配置块来使用该脚本,建议将该脚本执行于access阶段而不是content阶段,具体代码如下:
#点击次数统计的演示
location /visitcount {
#定义一个Nginx变量,用于在Lua脚本中保存访问次数
set $count 0;
access_by_lua_file luaScript/redis/RedisVisitCount.lua;
echo "10s内总的访问次数为: " $count;
}修改nginx-redis-demo.conf文件后重启Openrestry,然后使用浏览器访问其地址/visitcount,并且在浏览器中不断刷新,发现每刷新一次,页面的统计次数会加一,其结果如图8-23所示。
Nginx+Redis+Java容器实现高并发访问
在不需要高速访问的场景下,运行在Java后端的容器(如Tomcat)会直接从DB数据库(如MySQL)查询数据,然后返回给客户端。
由于数据库的连接数限制、网络传输延迟、数据库的IO频繁等多方面的原因,Java后端容器直接查询DB的性能会很低,这时会进行架构的调整,采用“Java容器+Redis+DB”的查询架构。针对数据一致性要求不是特别高但是访问频繁的API接口(实际上大部分都是),可以将DB数据放入Redis缓存,Java API可以优先查询Redis,如果缓存未命中,就回源到DB查询,从DB查询成功后再将数据更新到Redis缓存。
“Java容器+Redis+DB”的查询架构既起到Redis分流大量查询请求的作用,又大大提升了API接口的处理性能,可谓一举两得。该架构的请求处理流程如图8-24所示。
大家知道,常用的后端Java容器(如Tomcat、Jetty等)的性能其实不是太高,QPS性能指标一般会在1000以内。从笔者经历过的很多次性能攻关的数据来看,Nginx的性能是Java容器的10倍左右(甚至以上),并且稳定性更强,还不存在FullGC卡顿。
为了应对高并发,可以将“Java容器+Redis+DB”架构优化为“Nginx+Redis+Java容器”查询架构。新架构将后端Java容器的缓存判断、缓存查询前移到反向代理Nginx,通过Nginx直接进行Redis缓存判断、缓存查询。
“Nginx+Redis+Java容器”的查询架构不仅为Java容器减少了很多请求,而且能够充分发挥Nginx的高并发优势和稳定性优势。该架构的请求处理流程如图8-25所示。
这里以秒杀系统的商品数据查询为例提供一个“Nginx+Redis+Java容器”查询架构的参考实现。首先定义两个接口:一个模拟Java容器的商品查询接口;另一个模拟供外部调用的商品查询接口:
·模拟Java容器的商品查询接口:/java/good/detail。·模拟供外部调用的商品查询接口:/good/detail。
然后提供一个Lua操作缓存的类RedisCacheDemo,主要定义如下3个方法:
(1)getCache(self,goodId):根据商品id取得Redis商品缓存。
(2)goUpstream(self):通过capture内部请求访问上游接口获取商品数据。
(3)setCache(self,goodId,goodString):设置商品缓存,此方法用于模拟后台Java代码。
缓存操作类RedisCacheDemo的核心代码如下:
---启动调试,正式环境请注释
local mobdebug = require("luaScript.initial.mobdebug");
mobdebug.start();
--导入自定义的基础模块
local basic = require("luaScript.module.common.basic");
--导入自定义的RedisOperator模块
local redisOp = require("luaScript.redis.RedisOperator");
local PREFIX = "GOOD_CACHE:"
--RedisCacheDemo类
local _RedisCacheDemo = { }
_RedisCacheDemo.__index = _RedisCacheDemo
--类的方法new
function _RedisCacheDemo.new(self)
local object = {}
setmetatable(object, self)
return object;
end
--根据商品id取得缓存
function _RedisCacheDemo.getCache(self,goodId)
--创建自定义的redis操作对象
local red = redisOp:new();
--打开连接
if not red:open() then
basic:error("redis连接失败");
return nil;
end
--获取缓存数据 local json = red:getValue(PREFIX .. goodId);
red:close();
if not json or json==ngx.null then
basic:log(goodId .. "的缓存没有命中");
return nil;
end
basic:log(goodId .. "缓存成功命中");
return json;
end
--通过capture方法回源上游接口
function _RedisCacheDemo.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
--回源上游接口,比如Java后端rest接口
local res = ngx.location.capture("/java/good/detail",{
method = ngx.HTTP_GET,
args = args --重要:将请求参数原样向上游传递
})
basic:log("上游数据获取成功");
--返回上游接口的响应体body
return res.body;
end
--设置缓存,此方法主要用于模拟Java后台代码
function _RedisCacheDemo.setCache(self, goodId ,goodString)
--创建自定义的redis操作对象
local red = redisOp:new();
--打开连接
if not red:open() then
basic:error("redis连接失败");
return nil;
end
--set缓存数据
red:setValue(PREFIX .. goodId,goodString);
--60秒内过期
red:expire(PREFIX .. goodId, 60);
basic:log(goodId .. "缓存设置成功");
--归还连接到连接池
red:close();
return json;
end
return _RedisCacheDemo;在nginx-redis-demo.conf配置文件中编写一个location配置块来使用该脚本,该配置块是提供给外部调用的商品查询接口/good/detail,具体代码如下:
首先从缓存中查询商品
未命中再回源到
后台 #首先从缓存中查询商品,未命中再回源到Java后台
location = /good/detail {
content_by_lua_block {
local goodId=ngx.var.arg_goodid;
--判断goodId参数是否存在
if not goodId then
ngx.say("请输入goodId");
return;
end
--首先从缓存中根据id查询商品
local RedisCacheDemo = require "luaScript.redis.RedisCacheDemo";
local redisCacheDemo = RedisCacheDemo:new();
local json = redisCacheDemo:getCache(goodId);
--判断缓存是否被命中
if not json then
ngx.say("缓存是否被命中,回源到上游接口<br>");
--若没有命中缓存,则回源到上游接口
json = redisCacheDemo:goUpstream();
else
ngx.say("缓存已经被命中<br>");
end
ngx.say("商品信息:",json);
}
}出于调试方便,在nginx-redis-demo.conf配置文件中再编写一个location配置块来模拟Java容器的后台商品查询接口/java/good/detail。
理论上,后台接口的业务逻辑是从数据库查询商品信息并缓存到Redis,然后返回商品信息。这里为了方便演示对其进行简化,具体的代码如下:
#模拟Java后台接口查询商品,然后设置缓存
location = /java/good/detail {
#指定规则为internal内部规则,防止外部请求命中此规则
internal;
content_by_lua_block {
local RedisCacheDemo = require "luaScript.redis.RedisCacheDemo";
--Java后台将从数据库查找商品,这里简化成模拟数据
local json='{goodId:商品id,goodName:商品名称}';
--将商品缓存到Redis
local redisCacheDemo = RedisCacheDemo:new();
redisCacheDemo:setCache(ngx.var.arg_goodid, json);
--返回商品到下游网关
ngx.say(json);
}
} }修改了nginx-redis-demo.conf文件后重启OpenRestry,然后使用浏览器访问商品查询外部接口/good/detail,并且多次刷新,发现从二次请求开始就能成功命中缓存,其结果如图8-26所示。
Nginx+Redis实现黑名单拦截
我们在日常维护网站时经常会遇到这样一个需求,对于黑名单之内的IP需要拒绝提供服务。实现IP黑名单拦截有很多途径,比如以下方式:
(1)在操作系统层面配置iptables防火墙规则,拒绝黑名单中IP的网络请求。
(2)使用Nginx网关的deny配置指令拒绝黑名单中IP的网络请求。
(3)在Nginx网关的access处理阶段,通过Lua脚本检查客户端IP是否在黑名单中。
(4)在Spring Cloud内部网关(如Zuul)的过滤器中检查客户端
IP是否在黑名单中。
以上检查方式都是基于一个静态的、提前备好的黑名单进行的。在系统实际运行过程中,黑名单往往需要动态计算,系统需要动态识别出大量发起请求的恶意爬虫或者恶意用户,并且将这些恶意请求的IP放入一个动态的IP黑名单中。
Nginx网关可以依据动态黑名单内的IP进行请求拦截并拒绝提供服务。这里结合Nginx和Redis提供一个基于动态IP黑名单进行请求拦截的实现。
首先是黑名单的组成,黑名单应该包括静态部分和动态部分。静态部分为系统管理员通过控制台设置的黑名单。动态部分主要通过流计算框架完成,具体的方法为:将Nginx的访问日志通过Kafka消息中间件发送到流计算框架,然后通过滑动窗口机制计算出窗口内相同IP的访问计数,将超出阈值的IP动态加入黑名单中,流计算框架可以选用ApacheFlink或者Apache Storm。当然,除了使用流计算框架外,也可以使用RxJava滑动窗口进行访问计数的统计。
这里对黑名单的计算和生成不做研究,假定IP黑名单已经生成并且定期更新在Redis中。Nginx网关可以直接从Redis获取计算好的IP黑名单,但是为了提升黑名单的读取速度,并不是每一次请求过滤都从Redis读取IP黑名单,而是从本地的共享内存black_ip_list中获取,同时定期更新到本地共享内存中的IP黑名单。
Nginx+Redis实现黑名单拦截的系统架构如图8-27所示。
这里提供一个“Nginx+Redis”实现黑名单拦截的参考实现,具体的Lua脚本如下:
---启动调试,正式环境请注释
local mobdebug = require("luaScript.initial.mobdebug");
mobdebug.start();
--导入自定义的基础模块
local basic = require("luaScript.module.common.basic");
--导入自定义的RedisOperator模块
local redisOp = require("luaScript.redis.RedisOperator");local ip = basic.getClientIP();
basic.log("ClientIP:"..ip);
--lua_shared_dict black_ip_list 1m; #配置文件定义的ip_blacklist共享内存变量
local black_ip_list = ngx.shared.black_ip_list
--获得本地缓存的刷新时间,如果没有过期,就直接使用
local last_update_time = black_ip_list:get("last_update_time");
if last_update_time ~= nil then
local dif_time = ngx.now() - last_update_time
if dif_time < 60 then --缓存1分钟,没有过期
if black_ip_list:get(ip) then
return ngx.exit(ngx.HTTP_FORBIDDEN) --直接返回403
end
return
end
end
local KEY = "limit:ip:blacklist";
--创建自定义的redis操作对象
local red = redisOp:new();
--打开连接
red:open();
--获取缓存的黑名单
local ip_blacklist = red:getSmembers(KEY);
--归还连接到连接池
red:close();
if not ip_blacklist then
basic.log("black ip set is null");
return;
else
--刷新本地缓存
black_ip_list:flush_all();
--同步redis黑名单到本地缓存
for i,ip in ipairs(ip_blacklist) do
--本地缓存redis中的黑名单
black_ip_list:set(ip,true);
end
--设置本地缓存的最新更新时间
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该脚本名称为black_ip_filter.lua,作为测试,在nginx-redisdemo.conf配置文件中编写一个location配置块来执行该脚本,建议将该脚本执行于access阶段而不是content阶段,具体代码如下:
location /black_ip_demo {
access_by_lua_ file luaScript/redis/black_ip_filter.lua;
echo "恭喜,没有被拦截";
}另外,black_ip_filter.lua使用了名称为black_ip_list的共享内存区进行黑名单本地缓存,所以需要在配置文件中进行共享内存空间的定义,具体如下:
#定义存储IP黑名单的共享内存变量
lua_shared_dict black_ip_list 1m;这里使用lua_shared_dict指令定义了一块1MB大小的共享内存,有关该指令的使用方法在8.8.4节详细展开。修改nginx-redis-demo.conf文件后重启Openrestry,然后使用浏览器访问/black_ip_demo的完整链接地址,第一次访问时客户端IP没有加入黑名单,所以请求没有被拦截,结果如图8-28所示。
在Redis服务器上新建Set类型的键limit:ip:blacklist,并加入最新的当前客户端IP。然后再一次访问/black_ip_demo,发现请求已经被拦截,结果如图8-29所示。
使用Nginx Lua共享内存
Nginx Lua共享内存就是在内存块中分配出一个内存空间,该共享内存是一种字典结构,类似于Java Map的键-值(Key-Value)映射结构。同一个Nginx下的Worker进程都能访问存储在这里面的变量数据。
在Lua中定义共享内存非常简单,具体的指令如下:
语法:lua_shared_dict <DICT> <size>
上下文:http配置块。
例子:
lua_shared_dict black_ip_list 1m; #定义存储IP黑名单的共享内存变量lua_shared_dict指令用于定义一块名为DICT的共享内存空间,其内存大小为size。通过该命令定义的共享内存对于Nginx中所有Worker进程都是可见的。对于共享内存的引用可以使用以下两种形式来完成:
方式一:ngx.shared.DICT。
方式二:ngx.shared["DICT"]。ngx_lua提供了一系列API来操作共享内存,如表8-7所示。
如果读者熟悉Redis字符串的操作命令和参数,就会发现以上操作Niginx共享内存的API方法和Redis字符串的操作命令和参数有惊人的相似之处。
共享内存的API方法都是原子操作,也就是说,lua_shared_dict定义的是同一个共享内存区自带锁的功能,能够避免来自多个Worker工作进程的并发访问。
有关数据项的过期时间可以在新增数据项的时候进行设置。在新增数据项时,如果字典的内存区域不够,ngx.shared.DICT.set方法就会根据LRU算法淘汰一部分内容。当Nginx退出时,共享内存中的数据项都会丢失。
本文给大家讲解的内容是Nginx/OpenResty详解,Nginx Lua编程, Nginx Lua编程实战案例
- 下篇文章给大家讲解的是 Nginx/OpenResty详解,Nginx Lua编程,限流原理与实战;
- 觉得文章不错的朋友可以转发此文关注小编;
- 感谢大家的支持!
相关推荐
- 教育机构(教育机构最怕什么投诉才会退款)
-
五大正规教育培训机构1、新东方(集教育培训/教育产品研发/服务等于一体的大型教育科技集团,十大教育培训机构品牌,美国上市,北京新东方教育科技(集团)有限公司)2、学而思教育(国内最具知名度的中小学教育...
- win7按f8怎么一键还原(win7按f8怎么恢复出厂设置)
-
按F8,进入Windows7的高级启动选项; 选择“修复计算机”。 如果有管理员密码,需要输入;如果没有设置密码,直接“确定”即可; 进入系统恢复选项后,选择“DellDataSafe还原和...
- 下载电视直播免费版(电视直播在线下载)
-
在新买的智能电视上免费看电视直播的步骤:准备工具:U盘一个、智能电视一台。1、首先准备一个U盘,将“当贝桌面”的安装包文件保存到U盘。2、之后安装并打开桌面,并用遥控器移到一面,界面中点击左侧的影视快...
- 手机铃声下载来电铃声(手机铃声下载来电铃声排行榜免费)
-
1.点击【应用程序】-【互联网】-输入音乐下载网站的网址-搜索要下载的音乐-下载即可。2.在手机下载第三方音乐播放软件(例如酷狗),然后通过该类软件下载歌曲。3.手机中安装第三方应用软件(例如91助手...
- 怎样下载office2007免费版(如何下载microsoft office2007)
-
我们可以选择一个可靠连接,进入下载界面,点击下载,可以直接选择运行不需要保存,然后下载完成后开始安装。进入安装向导,勾选同意协议,点击继续。系统开始配置,兼容包将自动嵌入office文件包,等待进度完...
- 极品飞车最高通缉(极品飞车最高通缉手游)
-
在《极品飞车17:最高通缉》中,有几辆车被认为是最快的。以下是其中几辆车:1.路虎奇迹(LandRoverRangeRoverEvoque2013):这款SUV搭载了一台5.0升V8发动机...
-
- 海棠文学小说官方入口网站阅读
-
步骤/方式1打开海棠文学城官网,如图所示完成登录后点击【充值】。步骤/方式2如图所示点击选择【充值方式】按提示完成支付步骤/方式3如图所示完成充值后点击【我的书柜动态】查看充值结果...
-
2026-02-03 04:51 off999
- 浏览器手机网页版入口(手机浏览器网站)
-
360浏览器在线网页的入口是www.360.com右击“360浏览器”,然后选择“打开”。在“电脑端360浏览器”的页面右上角当中选择“打开菜单”。在随后出现的功能选项当中,选择“帮助”。然后我们就可...
- 1688批发厂家直销(1688批发厂家直销卫生纸)
-
比较成熟的平台。批发进货的话大部分还是比较靠谱的,给您几点建议参考吧。1.选择诚信通会员年限比较长的,存在的比较久,说明这家企业的供货能力还是不错的;2.选择采购回头率较高的企业,说明质量和服务也是相...
- 谷歌卫星地图免费版下载(谷歌卫星地图免费版下载苹果)
-
有写是没有数据的.并不是地球上每个地方都有数据的.LSV内置了谷歌地图、谷歌卫星影像哪款地图下载器哪个更好我不评判,但是免费的软件中,LSV还是最好用,至于你要说收费,给钱某宝什么图都有。日本东京高...
- 火萤视频壁纸app下载(火萤视频壁纸app下载官网)
-
火萤视频桌面是携带病毒的;根据工程师分析,用户安装该软件后,启动屏保功能出现屏保遮罩时,恶意程序就会在屏幕后公然执行劫持首页的行为,让用户无法发现;当用户卸载该软件时,卸载程序则立即向服务器发送请求询...
- txt小说下载网站(TXT小说下载网站免费下载)
-
因为许多小说都没有授权,而且99小说下载网的名声过大,被人追究了,不得不关闭了!久久小说下载网是一款免费看小说的软件,该平台每天都会为用户更新大量的小说资源,种类齐全,无论你想看霸道总裁还是古代穿越,...
- ghost系统镜像(ghost磁盘镜像)
-
答方法步骤如下1、一般GHOST工具是在PE启动后使用,这个就是PE中GHOST所在路径,找到这个软件并运行。2、界面是英文版本的,因为软件的易操作易学习性,所以这个软件基本没有中文版版本,然后在弹出...
- 二手车个人出售(闲鱼二手车个人出售)
-
从个人手里买二手车流程,首先查验车辆证照是否齐全,然后审核车辆是否真实有效,签订旧机动车买卖合同,然后到二手车交易市场开具二手车销售统一发票,并收取交易管理服务费,接着办理过户、转籍手续,最后是变更车...
欢迎 你 发表评论:
- 一周热门
-
-
抖音上好看的小姐姐,Python给你都下载了
-
全网最简单易懂!495页Python漫画教程,高清PDF版免费下载
-
飞牛NAS部署TVGate Docker项目,实现内网一键转发、代理、jx
-
win7系统还原步骤图解(win7还原电脑系统的步骤)
-
Python 3.14 的 UUIDv6/v7/v8 上新,别再用 uuid4 () 啦!
-
python入门到脱坑 输入与输出—str()函数
-
16949认证费用是多少(16949审核员太难考了)
-
linux软件(linux软件图标)
-
Python三目运算基础与进阶_python三目运算符判断三个变量
-
(新版)Python 分布式爬虫与 JS 逆向进阶实战吾爱分享
-
- 最近发表
- 标签列表
-
- python计时 (73)
- python安装路径 (56)
- python类型转换 (93)
- python进度条 (67)
- python吧 (67)
- python的for循环 (65)
- python格式化字符串 (61)
- python静态方法 (57)
- python列表切片 (59)
- python面向对象编程 (60)
- python 代码加密 (65)
- python串口编程 (77)
- python封装 (57)
- python写入txt (66)
- python读取文件夹下所有文件 (59)
- python操作mysql数据库 (66)
- python获取列表的长度 (64)
- python接口 (63)
- python调用函数 (57)
- python多态 (60)
- python匿名函数 (59)
- python打印九九乘法表 (65)
- python赋值 (62)
- python异常 (69)
- python元祖 (57)
