别再死记硬背了!用‘学成在线’项目实战,一次性搞懂Java面试里的分布式锁、幂等性和断点续传

张开发
2026/4/19 17:49:02 15 分钟阅读

分享文章

别再死记硬背了!用‘学成在线’项目实战,一次性搞懂Java面试里的分布式锁、幂等性和断点续传
用学成在线项目实战拆解Java面试三大高频难题在Java中高级面试中分布式锁、幂等性和断点续传几乎是绕不开的技术话题。但很多候选人的回答往往停留在概念层面缺乏真实项目场景的支撑。本文将基于学成在线教育平台的实际开发经验带你从业务需求出发深入理解这三个技术点的实现逻辑和避坑要点。1. 订单支付模块中的分布式锁实战在学成在线的订单支付场景中我们遇到了典型的并发控制问题当多个用户同时抢购同一门限时优惠课程时如何防止超卖这就是分布式锁的用武之地。1.1 为什么需要分布式锁在单体架构中我们可以用Java的synchronized或ReentrantLock解决并发问题。但在微服务架构下这些本地锁机制就失效了——因为服务是多实例部署的每个实例都有自己的锁无法跨JVM互斥。学成在线遇到的真实案例某热门编程课程推出限时5折活动库存设置为100个名额活动开始后瞬间涌入300个请求由于没有正确的分布式锁最终卖出了120单导致20个用户无法正常上课1.2 基于Redis的分布式锁实现我们最终选择了Redis实现分布式锁主要考虑以下几点性能要求高支付场景对响应时间敏感不需要严格的强一致性团队对Redis更熟悉核心代码实现public boolean tryLock(String lockKey, String requestId, int expireTime) { return redisTemplate.opsForValue().setIfAbsent( lockKey, requestId, expireTime, TimeUnit.SECONDS ); } public boolean releaseLock(String lockKey, String requestId) { String script if redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) else return 0 end; return redisTemplate.execute( new DefaultRedisScriptLong(script, Long.class), Collections.singletonList(lockKey), requestId ) 1; }关键点解析setIfAbsent相当于SETNX命令只有key不存在时才能设置成功必须设置过期时间防止死锁释放锁时要验证requestId避免误删其他线程的锁使用Lua脚本保证原子性1.3 踩坑与优化在实际使用中我们遇到了几个典型问题问题现象原因分析解决方案锁提前释放业务执行时间超过锁过期时间合理设置过期时间引入看门狗机制自动续期锁误删A线程的锁被B线程释放每个锁绑定唯一requestIdRedis主从切换导致锁失效主节点锁信息未同步到从节点使用RedLock算法需权衡复杂度提示对于核心支付场景我们最终采用了Redisson客户端它内置了看门狗、可重入锁等高级特性大大降低了使用门槛。2. 任务调度中的幂等性保障在媒资处理模块中我们经常需要执行视频转码、内容审核等异步任务。如何防止任务被重复执行这就是幂等性设计要解决的问题。2.1 什么是幂等性简单说就是同样的参数多次调用同一个接口产生的结果与一次调用相同。在学成在线中典型的非幂等操作包括重复扣款重复生成订单重复转码视频重复发放优惠券2.2 三种幂等性实现方案对比我们根据不同业务场景采用了多种幂等方案方案一数据库唯一索引ALTER TABLE xc_orders ADD UNIQUE INDEX idx_order_no (order_no);适用场景订单创建、支付回调等方案二乐观锁Update(update xc_task set versionversion1 where id#{id} and version#{version}) int updateTaskVersion(Param(id) String id, Param(version) int version);适用场景库存扣减、任务状态更新方案三Redis令牌// 生成令牌 String token UUID.randomUUID().toString(); redisTemplate.opsForValue().set(order:token:userId, token, 30, TimeUnit.MINUTES); // 校验令牌 String savedToken redisTemplate.opsForValue().get(order:token:userId); if(!token.equals(savedToken)){ throw new BusinessException(请勿重复提交); }适用场景前端防重复提交2.3 学成在线的实践案例以视频转码任务为例我们采用了组合方案任务创建时生成唯一taskId任务执行前检查状态if(task.getStatus() ! TaskStatus.INIT){ log.warn(任务已处理taskId:{}, taskId); return; }更新状态时使用乐观锁int updated taskMapper.updateStatus(taskId, TaskStatus.PROCESSING); if(updated 0){ throw new ConcurrentAccessException(任务状态已被修改); }任务完成后记录执行日志到去重表3. 大文件上传与断点续传实现学成在线作为教育平台核心功能就是视频上传。当用户上传2GB的高清课程视频时如何保证上传的可靠性断点续传是关键。3.1 整体架构设计前端文件分块通常每块5MB计算文件MD5并发上传各分块失败自动重试后端分块临时存储我们使用MinIO记录上传进度合并分块校验文件完整性3.2 核心代码实现分块上传接口PostMapping(/upload/chunk) public ResponseEntityUploadResult uploadChunk( RequestParam(file) MultipartFile file, RequestParam(chunkNumber) int chunkNumber, RequestParam(chunkSize) long chunkSize, RequestParam(totalChunks) int totalChunks, RequestParam(identifier) String identifier, RequestParam(filename) String filename) { // 检查分块是否已上传 if (storageService.chunkExists(identifier, chunkNumber)) { return ResponseEntity.ok(UploadResult.skip(chunkNumber)); } // 存储分块 String chunkPath storageService.storeChunk(identifier, chunkNumber, file); // 更新上传进度 progressService.updateProgress(identifier, chunkNumber); return ResponseEntity.ok(UploadResult.success(chunkNumber, chunkPath)); }分块合并逻辑public void mergeChunks(String identifier, String filename) throws IOException { // 检查是否所有分块都已上传 if (!progressService.isUploadComplete(identifier)) { throw new IncompleteUploadException(还有分块未上传); } // 创建最终文件 Path outputFile Paths.get(uploadDir, filename); try (OutputStream os Files.newOutputStream(outputFile, StandardOpenOption.CREATE)) { // 按顺序合并所有分块 for (int i 0; i progressService.getTotalChunks(identifier); i) { Path chunk storageService.getChunkPath(identifier, i); Files.copy(chunk, os); } } // 校验文件MD5 String actualMd5 DigestUtils.md5Hex(Files.newInputStream(outputFile)); if (!actualMd5.equals(progressService.getFileMd5(identifier))) { Files.delete(outputFile); throw new CorruptedFileException(文件校验失败); } // 清理临时分块 storageService.cleanChunks(identifier); }3.3 性能优化技巧分块大小选择太小增加网络请求开销太大失去断点续传意义建议5-10MB根据网络质量调整并发上传控制// 前端并发控制示例 const MAX_CONCURRENT 3; const queue []; function uploadChunk(chunk) { if (queue.length MAX_CONCURRENT) { queue.push(chunk); return doUpload(chunk).finally(() { queue.splice(queue.indexOf(chunk), 1); checkQueue(); }); } }服务端优化使用Zero-Copy技术减少IO操作合并时采用流式处理避免内存溢出设置合理的超时和重试机制4. 面试中的项目难点表述技巧当面试官问你在项目中遇到的最大技术挑战是什么时如何将上述技术点有效传达以下是几个建议4.1 STAR法则的应用Situation简要说明场景在学成在线平台的订单支付模块中我们需要处理高并发下的库存扣减问题Task明确技术挑战确保在秒杀活动期间既不超卖也不出现死锁Action详细解决方案我们对比了三种分布式锁方案最终基于Redis实现了...特别处理了...Result量化成果上线后支撑了每秒3000的订单创建零超卖事故4.2 技术选型的思考过程不要直接说我们用了Redis而要展现决策过程考虑过哪些方案数据库锁、Zookeeper等每种方案的优缺点比较为什么最终选择当前方案做了哪些妥协和权衡4.3 典型问题与解决方案准备几个故事最棘手的Bug比如分布式锁的死锁问题性能优化案例从500ms优化到50ms的过程团队协作中的技术决策如何说服团队采用某种方案示例回答结构先一句话总结难点解释为什么这是个挑战说明尝试过的解决方案最终如何解决的从中学到了什么记住面试官想看到的不仅是你解决了什么问题更是你解决问题的思考过程和技术判断力。

更多文章