【Redis】多级缓存(nginx缓存、redis缓存及tomcat缓存)

这篇具有很好参考价值的文章主要介绍了【Redis】多级缓存(nginx缓存、redis缓存及tomcat缓存)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

【Redis】多级缓存

1. 传统缓存的问题

传统的缓存策略一般是请求到达 tomcat 后,先查询redis,如果未命中则查询数据库。这种方式存在以下两个问题:

  1. 请求要经过 tomcat 处理,tomcat 的性能成为整个系统的瓶颈。
  2. redis缓存失效时,会对数据库产生冲击。

【Redis】多级缓存(nginx缓存、redis缓存及tomcat缓存)


2. 多级缓存方案

多级缓存就是充分利用请求处理的每个环节,分别添加缓存,减轻tomcat的压力,提升服务性能:

【Redis】多级缓存(nginx缓存、redis缓存及tomcat缓存)

注:用作缓存的nginx是业务nginx,需要部署为集群,再使用专门的nginx用来做反向代理


2.1 JVM进程缓存

【Redis】多级缓存(nginx缓存、redis缓存及tomcat缓存)

2.1.1 本地进程缓存

本地进程缓存:缓存在日常开发中起到了至关重要的作用,由于是存在在内存中,数据的读取速度非常快,能大量减少对数据库的访问,减少数据库的压力,我们把缓存分为两类:

  1. 分布式缓存,例如Redis:
    • 优点:存储容量大,可靠性更好,可以在集群间共享
    • 缺点:访问缓存有网络开销
    • 场景:缓存数据量较大、可靠性要求较高,需要在集群见共享
  2. 本地进程缓存,例如HashMap,GuavaCache:
    • 优点:读取本地内存,没有网络开销,速度更快
    • 缺点:存储容量有限,可靠性较低,无法共享
    • 场景:性能要求较高,缓存数据量较小

2.1.2 Caffeine

本地进程缓存Caffeine 是一个基于Java8开发的,提供了近乎最佳命中率的高性能的本地缓存库,目前spring内部的缓存使用的就算Caffeine。

引入依赖:

<dependency>
    <groupId>com.github.ben-manes.caffeine</groupId>
    <artifactId>caffeine</artifactId>
</dependency>

示例:

@Test
void testBasicOps() {
    //构建cache对象
    Cache<String, String> cache = Caffeine.newBuilder().build();
    //存数据
    cache.put("gf", "刘亦菲");

    //取数据
    String gf = cache.getIfPresent("gf");
    System.out.println("gf = " + gf);

    //取数据,如果未命中,则查询数据库
    //参数1:缓存的key
    //参数2:lambda表达式,表达式的参数就是缓存的key,方法体就是查询逻辑
    //优先根据key查询jvm缓存,如果未命中,则执行参数2的lambda表达式
    String defaultGF = cache.get("defaultGF", key -> {
        //根据key去数据库查询数据
        return "王祖贤";
    });
    System.out.println("defaultGF = " + defaultGF);
    System.out.println("defaultGF = " + cache.getIfPresent("defaultGF"));
}

运行结果如下:

【Redis】多级缓存(nginx缓存、redis缓存及tomcat缓存)


Caffeine 提供了三种缓存驱逐策略:

  • 基于容量:设置缓存的数量上限

    Cache<String, String> cache = Caffeine.newBuilder()
            .maximumSize(10_000)//上限为10000个key
            .build();
    
  • 基于时间:设置缓存的有效时间

    // 创建缓存对象
    Cache<String, String> cache = Caffeine.newBuilder()
        .expireAfterWrite(Duration.ofSeconds(10)) // 设置缓存有效期为 10 秒,从最后一次写入开始计时 
        .build();
    
  • 基于引用:设置缓存为软引用或弱引用,利用GC来回收缓存数据。性能较差,不建议使用。

在默认的情况下,当一个缓存元素过期的时候,Caffeine 不会自动立即将其清理和驱逐,而是在一次读或写操作后,或者在空闲时间完成对失效数据的驱逐。


