nginx+lua+redis实现广告缓存(网站首页高可用nginx+lua)

前言:

  1. 默认已经安装好 lua和openResty和nginx
    不会吧!不会吧! 还真的有人不会装吧。 我给兄弟们指一条路。在我下一篇博客将详细介绍(小声比比:装不好别怪我,怪你太菜)手动狗头保命
  2. 在互联网公司,Nginx可以说是标配组件,但是主要场景还是负载均衡、反向代理、代理缓存、限流等场景;而把Nginx作为一个Web容器使用的还不是那么广泛。Nginx开发主要是以C/C++模块的形式进行,我靠这学习成本也太高了, 如果有一种简单的语言来实现Web应用的开发,那么Nginx绝对是把好的瑞士军刀, Nginx团队也开始意识到这个问题,所以目前市面上用的非常成熟的扩展是由(中国)章亦春将Lua和Nginx粘合的ngx_lua模块,并且将Nginx核心、LuaJIT、ngx_lua模块、许多有用的Lua库和常用的第三方Nginx模块组合在一起成为OpenResty,这样开发人员就可以安装OpenResty,使用Lua编写脚本,然后部署到Nginx Web容器中运行。从而非常轻松就能开发出高性能的Web服务。
  3. 关于nginx,Lua和OpenResty这里我就不多做介绍了,想了解的同学可以捧一下老弟的场。
  4. 看到这里我就怕有人说 Tomcat不香吗?我靠>>>>>>

正文:

修改/usr/local/openresty/nginx/conf/nginx.conf,将配置文件使用的根设置为root,目的就是将来要使用lua脚本的时候,直接可以加载在root下的lua脚本。

#user nobody;配置文件第一行原来为这样,现改为下面的配置
user root root;

缓存预热

实现思路:

用于查询数据库中的数据更新到redis中。
(1)连接mysql,按照广告分类ID读取广告列表,转换为json字符串。
(2)连接redis,将广告列表json字符串存入redis。

定义请求:
请求:
    /ad_update
参数:
    position ‐‐指定广告位置
返回值:
    json

看一下图吧!


缓存预热

搞起来!!!

在/root/lua目录下创建ad_load.lua,实现连接mysql查询数据并存储到redis中。不要和我说不会写lua,我负责写,你负责看
相关脚本代码:

-- 指定json数据传递
ngx.header.content_type="application/json;charset=utf8"
-- 通过 require 关键字引入了两个第三方模块  require("cjson")代表支持json数据
local cjson = require("cjson")
-- require("resty.mysql")代表MySQL的致辞
local mysql = require("resty.mysql")
-- ngx.req.get_uri_args(请求路径上的参数) 赋值给一个名为 uri_args 的局部变量
local uri_args = ngx.req.get_uri_args()
-- 拿到请求参数后 拿到一个名为 position 的值 并赋值给一个名为 position 的局部变量
local position = uri_args["position"]
-- 开启MySQL的连接
local db = mysql:new()
-- 数据库连接超时时间
db:set_timeout(1000)
local props = {  
    host = "192.168.200.128",
    port = 3306,
    database = "changgou_business",
    user = "root",
    password = "root"
}  
local res = db:connect(props)   
-- 下面这句话的意思是代表连接到MySQL后进行的查询语句 (查询tb_ad表,并设置相关条件)
local select_sql = "select url,image from tb_ad where status ='1' and position='"..position.."' and start_time<= NOW() AND end_time>= NOW()"  
-- 通过db:query 将sql语句进行查询 并得到相关结果
res = db:query(select_sql)
-- 关闭数据库连接
db:close()
-- 通过 require 关键字引入 resty.redis 模块
local redis = require("resty.redis")
-- 创建Redis的连接
local red = redis:new()
-- 超时时间
red:set_timeout(2000)
-- Redis的ip
local ip ="192.168.200.128"
local port = 6379
red:connect(ip,port)
-- 向Redis 存放内容
red:set("ad_"..position,cjson.encode(res))
-- 关闭redis 的连接
red:close()
-- 随便返回一句话
ngx.say("{\"flag\":true,\"position\":\""..position.."\"}")

OK 到这里我们第一个lua脚本已经搭建完毕,
但是还差点火候,想让上面的配置文件生效, 就更改nginx 的配置文件,
修改/usr/local/openresty/nginx/conf/nginx.conf文件:
找到如下配置文件:

