Nginx+Lua 从Nginx和Redis缓存获取数据案例 详解

4 篇文章 0 订阅
在 Nginx、Redis、Ehcache 三级缓存中,Nginx 分发层 和 应用层。在分发层 Nginx,通过 Lua,将商品id、商品店铺id,都转发到后端的应用nginx

可以通过 Nginx 指令重新加载所有配置包括 Lua 脚本
# /opt/modules/openresty/nginx/sbin/nginx/sbin/nginx -s reload

1> 应用 Nginx 的 Lua脚本接收到请求
2> 获取请求参数中的商品id,以及商品店铺id
3> 根据 商品id 和 商品店铺id,在 Nginx 本地缓存中尝试获取数据
4> 如果在 Nginx本地缓存中没有获取到数据,那么就到 Redis分布式缓存中获取数据,如果获取到数据,还要设置到 Nginx本地缓存中

这里有个问题,建议不要用 Nginx+Lua 直接去获取 Redis数据,因为 OpenResty 没有太好的 redis cluster 的支持包,所以建议是发送http请求到缓存数据生产服务,由该服务提供一个 http接口。缓存数生产服务可以基于 redis cluster api 从 Redis 中直接获取数据,并返回给 Nginx

在 OpenResty 编译文件中,引入 lua http lib包(一个网络请求的库)
# cd  /opt/modules/openresty/lualib/resty
wget https://raw.githubusercontent.com/pintsized/lua-resty-http/master/lib/resty/http_headers.lua
正在解析主机 raw.githubusercontent.com (raw.githubusercontent.com)... 151.101.72.133
正在连接 raw.githubusercontent.com (raw.githubusercontent.com)|151.101.72.133|:443... 已连接。
已发出 HTTP 请求,正在等待回应... 200 OK
长度:1150 (1.1K) [text/plain]
正在保存至: “http_headers.lua”

100%[==============================================================================================================================================>] 1,150       --.-K/s 用时 0s

2018-05-10 21:23:27 (262 MB/s) - 已保存 “http_headers.lua” [1150/1150])

# wget https://raw.githubusercontent.com/pintsized/lua-resty-http/master/lib/resty/http.lua
正在解析主机 raw.githubusercontent.com (raw.githubusercontent.com)... 151.101.72.133
正在连接 raw.githubusercontent.com (raw.githubusercontent.com)|151.101.72.133|:443... 已连接。
已发出 HTTP 请求,正在等待回应... 200 OK
长度:29686 (29K) [text/plain]
正在保存至: “http.lua”

100%[==============================================================================================================================================>] 29,686      --.-K/s 用时 0.1s

2018-05-10 21:24:34 (239 KB/s) - 已保存 “http.lua” [29686/29686])
5> 如果缓存数据生产服务没有在 Redis分布式缓存中没有获取到数据,那么就在本地 Ehcache 中获取数据,返回数据给 Nginx,也要设置到 Nginx本地缓存中
6> 如果 Ehcache 本地缓存都没有数据,那么就需要去原始的服务中拉去数据,该服务会从 MySQL 中查询,拉去到数据之后,返回给 Nginx,并重新设置到 Ehcache和 Rdis 中
注 : 这里存在一个问题,那就是分布式缓存重建并发冲突问题
7> Nginx 最终利用获取到的数据,动态渲染网页模板

在 OpenResty 编译文件中,引入lua-resty-template库
# cd /opt/modules/openresty/lualib/resty
# wget https://raw.githubusercontent.com/bungle/lua-resty-template/master/lib/resty/template.lua
--2018-04-18 03:49:12--  https://raw.githubusercontent.com/bungle/lua-resty-template/master/lib/resty/template.lua
正在解析主机 raw.githubusercontent.com (raw.githubusercontent.com)... 151.101.0.133, 151.101.64.133, 151.101.128.133, ...
正在连接 raw.githubusercontent.com (raw.githubusercontent.com)|151.101.0.133|:443... 已连接。
已发出 HTTP 请求,正在等待回应... 200 OK
长度:15473 (15K) [text/plain]
正在保存至: “template.lua”