2.2 Nginx缓存

2.2.1 准备工作

首先需要安装 OpenResty ,它本质上也是一个nginx服务器,它具有以下特点:

  1. 具备Nginx的完整功能
  2. 基于Lua语言进行扩展,集成了大量精良的Lua库、第三方模块
  3. 允许使用Lua自定义业务逻辑、自定义库

安装好 OpenResty 后,安装目录为 /usr/local/openresty

/usr/local/openresty/nginx/conf 目录下的nginx.conf文件添加如下模块:

# 加载lua 模块  
 lua_package_path "/usr/local/openresty/lualib/?.lua;;";  
 # 加载c模块 
 lua_package_cpath "/usr/local/openresty/lualib/?.so;;";

/api/item 这个路径进行监听:

 location /api/item {
     # 响应类型,这里返回json
     default_type application/json;
     # 响应数据由 lua/item.lua这个文件来决定
     content_by_lua_file lua/item.lua;
 }

注:lua/item.lua的lua目录和nginx同级,完整路径为/usr/local/openresty/lua,在这个文件下面就可以编写缓存脚本。

2.2.2 请求参数处理
参数格式 参数示例 参数解析代码示例
路径占位符 /item/1001 【Redis】多级缓存(nginx缓存、redis缓存及tomcat缓存)【Redis】多级缓存(nginx缓存、redis缓存及tomcat缓存)
请求头 id:1001 – 获取请求头,返回值是table类型 local headers = ngx.req.get_headers()
Get请求参数 ?id=1001 – 获取GET请求参数,返回值是table类型 local getParams = ngx.req.get_uri_args()
Post表单参数 id=1001 – 读取请求体 ngx.req.read_body() – 获取POST表单参数,返回值是table类型 local postParams = ngx.req.get_post_args()
JSON参数 {“id”:1001} – 读取请求体 ngx.req.read_body() – 获取body中的json参数,返回值是string类型 local jsonBody = ngx.req.get_body_data()

2.2.3 nginx发送http请求tomcat

需求:

  1. 获取请求参数中的id

  2. 根据id向Tomcat服务发送请求,查询商品信息

  3. 根据id向Tomcat服务发送请求,查询库存信息

  4. 组装商品信息、库存信息,序列化为JSON格式并返回

【Redis】多级缓存(nginx缓存、redis缓存及tomcat缓存)

nginx内部提供了API用以发送http请求:

local resp = ngx.location.capture("/path",{
    method = ngx.HTTP_GET,   -- 请求方式
    args = {a=1,b=2},  -- get方式传参数
    body = "c=3&d=4" -- post方式传参数
})

返回的响应内容包括:

  • resp.status:响应状态码
  • resp.header:响应头,是一个table
  • resp.body:响应体,就是响应数据

注意:这里的path是路径,并不包括IP地址和端口,这个请求会被nginx内部的server监听并处理。但是我们希望这个请求能够被发送到tomcat服务器,所以还需要编写一个server来对这个路径做反向代理:

 location /path {
     # 这里是windows电脑的ip和Java服务端口,需要确保windows防火墙处于关闭状态
     proxy_pass http://192.168.150.1:8081; 
 }

2.2.3.1 封装http查询函数

我们可以把http查询的请求封装为一个函数,放到OpenResty函数库中,方便以后使用。

  1. 在/usr/local/openresty/lualib目录下创建common.lua文件:

    vi /usr/local/openresty/lualib/common.lua
    
  2. 在common.lua中封装http查询的函数:

    -- 封装函数,发送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
    
    

2.2.3.2 使用http函数查询数据

OpenResty提供了一个cjson的模块用来处理JSON的序列化和反序列化。它可以用来把多个对象通过序列化和反序列化组合成一个对象。

引入cjson模块:

--导入cjson库
local cjson = require('cjson')

序列化:

