十一、多级缓存——多级缓存实战详解

十一、多级缓存——多级缓存实战详解

缓存技术在性能优化中扮演了重要角色,尤其是在面试中,关于缓存的各种问题都是考察重点。本文将介绍多级缓存的概念、实现方案及其优化技巧,重点讨论如何通过多级缓存架构来提升系统性能和缓存命中率。
在这里插入图片描述

1. 多级缓存介绍

多级缓存是在系统的不同层级进行数据缓存的一种策略,旨在提高数据访问效率。以下是多级缓存的一般架构及其工作流程:

  1. 接入 Nginx

    • 将请求负载均衡到应用 Nginx。常见的负载均衡算法有轮询和一致性哈希。轮询可均衡负载,一致性哈希则能提升缓存命中率。
  2. 应用 Nginx 本地缓存

    • 读取本地缓存(可以是 Lua Shared Dict、Nginx Proxy Cache 等)。如果命中,则直接返回缓存数据。这种缓存可以提升吞吐量,降低后端压力,特别是应对热点数据时非常有效。
  3. 分布式缓存

    • 如果本地缓存未命中,则查询分布式缓存(如 Redis)。若分布式缓存命中,则将数据返回并写入到应用 Nginx 的本地缓存中。
  4. 回源到应用服务器

    • 如果分布式缓存也未命中,则请求回源到应用服务器(如 Tomcat 集群)。在此过程中,负载均衡算法(轮询或一致性哈希)可以再次发挥作用。
  5. Tomcat 本地堆缓存

    • 在 Tomcat 应用中,首先检查本地堆缓存。如果有缓存,则返回数据并写入主 Redis 集群。此层缓存可以防止缓存失效后的冲击。
  6. 主 Redis 集群(可选):

    • 如果 Tomcat 堆缓存未命中,再次尝试从主 Redis 集群读取数据,以防从 Redis 集群的问题引起的流量冲击。
  7. 查询数据库

    • 如果所有缓存都未命中,最终查询数据库或相关服务获取数据。
  8. 缓存更新

    • 步骤 7 返回的数据将异步写入主 Redis 集群。要处理多个 Tomcat 实例同时写入的问题,可以使用一致性哈希或分布式锁机制。

这种多级缓存方案包含了应用 Nginx 本地缓存、分布式缓存和 Tomcat 堆缓存,每一层缓存都用于解决特定问题,例如本地缓存用于应对热点数据,分布式缓存减少回源频率,而 Tomcat 堆缓存则用于处理缓存失效带来的冲击。

2. 如何缓存数据

2.1 过期与不过期
  • 不过期缓存

    • 适用场景:主要用于访问频率高且稳定的数据,例如用户信息、分类、商品详情等。这些数据变化不频繁,且需要在缓存中长期保存,以降低对数据库的频繁访问。
    • 实现方式:通常采用 Cache-Aside 模式(旁路缓存模式)。在更新数据时,首先更新数据库,然后更新缓存。当缓存容量不足时,可以采用 LRU(Least Recently Used,最近最少使用)策略驱逐旧数据,以释放空间。这种方式确保了热点数据能够常驻内存,减少缓存失效带来的查询压力。
  • 过期缓存

    • 适用场景:用于缓存空间有限且数据变更频率较高的场景,如实时性要求较低的热点数据。典型的例子包括库存数据、商品价格等,这些数据的短期不一致性对业务影响不大,但需要定期刷新。
    • 实现方式:采用懒加载模式(Lazy Loading),即当有请求时才查询缓存,缓存未命中时,再从数据库读取数据并写入缓存。同时设置过期时间(TTL,Time to Live)以确保缓存数据能及时更新。当缓存项过期或被淘汰时,新请求将触发数据重新加载。

在 Spring Boot 和 Spring Cloud 项目中,实现不过期缓存和过期缓存主要通过使用缓存框架,如 Ehcache、Caffeine 或 Redis 来完成。

以下是这两种缓存策略的实现方法:

2.1.1 不过期缓存实现

不过期缓存通常用于访问频率高且稳定的数据,可以使用 Cache-Aside 模式结合 Spring 的 @Cacheable 注解来实现。

import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

@Service
public class UserService {
   

    // 获取用户信息,并缓存结果
    @Cacheable(value = "userCache", key = "#userId")
    public User getUserById(Long userId) {
   
        // 从数据库获取用户信息
        return userRepository.findById(userId).orElse(null);
    }
}

关键点

  • @Cacheable 注解用于将方法的返回值缓存到指定的缓存中(这里是 userCache),缓存不会过期。
  • 当缓存满时,可配置如 LRU(Least Recently Used)机制来淘汰旧数据。
