提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
前言
随着数据量,访问量及用户的增加,增加服务器等措施已经不能满足需求,高频访问数据库对数据库的压力仍然很大,而“加一层”可以有效缓解对数据库的压力,这里可以对数据进行多级操作,将他存储在缓存中以减轻对数据库的压力,它包括浏览器缓存,nginx缓存,tomcat缓存及redis等第三方技术
一、JVM进程缓存
JVM进程缓存,即将数据缓存到tomcat中,这里用到的是Caffeine
1.1Caffeine
缓存主要分为2种,reids第三方缓存和jvm本地缓存
- 分布式缓存,例如Redis:
- 优点:存储容量更大、可靠性更好、可以在集群间共享
- 缺点:访问缓存有网络开销
- 场景:缓存数据量较大、可靠性要求较高、需要在集群间共享
- 进程本地缓存,例如HashMap、GuavaCache:
- 优点:读取本地内存,没有网络开销,速度更快
- 缺点:存储容量有限、可靠性较低、无法共享
- 场景:性能要求较高,缓存数据量较小
Caffeine是基于java8的本地缓存,在众多本地缓存中,他的性能遥遥领先
1.1.1Caffeine使用步骤
1.导入依赖
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
2.创建缓存对象
Cache<T,T> cache = Caffeine.newBuilder().build();
在newBuilder()后面可以继续设置 Caffeine的属性,如初始大小initialCapacity,最大容量(键)maximumSize,过期时间expireAfterWrite
常用api
- getIfPresent():获取一个存在的键,如果不存在就返回null
- get():查询目标键,返回对应的值,如果不存在则去数据库查找
@Test
void testBasicOps() {
// 创建缓存对象
Cache<String, String> cache = Caffeine.newBuilder().build();
// 存数据
cache.put("gf", "迪丽热巴");
// 取数据,不存在则返回null
String gf = cache.getIfPresent("gf");
System.out.println("gf = " + gf);
// 取数据,不存在则去数据库查询
String defaultGF = cache.get("defaultGF", key -> {
// 这里可以去数据库根据 key查询value
return "柳岩";
});
System.out.println("defaultGF = " + defaultGF);
}
1.2Caffeine基于SpringBoot的使用
1.2.1创建配置类
@Component
public class CaffeineConfig {
@Bean
public Cache<Long, Item> itemCache(){
return Caffeine.newBuilder()
//初始化大小为100
.initialCapacity(100)
//最大键容量为10000
.maximumSize(10_000)
.build();
}
@Bean
public Cache<Long, ItemStock> stockCache(){
return Caffeine.newBuilder()
.initialCapacity(100)
.maximumSize(10_000)
.build();
}
}
1.2.2自动注入后使用
二、nginx本地缓存
基于nginx实现本地缓存需要借助于OpenResty和Lua
Lua教程:https://www.runoob.com/lua/lua-tutorial.html
OpenResty安装:https://blog.csdn.net/FeenixOne/article/details/124879020
2.1为什么要使用OpenResty和Lua
OpenResty 是一个基于 Nginx的高性能 Web 平台,用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。能在其中使用lua脚本进行逻辑处理,具备下列特点:
- 具备Nginx的完整功能
- 基于Lua语言进行扩展,集成了大量精良的 Lua 库、第三方模块
- 允许使用Lua自定义业务逻辑、自定义库
Lua:lua是一种轻量级的脚本语言,可以保证事务的完整性
2.2基于OpenResty和Lua实现本地缓存
OpenResty目录机构
nginx
nginx.conf
2.2.1 修改nginx.conf文件
文件位置:OpenResty/nginx/conf/nginx.conf
1.设置监听路径
location 监听路径(如:/api/item) {
# 默认的响应类型
default_type application/json;
# 响应结果由lua目录下的某个lua文件来决定,如:lua/item.lua
content_by_lua_file 文件位置;
}
2.2.2编写lua文件
编写执行业务逻辑的Lua文件,如上面说到的lua/item.lua文件
2.2.2.1 获取参数的API
2.2.3 将数据缓存到nginx
1.修改配置
2.具体逻辑写入执行逻辑的Lua文件中
三、查询redis
OpenResty提供了操作Redis的模块,我们只要引入该模块就能直接使用
3.1引入redis并初始化
-- 导入redis
local redis = require('resty.redis')
-- 初始化redis
local red = redis:new()
red:set_timeouts(1000, 1000, 1000)
3.2封装redis工具类
-- 关闭redis连接的工具方法,其实是放入连接池
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的方法 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
完整的工具类lua文件
--导入redis
local redis = require("resty.redis")
--初始化redis
local red = redis:new()
red:set_timeouts(1000,1000,1000)
-- 关闭redis连接的工具方法,其实是放入连接池
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的方法 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
-- 封装函数,发送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,
read_redis = read_redis
}
return _M
3.1.3 在执行文件中的使用
三、查询tomcat
nginx提供了内部API用以发送http请求:
local resp = ngx.location.capture("/path",{
method = ngx.HTTP_GET, -- 请求方式
args = {a=1,b=2}, -- get方式传参数
})
返回的响应内容包括:
- resp.status:响应状态码
- resp.header:响应头,是一个table
- resp.body:响应体,就是响应数据
注意:这里的path是路径,并不包含IP和端口。这个请求会被nginx内部的server监听并处理。
但是我们希望这个请求发送到Tomcat服务器,所以还需要编写一个server来对这个路径做反向代理:
具体的方法已经写好分装到工具类中
-- 封装函数,发送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