加入收藏 | 设为首页 | 会员中心 | 我要投稿 云计算网_泰州站长网 (http://www.0523zz.com/)- 视觉智能、AI应用、CDN、行业物联网、智能数字人!
当前位置: 首页 > 综合聚焦 > 创业热点 > 经验 > 正文

换个思路看12306,其核心模型设计思路到底复杂在哪里?

发布时间:2016-02-29 20:43:04 所属栏目:经验 来源:InfoQ
导读:元宵节结束,年就真的过完了。挥别故里,回到打拼的城市,理性思维是否也跟着工作状态一起回归了呢?每一年的春运都是对 12306 的一次大考,抛去盲从和偏见,让我们用工程

我觉得 12306 这样的业务场景,非常适合使用 CQRS 架构;因为首先它是一个查多写少、但是写的业务逻辑非常复杂的系统。所以,非常适合做架构层面的读写分离,即采用 CQRS 架构。而且应该使用数据存储也分离的 CQRS。这样 CQ 两端才可以完全不需要顾及对方的问题,各自优化自己的问题即可。我们可以在 C 端使用 DDD 领域模型的思路,用良好设计的领域模型实现复杂的业务规则和业务逻辑。而 Q 端则使用分布式缓存方案,实现可伸缩的查询能力。

1、订票的实现思路

同时借助像 ENode 这样的框架,我们可以实现 in-memory + Event Sourcing 的架构。Event Sourcing 技术,可以让领域模型的所有状态修改的持久化统一起来,本来要用 ORM 的方式保存聚合根最新状态的,现在只需要简单的通用的方式保存一个事件即可(一次订票只涉及一个车次聚合根的修改,修改只产生一个事件,只需要持久化一个事件(一个 JSON 串)即可,保证了高性能,无须依赖事务,而且通过 ENode 可以解决并发问题)。

我们只要保存了聚合根每次变化的事件(事件的结构怎么设计,本文不做多的介绍了,大家可以思考下),就相当于保存了聚合根的最新状态。而正是由于 Event Sourcing 技术的引入,让我们的模型可以一直存活在内存中,即可以使用 in-memory 技术。不要小看 in-memory 技术,in-memory 技术在某些方面对提高命令的处理性能非常有帮助。

比如就以我们车次聚合根处理出票的逻辑,假设某个车次有大量的命令发送到分布式消息队列,然后有一台机器订阅了这个队列的消息,然后这台机器处理这个车次的订票命令时,由于这个车次聚合根一直在内存,所以就省去了每次要去数据库取出聚合根的步骤,相当于少了一次数据库 IO。

这样的好处是,因为一个车次能够真正出售的票是有限的,因为座位就那么几个,比如就 1000 个座位,估计一般正常情况也就出个 2000 个左右的票吧(具体能出多少张票要取决于区间的相交程度,上面分析过)。也就是说,这个聚合根只会产生 2000 个事件,也就是说只会有 2000 个订票命令的处理是会产生事件,并持久化事件;而其余的大量命令,因为车次在内存计算后发现没有余票了,就不会做任何修改,也不会产生领域事件,这样就可以直接处理下一个订票命令了。这样就可以大大提高处理订票命令的性能。

另外一个问题我觉得还需要提一下,因为用户订票成功后,还需要付款。但用户有可能不去付款或者没有在规定的时间内完成付款。那这种情况下,系统会自动释放该用户之前订购的票。所以基于这样的需求,我们在业务上需要支持业务级别的 2pc。即先预扣库存,也就是先占住这张票一定时间(比如 15 分钟),然后付款成功后再真实给你这张票,系统做真正的库存修改。

通过这样的预扣处理,可以保证不会出现超卖的情况。这个思路其实和传统电商比如淘宝这样的系统类似,我就不多展开了,我之前写的 Conference 案例也是这样的思路,大家有兴趣的可以去看一下我之前录制的视频。

2、查询余票的实现思路

我觉得余票的查询的实现相对简单。虽然对于 12306 来说,查询的请求占了 80%,提交订单的请求只占 20%。但查询由于对数据没有修改,所以我们完全可以使用分布式缓存来实现。我们只需要精心设计好缓存的 key 即可;缓存 key 的多少要看成本,如果所有可能的查询都设计对应的 key,那时间复杂度为 1,查询性能自然高;但代价也大,因为 key 多了。如果想 key 少一点,那查询的复杂度自然要上去一点。所以缓存设计无非就是空间换时间的思路。然后,缓存的更新无非就是:自动失效、定时更新、主动通知 3 种。通过 CQRS 架构,由于 CQ 两端是事件驱动的,当 C 端有任何状态变化,都会产生对应的事件去通知 Q 端,所以我们几乎可以做到 Q 端的准实时更新。

同时由于 CQ 两端的完全解耦,Q 端我们可以设计多种存储,如数据库和缓存(Redis 等);数据库用于线下维护关系型数据,缓存用户实时查询。数据库和缓存的更新速度相互不受影响,因为是并行的。对同一个事件,可以 10 台机器负责更新缓存,100 台机器负责更新数据库。即便数据库的更新很慢,也不会影响缓存的更新进度。这就是 CQRS 架构的好处,CQ 的架构完全不同,且我们随时可以重建一种新的 Q 端存储。不知道大家体会到了没有?

关于缓存 key 的设计,我觉得主要从查询余票时传递的信息来考虑。12306 的关键查询是:出发地、目的地、出发日期三个信息。我觉得有两种 key 的设计思路:

  1. 直接设计了该查询条件的 key,然后快速拿到车次信息,直接返回;这种方式就是要求我们系统已经枚举了所有车次的所有可能出现的票(区间)的缓存 key,相信你一定知道这样的 key 是非常多的。
  2. 不是枚举所有区间,而是把每个车次的每个原子区间(相邻的两个站点所连成的直线)的可用票数作为 key。这样,key 就非常少了,因为车次假如有 10000 个,然后每个车次平均 15 个区间,那也就 15W 个 key 而已。当我们要查询时,只需要把用户输入的出发地和目的地之间的所有原子区间的可用票数都查出来,然后比较出最小可用票数的那个原子区间。则这个原子区间的可用票数就是用户输入的区间的可用票数了。当然,到这里我提到考虑出发日期。我认为出发日期是用来决定具体是哪个车次聚合根的。同一个车次,不同的日期,对应的聚合根实例是不同的,即便是同一天,也可能有多个车次聚合根,因为有些车次一天有几班的,比如上午 9 点发车的一班,下午 3 点发车的一般。所以,我们也只要把日期也作为缓存 key 的一部分即可。

总结

本文完全是凭自己对 12306 这个网站的核心业务的简单思考而得到的一些设计结果。如果真正的 DDD 领域建模,更多的是要和业务一线的工作人员、领域专家进行深入沟通,才能更深入的了解该领域内的业务知识,从而才能设计出更靠谱的领域模型和架构设计。

非常惭愧,我没有上 12306 买过火车票,家离的比较近,就算要买也是家人给我买:)所以,本文所分享的内容难免是纸上谈兵。但我觉得 12306 这个系统的业务确实比传统的电商系统要复杂,且并发又这么高。所以,我觉得这个系统真的很值得大家重视模型的设计,而不只是只关注技术层面的实现。

注:相关网站建设技巧阅读请移步到建站教程频道。

(编辑:云计算网_泰州站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

推荐文章
    热点阅读