Nginx Lua编程实战案例.docx

上传人:b****9 文档编号:25695801 上传时间:2023-06-11 格式:DOCX 页数:15 大小:898.66KB
下载 相关 举报
Nginx Lua编程实战案例.docx_第1页
第1页 / 共15页
Nginx Lua编程实战案例.docx_第2页
第2页 / 共15页
Nginx Lua编程实战案例.docx_第3页
第3页 / 共15页
Nginx Lua编程实战案例.docx_第4页
第4页 / 共15页
Nginx Lua编程实战案例.docx_第5页
第5页 / 共15页
点击查看更多>>
下载资源
资源描述

Nginx Lua编程实战案例.docx

《Nginx Lua编程实战案例.docx》由会员分享,可在线阅读,更多相关《Nginx Lua编程实战案例.docx(15页珍藏版)》请在冰豆网上搜索。

Nginx Lua编程实战案例.docx

NginxLua编程实战案例

3个NginxLua编程实战案例

本节介绍如下3个NginxLua编程实战案例:

(1)一个基于Nginx+Redis分布式架构的访问统计实战案例。

(2)一个基于Nginx+Redis+Java容器架构的高并发访问实战案例。

(3)一个基于Nginx+Redis架构的黑名单拦截实战案例。

Nginx+Redis进行分布式访问统计

接口(或者页面)的访问统计是网站运营和优化的一个重要参考数据,对于分布式接口可以通过Nginx+Redis架构来简单实现分布式受访统计。

得益于Nginx的高并发性能和Redis的高速缓存,基于Nginx+Redis的受访统计的架构设计比纯Java实现受访统计的架构设计在性能上高出很多。

作为参考案例,这里使用前面定义的RedisOperator基础操作类编写了一个简单的受访统计类,具体的代码如下:

---启动调试,正式环境请注释

localmobdebug=require("luaScript.initial.mobdebug");

mobdebug.start();

--导入自定义的RedisOperator模块

localredisOp=require("luaScript.redis.RedisOperator");

--创建自定义的redis操作对象

localred=redisOp:

new();

--打开连接

red:

open();

--获取访问次数

localvisitCount=red:

incrValue("demo:

visitCount");

ifvisitCount==1then

--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$count0;

access_by_lua_fileluaScript/redis/RedisVisitCount.lua;

echo"10s内总的访问次数为:

"$count;

}

修改nginx-redis-demo.conf文件后重启Openrestry,然后使用浏览器访问其地址/visitcount,并且在浏览器中不断刷新,发现每刷新一次,页面的统计次数会加一,其结果如图1所示。

图1访问统计效果图

Nginx+Redis+Java容器实现高并发访问

在不需要高速访问的场景下,运行在Java后端的容器(如Tomcat)会直接从DB数据库(如MySQL)查询数据,然后返回给客户端。

由于数据库的连接数限制、网络传输延迟、数据库的IO频繁等多方面的原因,Java后端容器直接查询DB的性能会很低,这时会进行架构的调整,采用“Java容器+Redis+DB”的查询架构。

针对数据一致性要求不是特别高但是访问频繁的API接口(实际上大部分都是),可以将DB数据放入Redis缓存,JavaAPI可以优先查询Redis,如果缓存未命中,就回源到DB查询,从DB查询成功后再将数据更新到Redis缓存。

“Java容器+Redis+DB”的查询架构既起到Redis分流大量查询请求的作用,又大大提升了API接口的处理性能,可谓一举两得。

该架构的请求处理流程如图8-24所示。

图2“Java容器+Redis+DB”查询架构的请求处理流程

大家知道,常用的后端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所示。

图3“Nginx+Redis+Java容器”查询架构的请求处理流程

这里以秒杀系统的商品数据查询为例提供一个“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的核心代码如下:

---启动调试,正式环境请注释

localmobdebug=require("luaScript.initial.mobdebug");

mobdebug.start();

--导入自定义的基础模块

localbasic=require("luaSmon.basic");

--导入自定义的RedisOperator模块

localredisOp=require("luaScript.redis.RedisOperator");

localPREFIX="GOOD_CACHE:

"

--RedisCacheDemo类

local_RedisCacheDemo={}

_RedisCacheDemo.__index=_RedisCacheDemo

--类的方法new

function_RedisCacheDemo.new(self)

localobject={}

setmetatable(object,self)

returnobject;

end

--根据商品id取得缓存

function_RedisCacheDemo.getCache(self,goodId)

--创建自定义的redis操作对象

localred=redisOp:

new();

--打开连接

ifnotred:

open()then

basic:

error("redis连接失败");

returnnil;

end

--获取缓存数据localjson=red:

getValue(PREFIX..goodId);

red:

close();

ifnotjsonorjson==ngx.nullthen

basic:

log(goodId.."的缓存没有命中");

returnnil;

end

basic:

log(goodId.."缓存成功命中");

returnjson;

end

--通过capture方法回源上游接口

