缓存数据生产服务那一层已经搞定了,相当于三层缓存架构中的本地堆缓存 + 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 的流量分发策略
这里简化业务逻辑,实际上在你的公司中,你可以随意根据自己的业务逻辑和场景,去制定自己的流量分发策略
步骤如下:
-
获取请求参数,比如 productId
-
对 productId 进行 hash
-
hash 值对应用服务器数量取模,获取到一个应用服务器
-
利用 http 发送请求到应用层 nginx
-
获取响应后返回
为了能分发看出来效果,我们把之前 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 完成多级缓存架构的核心业务逻辑
脚本思路:
-
应用 nginx 的 lua 脚本接收到请求
-
获取请求参数中的商品 id,以及商品店铺 id
-
根据商品 id 和商品店铺 id,在 nginx 本地缓存中尝试获取数据
-
如果在 nginx 本地缓存中没有获取到数据,那么就到 redis 分布式缓存中获取数据,如果获取到了数据,还要设置到 nginx 本地缓存中
但是这里有个问题,建议不要用 nginx+lua 直接去获取 redis 数据
因为 openresty 没有太好的 redis cluster 的支持包,所以建议是发送 http 请求到缓存数据生产服务,由该服务提供一个 http 接口
缓存数生产服务可以基于 redis cluster api 从 redis 中直接获取数据,并返回给 nginx
-
如果缓存数据生产服务没有在 redis 分布式缓存中没有获取到数据,那么就在自己本地 ehcache 中获取数据,返回数据给 nginx,也要设置到 nginx 本地缓存中
-
如果 ehcache 本地缓存都没有数据,那么就需要去原始的服务中拉取数据,该服务会从 mysql 中查询,拉取到数据之后,返回给 nginx,并重新设置到 ehcache和 redis 中
这里先不考虑并发问题,后面要专门讲解一套分布式缓存重建并发冲突的问题和解决方案
-
nginx 最终利用获取到的数据,动态渲染网页模板
-
将渲染后的网页模板作为 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 分钟
来回顾下流程:
-
缓存数据生产
有数据变更则主动更新两级缓存(ehcache + redis)
通过缓存维度化拆分,来达到细粒度和小影响更新缓存
-
分发层 ngix + 应用层 nginx
自定义流量分发策略,提高缓存命中
nginx shared dice 缓存 -> redis 和 ehcache,
渲染 htm 模板并返回客户端
但是还差一个关键的要点,当前面的三级缓存失效(nginx、redis、ehcache)时, 就需要缓存服务重新拉取数据,去更新到 redis 和 ehcache 中。
这个关键点涉及到分布式缓存重建并发冲突问题