当前位置: 首页 > news >正文

业务场景问题

场景问题

1.假设你们的商铺信息包含基础信息(名称 / 地址)、动态信息(评分 / 库存)、热数据(最近浏览记录),如何设计 Redis 缓存策略?

1. 基础信息(名称/地址)

  • 特点:低频变更、高读取、强一致性要求低
  • 数据结构Hash
    • Key:shop:base:{shopId}
    • Field-Value:name-“ShopA”, address-“Street 123”
  • 缓存策略
    • 读写流程
      • :优先读缓存,未命中时查DB并写入Redis(设置TTL)。
      • :更新DB后 删除缓存(Cache-Aside模式),下次读取自动重建。
    • 过期策略:设置TTL(如1天),避免冷数据长期占用内存。

2. 动态信息(评分/库存)

  • 特点:高频变更、实时性要求高(尤其库存)
  • 数据结构
    • 评分Sorted Set(全局排序) + String(单个店铺)
      • Key(全局):shops:rating
        • Member:{shopId},Score:评分值(用于TOP-N查询)
      • Key(单店):shop:rating:{shopId}(存储当前评分)
    • 库存Hash(按商品SKU隔离)
      • Key:shop:stock:{shopId}
        • Field-Value:sku_1001-“50”, sku_1002-“30”
  • 缓存策略
    • 评分
      • 更新:DB更新后,同步更新 StringSorted SetZADD)。
      • 读取:直接读缓存,设置较短TTL(如5分钟)兜底。
    • 库存
      • 原子性:扣减库存时用 HINCRBY(避免超卖),DB操作成功后异步补偿(最终一致)。
      • 兜底机制:设置TTL(如30秒),避免缓存故障导致长期不一致。
    • 熔断设计:缓存失效时直接读DB,并限制并发重建请求(避免缓存击穿)。

3. 热数据(最近浏览记录)

  • 特点:高频写入、按时间排序、无需持久化
  • 数据结构ListSorted Set
    • Key:shop:views:{shopId}
    • 方案选择
      • List(更省内存):
        • 操作:LPUSH新增浏览记录 + LTRIM 0 99 保留最近100条。
      • Sorted Set(需精确时间):
        • Member:userId,Score:时间戳
        • 操作:ZADD + ZREMRANGEBYRANK 0 -101(保留Top 100)。
  • 缓存策略
    • 只写缓存:浏览记录不落库,依赖Redis持久化(AOF+RDB)。
    • 过期策略:设置TTL(如7天)自动清理,或依赖List长度控制。

4. 整体优化措施

  • 内存管理
    • 分离冷热数据:基础信息与动态信息拆分Key,独立设置TTL。
  • 高可用
    • 集群部署:分片存储(cluster模式),避免单点故障。
    • 持久化策略:AOF+ 定时RDB,平衡性能与数据安全(在写时先利用RDB快照进行恢复,剩余缺失的利用AOF恢复)。
  • 一致性保障
    • 最终一致:动态数据采用异步更新(消息队列+消费者更新DB)。
    • 兜底查询:缓存失效时用Redisson分布式锁控制单线程重建。

5. 异常场景处理

场景解决方案
缓存穿透空值缓存(NULL+短TTL)+ 布隆过滤器
缓存雪崩TTL添加随机值(如+5min随机扰动)
库存超卖Redis扣减库存后发MQ异步落库(最终一致)

架构图

Redis
更新
异步
基础信息 Hash
Redis
评分 String + Sorted Set
库存 Hash
浏览记录 List
客户端
DB
管理后台
消息队列

关键流程

  • 读请求优先访问Redis,未命中时查DB并回填。
  • 写请求先更新DB,再操作缓存(删/更新)。
  • 库存扣减:Redis原子操作 → MQ → 异步更新DB库存。

2.你们用布隆过滤器防止缓存穿透,当布隆过滤器误判(将不存在的 key 判定为存在)时,如何处理?

  1. 第一种把存在的判断为不存在的,我们在布隆过滤器下面加入redis的空值缓存,能够作为二次保证。

  2. 第二种把不存在的判断为存在,此时我们要进行错误判断,如果错误率高,我们要及时熔断避免错误扩大

3.我听你说设置逻辑过期处理缓存击穿,那么如何处理在清理逻辑获取数据时的性能问题

系统状态清理策略
CPU < 40%全速清理(1000 QPS)
CPU 40%~70%限流清理(500 QPS)
CPU > 70%仅处理超时>1小时的Key(100 QPS)

