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

Java场景题面试合集

1. 如何保证支付成功后 “更新订单状态、扣减库存、发放优惠券” 的事务一致性?

这三个操作涉及跨服务 / 跨数据库交互,需通过分布式事务方案保证一致性,核心是 “要么全成,要么全败”。具体可采用以下方案:

  • 优先选 TCC 模式:适合业务逻辑复杂、需强一致性的场景。

    • Try 阶段:检查资源合法性(如库存是否充足、优惠券是否可发放),冻结资源(如预扣库存、标记优惠券为 “待发放”)。
    • Confirm 阶段:若 Try 成功,执行实际操作(更新订单为 “已支付”、扣减冻结库存、发放优惠券到用户账户)。
    • Cancel 阶段:若任一操作失败,回滚资源(解冻库存、取消优惠券冻结状态、订单状态回滚为 “未支付”)。
  • 次选事务消息(最终一致性):若业务允许短暂不一致(如 1 分钟内),可通过 RocketMQ 等中间件实现。

    • 支付成功后,发送 “待确认” 事务消息,本地先更新订单状态(标记为 “支付中”)。
    • 消息确认后,库存服务、优惠券服务消费消息执行扣减和发放;若消费失败,通过重试机制补执行,确保最终一致。

2. 订单表分库分表策略及跨分页、ID 唯一性问题解决

订单表数据量大时,需通过水平分片(按行拆分) 分散压力,核心是合理选择分片键。

  • 分库分表策略

    • 分片键选user_id:用户的所有订单会落在同一分片,查询 “我的订单” 时无需跨库,效率高;缺点是查询 “全局订单(如平台所有订单)” 需跨分片。
    • 分片键选order_id:若订单 ID 含时间戳(如前 8 位为日期),可按时间范围分片(如按月份分表),适合 “按时间查订单” 场景;需保证 ID 生成规则包含分片信息(如嵌入用户 ID 哈希值)。
    • 中间件:用 Sharding-JDBC 配置分片规则(如按user_id % 8分 8 个库,每个库分 16 张表)。
  • 跨分页查询解决

    • 若按user_id分片,用户订单在单分片内,分页查询(如 “我的订单第 2 页”)直接在分片内执行,无问题。
    • 若查全局分页(如 “平台今日前 100 条订单”):通过冗余表(存订单 ID、用户 ID、创建时间等核心字段)或 Elasticsearch 预聚合,避免跨库全量扫描。
  • 订单 ID 全局唯一性

    • 雪花算法(Snowflake) 生成 ID:64 位 ID 包含时间戳(确保有序)、机器 ID(避免分布式冲突)、序列号(同一毫秒内去重),既保证唯一性,又便于按时间范围分片。

3. 高并发下用 Redis 实现优惠券防重发放

核心是通过 Redis 的原子操作,在发放前拦截重复请求,避免同一用户重复领取。

  • 防重方案步骤

    1. 定义唯一标识键:以coupon:user:{user_id}:{coupon_id}为 key(用户 ID + 优惠券 ID 唯一确定一次领取)。
    2. 原子性检查与标记:发放前,用SET key 1 NX EX 86400(NX:不存在才设置,EX:24 小时过期)执行操作。
      • 若返回 OK:表示首次领取,继续执行发放逻辑(写入数据库)。
      • 若返回 null:表示已领取,直接返回 “已发放” 提示,拦截重复请求。
    3. 异常处理:若 Redis 设置成功但数据库写入失败,需删除 Redis 键(DEL key),避免误拦截。
  • 优势:Redis 单线程原子操作保证并发安全,性能高(支持每秒 10 万 + 请求),适合高并发场景。

4. 商品价格叠加多优惠的灵活计算策略

