海南网站运营公司苏州网站建设建站网

张小明 2026/1/10 8:41:46
海南网站运营公司,苏州网站建设建站网,想让客户公司做网站的话语,建设一个网站的文案需要一、业务场景与挑战1.1 12306余票查询场景在12306系统中#xff0c;用户需要实时查询列车不同站点、不同座位类型的余票信息。为提升查询性能#xff0c;我们将余票信息缓存在Redis中。但在用户下单支付时#xff0c;需要同时更新数据库和缓存中的余票数据。核心挑战#x…一、业务场景与挑战1.1 12306余票查询场景在12306系统中用户需要实时查询列车不同站点、不同座位类型的余票信息。为提升查询性能我们将余票信息缓存在Redis中。但在用户下单支付时需要同时更新数据库和缓存中的余票数据。核心挑战高并发场景下的数据一致性扣减操作的原子性保证系统的高可用性要求1.2 数据一致性问题分析问题类型描述影响程度缓存穿透查询不存在的数据绕过缓存直接访问数据库高缓存击穿热点key过期瞬间大量请求直达数据库高缓存雪崩大量缓存同时过期数据库压力激增极高数据不一致缓存与数据库数据不一致中高二、一致性方案深度对比2.1 方案对比分析方案一致性复杂度性能适用场景先写缓存再写DB弱低高对一致性要求不高的读多写少场景先写DB再写缓存弱低中读多写少可容忍短暂不一致先删缓存再写DB弱低中写多读少场景缓存双删最终一致中中对一致性要求较高有MQ中间件先写DB再删缓存最终一致低高大部分业务场景推荐使用Binlog异步更新最终一致高高大型系统多数据源同步2.2 各种方案的缺陷分析方案1先写缓存再写数据库 ❌java// 问题示例代码 public boolean updateStock(String trainId, int seats) { // 1. 先更新缓存 redisTemplate.opsForValue().set(cacheKey, seats); // 2. 再更新数据库可能出现失败 int result stockMapper.updateStock(trainId, seats); return result 0; }问题缓存更新成功但数据库更新失败导致数据永久不一致。方案2先写数据库再写缓存 ❌javapublic boolean updateStock(String trainId, int seats) { // 1. 先更新数据库 int result stockMapper.updateStock(trainId, seats); if (result 0) return false; // 2. 并发问题此时另一个线程可能已经更新了数据库但还没写缓存 redisTemplate.opsForValue().set(cacheKey, seats); return true; }方案3先删缓存再写数据库 ❌javapublic boolean updateStock(String trainId, int seats) { // 1. 删除缓存 redisTemplate.delete(cacheKey); // 2. 更新数据库 int result stockMapper.updateStock(trainId, seats); // 3. 问题在1和2之间读请求可能读取旧数据并回填缓存 return result 0; }三、推荐方案深度解析3.1 方案5先写数据库再删除缓存推荐3.1.1 核心原理text写操作流程 1. 开启事务 2. 更新数据库 3. 提交事务 4. 删除缓存 读操作流程 1. 查询缓存命中则返回 2. 未命中则查询数据库 3. 将数据写入缓存3.1.2 实现代码javaService Slf4j public class TicketStockService { Autowired private RedisTemplateString, Integer redisTemplate; Autowired private TicketStockMapper stockMapper; Autowired private RedissonClient redissonClient; /** * 扣减库存带分布式锁 */ Transactional(rollbackFor Exception.class) public boolean deductStock(String trainId, String seatType, int count) { String lockKey lock:stock: trainId : seatType; RLock lock redissonClient.getLock(lockKey); try { // 1. 获取分布式锁防止超卖 boolean locked lock.tryLock(3, 10, TimeUnit.SECONDS); if (!locked) { throw new BusinessException(系统繁忙请稍后重试); } // 2. 查询当前库存 Integer currentStock stockMapper.selectStock(trainId, seatType); if (currentStock null || currentStock count) { throw new BusinessException(库存不足); } // 3. 更新数据库 int rows stockMapper.updateStock(trainId, seatType, currentStock - count, currentStock); if (rows 0) { throw new BusinessException(库存更新失败); } // 4. 提交事务后异步删除缓存 TransactionSynchronizationManager.registerSynchronization( new TransactionSynchronizationAdapter() { Override public void afterCommit() { deleteCacheAsync(trainId, seatType); } } ); return true; } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new BusinessException(系统异常); } finally { if (lock.isHeldByCurrentThread()) { lock.unlock(); } } } /** * 异步删除缓存带重试机制 */ private void deleteCacheAsync(String trainId, String seatType) { String cacheKey buildCacheKey(trainId, seatType); CompletableFuture.runAsync(() - { // 重试机制 for (int i 0; i 3; i) { try { Boolean deleted redisTemplate.delete(cacheKey); if (Boolean.TRUE.equals(deleted)) { log.info(缓存删除成功: {}, cacheKey); break; } } catch (Exception e) { log.error(第{}次删除缓存失败: {}, i 1, cacheKey, e); if (i 2) { try { Thread.sleep(100 * (i 1)); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); } } } } }); } /** * 查询库存带防穿透和击穿保护 */ public Integer getStock(String trainId, String seatType) { String cacheKey buildCacheKey(trainId, seatType); // 1. 尝试从缓存获取 Integer stock redisTemplate.opsForValue().get(cacheKey); if (stock ! null) { // 防缓存穿透空值标记 if (stock -1) { return 0; } return stock; } // 2. 获取分布式锁防止缓存击穿 String lockKey lock:query: cacheKey; RLock lock redissonClient.getLock(lockKey); try { // 只允许一个线程查询数据库 boolean locked lock.tryLock(1, 5, TimeUnit.SECONDS); if (!locked) { // 等待其他线程加载 Thread.sleep(50); return getStock(trainId, seatType); } // 3. 双重检查Double Check stock redisTemplate.opsForValue().get(cacheKey); if (stock ! null) { return stock; } // 4. 查询数据库 stock stockMapper.selectStock(trainId, seatType); // 5. 写入缓存 if (stock null) { // 防缓存穿透缓存空值短暂过期 redisTemplate.opsForValue().set(cacheKey, -1, 60, TimeUnit.SECONDS); return 0; } else { // 设置随机过期时间防止雪崩 int expireTime 300 new Random().nextInt(60); redisTemplate.opsForValue().set(cacheKey, stock, expireTime, TimeUnit.SECONDS); } return stock; } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new BusinessException(查询异常); } finally { if (lock.isHeldByCurrentThread()) { lock.unlock(); } } } private String buildCacheKey(String trainId, String seatType) { return String.format(stock:%s:%s, trainId, seatType); } }3.1.3 一致性分析text时间线分析 1. 写请求更新数据库10ms 2. 写请求删除缓存1ms 3. 读请求查询缓存未命中 4. 读请求查询数据库10ms 5. 读请求写入缓存1ms 不一致时间窗口 ≈ 22ms实际业务可接受3.2 方案6Binlog异步更新缓存高级方案3.2.1 架构设计text┌─────────┐ ┌─────────┐ ┌───────────┐ ┌─────────┐ │ 应用服务 │───▶│ MySQL │───▶│ Canal │───▶│ MQ │ └─────────┘ └─────────┘ └───────────┘ └─────────┘ │ ▼ ┌─────────┐ │ 消费者 │───▶ Redis └─────────┘3.2.2 实现方案javaComponent Slf4j public class BinlogSyncConsumer { Autowired private RedisTemplateString, Integer redisTemplate; Autowired private RedissonClient redissonClient; /** * 监听库存变更消息 */ KafkaListener(topics stock.change) public void consumeStockChange(StockChangeMessage message) { String cacheKey buildCacheKey(message.getTrainId(), message.getSeatType()); // 使用分布式锁保证更新顺序 String lockKey lock:binlog: cacheKey; RLock lock redissonClient.getLock(lockKey); try { // 1. 获取锁防止并发更新 boolean locked lock.tryLock(3, 30, TimeUnit.SECONDS); if (!locked) { log.warn(获取锁失败消息将重新入队: {}, message); throw new RuntimeException(获取锁失败); } // 2. 检查数据版本防止旧数据覆盖新数据 StockCacheMeta meta getCacheMeta(cacheKey); if (meta ! null meta.getVersion() message.getVersion()) { log.info(跳过旧版本数据: {}, message); return; } // 3. 更新缓存 redisTemplate.opsForValue().set(cacheKey, message.getStock()); // 4. 更新元数据 updateCacheMeta(cacheKey, message.getVersion(), System.currentTimeMillis()); log.info(缓存更新成功: {}, message); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException(处理中断); } finally { if (lock.isHeldByCurrentThread()) { lock.unlock(); } } } /** * 版本号冲突解决策略 */ private void handleVersionConflict(String cacheKey, StockChangeMessage message) { // 方案1查询最新数据库数据 Integer latestStock queryLatestFromDB(message); // 方案2使用时间戳判断 long cacheTimestamp getCacheTimestamp(cacheKey); if (message.getTimestamp() cacheTimestamp) { updateCacheWithRetry(cacheKey, message.getStock()); } } Data private static class StockCacheMeta { private Integer version; private Long timestamp; } }3.2.3 消息顺序保证javaComponent public class BinlogMessageSequencer { /** * 保证相同key的消息顺序消费 */ Bean public KafkaMessageListenerContainerString, StockChangeMessage kafkaMessageListenerContainer() { ContainerProperties containerProps new ContainerProperties(stock.change); // 使用ConcurrentMessageListenerContainer实现并发消费 ConcurrentMessageListenerContainerString, StockChangeMessage container new ConcurrentMessageListenerContainer(consumerFactory(), containerProps); // 按key分区保证同一列车座位的消息顺序消费 container.setConcurrency(3); container.getContainerProperties().setMessageListener(new MessageListenerString, StockChangeMessage() { Override public void onMessage(ConsumerRecordString, StockChangeMessage record) { String key record.key(); // trainId:seatType processMessage(key, record.value()); } }); return container; } private void processMessage(String key, StockChangeMessage message) { // 使用本地队列保证同一key的顺序处理 MessageQueue queue getMessageQueue(key); queue.offer(message); // 单线程处理同一key的消息 processQueue(queue); } }四、12306实际解决方案4.1 混合方案设计12306采用先写数据库再删缓存为主Binlog异步补偿为辅的混合方案。javaService public class TicketStockServiceImpl implements TicketStockService { /** * 主方案先写DB再删缓存 */ Override Transactional public boolean deductStock(String trainId, String seatType, int count) { // 1. 数据库扣减带乐观锁 boolean success deductFromDatabase(trainId, seatType, count); if (success) { // 2. 同步删除缓存 deleteCacheSync(trainId, seatType); // 3. 发送Binlog消息用于补偿和监控 sendBinlogMessage(trainId, seatType); } return success; } /** * 数据库扣减乐观锁实现 */ private boolean deductFromDatabase(String trainId, String seatType, int count) { int retryCount 0; while (retryCount 3) { // 查询当前库存和版本号 TicketStock stock stockMapper.selectForUpdate(trainId, seatType); if (stock.getAvailableSeats() count) { return false; } // 乐观锁更新 int rows stockMapper.updateWithOptimisticLock( trainId, seatType, stock.getAvailableSeats() - count, stock.getVersion() ); if (rows 0) { return true; } retryCount; // 指数退避 sleep((long) Math.pow(2, retryCount) * 10); } return false; } /** * 同步删除缓存带重试 */ private void deleteCacheSync(String trainId, String seatType) { String cacheKey buildCacheKey(trainId, seatType); try { // 立即删除 redisTemplate.delete(cacheKey); // 延迟双删处理极端情况 CompletableFuture.delayedExecutor(100, TimeUnit.MILLISECONDS) .execute(() - redisTemplate.delete(cacheKey)); } catch (Exception e) { log.error(缓存删除失败将记录补偿任务, e); recordCompensationTask(cacheKey); } } }4.2 补偿机制javaComponent Slf4j public class CacheCompensationService { Autowired private RedisTemplateString, Integer redisTemplate; Autowired private TicketStockMapper stockMapper; /** * 定时补偿任务 */ Scheduled(fixedDelay 60000) // 每分钟执行一次 public void executeCompensation() { ListCompensationTask tasks getPendingTasks(); for (CompensationTask task : tasks) { try { // 1. 查询数据库最新数据 Integer dbStock stockMapper.selectStock( task.getTrainId(), task.getSeatType()); // 2. 查询缓存当前数据 String cacheKey task.getCacheKey(); Integer cacheStock redisTemplate.opsForValue().get(cacheKey); // 3. 比较并修复 if (!Objects.equals(dbStock, cacheStock)) { if (dbStock null) { redisTemplate.delete(cacheKey); } else { redisTemplate.opsForValue().set(cacheKey, dbStock); } log.info(缓存修复完成: {}, task); } // 4. 标记任务完成 markTaskCompleted(task); } catch (Exception e) { log.error(补偿任务执行失败: {}, task, e); task.setRetryCount(task.getRetryCount() 1); if (task.getRetryCount() 3) { markTaskFailed(task); } } } } }4.3 监控告警体系yaml# 监控指标配置 metrics: cache: hit_rate: redis.command.statistics.hits / redis.command.statistics.total miss_rate: redis.command.statistics.misses / redis.command.statistics.total inconsistency_rate: count(compensation_tasks) / count(update_operations) alert: rules: - name: 缓存不一致率过高 expr: inconsistency_rate 0.01 for: 5m labels: severity: warning - name: 缓存命中率过低 expr: hit_rate 0.8 for: 10m labels: severity: critical五、最佳实践总结5.1 方案选择建议业务场景推荐方案说明普通电商库存先写DB再删缓存一致性要求中等实现简单金融账户余额缓存双删 事务强一致性要求可接受一定复杂度大型分布式系统Binlog异步更新多数据源最终一致性秒杀场景先写DB再删缓存 本地缓存极高并发需要多层保护5.2 关键注意事项分布式锁使用选择Redisson或Curator成熟的解决方案设置合理的锁超时时间避免锁粒度过大影响性能重试机制实现指数退避算法设置最大重试次数记录重试日志便于排查监控告警监控缓存命中率监控不一致率设置合理的告警阈值降级策略缓存不可用时降级到数据库设置本地缓存作为二级缓存实现熔断机制5.3 性能优化建议缓存Key设计java// 反例过于复杂 String key stock: trainId : seatType : date; // 正例简洁高效 String key String.format(s:%s:%s, trainId, seatType);批量操作java// 批量删除缓存 public void batchDeleteCache(ListString keys) { redisTemplate.delete(keys); }连接池优化yamlredis: lettuce: pool: max-active: 200 max-idle: 50 min-idle: 10 max-wait: 1000ms5.4 故障恢复预案javaComponent Slf4j public class CacheDisasterRecovery { /** * 缓存全量重建 */ public void rebuildAllCache() { // 1. 设置维护标志 setMaintenanceFlag(true); try { // 2. 分批次重建 int batchSize 1000; int offset 0; while (true) { ListTicketStock stocks stockMapper.selectBatch(offset, batchSize); if (stocks.isEmpty()) { break; } // 3. 批量写入缓存 MapString, Integer cacheData new HashMap(); for (TicketStock stock : stocks) { String key buildCacheKey(stock.getTrainId(), stock.getSeatType()); cacheData.put(key, stock.getAvailableSeats()); } redisTemplate.opsForValue().multiSet(cacheData); offset batchSize; log.info(已重建 {} 条缓存记录, offset); } } finally { // 4. 清除维护标志 setMaintenanceFlag(false); } } }六、总结在12306这样的高并发系统中缓存与数据库的一致性保障是一个复杂的系统工程。通过实践总结没有银弹方案需要根据具体业务场景选择合适策略混合方案更优主方案补偿机制监控告警的组合监控是保障完善的监控体系能及时发现和解决问题容错是关键设计时要考虑各种异常情况的处理推荐大多数企业采用先写数据库再删除缓存作为主方案配合Binlog异步更新作为补偿机制建立完善的监控告警体系这样可以在保证性能的同时获得较好的数据一致性保障。本回答由 AI 生成内容仅供参考请仔细甄别。
版权声明:本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

