Redis缓存介绍

二级缓存(Secondary Cache)是指在系统中使用两个缓存层次的架构,通常用于优化数据读取效率。最常见的二级缓存结构包括 本地缓存(一级缓存)远程缓存(Redis作为二级缓存)

在很多应用中,为了提高性能,使用 Redis 作为第二层缓存(即二级缓存)。一级缓存通常是应用程序的内存缓存(例如 Java 中的 ConcurrentHashMap 或使用框架如 CaffeineEhcache 等),而 Redis 作为二级缓存,提供分布式的缓存层,能够支持多个应用实例共享缓存数据。

在这种模式下,一级缓存负责快速获取和存储数据,二级缓存(Redis)则是一个备份缓存,它的数据通常比一级缓存的存储数据更大,且存储时间较长,能够缓存大量的共享数据。

一级缓存与二级缓存的工作原理

  1. 一级缓存(本地缓存):一级缓存通常存在于应用的内存中,每次访问时都会首先检查本地缓存。本地缓存的优势是访问速度非常快,但缺点是只能在单一应用实例中使用,且缓存数据的量受到应用内存的限制。
  2. 二级缓存(Redis):Redis 作为二级缓存通常用来存储大量数据。它是分布式的,可以跨多个应用实例共享缓存数据,适合存储相对持久且访问频繁的数据。Redis 的访问速度也非常快,但相较于本地缓存稍慢。

Redis 二级缓存的工作流程

  1. 查询流程:首先检查一级缓存:当应用程序需要获取数据时,首先会检查本地缓存(一级缓存)。如果一级缓存中没有数据:然后查询 Redis 二级缓存,如果 Redis 中有数据,则从 Redis 中获取并将数据缓存到一级缓存中。如果 Redis 中也没有数据,则从数据库中查询,并将数据先写入 Redis,然后再缓存到一级缓存。
  2. 写入流程:当数据被更新时,先更新数据库,再更新 Redis(二级缓存),最后将更新的数据写入一级缓存。通常使用 缓存失效策略,如 TTL(过期时间)来保证缓存中的数据保持一致性。
  3. 一致性问题:由于本地缓存和 Redis 缓存存在不同步的问题,因此需要采取一些策略来保持缓存数据的一致性。例如:更新缓存策略:每次更新数据时,不仅更新数据库,也更新缓存。过期策略:通过定期过期或手动失效缓存,确保缓存中的数据不会过时。

Redis 二级缓存的优势

  • 提高性能:通过将数据存储在内存中,无需每次都访问数据库,减少数据库的压力,提高响应速度。
  • 减少数据库负载:只要数据存在缓存中,就无需查询数据库,减少数据库的压力。
  • 扩展性:使用 Redis 作为二级缓存时,能够有效处理大规模的数据缓存,支持分布式环境下的多实例共享。

Redis 二级缓存的常见问题与解决方案

  1. 缓存穿透:即查询的数据在缓存和数据库中都不存在,解决方案是将空值缓存(空对象缓存),避免每次请求都查询数据库。
  2. 缓存击穿:某个热点数据的缓存过期,导致多个请求同时查询数据库,解决方案是使用互斥锁(SETNX 命令)来保证只有一个请求查询数据库,其他请求等待。
  3. 缓存雪崩:大量缓存同时过期,导致所有请求同时访问数据库,解决方案是设置不同的缓存过期时间,避免缓存集中失效。
  4. 缓存一致性:本地缓存和 Redis 中的数据可能不一致,解决方案是使用双写机制,即更新数据库时,同时更新 Redis 和本地缓存。

Redis二级缓存

为了实现 Redis 作为二级缓存,可以使用 JedisSpring Cache 等工具来管理缓存。以下是一个简单的 JedisConcurrentHashMap 本地缓存实现二级缓存的示例。

1. 使用 Jedis 和本地缓存实现 Redis 二级缓存

Maven 依赖
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>4.3.0</version>
</dependency>
代码实现
import redis.clients.jedis.Jedis;
import java.util.concurrent.ConcurrentHashMap;

public class RedisSecondaryCacheExample {
    
