4.3 多级缓存
一、安装OpenResty
OpenResty是一个基于Nginx的高性能Web平台,用于方便的搭建能够处理高并发。扩展性极高的动态Web应用、Web服务和动态网关。具备下列特点,官网地址:http://openresty.org/cn/
具备Nginx的完整功能
基于Lua语言进行扩展,集成了大量精良的Lua库、第三方模块
允许使用Lua自定义业务逻辑,自定义库
Linux安装:查看附录
二、OpenResty快速入门
假如反向代理的nginx中有HTML页面,内容从OpenResty中获取
修改nginx.conf文件
修改nginx.conf的http下面,添加对OpenResty的Lua模块的加载:
# 加载lua模块 lua_package_path "/usr/local/openresty/lualib/?.lua;;"; # 加载C模块 lua_package_cpath "/usr/local/openresty/lualib/?.os;;";在nginx.conf的server下面,添加对指定路径的监听:
location /api/item { # 响应类型,这里返回json default_type application/json; # 相应数据由 lua/item.lua(自定义的文件)这个文件来决定 content_by_lua_file lia/item.lua; }备注:这里的/api/item表示被监听的路径
在OpenResty的nginx目录下创建一个lua文件,命名item.lua(上一步配置)
编写文件
-- 编写假数据,直接返回假数据做测试,ngx.say函数,就是写数据到Response中 ngx.say('{"id":1001, "name": "testName"}')重新加载nginx
nginx -s reload
三、请求参数处理
OpenResty提供了各种API来获取不同类型的请求参数:
参数格式参数示例参数解析代码示例路径占位符
/item/1001
1. 正则表达式匹配: localtion ~ /item/(\d+) { content_by_lua_file lua/item.lua; } 2.匹配到的参数会存入ngx.var数组中,可以用角标获取 local id = ngx.ver[1]
请求头
Id: 1001
-- 获取请求头返回值是table类型 local headers = ngx.req.get_headers()
Get请求参数
?id=1001
-- 获取GET请求参数,返回值是table类型 local getParams = ngx.req.get_uri_args()
Post表单参数
id=1001
-- 读取请求体 ngx.req.read_body() -- 获取POST表单参数,返回值是table类型 local postParams = ngx.req.get_post_args()
JSON参数
{"id": 1001}
-- 读取请求体 ngx.req.read_body() -- 获取body中的json参数,返回值是string类型 local jsonBody = ngx.req.get_body_data()
四、查询Tomcat
nginx内部发送Http请求
nginx提供了内部API用以发送http请求:
local resp = ngx.location.capture("/path", { method = ngx.HTTP_GET, -- 指定请求方式 args = {a=1, b=2}, -- get请求传参 body = "c=3&d=4" -- post方式传参 })备注:返回的相应内容包括
resp.status:相应状态码
resp.header:响应头,是一个table
resp.body:响应体,就是响应数据
注意:这里的path是路径,并不包含IP和端口。这个请求会被nginx内部的server监听并处理
所以,需要在nginx.conf中添加配置,转发到对应的服务器中
location /path { # 这里是对应服务的ip和Java服务端口 proxy_pass http://ip:端口 }因为http请求可能会经常使用,因此,给封装成函数
在/usr/local/openresty/lualib目录下创建common.lua文件
在内部封装http查询函数
-- 封装函数,发送http请求,并解析响应 local function read_http(path, params) local resp = ngx.location.capture(path,{ -- 传参路径path method = ngx.HTTP_GET, -- 做GET请求 args = params, -- get请求参数 }) if not resp then -- 如果resp为空(nil),not nil = true -- 记录错误信息,返回404 ngx.log(ngx.ERR, "http not found, path: ", path , ", args: ", args) ngx.exit(404) end return resp.body -- 返回内容 end -- 将方法导出,也就是将整个文件导出,只要使用这个文件,就可以获取全部函数 local _M = { read_http = read_http } return _M使用方式:
-- 导入common函数库 local common = require("common") local read_http = common.read_http -- 导入cjson库 local cjson = require("cjson") -- 获取参数路径 local id = ngx.var[1] -- 查询1 local data = read_http("/item/" .. id, nil) -- 查询2 local data2 = ………… -- 拼接数据(结合下一知识点,json解析) local data1Table = cjson.decode(data1) local data2Table = cjson.decode(data2) -- 赋值获取数据啥的,直接.名字即可 data1Table.stock = data2Table.stock data1Table.sold = data2Table.sold -- 返回结果 ngx.say(cjson.encode(data1Table))备注:导包时候需要用变量来接收,require后面的是文件的名字!(如果有目录,需要带上目录名)
OpenResty对多段json结果合并处理
OpenResty提供了cjson模块来进行json的序列化和反序列化,官网地址:https://github.com/openresty/lua-cjson
引入cjson模块
local cjson = require("cjson")序列化
local obj = { name = 'jack', age = 21 } local json = cjson.encode(obj)反序列化
local json = '{"name": "jack", "age": 21}' local obj = cjson.decode(json)
Tomcat集群的负载均衡
配置负载均衡
# tomcat集群配置 upstream tomcat-cluster { server ip1:端口; server ip2:端口; ………… } # 反向代理配置,将请求代理到集群中 location /item { proxy_pass http://tomcat-cluster; }备注:因为JVM的进程缓存无法共享,而这样配置Nginx又是造成轮询请求,因此同样的请求可能访问在不同的服务上,造成进程缓存冗余
上述备注问题解决办法
# tomcat集群配置 upstream tomcat-cluster { hash $request_uri; server ip1:端口; server ip2:端口; ………… }备注:对请求的路径做哈希运算,然后对集群数量取余,就可以指定某个请求请求指定服务器
五、Redis预存预热
冷启动与预热缓存
冷启动:服务刚刚启动,Redis中并没有缓存,如果所有商品数据都在第一次查询时候添加缓存,可能会给赎回句酷带来较大压力
缓存预热:在实际开发中,我们可以利用大数据统计用户访问的热点数据,在项目启动时将这些热点数据提前查询并保存到Redis中
缓存预热
在Java代码项目中添加Redis的坐标以及链接操作等,在Java服务启动时候,就会将数据存入Redis中
// 编写初始化缓存的业务逻辑 @Component public class RedisHandler implments InitializingBean { @Autowirred private StringRedisTemplate redisTemplate; // 注入查询用的service // 引入Spring自带的json处理工具 private static final ObjectMapper MAPPER = new ObkectMapper(); @Override public void afterPropertiesSet() throes Exception() { // 初始化缓存操作 // 1.查询商品信息(大量数据时,查热点信息) // 2.放入缓存 // 2.1 循环遍历,将实体转化为JSON,使用Spring自带的工具转换 String json = MAPPER.writeValueAsString(循环出来的实体类); // 2,2 将JSON存入redis中,将商品id为key添加 redisTemplate.opsForValue().set("item:id:" + item.getId(), json); } }
六、查询Redis缓存
OpenResty提供了操作Redis的模块,只要我们引入该模块就可以使用
引入Redis模块,并初始化Redis对象
-- 引入Redis模块 local redis = require("resty.redis") -- 初始化Redis对象 local red = redis:new() -- 设置Redis超时时间 red:set_timeouts(1000, 1000, 1000)封装函数,用来释放Redis链接,其实是放入连接池
-- 关闭redis连接工具的方法,其实时放入链接池 lcoal function closr_redis(red) local pool_max_idle_time = 10000 -- 连接的空闲时间,单位是毫秒 local pool_size = 100 -- 连接池大小 local ok, err = red:set_keepalive(pool_max_idle_time, pool_size) if not ok then ngx.log(ngx.ERR, "放入Redis连接池失败:", err) end end封装函数,从Redis读取数据并返回
-- 查询redis的方法 ip和port是redis地址,key是查询的key local function read_redis(ip, port, key) -- 获取一个连接 local ok, err = red:connect(ip, port) if not ok then ngx.log(ngx.ERR, "连接redis失败 : ", err) return nil end -- 查询redis local resp, err = red:get(key) -- 查询失败处理 if not resp then ngx.log(ngx.ERR, "查询Redis失败: ", err, ", key = " , key) end --得到的数据为空处理 if resp == ngx.null then resp = nil ngx.log(ngx.ERR, "查询Redis数据为空, key = ", key) end close_redis(red) return resp end
备注:上面的内容存放到common中,添加暴露read方式
直接在 local _M = { read_http = read_http } 中添加redis的读取函数名即可OpenResty查询,先查询Redis操作
-- 封装函数,先查询Redis,再查询http local function read_data(key, path, params) -- 查询redis local resp = read_redis("127.0.0.1", 6379, key) -- 判断redis是否命中 if not resp then -- Redis查询失败,查询http resp = read_http(path, params) end return resp end
七、Nginx本地缓存——在OpenResty中添加本地缓存
OpenResty为Nginx提供了shard dict的功能,可以在Nginx的多个worker之间共享数据,实现缓存功能
开启共享字典,在nginx.config的http下添加配置:
# 共享字典,也就是本地缓存,名称叫做item_cache,大小150M lua_shared_dict item_cache 150m;操作共享字典:
-- 获取本地缓存对象 lua item_cache = ngx.shared.item_cache -- 存储,指定key, value, 过期时间,单位s,默认为0表示永不过期 item_cache:set('key', 'value', 1000) -- 读取 local val = item_cache:get('key')
最后更新于
这有帮助吗?