从零开始 Spring Cloud 15:多级缓存

本文介绍了Spring Cloud中多级缓存的实现,包括JVM进程缓存(使用Caffeine)和Redis分布式缓存。讲解了Caffeine的简单示例和缓存驱逐策略,以及如何在Nginx中使用Lua实现本地缓存。此外,还探讨了通过Canal实现缓存同步,确保数据一致性。最后,展示了如何在Nginx中使用OpenResty和Lua进行编程,实现多级缓存架构以提升系统性能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

从零开始 Spring Cloud 15:多级缓存

多级缓存架构

传统的缓存使用 Redis,大致架构如下:

image-20210821075259137

这个架构存在一些问题:

  • 请求要经过Tomcat处理,Tomcat的性能成为整个系统的瓶颈

  • Redis缓存失效时,会对数据库产生冲击

可以使用多级缓存来解决这个问题:

image-20210821075558137

具体过程为:

  • 浏览器访问静态资源时,优先读取浏览器本地缓存
  • 访问非静态资源(ajax查询数据)时,访问服务端
  • 请求到达Nginx后,优先读取 Nginx本地缓存
  • 如果Nginx本地缓存未命中,则去直接查询Redis(不经过Tomcat)
  • 如果Redis查询未命中,则查询Tomcat
  • 请求进入Tomcat后,优先查询JVM进程缓存
  • 如果JVM进程缓存未命中,则查询数据库

传统架构中的 Nginx 仅仅充当 Tomcat 的反向代理服务器,所以只要部署一个实例就可以了,在这个新架构中,Nginx 上同样要实现业务逻辑,以便直接从 Redis 上查询业务数据,所以为了提高可靠性, Nginx 同样需要集群部署:

image-20210821080511581

当然,Tomcat 作为主要业务代码的托管方,也要集群部署:

image-20210821080954947

JVM 进程缓存

导入案例

在继续教程前,需要先导入示例工程,具体可以阅读案例导入说明

Caffine

缓存可以按照存储的位置大致分为两类:

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

下面我们会使用 Caffeine 作为本地缓存。

Caffeine是一个基于Java8开发的,提供了近乎最佳命中率的高性能的本地缓存库。目前Spring内部的缓存使用的就是Caffeine。GitHub地址:https://github.com/ben-manes/caffeine

简单示例

示例项目中已经引入了 Caffine 的依赖:

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

用 Caffine 写一个使用本地缓存的简单示例:

@Test
void simpleTest(){
   
    Cache<String, String> cache = Caffeine.newBuilder()
        .build();
    String name = cache.getIfPresent("name");
    if (name == null){
   
        System.out.println("缓存未命中,写入缓存");
        cache.put("name", "icexmoon");
        System.out.println("从缓存读取");
        name = cache.getIfPresent("name");
    }
    System.out.println(name);
}

Cache.getIfPresent 会在未命中缓存时返回 null,这和使用容器作为内存缓存时用法是类似的。不过 Caffine 提供更方便的 API:

@Test
void simpleTest2(){
   
    Cache<String, String> cache = Caffeine.newBuilder()
        .build();
    String name = cache.get("name", key-> "icexmoon");
    System.out.println(name);
}

Cache.get方法会先检查缓存中是否有,如果有就直接返回,如果没有,就使用参数列表中的 Lamda 表达式生成缓存值,写入缓存,然后再返回。整个过程和上边的示例是相同的,但写法要简洁很多。

缓存驱逐

所有的缓存机制都需要考虑缓存驱逐(过期)的问题,以防止存储空间被耗尽。

Caffine 有三种缓存驱逐策略可选:

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

    // 创建缓存对象
    Cache<String, String> cache = Caffeine.newBuilder()
        .maximumSize(1) // 设置缓存大小上限为 1
        .build();
    
  • 基于时间:设置缓存的有效时间

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

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

实现 JVM 进程缓存

下面使用 Caffine 在示例项目中实现 JVM 进程缓存。

这里使用 Caffine 缓存商品详情和商品库存的查询结果。

首先在配置类中将缓存对象定义为 Spring Bean:

@Configuration
public class WebConfig {
   
    /**
     * 商品详情缓存
     * @return
     */
    @Bean
    public Cache<Long, Item> itemCache() {
   
        return Caffeine.newBuilder()
                .initialCapacity(100)
                .maximumSize(10000)
                .build();
    }

    /**
     * 库存缓存
     * @return
     */
    @Bean
    public Cache<Long, ItemStock> itemStockCache(){
   
        return Caffeine.newBuilder()
                .initialCapacity(100)
                .maximumSize(10000)
                .build();
    }
}

在 Controller 中使用缓存对象缓存查询结果:

@RestController
@RequestMapping("item")
public class ItemController {
   
    // ...
    @Autowired
    private Cache<Long, Item> itemCache;
    @Autowired
    private Cache<Long, ItemStock> itemStockCache;
    // ...
    @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 itemStockCache.get(id, key -> stockService.getById(key));
    }
}

Lua

在 Nginx 上实现业务代码需要使用 Lua 作为编程语言,所以下面先学习 Lua。

介绍

Lua 是一种轻量小巧的脚本语言,用标准C语言编写并以源代码形式开放, 其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。官网:https://www.lua.org/

Lua经常嵌入到C语言开发的程序中,例如游戏开发、游戏插件等。

Nginx本身也是C语言开发,因此也允许基于Lua做拓展。

Hello World

首先查看虚拟机上是否已经安装了 Lua:

[icexmoon@192 tmp]$ lua -v
Lua 5.4.4  Copyright (C) 1994-2022 Lua.org, PUC-Rio

如果没有安装,用包管理器安装:


                
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值