    // 一级缓存:本地缓存,使用 ConcurrentHashMap
    private static final ConcurrentHashMap<String, String> localCache = new ConcurrentHashMap<>();

    // Redis 客户端实例(假设 Redis 服务运行在本地)
    private static final Jedis jedis = new Jedis("localhost", 6379);

    public static void main(String[] args) {
        String key = "user:1001";

        // 1. 尝试从一级缓存获取数据
        String value = getFromLocalCache(key);
        if (value == null) {
            // 2. 如果一级缓存没有,从 Redis 获取数据
            value = getFromRedis(key);
            if (value == null) {
                // 3. 如果 Redis 没有,从数据库获取数据
                value = getFromDatabase(key);

                // 4. 更新 Redis 和一级缓存
                setToRedis(key, value);
                setToLocalCache(key, value);
            }
        }
        
        System.out.println("Fetched value: " + value);
    }

    // 从本地缓存获取数据
    public static String getFromLocalCache(String key) {
        return localCache.get(key);
    }

    // 设置数据到本地缓存
    public static void setToLocalCache(String key, String value) {
        localCache.put(key, value);
    }

    // 从 Redis 获取数据
    public static String getFromRedis(String key) {
        return jedis.get(key);
    }

    // 设置数据到 Redis
    public static void setToRedis(String key, String value) {
        jedis.setex(key, 3600, value); // 设置 1 小时过期时间
    }

    // 模拟从数据库获取数据
    public static String getFromDatabase(String key) {
        // 模拟从数据库查询数据
        return "Database value for " + key;
    }
}

  1. 本地缓存(一级缓存):使用 ConcurrentHashMap 作为本地缓存。通过 localCache.get(key) 方法检查是否有缓存数据。
  2. Redis 二级缓存:使用 Jedis 客户端连接 Redis。通过 jedis.get(key) 获取 Redis 中的缓存数据,若 Redis 中没有数据,则从数据库查询并更新 Redis 和本地缓存。
  3. 缓存操作:使用 setex 方法将数据写入 Redis,并设置过期时间(1 小时)。
  4. 模拟数据库查询:getFromDatabase 方法模拟从数据库获取数据的操作。实际生产中,应该替换为实际的数据库查询操作。

2. 使用 Spring Cache 配置 Redis 作为二级缓存

Spring 提供了 Spring Cache 抽象,可以非常方便地实现二级缓存,结合 Redis 和本地缓存。以下是 Spring Cache 配置示例。

Maven 依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
Spring 配置示例
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.cache.CacheManager;

@Configuration
@EnableCaching
public class CacheConfig {

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
        RedisCacheConfiguration cacheConfig = RedisCacheConfiguration.defaultCacheConfig();
        return RedisCacheManager.builder(connectionFactory)
                .cacheDefaults(cacheConfig)
                .build();
    }
}
使用 Spring Cache
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

@Service
public class UserService {

    // Redis 作为二级缓存,使用 @Cacheable 注解来标记缓存操作
    @Cacheable(value = "userCache", key = "#userId")
    public String getUserById(String userId) {
        // 模拟从数据库获取数据
        return "Database value for user " + userId;
    }
}

  1. @Cacheable 注解:使用 @Cacheable 注解标记需要缓存的方法,Spring 会自动将方法的返回值缓存到 Redis 中。
  2. Redis 配置:通过 RedisCacheManager 配置 Redis 作为缓存提供者。
  3. 本地缓存与 Redis 缓存:Spring 会先查找本地缓存(如 ConcurrentHashMap),如果没有,则从 Redis 中获取缓存。如果 Redis 中没有,则执行方法并缓存返回值。
Redis的碎碎念 文章被收录于专栏

Redis面试中的碎碎念

全部评论

相关推荐

