Nginx + Lua + Redis 处理访问量

记录一次在项目当中应用 Nginx + Lua + Redis 处理访问量的问题。

项目背景

项目背景是这样的。项目是文章分发平台,就是在一个平台上发送一篇带有图文的文章,发送文章时会选择要把文章发送到哪些媒体资讯平台,比如:聚焦财经、太原新闻网...等这类平台。但是呢文章发布到了媒体资讯平台,那么文章在各媒体资讯平台的浏览量就获取不到了。

初次方案-放弃

最初想到了一种方式:根据文章在媒体资讯平台上的 URL 链接,通过程序抓取文章的浏览量,但是呢每家媒体资讯平台浏览量的展现方式都不一样,这样处理起来很是麻烦,所以就放弃了。

调整方案

文章资讯虽然是发送到了其他媒体资讯平台,但是呢这个图片的资源使用的还是我们自己的服务,就想到了在图片进行入手的想法。

在每篇文章内容里都添加上一张固定的图片 http://www.example.com/code.jpg,with 和 height 都设置为0,不占用页面上的空间,在加载文章时还可以加载图片。

<p>测试测试</p>

<p><img src="http://www.example.com/code.jpg?u=21&o=6&m=12-20" with="0" height="0" /></p>

图片上携带关于文章信息的参数,比如:u 表示用户,o 表示文章ID,m 表示发布的媒体资讯平台的ID,用“-”连接。

在加载这张固定图片的时候,获取 URL 上的参数然后进行处理。

lua_nginx_module 模块

列一下需要用到的模块:

  1. lua_nginx_module (Nginx 的 Lua 模块)
  2. neturl(Lua 中解析 URL 的包)
  3. lua-resty-redis(Lua 中使用 Redis 的包)

在 Nginx 中使用 lua 的方式已经被广泛应用了,所以采用这种方式。下面演示整个配置流程:

引入 lua-resty-redis 和 neturl

我们的服务器环境是使用 宝塔Linux 面板搭建的,里面已经集成了 lua_nginx_module 模块,想要了解 Nginx 如何安装第三方模块的自行百度吧。

在 Nginx 的 http 中引入 lua 包:

http {

lua_package_path "/www/server/nginx_module/lua-resty-redis/lib/?.lua;/www/server/nginx_module/neturl/lib/?.lua;/www/server/nginx_module/local_media_mapp/lib/?.lua;;";

}

这里还引入了一个自己写的包,内容是项目需要的媒体资讯平台 ID 和域名映射关系,内容大致如下:

local site_map = {["1"]="www.baidu.com",["2"]="www.sina.com",["20"]="www.ngxtest.vip",...}

return site_map

图片参数处理

在 http 中引入了 lua 包之后,接下来在 nginx server 块中处理图片携带的参数:

这里配置两个网址来演示:www.nginxtest.vip 图片存放的域名,www.ngxtest.vip 媒体资讯平台域名。

