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

《多级缓存架构设计与实现全解析》

目录

1. 什么是多级缓存

1.1 传统缓存架构的局限性

1.2 多级缓存架构的优势

1.3 多级缓存架构的部署方案

2. JVM进程缓存实现

2.1 案例准备

2.2 Caffeine缓存框架

2.2.1 缓存分类

2.2.2 Caffeine简介

2.2.3 基本API使用

2.2.4 缓存清除策略

2.3 JVM进程缓存实现

2.3.1 需求分析

2.3.2 实现代码

3. Lua语法入门

3.1 Lua语言简介

3.2 基础语法

3.2.1 Hello World

3.2.2 变量与数据类型

3.2.3 循环结构

3.3 函数与条件控制

3.3.1 函数定义

3.3.2 条件控制

3.3.3 案例:安全打印数组

4. 多级缓存完整实现

4.1 OpenResty安装与配置

4.2 OpenResty快速入门

4.2.1 请求处理流程

4.2.2 配置Nginx监听

4.2.3 编写Lua脚本

4.3 请求参数处理

4.3.1 获取路径参数

4.4 查询Tomcat

4.4.1 Nginx内部请求API

4.4.2 封装HTTP工具

4.4.3 JSON处理

4.4.4 完整商品查询

4.4.5 负载均衡优化

4.5 Redis缓存预热

4.6 查询Redis缓存

4.6.1 封装Redis工具

4.6.2 实现多级查询

4.7 Nginx本地缓存

4.7.1 共享字典配置

4.7.2 本地缓存实现

5. 缓存同步方案

5.1 数据同步策略

5.2 Canal实现缓存同步

5.2.1 Canal原理

5.2.2 安装配置

5.2.3 SpringBoot集成

总结


本文将通过理论与实践相结合的方式,全面讲解多级缓存架构的设计与实现。学习完本文后,您将能够:

  1. 深入理解多级缓存的概念及其优势

  2. 掌握JVM进程缓存的实现方法(基于Caffeine)

  3. 学习Lua语言基础及其在Nginx中的应用

  4. 完整实现包含Nginx本地缓存、Redis缓存和Tomcat缓存的四级缓存架构

  5. 掌握使用Canal实现数据库与缓存同步的方案

1. 什么是多级缓存

1.1 传统缓存架构的局限性

在传统的Web应用架构中,通常会采用如图所示的缓存策略:

请求到达Tomcat后,先查询Redis缓存,如果未命中则查询数据库。这种架构存在两个明显的问题:

  1. 性能瓶颈:所有请求都必须经过Tomcat处理,当并发量高时,Tomcat的处理能力成为整个系统的性能瓶颈

  2. 缓存雪崩风险:当Redis缓存失效时,大量请求会直接冲击数据库,可能导致数据库崩溃

1.2 多级缓存架构的优势

多级缓存的核心思想是充分利用请求处理的每个环节,分别添加缓存层,减轻Tomcat压力,提升整体服务性能。一个完整的多级缓存架构工作流程如下:

  1. 浏览器缓存:静态资源优先读取浏览器本地缓存

  2. 服务端请求:动态数据(AJAX请求)访问服务端

  3. Nginx缓存:请求到达Nginx后,优先读取Nginx本地缓存

  4. Redis查询:Nginx本地缓存未命中时,直接查询Redis(不经过Tomcat)

  5. Tomcat查询:Redis查询未命中时,才查询Tomcat

  6. JVM进程缓存:请求进入Tomcat后,优先查询JVM进程缓存

  7. 数据库查询:JVM进程缓存未命中时,最后才查询数据库

1.3 多级缓存架构的部署方案

在这种架构中,Nginx不再仅仅是反向代理服务器,而是需要编写本地缓存查询、Redis查询、Tomcat查询等业务逻辑的业务服务器。因此需要考虑以下部署方案:

  1. 业务Nginx集群:处理业务逻辑的Nginx服务需要集群化部署以提高并发能力

  2. 反向代理Nginx:前端使用专门的Nginx服务做负载均衡和反向代理

  3. Tomcat集群:后端Tomcat服务同样需要集群化部署

