这几天正好看到一条新闻
铁道部:新客票系统2015年建成 ,正好最近想整理和总结一下这几年的工作中的收获,正好可以借这个机会,尝试设计一下铁路客票系统,把自己所学全部用到这个系统中去,顺便也希望各位猿们拍砖,一起探讨一下设计,技术吗,讨论讨论总是有点收获的,总比一个人在那里看书好。
铁道部客票系统设计交流(前言)
铁道部新客票系统的设计(一)
铁道部新客票系统的设计(二)
铁道部新客票系统的设计(三)
铁道部客票系统设计交流
前言
难得在博客园上能看到案例设计讨论,而且是关于12306售票系统的。于是我也来发表下的我一些感想,欢迎来吐槽!
12306系统升级了,但还是被吐槽了。这次国庆我没买火车票,按照去年春节的购票经历,我谈谈我的看法。
什么才是12306最需解决的问题?
1、 重大节假日前期,系统登陆难。
2、 抢票环节的并发处理能力。
3、 余票查询的响应速度。
人们往往有先入为主的观念,导致了解决问题的思维方式收到束缚,难以跳出既定的圈子。谁能说现在的购票系统的业务逻辑和用户体验是最合理的呢?它的设计合理之处又在哪里呢?我想完全不懂技术的人做产品经理,可以比技术出身的人做的更好,因为不懂技术的产品经理提出的需求不会受技术的束缚,更加注重用户体验。12306的余票查询的用户体验太糟糕了,为什么非要有帐号才可以查询-_-
购票系统的功能架构和技术架构,势必要考虑到峰值的处理能力,尤其是超大规模并发的处理能力。12306虽说是非盈利性的,但是这毕竟关乎到民生,为何不公开技术架构,让更多的人参与改进呢?
以上内容可以忽略。以下是我的设计思路,主要采用功能适度分离的思想进行设计。
1、余票查询的优化方案
将余票查询系统与抢票系统功能分离,余票查询系统部署到镜像站点CDN上,抢票系统应用单独部署,支付和退票应用单独部署。(这点很关键)
数据库的读写分离,主数据库只做写操作,写入购票记录和更新实时余票信息,余票查询库可以通过异步更新获取余票信息。余票查询功能可以基于部署到CDN上,建议免登陆,建议向社会开放余票资源和API。(解决查票问题)
余票查询系统的系统架构。我们需要什么级别的实时性?毫秒级?不需要!余票查询的操作远大于抢票,每1秒内信息的变化都难以想象,所以在余票查询上实时性太高意义不大,只能作为抢票前的参考,所以也没必要一定要用关系数据库,NOSQL其实很合适。系统只要保证购票者在信息获取是平等的,抢票环节是公平的(按照先购先得的原则),然后进行适度优化设计。余票查询的系统架构,我有2个设计方案
1) 内存数据为主、数据库为辅方案。
写一个分布式数据分发系统(主站为SApp,镜像站点为CApp),支持远程调用更新,设计特定的类或数据结构,将所有待出售的车票余票信息存储在特定的类或数据结构中(也可以是数据缓存),常驻内存。余票信息变更后,更新SApp。SApp与CApp的数据同步可以设置为15秒1次(或1min一次甚至更久,没数据变化当然不需要去更新啦,因为查询这块应用没必要做到秒级的同步)。SApp的故障恢复,数据可以通过DB(DB可以是一个数据库群集或NOSQL)获取;而CApp的故障恢复,就直接从SApp进行提取即可。如果余票信息不存在于本CApp中,则直接通过算法,从对应的CApp_x获取,如果对应的CApp_x中数据不存在,则直接从SApp获取,并告诉SApp应该去更新CApp_x数据。
2) 纯数据库方案
基于车票余额信息进行分库,并进行数据库远程同步。考虑到实际应用特点,可以对余票信息按照部署城市或者地区进行分库。如在杭州买票,基本上都是查询涉及包含杭州的车次信息,所以可以根据这点做深入的优化,增加冗余记录,减少跨库查询。所以这些数据的实时性要求会比较高,要直接从CDN的镜像库获取。出现异地代购性质的余票查询时,通过主数据库的读库(或者对应CDN的镜像库)进行查询。
2、 抢票环节的优化方案
我可以理解在火车站买票需要排队,因为这样可以维持秩序,对先排队的人也是一种。但是无法理解在网络上买票还需要排队,难道排到后一定有票?更无法理解登陆还要排队,因为把购票者堵在外面不是解决问题的办法,提高窗口的并发处理能力才是关键。
消息队列是需要的,但不应该出现在登陆和购票环节。虽说抢票靠的是硬件、软件、网络和人品,但是每个人都应该同享购票的权利。如何做到这点?
1) 抢票处理服务和身份认证必须分开部署,前端通过负载均衡提高并发处理能力。
2) 将每个抢票请求提交到对应的消息队列(不同车次的抢票处理服务,可以部署在不同的服务器上),并返回队列位置ID,抢票环节完成。然后就是等待抢票决策系统的处理结果。保证每个请求都会被登记并被处理,这点才能体现抢票环节的公平、公正、公开。
3) 异步处理抢票消息队列中的每个信息。抢票成功(表示抢票信息被处理后,受理通过),则给予车票支付序列号,按照序列号支付,完成购票环节。
4) 用户抢票申请的被接收和被处理过程分开。抢票被接收后,如果当前车次的未处理消息队列大于设定的阀值,则堵塞新的抢票申请。如果消息队列数小于阀值,已经没有余票,但是还有未完成支付的余票,则询问用户知否排队等待(如果有人退票或未能完成支付,则享受优先获得该票的资格,在退票和支付过期处理系统中优先考虑这部分申请)。
5) 单独部署未支付订单审查系统。每分钟检测超时未支付订单,并将失效的购票申请反馈到相应的队列处理服务器。
6) 用户可以在有效时间内完成支付工作。可以通过在线支付平台,完成对特定车票支付序列号的支付。支付应用最好也单独部署,加入负载均衡。
铁道部新客票系统的设计(一)
非功能性要求
废话不说,这里先脱离系统的整体架构,后续在不断完善整体架构,这里首先讨论的是数据库层面的设计,因为对于整个架构系统来说,数据库的设计是最为关键重要的,数据库的设计好与坏,决定了整个系统的性能,可用性,扩展性。在考虑数据库的设计之前,我们可以先抛开非业务功能的需求,先看看非功能性需求,主要包括
1 数据库的类型选择
目前市场上数据库主要有:关系型数据库(Oracle,DB2,Mysql),NOSQL类型数据库(MogonDB),对象数据库(不是很了解),面向文档的数据库(apache
couchBD),面向统计的数据库(HBASE)
根据客票系统的类型,应该是属于OLTP类型的系统,但是考虑到商业上分析需求,也属于OLAP型系统,由于本次讨论OLTP系统的设计,优先选择Oracle。为啥,用的公司多,市场上相关的技术工程师多,DBA管理员多,安全性和性能都不错。就是有点贵,不过考虑到是铁道部,完全忽略。对于部分客票系统非关键性业务也不重要的,这一部分数据可以考虑使用Mysql。至于NOSQL,没有用过,这个主要是面向web2.0的,对于事务要求高的系统,不太适合。
2 多数据中心
在金融行业,都必须部署多个数据中心,避免在一个数据库机房故障之后,全部数据都不可用。比如假如某地地震,数据库所在机房宕机了,如果这个时候检票或者买票,就sb了,所以需要尽快恢复。这样必须马上启动另外一个数据库机房配置。
除去灾备情况,考虑到铁路售票系统数据库的巨大访问量,2011年的铁道部的旅客发送量---2011年全国铁路运输目标:旅客发送量19亿人次,根据这个,初略估计一下一年估计要20亿张票,这个只是2011年的量,按照未来的几年的增长,按照目标值100亿人次估计,相当于一天有2700W独立UV,1亿PV。考虑到春节这个变态的高峰期,瞬间的并发量比平时会高上千倍。如果只在一个数据库只有一台,数据库就会存在单点,一旦数据库挂掉了,需要尽快的恢复。这个时候不太可能启用灾备数据库,因为灾备是异地备份,备份数据库同步数据比较慢(网络延迟),所以必须必须在同一个城市在部署一套数据库。这样在单点数据库故障的时候,可以马上切到备份数据库。
下面两幅图主要介绍异地灾备以及同城异机房备份的实现原理。
同城备份
数据一次写一份,日志写两份。由于日志文件实时同步,A服务器写完B服务器的日志文件,B服务器马上就写自己的数据文件。这样不会丢失数据。当A服务器故障,应用马上就可以切换到B服务器。不会存在单点故障。但是考虑特殊情况,北京地震,A,B机房同时故障,整个数据都丢失了。所以必须由异地灾备的数据中心。不过还有其他的方式,这里就不做叙述了。总之是要做好去除单点。
异地备份
这个架构和同城备份有一点区别,就是A服务器只会写A机房的日志文件,然后A异步同步日志到B机房的日志文件。这里面会有几分钟的网络延迟。这里不实时写B机房的日志文件,主要是性能。如果实时写B机房的数据,一次更新操作,就会至少有一次网络延迟(上海到北京的网络传输时间)。会影响数据库的性能。而同城市通过光纤连接,传输速率快,可以忽略网络延迟。如果A机房故障(灾难性的故障,比如地震,机房被恐怖分析袭击),就会丢失一部分数据,丢失的数据就是网络延迟同步的数据。对于购票业务来说,数据丢失几分钟的,是可以接受的,大不了我铁道部亏一点,这几分钟丢失的数据我全部免票。也可以做一次好的营销。但是对于金融行业来说,数据是不能丢失的,这里的异地备份就不符合金融业的要求。用性能换取可用性。就像atm取钱一样,一次交易涉及几分钟。你的交易数据银行至少会备份2份,一份同城的,一份异地的。
3 硬件配置
这一块不是很熟悉,交给dba专业的人去做吧。小机 + 存储(SAS)。不过对于铁道部有钱,上大机即可。不过我们还是按照互联网方式去分析设计系统,使用普通的存储以及服务器。
功能性分析
1 业务流程分析
先简单的了解一下购票系统的业务流程:旅客到互联网(也可能是其他渠道)登录,根据出发日期,起始站,终点站查询车票,确定车次和座位,预定车票,然后进行支付,支付成功之后,发短信,之后客户到线下去取票。一个简单的流程就结束了。
从上面的流程可以看出,整个业务流程中有几个以下几个实体以及实体的重要属性信息
- 旅客信息:假设都是实名的,至少有三个重要信息 姓名,身份证,手机号
- 车次信息:车次,起始站,终点站,类型,发车时间,到达时间
- 车次停靠信息:车次,停靠站,达到时间,停靠时间
- 余票信息:车次,起始站,终点站,发车日期,剩余座票,剩余卧铺。。。
- 车票信息:车次,起始站,终点站,发车日期,购票日期,旅客姓名,身份证,手机号,状态,购票渠道,支付日期
- 支付信息:金额,支付日期,支付银行,支付金额,支付方式
- 短信信息:车票信息,验证码,短信内容
整个购票过程包括以上几个重要的实体,其他的几个字段可以先不管。我们可以假设一下每一个实体的数据规模
实体 |
数据量 |
日增量 |
旅客信息 |
上限-中国人口数 16亿 |
这个真不好估计 |
车次信息 |
比较少,假设10万 |
日增应该也不会超过10 |
车次停靠信息 |
车次信息 ×200 == 200W |
日增应该也不会超过100 |
车票信息 |
巨大,目前年增20亿,未来年曾100亿 |
自己换算一下,不过不会很平均,春节几天会暴增 |
支付信息 |
和车票信息同数量级 |
和车票信息一样 |
短信信息 |
少于车票信息,毕竟只有网络订票才会有短信,线下购票不会有 |
未知,假设100W |
余票信息 |
一年交易量 365×车次信息 == 5000W |
日增数据量 和 车次信息数量一致 假设10W |
从数据量来看 车票信息 > 支付信息 > 短信信息 >
余票信息 > 旅客信息 > 车次停靠信息 > 车次信息
从如此数据量来看,必须进行分库分表。所以分库分表就在设计库的时候就显的非常重要。
2 简单分库策略
旅客信息相关的信息单独分在一个库,这些数据相对来说是读多写少,并且可以大量进行缓存,是基础相数据。
车次相关的信息,比如车次,车次停靠可以单独放在一个标里面,这个标是保存原数据信息,数据量不大,数据完全可以缓存。可以考虑和余票库放在一次。
剩下就是四大的实体信息,余票信息,车票信息,支付信息,短信通知信息,首先短信通知信息相对来说比较通用的,可能未来很多业务都会涉及到短信通知,所以短信相关的信息放在一个库
在剩下就是考虑业务上比较耦合的三个实体:余票,车票,支付。
余票查询频繁,没出一张票,就会更新余票数
车票数据量巨大,有查询和更新需求
支付信息:单笔查询和更新需求
考虑到车票和支付信息在数据量级上远远高于余票信息,余票信息单独一个库,而车票信息和支付信息业务上差异比较大,也单独分出来。
根据以上的分析,可以分出来5个逻辑库:旅客库,车票库,短信库,车次库(车次信息,余票信息),支付库。
继续往下分析,在5个逻辑库里面,由于旅客库数据量有上线,只需要分配一个实体库即可。
车次库增长有限,也暂时分配一个实体库里面。而车库票,支付库,短信库数据量比较大,分配一个实体库显然不够。下面单独分析这三个逻辑库的分库策略
3 车票库分库策略
对于车票信息的分库策略,需要考虑到以下几个因素
1. 功能需求
查询需求:按照购票日期 + 旅客信息 + 状态 或者 旅客 + 发车日期
+ 状态
统计需求:按发车日期统计购票总数量和金额 或者其他几个维度
交易需求:创建车票信息,更新车票状态,创建关联支付信息
对账需求:(假设不考虑退票需求)按照支付日期统计支付流水总金额 以及
统计 支付成功的车票信息总金额
2. 负载均衡
按照数据库处理实时要求进行分析,响应要求高的请求和耗时类请求分开,优先保证能够卖出票,支付车票。同时不能所有的请求都指向一个库。
3 高可用性
避免单点,否则购票功能就不可用。所以数据库至少有备份机制。
4 数据均匀
避免大多数据都在同一个库,否则遇到大数据查询数据库负载会很高,进而影响系统整体的可用性。因为大多数请求都会排队,导致系统资源耗尽。
5 可扩展性
当数据增长过快时候,能够很灵活的添加数据库而整个应用不需要做太大的修改
根据以上几个因素考虑,每一张车票生成一个全局的唯一性id,根据id最后一位数字/N平均把数据分配到N个数据库中,这样每张车票库最多可以分成10个库,可扩展性不强。也可以考虑一致性hash算法进行分库,但是这个比较复杂。还有一种方式就是随机分库,好处是可以无限扩展数据库,但是会给查询带来很大的影响。考虑到查询需求,太多的库可能导致查询复杂,甚至在有限时间内无法查询出来。这里采用最后数字/N的方式进行分库,N为数据库的个数。在这个基础上,在增加一个库,就是历史库,暂时只需要一个即可,把完结的历史数据迁移到这个库,一个季度之前的数据不需要在进行操作,一般只是查询需求,就迁移到这个库里面,减少交易库的压力。
这里分10个线上库,一个历史库。一共11个库。能够支持年100亿的交易规模。不过对于对账的需求,可以考虑另外的方式来实现。这里以后在说。
下面一幅图展示了分库的模型:
通过以上的分库策略,如果某一个库宕机了,会影响1/10的购票用户。
4 余票库分库策略
余票库虽然数据量没有车票库数大,但是查询和更新需求远比车票库频繁。而且是整个业务的关键路径。在考虑这个库的只需要保持一个月的数据即可,一个月前的数据可以迁移走,不需要所有的数据。而在春运期间,这个库的瞬间访问量会飙升,相当于淘宝的秒杀,要求实时性比较高,所以不太可能读写分离。综合考虑,可以分为两个库,
线上库和历史库。历史库用来做分析。这个后续是设计的重点。
5 支付库分库策略
支付信息和车票信息这两个库有点类似,只是用户查询相对来说比较少。这个支付库分库策略和车票库的策略可以一样。
6 短信库分库策略
由于短信通知是全站业务,这个完全可以独立与购票业务。消息量大,但非关键业务,非系统关键路径,对实时行要求不高,所以简单分成两个库即可。
在分库之后,数据一致性要求就会比较高,涉及到分布式事务 ,后续会重点讨论分布式事务的设计。
先写到这里吧,下一篇 会分析表结构以及分表策略和索引。
铁道部信客票系统设计(二)
在上一篇文章中 铁道部信客票系统设计(一)
里面,探讨了关于数据库层面的功能性需求以及非功能性的需求,在非功能性需求里面,一博主 提出了没有考虑到峰值的情况,这一点的确漏掉了,因为我们铁道部的特殊需求,在春运期间负载很大,平时可能一般,如果用考虑最大的情况,则回存在浪费的情况,如果考虑不足,就像网络订票一样,苦逼。就好比
铁道部春运的时候,发车量大,但是如果制造大量列车,平时就空闲了,也就很亏。机器的折旧很是块的。春运期间可以考虑紧急扩容来实现,所以从设计上可以保持这种扩展性。
扩容是一项工程,整体来说比较复杂。
上一篇博客发表后,也有博主和我探讨过一些问题,也让我了解到铁道部目前的状态。由于这个纯粹是技术上的分析,先不去考虑一些政治因素,毕竟这个比技术复杂多了
正题开始,原来打算这一篇里面介绍数据库表的设计,但是上一篇文章中还有很多细节问题,没有解决,这里面继续上一张,把数据这一层在慢慢完善
购票的业务流程
由于购票过程中是铁道部售票系统的主要功能也是核心业务逻辑,这里先从购票的业务流程开始,讨论购票业务流程中相关的数据库设计
简单的购票流程
终端-->查询余票-->选择车次-->确认座位-->选择张数-->支付-->出票
这里面重要的是两个环节 查询余票 和 支付过程
我们先模拟以下正常网络购票过程中数据库的操作,这里面先把问题简单化,假设用户只买始发站到终点站的数据
1 select * from 余票
2 insert into 车票
3 update 余票 -1
4 update 车票 set status='WAIT_PAY' where
id = xxx
5 update 车票 set status='PAY' where
id = xxx
电话订票类似,只不过订的票不会由于过期而取消,要么支付,要么退票
而在窗口、代售点买的票,支付方式只不过是现金,出票的时候自动支付。
其实无论从那个终端过来的请求,都会涉及到查询余票,创建车票,支付车票
过程,考虑一中简单的情况,就是用户只查询一次,就选择了自己要确定的车次,然后购票,去支付。那么一次购票请求,会至少
一次余票查询 一次余票更新,一次车票insert,两次车票update,这个还是最少的情况,实际铁道部的业务应该比这个复杂多了。由于查询余票是购票请求的入口,所有的购票请求都会优先查询余票库
余票库的设计
在第一篇文章中,余票库没有设计成为读写分离,主要是考虑的用户一定获取的是最实时信息,读写分离的话,读库和写库的数据有效性上面会有差异,比如我更新了一个数据,必须马上反映到余票上,否则用户看到一个过期的数据,对用户体验很不好。这个库的访问量超级大,而且还会涉及到热点数据的锁定,一旦同一条数据(比如我这次想买Z27硬卧)同时被大量用户请求,根据上面分析的,出票就要锁定余票表中Z27这一条记录,由于一次只允许一条用户请求能够获得锁,请求要必须尽快的处理,除了必要的原子操作,比如票数-1,产生购票表,其他的耗时操作就应该越少越好,尽量异步化操作,核心思想就是尽快的释放锁,否则请求排队的线程越来越多,导致数据库所有数据库的连接资源被耗尽,系统会变的很慢。
整理以下:在设计余票库的时候,在性能上提升,可以从下面几个方面去考虑
1 尽量减少没有必要的查询,减少数据库的资源消耗
2 锁的粒度越小越好
3 订票事务处理越短越好,消耗的业务逻辑处理越快越好,争取最大的异步处理。
接下来就是考虑如何通过上述思想,找出具体的设计方案
1 减少对数据库的查询
一般情况下,先会查询某日余票信息,接下来就是根据查询出来的信息,选择车次,席位。然后张数,然后订票成功。
首先,假设我们把余票信息缓存,应用先查询缓存,如果有票,用户选择车次和席位,这样会减少一次数据库的查询。
缓存有两种方式,一种是应用局部缓存,每个渠道缓存票务数据,这里涉及到数据的更新以及各个缓存之间的同步,不及时,暂时不考虑。
另外一个种是分布式缓存,建立缓存服务器(这里面说的缓存都是指内存缓存),数据库只需要和缓存服务器之间保持同步,但是这样一来,如果会员想获取最新的数据,缓存服务器也需要保持很频繁的更新,相当于要保持缓存和余票数据的同步。这个成本也是非常高。还有一个折中方案,就是缓存不缓存票数,而是缓存有票无票信息,每次用户查询票数的时候,先查缓存信息,看看是否有票,如果有票,就查询数据查询具体的票数,如果无票,就不需要进行查询了,这样减少了数据库的查询。同时缓存的更新也少了很多,只需要在票数等于0的时候,更新以下缓存数据。假设票在一分钟之内卖掉,相当于只需要承受一分钟的查询请求。
当缓存替代数据库作为主要查询请求处理者的时候,缓存成为整个系统的瓶颈。
2 减少锁的粒度
当旅客选择一张票的时候,我需要锁定一条记录,避免同时更新,造成重复出票(这里说以下,我记得大学的时候,我从武汉买回家的票,铁道部一个座位卖出三张表,而且是大面积情况,相当于一个车厢人数比平时多了三倍,当初我以为是假票,现在看来,可能是重复出票了)。还是拿Z27距离,假设我要买20120931日期票,我必须要选择一个座位,那么设计的时候,就可以
按照日期,车次,席位类型 三者确定一条记录,然后锁定它。而不是值根据车次 + 日期,这样在你买坐票的时候,买卧铺的旅客就不会受到影响。(PS:实际铁道部售票会远远这个模型简单,因为涉及到始发站,停靠站,终点到,假设一个车次停靠
S1->S2-S3,那么旅客买S1->S2 和 旅客买S2->S3 就不会收到影响,我们先简化模型,这里只是先提出设计的思想)
3 减少订票处理事务时间
在整个订票业务流程中,发邮件,发短信,计算各个站点的余票信息等比较等耗时业务操作,完全异步化处理。只需要找出关键的流程,如果需要保持一个事务,那么通过异步确保的方式进行。这个是技术层面的东西,后面在介绍。
存在的问题
通过分析,我们给出了一个最简单的订票数据库这一层的解决方案,再仔细分析其中存在的问题
1 余票查询的维度并不是 车次 + 日期 + 席位类型,应该还有始发站-->终点站因素,必须有一个非常快的算法,判断是否有票,然后告知应用。
2 缓存是系统中的单点,一旦缓存故障,数据库估计承受不了
3 数据库上次我说的只需要分成一个库(因为上一章节建立数据库备份机制,故这里面不存在单点),这里面可能存在性能,这里需要进行压力测试,模拟测试。看看容量上线。为了继续进行设计,我们假设即使在缓存存在的情况,数据库没有办法处理当前的数据,主要是为了应付春运
这里先解决余票库分库的问题,分库考虑的原则在上一篇文章分析过,但是由于这个库的数据量不大,只是访问会比较频繁,我们竟可能减少用户的访问为主要考虑因素,铁路购票有其特殊的因素,比如春节的时候从上海买去成都的票非常紧张,查询量也是最大,但是相反,这段期间买成都去上海的票的人就会比较少,查询量比较少。而春节过后上班也就相反。这个思路也就是说按照站站来分,也可以按照铁路局卖的票来分。我们的思路就是尽量各个库的访问量均匀。不过也存在一个问题,就是分库的扩展性比较差,一旦扩容,就要做改动。
在谈缓存的问题,一台缓存服务器不够,可以部署缓存集群。至于是不是一台缓存服务器存放所有的数据,还是要看数据的多少,尽可能的所有数据都缓存在一台服务器上面。缓存的数据维度为
预售期、车次 、席位、始发站、终点站 、是否有票 ,按照道理,应该可以缓存所有的数据,不过这个也要看缓存的实现支持最大的内存数量。比如java实现的缓存
在32位机器上面 只能支持差不多2G的缓存空间。这里面假设一台缓存服务器能够实现所有预售期车票数据的缓存,那么这里面只需要的就是在余票数据更新的时候更新所有集群的缓存数据。
上面一幅图只是简单的演示了基本的架构模型。
而余票的计算则是里面最为复杂的了,因为新增了两个维度,就是始发站->到达站。这个问题比较复杂,先暂时放到下一篇文章区分析。
继续分析,发现上面的分析中,貌似还少了一个比较重要的因素,那就是渠道因素,我们知道,订票有窗口渠道,代理售票口,网站,电话等等。假如每个渠道售出的票都是公平的,那么肯定不合乎道理的,那互联网可能就是比较占优势的(如果系统设计的足够好的话),对于辛苦排队的人来说,相当不公平。这里面有两种解决方案
1 可以设计为每个渠道进行配额,比如网络订票 ,我给总票数的多少,每个代理点,我给的票数是多少等等。如果把这些因素在加入到余票信息中,会变的非常复杂,也不好扩展,毕竟这个是属于经常变化的。设计的一个原则分离不变和易变。如果不变的和易变柔和在一起,系统的扩展性就回很弱。
2 可以按照请求排队,按照渠道优先级进行分配,这样在大多数请求排队的情况下,有一些请求就回被饿死,也就是部分渠道根本买不到票,因为请求会被饿死。
如果要我选择两种方案的,我会选择第一种,因为可以在不同时刻进行放票,这样可以分散请求。来自互联网10点放票,窗口的8点放票,代理点9点。自然把流量就分开了。渠道配额管理这一块我觉得将会是最复杂的一个系统,涉及到利益太多。余票库这里面我想设计的简单一点,不想把复杂的渠道,配额管理引入进来,尽量放在外围系统中控制。
评论回复:
1 有一些用户说这样的系统用到锁就死定了,这个说法不太准确。首先,在核心票数处理的情况下,必须要保持数据一致性,处理票数减一的情况必须使用锁,所以考虑的是尽量减少锁的范围和锁超时的时间。因为一旦多出票了,会给旅客带来严重的问题,一个座位两张票。其次,在保证数据一致性的情况下要最大程度的保证用户体验和性能
2 使用NoSql其实有一定的适用范围。这个是看架构权衡的问题。我有限要保证的是数据一致性,然后才是性能。NoSql对数据的一致性要求并不太严格。可以作为缓存集群的替代品。由于我个人做金融系统的,对oracle比较偏爱,可能做互联网sns类产品的,对NoSql比较偏爱。但是两个问题领域不一样,要有取舍。
3 由于大部分请求都会在缓存层处理掉,不会直接进入进入请求到数据库,至于锁定数据库的情况,会更少。这个情况下,可以按照秒杀的实现方式。这一点可以专门放一篇来讲。
4 对于窗口售票部分,肯定考虑过了。如果网站做的好的,网站的请求量肯定是最大的。你可以想象一下售票点的终端系统,全国能够有多少W?网络客户端有多少W
几千万应该有了吧,几个终端的数量级。而且终端的话,只有一个人在串行操作,不会无聊到刷浏览器。而IE是个人的,刷死你。还有这个是关于数据库这一层的设计,还有到整体业务架构层的设计,无论售票窗口,互联网等渠道,最终都是要进入到数据库这一层。
5 分库分表要了解背后原因以及策略。
其实只需要12行代码 就可以搞定12306:
今天先写到这里,发现在这上面思考的比较多了,后面再持续分析吧。还要继续开发我的ios
app,写文章有点超时了。
铁道部新客票系统设计(三)
在第二篇文章里面,重点分析了余票库的整体设计,我看到有的评论说了几点,现在整理一下
1 为什么要用悲观锁
为什么要用锁,由于之前是做金融系统,对数据的一致性要求很高。铁道部的出票操作要保证数据一致性,所以必须在获取余票的情况下锁定余票记录,否则会导致并发问题,多出票。如果是站票还无所谓,如果是卧铺咋办,一个席位,两张票。这个操作和帐户扣款一样,考虑下面的例子:
这里没有锁,导致多出票,这个只是两个线程,假设有10个线程,则多出10张票
考虑有锁的情况:
2 系统能否承受悲观锁
首先,我们需要看到什么情况下锁,以及锁持续时间,以及请求的并发数来进行分析。大多数情况下,铁道部购票系统承受的并发量都不会太大,除了节假日,主要是国庆节和春节。及时在国庆节和春节的情况下,我们假设每秒钟1000个请求去买座Z27的20121001的座票,那么锁记录持续的时间是多长了
分两种情况
a 有票:业务逻辑完成,释放锁
假设一般坐票有1000张,那么会有1000次锁业务操作,假设业务持续锁时间0.1秒(这个需要实际去进行压力测试),一秒钟处理10条记录,需要2分钟才能消化。而且这两分钟内oracle连接也被占用,后续的请求排队,系统缓慢,然后假死。这里面我们可以设置一个值,假设超过5秒,还没有办法获取到锁,自动释放连接,数据库返回错误,客户端可以选择重试查询票数或者报错给客户,当然,这个涉及到客户体验,如果获取不到锁,基本上可以告知客户票已经售完。
考虑到如果余票库有10个,那么就可以分摊一秒钟就可以100请求。这个具体还是,不过以上只是假设,需要有实际的数据以及压力测试要测试一下性能。
b 无票:直接释放锁
基本上非常快,不会占用资源。在下一个查询周期就不会在锁定记录,因为直接在缓存出就排除了。
所以个人认为使用悲观锁不会存在太大的问题,只要库设计的合理,锁超时时间设计的合理,对请求进行有限的控制,是可以支持的,当然,这个要求实际去检测。
3 不需要锁,只需要一台应用服务器启动线程处理购票,也不需要应用集群?
那可以想象一下,所有的购票请求都会同时请求到那一台处理购票的服务器上,假设这个每秒钟处理1000请求(这个据我所知已经算高了)基本上高峰期几秒种的数据就把服务压挂了。并且这个是单点,一旦这一台服务器挂了,整个系统瘫痪。
4 余票数据可以放在内存中吗?
放在内存,也就是放在内存缓存里面,这个问题有以下几个?
1 缓存故障
当缓存出现故障的时候,怎么办?当然可以缓存集群,切换到另外一台缓存服务器,但是余票的数据如何同步?两者不一致怎么办
2 系统发布
当系统发布的时候怎么解决缓存中的数据库问题,当然可以先把请求处理完成,然后在发布。或者发布之前,把缓存的数据同步到数据库中,这样也是可以的。但是设计上太复杂。
3 极端情况
硬件故障
操作系统故障
机房断电
这些故障都会导致内存数据丢失,余票数据都丢失了
我就知道我所在的公司遇到的变态的情况如下:
机房无故断电,网卡故障,磁盘写入失败,
经常遇到的情况是:jvm crash,内存泄漏,这些会导致放在内存的数据比较危险。
还有领域驱动设计和数据库设计两者没有必要联系。领域驱动是解决复杂业务领域的时候用的一种设计思想,自己在这方面开发比较多,和数据库这一层设计没有关系
对于性能来说,当然可以把所有的数据都放在内存里面,这样的处理效率最高,但是内存的数据就易丢失的数据。设计一个系统架构最大的问题就是权衡利弊。对于火车票系统,在出票的流程上,相当于金融系统的转账,余票相当于用户的余额,要保证最高优先级的安全性和一致性。这个性能相比,优先级要高。
谈谈对架构的认识
这里在说说谈谈自己对架构的认识吧,架构不是说就只管系统的性能,架构是全局观。涉及到安全性,可用性,性能,数据一致性,可扩展性等等。每个系统的应用特点不一样,所以思考的重点不一样。金融类系统对可用性和安全性数据一致性要求非常高,不能有任何的妥协,宁可牺牲性能,这就是为什么银行喜欢用IBM的产品。新浪这样的网站,性能很重要,但是如果丢失的用户少量数据,无所谓,所以数据可以放在内存里面,甚至可以用nodejs这样的新技术来实现。铁道部这样的国家级系统,可用性一定是在第一位的,其次是数据一致性,然后才是性能。就扯这么多,每个人的观点不一样,但是架构就是这么虚的东东。关系型数据库目前证明还是最可靠的,你能想象金融类帐务系统用的是NoSql这样的对事务要求不高的数据库吗?所以关系型适合在购票这样的核心应用。其他的库可以用NoSql来实现,比如会员库,没有问题。
渠道购票分配
上一篇文章提到了这个渠道订票的需求,目前有两种方式进行数据库层面的设计,第一种是各个渠道的数据切割开,互补影响,也就是说我网络订票不影响窗口售票,我想这个需求还是比较合理的,有限保证窗口票,那么可以把数据库水平分割,按照渠道来设计
这种设计的最大好处,就是各个渠道订票完全是分割开的,互相都不影响,但是这样最大的问题就是事先算好每个渠道的票的配合,比如窗口100张,网络20张,代售200张,电话30张。非常不灵活,比如假设我窗口还有票,但是窗口没有人买,怎么办,那只能其他渠道来卖。这样也比较浪费,毕竟大多数情况下,铁道部的购票还是系统还不是那么繁忙的。
另外一种设计就是在渠道层做流量控制,让流量控制这一层负责票数分配
这种设计方式就是比较考验渠道层,渠道层需要做流量控制,这样分配的最大问题,就是分配均匀,所以很好的算法。但是数据库层统一了,可扩展性比较强。容易进行扩容,也不会造成硬件的浪费。
两种设计方式体现的思想不一样,各有利弊。不过从更倾向于第二种设计方式,比较灵活。而且渠道流量这一层本身就必须的,这一层可以设计的比较厚,而且可以大量使用缓存或者NoSql。
余票库的再分析
1 余票库的分库策略
昨天讨论余票库分库策略,想起和一个铁路工程师聊起,再想想自己电话订票的时候,可以发现更好的分库方式。下面是从百度百科搜到的铁道部的组织架构:
铁路局是中国铁路管理体制的特色产物,是中国铁路四级(现在为三级)体制的重要组成部分。中国目前有18个铁路局(公司),分别是:哈尔滨、沈阳、北京、呼和浩特、郑州、济南、上海、南昌、广铁集团、柳州(2007年已搬迁至南宁)、成都、昆明、兰州、乌鲁木齐、青藏铁路公司、太原、西安、武汉。
铁道部下面分为18个铁路局,我估计每个铁路局负责的车次不一样,那么分库就可以按照铁路局分库,这样就可以分为18个数据库。但是考虑到余票库的实际数据量并不大,只是高并发,我们没有必要分成18个库。但是我估计由于利益分配,这个18个铁路局应该每个铁路局都有自己的数据中心。但是我们这是纯技术层面的分析,先不考虑利益和实际情况。我们可以按照铁局路分库,但是前提是负责卖票的车次都不一样,否则一个车次,分到两个库里面系统会变的很复杂。假设范围分为四个,东南西北,比如(哈尔并,沈阳,北京,呼和浩特)一个库。这样我们就有一个简单的模型了,那么在梳理一下一次典型的购票数据流
其中虚线圈起来的部分是要保持数据一致性。
简单的说就是 余票减少一张,车票库待支付记录就会多一条;车票支付成功,车票状态要变更,支付数据完成,短信要发送,当然这个要看你对一致性要求有多高,车票购票成功,短信发送不一定要成功。但是车票支付成功,支付数据总要有,并且是成功的吧。这个要进行对账的,否则就是一笔糊涂账。据说铁道部还会建立会员营销系统,那么越来越多的功能就会更复杂,比如支付成功,积分增加一百,积分以后可以进行车票的支付等等。不过这个不是核心,如果能继续写,在分析吧。
至于在分布式环境下如何保持数据一致性,这个我会专门写几篇文章来进行分析,我觉得这个是比较有价值的。
2 如何确定余票
这个是第二篇博文中我没有写的,我觉得这个是最复杂的,现在继续分析。
确定余票的因素:日期,车次(假设动车,普通车次不重复),出发站,到达站,席位
其他三个很容易从数据库中获取,这里不说了,主要是出发站,到达站两个维度。
如果通过其实出发站和到达站进行查询,首先我们根据车站确认可以通的车次,可以简化为对N个车次查询,在根据出发站,到达站进行分析
假设 车次Z27,坐票,20121001,中途经过5站(A,B,C,D,E),共有100张,简化记录为(A->E)
= 100
假设用户U1买了从A->E的车票一张,那么还剩下99张, 就是
(A->E) 99
假设用户U2买了从A->C,那么 (A->E) = 98
,(A->C)=98 (C->E) = 99
假设用户U3买了从C->D, 那么 (A->E) =97
,(A->C) = 98 , (C->E) = 98 (C->D) = 98 , (D->E)
99
看来越来越复杂,但是我们发现这个和二叉树有关,看一下下面的图,不断构造一个树的过程,而且判断票和也查找节点有关,找到之后此节点票数-1,所有的父节点票数-1。我算法不好,只能想到这个算法了,不知道有没有更好的算法,这个树不需要实时更新,放在内存里面。这样就方便查询了。
整个架构的基本模型
把第一篇和第二票文章整理的内容在综合整理以下,在应用在丰富以下,可以搭建一个简单的分布式系统的应用原型,透过这个原型,我们在不断的完善系统
目前铁道部可能的架构模型
最后看到铁道部的组织架构,感觉铁道部的系统架构可能大致是这样的:随便乱太猜想的,下面一个简单的模型,实际情况远远比这个复杂。
由于数据中心是分布的,所以系统很慢。因为数据这一层路由,都是远程调用啊。同时铁道部的应用应该也是分布部署的,由于数据中心不集中,花在系统间通信的成本太高。感觉这个可能是铁道部内部的政治格局导致的,以前的银行都是这么搞的,每个省都有自己的一套系统。
后续
写了这么多,先休息一段时间吧,后续有时间在继续分析这个系统,今天也恰好看到一个新闻:
据介绍,12306互联网购票系统是基于中国铁路客票发售和预订系统(简称客票系统)这一核心系统构建的。客票系统在10余年的运营过程中先后完成6次升级:1.0版本实现了计算机售票取代人工硬板票,2.0版本实现了区域级联网,3.0版本实现了全国联网售票,4.0版本实现了与清分清算系统的对接,5.0版本实现了席位复用和共用,5.2版本实现了实名制售票、电子客票和电子支付。
由于2.0版本是区域级别售票,3.0实现全国联网售票,但是我估计所谓实现全国联网售票,因为是在2.0的基础上通过适配和路由搭建的,铁道部应该还没有统一的数据中心,数据应该是各个铁路局控制的。
4.0版本主要做清分系统,这个就是内部的核算系统,应该算是铁道部内部最为复杂的一个系统了,比如我卖了1000张票,应该得多少钱,应付给代售点和合作商户做多少钱。
5.0可能就是上面说的同一个位置,由于区域段不同,可以买两张以上的票,只要铁路段不重复,也就是我说的余票树模型。
5.2就是实名制和增加了电子支付和电子客票,这个应该稍微简单一点,只是增加了一种支付工具和验票手段。所以版本号就没有怎么升级。
看看新的客票系统的愿景;
铁道部透露,新一代客票系统实现了四方面创新。一是服务模式创新,系统支撑包括票务服务、旅行服务等各种延伸服务在内的预订业务。二是营销理念创新,对铁路列车开行、运力调整、票价优惠等提出合理化建议;制定铁路客户常旅客计划,建立旅客积分、奖励制度。三是管理手段创新,满足淡旺季不同客流特点下的售票组织需要。四是技术架构创新,研发高性能核心交易平台。
从业务角度来看,
1 铁道部想建立客户模型,目前铁道部还没有客户模型,毕竟实名制刚开始,区分优质客户以及黑名单。未来可能你座的越多,可能走优先贵宾通道上车等等
2 建立积分模型,这个主要是用来营销,和航空和银行一样,估计就是换礼品什么的
3 技术架构创新,我觉得这个是最重要的,现在的架构可能因为历史的原因,比较弱。
4 高性能核心交易平台:这个是必须的,类似淘宝那样的交易平台。从全球性来看,ebay的交易平台比较牛逼,不过淘宝最近的交易数据不知道超过ebay没有。这个交易平台未来会不会开放,也是一个想想的空间。
未来还可以想想的是,一旦铁道部建立的会员模型,更有可能往金融上面去靠拢,建立自己的账户模型,推出更多的支付方式,比如预存款,联名卡等等。铁道部相当于拥有最多实名制会员的公司,想象空间很大。但是前提是系统要做得好,别再被人骂。
|