server{

listen 80;

server_name www.nginxtest.vip;

index index.php index.html index.htm default.php default.htm default.html;

root /www/wwwroot/www.nginxtest.vip;

location ~ /code.jpg$

{

valid_referers www.nginxtest.vip www.ngxtest.vip;

if ($invalid_referer) {

rewrite ^/ /wp-content/uploads/aaacj/20220327vue1234/40310.jpeg permanent;

}

access_by_lua_block {

-- 获取 URL 参数和 header 头

local args = ngx.req.get_uri_args();

local receive_headers = ngx.req.get_headers();

function string:split(sep)

local sep, fields = sep or "\t", {}

local pattern = string.format("([^%s]+)", sep)

self:gsub(pattern, function(c) fields[#fields+1] = c end)

return fields

end

if args.u and args.m and args.o and receive_headers.referer then

local url = require "net.url";

local uinfo = url.parse(receive_headers.referer);

local referer = uinfo.host;

--local site_map = {["12"]="www.baidu.com",["13"]="www.sina.com",["14"]="www.ngxtest.vip"};

local site_map = require "media.sitemap";

local mids = args.m;

local midsarr = mids:split("-");

for k,v in pairs(midsarr) do

if site_map[v] == referer then

local redis = require "resty.redis";

local instance = redis:new();

local ok,err = instance:connect("127.0.0.1", 6379);

if ok then

local hashkey="media_visit";

local key = "media:"..args.u..":"..args.o..":"..v;

local hexists_ok,hexists_err = instance:hexists(hashkey,key);

if hexists_ok then

local hincr_ok,hincr_err = instance:hincrby(hashkey,key,1);

else

local hset_ok,hset_err = instance:hset(hashkey,key,1);

end

end

local close_ok, close_err = instance:close();

end

end

end

}

}

}

这里使用的是 access_by_lua_block块,没用 content_by_lua_block块是因为 content 块直接返回了。

这是图片的防盗链处理,可以根据实际选择是否添加:

valid_referers www.nginxtest.vip www.ngxtest.vip;

if ($invalid_referer) {

rewrite ^/ /wp-content/uploads/aaacj/20220327vue1234/40310.jpeg permanent;

}

这是自定义的字符串分割为数组的函数:

-- 字符串分割为数组的函数

function string:split(sep)

local sep, fields = sep or "\t", {}

local pattern = string.format("([^%s]+)", sep)

self:gsub(pattern, function(c) fields[#fields+1] = c end)

return fields

end

这一块内容是处理图片携带的参数程序,处理的思路简单叙述下:

  1. 先判断图片是否携带了指定的参数:u、o、m,和 header 头中是否存在 referer 信息。如果存在则进行下一步处理,不存在则寻找图片资源。
  2. 使用 URL 解析包解析 header 头的 referer。
  3. 引入自定义的媒体资讯平台ID 和域名映射包。
  4. 将参数 m 的值分割为数组,然后循环数组。
  5. 判断当前的媒体资讯 ID 映射的域名是否和 header 中的 referer 相等。
  6. 引入 Redis 包并实例化。
  7. 这里使用的 Redis 的 Hash 类型,关于 Hash 类型就不多说了。
  8. 先判断指定的 field 是否在 hashkey 中,如果存在则值 + 1,不存在则执行 set 初始值为 1。
-- 判断图片的请求中是否存在字段:u、o、m,header 头中是否存在 referer 信息

if args.u and args.m and args.o and receive_headers.referer then

-- 引入 URL 解析包

local url = require "net.url";

-- 解析 header 头的 referer

local uinfo = url.parse(receive_headers.referer);

local referer = uinfo.host;

-- 引入自定义的媒体资讯平台ID 和域名映射包

local site_map = require "media.sitemap";

-- 解析参数 m 为数组

local mids = args.m;

local midsarr = mids:split("-");

-- 循环数组

for k,v in pairs(midsarr) do

-- 判断当前的媒体资讯 ID 映射的域名是否和 header 中的 referer 相等

if site_map[v] == referer then

-- 引入 Redis 包

local redis = require "resty.redis";

local instance = redis:new();

local ok,err = instance:connect("127.0.0.1", 6379);

if ok then

-- 这里采用的将数据存入 Redis Hash 类型中

-- 定义 hashkey

local hashkey="media_visit";

-- 组装 hash field

local key = "media:"..args.u..":"..args.o..":"..v;

-- 判断是否在 hashkey 中存在

local hexists_ok,hexists_err = instance:hexists(hashkey,key);

if hexists_ok then

-- 存在则值 + 1

local hincr_ok,hincr_err = instance:hincrby(hashkey,key,1);

else

-- 不存在则 set 到 hashkey 中,初始值为 1

local hset_ok,hset_err = instance:hset(hashkey,key,1);

end

end

end

end

end

www.ngxtest.vip 引用图片的 HTML 代码

<!doctype html>

<html>

<head>

<meta charset="utf-8">

<title>恭喜,站点创建成功!</title>

<style>

.container {

width: 60%;

margin: 10% auto 0;

background-color: #f0f0f0;

padding: 2% 5%;

border-radius: 10px

}

ul {

padding-left: 20px;

}

ul li {

line-height: 2.3

}

a {

color: #20a53a

}

</style>

</head>

<body>

<div class="container">

<h1>恭喜, 站点创建成功!</h1>

<h3>这是默认index.html,本页面由系统自动生成</h3>

<ul>

<li>本页面在FTP根目录下的index.html</li>

<li>您可以修改、删除或覆盖本页面</li>

<li>FTP相关信息,请到“面板系统后台 > FTP” 查看</li>

</ul>

</div>

<img src="/wp-content/uploads/aaacj/20220327vue1234/map11.png?u=21&m=12-20&o=6" width="0" height="0" />

</body>

</html>

Redis hashkey 详情

统计入库

可以每天定时统计 Redis 中的数据更新入库,然后再清空 hashkey 的数据,防止项目运行时间长了数据过大。

小结

本人主写的语言不是 Lua,以上的 Lua 代码是现学现卖写的,如果有语法上的错误请指出。这个方案呢在实施上可能会存在一定的风险性,比如说:伪造 header 中的 referer;更改图片请求参数值发起恶意请求(可以通过限流方式解决)等,如有想到的可以提出来,使用需谨慎,目前在项目中已使用也是非常谨慎。通过这种方案还可以延申出统计用户 IP等。

以上是 Nginx + Lua + Redis 处理访问量 的全部内容, 来源链接: utcz.com/z/267711.html

回到顶部