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个请求的时候,没有延迟,等到完成之后,按照正常的速率处理。如上两种配置结合就达到了速率稳定,但突然流量也能正常处理的效果。

第一次来到,如果写的有错误,欢迎指正!

你可能感兴趣的:(nginx+lua+redis实现广告缓存(网站首页高可用nginx+lua))