找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

3234

积分

0

好友

436

主题
发表于 2 小时前 | 查看: 5| 回复: 0

“系统又挂了!凌晨3点被叫起来改代码,结果只是Redis缓存没设过期时间——50万条数据把内存撑爆了。”

Redis这东西,用好了是神器,用不好就是深坑。今天,我们从数据结构分布式锁,从缓存策略集群部署,把Redis的核心知识点和实战避坑指南一次讲清楚。

01. 先说说 Redis 是什么

Redis,全称 Remote Dictionary Server,远程字典服务。官方的定义是:开源的、基于内存的数据结构存储系统

说人话就是:Redis是一个主要把数据存在内存里的数据库,读写速度极快,同时也能将数据持久化到硬盘,防止丢失。

为什么Redis这么火?核心就是!其读取速度可达10万次/秒,写入速度也能达到8万次/秒,传统的关系型数据库难以望其项背。

特性 说明 优势
基于内存 数据主要存储在内存中 纳秒级访问速度
数据结构丰富 String, List, Hash, Set, ZSet 支持多样化的业务场景
单线程 核心命令处理是单线程 避免了多线程的锁竞争开销
支持持久化 RDB + AOF 数据可靠性有保障
支持集群 主从、Sentinel、Cluster 实现高可用与横向扩展

02. Redis 数据结构——不仅是缓存

很多人误以为Redis只能存简单的字符串键值对,这只是它能力的冰山一角。Redis提供了5种基础数据结构,每种都对应着独特的应用场景。

2.1 String(字符串)—— 最常用

String类型是Redis最基础的数据结构,一个Key对应一个Value,常用于缓存、计数器等场景。

// Java 中使用 Redis
String value = redisTemplate.opsForValue().get("user:1001");
redisTemplate.opsForValue().set("user:1001", JSON.toJSONString(user));

// 设置过期时间(验证码场景)
redisTemplate.opsForValue().set("captcha:1234", "abcde", Duration.ofMinutes(5));

// 计数器(阅读量、点赞数)
redisTemplate.opsForValue().increment("page:view:20260315");  // +1
redisTemplate.opsForValue().decrement("stock:product:001");    // -1

面试点:String 最大能存多大?
:512 MB。

2.2 List(列表)—— 队列神器

List是一个双向链表,支持从头部或尾部进行插入和弹出操作,是构建简单消息队列的理想选择。

// 左边入队(生产者)
redisTemplate.opsForList().leftPush("queue:order", orderJson);

// 右边出队,阻塞等待最多10秒(消费者)
String order = redisTemplate.opsForList().rightPop("queue:order", 
    Duration.ofSeconds(10));

// 获取列表长度
Long length = redisTemplate.opsForList().size("queue:order");

典型场景

  • 消息队列:生产者左端推入(LPush),消费者右端弹出(RPop)
  • 最新列表:存储最新10条微博或评论
  • 粉丝列表:按关注时间顺序排列

2.3 Hash(哈希)—— 对象存储

Hash是一个String类型的Field和Value的映射表,特别适合存储对象。

// 存储用户对象
Map<String, Object> userMap = new HashMap<>();
userMap.put("name", "张三");
userMap.put("age", "28");
userMap.put("city", "北京");

redisTemplate.opsForHash().putAll("user:1001", userMap);

// 获取单个字段(无需反序列化整个对象)
String name = (String) redisTemplate.opsForHash().get("user:1001", "name");

// 获取整个对象
Map<Object, Object> user = redisTemplate.opsForHash().entries("user:1001");

面试点:Hash 和 String + JSON 存对象有什么区别?

  • String + JSON:存取整个对象方便,但更新任意字段都需要序列化/反序列化整个对象。
  • Hash:可以独立更新(HSet)或读取(HGet)某个字段,更节省网络流量,但Field数量不宜过多。
  • 最佳实践:频繁变动的对象字段用Hash,固定的配置信息用String+JSON。

2.4 Set(集合)—— 去重利器

Set是String类型的无序集合,通过哈希表实现,元素不重复。

// 添加用户标签
redisTemplate.opsForSet().add("user:1001:tags", "Java", "Redis", "架构");

// 检查是否拥有某个标签
Boolean hasTag = redisTemplate.opsForSet().isMember("user:1001:tags", "Java");

// 获取所有标签
Set<Object> tags = redisTemplate.opsForSet().members("user:1001:tags");

