深入浅出:我如何用 Redis + Kafka 实现高可用“最终一致性”系统架构(实战+思考)

🏆本文收录于「滚雪球学SpringBoot」(全网一个名)专栏,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!

🎬 前言:数据一致性,真不是那么难!

作为一名全栈开发,我一直在想一个问题------如何让分布式系统又快又稳,尤其是在高并发场景下,保证数据一致性? 。思来想去想了很久,最终发现最靠谱的方案就是通过 RedisKafka 来搭建最终一致性消息链路。它们像两位"得力助手",帮我解决了好多常见的架构问题------缓存穿透、消息丢失、重复消费等,这些让我乃至大家都头疼的问题,它们都能轻松搞定,不信你可以采访下身边的大佬。

这篇文章是我心得总结所撰,希望能给大家带来一些启发。如果你也有类似的系统设计需求,或者对 Redis 和 Kafka 的结合感兴趣,往下看,我会用我的亲身经验告诉你,如何将这两个"神器"搭配起来,让你的项目分布式架构更加可靠。

🧩 一、分布式架构中的三大一致性挑战

在我们搭建分布式系统时,常常要面临三个关键问题,那三个?

1. 缓存穿透

你肯定知道,缓存是用来减轻数据库压力的。可是当请求的数据不在缓存里时,它就直接查询数据库。很多无效的请求会导致数据库压力增大,尤其是在流量高峰期,简直就是毁灭性的打击,我就遇到过,那时候还是刚接触到一个电商平台,为了迭代才发现项目中存在这个性能压力。

我怎么做的呢?

通过 Redis,我设置了"空值缓存"------当缓存没有数据时,我会把"没有数据"的标志缓存起来。这样,当其他相同请求过来时,就不会再打到数据库了,而是直接从缓存中得到数据返回给前端。是不是挺巧妙的?没错,这也是后来延伸为面试八股文之一!基本都会被拿来问,还伴随其他两问,缓存击穿跟缓存雪崩,此次我就不一一展开讲解了,感兴趣的小伙伴可以私下学习。

2. 消息丢失

其次,对于分布式系统中,消息的传递至关重要。有时候因为网络问题或者系统崩溃,消息很有可能就会丢失。消息丢了,数据就不一致了,这个问题不能小觑,也是非常令人头疼的问题之一。

我怎么做的呢?

通过 消息中间件Kafka ,我可以保证即使消息丢失了,也能从上次的位置重新开始消费。Kafka 的 消息持久化特性就像给每条消息上了"保险",让它不怕丢失。即使系统宕机重启,也能无缝恢复。

3. 重复消费

有时候,消费者可能会重复消费同一条消息,导致重复操作或者数据错误。这会导致很多麻烦,比如订单被重复创建、支付被重复扣费,大家一定都不想看到这种情况。

我怎么做的呢?

每条消息我都会赋予一个 唯一的标识符,然后用 Redis 来记录哪些消息已经处理过。如果消息已经处理过了,就直接跳过,避免重复处理。虽然还有很多其他方式,但是思路就是这么个思路,怎么实现都不要紧,关键是你要有这个解题思路。

🧱 二、Redis:让缓存更加"聪明"

✅ 1. 缓存穿透的聪明解决方案

以前我一直觉得缓存就是用来存数据的,直到有一天,我发现一个问题------如果缓存里找不到数据,怎么办? 难道就直接去数据库查吗?那不就白白增加数据库的压力?

于是我想到了一个方案,缓存空值 !如果查不到数据,我就把"查不到数据"的标记存入缓存,让后续相同的请求直接返回空值,避免频繁查询数据库

代码示例:缓存穿透

java 复制代码
public Product getProduct(String productId) {
    String key = "product:" + productId;
    String cacheResult = redisTemplate.opsForValue().get(key);

    if (cacheResult != null) {
        if ("NULL".equals(cacheResult)) {
            return null;  // 如果缓存为NULL,返回空
        }
        return JSON.parseObject(cacheResult, Product.class);  // 从缓存中取数据
    }

    Product product = productRepository.findById(productId);
    if (product != null) {
        redisTemplate.opsForValue().set(key, JSON.toJSONString(product), 300, TimeUnit.SECONDS);  // 缓存正常数据
    } else {
        redisTemplate.opsForValue().set(key, "NULL", 60, TimeUnit.SECONDS);  // 缓存空值,避免后续查数据库
    }

    return product;
}

这个方法不仅避免了数据库的重复查询,还能提升系统的性能,效果杠杠的

✅ 2. 幂等性:防止重复消费

在一些关键操作中,比如 订单处理 ,我们不希望一个消息被重复处理两次。重复操作不仅会造成资源浪费,还可能导致数据不一致。

我的做法是:每条消息都会有一个唯一的 ID,处理完消息之后,我会用 Redis 来记录这个 ID,下次再遇到相同的消息时,就跳过不再处理

代码示例:防止重复消费

