一、多级缓存
高并发的的多级缓存实际上是为了解决Tomcat的压力。
1.1 JVM进程缓存
利用Caffeine进程缓存技术来实现JVM进程缓存。
1.1.1 测试Caffeine
- 导入依赖
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
- 测试代码
// 构建cache对象
Cache<String, String> cache = Caffeine.newBuilder().build();
// 存数据
cache.put("gf", "迪丽热巴");
// 取数据
String gf = cache.getIfPresent("gf");
System.out.println("gf = " + gf);
// 取数据,如果未命中,则查询数据库
String defaultGF = cache.get("defaultGF", key -> {
// 根据key去数据库查询数据
return "柳岩";
}); //如果从缓存中没有读到数据就通过lamda表达式从数据库查出保存并返回
System.out.println("defaultGF = " + defaultGF);
1.1.2 Caffeine缓存的空间有限制,需要做缓存驱逐策略
- 基于容量
通过设置缓存的数量上限,Caffeine.newBuilder().maximumSize(1).build()//设置大小为1
- 基于过期时间
Caffeine.newBuilder().expireAfterWrite(Duration.ofSeconds(10)).build()//设置10秒过期
- 基于引用
设置缓存为软引用或者弱引用,利用GC来回收,性能差,不建议使用。
1.2 Nginx缓存
Nginx里代码需要通过Lua进行编写
1.2.1 Lua
Lua是一个小巧的脚本语言,在CentOs里有自带Lua。
- lua中的数据类型
- 声明一个局部变量使用local,不声明为全局变量
- table类似于map和数组
local xx = {name="张三",age=15}
local xxx = {1,2,3,12,23}
- 字符串拼接 ..
local str = “xxx” .. “xxxx”
- for 循环,index,value in ipair(arr) 解析数组出一对键值对给前面。for循环do为开始,end为结束。
for index,value in ipairs(arr) do
>> print("index" .. index .." value:" .. value)
>> end
- 遍历map和数组类似,但是解析需要使用pairs(map)
for index,value in pairs(map) do
>> print("index:" .. index .. " value:" .. value)
>> end
- 声明函数
function add(a,b)
>> return a+b
>> end
- 条件控制
unction compare(a,b)
>> if(a>b)
>> then
>> print(a .. "大于" .. b)
>> else if(a<b)
>> then print(a .. "小于" .. b)
>> else
>> print(a .. "等于" .. b)
>> end
- 操作符
and 、or 、not(逻辑非)
1.2.2 安装OpenResty
OpenResty是一个基于Nginx的高性能web平台,用于方便地搭建能够处理超高并发、扩展性极高的动态Web应用、Web服务和动态网关。具备下列特点:
。具备Nginx的完整功能
。基于Lua语言进行扩展,集成了大量精良的Lua库、第三方模块·允许使用Lua自定义业务逻辑、自定义库
安装OpenResty
https://blog.csdn.net/qq_46624276/article/details/126641868?spm=1001.2014.3001.5501
1.2.3 修改nginx.conf
- 在http下面添加对OpenResty的Lua模块加载
#lua 模块
lua_package_path "/usr/local/openresty/lualib/?.lua;;";
#c模块
lua_package_cpath "/usr/local/openresty/lualib/?.so;;";
- 在server下面添加对/api/item路径的监听
localtion /api/item {
#响应类型
default_type application/json;
#响应的数据由Lua决定
content_by_lua_file lua/item.lua;
}
1.2.4 编写 item.lua
- 进入openresty的nginx目录新建./lua/item.lua
编写以下语句,相当于响应体write
ngx.say(“{“id”:10001,“name”:SALAS AIR}”)
1.2.4 lua返回真实参数
-
路径占位符需要通过正则表达式匹配
- /item/(\d+) 。 ~为正则表达式匹配, (\d+)为所有数字,至少一个字符。
需要修改匹配路径为 location ~ /api/item/(\d+)
1.2.4 nginx Http请求后端获取数据
nginx提供的http请求方式
resq的三个返回的属性
- resp.status:状态码
- resp.header:响应头,是一个table
- resp.body:响应体
path会被nginx自己监听到,需要通过自己反向代理到tomcat
location /item{
proxy_pass http://192.168.229.128:8081;
}
1.2.5 编写请求工具类common.lua,放在lualib里面
-- 封装函数,发送http请求,并解析响应
local function read_http(path, params)
local resp = ngx.location.capture(path,{
method = ngx.HTTP_GET,
args = params,
})
if not resp then
-- 记录错误信息,返回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
1.2.6 编写业务lua
通过导入cjson实现序列化和反序列化
--导入common函数
local common = require('common')
local cjson = require('cjson')
local read_http = common.read_http
--获取路径参数
local id = ngx.var[1]
--查询商品信息
local itemJSON = read_http("/item/" .. id,nil)
local item = cjson.decode(itemJSON)
--查询库存信息
local stockJSON = read_http("/item/stock/" .. id,nil)
local stock = cjson.decode(stockJSON)
item.sold = stock.sold
item.stock = stock.stock
-- 序列化为json返回
ngx.say(cjson.encode(item))
对于请求,进来会通过nginx转发到另一个缓存nginx,缓存nginx查询后端,如果配置集群,只有访问到的那一台服务器有数据,所以不使用轮询,使用hash $request_uri;对请求路径做hash运算,相同数据就访问一个服务器。
1.3 Nginx访问Redis缓存
冷启动:服务刚刚启动时,Redis中并没有缓存,如果所有商品数据都在第一次查询时添加缓存,可能会给数据库带来较大压力。
缓存预热︰在实际开发中,我们可以利用大数据统计用户访问的热点数据,在项目启动时将这些热点数据提前查询并保存到Redis中。
1.3.1 启动redis,项目里做缓存预热
docker run -d --name redis -p 6379:6379 redis redis-server --appendonly yes
导入依赖,加入配置
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
redis:
host: 192.168.229.129
使用RedisHandler继承InitializingBean,重写afterPropertiesSet方法,实现预热缓存.
1.3.2 Lua创建和释放Redis对象
初始化redis
--导入redis
local redis = require('resty.redis')
-- 初始化redis
local red = redis:new()
red:set_timeouts(1000,1000,1000)
封装释放连接
local function close_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读取
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
封装查找数据(先找redis,没命中就查询tomcat)
local function read_data(key,path,params)
--查询redis
local resp = read_redis("192.168.229.129","6379",key)
--判断redis是否命中
if not resp then
--redis命中失败,查询http
read_http(path,params)
end
return resp
end
1.5 Nginx本地缓存
OpenResty为Nginx提供了shard dict的功能,可以在nginx的多个worker之间共享数据,实现缓存功能。
1.5.1 nginx.conf http下配置
#开启共享缓存为item_cache 大小为150m
lua_shared_dict item_cache 150m;