// 求交集(共同兴趣标签)
Set<Object> javaUsers = redisTemplate.opsForSet().intersect("users:tags:Java", "users:tags:Redis");

典型场景

  • 标签系统:文章标签、用户兴趣标签
  • 去重:用户点赞记录、UV统计
  • 社交关系:共同关注、共同好友

2.5 ZSet(有序集合)—— 排行榜专用

ZSet在Set的基础上,为每个元素关联了一个score(分数),元素按score排序。

// 添加用户积分
redisTemplate.opsForZSet().add("ranking:score", "user:1001", 9800.0);
redisTemplate.opsForZSet().add("ranking:score", "user:1002", 9600.0);
redisTemplate.opsForZSet().add("ranking:score", "user:1003", 9400.0);

// 获取用户排名(从0开始,reverseRank是降序排名)
Long rank = redisTemplate.opsForZSet().reverseRank("ranking:score", "user:1001");

// 获取前10名
Set<Object> top10 = redisTemplate.opsForZSet().reverseRange("ranking:score", 0, 9);

// 获取用户具体分数
Double score = redisTemplate.opsForZSet().score("ranking:score", "user:1001");

典型场景

  • 排行榜:游戏积分榜、商品热度榜
  • 延迟队列:用时间戳作为score,定时取出
  • 优先级队列:VIP用户优先处理

03. 缓存策略——怎么用才不翻车?

缓存用得好能极大提升性能,用不好则会导致数据不一致、系统雪崩。下面介绍几种常见的缓存读写策略。

3.1 Cache-Aside(旁路缓存)—— 最常用

应用程序直接与缓存和数据库交互。读时先读缓存,未命中则读库并回填;写时先更新数据库,再删除缓存。

public User getUserById(Long userId) {
    // 1. 先查缓存
    String cacheKey = "user:" + userId;
    String cached = redisTemplate.opsForValue().get(cacheKey);

    if (cached != null) {
        // 缓存命中,直接返回
        return JSON.parseObject(cached, User.class);
    }

    // 2. 缓存未命中,查数据库
    User user = userRepository.findById(userId);

    if (user != null) {
        // 3. 写入缓存,设置过期时间
        redisTemplate.opsForValue().set(
            cacheKey,
            JSON.toJSONString(user),
            Duration.ofHours(1)  // 1 小时过期
        );
    }

    return user;
}

public void updateUser(User user) {
    // 1. 先更新数据库
    userRepository.save(user);

    // 2. 删除缓存(注意:是删除,不是更新!)
    String cacheKey = "user:" + user.getId();
    redisTemplate.delete(cacheKey);

    // 思考:为什么是删除而不是更新缓存?
    // 答:在并发场景下,更新缓存可能导致数据不一致(数据库更新成功,但另一个线程将旧数据写入了缓存)。
    // 删除缓存,则下次读取时自然会从数据库加载最新数据,保证最终一致性。
}

流程图

缓存访问流程序列图

3.2 Read-Through(读穿透)

与Cache-Aside类似,但应用程序只与缓存交互。当缓存未命中时,缓存组件自身负责从数据库加载数据并填充缓存。对应用透明,如Spring Cache。

@Cacheable(value = "users", key = "#userId")
public User getUserById(Long userId) {
    // 只有在缓存未命中时,这个方法才会被执行
    return userRepository.findById(userId);
}

3.3 Write-Through(写穿透)

应用程序写数据时,同时写入缓存和数据库,且由缓存组件保证这两个写操作的原子性。数据一致性高,但写延迟增加。

public void saveUser(User user) {
    // 1. 先写缓存
    String cacheKey = "user:" + user.getId();
    redisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(user));

    // 2. 再写数据库
    userRepository.save(user);
}

3.4 Write-Behind(异步写入)

应用程序写数据时,只更新缓存,然后异步批量地将缓存数据持久化到数据库。性能极高,但存在数据丢失风险(缓存宕机)。

public void saveUser(User user) {
    // 1. 只写缓存
    String cacheKey = "user:" + user.getId();
    redisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(user));

    // 2. 异步写数据库(例如通过消息队列)
    messageQueue.send("user:save", user);
}
策略对比 策略 优点 缺点 适用场景
Cache-Aside 简单,灵活,兼容性好 存在并发一致性问题 绝大多数场景
Read-Through 对应用透明,代码简洁 依赖缓存组件能力 读多写少
Write-Through 数据一致性高 写延迟较高,性能一般 对数据一致性要求极高的场景
Write-Behind 写入性能极高 可能丢失数据,实现复杂 允许最终一致性的高写入场景(如计数、日志)