2.1.2 过期缓存实现

过期缓存适用于短期数据不一致性允许的场景,可以通过设置 TTL(Time to Live)来实现缓存自动过期。

import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

@Service
public class ProductService {
   

    // 获取商品信息,并缓存结果,设置缓存过期时间
    @Cacheable(value = "productCache", key = "#productId")
    public Product getProductById(Long productId) {
   
        // 从数据库获取商品信息
        return productRepository.findById(productId).orElse(null);
    }
}

如果使用 Redis 作为缓存,则可以配置 Redis 缓存的 TTL:

spring:
  cache:
    type: redis
  redis:
    time-to-live: 60000  # 设置缓存过期时间为60秒

关键点

  • time-to-live 配置项可以设置缓存的过期时间。
  • 在缓存失效时,新的请求将触发数据重新加载并更新缓存。

这两种缓存策略各有优劣,在不同的业务场景下应灵活应用。

2.1.3 配置淘汰策略

在 Spring 中,@Cacheable 注解本身并不直接处理缓存的淘汰策略,如 LRU(Least Recently Used)等。要配置 LRU 等缓存淘汰机制,通常需要结合具体的缓存实现工具,如 EhcacheCaffeine、或 Guava 等。下面是如何通过不同缓存实现工具来配置 LRU 淘汰策略的示例。

1. 使用 Caffeine 配置 LRU 机制

Caffeine 是一个高性能的 Java 缓存库,可以很容易地与 Spring Cache 集成,并支持多种缓存策略,包括 LRU。

步骤如下:

  1. 添加 Caffeine 依赖:

    <dependency>
        <groupId>com.github.ben-manes.caffeine</groupId>
        <artifactId>caffeine</artifactId>
        <version>3.1.8</version> <!-- 使用最新版本 -->
    </dependency>
    
  2. 配置 Caffeine 缓存:

    import com.github.benmanes.caffeine.cache.Caffeine;
    import org.springframework.cache.CacheManager;
    import org.springframework.cache.annotation.EnableCaching;
    import org.springframework.cache.caffeine.CaffeineCache;
    import org.springframework.cache.caffeine.CaffeineCacheManager;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    @EnableCaching
    public class CacheConfig {
         
    
        @Bean
        public CacheManager cacheManager() {
         
            CaffeineCacheManager cacheManager = new CaffeineCacheManager("userCache");
            cacheManager.setCaffeine(caffeineCacheBuilder());
            return cacheManager;
        }
    
        Caffeine<Object, Object> caffeineCacheBuilder() {
         
            return Caffeine.newBuilder()
                    .maximumSize(1000)  // 设置缓存的最大数量
                    .expireAfterWrite(10, TimeUnit.MINUTES) // 可选的过期时间配置
                    .evictionListener((key, value, cause) -> {
         
                        System.out.println("Eviction cause: " + cause);
                    });
        }
    }
    
  3. 使用 @Cacheable 注解:

    @Service
    public class UserService {
         
    
        @Cacheable(value = "userCache", key = "#userId")
        public User getUserById(Long userId) {
         
            // 从数据库获取用户信息
            return userRepository.findById(userId).orElse(null);
        }
    }
    

2. 使用 Ehcache 配置 LRU 机制

Ehcache 是另一个流行的缓存实现,支持各种缓存淘汰策略,包括 LRU。

步骤如下:

  1. 添加 Ehcache 依赖:

    <dependency>
        <groupId>org.ehcache</groupId>
        <artifactId>ehcache</artifactId>
        <version>3.10.8</version> <!-- 使用最新版本 -->
    </dependency>
    
  2. 创建 Ehcache 配置文件 ehcache.xml

    <config xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
            xmlns='http://www.ehcache.org/v3'
            xsi:schemaLocation="http://www.ehcache.org/v3 http://www.ehcache.org/schema/ehcache-core-3.0.xsd">
    
        <cache alias="userCache">
            <key-type>java.lang.Long</key-type>
            <value-type>com.example.User</value-type>
            <expiry>
                <ttl unit="minutes">10</ttl> <!-- 可选的过期时间配置 -->
            </expiry>
            <resources>
                <heap unit="entries">1000</heap> <!-- 设置缓存的最大数量 -->
            </resources>
            <eviction strategy="LRU"/> <!-- 设置 LRU 淘汰策略 -->
        </cache>
    
    </config>
    
  3. 配置 Ehcache 管理器:

    import org.springframework.cache.CacheManager;
    import org.springframework.cache.annotation.EnableCaching;
    import org.springframework.cache.ehcache.EhCacheCacheManager;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    @EnableCaching
    public class CacheConfig {
         
    
        @Bean
        public CacheManager cacheManager() {
         
            return new EhCacheCacheManager(ehCacheManagerFactoryBean().getObject());
        }
    
        @Bean
        public EhCacheManagerFactoryBean ehCacheManagerFactoryBean() {
         
            EhCacheManagerFactoryBean factory = new EhCacheManagerFactoryBean();
            factory.setConfigLocation(new ClassPathResource("ehcache.xml"));
            factory.setShared(true);
            return factory;
        }
    }
    
  4. 使用 @Cacheable 注解:

    @Service
    public class UserService {
         
    
        @Cacheable(value = "userCache", key = "#userId")
        public User getUserById(Long userId) {
         
            // 从数据库获取用户信息
            return userRepository.findById(userId).orElse(null);
        }
    }
    

