高并发高可用复杂系统中的缓存架构(二十) 基于“分发层 + 应用层”双层 nginx 架构提升缓存命中率

缓存数据生产服务那一层已经搞定了,相当于三层缓存架构中的本地堆缓存 + redis 分布式缓存都搞定了

就要来做三级缓存中的 nginx 那一层的缓存了

缓存命中率低

如果一般来说,你默认会部署多个 nginx,在里面都会放一些缓存,就默认情况下,此时缓存命中率是比较低的

 

如上图,被均衡分发了,所以命中率很低。

如何提升缓存命中率?

方案:分发层+应用层,双层 nginx

分发层 nginx,负责流量分发的逻辑和策略,这个里面它可以根据你自己定义的一些规则, 比如根据 productId 去进行 hash,然后对后端的 nginx 数量取模,将某一个商品的访问的请求, 就固定路由到一个 nginx 后端服务器上去,保证说只会从 redis 中获取一次缓存数据, 后面全都是走 nginx 本地缓存了

后端的 nginx 服务器,就称之为应用服务器; 最前端的 nginx 服务器,被称之为分发服务器

 

看似很简单,其实很有用,在实际的生产环境中,可以大幅度提升你的 nginx 本地缓存这一层的命中率, 大幅度减少 redis 后端的压力,提升性能

基于 OpenResty 部署应用层 nginx 以及 nginx + lua 开发 hello world

在 nginx 里去写 lua 脚本,因为我们需要自定义一些特殊的业务逻辑

比如说,流量分发,自己用 lua 去写分发的逻辑,在分发层 nginx 里去写的

再比如说,要用 lua 去写多级缓存架构存取的控制逻辑,在应用层 nginx 里去写的

后面还要做热点数据的自动降级机制,也是用 lua 脚本去写降级机制的,在分发层 nginx 里去写的

因为我们要用 nginx+lua 去开发,所以会选择用最流行的开源方案,就是用 OpenResty

nginx+lua 打包在一起,而且提供了包括 redis 客户端,mysql 客户端,http 客户端在内的大量的组件

我们这一讲是去部署应用层 nginx,会采用 OpenResty 的方式去部署 nginx, 而且会带着大家写一个 nginx+lua 开发的一个 hello world

部署第一个 nginx,作为应用层 nginx,部署机器:nginx01

部署 openresty

mkdir -p /usr/servers  
cd /usr/servers/
# 安装依赖
yum install -y readline-devel pcre-devel openssl-devel gcc
​
# 可能需要翻墙才能下载,所以可以离线下载好
wget http://openresty.org/download/ngx_openresty-1.7.7.2.tar.gz  
tar -xzvf ngx_openresty-1.7.7.2.tar.gz  
rm -rf ngx_openresty-1.7.7.2.tar.gz
​
# 安装 lua 等相关组件
cd /usr/servers/ngx_openresty-1.7.7.2/
cd bundle/LuaJIT-2.1-20150120/  
make clean && make && make install  
ln -sf luajit-2.1.0-alpha /usr/local/bin/luajit
cd ../
wget https://github.com/FRiCKLE/ngx_cache_purge/archive/2.3.tar.gz  
tar -xvf 2.3.tar.gz  
wget https://github.com/yaoweibin/nginx_upstream_check_module/archive/v0.3.0.tar.gz  
tar -xvf v0.3.0.tar.gz  
​
#
cd /usr/servers/ngx_openresty-1.7.7.2  
./configure --prefix=/usr/servers --with-http_realip_module  --with-pcre  --with-luajit --add-module=./bundle/ngx_cache_purge-2.3/ --add-module=./bundle/nginx_upstream_check_module-0.3.0/ -j2  
make && make install
​
# 会发现多了好多目录
[root@ servers]# ll
total 3316
drwxr-xr-x 2 root root    4096 Apr  1 23:38 bin
drwxr-xr-x 6 root root    4096 Apr  1 23:38 luajit
drwxr-xr-x 5 root root    4096 Apr  1 23:38 lualib
drwxr-xr-x 6 root root    4096 Apr  1 23:38 nginx
drwxrwxr-x 4 1000 1000    4096 Apr  1 23:35 ngx_openresty-1.7.7.2
​
# 启动nginx:
/usr/servers/nginx/sbin/nginx

