铁路客票系统架构设计.docx
《铁路客票系统架构设计.docx》由会员分享,可在线阅读,更多相关《铁路客票系统架构设计.docx(32页珍藏版)》请在冰豆网上搜索。
![铁路客票系统架构设计.docx](https://file1.bdocx.com/fileroot1/2023-2/25/6cd75cf6-3bce-4e57-bde7-d3f6f78b05e1/6cd75cf6-3bce-4e57-bde7-d3f6f78b05e11.gif)
铁路客票系统架构设计
铁路客票系统架构设计
前言
什么才是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),对象数据库(不是很了解),面向文档的数据库(apachecouchBD),面向统计的数据库(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业务流程分析
先简单的了解一下购票系统的业务流程:
旅客到互联网(也可能是其他渠道)登录,根据出发日期,起始站,终点站查询车票,确定车次和座位,预定车票,然后进行支付,支付成功之后,发短信,之后客户到线下去取票。
一个简单的流程就结束了。
从上面的流程可以看出,整个业务流程中有几个以下几个实体以及实体的重要属性信息
1.旅客信息:
假设都是实名的,至少有三个重要信息姓名,身份证,手机号
2.车次信息:
车次,起始站,终点站,类型,发车时间,到达时间
3.车次停靠信息:
车次,停靠站,达到时间,停靠时间
4.余票信息:
车次,起始站,终点站,发车日期,剩余座票,剩余卧铺。
。
。
5.车票信息:
车次,起始站,终点站,发车日期,购票日期,旅客姓名,身份证,手机号,状态,购票渠道,支付日期
6.支付信息:
金额,支付日期,支付银行,支付金额,支付方式
7.短信信息:
车票信息,验证码,短信内容
整个购票过程包括以上几个重要的实体,其他的几个字段可以先不管。
我们可以假设一下每一个实体的数据规模
实体
数据量
日增量
旅客信息
上限-中国人口数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短信库分库策略
由于短信通知是全站业务,这个完全可以独立与购票业务。
消息量大,但非关键业务,非系统关键路径,对实时行要求不高,所以简单分成两个库即可。
在分库之后,数据一致性要求就会比较高,涉及到分布式事务,后续会重点讨论分布式事务的设计。
先写到这里吧,下一篇会分析表结构以及分表策略和索引。
铁道部信客票系统设计
(二)
在上一篇文章中 铁道部信客票系统设计
(一) 里面,探讨了关于数据库层面的功能性需求以及非功能性的需求,在非功能性需求里面,一博主提出了没有考虑到峰值的情况,这一点的确漏掉了,因为我们铁道部的特殊需求,在春运期间负载很大,平时可能一般,如果用考虑最大的情况,则回存在浪费的情况,如果考虑不足,就像网络订票一样,苦逼。
就好比铁道部春运的时候,发车量大,但是如果制造大量列车,平时就空闲了,也就很亏。
机器的折旧很是块的。
春运期间可以考虑紧急扩容来实现,所以从设计上可以保持这种扩展性。
扩容是一项工程,整体来说比较复杂。
上一篇博客发表后,也有博主和我探讨过一些问题,也让我了解到铁道部目前的状态。
由于这个纯粹是技术上的分析,先不去考虑一些政治因素,毕竟这个比技术复杂多了
正题开始,原来打算这一篇里面介绍数据库表的设计,但是上一篇文章中还有很多细节问题,没有解决,这里面继续上一张,把数据这一层在慢慢完善
购票的业务流程
由于购票过程中是铁道部售票系统的主要功能也是核心业务逻辑,这里先从购票的业务流程开始,讨论购票业务流程中相关的数据库设计
简单的购票流程
终端-->查询余票-->选择车次-->确认座位-->选择张数-->支付-->出票
这里面重要的是两个环节查询余票和支付过程
我们先模拟以下正常网络购票过程中数据库的操作,这里面先把问题简单化,假设用户只买始发站到终点站的数据
1select*from余票
2insertinto车票
3update余票-1
4update车票setstatus='WAIT_PAY'whereid=xxx
5update车票setstatus='PAY'whereid=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可以设计为每个渠道进行配额,比如网络订票,我给总票数的多少,每个代理点,我给的票数是多少等等。
如果把这些因素在加入到余票信息中,会变的非常复杂,也不好扩展,毕竟这个是属于经常变化的。
设计的一个原则分离不变和易