local obj = {
    name = 'jack',
    age = 21
}
local json = cjson.encode(obj)

反序列化:

local json = '{"name": "jack", "age": 21}'
-- 反序列化
local obj = cjson.decode(json);
print(obj.name)

综合实践:

修改之前编写的item.lua文件:

--导入common函数库
local common = require('common')
local read_http = common.read_http
--导入cjson库
local cjson = require('cjson')

--获取路径参数
local id = ngx.var[1]

--查询商品信息
local itemJSON = read_http("/item/"..id,nil)
--查询库存信息
local stockJSON = read_http("/item/stock/"..id,nil)

--JSON转化为lua的table
local item = cjson.decode(itemJSON)
local stock = cjson.decode(stockJSON)

--组合数据
item.stock = stock.stock
item.sold = stock.sold

--把item序列化为json 返回结果
ngx.say(cjson.encode(item))

修改完item.lua脚本后,我们要将openresty中的nginx.conf配置也做相应修改。

将反向代理修改为如下配置:

# tomcat集群配置
upstream tomcat-cluster{
	hash $request_uri;#一致性hash,一直访问有缓存的节点
    server 192.168.150.1:8081;
    server 192.168.150.1:8082;
}

# 反向代理配置,将/item路径的请求代理到tomcat集群        
location /item {
    proxy_pass 	http://tomcat-cluster;
}


2.2.4 nginx查询redis缓存

比起直接从nginx查询tomcat,先去查询redis显然是一种更好的方式。

【Redis】多级缓存(nginx缓存、redis缓存及tomcat缓存)

2.2.4.1 缓存预热

编写一个类实现 InitializingBean 接口,实现其中的方法,就可以使得该方法在该类被注入到容器,完成依赖注入后就会执行该方法:

@Component
public class RedisHandler implements InitializingBean {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private ItemService itemService;

    private static final ObjectMapper MAPPER = new ObjectMapper();

    @Autowired
    private IItemStockService stockService;

    @Override
    public void afterPropertiesSet() throws Exception {
        //1.初始化缓存
        //2.查询商品信息
        List<Item> list = itemService.list();
        for (Item item : list) {
            //3.放入缓存
            String json = MAPPER.writeValueAsString(item);
            stringRedisTemplate.opsForValue().set("item:id:" + item.getId(), json);
        }
        //4.查询库存信息
        List<ItemStock> stockList = stockService.list();
        for (ItemStock itemStock : stockList) {
            //5.放入缓存
            String json = MAPPER.writeValueAsString(itemStock);
            stringRedisTemplate.opsForValue().set("item:stock:id:" + itemStock.getId(), json);
        }
    }
}

2.2.4.2 查询redis缓存

OpenResty提供了操作Redis的模块,我们只要引入该模块就能直接使用:

  1. 引入Redis模块,并初始化Redis对象

    -- 引入redis模块
    local redis = require("resty.redis")
    -- 初始化Redis对象
    local red = redis:new()
    -- 设置Redis超时时间
    red:set_timeouts(1000, 1000, 1000)
    
  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  
    

这些操作都要添加到common.lua文件中,common.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,password, key)
    -- 获取一个连接
    local ok, err = red:connect(ip, port)

    if not ok then
        ngx.log(ngx.ERR, "连接redis失败 : ", err)
        return nil
    end

    -- 发送密码验证命令
    local res, err = red:auth(password)

    if not res then
        ngx.log(ngx.ERR, "redis认证失败: ", err)
        return
    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 查询失败, path: ", path , ", args: ", args)
        ngx.exit(404)
    end
    return resp.body
end

-- 将方法导出
local _M = {  
    read_http = read_http,
    read_redis = read_redis
}  
return _M

需求:

  1. 修改item.lua,封装一个函数read_data,实现先查询Redis,如果未命中,再查询tomcat
  2. 修改item.lua,查询商品和库存时都调用read_data这个函数

完整的item.lua内容如下所示:

--导入common函数库
local common = require('common')
local read_http = common.read_http
--导入redis库
local read_redis = common.read_redis
--导入cjson库
local cjson = require('cjson')

--封装查询函数,先查询redis,再查询http
function read_data(key,path,params)
    --查询redis
    local resp = read_redis("127.0.0.1",6379,"redis",key)
    --判断查询结果
    if not resp then
        ngx.log("redis查询失败,尝试查询http,key:",key)
        resp = read_http(path,params)
    end
    return resp
end

--获取路径参数
local id = ngx.var[1]

--查询商品信息
local itemJSON = read_data("item:id:"..id,"/item/"..id,nil)
--查询库存信息
local stockJSON = read_data("item:stock:id:"..id,"/item/stock/"..id,nil)

--JSON转化为lua的table
local item = cjson.decode(itemJSON)
local stock = cjson.decode(stockJSON)

--组合数据
item.stock = stock.stock
item.sold = stock.sold


--把item序列化为json 返回结果
ngx.say(cjson.encode(item))

2.2.5 查询nginx本地缓存

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

  • 开启共享字典,在nginx.conf的http下添加配置:

    # 共享字典,也就是本地缓存,名称叫做:item_cache,大小150m
     lua_shared_dict item_cache 150m;
    
  • 操作共享词典:

    -- 获取本地缓存对象
    local item_cache = ngx.shared.item_cache
    -- 存储, 指定key、value、过期时间,单位s,默认为0代表永不过期
    item_cache:set('key', 'value', 1000)
    -- 读取
    local val = item_cache:get('key')
    

需求:

  1. 修改item.lua中的read_data函数,优先查询本地缓存,未命中时再查询Redis、Tomcat
  2. 查询Redis或Tomcat成功后,将数据写入本地缓存,并设置有效期
  3. 商品基本信息,有效期30分钟
  4. 库存信息,有效期1分钟

修改之前item.lua文件中的read_data函数:

--封装查询函数,先查询redis,再查询http
function read_data(key,expire,path,params)
    --查询本地缓存
    local val = item_cache:get(key)
    if not val then
        ngx.log(ngx.ERR,"本地缓存查询失败,尝试查询redis,key:",key)
        --查询redis
        val = read_redis("127.0.0.1",6379,"redis",key)
        --判断查询结果
        if not val then
            ngx.log(ngx.ERR,"redis查询失败,尝试查询http,key:",key)
            val = read_http(path,params)
        end
    end
    --查询成功,把数据写入本地缓存(重置了缓存的时间)
    item_cache:set(key,val,expire)
    --返回数据
    return val
end

3. 总结

【Redis】多级缓存(nginx缓存、redis缓存及tomcat缓存)文章来源地址https://www.toymoban.com/news/detail-419887.html