需设计可扩展的规则引擎,支持新增优惠类型时无需修改核心代码,核心是 “策略模式 + 配置化”。

  • 设计方案

    1. 抽象优惠策略:定义统一接口DiscountStrategy,包含calculate(PriceContext context)方法(入参为商品基础价、用户信息等)。
    2. 实现具体策略:为每种优惠实现接口,如:
      • MemberDiscount:按会员等级计算折扣(如 VIP 打 9 折)。
      • FullReduction:满减计算(如满 200 减 30)。
      • CouponDiscount:优惠券抵扣(如固定减 50 元)。
    3. 定义优惠优先级:通过配置表设置执行顺序(如先会员折扣→再满减→最后用优惠券),避免因顺序导致的结果差异。
    4. 动态加载策略:从数据库读取用户可享受的优惠规则(如用户有哪些优惠券、是否满足满减条件),动态选择对应的策略类执行计算。
  • 优势:新增优惠类型时,只需新增策略类并配置规则,符合 “开闭原则”,灵活支持业务迭代

5. 订单支付成功后通知物流系统,如何保证消息不丢失?

消息不丢失需覆盖发送、存储、消费三个环节,核心是 “每个环节都有确认机制”,具体方案如下:

  • 发送环节:确保消息能成功投递到中间件

    • 事务消息(如 RocketMQ 的 TransactionMQ)绑定本地事务与消息发送:支付成功后,先执行本地 “更新订单为已支付” 事务,再发送 “通知物流” 的事务消息;若本地事务失败,消息不会被投递,避免消息超前。
    • 发送端加重试机制:若消息发送超时 / 失败,通过定时任务(如每隔 10s)重试,上限 3 次,避免网络波动导致的瞬时失败。
  • 存储环节:确保中间件能持久化消息

    • 中间件开启持久化(如 RocketMQ 将消息写入 CommitLog 磁盘文件,Kafka 落盘到分区日志),即使中间件宕机,重启后可恢复消息。
    • 配置消息过期时间(如 24 小时),避免无效消息长期占用存储。
  • 消费环节:确保物流系统能正确处理消息

    • 消费端采用手动 ACK 机制:物流系统处理完 “创建物流单” 后,再向中间件发送确认(ACK);若处理失败(如接口报错),不发送 ACK,中间件会将消息重新放入队列,等待重试。
    • 消费端加幂等处理:物流系统用 “订单 ID” 作为唯一键,处理前检查是否已创建物流单,避免重复处理(因重试导致的消息重复)。

6. 用户下单预占库存,15 分钟未支付自动释放,如何实现?

核心是 “定时触发库存释放”,需兼顾实时性与性能,推荐延迟队列 + 定时任务兜底方案:

  • 主方案:基于中间件的延迟队列

    • 下单预占库存时,同时向延迟队列(如 RabbitMQ 的死信队列)发送一条 “释放库存” 消息,设置消息 TTL(存活时间)为 15 分钟。
    • 消息过期后,自动进入死信交换机绑定的死信队列,库存服务监听该队列,消费消息时检查订单状态:若订单仍为 “未支付”,则释放预占库存(恢复可用库存),并更新订单为 “已取消”。
    • 优势:无需主动轮询,实时性高(过期即处理),对系统压力小。
  • 兜底方案:定时任务补漏

    • 因中间件可能存在消息丢失风险,需加定时任务(如 Quartz)每小时执行一次:扫描 “未支付且创建时间超过 15 分钟” 的订单,批量释放其预占库存。
    • 扫描时用索引优化(如按 “订单状态 + 创建时间” 建联合索引),避免全表扫描,减少 DB 压力。

7. 每日对账时发现订单金额与支付记录不一致,如何设计核对系统?

核对系统需实现 “自动比对、差异定位、异常处理”,核心是 “全量比对 + 分层校验”:

  • 第一步:明确数据源与比对维度

    • 数据源:订单表(订单 ID、应付金额、实付金额、订单状态)、支付记录表(支付 ID、关联订单 ID、支付金额、支付状态、支付时间)。
    • 比对维度:按 “订单 ID” 关联,核对 “实付金额是否相等”“订单状态与支付状态是否匹配”(如订单 “已支付” 对应支付 “成功”)。
  • 第二步:自动比对流程

    1. 全量同步:每日凌晨(如 2 点),通过 ETL 工具(如 Flink)将前一天的订单数据与支付数据同步到核对库(独立于业务库,避免影响业务)。
    2. 关联比对:用订单 ID 左连接支付记录,生成差异表:
      • 金额差异:订单实付≠支付金额(如订单 100 元,支付 99 元)。
      • 状态差异:订单 “已支付” 但无支付记录,或支付 “成功” 但订单 “未支付”。
      • 冗余差异:支付记录对应订单不存在(可能支付错单)。
    3. 分级处理
      • 可自动修复:如状态差异(支付成功但订单未更新),调用订单服务接口补更新状态。
      • 需人工介入:如金额差异(需财务核查是否有退款、优惠计算错误),生成差异报表推送给运营 / 财务。
  • 第三步:保障机制

    • 比对过程日志全量记录(谁、何时、处理了什么差异),支持追溯。
    • 重试机制:若同步或比对失败,自动重试 3 次,避免临时网络问题导致的遗漏。

