zoukankan      html  css  js  c++  java
  • 《12306核心模型设计思路和架构设计》阅读心得

    对12306系统的服务以及售票系统有了进一步的了解:

    其实,12306系统也相当于是电商系统,而且看起来商品就是票了。因为如果把一张票看成是一个商品,那购票就类似于购买商品,然后每张票都有库存,商品也有库存的概念。但是如果我们仔细想想,会发现12306要复杂很多,因为我们无法预先确定好所有的票。

    对于12306系统来说,他的用户无异于是是两者,其一就是我们这些进行出行购票普通用户,另一方就是要售票的铁道部。

    对于传统的电商系统来说,如果按照普通电商的思路,把票(站点区间)设计为商品(聚合根),然后为票设计库存数量。我个人觉得是很糟糕的。因为一方面这种聚合根非常多,另一方面,即便枚举出来了,一次购票也一定会影响非常多其他聚合根的库存数量(只要被部分或全部重叠的区间都受影响)。这样的一次订单处理的复杂度是难以评估的。而且这么多聚合根的更新要在一个事务里,这不是为难数据库吗?而且,这种设计必然带来大量的事务的并发冲突,很可能导致数据库死锁。总之,我认为这种是典型的由于领域模型的设计错误,导致并发冲突高、数据持久化落地困难。

    对12306系统来说,车次具有一次出票的所有信息,所以我们应该把出票的职责交给车次。我们应该知道,聚合设计有一个原则,就是:聚合内强一致性,聚合之间最终一致性。对于系统售票,要产生一张票,其实要影响很多和这个票对应的直线相交的其他票的可用数量。因为所有的站点信息都在车次聚合内部,所以车次聚合内部自然可以维护所有的原子区间,以及每个原子区间的可用票数(相当于是库存数)。当一个原子区间的可用票数为0的时候,意味着火车针对这个区间的票已经卖完了。所以,我们完全可以让车次这个聚合根来保证出票时对所有原子区间的可用票数的更新的强一致性。对于车次聚合根来说,这很简单,因为只是几次简单的内存操作而已,耗时可以忽略。一列火车假如有ABCD四个站点,那原子区间就是3个。

    根据订单信息,拿到出发地和目的地,然后获取这段区间里的所有的原子区间。然后尝试将每个原子区间的可用票数减1,如果所有的原子区间都够减,则购票成功;否则购票失败,提示用户该票已经卖完了。是不是很简单呢?知道了出票的逻辑,那退票的逻辑也就很简单了,就是把这个票的所有原子区间的可用票数加1就OK了。如果我们从线段的厚度的角度去考虑,那出票时,每个原子区间的厚度就是+1,退票时就是减一。就是相反的操作,但本质是一样的。

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

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

     文章部分总结摘自原文,向原作者致敬!如有侵权或不周之处,敬请劳烦联系(微信:15227013954)马上删除,谢谢!

  • 相关阅读:
    Ubuntu终端Terminal常用快捷键
    继承(一)
    c语言中动态数组的建立
    指针的一些小的知识点
    什么是内存地址
    组合(composition)与继承(inheritance)
    重载new和delete
    不要轻易delete void*指针,这样会隐藏比较多的错误。
    内存管理运算符new delete与内存管理函数malloc free的区别——已经他们对对象创建的过程。
    自动类型转换之全局重载运算符与成员重载运算符
  • 原文地址:https://www.cnblogs.com/zhaochunhui/p/10485654.html
Copyright © 2011-2022 走看看