12306项目学习day6

发布时间:2026/7/30 10:03:26
12306项目学习day6 学完 12306 分布式票务项目后我对高并发系统设计的整体理解前段时间我系统学习了一个 12306 分布式票务项目。刚开始看这个项目时我更多关注的是“用了哪些技术”Redis、RocketMQ、ShardingSphere、Canal、Redisson、Sentinel、Spring Boot Starter 等。但真正把源码、业务链路和面试问题串起来之后我发现这个项目最值得学习的地方并不是某一个单独技术点而是它围绕“高并发购票”这个核心场景把缓存、消息队列、分库分表、幂等、分布式锁、异常处理、敏感数据保护等能力组合到了一起。这篇文章是我学完整个项目后的总结不再单独展开某一个知识点而是从整体角度复盘这个项目解决了什么问题、核心链路怎么设计、每个技术点在里面承担什么角色以及我学完之后对高并发系统设计有哪些新的理解。一、我对这个项目整体定位的理解这个项目模拟的是 12306 购票系统核心业务包括用户注册、登录、乘车人管理车次查询、余票查询用户购票、选座、生成订单支付回调、订单状态变更超时未支付自动关单订单查询、冷热数据区分余票缓存同步、库存回滚从服务划分来看项目不是一个单体应用而是拆成了多个微服务user-service 用户服务 ticket-service 车票服务 order-service 订单服务 pay-service 支付服务 aggregation-service 聚合服务 gateway-service 网关服务我的理解是这种拆分不是为了“微服务而微服务”而是围绕业务边界拆的用户服务负责用户身份和乘车人信息。车票服务负责车次、座位、余票和购票核心逻辑。订单服务负责订单生命周期。支付服务负责支付单、支付回调、退款。聚合服务负责把多个服务的数据组装给前端。网关服务负责统一入口。这让我理解到微服务拆分的关键不是服务数量而是边界是否清晰。比如购票下单一定会涉及车票和订单但车票服务不应该直接管理订单表订单服务也不应该直接操作座位库存所以两者之间通过远程调用和消息机制协作。二、整个购票链路是我认为最核心的主线学习这个项目时我觉得最应该先抓住的是购票主链路。大致流程可以理解为用户查询车次 ↓ 选择车次和乘车人 ↓ 提交购票请求 ↓ 令牌桶校验余票 ↓ 座位选择与锁定 ↓ 创建订单 ↓ 发送延迟关单消息 ↓ 用户支付 ↓ 支付回调更新订单状态如果用户没有在规定时间内支付RocketMQ 延迟消息到达 ↓ 消费端查询订单状态 ↓ 如果仍是待支付关闭订单 ↓ 释放座位 ↓ 回滚 Redis 余票和令牌桶这个链路把很多技术点串在了一起环节项目里的核心技术余票校验Redis Hash Lua 令牌桶并发扣减Lua 原子操作座位锁定数据库座位状态 缓存预扣订单生成分库分表 雪花 ID超时关单RocketMQ 延迟消息重复消费幂等框架并发状态修改Redisson 分布式锁缓存一致性Canal RocketMQ 策略模式学完后我最大的感受是高并发系统不是靠某一个技术解决问题而是靠一整套链路协作。Redis 解决的是“别让所有请求都打数据库”。Lua 解决的是“并发下校验和扣减必须原子”。RocketMQ 解决的是“异步解耦和延迟补偿”。幂等解决的是“消息重复、请求重复不能重复执行业务”。分布式锁解决的是“多个节点同时改同一笔订单状态时不能乱”。Canal 解决的是“数据库变化后缓存最终要同步”。这些技术点单独看都不复杂但放到购票链路里才体现出价值。三、我对“防超卖”的理解发生了变化一开始我理解的防超卖比较简单库存扣减时加锁或者数据库 update 加条件。但学习这个项目后我发现真正的高并发防超卖不能只靠数据库。原因很简单如果所有购票请求都直接打数据库数据库很快就会扛不住。所以项目在数据库之前做了多层削峰和预扣请求进入 ↓ Redis 令牌桶判断余票是否足够 ↓ Lua 原子扣减令牌 ↓ 进入座位锁定和订单创建 ↓ 数据库最终落库项目里的令牌桶不是传统意义上“按固定速率生成令牌”的限流桶而更像是“按车次、区间、座位类型维护的余票令牌容器”。它的 Redis 结构大致是Key: ticket_availability_token_bucket:{trainId} Field: {startStation}_{endStation}_{seatType} Value: 当前区间当前座位类型剩余令牌数购票时Lua 脚本会先判断所有相关区间的令牌是否足够。如果足够再一次性扣减。这样可以保证两个动作是原子的判断余票是否足够 扣减余票令牌 ↓ 一个 Lua 脚本内完成不会被其他请求插入这让我理解到防超卖不是简单“加锁”而是要根据并发量、数据库压力、业务一致性要求综合设计。项目里的防超卖大致可以分三层第一层是 Redis 预扣减挡住大部分无效请求。第二层是 Lua 原子操作保证缓存层不会并发扣乱。第三层是数据库座位状态作为最终兜底。如果订单超时未支付还会通过延迟消息释放座位并回滚令牌桶。这说明防超卖不只包含“扣减”还必须包含“回滚”。只会扣不会回滚系统最终也会不一致。四、我对 RocketMQ 的理解不再停留在“发消息”这个项目里 RocketMQ 不只是用来异步发送通知而是参与了多个关键业务流程。我印象最深的是延迟关单。创建订单后项目会发送一条延迟消息。消息到期后消费者会检查订单状态如果订单仍然是待支付就关闭订单、释放座位、回滚余票。如果订单已经支付就直接跳过。这让我理解到延迟消息本质上是在做一种“未来某个时间点的业务补偿”。如果不用 MQ也可以用定时任务扫表比如每分钟扫描一次超时订单。但定时任务有几个问题扫表成本高订单量大时压力明显。时间精度不稳定可能延迟较大。多实例部署时还要解决重复扫描和并发处理。延迟消息的优势是每个订单创建时就绑定一条未来要执行的检查任务到点后消费即可。不过学习源码时我也注意到一个细节RocketMQ 的延迟级别不是任意时间而是固定等级。项目代码里用了delayLevel(14)按 RocketMQ 默认配置对应的是 10 分钟不是 30 分钟。如果要 30 分钟默认应该用 level 16。这个点让我意识到学习项目不能只看注释还要结合框架本身的机制核对。RocketMQ 在项目里还有一个重要价值是解耦。比如支付服务收到支付回调后不应该直接强耦合订单服务内部逻辑而是通过消息通知订单服务处理支付结果。这样支付和订单之间的耦合会更低后续扩展也更方便。五、幂等框架是我觉得最值得学习的工程化设计之一高并发系统里请求重复和消息重复是很常见的。比如用户连续点击提交按钮。网络抖动导致前端重试。MQ 消费失败后重新投递。服务超时后调用方再次发起请求。如果没有幂等保护同一个业务可能被执行多次。对购票系统来说这可能导致重复下单、重复释放座位、重复回滚库存等问题。项目里封装了一个通用幂等框架核心是Idempotent 注解 ↓ AOP 拦截方法 ↓ 根据注解类型选择处理器 ↓ 生成唯一幂等 key ↓ Redis 原子写入 key ↓ key 已存在则说明重复请求它支持三类幂等方式类型适用场景我的理解PARAMHTTP 参数幂等根据请求参数生成唯一标识TOKEN表单防重复提交先申请 token再提交时消费 tokenSPEL灵活表达式MQ 或复杂对象场景下用 SpEL 取字段我觉得这个设计最有价值的地方是它不是在业务代码里到处写 Redis 判断而是通过注解和 AOP 做成了框架能力。业务方法只需要这样标记Idempotent(uniqueKeyPrefixindex12306-ticket:delay_close_order:,key#message.getKeys() _ #message.hashCode(),typeIdempotentTypeEnum.SPEL,sceneIdempotentSceneEnum.MQ,keyTimeout7200L)剩下的事情交给框架。这让我理解到工程化能力的一个重要体现就是把横切逻辑从业务代码中抽出来。幂等、日志、异常、鉴权、限流都属于这类横切逻辑。六、策略模式让我理解了“少写 if-else”的真正意义以前我理解策略模式时更多停留在设计模式书上的例子比如不同支付方式、不同折扣策略。这个项目里策略模式的落地更工程化。项目封装了两个核心类AbstractExecuteStrategy AbstractStrategyChoose所有策略处理器实现AbstractExecuteStrategy并通过mark()返回自己的标识。项目启动时AbstractStrategyChoose会从 Spring 容器中拿到所有策略 Bean放进一个 MapMapmark, 策略实例运行时根据 mark 找到对应策略执行。我觉得这个设计最典型的应用是 Canal binlog 同步缓存。Canal 消费者拿到 binlog 消息后并不直接写if(table.equals(t_seat)){// 更新余票缓存}elseif(table.equals(t_order)){// 订单关闭回滚}而是abstractStrategyChoose.chooseAndExecute(message.getTable(),message,predicateFlag);然后不同表对应不同处理器TicketAvailabilityCacheUpdateHandler处理座位表变化。OrderCloseCacheAndTokenUpdateHandler处理订单关闭后的缓存和令牌桶回滚。这让我理解到策略模式不是为了“看起来高级”而是为了把变化点隔离出来。如果后续新增一张表的 binlog 同步逻辑不需要改消费者主流程只需要新增一个策略处理器即可。这就是开闭原则在项目里的真实体现。七、Canal MQ 让我理解了缓存一致性的另一种方案在项目里Redis 缓存不是简单地在业务代码里手动更新。项目使用 Canal 监听 MySQL binlog再通过 RocketMQ 把变更事件发送出来由消费者更新 Redis。完整链路可以理解为业务更新 MySQL ↓ MySQL 写 binlog ↓ Canal 监听 binlog ↓ Canal 投递 MQ 消息 ↓ 项目消费者消费消息 ↓ 根据表名路由到不同策略处理器 ↓ 更新 Redis 缓存这个设计的好处是业务代码不用强依赖 Redis 更新逻辑。数据库发生变化后通过 binlog 驱动缓存同步。我觉得它适合几个场景多个服务都可能修改数据库但缓存更新逻辑希望统一。不想把缓存同步代码散落在业务逻辑里。接受短暂延迟追求最终一致性。但这个方案也不是没有代价。它是异步的所以一定存在延迟。也就是说数据库更新成功后Redis 可能短时间内还是旧数据。因此这种方案更适合最终一致性场景不适合强一致读写。项目里通过策略模式处理不同表的 binlog我觉得这是 Canal 这条链路里比较好的设计消费者只负责接收事件具体怎么更新缓存交给策略处理器。八、分库分表让我理解了“分片键比框架更重要”这个项目里订单服务和用户服务都使用了 ShardingSphere。订单服务是 2 库 32 表订单表按user_id order_sn复合分片。用户服务也是 2 库 32 表但不同表分片键不同t_user按usernamet_passenger按usernamet_user_mail按mailt_user_phone按phone学习这部分后我最大的收获是分库分表最重要的不是配置语法而是分片键选择。如果用户订单列表查询最常带的是user_id那订单表就应该优先考虑user_id。如果支付回调只拿得到order_sn那算法也需要兼容order_sn。如果用户可以通过手机号登录就需要让手机号查询能精准路由所以项目单独设计了t_user_phone。这让我意识到分片键一定要从业务查询路径出发而不是从数据库角度拍脑袋决定。分库分表也有代价跨库聚合查询变复杂。分页、排序、统计会更麻烦。分片键缺失时可能导致全路由。后续扩容和数据迁移也需要考虑。所以分库分表不是越早越好也不是表越多越好。只有当数据规模、写入压力、单库瓶颈真的出现时才值得引入。九、订单冷热分离让我理解了“逻辑分离”和“物理分离”的区别项目中订单数据并没有真正迁移到冷热两套物理表而是通过订单状态做逻辑冷热分离。订单状态大致包括0 待支付 10 已支付 11 部分退款 12 全部退款 20 已完成 30 已关闭我的理解是待支付、已支付、退款中属于用户近期可能操作的数据是热数据。已完成、已关闭属于历史数据是冷数据。查询订单时前端传statusType后端根据它转换成不同的订单状态集合。比如statusType 0 - 待支付 statusType 1 - 已支付/退款中 statusType 2 - 已完成这种方式的优点是简单不需要额外的归档任务也不需要维护冷热两套表。但缺点也明显数据还是在同一组分片表里随着历史订单越来越多主表压力仍然会上升。所以我认为这个项目里的冷热分离更准确地说是“逻辑冷热分离”。如果未来数据量继续增长可以演进成物理冷热分离主订单表只保留近期订单。历史订单迁移到t_order_history。复杂历史查询走 ES 或归档库。这让我理解到很多项目里的设计是分阶段演进的。不是一开始就上最复杂方案而是先用简单方案满足当前规模再给未来演进留空间。十、全局异常和统一返回让我理解了框架封装的价值项目里封装了统一返回对象、全局异常处理器、自定义异常体系。以前我觉得这些东西比较基础但看完整个项目后我发现它们非常重要。如果没有统一异常处理业务代码里会到处出现try{// business}catch(Exceptione){returnResult.fail(xxx);}这样会导致代码重复。返回格式不统一。错误码难管理。未知异常可能暴露内部细节。项目里通过RestControllerAdvice统一拦截参数校验异常业务异常远程调用异常未知系统异常业务层只需要抛出ServiceException、ClientException、RemoteException框架层统一转换成Result。我对这块最大的感受是成熟项目里业务代码应该尽量只表达业务逻辑通用能力应该沉到框架层。这也是项目里多个 starter 的意义common convention web database cache distributedid designpattern idempotent log bizs user这些 starter 不是为了拆目录而是为了把通用能力标准化。十一、敏感数据保护让我理解了“存储安全”和“展示安全”项目里对敏感数据做了两层保护第一层是数据库加密。用户服务和订单服务的 ShardingSphere 配置里对身份证、手机号、邮箱、地址等字段配置了 AES 加密。这样即使数据库泄露也不至于直接暴露明文。第二层是接口返回脱敏。项目通过 Jackson 自定义序列化器对手机号和身份证号做脱敏手机号类似138****5678身份证保留前 4 位和后 4 位这让我理解到加密和脱敏不是一回事。加密解决的是落库安全。脱敏解决的是展示安全。比如数据库加密后业务代码查出来的对象可能已经是明文。如果接口直接返回前端仍然能拿到完整身份证号。所以还需要在响应 DTO 上做脱敏。这块我也看到一些可以改进的地方AES 密钥不应该直接写在 YAML 里生产环境应放到 KMS、配置中心或环境变量。用户服务和订单服务里有重复的脱敏序列化器可以抽到公共 starter。邮箱、地址等字段如果会返回给前端也应该有统一脱敏规则。学习这部分后我对“安全”有了更具体的理解安全不是只靠一个点而是要覆盖存储、传输、展示、日志等多个环节。十二、我对这个项目不足之处的理解学完之后我觉得这个项目作为学习项目已经覆盖了很多核心问题但如果从生产级系统角度看还有一些可以继续优化的地方。1. 部分配置更适合外置比如 ShardingSphere Encrypt 的 AES 密钥直接写在配置文件中生产环境会有风险。更合理的方式是放到配置中心、密钥管理系统或环境变量中。2. 部分能力可以进一步统一封装例如手机号、身份证脱敏序列化器在用户服务和订单服务里都有实现。这个可以抽成公共 starter避免重复。3. 逻辑冷热分离还不是物理冷热分离目前订单冷热分离依赖status字段过滤本质上还在同一组表里。数据量继续增长后可以考虑历史订单归档、ES 查询、冷热库拆分。4. Canal 同步存在最终一致性延迟Canal MQ 更新 Redis 是异步链路数据库更新后 Redis 会有短暂延迟。对于强一致场景还需要业务同步更新或读数据库兜底。5. 分库分表后复杂查询成本会上升分库分表可以解决单库单表压力但也会带来跨库分页、聚合统计、分片键缺失全路由等问题。后续如果要做复杂检索可以借助 ES 或宽表。我觉得能看到这些不足也很重要。学习项目不是只背它“用了什么技术”还要知道它为什么这样设计以及它还有什么边界。十三、如果面试让我讲这个项目我会怎么组织学完之后我觉得面试讲项目不能一上来就堆技术名词而是要围绕业务主线讲。我会这样组织第一步先讲项目背景。这是一个模拟 12306 的分布式票务系统核心场景是高并发购票。项目拆分为用户、车票、订单、支付、聚合、网关等服务。第二步讲核心业务链路。用户提交购票请求后系统先通过 Redis Lua 令牌桶做余票预扣再进行座位锁定和订单创建。订单创建成功后发送 RocketMQ 延迟消息如果用户超时未支付消费端会关闭订单、释放座位并回滚令牌桶。第三步讲高并发保障。项目通过 Redis 预扣减降低数据库压力通过 Lua 保证扣减原子性通过分布式锁控制订单状态并发修改通过幂等框架解决重复请求和 MQ 重复消费。第四步讲数据层设计。用户和订单服务使用 ShardingSphere 分库分表。订单表按user_id order_sn复合分片兼顾用户订单查询和订单号查询。用户服务按username、phone、mail等业务键分片。第五步讲缓存一致性。项目使用 Canal 监听 MySQL binlog再通过 RocketMQ 发送变更事件消费者根据表名通过策略模式路由到不同处理器最终更新 Redis 缓存。第六步讲工程化。项目封装了多个 starter比如统一异常处理、幂等、日志、缓存、分布式 ID、策略模式等把通用能力从业务代码中抽离出来。最后主动补充不足。这个项目里订单冷热分离是逻辑分离还没有做物理归档Canal 同步 Redis 是最终一致性AES 密钥直接写在配置文件中生产环境可以进一步优化。我觉得这样的表达会比单纯说“我用了 Redis、RocketMQ、ShardingSphere”更清楚因为它能体现我知道每个技术点解决了什么问题。十四、学完这个项目后我最大的收获如果只用一句话总结我的收获高并发系统设计不是堆技术而是围绕核心业务链路把每个技术放在合适的位置解决具体问题。这个项目让我对很多问题有了更具体的理解。比如Redis 不是简单做缓存也可以做预扣减、令牌桶、幂等键。Lua 的价值不只是脚本而是把多步 Redis 操作变成原子操作。MQ 不只是异步通知也可以做延迟关单、削峰、最终一致性补偿。幂等不是可有可无而是分布式系统必须考虑的问题。分库分表的关键不是配置而是分片键是否贴合业务查询。策略模式不是为了炫设计模式而是为了隔离变化点。全局异常处理、统一返回、脱敏这些基础能力决定了项目是否规范。学这个项目的过程也让我意识到看源码不能只看“这个类干了什么”而要把它放回业务链路里思考。比如看到令牌桶就要问它在购票链路的哪个位置为什么不能直接扣数据库失败后怎么回滚看到延迟消息就要问消息重复怎么办订单已支付怎么办释放座位失败怎么办看到分库分表就要问按什么字段分哪些查询能精准路由哪些查询会全路由只有带着这些问题看源码才能真正把项目学透。十五、后续我还想继续补的能力虽然这个项目已经覆盖了很多内容但我觉得后续还可以继续补几块能力1. 压测和性能分析项目里有很多高并发设计但如果能配合 JMeter、Arthas、Prometheus 这类工具做压测和指标观察会更完整。我希望后续能补充购票接口 QPS 压测Redis 和 MySQL 压力对比Lua 脚本执行耗时RocketMQ 消费堆积观察分库分表前后查询性能对比2. 更深入的事务一致性项目中用了 MQ、Canal、幂等、补偿等方式保证最终一致性但如果继续深入可以学习本地消息表事务消息TCCSaga最大努力通知这样对分布式事务的理解会更完整。3. 更完整的生产级安全设计敏感数据保护可以继续扩展密钥托管日志脱敏接口权限控制数据访问审计防止越权查询4. 服务治理和可观测性项目里可以继续补充Sentinel 限流熔断SkyWalking 链路追踪Prometheus Grafana 指标监控ELK 日志检索这些能力能让项目更接近真实生产环境。十六、我给自己整理的源码复习索引学完整个项目后我觉得后面复习不能再从头到尾翻所有代码而是应该按能力模块复习。下面是我给自己整理的源码索引。1. 高并发购票主链路services/ticket-service/src/main/java/org/opengoofy/index12306/biz/ticketservice/service/handler/ticket/tokenbucket/TicketAvailabilityTokenBucket.java services/ticket-service/src/main/resources/lua/ticket_availability_token_bucket.lua services/ticket-service/src/main/resources/lua/ticket_availability_rollback_token_bucket.lua services/ticket-service/src/main/java/org/opengoofy/index12306/biz/ticketservice/mq/consumer/DelayCloseOrderConsumer.java services/order-service/src/main/java/org/opengoofy/index12306/biz/orderservice/service/impl/OrderServiceImpl.java复习重点购票请求为什么先经过令牌桶Redis Hash 如何组织区间余票Lua 如何保证判断和扣减原子性订单关闭后座位、缓存、令牌桶如何回滚。2. 幂等框架frameworks/idempotent/src/main/java/org/opengoofy/index12306/framework/starter/idempotent/annotation/Idempotent.java frameworks/idempotent/src/main/java/org/opengoofy/index12306/framework/starter/idempotent/aop/IdempotentAspect.java frameworks/idempotent/src/main/java/org/opengoofy/index12306/framework/starter/idempotent/core/IdempotentExecuteHandler.java frameworks/idempotent/src/main/resources/lua/set_if_absent_and_get.lua复习重点Idempotent注解标记了哪些信息AOP 在什么时候拦截业务方法PARAM、TOKEN、SPEL 三种模式分别解决什么问题Redis 幂等 key 如何生成。3. 策略模式与 Canal 路由frameworks/designpattern/src/main/java/org/opengoofy/index12306/framework/starter/designpattern/strategy/AbstractExecuteStrategy.java frameworks/designpattern/src/main/java/org/opengoofy/index12306/framework/starter/designpattern/strategy/AbstractStrategyChoose.java services/ticket-service/src/main/java/org/opengoofy/index12306/biz/ticketservice/mq/consumer/CanalCommonSyncBinlogConsumer.java services/ticket-service/src/main/java/org/opengoofy/index12306/biz/ticketservice/canal/TicketAvailabilityCacheUpdateHandler.java services/ticket-service/src/main/java/org/opengoofy/index12306/biz/ticketservice/canal/OrderCloseCacheAndTokenUpdateHandler.java复习重点策略处理器如何通过mark()暴露自己的标识Spring 容器启动后如何收集所有策略 BeanCanal 消费者为什么不用大量if-else。4. 分库分表与工程化基础能力services/order-service/src/main/resources/shardingsphere-config.yaml services/user-service/src/main/resources/shardingsphere-config.yaml services/order-service/src/main/java/org/opengoofy/index12306/biz/orderservice/dao/algorithm/OrderCommonDataBaseComplexAlgorithm.java services/order-service/src/main/java/org/opengoofy/index12306/biz/orderservice/dao/algorithm/OrderCommonTableComplexAlgorithm.java frameworks/web/src/main/java/org/opengoofy/index12306/framework/starter/web/GlobalExceptionHandler.java frameworks/web/src/main/java/org/opengoofy/index12306/framework/starter/web/Results.java services/user-service/src/main/java/org/opengoofy/index12306/biz/userservice/serialize/PhoneDesensitizationSerializer.java services/user-service/src/main/java/org/opengoofy/index12306/biz/userservice/serialize/IdCardDesensitizationSerializer.java复习重点订单表为什么用user_id order_sn复合分片用户服务为什么按多个业务键分片全局异常如何统一错误出口存储加密和接口脱敏分别解决什么问题。十七、我给自己整理的复习路线如果后面重新复习这个项目我不会再按模块从头看而是按这条路线复习第一轮先看业务链路 购票请求 - 令牌桶 - 座位锁定 - 创建订单 - 延迟关单 - 回滚库存 第二轮再看高并发保障 Redis Lua - 幂等 - 分布式锁 - MQ 延迟补偿 第三轮看数据层设计 分库分表 - 冷热分离 - 雪花 ID - 敏感字段加密 第四轮看工程化封装 全局异常 - 统一返回 - 策略模式 - starter 抽象 第五轮看一致性和补偿 Canal - RocketMQ - Redis 同步 - 重复消费 - 最终一致性这条路线的好处是它不是按文件夹学习而是按系统问题学习。这样更容易把源码和业务场景对应起来。十八、我最后形成的一张系统设计脑图最后我把这个项目按系统能力整理成一张文字版脑图12306 分布式票务系统 | |-- 业务主线 | |-- 用户注册登录 | |-- 车次和余票查询 | |-- 购票下单 | |-- 支付回调 | |-- 订单关闭 | -- 订单查询 | |-- 高并发能力 | |-- Redis 余票缓存 | |-- Redis Hash 令牌桶 | |-- Lua 原子扣减 | |-- Redisson 分布式锁 | -- MQ 削峰和延迟补偿 | |-- 一致性能力 | |-- MQ 延迟关单 | |-- 幂等框架防重复消费 | |-- Canal 监听 binlog | |-- Redis 缓存最终一致性 | -- 订单状态校验兜底 | |-- 数据层能力 | |-- ShardingSphere 分库分表 | |-- 用户表多业务键分片 | |-- 订单表复合分片 | |-- 雪花算法生成分布式 ID | -- 逻辑冷热分离 | |-- 工程化能力 | |-- starter 封装 | |-- 全局异常处理 | |-- 统一返回结果 | |-- 策略模式路由 | |-- 责任链模式扩展 | -- 日志与操作记录 | -- 安全能力 |-- ShardingSphere Encrypt 存储加密 |-- Jackson 序列化脱敏 |-- 手机号脱敏 |-- 身份证号脱敏 -- 统一异常避免内部信息泄露这张图也是我后面复习这个项目时最重要的总览。只要这张图能讲清楚说明我对项目的整体理解就不会散。结尾这次学习 12306 项目对我最大的帮助不是记住某个框架的 API而是让我开始从系统角度思考问题。以前看到一个技术点我可能只会问“它怎么用”。现在我更会问它解决了哪个业务问题它在链路的哪个位置它失败后有没有兜底它和其他组件怎么配合它有什么边界和不足我觉得这才是学习项目最重要的收获。项目源码只是起点真正有价值的是通过源码把业务、架构、工程化和面试表达串起来。