04-02 20:41
西北大学 Java
一、基础信息与求职意向类你现在是在哪?你对工作地点有什么要求吗?(北上广深都&nbsp;OK&nbsp;吗?)二、项目相关类(用户答题项目)你挑一个项目来聊一下。这个题目是什么题目类型,这些是怎么规定的?所有的题目都是依赖于你们自己去写吗?题目是随机生成的,它的范围是随机生成吗?会生成各种难易程度的题是吗?那怎么判断题目结果是否正确呢?没有主观题的话需要怎么处理(判断)?那我们的第一版(项目)有没有这些&nbsp;AI&nbsp;相关功能呢?如果是基于第一版,我们怎么样去做主观题的判断呢?你这里面用的那个&nbsp;redis,这个&nbsp;redis&nbsp;的话是做了些啥呢?那相当于说&nbsp;redis&nbsp;只是做排序用是吧?那你这个(redis&nbsp;数据)如果说数据有更新,你这块需要怎么更新?这个(先操作数据库再删缓存)有没有什么问题?如果说我们有一个数据场景,在一个方法内后续的流程会立马用到缓存,那延迟双删有效果吗?那你这个积分的更新的话是怎么做的呢?怎么避免一个更新多更的场景?积分是加积分吗?还是减积分?那累加的话,你怎么避免加少加错呢?如果说是集群场景(积分累加),怎么处理?使用&nbsp;redis&nbsp;的&nbsp;set&nbsp;NX&nbsp;做分布式锁,假设出现网络超时,这种会不会有风险呢?分布式的场景有哪些?分布式锁的优缺点是什么?锁的获取是有公平性的吗?死锁产生的必要条件有哪些?积分的更新除了加锁这个方案,还有其他方案吗?(乐观锁&nbsp;/&nbsp;CAS)具体怎么实现?是在哪一层实现的?(数据库层面)通过&nbsp;SQL&nbsp;要怎么做来保证积分更新的准确性?(数据库更新积分的&nbsp;SQL)你这个&nbsp;service&nbsp;要怎么写,大概可以描述一下吗?(举例:库存有&nbsp;30&nbsp;个,要把库存加&nbsp;1)你怎么保证数据库操作中判断的数值是一致的?或者说你这个一样的值是什么值?你怎么样保证&nbsp;AI&nbsp;出题的结果按照预期返回?那&nbsp;AI&nbsp;出题会不会有乱返回的情况?三、技术基础类缓存的优缺点是什么?缓存性能比数据库好的具体原因有了解过吗?缓存有哪些结构呢?缓存雪崩有了解过吗?(怎么解决?)如果数据量特别大,缓存雪崩该怎么处理?缓存的删除逻辑&nbsp;/&nbsp;过期逻辑是什么样子的?比如说缓存过期了,会立马删除吗?TCP&nbsp;和&nbsp;UDP&nbsp;了解吗?TCP&nbsp;和&nbsp;UDP&nbsp;的区别是什么?UDP&nbsp;的使用场景是什么呢?TCP&nbsp;是怎么去保证它的可靠性传输的?(关于&nbsp;TCP)滑动窗口的相关知识可以说一下吗?TCP&nbsp;的慢启动为什么要这么设计呢?mysql&nbsp;怎么避免注入的呢?在&nbsp;mysql&nbsp;层面是怎么做防&nbsp;SQL&nbsp;注入的,不是在业务层面?四、AI&nbsp;工具与大模型相关类你有用那个&nbsp;open&nbsp;class(OpenAI&nbsp;相关工具)吗?那你用它去做了哪些事情呢?那你有没有去写过&nbsp;skills?那怎么避免&nbsp;skills&nbsp;带来的安全风险呢?那&nbsp;skills&nbsp;和&nbsp;MCP&nbsp;的区别是什么呢?你平时中用到了哪些大模型?你觉得大模型给你个人有了哪些提升吗?或者说他给你带来的帮助有哪些呢?大模型生成的代码是一个白盒的吗?(功能是否符合预期?)五、算法实操类挑简单&nbsp;/&nbsp;中等&nbsp;/&nbsp;困难的算法题来做一道。(二分法递归的一道hard题,题目有点想不起来了,手撕失败,写了50多行没跑通)六、职业发展类(求职者提问后,无直接问题,为交流式回应,无明确提问点)
点赞 评论 收藏
分享
评论
4
5
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务