实现多级缓存的两个关键技术点:

  1. Nginx业务逻辑开发:使用OpenResty框架结合Lua语言实现Nginx本地缓存、Redis查询和Tomcat查询的业务逻辑

  2. JVM进程缓存实现:在Tomcat中使用Caffeine等框架实现本地缓存

2. JVM进程缓存实现

2.1 案例准备

为了演示多级缓存的实现,我们准备一个商品查询的案例。具体导入步骤请参考相关文档。

2.2 Caffeine缓存框架

2.2.1 缓存分类

在分布式系统中,缓存通常分为两类:

缓存类型代表技术优点缺点适用场景
分布式缓存Redis存储容量大、可靠性高、集群共享有网络开销大数据量、高可靠性要求、集群共享
进程本地缓存Caffeine, GuavaCache无网络开销、速度极快容量有限、可靠性低、无法共享高性能要求、小数据量
2.2.2 Caffeine简介

Caffeine是基于Java 8开发的高性能本地缓存库,具有接近最优的命中率。Spring框架内部就使用了Caffeine作为缓存实现。

GitHub地址:https://github.com/ben-manes/caffeine

性能对比显示,Caffeine在各项指标上都遥遥领先:

2.2.3 基本API使用
@Test
void testBasicOps() {// 构建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 -> {// 模拟数据库查询return "柳岩";});System.out.println("defaultGF = " + defaultGF);
}
2.2.4 缓存清除策略

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

  1. 基于容量

Cache<String, String> cache = Caffeine.newBuilder().maximumSize(1) // 设置缓存大小上限.build();
  1. 基于时间

Cache<String, String> cache = Caffeine.newBuilder().expireAfterWrite(Duration.ofSeconds(10)) // 写入后10秒过期.build();
  1. 基于引用(不推荐):利用GC回收缓存数据,性能较差

注意:Caffeine不会立即清理过期数据,而是在读写操作或空闲时进行清理。

2.3 JVM进程缓存实现

2.3.1 需求分析

实现以下功能:

  1. 商品查询缓存:未命中时查询数据库

  2. 库存查询缓存:未命中时查询数据库

  3. 初始缓存大小:100

  4. 最大缓存大小:10000

2.3.2 实现代码
  1. 配置Caffeine缓存Bean:

@Configuration
public class CaffeineConfig {@Beanpublic Cache<Long, Item> itemCache(){return Caffeine.newBuilder().initialCapacity(100).maximumSize(10_000).build();}@Beanpublic Cache<Long, ItemStock> stockCache(){return Caffeine.newBuilder().initialCapacity(100).maximumSize(10_000).build();}
}
  1. 控制器实现:

@RestController
@RequestMapping("item")
public class ItemController {@Autowiredprivate Cache<Long, Item> itemCache;@Autowiredprivate Cache<Long, ItemStock> stockCache;@GetMapping("/{id}")public Item findById(@PathVariable("id") Long id) {return itemCache.get(id, key -> itemService.query().ne("status", 3).eq("id", key).one());}@GetMapping("/stock/{id}")public ItemStock findStockById(@PathVariable("id") Long id) {return stockCache.get(id, key -> stockService.getById(key));}
}

3. Lua语法入门

3.1 Lua语言简介

Lua是一种轻量级脚本语言,用标准C编写并以源代码形式开放。设计目的是嵌入应用程序中,提供灵活的扩展和定制功能。

官网:The Programming Language Lua

Lua常用于游戏开发和插件系统。Nginx通过OpenResty支持Lua扩展,使其能够处理复杂业务逻辑。

3.2 基础语法

3.2.1 Hello World

创建hello.lua文件:

print("Hello World!")

执行:lua hello.lua

3.2.2 变量与数据类型

Lua基本数据类型:

  • nil:空值

  • boolean:布尔

  • number:数字

  • string:字符串

  • table:表(数组和字典)

  • function:函数

  • userdata:用户数据

  • thread:线程

变量声明:

local str = 'hello'  -- 字符串
local num = 21       -- 数字
local flag = true    -- 布尔
local arr = {'java', 'python', 'lua'}  -- 数组
local map = {name='Jack', age=21}      -- 字典
3.2.3 循环结构

数组遍历:

for index,value in ipairs(arr) doprint(index, value)
end

字典遍历:

for key,value in pairs(map) doprint(key, value) 
end

3.3 函数与条件控制

3.3.1 函数定义
function printArr(arr)for index, value in ipairs(arr) doprint(value)end
end
3.3.2 条件控制
if not arr thenprint('数组不能为空!')
elseif #arr > 10 thenprint('数组过长')
elseprint('数组正常')
end
3.3.3 案例:安全打印数组
function safePrintArr(arr)if not arr thenprint('数组不能为空!')returnendfor index, value in ipairs(arr) doprint(value)end
end

4. 多级缓存完整实现

4.1 OpenResty安装与配置

OpenResty是基于Nginx的高性能Web平台,集成了Lua支持。安装步骤参考相关文档。

4.2 OpenResty快速入门

4.2.1 请求处理流程
  1. 浏览器发起AJAX请求

  2. Windows Nginx反向代理到OpenResty集群

  3. OpenResty处理请求并返回响应

4.2.2 配置Nginx监听
location /api/item {default_type application/json;content_by_lua_file lua/item.lua;
}
4.2.3 编写Lua脚本

/usr/local/openresty/nginx/lua/item.lua:

ngx.say('{"id":10001,"name":"SALSA AIR","price":17900}')

4.3 请求参数处理

4.3.1 获取路径参数

修改Nginx配置:

location ~ /api/item/(\d+) {default_type application/json;content_by_lua_file lua/item.lua;
}

Lua脚本获取ID:

local id = ngx.var[1]
ngx.say('{"id":'..id..'}')

4.4 查询Tomcat

4.4.1 Nginx内部请求API
local resp = ngx.location.capture("/path",{method = ngx.HTTP_GET,args = {a=1,b=2},
})
4.4.2 封装HTTP工具

common.lua:

local function read_http(path, params)local resp = ngx.location.capture(path,{method = ngx.HTTP_GET,args = params,})if not resp thenngx.log(ngx.ERR, "http请求失败")ngx.exit(404)endreturn resp.body
end
4.4.3 JSON处理

使用cjson模块:

local cjson = require "cjson"
local obj = cjson.decode(jsonStr)
local json = cjson.encode(obj)
4.4.4 完整商品查询
local common = require("common")
local read_http = common.read_http
local cjson = require("cjson")local id = ngx.var[1]
local itemJSON = read_http("/item/"..id, nil)
local stockJSON = read_http("/item/stock/"..id, nil)local item = cjson.decode(itemJSON)
local stock = cjson.decode(stockJSON)
item.stock = stock.stock
item.sold = stock.soldngx.say(cjson.encode(item))
4.4.5 负载均衡优化

基于ID的hash负载均衡:

upstream tomcat-cluster {hash $request_uri;server 192.168.150.1:8081;server 192.168.150.1:8082;
}

4.5 Redis缓存预热