java 复制代码
public void processOrder(OrderMessage message) {
    String redisKey = "order:" + message.getId();

    Boolean exists = redisTemplate.hasKey(redisKey);
    if (Boolean.TRUE.equals(exists)) {
        System.out.println("重复消息,跳过处理");
        return;  // 如果已经处理过,就跳过
    }

    // 处理订单
    orderService.saveOrder(message);

    // 设置标记,表示消息已经处理过
    redisTemplate.opsForValue().set(redisKey, "processed", 1, TimeUnit.HOURS);
}

幂等性保证 :通过这种方式,我确保了 即使消息被多次消费,也不会出问题。这是我在实际项目中用过的一个很简单但非常有效的技巧,分享给大家。

🚀 三、Kafka:消息的"保障"

✅ 1. Kafka 保证消息的可靠传递

想象一下,假设我们有一个 订单处理系统用户下单时,我们需要把订单信息传递给其他服务。万一消息丢了,整个订单流程就乱套了!这个问题对我们来说至关重要。

Kafka 是一个高可靠的消息队列,它保证了消息 持久化不丢失 。即使某个消费者失败,消息仍然保留在 Kafka 中,我们可以通过 offset 重新消费消息。

代码示例:Kafka 消费

java 复制代码
@KafkaListener(topics = "user_behavior", groupId = "analyze_group")
public void consume(ConsumerRecord<String, String> record, Acknowledgment ack) {
    try {
        UserBehaviorEvent event = JSON.parseObject(record.value(), UserBehaviorEvent.class);
        userBehaviorService.process(event);  // 处理业务逻辑
        ack.acknowledge();  // 手动提交 offset
    } catch (Exception e) {
        log.error("消费失败,将进行重试", e);
        // 不提交 offset,Kafka 会重新投递消息
    }
}

这里的 ack.acknowledge() 让我们手动控制消息的消费状态,确保消费完毕才能提交,保证消息不丢失。

🧩 四、最终一致性链路架构设计

结合 RedisKafka ,我们可以创建一个既高效又可靠的 最终一致性消息链路,处理高并发、高可用场景下的复杂需求。

场景示例:用户下单 ➜ 异步发货 ➜ 推送通知

  1. 用户下单:订单服务将订单写入数据库。
  2. Kafka 通知 :写入 Kafka order_created 主题,通知其他服务。
  3. 发货服务:监听 Kafka,处理发货。
  4. 通知服务:监听 Kafka,推送消息给用户。
  5. Redis 幂等:通过 Redis 防止重复消费。

架构图:最终一致性链路

这里我们将整个链路通过流程图的形式,给大家演示下,以辅助大家理解。

✅ 链路中的容错机制:

  • Kafka 提供消息持久化 ➜ 防丢失

  • Redis 记录处理状态 ➜ 防重复

  • 幂等逻辑处理 ➜ 防数据错误

  • Offset 控制 ➜ 可重播 ➜ 自恢复

🎯 五、总结:Redis + Kafka,强强联手

我们通过结合 RedisKafka ,就能够轻松解决 缓存穿透消息丢失消费幂等性 等常见问题,实现系统的高可用和高可靠性。

从我个人的经验来说,这两个中间件的结合,不仅可以提升系统的稳定性,还能极大地简化我们处理分布式数据一致性时的复杂度。

希望这篇文章对大家有所帮助!如果你们在设计自己的系统时遇到类似问题,试试 Redis + Kafka 这两组合吧,你会发现它们真的是非常强大的搭档。

如果你有任何问题,随时跟我交流,我们一起探讨更好的解决方案!

📣 关于我

我是bug菌,CSDN | 掘金 | InfoQ | 51CTO | 华为云 | 阿里云 | 腾讯云 等社区博客专家,C站博客之星Top30,华为云多年度十佳博主&最具价值贡献奖,掘金多年度人气作者Top40,掘金等各大社区平台签约作者,51CTO年度博主Top12,掘金/InfoQ/51CTO等社区优质创作者;全网粉丝合计 30w+ ;硬核微信公众号「猿圈奇妙屋」,欢迎你的加入!免费白嫖最新BAT互联网公司面试真题、4000G PDF电子书籍、简历模板等海量资料,你想要的我都有,关键是你不来拿。

-End-

相关推荐
舒一笑9 分钟前
PandaCoder重大产品更新-引入Jenkinsfile文件支持
后端·程序员·intellij idea
PetterHillWater41 分钟前
AI编程之CodeBuddy的小试
后端·aigc
codervibe1 小时前
如何用 Spring Security 构建无状态权限控制系统(含角色菜单控制)
java·后端
codervibe1 小时前
项目中如何用策略模式实现多角色登录解耦?(附实战代码)
java·后端
expect7g1 小时前
Flink-Checkpoint-2.OperatorChain
后端·flink
大葱白菜1 小时前
🧱 Java 抽象类详解:从基础到实战,掌握面向对象设计的核心基石
后端·程序员
SimonKing1 小时前
颠覆传统IO:零拷贝技术如何重塑Java高性能编程?
java·后端·程序员
MARS_AI_1 小时前
大语言模型驱动智能语音应答:技术演进与架构革新
人工智能·语言模型·自然语言处理·架构·信息与通信
mCell2 小时前
为什么我们需要 `.proto` 文件
后端·微服务·架构