8. 异步处理订单的线程池任务堆积,如何定位和调整参数?

核心是 “先定位堆积原因,再针对性调优”,分两步处理:

第一步:定位任务堆积原因
  • 监控核心指标(通过 Spring Boot Actuator 或 Prometheus):
    • 线程池状态:活跃线程数(是否达最大值)、队列剩余容量(是否已满)、拒绝次数(是否激增)。
    • 任务执行耗时:平均 / 最大执行时间(若耗时过长,会导致线程被占用,新任务只能入队列)。
    • 任务提交速率:单位时间提交的任务数(是否超过线程池处理能力)。
  • 常见原因
    • 任务执行慢:如处理时调用了慢接口(第三方物流、支付回调),或 DB 查询未走索引。
    • 线程资源不足:核心线程数 / 最大线程数设置过小,无法及时处理任务。
    • 队列容量不合理:队列过大导致任务积压,过小则频繁触发拒绝策略。
第二步:调整参数与优化
  • 针对任务执行慢
    • 优化任务逻辑:异步任务拆分(如 “更新订单状态” 与 “通知用户” 拆分为两个任务),避免单任务做太多事;慢接口加缓存或异步化。
    • 增加超时控制:用Future.get(timeout)设置任务超时时间,避免线程被无限阻塞。
  • 针对线程资源不足
    • 调整线程池参数(结合任务类型):
      • IO 密集型任务(如调用外部接口):最大线程数可设为CPU核心数*2,允许更多线程并行等待 IO。
      • CPU 密集型任务(如复杂计算):最大线程数设为CPU核心数+1,避免线程切换开销。
    • 动态调整:用动态线程池(如 Hippo4j),支持运行时修改核心线程数、队列容量,无需重启服务。
  • 针对队列问题
    • 队列容量适中:若任务允许短暂积压,队列容量可设为最大线程数*10;若需快速响应,队列设小些(如 100),并配置合理的拒绝策略(如记录日志 + 异步重试,而非直接丢弃)。

9. 频繁查询不存在的商品 ID 导致 DB 压力,如何防御?

核心是 “提前拦截无效请求,减少 DB 访问”,采用 “多级缓存 + 过滤” 方案:

  • 第一级:布隆过滤器(Bloom Filter)前置过滤

    • 启动时加载所有有效商品 ID 到布隆过滤器(如 Guava 的 BloomFilter),存储在内存中。
    • 收到商品查询请求时,先通过布隆过滤器判断:
      • 若过滤器判定 “不存在”:直接返回 “商品不存在”,不查 DB。
      • 若判定 “可能存在”(允许小概率误判):继续后续查询(因布隆过滤器有极小假阳性)。
    • 优势:内存占用小(100 万商品 ID 约占 120KB),查询耗时微秒级,适合高并发场景。
  • 第二级:缓存空结果

    • 对布隆过滤器误判的 “不存在商品 ID”,查询 DB 后发现不存在,将其写入 Redis(键为product:{id},值为null),设置短期过期时间(如 5 分钟)。
    • 下次同一 ID 查询时,直接从 Redis 获取空结果,避免再次访问 DB。
  • 第三级:限流与监控

    • 对高频查询同一不存在 ID 的请求(可能是恶意攻击),用 Sentinel 限流(如单 ID 每秒最多 5 次请求),防止刷库。
    • 监控异常 ID:统计 “查询不存在商品 ID 的次数”,超过阈值(如单 ID 日查 1000 次)告警,人工排查是否为恶意请求或 ID 生成逻辑错误。