nginx+lua 开发的 hello world

vi /usr/servers/nginx/conf/nginx.conf
​
# 在 http 部分添加如下内容
​
lua_package_path "/usr/servers/lualib/?.lua;;";  
lua_package_cpath "/usr/servers/lualib/?.so;;";  
​
如下位置
​
http {
    lua_package_path "/usr/servers/lualib/?.lua;;";
    lua_package_cpath "/usr/servers/lualib/?.so;;";
​
    include       mime.types;
    default_type  application/octet-stream;

/usr/servers/nginx/conf 下,创建一个 lua.conf,内容如下

server {  
    listen       80;  
    server_name  _;  
}  

接着在 nginx.conf 的 http 部分添加:include lua.conf;

验证配置是否正确:

[root@ conf]# /usr/servers/nginx/sbin/nginx -t
nginx: the configuration file /usr/servers/nginx/conf/nginx.conf syntax is ok
nginx: configuration file /usr/servers/nginx/conf/nginx.conf test is successful

在 lua.conf 的 server 部分添加:

location /lua {  
    default_type 'text/html';  
    content_by_lua 'ngx.say("hello world")';  
}

添加完成后,再次测试验证是否正确:/usr/servers/nginx/sbin/nginx -t

重新加载 nginx 配置: /usr/servers/nginx/sbin/nginx -s reload

访问 http://192.168.99.170/lua 会看到页面输出 「hello world」

这里把嵌入配置文件的代码迁移到独立文件中

mkdir -p /usr/servers/nginx/conf/lua
vi /usr/servers/nginx/conf/lua/test.lua
# 在文件中写入迁入的代码
ngx.say("hello world");
​
# 并修改 lua.conf ,配置成 file 方式
​
location /lua {  
    default_type 'text/html';  
    content_by_lua_file conf/lua/test.lua;
}

查看异常日志 : tail -f /usr/servers/nginx/logs/error.log

工程化的 nginx+lua 项目结构

项目工程结构

|- hello  # 项目名
  |- hello.conf     
  |- lua              
    |- hello.lua
  |- lualib  # 该包的文件可以在之前的 /usr/servers/lualib 全部 copy 过来           
    |- *.lua
    |- *.so

放在 /usr/hello 目录下

搭建另外一个应用层 nginx

如法炮制,在另外一个机器上,也用 OpenResty 部署一个 nginx

部署机器:nginx02

这次的 hello word 就按照工程化的目录来放置和配置

mkdir /usr/hello
​
vi hello.conf
# 内容如下
server {  
    listen       80;  
    server_name  _;  
    location /lua {  
      default_type 'text/html';  
      content_by_lua_file /usr/hello/lua/hello.lua;
    }
}
​
mkdir lua
vi lua/hello.lua
# 内容如下
ngx.say("hello world,工程化结构")
​
# 复制 lualib
scp -r /usr/servers/lualib/ ./
​
# 编辑 nginx.conf
vi /usr/servers/nginx/conf/nginx.conf
​
# 添加内容
http {
    lua_package_path "/usr/hello/lualib/?.lua;;";
    lua_package_cpath "/usr/hello/lualib/?.so;;";
​
    include /usr/hello/hello.conf;
    include       mime.types;
​
# 测试是否成功
/usr/servers/nginx/sbin/nginx -t
# 重新加载 nginx 配置
/usr/servers/nginx/sbin/nginx -s reload

浏览器访问 http://nginx02/lua 显示出信息,成功

部署分发层 nginx 以及基于 lua 完成基于商品 id 的定向流量分发策略

nginx03 上也需要搭建一个 应用层 nginx

nginx 三台的作用:

  • nginx01:应用层

  • nginx02:应用层

  • nginx03:分发层

在 nginx03 中编写 lua 脚本,完成基于商品 id 的流量分发策略

这里简化业务逻辑,实际上在你的公司中,你可以随意根据自己的业务逻辑和场景,去制定自己的流量分发策略

步骤如下:

  1. 获取请求参数,比如 productId

  2. 对 productId 进行 hash

  3. hash 值对应用服务器数量取模,获取到一个应用服务器

  4. 利用 http 发送请求到应用层 nginx

  5. 获取响应后返回

为了能分发看出来效果,我们把之前 hello 输出信息修改为自己的主机名,如 hello world,nginx03

使用 lua 转发需要用到 lua 的 http 包,这里导入下

cd /usr/hello/lualib/resty/
wget https://raw.githubusercontent.com/pintsized/lua-resty-http/master/lib/resty/http_headers.lua  
wget https://raw.githubusercontent.com/pintsized/lua-resty-http/master/lib/resty/http.lua

脚本编写

/usr/hello/lua/hello.lua

-- 拿一个地址来说明:http://nginx03/lua?method=hello&productId=1
-- 获取问号后面的参数列表
local uri_args = ngx.req.get_uri_args()
-- 获取参数
local productId = uri_args["productId"]
​
-- 定义后端应用 ip
local host = {"192.168.99.170", "192.168.99.171"}
-- 对商品 id 取模并计算 hash 值
local hash = ngx.crc32_long(productId)
hash = (hash % 2) + 1  
-- 拼接 http 前缀
backend = "http://"..host[hash]
​
-- 获取到参数中的路径,比如你要访问 /hello,这个例子中是需要传递访问路径的
local method = uri_args["method"]
-- 拼接具体的访问地址不带 host,如:/hello?productId=1
local requestBody = "/"..method.."?productId="..productId
​
-- 获取 http 包
local http = require("resty.http")  
local httpc = http.new()  
​
-- 访问,这里有疑问:万一有 cooke 这些脚本支持吗?会很麻烦吗?
local resp, err = httpc:request_uri(backend, {  
    method = "GET",  
    path = requestBody,
    keepalive=false
})
​
-- 如果没有响应则输出一个 err 信息
if not resp then  
    ngx.say("request error :", err)  
    return  
end
​
-- 有响应测输出响应信息
ngx.say(resp.body)  
​
-- 关闭 http 客户端实例
httpc:close()

刷新配置:/usr/servers/nginx/sbin/nginx -s reload

访问

这里的意思是,访问 03 这台分发 nginx,但是需要告诉它我访问后端哪个服务路径
由于后端两个 nginx 都只有 /lua 这个请求路径,所以就访问了它
http://nginx03/lua?method=lua&productId=1
页面输出:hello world,nginx02
​
http://eshop-cache03/lua?method=lua&productId=4
页面输出:hello world,nginx01

基于商品 id 的定向流量分发策略的 lua 脚本就开发完了,而且也测试过了

我们就可以看到,如果你请求的是固定的某一个商品,那么就一定会将流量打到固定的一个应用 nginx 上面去

 

2019/04/02 02:00:33 [error] 8451#0: *16 lua entry thread aborted: runtime error: /usr/hello/lualib/resty/http.lua:926: bad argument #2 to 'set_keepalive' (number expected, got nil)
stack traceback:
coroutine 0:
    [C]: in function 'set_keepalive'
    /usr/hello/lualib/resty/http.lua:926: in function 'request_uri'
    /usr/hello/lua/hello.lua:25: in function </usr/hello/lua/hello.lua:1>, client: 192.168.99.111, server: _, request: "GET /lua?method=lua&productId=1 HTTP/1.1", host: "nginx03"

基于 nginx + lua + java 完成多级缓存架构的核心业务逻辑

 

脚本思路:

  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

  5. 如果缓存数据生产服务没有在 redis 分布式缓存中没有获取到数据,那么就在自己本地 ehcache 中获取数据,返回数据给 nginx,也要设置到 nginx 本地缓存中

  6. 如果 ehcache 本地缓存都没有数据,那么就需要去原始的服务中拉取数据,该服务会从 mysql 中查询,拉取到数据之后,返回给 nginx,并重新设置到 ehcache和 redis 中

    这里先不考虑并发问题,后面要专门讲解一套分布式缓存重建并发冲突的问题和解决方案

  7. nginx 最终利用获取到的数据,动态渲染网页模板

  8. 将渲染后的网页模板作为 http 响应,返回给分发层 nginx

下面来一步一步做;

分发层 lua

nginx03 服务器上

/usr/hello/hello.conf

server {
    listen       80;
    server_name  _;
    location /lua {
      default_type 'text/html';
      # 防止响应中文乱码
      charset utf-8;
      content_by_lua_file /usr/hello/lua/hello.lua;
    }
​
    # 分发逻辑脚本映射
    location /product {
      default_type 'text/html';
      # 防止响应中文乱码
      charset utf-8;
      content_by_lua_file /usr/hello/lua/distribute.lua;
    }
}

/usr/hello/lua/distribute.lua,这里使用之前写好的分发逻辑修改, 因为想在一个映射中写完商品和店铺信息的分发,所以这里还需要添加一个 shopId

local uri_args = ngx.req.get_uri_args()
-- 获取参数
local productId = uri_args["productId"]
local shopId = uri_args["shopId"]
​
-- 定义后端应用 ip
local host = {"192.168.99.170", "192.168.99.171"}
-- 对商品 id 取模并计算 hash 值
local hash = ngx.crc32_long(productId)
hash = (hash % 2) + 1
-- 拼接 http 前缀
backend = "http://"..host[hash]
​
-- 获取到参数中的路径,比如你要访问 /hello,这个例子中是需要传递访问路径的
local method = uri_args["method"]
-- 拼接具体的访问地址不带 host,如:/hello?productId=1
local requestBody = "/"..method.."?productId="..productId.."&shopId="..shopId
​
-- 获取 http 包
local http = require("resty.http")
local httpc = http.new()
​
-- 访问,这里有疑问:万一有 cooke 这些脚本支持吗?会很麻烦吗?
local resp, err = httpc:request_uri(backend, {
    method = "GET",
    path = requestBody,
    keepalive=false
})
​
-- 如果没有响应则输出一个 err 信息
if not resp then
    ngx.say("request error :", err)
    return
end
​
-- 有响应测输出响应信息
ngx.say(resp.body)  
​
-- 关闭 http 客户端实例
httpc:close()

应用层 nginx

应用层在 nginx01 和 nginx02 上。 这里可以再 01 上写完逻辑,然后再 copy 到 02 上。

先安装依赖:

# 需要再后端服务获取信息,安装 http 依赖
cd /usr/hello/lualib/resty/
wget https://raw.githubusercontent.com/pintsized/lua-resty-http/master/lib/resty/http_headers.lua  
wget https://raw.githubusercontent.com/pintsized/lua-resty-http/master/lib/resty/http.lua
​
# 拿到数据之后需要进行模板渲染,添加 template 依赖
# 这里渲染也是使用 lua 来完成
cd /usr/hello/lualib/resty/
wget https://raw.githubusercontent.com/bungle/lua-resty-template/master/lib/resty/template.lua
mkdir /usr/hello/lualib/resty/html
cd /usr/hello/lualib/resty/html
wget https://raw.githubusercontent.com/bungle/lua-resty-template/master/lib/resty/template/html.lua

/usr/hello/hello.conf

# 配置 lua 的一个缓存实例,my_cache 是我们自定义的一块缓存名称
# 要配置在 http 中,server 外,否则会报错
# nginx: [emerg] "lua_shared_dict" directive is not allowed here in /usr/hello/hello.conf:11
lua_shared_dict my_cache 128m;
server {  
    listen       80;  
    server_name  _;
​
    # 配置模板路径
    set $template_location "/templates";  
    # 当然这个路径需要存在,因为后续需要用来存放 html
    set $template_root "/usr/hello/templates";
​
    location /lua {  
      default_type 'text/html';  
      # 防止响应中文乱码
      charset utf-8;
      content_by_lua_file /usr/hello/lua/hello.lua;
    }
​
    # 配置一个脚本映射,访问 product 的时候
    # 就执行 product.lua 脚本来完成 获取缓存渲染 html 并返回 html 的功能
    location /product {
      default_type 'text/html';
      # 防止响应中文乱码
      charset utf-8;
      content_by_lua_file /usr/hello/lua/product.lua;
    }    
​
}

/usr/hello/lua/product.lua

local uri_args = ngx.req.get_uri_args()
local productId = uri_args["productId"]
local shopId = uri_args["shopId"]
​
-- 获取到之前配置中分配的缓存对象
local cache_ngx = ngx.shared.my_cache
​
-- 拼接两个缓存 key
local productCacheKey = "product_info_"..productId
local shopCacheKey = "shop_info_"..shopId
​
-- 通过缓存对象获取缓存中的 value
local productCache = cache_ngx:get(productCacheKey)
local shopCache = cache_ngx:get(shopCacheKey)
​
-- 如果缓存中不存在对于的 value
-- 就走后端缓存服务获取数据(缓存服务先走 redis ,不存在再走 ehcache,再走数据库)
if productCache == "" or productCache == nil then
    local http = require("resty.http")
    local httpc = http.new()
  -- 这里地址是开发机器 ip,因为我们在 windows 上开发的,
  -- 这里直接访问开发环境比较方便
    local resp, err = httpc:request_uri("http://192.168.99.111:6002",{
        method = "GET",
        path = "/getProductInfo?productId="..productId,
      keepalive=false
    })
​
    productCache = resp.body
  -- 获取到之后,再设置到缓存中
    cache_ngx:set(productCacheKey, productCache, 10 * 60)
end
​
if shopCache == "" or shopCache == nil then
    local http = require("resty.http")
    local httpc = http.new()
​
    local resp, err = httpc:request_uri("http://192.168.99.111:6002",{
        method = "GET",
        path = "/getShopInfo?shopId="..shopId,
      keepalive=false
    })
​
    shopCache = resp.body
    cache_ngx:set(shopCacheKey, shopCache, 10 * 60)
end
​
-- 因为存到缓存中是一个字符串
-- 所以使用 cjson 库把字符串转成 json 对象
local cjson = require("cjson")
local productCacheJSON = cjson.decode(productCache)
local shopCacheJSON = cjson.decode(shopCache)
​
-- 把商品信息和店铺信息拼接到一个大 json 对象中
-- 这样做的原因是:template 渲染需要这样做
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
}
​
-- 使用 template 渲染 product.html 模板
local template = require("resty.template")
template.render("product.html", context)

product.html 内容,就是很简单的插值占位

product id: {* productId *}<br/>
product name: {* productName *}<br/>
product picture list: {* productPictureList *}<br/>
product specification: {* productSpecification *}<br/>
product service: {* productService *}<br/>
product color: {* productColor *}<br/>
product size: {* productSize *}<br/>
shop id: {* shopId *}<br/>
shop name: {* shopName *}<br/>
shop level: {* shopLevel *}<br/>
shop good cooment rate: {* shopGoodCommentRate *}<br/>

配置完成后,记得测试配置文件和重启 nginx

/usr/servers/nginx/sbin/nginx -t
/usr/servers/nginx/sbin/nginx -s reload

错误解决

如果报错 product.lua:20: module 'resty.http' not found: 那么请检查

vi /usr/servers/nginx/conf/nginx.conf
这里引入的内容是否是 hello 目录下的。
http {
    lua_package_path "/usr/hello/lualib/?.lua;;";
    lua_package_cpath "/usr/hello/lualib/?.so;;";

测试

访问地址:http://nginx02/product?productId=1&shopId=1

肯定会报错,因为后端服务都没有写的。但是可以看看日志报错信息

tail -f /usr/servers/nginx/logs/error.log
可以看到如下的错误:

2019/05/06 22:19:10 [error] 8758#0: *34 connect() failed (113: No route to host), client: 192.168.99.1, server: _, request: "GET /product?productId=1&shopId=1 HTTP/1.1", host: "eshop-cache02"
2019/05/06 22:19:10 [error] 8758#0: *34 lua entry thread aborted: runtime error: /usr/hello/lua/product.lua:29: attempt to index local 'resp' (a nil value)
stack traceback:
coroutine 0:
	/usr/hello/lua/product.lua: in function </usr/hello/lua/product.lua:1>, client: 192.168.99.1, server: _, request: "GET /product?productId=1&shopId=1 HTTP/1.1", host: "nginx02"

 

缓存服务实现

CacheController

/**
  * 这里的代码别看着奇怪,简单回顾下之前的流程: 1. nginx 获取 redis 缓存 2. 获取不到再获取服务的堆缓存(也就是这里的 ecache) 3.
  * 还获取不到就需要去数据库获取并重建缓存
  */
 @RequestMapping("/getProductInfo")
 @ResponseBody
 public ProductInfo getProductInfo(Long productId) {
     ProductInfo productInfo = cacheService.getProductInfoOfReidsCache(productId);
     log.info("从 redis 中获取商品信息");
     if (productInfo == null) {
         productInfo = cacheService.getProductInfoFromLocalCache(productId);
         log.info("从 ehcache 中获取商品信息");
     }
     if (productInfo == null) {
         // 两级缓存中都获取不到数据,那么就需要从数据源重新拉取数据,重建缓存
         // 但是这里暂时不讲
         log.info("缓存重建 商品信息");
     }
     return productInfo;
 }
​
 @RequestMapping("/getShopInfo")
 @ResponseBody
 public ShopInfo getShopInfo(Long shopId) {
     ShopInfo shopInfo = cacheService.getShopInfoOfReidsCache(shopId);
     log.info("从 redis 中获取店铺信息");
     if (shopInfo == null) {
         shopInfo = cacheService.getShopInfoFromLocalCache(shopId);
         log.info("从 ehcache 中获取店铺信息");
     }
     if (shopInfo == null) {
         // 两级缓存中都获取不到数据,那么就需要从数据源重新拉取数据,重建缓存
         // 但是这里暂时不讲
         log.info("缓存重建 店铺信息");
     }
     return shopInfo;
 }

CacheService

/**
 * 从 redis 中获取商品
 */
ProductInfo getProductInfoOfReidsCache(Long productId);
​
/**
 * 从 redis 中获取店铺信息
 */
ShopInfo getShopInfoOfReidsCache(Long shopId);
​

CacheServiceImpl

@Override
public ProductInfo getProductInfoOfReidsCache(Long productId) {
    String key = "product_info_" + productId;
    String json = jedisCluster.get(key);
    return JSON.parseObject(json, ProductInfo.class);
}
​
@Override
public ShopInfo getShopInfoOfReidsCache(Long shopId) {
    String key = "shop_info_" + shopId;
    String json = jedisCluster.get(key);
    return JSON.parseObject(json, ShopInfo.class);
}

测试应用层

访问地址:http://nginx02/product?productId=1&shopId=1

tail -f /usr/servers/nginx/logs/error.log
可以看到如下的错误:
​
2019/05/06 22:46:59 [error] 8834#0: *46 lua entry thread aborted: runtime error: /usr/hello/lualib/resty/http.lua:929: bad argument #2 to 'set_keepalive' (number expected, got nil)
stack traceback:
coroutine 0:
    [C]: in function 'set_keepalive'
    /usr/hello/lualib/resty/http.lua:929: in function 'request_uri'
    /usr/hello/lua/product.lua:24: in function </usr/hello/lua/product.lua:1>, client: 192.168.99.1, server: _, request: "GET /product?productId=1&shopId=1 HTTP/1.1", host: "nginx02"

这次通过 debug 后端服务,服务中能请求到了,响应之后报错 bad argument #2 to 'set_keepalive'

这个问题在前面记忆中已经解决过了,设置下 keepalive=false 即可

local resp, err = httpc:request_uri("http://192.168.99.111:6002",{
    method = "GET",
    path = "/getShopInfo?shopId="..shopId,
    keepalive=false
})

再次访问:http://nginx02/product?productId=1&shopId=1

product id: 1
product name: iphone7手机
product picture list: a.jpg,b.jpg
product specification: iphone7的规格
product service: iphone7的售后服务
product color: 红色,白色,黑色
product size: 5.5
shop id: 1
shop name: 小王的手机店
shop level: 5
shop good cooment rate: 0.99

如果响应的中文乱码,需要在拦截的地方添加编码

location /product {
  default_type 'text/html';
  charset utf-8;
  content_by_lua_file /usr/hello/lua/product.lua;
}

优化模板文件

vi /usr/hello/templates/product.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta 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>

记得重启 /usr/servers/nginx/sbin/nginx -s reload

再次访问:http://nginx02/product?productId=1&shopId=1

商品 ID: 1
商品名称: iphone7手机
商品图片列表: a.jpg,b.jpg
商品规格: iphone7的规格
商品售后服务: iphone7的售后服务
商品颜色: 红色,白色,黑色
商品尺寸: 5.5
店铺 ID: 1
店铺名称: 小王的手机店
店铺级别: 5
店铺评分: 0.99

测试分发层

刚刚应用层已经测试通过,现在来从分发层测试

访问:http://nginx03/product?method=product&productId=1&shopId=1

::: tip 注意,由于使用的是 hash 分发,可以在 nginx01 和 nginx02 上显示访问日志

tail -f /usr/servers/nginx/logs/access.log

这样就能看到被分发到哪台机器上去了。 :::

成功响应 html 信息

商品 ID: 1
商品名称: iphone7手机
商品图片列表: a.jpg,b.jpg
商品规格: iphone7的规格
商品售后服务: iphone7的售后服务
商品颜色: 红色,白色,黑色
商品尺寸: 5.5
店铺 ID: 1
店铺名称: 小王的手机店
店铺级别: 5
店铺评分: 0.99

这里来多次访问:http://nginx03/product?method=product&productId=1&shopId=1, 并观察后台打印日志信息

2019-05-07 21:37:24.009  INFO 5792 --- [nio-6002-exec-1] c.m.c.e.c.controller.CacheController     : 从 redis 中获取商品信息
2019-05-07 21:37:24.275  INFO 5792 --- [nio-6002-exec-3] c.m.c.e.c.controller.CacheController     : 从 redis 中获取店铺信息

第一次访问的时候,nginx 本地缓存没有,会去 redis 中获取,后面多次访问的时候, 就会走 nginx 本地缓存了,过期时间设置的是 10 分钟

来回顾下流程:

  1. 缓存数据生产

    有数据变更则主动更新两级缓存(ehcache + redis)

    通过缓存维度化拆分,来达到细粒度和小影响更新缓存

  2. 分发层 ngix + 应用层 nginx

    自定义流量分发策略,提高缓存命中

    nginx shared dice 缓存 -> redis 和 ehcache,

    渲染 htm 模板并返回客户端

但是还差一个关键的要点,当前面的三级缓存失效(nginx、redis、ehcache)时, 就需要缓存服务重新拉取数据,去更新到 redis 和 ehcache 中。

这个关键点涉及到分布式缓存重建并发冲突问题

 
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值