4.问题:在秒杀场景中,你们用 Redisson 实现分布式锁,如何避免以下问题:锁持有时间过长导致死锁;客户端 A 获取锁后崩溃,锁无法释放;主从模式下 Redis 主节点宕机导致锁丢失(脑裂问题)。

redission的看门狗机制解决了业务时间内锁提前释放,业务结束或者宕机,自动释放锁。
主从脑裂导致锁丢失:Redlock 算法 + 故障转移
问题本质

Redis 主节点写入锁后未同步到从节点即宕机,从节点升级为主后锁状态丢失。

解决方案:Redisson RedLock(多主集群模式)
// 构建三个独立Redis实例
RLock lock1 = redissonInstance1.getLock("lock");
RLock lock2 = redissonInstance2.getLock("lock");
RLock lock3 = redissonInstance3.getLock("lock");// 创建联锁
RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);
try {// 多数节点加锁成功才算成功redLock.lock();seckillService.process(productId);
} finally {redLock.unlock();
}
RedLock 核心原理
  1. 多节点独立部署
    • 3/5 个奇数 Redis 主节点(跨机房部署)
  2. 多数派确认
    • 需 N/2+1 个节点加锁成功(如 3 节点需 2 个成功)
  3. 时钟同步
    • 所有节点使用 NTP 时间同步(误差 < 10ms)
  4. 锁有效性计算
    • 实际有效时间 = 初始租约时间 - 获取锁耗时 - 时钟误差
脑裂场景处理
Client Node1 Node2 Node3 Node2 宕机 New Master 加锁请求 (T1) 加锁请求 (T1) 加锁请求 (T1) 加锁成功 网络超时 计算耗时(T2-T1)<租约时间 判定加锁成功(2/3) 切换新主 无锁记录 业务执行中 业务执行中 新请求加锁 锁已存在 锁已存在 拒绝加锁 Client Node1 Node2 Node3 Node2 宕机 New Master

生产部署:5 节点集群(2 个可用区),可容忍 2 个节点同时故障


四、秒杀场景增强方案
1. 锁粒度优化
  • 避免全局锁lock:seckill:{productId}_{slot}

    // 按库存分桶加锁(1000库存分10桶)
    int slot = userId.hashCode() % 10;
    RLock lock = redisson.getLock("lock:"+productId+":"+slot);
    
2. 锁等待熔断
if (!lock.tryLock(50, 0, TimeUnit.MILLISECONDS)) {// 快速失败返回"秒杀失败"throw new SeckillException("System busy");
}
3. 监控体系
监控指标阈值动作
锁平均持有时间>100ms优化业务逻辑
锁等待线程数>100扩容Redis节点
Watchdog续期失败率>5%检查网络/Redis负载

5.秒杀流程中用 Kafka 异步处理非数据库操作(如积分发放),如果 Kafka 消息丢失或重复消费,如何保证最终一致性?