server {
        listen       80;
        server_name  localhost;
        charset utf-8;
        #charset koi8-r;
        #access_log  logs/host.access.log  main;

在底下添加

# 添加广告
        location /ad_update {
            content_by_lua_file /root/lua/ad_update.lua;  # 加载root/lua/ad_update.lua文件
        }

最后nginx.conf配置文件长成这样(我是不是有点瞧不起大家)

server {
        listen       80;
        server_name  localhost;
        charset utf-8;
        #charset koi8-r;
        #access_log  logs/host.access.log  main;
        # 添加广告
        location /ad_update {
            content_by_lua_file /root/lua/ad_update.lua;  
        }

现在来开始测试,

等一等

不要忘记一件事:但凡我们改了nginx的配置文件就要记得重新启动nginx服务
进入 /usr/local/openresty/nginx/sbin

./nginx -s reload

这个时候应该没问题了,我们来测试一哈
测试:http://自己的ip/ad_update?position=自己数据库表中的数据


浏览器发起请求

Redis中新增的数据

能到这一步代表大家才迈出一小步
如果执行报错或者查不到数据:相信大家都是优秀的程序员,找bug的能力还是有的

广告缓存读取

现在Redis中有数据,怎么读出来呢?接下来请看我操作!
定义请求:

请求:
    /ad_read
参数:
    position
返回值:
    json

在/root/lua目录下创建ad_read.lua

--设置响应头类型
ngx.header.content_type="application/json;charset=utf8"
--获取请求中的参数ID
local uri_args = ngx.req.get_uri_args();
local position = uri_args["position"];
--引入redis库
local redis = require("resty.redis");
--创建redis对象
local red = redis:new()
--设置超时时间
red:set_timeout(2000)
--连接
local ok, err = red:connect("192.168.200.128", 6379)
--获取key的值
local rescontent=red:get("ad_"..position)
--输出到返回响应中
ngx.say(rescontent)
--关闭连接
red:close()

想让上面的配置生效 ,必须修改nginx的配置文件!

在/usr/local/openresty/nginx/conf/nginx.conf中server下添加配置

server {
        listen       80;
        server_name  localhost;
        charset utf-8;
        #charset koi8-r;
        #access_log  logs/host.access.log  main;
        # 添加广告
        location /ad_update {
            content_by_lua_file /root/lua/ad_update.lua;  # 加载root/lua/ad_update.lua文件
        }  
        # 读取广告
        location /ad_read {
            content_by_lua_file /root/lua/ad_read.lua;
        }

既然已经修改nginx的配置文件,想让配置生效怎么操作就不说了
随随便便测试一下:http://自己的IP地址/ad_read?position=web_index_lb
效果图就是返回一串json数据

二级缓存-加入openresty本地缓存

到这一步代表着以上都没问题,但是思考一个问题,如果所有的请求都到Redis中,Redis怎能受得了,唉 英年早逝!
所以我们一般采用多级缓存的方式来减少下游系统的服务压力。先查询openresty本地缓存如果没有再查询redis中的数据。(openresty是啥不知道>>>>http:www.baidu.com
所以接着修改lad_read脚本文件
内容如下:

--设置响应头类型
ngx.header.content_type="application/json;charset=utf8"
--获取请求中的参数ID
local uri_args = ngx.req.get_uri_args();
local position = uri_args["position"];
--获取本地缓存
local cache_ngx = ngx.shared.dis_cache;
--根据ID 获取本地缓存数据
local adCache = cache_ngx:get('ad_cache_'..position);
if adCache == "" or adCache == nil then
    --引入redis库
    local redis = require("resty.redis");
    --创建redis对象
    local red = redis:new()
    --设置超时时间
    red:set_timeout(2000)
    --连接
    local ok, err = red:connect("192.168.200.128", 6379)
    --获取key的值
    local rescontent=red:get("ad_"..position)
    --输出到返回响应中
    ngx.say(rescontent)
    --关闭连接
    red:close()
    --将redis中获取到的数据存入nginx本地缓存
    cache_ngx:set('ad_cache_'..position, rescontent, 10*60);
else
     --nginx本地缓存中获取到数据直接输出
     ngx.say(adCache)
end

接着修改
nginx配置文件vi/usr/local/openresty/nginx/conf/nginx.conf,http节点下添 加配置:

# HTTPS server
    #
    #server {
    #    listen       443 ssl;
    #    server_name  localhost;
    #    ssl_certificate      cert.pem;
    #    ssl_certificate_key  cert.key;
    #    ssl_session_cache    shared:SSL:1m;
    #    ssl_session_timeout  5m;
    #    ssl_ciphers  HIGH:!aNULL:!MD5;
    #    ssl_prefer_server_ciphers  on;
    #    location / {
    #        root   html;
    #        index  index.html index.htm;
    #    }
    #}
    
    #包含redis初始化模块
    lua_shared_dict dis_cache 5m;  #共享内存开启

既然已经修改nginx的配置文件,想让配置生效怎么操作就不说了(不知道?>>>>http:www.baidu.com)

加油!

nginx限流

一般情况下,首页的并发量是比较大的,(万一碰到那个手痒痒的频繁访问)即使有了多级缓存,如果有大量恶意的请求,也会对系统造成影响。而限流就是保护措施之一,那我们能不能学习一下像微服务网关一样限流,巧了,nginx就有这么强大!
nginx提供两种限流的方式:
一 是控制速率
二 是控制并发连接数

控制速率

控制速率的方式之一就是采用漏桶算法。漏桶(LeakyBucket)算法思路很简单,水(请求)先进入到漏桶里,漏桶以一定的速度出水(接口有响应速率),当水流入速度过大会直接溢出(访问频率超过接口响应速率),然后就拒绝请求,可以看出漏桶算法能强行限制数据的传输速率.示意图如下:


示意图

漏桶算法实现nginx的配置:

修改/usr/local/openresty/nginx/conf/nginx.conf:

#user  nobody;
user root root;
worker_processes  1;
events {
    worker_connections  1024;
}
http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;

    # 设置限流配置
     limit_req_zone $binary_remote_addr zone=myRateLimit:10m rate=2r/s;
     server {
          listen       8080;
          server_name  localhost;
          charset utf-8;
          location / {
              limit_req zone=myRateLimit;
              root   html;
              index  index.html index.htm;
          }
      }

    server {
        listen       80;
        server_name  localhost;
        charset utf-8;
        # 添加广告
        location /ad_update {
            content_by_lua_file /root/lua/ad_update.lua;  # 加载root/lua/ad_update.lua文件
        }
        # 读取广告
        location /ad_read {
            content_by_lua_file /root/lua/ad_read.lua;
        }
        location / {
            root   html;
            index  index.html index.htm;
        }
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
    #包含redis初始化模块
    lua_shared_dict dis_cache 5m;  #共享内存开启
}

解释:
binary_remote_addr是一种key,表示基于remote_addr(客户端IP)来做限流,binary_的目的是压缩内存占用量。zone:定义共享内存区来存储访问信息,
myRateLimit:10m表示一个大小为10M,名字为myRateLimit的内存区域。1M能存储16000IP地址的访问信息,10M可以存储16WIP地址访问信息。
rate用于设置最大访问速率,rate=10r/s表示每秒最多处理10个请求。
Nginx实际上以毫秒为粒度来跟踪请求信息,因此10r/s实际上是限制:每100毫秒处理一个请求。这意味着,自上一个请求处理完后,若后续100毫秒内又有请求到达,将拒绝处理该请求.我们这里设置成2方便测试。

处理突发流量

上面例子限制2r/s,如果有时正常流量突然增大,超出的请求将被拒绝,无法处理突发流量,可以结合burst参数使用来解决该问题。

server{
    location/{
        limit_req zone=myRateLimit  burst=5;
        root html;
        index index.htmlindex.htm;
    }
}

burst译为突发、爆发,表示在超过设定的处理速率后能额外处理的请求数,当rate=2r/s时,将1s拆成2份,即每500ms可处理1个请求。

此处,burst=5,若同时有6个请求到达,Nginx会处理第一个请求,剩余5个请求将放入队列,然后每隔500ms从队列中获取一个请求进行处理。若请求数大于6,将拒绝处理多余的请求,直接返回503.

不过,单独使用burst参数并不实用。假设burst=50,rate为10r/s,排队中的50个请求虽然每100ms会处理一个,但第50个请求却需要等待50*100ms即5s,这么长的处理时间自然难以接受。

因此,burst往往结合nodelay一起使用。

server{
    location/{
        limit_req zone=myRateLimit  burst=5 nodelay;
        root html;
        index index.htmlindex.htm;
    }
}

如上表示:处理突发5个请求的时候,没有延迟,等到完成之后,按照正常的速率处理。如上两种配置结合就达到了速率稳定,但突然流量也能正常处理的效果。

第一次来到简书,如果写的有错误,欢迎指正!
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,175评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,674评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,151评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,597评论 1 269
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,505评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,969评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,455评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,118评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,227评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,213评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,214评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,928评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,512评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,616评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,848评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,228评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,772评论 2 339