04. 缓存问题——面试必问

使用缓存时,有三个经典问题必须面对:缓存穿透、缓存击穿、缓存雪崩

4.1 缓存穿透——查不到还一直查

问题:查询一个数据库中根本不存在的数据,缓存中自然也没有。导致每次请求都直接穿透到数据库,可能压垮DB。

解决方案

  1. 缓存空值:即使数据库没查到,也将一个标识(如“NULL”)写入缓存,并设置一个较短的过期时间。
  2. 布隆过滤器:在查询缓存前,先用布隆过滤器判断Key是否可能存在。如果布隆过滤器说“不存在”,则一定不存在,直接返回。
// 方案1:缓存空值
public User getUserById(Long userId) {
    String cacheKey = "user:" + userId;
    String cached = redisTemplate.opsForValue().get(cacheKey);

    if (cached != null) {
        if ("NULL".equals(cached)) {
            return null;  // 缓存了空值
        }
        return JSON.parseObject(cached, User.class);
    }

    User user = userRepository.findById(userId);

    if (user != null) {
        redisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(user), Duration.ofHours(1));
    } else {
        // 缓存空值,设置短过期时间(如5分钟)
        redisTemplate.opsForValue().set(cacheKey, "NULL", Duration.ofMinutes(5));
    }

    return user;
}

// 方案2:布隆过滤器(伪代码示意)
@Configuration
public class BloomFilterConfig {
    @Bean
    public RedisBloomFilter<String> bloomFilter() {
        // 预期数据量:1000万,误判率:0.01
        return new RedisBloomFilter<>(10_000_000, 0.01);
    }
}

public User getUserByIdWithBloom(Long userId) {
    String key = "user:" + userId;
    // 先检查布隆过滤器
    if (!bloomFilter.mightExist(key)) {
        return null;  // 肯定不存在
    }
    // 再走正常的缓存/数据库查询流程
    // ...
}

4.2 缓存击穿——热点 key 失效

问题:一个热点Key在缓存中过期的瞬间,大量并发请求同时发现缓存失效,全部涌向数据库,造成数据库瞬时压力过大。

解决方案

  1. 分布式锁:只允许一个请求去数据库查询并回填缓存,其他请求等待或重试。
  2. 逻辑过期:缓存不设置物理过期时间,而是将过期时间存在Value里。发现逻辑过期后,异步更新缓存,并返回旧数据。
// 方案1:分布式锁(推荐)
public User getUserByIdWithLock(Long userId) throws InterruptedException {
    String cacheKey = "user:" + userId;
    String cached = redisTemplate.opsForValue().get(cacheKey);

    if (cached != null && !"NULL".equals(cached)) {
        return JSON.parseObject(cached, User.class);
    }

    // 获取锁,只允许一个请求查数据库
    String lockKey = "lock:user:" + userId;
    Boolean acquired = redisTemplate.opsForValue()
        .setIfAbsent(lockKey, "1", Duration.ofSeconds(10)); // SET NX PX

    if (Boolean.TRUE.equals(acquired)) {
        try {
            // 双重检查:拿到锁后再次确认缓存是否已被其他线程填充
            cached = redisTemplate.opsForValue().get(cacheKey);
            if (cached != null && !"NULL".equals(cached)) {
                return JSON.parseObject(cached, User.class);
            }

            // 查数据库
            User user = userRepository.findById(userId);
            if (user != null) {
                redisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(user), Duration.ofHours(1));
            } else {
                redisTemplate.opsForValue().set(cacheKey, "NULL", Duration.ofMinutes(5));
            }
            return user;
        } finally {
            redisTemplate.delete(lockKey); // 释放锁
        }
    } else {
        // 没拿到锁,短暂等待后重试
        Thread.sleep(50);
        return getUserByIdWithLock(userId);
    }
}

4.3 缓存雪崩——大量 key 同时失效

问题:大量缓存Key在同一时间集中过期,导致所有对应请求在短时间内全部穿透到数据库。