3. 使用 Redis 配置 LRU 机制

Redis 本身支持 LRU 机制,但需要通过配置 Redis 的最大内存使用量以及淘汰策略来实现。

步骤如下:

  1. 配置 Redis 服务器的 redis.conf 文件:

    maxmemory 256mb  # 设置最大内存使用量
    maxmemory-policy allkeys-lru  # 设置全局 LRU 淘汰策略
    
  2. Spring Boot Redis 配置:

    application.yml 文件中配置 Redis 连接:

    spring:
      redis:
        host: localhost
        port: 6379
    
  3. 配置 RedisCacheManager:

    import org.springframework.cache.CacheManager;
    import org.springframework.cache.annotation.EnableCaching;
    import org.springframework.cache.redis.RedisCacheConfiguration;
    import org.springframework.cache.redis.RedisCacheManager;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.data.redis.connection.RedisConnectionFactory;
    import org.springframework.data.redis.serializer.RedisSerializationContext;
    import org.springframework.data.redis.serializer.StringRedisSerializer;
    
    @Configuration
    @EnableCaching
    public class CacheConfig {
         
    
        @Bean
        public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
         
            RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));
            return RedisCacheManager.builder(redisConnectionFactory)
                .cacheDefaults(config)
                .build();
        }
    }
    
  4. 使用 @Cacheable 注解:

    @Service
    public class UserService {
         
    
        @Cacheable(value = "userCache", key = "#userId")
        public User getUserById(Long userId) {
         
            // 从数据库获取用户信息
            return userRepository.findById(userId).orElse(null);
        }
    }
    
2.2 维度化缓存与增量缓存

缓存机制是提升分布式系统性能的关键之一。在分布式系统中,通过合理设计缓存策略,可以有效降低数据库压力,减少接口调用次数,提高系统的响应速度。维度化缓存增量缓存是两种常见且高效的缓存策略,尤其适用于需要频繁更新数据的场景。

2.2.1 维度化缓存

维度化缓存 是指将数据按不同维度拆分,以减少数据更新的成本和复杂性。例如,在商品数据的缓存中,可以将数据按照不同的属性进行维度化拆分,如基础属性(价格、名称、描述等)、图片列表、库存信息、上下架状态等。这样,当某个维度的数据发生变化时,只需更新对应维度的数据,而无需重新缓存整个商品数据。

优点:

  1. 减少更新成本:在维度化缓存中,由于数据被分拆成不同维度,更新时只需修改变更的维度部分,而不必重建整个缓存。
  2. 提高缓存命中率:不同维度的数据可以单独缓存,这使得缓存更具细粒度,从而提高缓存的利用率和命中率。
  3. 降低网络带宽消耗:由于只需更新变化的维度数据,减少了不必要的数据传输,降低了网络带宽的消耗。

Spring Cloud 实现:

在 Spring Cloud 中,可以使用 Redis 作为缓存中间件,通过维度化设计实现缓存。具体实现步骤如下:

  1. 定义不同维度的数据结构

    // 商品基础属性类
    public class Product {
         
        private Long id;               // 商品ID
        private String name;           // 商品名称
        private BigDecimal price;      // 商品价格
        private String description;    // 商品描述
        // 其他基础属性
    }
    
    // 商品图片信息类
    public class ProductImage {
         
        private Long productId;        // 商品ID
        private List<String> imageUrls; // 商品图片URL列表
    }
    
    // 商品状态类
    public class ProductStatus {
         
        private Long productId;        // 商品ID
        private Boolean available;     // 商品是否可用
    }
    
  2. 将维度化数据分别存入 Redis

    @Autowired
    private RedisTemplate<String, Object> redisTemplate; // Redis操作模板
    
    // 缓存商品基础属性数据
    public void cacheProductData(Product product) {
         
        redisTemplate.opsForValue().set("product:info:" + product.getId(), product); /
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

呆萌宝儿姐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值