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

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

目录

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.xdnf.cn/news/17715.html

相关文章:

  • 【跨越 6G 安全、防御与智能协作:从APT检测到多模态通信再到AI代理语言革命】
  • 机器视觉的磁芯定位贴合应用
  • GraphRAG查询(Query)流程实现原理分析
  • Java+Vue构建的MES信息管理系统,含完整源码,功能涵盖生产跟踪、质量管控等,助力企业实现精细化、智能化生产管理
  • 【16-softmax回归】
  • AI 赋能的软件工程全生命周期应用
  • springboot+vue实现通过poi完成excel
  • Postman 平替 技术解析:架构优势与实战指南
  • 观察者模式(C++)
  • 【Leetcode hot 100】76.最小覆盖字串
  • 【HarmonyOS】Window11家庭中文版开启鸿蒙模拟器失败提示未开启Hyoer-V
  • SwiftUI 页面弹窗操作
  • 用飞算JavaAI一键生成电商平台项目:从需求到落地的高效实践
  • 使用免费API开发口播数字人
  • [机器学习]07-基于多层感知机的鸢尾花数据集分类
  • c++中的Lambda表达式详解
  • Java基础07——基本运算符(本文为个人学习笔记,内容整理自哔哩哔哩UP主【遇见狂神说】的公开课程。 > 所有知识点归属原作者,仅作非商业用途分享)
  • k8s+isulad 网络问题
  • 如何使用 AI 大语言模型解决生活中的实际小事情?
  • 【P81 10-7】OpenCV Python【实战项目】——车辆识别、车流统计(图像/视频加载、图像运算与处理、形态学、轮廓查找、车辆统计及显示)
  • 网络协议序列化工具Protobuf
  • 4.1vue3的setup()
  • 2019 GPT2原文 Language Models are Unsupervised Multitask Learners - Reading Notes
  • Kotlin Data Classes 快速上手
  • Qt TCP 客户端对象生命周期与连接断开问题解析
  • 解锁Prompt秘籍:框架、技巧与指标全解析
  • Windows 11操作系统 Git命令执行速度慢
  • SpringMVC基本原理和配置
  • 第2节 如何计算神经网络的参数:AI入门核心逻辑详解
  • pytorch学习笔记-加载现有的网络模型(VGG16)、增加/修改其中的网络层(修改为10分类)