解决方案

  1. 随机过期时间:为缓存Key的过期时间加上一个随机值,避免同时过期。
  2. Redis集群:通过集群将数据分散,降低单点故障影响。
  3. 多级缓存:引入本地缓存(如Caffeine)作为二级缓存。
  4. 永不失效:结合“逻辑过期”策略,后台异步更新。
// 方案1:随机过期时间
public void setCacheWithRandomExpire(String key, Object value) {
    // 基础过期时间 1 小时,加随机偏移量(0-30 分钟)
    int randomMinutes = new Random().nextInt(30);
    redisTemplate.opsForValue().set(key, value,
        Duration.ofMinutes(60 + randomMinutes));
}

05. 分布式锁——Redis 的杀手锏

在单机多线程环境下,我们可以用 synchronizedReentrantLock。但在分布式系统中,多个服务实例部署在不同机器上,就需要一个跨JVM的锁机制,这就是分布式锁

5.1 Redis 分布式锁基本原理

核心命令:SET lock_key unique_value NX PX expire_time

  • NX:仅当Key不存在时才设置,保证互斥性。
  • PX:设置过期时间,防止锁持有者崩溃导致死锁。
  • unique_value:唯一标识(如UUID),确保只有锁的持有者才能释放。
@Service
public class RedisLockService {
    @Autowired
    private StringRedisTemplate redisTemplate;

    /**
     * 获取锁
     * @param lockKey 锁的 key
     * @param requestId 唯一标识(UUID)
     * @param expireTime 过期时间(毫秒)
     * @return 是否获取成功
     */
    public boolean tryLock(String lockKey, String requestId, long expireTime) {
        // SET key value NX PX expireTime
        Boolean result = redisTemplate.opsForValue()
            .setIfAbsent(lockKey, requestId, Duration.ofMillis(expireTime));
        return Boolean.TRUE.equals(result);
    }

    /**
     * 释放锁(Lua脚本保证原子性)
     * @param lockKey 锁的 key
     * @param requestId 唯一标识
     */
    public void releaseLock(String lockKey, String requestId) {
        // Lua 脚本:只有持有锁的请求才能释放
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
            "return redis.call('del', KEYS[1]) else return 0 end";
        redisTemplate.execute(
            new DefaultRedisScript<>(script, Long.class),
            Collections.singletonList(lockKey),
            requestId
        );
    }
}

5.2 使用示例:秒杀扣库存

@Service
public class OrderService {
    @Autowired
    private RedisLockService lockService;
    @Autowired
    private StockService stockService;

    public void createOrder(Long productId, Integer quantity) {
        String lockKey = "lock:stock:" + productId;
        String requestId = UUID.randomUUID().toString();

        try {
            // 尝试获取锁(10秒过期)
            boolean acquired = lockService.tryLock(lockKey, requestId, 10_000);
            if (!acquired) {
                throw new RuntimeException("系统繁忙,请稍后重试");
            }

            // 1. 检查库存
            Integer stock = stockService.getStock(productId);
            if (stock < quantity) {
                throw new RuntimeException("库存不足");
            }
            // 2. 扣减库存
            stockService.decreaseStock(productId, quantity);
            // 3. 创建订单...
        } finally {
            // 释放锁
            lockService.releaseLock(lockKey, requestId);
        }
    }
}

5.3 Redis 分布式锁的进阶问题与解决方案

面试官:Redis分布式锁有什么缺陷?

问题 1:单点故障
如果Redis主节点宕机,锁将丢失。即使有主从复制,主从同步是异步的,在主节点获取锁后宕机,可能导致锁在从节点上未同步,从而出现多个客户端同时持有锁。

解决方案:RedLock算法。在多个独立的Redis实例上尝试获取锁,当且仅当从大多数(N/2+1)实例上获取到锁,并且总耗时小于锁的过期时间时,才认为获取成功。

问题 2:可重入性
同一个线程内,如果方法A获取了锁,方法A又调用了也需要同一把锁的方法B,就会造成死锁。

解决方案:在客户端记录重入次数。可以使用ThreadLocal,或者直接使用成熟的客户端如Redisson,它内置了可重入锁的实现。

问题 3:锁自动过期,业务未执行完
如果业务执行时间超过了锁的过期时间,锁会自动释放,可能导致其他线程进入临界区,造成数据混乱。

解决方案Watchdog(看门狗)自动续期。Redisson的看门狗机制会在业务执行期间,定时(默认每10秒)检查并延长锁的持有时间,直到业务完成并主动释放锁。

