多级缓存的原理和实现

一、多级缓存
高并发的的多级缓存实际上是为了解决Tomcat的压力。
多级缓存的原理和实现_第1张图片
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进行编写
多级缓存的原理和实现_第2张图片
1.2.1 Lua

Lua是一个小巧的脚本语言,在CentOs里有自带Lua。

  • lua中的数据类型
    多级缓存的原理和实现_第3张图片
  • 声明一个局部变量使用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+)
多级缓存的原理和实现_第4张图片

1.2.4 nginx Http请求后端获取数据
nginx提供的http请求方式
多级缓存的原理和实现_第5张图片

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运算,相同数据就访问一个服务器。
多级缓存的原理和实现_第6张图片
多级缓存的原理和实现_第7张图片
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方法,实现预热缓存.
多级缓存的原理和实现_第8张图片

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;

你可能感兴趣的:(java,缓存,lua,开发语言)