100%[=================================================================================================>] 15,473      95.8KB/s 用时 0.2s

2018-04-18 03:49:14 (95.8 KB/s) - 已保存 “template.lua” [15473/15473])

# mkdir html
# cd html
# wget https://raw.githubusercontent.com/bungle/lua-resty-template/master/lib/resty/template/html.lua
--2018-04-18 03:50:32--  https://raw.githubusercontent.com/bungle/lua-resty-template/master/lib/resty/template/html.lua
正在解析主机 raw.githubusercontent.com (raw.githubusercontent.com)... 151.101.0.133, 151.101.64.133, 151.101.128.133, ...
正在连接 raw.githubusercontent.com (raw.githubusercontent.com)|151.101.0.133|:443... 已连接。
已发出 HTTP 请求,正在等待回应... 200 OK
长度:1235 (1.2K) [text/plain]
正在保存至: “html.lua”

100%[=================================================================================================>] 1,235       --.-K/s 用时 0s
在 lua.conf 也就是 Nginx 配置中引入的配置文件,在该文件的 server 标签中配置模板位置
# cd /opt/modules/openresty
# mkdir templates/
# mkdir -p hello/templates

# cd /opt/modules/openresty/nginx/conf
# vi lua.conf

server {
    listen       80;
    server_name  _;
    # template_root (set $template_root /var/www/site/templates)
    # template_location (set $template_location /templates)
    # 如果在 Nginx 配置中没有这些设置,则使用 ngx.var.document_root的值。如果设置 template_location,则正常返回(状态码200),则使用优先使用该配置其渲染。
    # 如果找不到,将尝试使用 template_root 或 document_root
    # 如果使用 $template_location 此时服务器使用 ngx_static 模式
    # $template_root 无法识别该目录下子目录中存放的模板

    #set $template_location "/opt/modules/openresty/templates";
    set $template_root "/opt/modules/openresty/templates";
    location /lua {
        default_type 'text/html';
        content_by_lua_file conf/lua/test.lua;
    }
    location /hello {
        default_type 'text/html';
        content_by_lua_file conf/lua/hello.lua;
    }
}
创建产品 HTML 模板
# cd /opt/modules/openresty/templates
vi product.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>商品详情页</title>
</head>
<body>

商品id: {* productId *}<br/>
商品名称: {* productName *}<br/>
商品图片列表: {* productPictureList *}<br/>
商品规格: {* productSpecification *}<br/>
商品售后服务: {* productService *}<br/>
商品颜色: {* productColor *}<br/>
商品大小: {* productSize *}<br/>
店铺id: {* shopId *}<br/>
店铺名称: {* shopName *}<br/>
店铺评级: {* shopLevel *}<br/>
店铺好评率: {* shopGoodCommentRate *}<br/>

</body>
</html>

8> 将渲染后的网页模板作为 http 响应,返回给 分发层Nginx
修改 Lua 缓存大小
# cd /opt/modules/openresty/nginx/conf
# vi nginx.conf

#user  nobody;
worker_processes  1;

#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

#pid        logs/nginx.pid;

events {
    worker_connections  1024;
}


http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    keepalive_timeout  65;
     # 设置缓存大小
     lua_shared_dict my_cache 128m;

    lua_package_path "/opt/modules/openresty/lualib/?.lua;;";
    lua_package_cpath "/opt/modules/openresty/lualib/?.so;;";
    include lua.conf;
}

添加产品访问监控,并设置产品 Lua 脚本
# cd /opt/modules/openresty/nginx/conf
# vi lua.conf
server {
    listen       80;
    server_name  _;
    # 设置模板存放位置
    set $template_location "/opt/modules/openresty/templates";
    set $template_root "/opt/modules/openresty/hello/templates";
    # 设置缓存大小
    lua_shared_dict my_cache 128m;
    location /lua {
        default_type 'text/html';
        content_by_lua_file conf/lua/test.lua;
    }
    location /hello {
        default_type 'text/html';
        content_by_lua_file conf/lua/hello.lua;
    }
    # 产品访问位置
    location /product {
        default_type 'text/html';
        content_by_lua_file conf/lua/product.lua;
    }
}