// 使用Redisson框架(已解决上述大部分问题)
RLock lock = redissonClient.getLock("myLock");
lock.lock(30, TimeUnit.SECONDS); // Watchdog会自动续期
try {
    // 业务逻辑
} finally {
    lock.unlock();
}

06. Redis 持久化——数据怎么不丢?

Redis是内存数据库,数据保存在内存中。持久化机制保证了在服务器重启后数据不丢失。主要有两种方式:RDB和AOF。

6.1 RDB(快照)

原理:在指定时间间隔内,将内存中的数据集快照(Snapshot)写入磁盘二进制文件(dump.rdb)。

触发方式

  • 手动触发SAVE(阻塞)或 BGSAVE(后台异步)。
  • 自动触发:在配置文件中设置规则。
# redis.conf 配置示例
save 900 1      # 900秒内至少有1个key被改变,则触发bgsave
save 300 10     # 300秒内至少有10个key被改变
save 60 10000   # 60秒内至少有10000个key被改变

优点:文件紧凑,恢复速度快,适合大规模数据恢复和备份。
缺点:可能丢失最后一次快照之后的数据(取决于备份周期)。

6.2 AOF(追加文件)

原理:记录服务器执行的所有写操作命令,并在服务器启动时重新执行这些命令来还原数据。

# redis.conf 配置
appendonly yes
appendfsync everysec  # 推荐配置,每秒同步一次,平衡性能与安全
# appendfsync always  # 每次写命令都同步,最安全,性能最低
# appendfsync no      # 由操作系统决定何时同步,性能最好,最不安全

优点:数据安全性更高,最多丢失一秒数据。AOF文件易于理解,可手动修改(修复)。
缺点:文件体积通常比RDB大,恢复速度慢。

6.3 混合持久化(Redis 4.0+)

结合了RDB和AOF的优点。在AOF重写时,将当前内存数据以RDB格式写入AOF文件头部,后续的写操作继续以AOF格式追加。

# 开启混合持久化
aof-use-rdb-preamble yes

恢复时,先加载RDB部分,再重放AOF日志,速度和安全性兼备。

面试点:生产环境用哪种?
:通常开启混合持久化,并定期备份RDB文件到远程存储,以应对极端情况。

07. Redis 集群——高可用怎么搞?

单机Redis存在容量和性能瓶颈。Redis提供了三种集群化方案:主从复制、Sentinel哨兵和Cluster集群。

7.1 主从复制(Replication)

一个主节点(Master)负责写,多个从节点(Slave)负责读,数据从主节点异步复制到从节点。

配置从节点

# 在从节点的 redis.conf 中配置
replicaof 192.168.1.100 6379

Java客户端配置读写分离

@Bean
public RedisConnectionFactory redisConnectionFactory() {
    RedisStandaloneConfiguration config = new RedisStandaloneConfiguration("192.168.1.100", 6379);
    LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
        .readFrom(ReadFrom.REPLICA_PREFERRED)  // 优先读从节点
        .build();
    return new LettuceConnectionFactory(config, clientConfig);
}

缺点:主节点故障需要手动切换。

7.2 Sentinel(哨兵)—— 自动故障转移

哨兵是一个分布式系统,用于监控主从节点,并在主节点故障时,自动将一个从节点升级为新主节点,并通知客户端。

哨兵配置(sentinel.conf)

sentinel monitor mymaster 192.168.1.100 6379 2
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 60000

Java客户端连接Sentinel

@Bean
public RedisConnectionFactory redisConnectionFactory() {
    RedisSentinelConfiguration config = new RedisSentinelConfiguration()
        .master("mymaster")
        .sentinel("192.168.1.101", 26379)
        .sentinel("192.168.1.102", 26379);
    return new LettuceConnectionFactory(config);
}

7.3 Cluster(集群)—— 数据分片

Redis官方提供的分布式解决方案。采用无中心结构,数据按槽(slot,共16384个) 分布在不同节点上,支持在线水平扩容。

创建集群(3主3从)

redis-cli --cluster create \
    192.168.1.101:6379 \
    192.168.1.102:6379 \
    192.168.1.103:6379 \
    192.168.1.104:6379 \
    192.168.1.105:6379 \
    192.168.1.106:6379 \
    --cluster-replicas 1

Java客户端连接Cluster