到了这里,关于【Redis】多级缓存(nginx缓存、redis缓存及tomcat缓存)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Redis高级篇 - 多级缓存

    传统的缓存策略一般是请求到达Tomcat后,先查询Redis,如果未命中则查询数据库,如图: 存在下面的问题: 请求要经过Tomcat处理,Tomcat的性能成为整个系统的瓶颈 Redis缓存失效时,会对数据库产生冲击 多级缓存就是充分利用请求处理的每个环节,分别添加缓存,减轻Tomcat压

    2024年02月08日
    浏览(31)
  • Redis多级缓存

    传统的缓存策略一般是请求到达Tomcat后,先查询Redis,如果未命中则查询数据库,会存在以下问题: 请求需要经过Tomcat处理,Tomcat的性能成为整个系统的瓶颈。 Redis缓存失效时,会对数据库产生冲击。 而多级缓存就是充分利用请求处理的每个环节,分别添加缓存,减轻Tomca

    2024年02月15日
    浏览(41)
  • Redis学习(三)持久化机制、分布式缓存、多级缓存、Redis实战经验

    单节点Redis存在着: 数据丢失问题:单节点宕机,数据就丢失了。 并发能力和存储能力问题:单节点能够满足的并发量、能够存储的数据量有限。 故障恢复问题:如果Redis宕机,服务不可用,需要一种自动的故障恢复手段。 RDB持久化 RDB(Redis database backup file,Redis数据库备份

    2024年02月16日
    浏览(39)
  • 【Redis】多级缓存之缓存数据同步策略与Canal

    目录 一、数据同步策略 1.设置有效期 2.同步双写 3.异步通知 二、Canal 三、监听Canal 缓存数据同步的常见方式有三种: 给缓存设置有效期,到期后自动删除。再次查询时更新,他简单、方便,但是时效性差,缓存过期之前可能不一致,适用于更新频率较低,时效性要求低的业

    2024年02月11日
    浏览(65)
  • 黑马Redis视频教程高级篇(二:多级缓存)

    目录 一、什么是多级缓存? 二、JVM进程缓存 2.1、导入案例 2.2、初识Caffeine 2.3、实现JVM进程缓存 2.3.1、需求 2.3.2、实现 三、Lua语法入门 3.1、初识Lua 3.2、HelloWord 3.3、变量和循环 3.3.1、Lua的数据类型 3.3.2、声明变量 3.3.3、循环 3.4、条件控制、函数 3.4.1、函数 3.4.2、条件控制

    2024年02月09日
    浏览(28)
  • 中间件系列 - Redis入门到实战(高级篇-多级缓存)

    学习视频: 黑马程序员Redis入门到实战教程,深度透析redis底层原理+redis分布式锁+企业解决方案+黑马点评实战项目 中间件系列 - Redis入门到实战 本内容仅用于个人学习笔记,如有侵扰,联系删除 学习目标 JVM进程缓存 Lua语法入门 实现多级缓存 缓存同步策略 传统的缓存策略

    2024年02月03日
    浏览(43)
  • docker应用部署(部署MySql,部署Tomcat,部署Nginx,部署Redis)

    一、部署MySQL 搜索mysql镜像 拉取mysql镜像 创建容器,设置端口映射、目录映射 参数说明: -p 3307:3306 :将容器的 3306 端口映射到宿主机的 3307 端口。 -v $PWD/conf:/etc/mysql/conf.d :将主机当前目录下的 conf/my.cnf 挂载到容器的 /etc/mysql/my.cnf。配置目录 -v $PWD/logs:/logs :将主机当前目

    2024年02月03日
    浏览(31)
  • Linux基础+命令操作+mysql、tomcat、nginx、RabbitMQ、Redis,ElasticSearch

    配置代理 linux组成:系统内核+系统级应用程序 内核功能:CPU调度,没存调度,文件调度,网络调度,IO调度 系统级应用程序:文件管理器,软件等 linux内核下载:https://www.kernel.org linux发行版:内核开源,可修改+系统级程序完整封装为linux发行版。 不同发行版基础操作命令都

    2024年02月11日
    浏览(33)
  • 基于ssm+shiro+redis+nginx tomcat服务器集群管理项目

    毕业设计——基于ssm+shiro+redis+nginx tomcat服务器集群管理项目 完整项目地址:https://download.csdn.net/download/lijunhcn/88430549 1.搭建一个最简洁,模块划分最明确的ssm+swargger+shiro+redis+nginx整合项目,采用maven作为构建工具,在有新项目开发时可以借助此demo快速构建项目 2.实现shiro的授

    2024年02月03日
    浏览(43)
  • 基于dockerfile构建sshd、httpd、nginx、tomcat、mysql、lnmp、redis镜像

    一、镜像概述 Docker 镜像是Docker容器技术中的核心,也是应用打包构建发布的标准格式。一个完整的镜像可以支撑多个容器的运行,在Docker的整个使用过程中,进入一个已经定型的容器之后,就可以在容器中进行操作,最常见的操作就是在容器中安装应用服务。 如果想要把已

    2024年02月14日
    浏览(32)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包