@Component
public class RedisHandler implements InitializingBean {@Autowiredprivate StringRedisTemplate redisTemplate;public void afterPropertiesSet() {// 预热商品数据List<Item> items = itemService.list();for (Item item : items) {String json = MAPPER.writeValueAsString(item);redisTemplate.opsForValue().set("item:id:"+item.getId(), json);}// 预热库存数据List<ItemStock> stocks = stockService.list();for (ItemStock stock : stocks) {String json = MAPPER.writeValueAsString(stock);redisTemplate.opsForValue().set("item:stock:id:"+stock.getId(), json);}}
}

4.6 查询Redis缓存

4.6.1 封装Redis工具

common.lua扩展:

local redis = require("resty.redis")
local red = redis:new()
red:set_timeouts(1000, 1000, 1000)local function read_redis(ip, port, key)local ok, err = red:connect(ip, port)if not ok then return nil endlocal resp, err = red:get(key)if not resp then return nil endif resp == ngx.null then return nil endreturn resp
end
4.6.2 实现多级查询
function read_data(key, path, params)-- 先查Redislocal val = read_redis("127.0.0.1", 6379, key)if not val then-- Redis未命中查HTTPval = read_http(path, params)endreturn val
end

4.7 Nginx本地缓存

4.7.1 共享字典配置

nginx.conf:

lua_shared_dict item_cache 150m;
4.7.2 本地缓存实现
local item_cache = ngx.shared.item_cachefunction read_data(key, expire, path, params)-- 1.查本地缓存local val = item_cache:get(key)if not val then-- 2.查Redisval = read_redis("127.0.0.1", 6379, key)if not val then-- 3.查HTTPval = read_http(path, params)end-- 写入本地缓存item_cache:set(key, val, expire)endreturn val
end

5. 缓存同步方案

5.1 数据同步策略

策略实现方式优点缺点适用场景
设置有效期缓存设置TTL简单方便时效性差更新频率低
同步双写修改DB同时更新缓存强一致性耦合度高一致性要求高
异步通知通过MQ或Canal通知低耦合最终一致性多个服务需要同步

5.2 Canal实现缓存同步

5.2.1 Canal原理

Canal伪装成MySQL从节点,监听binlog变化并通知客户端。

5.2.2 安装配置

参考相关文档安装配置Canal服务。

5.2.3 SpringBoot集成
  1. 引入依赖:

<dependency><groupId>top.javatool</groupId><artifactId>canal-spring-boot-starter</artifactId><version>1.2.1-RELEASE</version>
</dependency>
  1. 配置Canal:

canal:destination: heimaserver: 192.168.150.101:11111
  1. 编写监听器:

@CanalTable("tb_item")
@Component
public class ItemHandler implements EntryHandler<Item> {@Overridepublic void update(Item before, Item after) {// 更新JVM缓存itemCache.put(after.getId(), after);// 更新RedisredisHandler.saveItem(after);}// 实现insert/delete方法...
}

总结

本文详细介绍了从浏览器到数据库的完整多级缓存架构实现,包含:

  1. 浏览器本地缓存

  2. Nginx本地缓存

  3. Redis分布式缓存

  4. Tomcat JVM进程缓存

通过多级缓存架构,系统可以:

  • 显著提高响应速度

  • 大幅降低数据库压力

  • 提高系统可用性和扩展性

同时,通过Canal实现的缓存同步方案,保证了缓存数据的最终一致性,使系统既保持了高性能,又保证了数据的准确性。

http://www.dtcms.com/a/331045.html

相关文章:

  • 自动化测试|持续集成Git使用详解
  • label studio 服务器端打开+xshell端口转发设置
  • 01数据结构-最短路径Dijkstra
  • 【数据结构入门】
  • 移动机器人底盘在高校科研中的AI智能教育应用
  • (第十五期)HTML文本格式化标签详解:让文字更有表现力
  • Flutter GetX 全面指南:状态管理、路由与依赖注入的最佳实践
  • SpringMVC请求与响应
  • 三坐标测量仪:从机械精密到智能协同的技术
  • flutter 开发 鸿蒙 App
  • gitee_配置自动部署vue项目
  • Uniapp 获取系统信息:uni.getSystemInfo 与 uni.getSystemInfoSync
  • vs2022 opencv环境配置(使用相对地址-将依赖都放入项目中)
  • spring boot配置es
  • 开发避坑指南(26):Vue3 input输入框前置后 置元素解决方案
  • 新增和编辑共用弹窗模板
  • .Net Core控制台程序连接HGDB并部署到Linux
  • 【C#】跨平台创建你的WinForms窗体应用(WindowsUbuntu)
  • 上网行为安全概述和组网方案
  • 深入解析 HTTP 协议演进:从 1.0 到 3.0
  • 【web站点安全开发】任务4:JavaScript与HTML/CSS的完美协作指南
  • 嵌入式Linux学习-编译内核源码
  • vscode的ws环境,esp32s3连接wifi
  • 深入解析Python身份切换:安全权限管理实践指南
  • MyBatis缓存模块深度解析
  • dolphinscheduler中任务输出变量的问题出现ArrayIndexOutOfBoundsException
  • MCP和Agent之间的区别和联系
  • vercel部署上线
  • lesson38:MySQL数据库核心操作详解:从基础查询到高级应用
  • 飞算JavaAI智慧零售场景实践:从用户洞察到供应链优化的全链路技术升级