@Bean
public RedisConnectionFactory redisConnectionFactory() {
    RedisClusterConfiguration config = new RedisClusterConfiguration();
    config.addNode("192.168.1.101", 6379);
    config.addNode("192.168.1.102", 6379);
    // ... 添加所有节点
    return new LettuceConnectionFactory(config);
}

08. Redis 性能优化——怎么更快?

8.1 内存优化与淘汰策略

防止内存被撑爆,必须设置最大内存和淘汰策略。

# redis.conf
maxmemory 2gb
maxmemory-policy allkeys-lru  # 推荐:当内存不足时,移除最近最少使用的key

其他策略:volatile-lru(只淘汰带过期时间的Key)、allkeys-randomvolatile-ttl(淘汰剩余时间最短的)等。

8.2 Pipeline(管道)

将多个命令打包一次发送,减少网络往返延迟(RTT),显著提升批量操作的性能。

// 普通方式:10次网络往返
for (int i = 0; i < 10; i++) {
    redisTemplate.opsForValue().increment("counter");
}

// Pipeline方式:1次网络往返
List<Object> results = redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
    for (int i = 0; i < 10; i++) {
        connection.stringCommands().incr("counter".getBytes());
    }
    return null;
});

8.3 Lua 脚本

Lua脚本在Redis中原子性执行,可用于实现复杂的原子操作,避免竞态条件。

// Lua 脚本:原子性扣减库存
String luaScript = "local stock = redis.call('get', KEYS[1]) " +
    "if tonumber(stock) >= tonumber(ARGV[1]) then " +
    "  return redis.call('decrby', KEYS[1], ARGV[1]) " +
    "else " +
    "  return -1 " +
    "end";

Long result = redisTemplate.execute(
    new DefaultRedisScript<>(luaScript, Long.class),
    Collections.singletonList("stock:product:001"),
    "10"  // 扣减数量
);
if (result == -1) {
    throw new RuntimeException("库存不足");
}

09. 常见面试问题汇总

Q1:Redis 和 Memcached 有什么区别?

对比项 Redis Memcached
数据结构 丰富(5种基础+扩展) 仅Key-Value字符串
持久化 支持(RDB/AOF) 不支持
集群模式 原生支持Cluster、Sentinel 客户端实现分布式
线程模型 单线程(主处理逻辑) 多线程
适用场景 缓存、消息队列、分布式锁等 纯缓存

Q2:Redis 为什么快?

  1. 基于内存操作
  2. 单线程模型,避免上下文切换和锁竞争。
  3. IO多路复用(epoll/kqueue),高效处理大量连接。
  4. 使用高效的数据结构,如跳表、压缩列表。

Q3:Redis 内存满了怎么办?

  1. 使用 maxmemory-policy 配置内存淘汰策略。
  2. 分析内存使用(redis-cli --bigkeys),优化数据结构。
  3. 对Value进行压缩。
  4. 搭建Redis集群,进行数据分片。

Q4:如何保证 Redis 和数据库的数据一致性?

这是一个权衡问题,没有银弹。 方案 一致性 性能 复杂度
先更库,再删缓 最终一致 低(推荐)
延迟双删(更库→删缓→延迟→再删缓) 更好
订阅数据库Binlog(如Canal) 最终一致
分布式事务(如XA) 强一致

最佳实践:对于大多数业务,采用 Cache-Aside + 先更新数据库再删除缓存 的策略,并接受秒级的最终一致性。对于极强一致性要求,可考虑使用分布式事务,但需承受性能代价。

Q5:Redis 集群怎么保证数据一致?

Redis主从复制是异步的,默认不保证强一致。

  • 强一致性:使用 WAIT 命令,等待写操作同步到指定数量的从节点。但会阻塞并影响性能。
  • 最终一致性:异步复制是默认方式,性能好,网络分区或主节点宕机时可能丢数据。

对于高可用的分布式系统设计,通常优先保证可用性和分区容错性(CAP中的AP),接受最终一致性。


掌握以上这些核心知识点和实战代码,足以应对绝大多数与Redis面试相关的问题。技术学习永无止境,在实践中不断思考和总结才能融会贯通。欢迎到云栈社区交流讨论更多技术细节。




上一篇:宇树科技科创板IPO募资42亿,近半投入机器人大模型研发补脑
下一篇:剖析美团到店利润首降:与抖音的商战胶着与外卖资源挤占
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-4-16 23:26 , Processed in 0.620131 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表