目录
1、前言
2、分发层实现
3、应用层实现
4、总结
1、前言
eshop-cache01和eshop-cache02作为应用层nginx,用eshop-cache03作为分发层nginx
在eshop-cache03,也就是分发层nginx中,编写lua脚本,完成基于商品id的流量分发策略。
2、分发层实现
1、获取请求参数,比如productId
2、对productId进行hash
3、hash值对应用服务器数量取模,获取到一个应用服务器
4、利用http发送请求到应用层nginx
5、获取响应后返回
这个就是基于商品id的定向流量分发的策略,lua脚本来编写和实现:
作为一个流量分发的nginx,会发送http请求到后端的应用nginx上面去,所以要先引入lua http lib包,下载:
cd /usr/hello/lualib/resty/ wget /pintsized/lua-resty-http/master/lib/resty/http_headers.lua wget /pintsized/lua-resty-http/master/lib/resty/http.lua
vi lua/hello.lua
local uri_args = ngx.req.get_uri_args()local productId = uri_args["productId"]local host = {"192.168.31.19", "192.168.31.187"}local hash = ngx.crc32_long(productId)hash = (hash % 2) + 1 backend = "http://"..host[hash]local method = uri_args["method"]local requestBody = "/"..method.."?productId="..productIdlocal http = require("resty.http") local httpc = http.new() local resp, err = httpc:request_uri(backend, { method = "GET", path = requestBody})if not resp then ngx.say("request error :", err) return endngx.say(resp.body) httpc:close()
注:
1、lua中连接字符串用..
2、local http = require("resty.http") 表示引用程序包
/usr/servers/nginx/sbin/nginx -s reload
基于商品id的定向流量分发策略的lua脚本就开发完了,而且也测试过了
我们就可以看到,如果你请求的是固定的某一个商品,那么就一定会将流量打到固定的一个应用nginx上面去
3、应用层实现
分发层nginx,lua应用,会将商品id,商品店铺id,都转发到后端的应用nginx
/usr/servers/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
cd /usr/hello/lualib/resty/ wget /pintsized/lua-resty-http/master/lib/resty/http_headers.lua wget /pintsized/lua-resty-http/master/lib/resty/http.lua
5、如果缓存数据生产服务没有在redis分布式缓存中没有获取到数据,那么就在自己本地ehcache中获取数据,返回数据给nginx,也要设置到nginx本地缓存中
6、如果ehcache本地缓存都没有数据,那么就需要去原始的服务中拉去数据,该服务会从mysql中查询,拉去到数据之后,返回给nginx,并重新设置到ehcache和redis中
这里先不考虑,后面要专门讲解一套分布式缓存重建并发冲突的问题和解决方案
7、nginx最终利用获取到的数据,动态渲染网页模板
首先要下载模板组件:
cd /usr/hello/lualib/resty/wget /bungle/lua-resty-template/master/lib/resty/template.luamkdir /usr/hello/lualib/resty/htmlcd /usr/hello/lualib/resty/htmlwget /bungle/lua-resty-template/master/lib/resty/template/html.lua
在hello.conf的server中配置模板位置
set $template_location "/templates"; set $template_root "/usr/hello/templates";
mkdir /usr/hello/templates
vi product.html
<html><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
hello.conf中:
lua_shared_dict my_cache 128m;
vi /usr/servers/ngnix/conf
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_cachelocal productCacheKey = "product_info_"..productIdlocal shopCacheKey = "shop_info_"..shopIdlocal productCache = cache_ngx:get(productCacheKey)local shopCache = cache_ngx:get(shopCacheKey)if productCache == "" or productCache == nil thenlocal http = require("resty.http")local httpc = http.new()local resp, err = httpc:request_uri("http://192.168.31.179:8080",{method = "GET",path = "/getProductInfo?productId="..productId})productCache = resp.bodycache_ngx:set(productCacheKey, productCache, 10 * 60)endif shopCache == "" or shopCache == nil thenlocal http = require("resty.http")local httpc = http.new()local resp, err = httpc:request_uri("http://192.168.31.179:8080",{method = "GET",path = "/getShopInfo?shopId="..shopId})shopCache = resp.bodycache_ngx:set(shopCacheKey, shopCache, 10 * 60)endlocal 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)
Java Controller层的代码
@RequestMapping("/getProductInfo")@ResponseBodypublic ProductInfo getProductInfo(Long productId) {ProductInfo productInfo = null;productInfo = cacheService.getProductInfoFromReidsCache(productId);System.out.println("=================从redis中获取缓存,商品信息=" + productInfo); if(productInfo == null) {productInfo = cacheService.getProductInfoFromLocalCache(productId);System.out.println("=================从ehcache中获取缓存,商品信息=" + productInfo); }if(productInfo == null) {// 就需要从数据源重新拉去数据,重建缓存,但是这里先不讲}return productInfo;}
@RequestMapping("/getShopInfo")@ResponseBodypublic ShopInfo getShopInfo(Long shopId) {ShopInfo shopInfo = null;shopInfo = cacheService.getShopInfoFromReidsCache(shopId);System.out.println("=================从redis中获取缓存,店铺信息=" + shopInfo); if(shopInfo == null) {shopInfo = cacheService.getShopInfoFromLocalCache(shopId);System.out.println("=================从ehcache中获取缓存,店铺信息=" + shopInfo); }if(shopInfo == null) {// 就需要从数据源重新拉去数据,重建缓存,但是这里先不讲}return shopInfo;}
最终效果:
至此已经OK
第一次访问的时候,其实在nginx本地缓存中是取不到的,所以会发送http请求到后端的缓存服务里去获取,会从redis中获取
拿到数据以后,会放到nginx本地缓存里面去,过期时间是10分钟
然后将所有数据渲染到模板中,返回模板
以后再来访问的时候,就会直接从nginx本地缓存区获取数据了
4、总结
总体流程:
缓存数据生产 -> 有数据变更 -> 主动更新两级缓存(ehcache+redis)-> 缓存维度化拆分
分发层nginx + 应用层nginx -> 自定义流量分发策略提高缓存命中率
nginx shared dict缓存 -> 缓存服务 -> redis -> ehcache -> 渲染html模板 -> 返回页面
至此貌似基本都讲完了,但是还差最后一个很关键的要点,就是如果你的数据在nginx -> redis -> ehcache三级缓存都不在了,可能就是被LRU清理掉了
这个时候缓存服务会重新拉去数据,去更新到ehcache和redis中,这里我们还没讲解
分布式的缓存重建的并发问题。