function_RedisCacheDemo.goUpstream(self)

localrequest_method=ngx.var.request_method

localargs=nil

--获取参数的值

if"GET"==request_methodthen

args=ngx.req.get_uri_args()

elseif"POST"==request_methodthen

ngx.req.read_body()

args=ngx.req.get_post_args()

end

--回源上游接口,比如Java后端rest接口

localres=ngx.location.capture("/java/good/detail",{

method=ngx.HTTP_GET,

args=args--重要:

将请求参数原样向上游传递

})

basic:

log("上游数据获取成功");

--返回上游接口的响应体body

returnres.body;

end

--设置缓存,此方法主要用于模拟Java后台代码

function_RedisCacheDemo.setCache(self,goodId,goodString)

--创建自定义的redis操作对象

localred=redisOp:

new();

--打开连接

ifnotred:

open()then

basic:

error("redis连接失败");

returnnil;

end

--set缓存数据

red:

setValue(PREFIX..goodId,goodString);

--60秒内过期

red:

expire(PREFIX..goodId,60);

basic:

log(goodId.."缓存设置成功");

--归还连接到连接池

red:

close();

returnjson;

end

return_RedisCacheDemo;

在nginx-redis-demo.conf配置文件中编写一个location配置块来使用该脚本,该配置块是提供给外部调用的商品查询接口/good/detail,具体代码如下:

首先从缓存中查询商品

未命中再回源到

后台#首先从缓存中查询商品,未命中再回源到Java后台