微网站 微官网的区别建一个商城网站需要多少钱

掌握质谱分析:OpenMS完整使用指南与实战技巧 【免费下载链接】OpenMS The codebase of the OpenMS project 项目地址: https://gitcode.com/gh_mirrors/op/OpenMS OpenMS作为一款强大的开源质谱数据分析工具,为科研人员提供了从数据处理到结果可视…

张小明 2025/12/27 22:33:14 网站建设

企业型网站制作网站关键词库如何做

引言 物联网(IoT)技术的快速发展,让各类终端设备的数据采集与云端交互成为常态。STM32F103 作为意法半导体推出的经典 ARM Cortex-M3 内核微控制器,凭借高性价比、稳定的性能和丰富的外设,成为物联网终端开发的首选芯…

张小明 2025/12/24 9:02:31 网站建设

双流建设局网站wordpress网站在哪里修改

基于三有源桥的模型预测控制仿真,可以独立控制输出侧两个端口的电压或者电流,动态响应快,也可以扩展至四有源桥电路。最近在研究基于三有源桥的模型预测控制(MPC)仿真,发现这东西挺有意思的。三有源桥电路结…

张小明 2026/1/8 20:34:47 网站建设

系网站建设总结报告广州网站建设设计哪家好

目录具体实现截图项目介绍论文大纲核心代码部分展示项目运行指导结论源码获取详细视频演示 :文章底部获取博主联系方式!同行可合作具体实现截图 本系统(程序源码数据库调试部署讲解)同时还支持java、ThinkPHP、Node.js、Spring B…

张小明 2025/12/24 8:59:21 网站建设

高端h5网站建设 上海梅州网站建设wlwl

Data Formulator终极指南:5大技巧让时间序列分析从未如此简单 【免费下载链接】data-formulator 🪄 Create rich visualizations with AI 项目地址: https://gitcode.com/GitHub_Trending/da/data-formulator 还在为复杂的时间序列数据发愁吗&am…

张小明 2026/1/4 19:11:24 网站建设