# cd lua
# vi product.lua
local uri_args = ngx.req.get_uri_args()
local productId = uri_args["productId"]
local shopId = uri_args["shopId"]

-- 使用在 nginx.conf 中通过 lua_shared_dict my_cache 128m; 定义的缓存中获取数据
local cache_ngx = ngx.shared.my_cache

-- 生成产品和商店的 key
local productCacheKey = "product_info_" .. productId
local shopCacheKey = "shop_info_" .. shopId

-- 获取缓存信息
local productCache = cache_ngx:get(productCacheKey)
local shopCache = cache_ngx:get(shopCacheKey)

-- 请求访问地址

-- 检查 product 缓存信息,如果不存在,则向服务端获取
if productCache == "" or productCache == nil then
    local http = require("resty.http")
    local httpc = http.new()
    local resp, err = httpc:request_uri("http://192.168.86.226:8080", {
        method = "GET",
        path = "/getProductInfo?productId=" .. productId
    })
    productCache = resp.body
    -- 缓存产品信息,并设置缓存时间 10 * 60 秒,也就是 10分钟
    cache_ngx:set(productCacheKey, productCache, 10 * 60)
end

-- 检查 shop 缓存信息,如果不存在,则向服务端获取
if shopCache == "" or shopCache == nil then
    local http = require("resty.http")
    local httpc = http.new()
    local resp, err = httpc:request_uri("http://192.168.86.226:8080", {
        method = "GET",
        path = "/getShopInfo?shopId=" .. shopId
    })
    shopCache = resp.body
    -- 缓存店铺信息,并设置缓存时间 10 * 60 秒,也就是 10分钟
    cache_ngx:set(shopCacheKey, shopCache, 10 * 60)
end

local cjson = require("cjson")
local productCacheJSON = cjson.decode(productCache)
local shopCacheJSON = cjson.decode(shopCache)

local context = {
    productId = productCacheJSON.id,
    productName = productCacheJSON.name,
    productPrice = productCacheJSON.price,
    productPictureList = productCacheJSON.pictureList,
    productSpecification = productCacheJSON.specification,
    productService = productCacheJSON.service,
    productColor = productCacheJSON.color,
    productSize = productCacheJSON.size,
    shopId = shopCacheJSON.id,
    shopName = shopCacheJSON.name,
    shopLevel = shopCacheJSON.level,
    shopGoodCommentRate = shopCacheJSON.goodCommentRate
}
-- 使用模板生成相应的文件
local template = require("resty.template")
template.render("product.html", context)

# 重新加载所有配置包括,有新模板的添加也要使用 Nginx 重新加载一次模板
# cd /opt/modules/openresty/nginx
# ./sbin/nginx -s reload

第一次访问的时候,其实在 Nginx本地缓存中是取不到的,所以会发送 http请求到后端的缓存服务里去获取,会从 Redis 中获取。拿到数据以后,会放到 Nginx 本地缓存里面去,过期时间是 10分钟,然后将所有数据渲染到模板中,返回模板,以后再来访问的时候,就会直接从 Nginx本地缓存区获取数据 :
缓存数据生产 -> 有数据变更 -> 主动更新两级缓存(ehcache+redis) -> 缓存维度化拆分
分发层Nginx + 应用层Nginx -> 自定义流量分发策略提高缓存命中率
nginx shared dict缓存 -> 缓存服务 -> redis -> ehcache -> 渲染html模板 -> 返回页面

还差最后一个很关键的要点,就是如果数据在 nginx -> redis -> ehcache 三级缓存都不在,可能就是数据通过 LRU 算法给清理掉了,这个时候缓存服务会重新拉去数据,去更新到 ehcache 和 redis中,这里存在 分布式的缓存重建的并发问题,这个问题可以通过分布式锁解决 
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值