当前位置: 首页 > news >正文

多级缓存的原理和实现

一、多级缓存
高并发的的多级缓存实际上是为了解决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;

相关文章:

  • hadoop学习使用
  • 【WSL2】CENTOS7 安装与配置
  • Python调用OpenCV接口播放本地视频文件、本地和网络摄像头
  • 推进智慧工地建设,智慧工地是什么?建筑工地人必看!
  • 进阶笔录-深入理解Java线程之Synchronized
  • Python性能测试工具汇总
  • java基础之浅聊阻塞队列BlockingQueue
  • 单分散亚微米聚苯乙烯—聚乙酸乙烯酯(P(St-VAc))聚合物微球/聚苯乙烯塑料微球聚乙烯醇相关知识
  • frp记录
  • 阿里巴巴按关键字搜索商品 API 返回值说明
  • 天花板级别的python读取文件方法,真的香.......
  • Hadoop伪分布式搭建
  • 【Java】博图S7通讯仿真测试上位机连接
  • 【FPGA教程案例71】基础操作1——Xilinx原语学习及应用1
  • 解决pycharm无法使用install package
  • 9月CHINA-PUB-OPENDAY技术沙龙——IPHONE
  • 【MySQL经典案例分析】 Waiting for table metadata lock
  • Angular js 常用指令ng-if、ng-class、ng-option、ng-value、ng-click是如何使用的?
  • Bootstrap JS插件Alert源码分析
  • Centos6.8 使用rpm安装mysql5.7
  • CentOS从零开始部署Nodejs项目
  • Dubbo 整合 Pinpoint 做分布式服务请求跟踪
  • Fabric架构演变之路
  • Java到底能干嘛?
  • JS笔记四:作用域、变量(函数)提升
  • js作用域和this的理解
  • MD5加密原理解析及OC版原理实现
  • QQ浏览器x5内核的兼容性问题
  • Quartz实现数据同步 | 从0开始构建SpringCloud微服务(3)
  • VuePress 静态网站生成
  • 力扣(LeetCode)357
  • 前端技术周刊 2018-12-10:前端自动化测试
  • 实习面试笔记
  • 写代码的正确姿势
  • 自制字幕遮挡器
  • elasticsearch-head插件安装
  • ​七周四次课(5月9日)iptables filter表案例、iptables nat表应用
  • # 日期待t_最值得等的SUV奥迪Q9:空间比MPV还大,或搭4.0T,香
  • ## 临床数据 两两比较 加显著性boxplot加显著性
  • (11)MSP430F5529 定时器B
  • (2)STM32单片机上位机
  • (2022版)一套教程搞定k8s安装到实战 | RBAC
  • (4)logging(日志模块)
  • (C语言)字符分类函数
  • (done) ROC曲线 和 AUC值 分别是什么?
  • (一)spring cloud微服务分布式云架构 - Spring Cloud简介
  • (转)树状数组
  • (转)四层和七层负载均衡的区别
  • .“空心村”成因分析及解决对策122344
  • .form文件_一篇文章学会文件上传
  • .NET 材料检测系统崩溃分析
  • .net 程序发生了一个不可捕获的异常
  • .NET 使用配置文件
  • .Net接口调试与案例
  • .Net面试题4