• 多级缓存的原理和实现


    一、多级缓存
    高并发的的多级缓存实际上是为了解决Tomcat的压力。
    在这里插入图片描述
    1.1 JVM进程缓存

    利用Caffeine进程缓存技术来实现JVM进程缓存。

    1.1.1 测试Caffeine

    • 导入依赖
    <dependency>
                <groupId>com.github.ben-manes.caffeine</groupId>
                <artifactId>caffeine</artifactId>
            </dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 测试代码
       // 构建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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    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}
    
    • 1
    • 2
    • 字符串拼接 ..

    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
    
    
    • 1
    • 2
    • 3
    • 4
    • 遍历map和数组类似,但是解析需要使用pairs(map)
    for index,value in pairs(map) do
    >> print("index:" .. index .. "    value:" .. value)
    >> end
    
    • 1
    • 2
    • 3
    • 声明函数
     function add(a,b)
    >>  return a+b
    >> end
    
    • 1
    • 2
    • 3
    • 条件控制
    unction compare(a,b)
    >> if(a>b)
    >> then 
    >> print(a .. "大于" .. b)
    >> else if(a<b)
    >> then print(a .. "小于" .. b)
    >> else
    >> print(a .. "等于" .. b)
    >> end
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 操作符

    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;;";  
    
    • 1
    • 2
    • 3
    • 4
    • 在server下面添加对/api/item路径的监听
    localtion /api/item {
    	#响应类型
    	default_type application/json;
    	#响应的数据由Lua决定
    	content_by_lua_file lua/item.lua;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    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
    • 3
    • 4

    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
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    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))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    对于请求,进来会通过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
    
    
    • 1
    • 2

    导入依赖,加入配置

     <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-redis</artifactId>
            </dependency>
    
    • 1
    • 2
    • 3
    • 4
      redis:
        host: 192.168.229.129
    
    • 1
    • 2

    使用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)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    封装释放连接

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    封装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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    封装查找数据(先找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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    1.5 Nginx本地缓存

    OpenResty为Nginx提供了shard dict的功能,可以在nginx的多个worker之间共享数据,实现缓存功能。

    1.5.1 nginx.conf http下配置

    #开启共享缓存为item_cache 大小为150m
    lua_shared_dict item_cache 150m;
    
    • 1
    • 2
  • 相关阅读:
    div或者div中的内容居中对齐的方法
    Vue2项目练手——通用后台管理项目第四节
    [C#]vs2022安装后C#创建winform没有.net framework4.8
    探究Nginx应用场景
    电脑重装系统后Win11扬声器无插座信息如何解决?
    JDBC中setTransactionIsolation
    SpreadJS 15.2 英文版-Crack
    基础的正则表达式
    技能大赛训练:A部分加固题目
    【数据结构】探索树中的奇妙世界
  • 原文地址:https://blog.csdn.net/qq_46624276/article/details/126635756