location=/good/detail{

content_by_lua_block{

localgoodId=ngx.var.arg_goodid;

--判断goodId参数是否存在

ifnotgoodIdthen

ngx.say("请输入goodId");

return;

end

--首先从缓存中根据id查询商品

localRedisCacheDemo=require"luaScript.redis.RedisCacheDemo";

localredisCacheDemo=RedisCacheDemo:

new();

localjson=redisCacheDemo:

getCache(goodId);

--判断缓存是否被命中

ifnotjsonthen

ngx.say("缓存是否被命中,回源到上游接口
");

--若没有命中缓存,则回源到上游接口

json=redisCacheDemo:

goUpstream();

else

ngx.say("缓存已经被命中
");

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{

localRedisCacheDemo=require"luaScript.redis.RedisCacheDemo";

--Java后台将从数据库查找商品,这里简化成模拟数据

localjson='{goodId:

商品id,goodName:

商品名称}';

--将商品缓存到Redis

localredisCacheDemo=RedisCacheDemo:

new();

redisCacheDemo:

setCache(ngx.var.arg_goodid,json);

--返回商品到下游网关

ngx.say(json);

}

}

}

修改了nginx-redis-demo.conf文件后重启OpenRestry,然后使用浏览器访问商品查询外部接口/good/detail,并且多次刷新,发现从二次请求开始就能成功命中缓存,其结果如图8-26所示。

图4使用浏览器访问商品查询外部接口/good/detail的结果

Nginx+Redis实现黑名单拦截

我们在日常维护网站时经常会遇到这样一个需求,对于黑名单之内的IP需要拒绝提供服务。

实现IP黑名单拦截有很多途径,比如以下方式:

(1)在操作系统层面配置iptables防火墙规则,拒绝黑名单中IP的网络请求。

(2)使用Nginx网关的deny配置指令拒绝黑名单中IP的网络请求。

(3)在Nginx网关的access处理阶段,通过Lua脚本检查客户端IP是否在黑名单中。

(4)在SpringCloud内部网关(如Zuul)的过滤器中检查客户端

IP是否在黑名单中。

以上检查方式都是基于一个静态的、提前备好的黑名单进行的。

在系统实际运行过程中,黑名单往往需要动态计算,系统需要动态识别出大量发起请求的恶意爬虫或者恶意用户,并且将这些恶意请求的IP放入一个动态的IP黑名单中。

Nginx网关可以依据动态黑名单内的IP进行请求拦截并拒绝提供服务。

这里结合Nginx和Redis提供一个基于动态IP黑名单进行请求拦截的实现。

首先是黑名单的组成,黑名单应该包括静态部分和动态部分。

静态部分为系统管理员通过控制台设置的黑名单。

动态部分主要通过流计算框架完成,具体的方法为:

将Nginx的访问日志通过Kafka消息中间件发送到流计算框架,然后通过滑动窗口机制计算出窗口内相同IP的访问计数,将超出阈值的IP动态加入黑名单中,流计算框架可以选用ApacheFlink或者ApacheStorm。

当然,除了使用流计算框架外,也可以使用RxJava滑动窗口进行访问计数的统计。

这里对黑名单的计算和生成不做研究,假定IP黑名单已经生成并且定期更新在Redis中。

Nginx网关可以直接从Redis获取计算好的IP黑名单,但是为了提升黑名单的读取速度,并不是每一次请求过滤都从Redis读取IP黑名单,而是从本地的共享内存black_ip_list中获取,同时定期更新到本地共享内存中的IP黑名单。

Nginx+Redis实现黑名单拦截的系统架构如图8-27所示。

图5Nginx+Redis实现黑名单拦截的系统架构

这里提供一个“Nginx+Redis”实现黑名单拦截的参考实现,具体的Lua脚本如下:

---启动调试,正式环境请注释

localmobdebug=require("luaScript.initial.mobdebug");

mobdebug.start();

--导入自定义的基础模块

localbasic=require("luaSmon.basic");

--导入自定义的RedisOperator模块

localredisOp=require("luaScript.redis.RedisOperator");localip=basic.getClientIP();

basic.log("ClientIP:

"..ip);

--lua_shared_dictblack_ip_list1m;#配置文件定义的ip_blacklist共享内存变量

localblack_ip_list=ngx.shared.black_ip_list

--获得本地缓存的刷新时间,如果没有过期,就直接使用

locallast_update_time=black_ip_list:

get("last_update_time");

iflast_update_time~=nilthen

localdif_time=ngx.now()-last_update_time

ifdif_time<60then--缓存1分钟,没有过期

ifblack_ip_list:

get(ip)then

returnngx.exit(ngx.HTTP_FORBIDDEN)--直接返回403

end

return

end

end

localKEY="limit:

ip:

blacklist";

--创建自定义的redis操作对象

localred=redisOp:

new();

--打开连接

red:

open();

--获取缓存的黑名单

localip_blacklist=red:

getSmembers(KEY);

--归还连接到连接池

red:

close();

ifnotip_blacklistthen

basic.log("blackipsetisnull");

return;

else

--刷新本地缓存

black_ip_list:

flush_all();

--同步redis黑名单到本地缓存

fori,ipinipairs(ip_blacklist)do

--本地缓存redis中的黑名单

black_ip_list:

set(ip,true);

end

--设置本地缓存的最新更新时间

black_ip_list:

set("last_update_time",ngx.now());

end

ifblack_ip_list:

get(ip)then

returnngx.exit(ngx.HTTP_FORBIDDEN)--直接返回403

end

该脚本名称为black_ip_filter.lua,作为测试,在nginx-redisdemo.conf配置文件中编写一个location配置块来执行该脚本,建议将该脚本执行于access阶段而不是content阶段,具体代码如下:

location/black_ip_demo{

access_by_lua_fileluaScript/redis/black_ip_filter.lua;

echo"恭喜,没有被拦截";

}

另外,black_ip_filter.lua使用了名称为black_ip_list的共享内存区进行黑名单本地缓存,所以需要在配置文件中进行共享内存空间的定义,具体如下:

#定义存储IP黑名单的共享内存变量

lua_shared_dictblack_ip_list1m;

这里使用lua_shared_dict指令定义了一块1MB大小的共享内存,有关该指令的使用方法在8.8.4节详细展开。

修改nginx-redis-demo.conf文件后重启Openrestry,然后使用浏览器访问/black_ip_demo的完整链接地址,第一次访问时客户端IP没有加入黑名单,所以请求没有被拦截,结果如图8-28所示。

图6第一次访问时客户端IP没有加入黑名单

在Redis服务器上新建Set类型的键limit:

ip:

blacklist,并加入最新的当前客户端IP。

然后再一次访问/black_ip_demo,发现请求已经被拦截,结果如图8-29所示。

图7客户端IP加入黑名单后请求被拦截

使用NginxLua共享内存

NginxLua共享内存就是在内存块中分配出一个内存空间,该共享内存是一种字典结构,类似于JavaMap的键-值(Key-Value)映射结构。

同一个Nginx下的Worker进程都能访问存储在这里面的变量数据。

在Lua中定义共享内存非常简单,具体的指令如下:

语法:

lua_shared_dict  

上下文:

http配置块。

例子:

lua_shared_dictblack_ip_list1m;#定义存储IP黑名单的共享内存变量lua_shared_dict指令用于定义一块名为DICT的共享内存空间,其内存大小为size。

通过该命令定义的共享内存对于Nginx中所有Worker进程都是可见的。

对于共享内存的引用可以使用以下两种形式来完成:

方式一:

ngx.shared.DICT。

方式二:

ngx.shared["DICT"]。

ngx_lua提供了一系列API来操作共享内存,如表8-7所示。

表8ngx_lua字典API及其方法

如果读者熟悉Redis字符串的操作命令和参数,就会发现以上操作Niginx共享内存的API方法和Redis字符串的操作命令和参数有惊人的相似之处。

共享内存的API方法都是原子操作,也就是说,lua_shared_dict定义的是同一个共享内存区自带锁的功能,能够避免来自多个Worker工作进程的并发访问。

有关数据项的过期时间可以在新增数据项的时候进行设置。

在新增数据项时,如果字典的内存区域不够,ngx.shared.DICT.set方法就会根据LRU算法淘汰一部分内容。

当Nginx退出时,共享内存中的数据项都会丢失。

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 高等教育 > 其它

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1