10. 促销期间如何防止系统被流量打挂?实现一个基于 Sentinel 的限流方案

核心是 “按需限流 + 熔断降级”,基于 Sentinel 实现 “流量控制 + 过载保护”:

方案设计步骤
  1. 明确限流维度(按业务优先级划分):

    • 核心接口:下单接口(/order/create)、支付接口(/pay/submit)—— 允许较高阈值,保证核心流程可用。
    • 非核心接口:商品详情(/product/detail)、评论列表(/comment/list)—— 阈值可设低,优先保障下单。
    • 用户维度:对普通用户限流,对 VIP 用户放宽限制(通过userId白名单)。
  2. 配置限流规则(通过 Sentinel 控制台或 Nacos 持久化):

    • 限流模式:QPS 模式(限制每秒请求数),如核心接口/order/create设 QPS=10000,非核心/product/detail设 QPS=5000。
    • 流控效果:快速失败(超出阈值直接返回 “系统繁忙”),或匀速排队(如秒杀场景,控制请求匀速进入,避免瞬间峰值)。
  3. 熔断降级兜底

    • 对依赖的下游服务(如库存服务、支付服务)配置熔断规则:若接口成功率低于 90% 或响应时间超过 500ms,自动熔断(暂停调用),避免级联失败。
    • 降级策略:返回缓存数据(如商品详情用本地缓存兜底)或默认值(如 “库存查询失败,请稍后再试”)。
  4. 监控与动态调整

    • 通过 Sentinel Dashboard 实时监控流量曲线、通过 / 拒绝数,发现阈值不合理时动态调整(无需重启)。
    • 结合压测结果:促销前通过压测确定各接口最大承载 QPS,限流阈值设为压测值的 80%(留缓冲)。
http://www.xdnf.cn/news/19983.html

相关文章:

  • 「数据获取」中国科技统计年鉴(1991-2024)Excel
  • 江协科技STM32学习笔记补充之004
  • ETL VS ELT企业应该怎么选择数据集成方式
  • 前缀和和差分思路理解以及典题题解
  • Java面试宝典:Redis的设计、实现
  • Flash Attention vs Paged Attention:大语言模型注意力计算的内存管理革命
  • 【国内电子数据取证厂商龙信科技】IOS 逆向脱壳
  • Milvus快速入门以及用 Java 操作 Milvus
  • PAT 1093 Count PAT‘s
  • [技术革命]Harmonizer:仅20MB模型如何实现8K图像_视频的完美和谐化?
  • 三高项目-缓存设计
  • k8s证书理论知识之/etc/kubernetes/pki/ 和/var/lib/kubelet/pki/的区别
  • 将 PDF 转换为 TIFF 图片:简单有效的 Java 教程
  • 23种设计模式——抽象工厂模式(Abstract Factory Pattern)详解
  • 实战复盘:pnpm Monorepo 中的 Nuxt 依赖地狱——Unhead 升级引发的连锁血案
  • Node.js 18+安装及Claude国内镜像使用、idea中claude插件下载指南
  • MMD动画(二)动作制作
  • Spring线程池ThreadPoolTaskExecutor‌详解
  • 大语言模型的“思考”逻辑:从Token生成到上下文理解的内部流程
  • 我的创作纪念日——《惊变365天》
  • 裸签、Attach、Detach及其验签方式
  • Docker学习笔记(二):镜像与容器管理
  • 基于STM32的智能家居环境监控系统设计
  • 如何看懂GPU架构?万云智算一分钟带你了解GPU参数指标
  • Matter安全实现
  • Deathnote: 1靶场渗透
  • RTC实时时钟RX8025SA国产替代FRTC8025S
  • 2025打磨机器人品牌及自动化打磨抛光设备技术新版分析
  • 为何三折叠手机只有华为可以?看华为Mate XTs非凡大师就知道
  • 【CouponHub项目开发】EasyExcel解析Excel并使用线程池异步执行和延时队列兜底