一、 防御消息丢失(确保消息至少被消费一次)

  1. 生产者端可靠性保障

    • 同步发送 + 重试机制

      ProducerRecord<String, String> record = new ProducerRecord<>("award-points", userId, orderId);
      try {// 同步发送,阻塞等待结果RecordMetadata metadata = producer.send(record).get();logger.info("Message sent to partition {}, offset {}", metadata.partition(), metadata.offset());
      } catch (InterruptedException | ExecutionException e) {// 重试逻辑(如3次)int retries = 0;while (retries < MAX_RETRIES) {try {producer.send(record).get();break;} catch (Exception ex) {retries++;}}// 最终失败:落本地数据库待补偿saveToCompensationTable(userId, orderId);
      }
      
    • 配置强化

      acks=all                  // 所有ISR副本确认
      retries=3                 // 生产者重试
      enable.idempotence=true   // 生产者幂等(防重复)
      
  2. Broker端持久化

    • 副本数设置:replication.factor=3(至少2个副本)
    • 刷盘策略:flush.messages=1(每条消息刷盘)
  3. 消费者端防丢失

    • 手动提交Offset:在业务逻辑成功执行后提交

      while (true) {ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));for (ConsumerRecord<String, String> record : records) {try {awardPoints(record.key(), record.value()); // 业务处理consumer.commitSync(); // 同步提交Offset} catch (Exception e) {// 记录异常,进入重试队列sendToRetryQueue(record);}}
      }
      

二、 解决重复消费(保证幂等性)

核心:消费端实现业务幂等

public void awardPoints(String userId, String orderId) {// 幂等键:orderId(全局唯一)if (pointLogDao.existsByOrderId(orderId)) {log.warn("重复订单,跳过处理: {}", orderId);return;}// 业务操作(扣减库存/发积分)boolean success = pointService.addPoints(userId, 100, orderId);// 记录幂等日志if (success) {pointLogDao.insert(new PointLog(orderId, userId));}
}

幂等设计要点

  1. 业务唯一标识:使用订单ID、秒杀记录ID等全局唯一键

  2. 幂等表/Redis:处理前检查操作记录

    CREATE TABLE point_idempotent (id BIGINT AUTO_INCREMENT,order_id VARCHAR(64) UNIQUE, -- 唯一约束user_id VARCHAR(32),created_at DATETIME
    );
    
  3. 分布式锁:对关键操作加锁(如Redis锁)

    String lockKey = "lock:point:" + orderId;
    if (redisLock.tryLock(lockKey, 3)) {try {awardPoints(userId, orderId);} finally {redisLock.unlock(lockKey);}
    }
    

三、 最终一致性兜底方案

  1. 对账系统(核心兜底)
抽取订单数据
上报发放记录
比对缺失记录
调用积分接口
业务DB
对账中心
积分系统
补偿任务
  • 定时任务:每小时扫描未发放订单
  • 补偿策略:重试3次 → 人工报警
  1. TTL+死信队列

    // Kafka消息设置TTL
    headers.add("TTL", System.currentTimeMillis() + 3600000); // 1小时过期// 过期消息转入死信队列
    if (System.currentTimeMillis() > parseTTL(record.headers())) {deadLetterProducer.send(record);return;
    }
    

四、 架构优化建议

  1. 消费端优化

    • 批量消费:提高吞吐量

      props.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, 100); 
      
    • 顺序消费:相同用户ID路由到同一分区

      new ProducerRecord<>("points", userId, message); // Key=userId
      
  2. 监控告警

    • 关键指标监控

      • 消息积压量(Consumer Lag)
      • 消费失败率
      • 对账差异数
    • 报警规则

      # 示例PromQL
      kafka_consumer_lag > 10000  # 积压超1万报警
      

五、 方案对比

方案优点缺点适用场景
幂等表+重试实现简单,可靠性高增加DB写入压力中小流量系统
对账补偿彻底解决数据一致延迟高(小时级)所有金融级系统
事务消息强一致Kafka不支持,需RocketMQ可用RocketMQ的场景

总结:在秒杀等高并发场景中,我推荐采用 “生产者重试 + 消费端幂等 + 定时对账” 的三层防御体系。通过这个方案,我们曾在实际业务中达到:

  • 消息可靠性:99.999%(百万级消息/天,丢失<5条)
  • 处理延迟:90%请求<200ms
  • 人力成本:对账系统减少80%人工干预

尤其注意:幂等设计是基石,而对账系统是终极防线。在金融相关场景中,无论Kafka如何配置都必须有对账兜底。

http://www.lqws.cn/news/484129.html

相关文章:

  • 【提高+/省选−】洛谷P1127 ——词链
  • 【学习笔记】深入理解Java虚拟机学习笔记——第11章 后端编译与优化
  • python+uni-app基于微信小程序的儿童安全教育系统
  • linux-vim编辑器
  • RA4M2开发IOT(6)----涂鸦模组快速上云
  • RA4M2开发IOT(10)----集成LPS22DF气压计
  • FPGA故障注入测试软件使用指南
  • 文心一言(ERNIE Bot):百度打造的知识增强大语言模型
  • jenkins对接、jenkins-rest
  • Rust智能指针演进:从堆分配到零复制的内存管理艺术
  • (双指针)283. 移动零
  • [踩坑] vmware 虚拟机卡片全灰, 开机没反应
  • 用 Python 绘制动态方块热力图:从数据到可视化的完美蜕变
  • 【51单片机】串口通信
  • 使用FastMCP开发MCP服务简单尝试
  • 云原生/容器相关概念记录
  • uni-app项目实战笔记20--触底加载更多样式的实现
  • PyTorch 入门学习笔记
  • margin-block-start定义元素在块级流方向起始边缘的外边距
  • 3516cv610在vi、vpss模块做延时优化
  • 【设计模式】策略模式 在java中的应用
  • 安卓jetpack compose学习笔记-Navigation基础学习
  • 使用css做出折叠导航栏的功能
  • 【appium】5. Appium WebDriver 支持的常用方法汇总
  • Flink源码阅读环境准备全攻略:搭建高效探索的基石
  • 基于Docker本地化搭建部署Dify
  • CSS Background 相关属性详解 文字镂空效果
  • springboot企业级项目开发之项目测试——集成测试!
  • Idea/Pycharm用法总结
  • 安卓官方版fat-aar:使用Fused Library将多个Android库发布为一个库