likes
comments
collection
share

我也来爬一爬12306 - Day7 总结和思考在本章节中,笔者根据自己在项目构造和操作的过程中。对12306的设计和实

作者站长头像
站长
· 阅读数 31

概述

今天是最后一天,按照设定,其实已经没有具体的工作要做了。但笔者想要来讨论一下,在过去的几天的实践中,对于12306的设计、实现和技术等方面的一些看法和思考。

Github项目

笔者将相关项目和代码稍微总结了一下,放在了github上,有兴趣的读者可以参考和执行一下,就能够对项目和操作有更加直观的理解。

github.com/yanjh007/12…

操作中遇到的问题

在整个项目的具体实践和操作过程中,遇到了一些问题。这里先说明,这些问题,可能并不是系统本身设计或者实现的问题,也有可能是当前使用和操作的版本和软件兼容性的问题。这里只是列出来参考和讨论。

  • 查询操作限制和错误

在对12306进行业务和功能分析的时候,有时候会遇到查询出现错误,或者不符合预期,或者体验不佳的情况。例如12306首页上,有一个显著的“常用查询”-“正晚点”功能。设计者的意图是选择输入车站和相关车次,然后就可以进行查询了。但事实上由于这个查询有一定的业务查询条件的限制(如3小时内的信息),在大多数情况下,查询都是失败的(下图)。这其实就是一个很不好的应用体验。第一,现在的技术条件,查询整个车次在一段时限内的正晚点状态,并没有太大的问题;第二,其实可以通过直接输入车次来进行查询,可以有效降低用户的操作难度;第三,如果没有相关数据,应该直接响应相关限制信息,而不是等到验证和跳转后才显示。当然,如果设计者的目的,就是提高用户的操作成本和台阶,或者就是故意让他们不便使用,从而在客观上进行限流,也许也是一种实现思路。

我也来爬一爬12306 - Day7 总结和思考在本章节中,笔者根据自己在项目构造和操作的过程中。对12306的设计和实

我也来爬一爬12306 - Day7 总结和思考在本章节中,笔者根据自己在项目构造和操作的过程中。对12306的设计和实

这些设计,其实就是典型的“工程师”思维的表现,就是出问题了,再给你显示,而不是“你其实不需要做这些操作”。

  • 车站数据缺失

笔者在检查访问过程和数据的时候,发现一些操作的错误。例如,有一趟列车S1696。数据库记录中,其出发站为null,所以无法正确的获取时刻表信息。然后经过分析和追踪,发现这趟列车是一个“市域”(S)列车,起始站为“金山卫”,但在车站列表(包括station_name.js文件)中,是找不到这个车站的记录的。后面只能通过手动添加记录(金山卫-BGH)来进行解决。

我也来爬一爬12306 - Day7 总结和思考在本章节中,笔者根据自己在项目构造和操作的过程中。对12306的设计和实

但实际上,在接口上,是可以进行查询的,如下:


https://search.12306.cn/search/v1/train/search?keyword=S1696&date=20240815
Data: [
  {
    date: '20240815',
    from_station: '金山卫',
    station_train_code: 'S1696',
    to_station: '上海南',
    total_num: '8',
    train_no: '55000S169600'
  }
]

有趣的是,虽然在12306网站上,没有“金山卫”这个车站;但在APP上,却可以查询和使用相关的信息(下图)。这也说明作为一个整体的系统,12306在一致性方面,还是有一定的问题的。

我也来爬一爬12306 - Day7 总结和思考在本章节中,笔者根据自己在项目构造和操作的过程中。对12306的设计和实

  • 特殊车次处理

笔者在操作中,还发现了一些有趣的问题和设定。比如有一趟列车G8769,在访问数据时,无法找到到达站的数据。经过检查后,发现这趟列车是由“贵阳北”开往“贵阳北”的,也就是说,它是一趟环线列车。但是,系统的接口设计,却没有直接使用车站名,而是在终点站的名称上做了一个小手脚,就是使用“贵_阳北”这个名称。笔者的系统里,当然无法逆向查找到这个信息的原始编码,到站的编码就成了null,导致程序出错。下面就是这个查询的数据:

https://search.12306.cn/search/v1/train/search?keyword=G8769&date=20240816
Data: [
  {
    date: '20240816',
    from_station: '贵阳北',
    station_train_code: 'G8769',
    to_station: '贵  阳北',
    total_num: '11',
    train_no: '78000G876804'
  }
]

但在实际上,在笔者的项目中,这个设置并不会造成冲突和困扰。因为起点和终点是两个项目,即使是相同的,在逻辑上也不会错误,不知道12306为什么要这样设计。

设计和实现问题

上面是笔者在具体实践中发现的一些问题,其实相关类似的问题还挺多,这里只是举一些典型的和项目工作相关的问题。下面就是结合这些问题和项目的实现细节,更深入的从设计和开发的角度,来对12306这个系统进行技术方面的审视。

首先说明,此处的讨论不主要针对其业务特性、设计和细节,纯粹是从技术实现框架、性能规划和安全考量等方面进行讨论。笔者也对12306本身也没有任何看法和反感,只是觉得它可能适合做为一个技术研讨的对象。而且本文讨论的内容,只限于当前我们能从表面上获取的呈现和信息,如现在开放的12306公共网站,包括其使用和生成的页面以及直接使用的接口,不涉及其他技术体系如内部网站、系统软硬件选项、接口实现和应用程序等等,所以讨论的问题和观点也可能是偏颇的。探讨的目的也是发现问题,总结经验,改进系统,绝非攻击贬低。如果有读者对此有意见,只要是技术方面的,有理有据的,欢迎提出讨论。

下面我们先针对系统的设计和实现等技术角度,分成几个方面进行探讨。

Web应用实现框架

从12306这个Web应用系统本身而言,使用的技术和实现主要是比较“传统”的,基于HTML页面的体系架构,而不是可能更先进、可以提高更多灵活性和可扩展性的前后端分离的Web应用架构。

这在设计和实现之初(大概十几年前),当然是基于当时的技术环境,人员配备,特别是应用环境比如浏览器兼容性的考虑,对于比较新的Web技术无法大胆的尝试和使用,是比较容易理解的。但从另一个角度而言,可能也暴露了这个系统的整体设计和运营思路,主要还是基于一个“信息管理”类型的应用系统(12306好像本身就脱胎和升级于一个内部购票系统)和项目的思维方式,而不是架构和运营一个互联网电子商务系统和平台的做法。

笔者并不是一个唯技术派,认为技术改进的目标从来都不是为了尝试和使用技术本身,而是合理的技术演进,确实可以改善应用体验,提高系统的性能和可靠性,降低运营成本,扩展业务模式等等,可以带来实实在在的收益。其实,解决和改善这个问题,行业也有很成熟的思路和做法,就是架构一个新的改版系统,然后逐渐进行割接和迁移,逐步淘汰老旧的系统和技术。但这个方式,就需要一个相对的长期主义,有比较清晰明确的技术发展和演进方向的思路和规划,并且能够有能力有条不紊的进行实现和推进。这对于建构和运营技术团队,确实也是一个很高的要求和挑战。

系统一致性

从本项目的实践过程中,笔者发现,这个系统的一致性比较差。举几个例子:

  • 信息查询

笔者感觉,就是使用很广泛的信息查询,查询的模式和内容都差不多,但它们就是分布在各个子系统内,拥有不同的路径、不同的域名、参数定义和请求方式都可能不同,显然没有进行很好的规划和整合,为后续改进、扩展和外部应用集成带来了不便。

  • 编码规则

以车次查询和时刻表查询为例,它们都需要使用日期作为一个查询参数,但就在这个日期的编码规则,都是不一样的,这样就会给客户端应用的开发,造成一些不必要的额外处理工作。

  • 参数转换

在一些信息查询的接口中,参数使用的方式也是不一样的。例如同样是使用车站名称作为参数,有时候就用电报码,但可能前一步的查询结果是车站名称,这时就需要进行一个转换,增加了开发的成本。

  • 手机版和桌面版

前面我们已经简单的讨论过,12306这个系统,提供Web端和APP端,但从前面一个简单的案例就可以看到,起码比较基础的数据层面,也有不一致的情况。这起码说明,它们在各个模块和系统间的协调,是有一定的问题的。可以理解作为子系统之间的差异和一些工程技术上的限制,但12306不是普通的互联网产品,开发方名义上也不是普通的商业化公司,也都在12306这个共同品牌之下,不应当出现这样的问题。

系统的一致性比较差,会带来很多问题,特别是业务层面的影响。比如一个基础数据和信息的不一致,会导致相关的业务都受到影响。当然对于系统的演进、运维也会有一定的影响,需要在各个子系统和模块中,进行相关检查和改进。

当然,从逻辑上我们当然希望系统的一致性是很强的。但在实际场景中,这种强一致性会带来出问题的时候,可能会造成的影响比较大,也就是错误的风险和影响更大了,这需要通过加强评估和测试来改进。

接口设计

前面我们看到,12306虽然作为一个比较传统的Web技术体系,但在其中,还是有一定的数据接口设计和应用的,不完全是Web页面内容承载的方式。但这个接口体系的设计,还是有明显的问题。就一个简单的基础信息查询的功能,我们就可以看到三种实现方式:JS文件嵌入(车站列表),HTTP Get + queryString(车站车次),HTTP Post + Form(车次时刻表),给人的感觉,这个设计和实现,就是开发者随心所欲决定的,过于草率和随意了。这样就会给系统的业务演进、性能优化、缺陷和故障排查带来很多困扰。这也是和一致性设计是相关的。

一个简单的应用实践中,我们就发现了这么多问题。这样的细节化的问题,笔者相信在12306中应该是普遍存在的。当然这很容易理解。就是一个发展周期比较长的系统,其中没有经过很好的改版和重构,这种情况基本上不可避免。但也从另一个角度看到,12306系统的技术演进,没有一个很好的节奏和规划,还是以前做信息系统项目的思路,而不是互联网系统正常演进不断优化和满足新业务的方式。

性能问题

除了一些基础的业务功能和数据之外,12306作为一个被广泛和大量使用的公共应用,它在性能方面的设计和考虑,也应当是一个比较突出的,我们重点关注的一个方面。我们先从笔者在这个系统上看到和理解的一些性能方面的问题出发来了解一下相关的具体情况,然后来谈谈笔者对于12306系统性能需求和设计的思考和看法。

页面加载

首先,最直观和影响应用体验的,就是页面性能了。作为一个标准的Web应用系统,12306网站的性能方面的分析,也脱离不了网络响应、数据传输、接口性能、前端程序加载和运行、页面渲染等方面的内容。

先来分析一下12306首页加载的时序图(下图)。

我也来爬一爬12306 - Day7 总结和思考在本章节中,笔者根据自己在项目构造和操作的过程中。对12306的设计和实

所以,这个12306主页的设计,给笔者的感觉就是: 基本上就没有考虑优化!?下面例举具体一些笔者觉得有问题的地方:

  • 作为一个潜在用户规模很大的网站类的应用,它的主页设计的过于复杂,网络内容体量太大,会对其承载的IT系统造成很大的负担
  • 笔者的无缓存条件下,主页内容资源大小为7M,加载时间2s,请求数量87
  • 加载时间最长的竟然是一个QR码,160K,1s
  • 最大的资源是一个banner图片,905K,900ms
  • 出现了一个502bad gateway错误
  • 作为一个静态资源而非应用构建为主的应用,其内存占用还好,snapshot只有7M
  • 虽然看起来加载时间稍长,但可能规划的比较好,并没有明显的卡顿的感觉

所以,虽然经过比较长时间的演进和发展,12306网站和网页的应用在性能方面的体验是可以接受的,但显然还有很多可以优化的空间。

页面内容

不说别的,来看看下面的内容:

我也来爬一爬12306 - Day7 总结和思考在本章节中,笔者根据自己在项目构造和操作的过程中。对12306的设计和实

这可是12306主页的HTML头中的内容。作为Simlarweb全球排名7000余名的网站,内容中竟然有如此多的注释内容和无效冗余信息,这还主要是从开发和技术层面的。在产品层面,作为一个专业销售火车票的电子商务系统,还有很多和客票销售没有直接关系的引流、宣传和广告等内容,其实都会造成很不好的应用体验。

接口设计

前面已经探讨过12306数据接口的多样性和不一致等方面的问题。除了影响开发和业务演进之外,它们可能也会潜在的对整个系统的性能造成影响。

其实原本的HTTP协议,对于信息和数据的操作在语义设计上是比较清晰、明确和完整的。就是数据查询,应该用GET方法,数据增加用POST(修改用PUT,删除用DELETE),一般我们就考虑查询和修改就可以了。对于查询,因为默认考虑到这个操作不会对数据产生影响,就可以使用一些查询优化的方式来提高性能,比如只读数据库、服务/客户端缓存、CDN等等。笔者不知道12306为什么要设计成为POST来查询数据,但从语义和一般框架实现上而言,所使用的优化方式显然是不一样的。

信息结构

笔者的感觉是结构和内容比较冗余。中间很多业务信息,其实是可以生成的,并不需要全部进行记录和存储。如时刻表中的到点、开点和停车时间,只需要有两者就行了。再如时刻表中车站顺序,其实就是数组顺序,不需要再加一个次序的标识等等。还有就是信息的必要性,比如车站列表,现在使用一个JS文件和字符串变量存储所有车站的信息,容量有160K。这在大多数情况下,也是没有必要的,完全可以设计为热门车站字典+扩展搜索的方式。

精简紧凑的信息结构设计,带来的一个直接的好处就是减少信息存储和传输的规模,对于12306这种流量巨大的系统,其实更有意义。

可能这种实现,我们从另一个方面可以看出来其前端技术的演进和应用并不理想,大量的信息,其实是可以在前端处理的,并没有很好的利用终点系统硬件和浏览器或者APP的处理能力。这样相对而言,就会给后端和服务器系统造成更多的性能负载。

性能挑战的迷思

12306在中国的互联网信息系统应用开发生态中,也算是一个传统的经典案例了。一般的开发者,都认为它是一个典型的超大规模用户,超大负载和高并发应用的代表。就用户规模而言,它的用户应该在10亿的级别。从并发数量来看,一般认为它的应用需求在100万QPS这个级别。

遗憾的是,12306官方,从来没有公开的公布或者讨论其系统设计和实现的相关细节(相关披露的信息,笔者认为从专业的角度来看是不足的),我们作为外部人员,是无从知晓其内部的基本架构、部署和运行方式的,也不知道其大致的系统承载所需要的软硬件配置级别和研发投入的规模,这可能对于我们的认知和讨论造成一些困扰,只能从外部的应用经验,加上一些合理的推论和想象,来分析其遇到的困难和问题,并提出可能有效的解决和优化方案。

幸运的是,随着技术、软硬件系统、应用和开发经验和认知的不断发展,在当下的2024年,我们如果在更高的层次,更客观和理性的看待这个问题。就应该可以看到,虽然12306还是一个很大的技术和工程挑战,但相对而言,并不是像在世纪初那样几乎是不可解的问题,而是经过合理的规划、设计、系统架构、技术模块的选择和组合,再加上合适的软硬件基础设施和业务过程组织,即使是一个中小型互联网企业,在现有的工业标准和开源软硬件体系之上,就可以架构和实现的系统,而且并没有绝对的技术难度和限制。这一点,我们在后面的分析中,就可以看到。

首先,简单的分析一下12306的设计和实现需求。这里有一篇新闻报道和一篇名为《12306互联网售票系统的架构优化及演进》的技术论文,让我们可以从需求的角度一窥12306的基本状况:

www.chinanews.com.cn/cj/shipin/c…

www.modb.pro/doc/93043

结合项目中收集到的一些业务信息,12306的主要业务状态和指标如下:

  • 年售票30亿张,高峰日1300万张,每秒1000张
  • 高峰日访问量1600亿次
  • 业务核心难题是“余票计算”
  • 经过了数次技术和架构演进,应用了异步交易排队,售取分离,读写分离等多项架构和范式
  • 全路2300余个客运车站,运营16000余趟次列车

如果这些信息没有大的错误的话,这里面已经呈现出一个很大的问题。如果高峰日售票1300万张,而访问量是1600亿次,这背后有一个看起来很荒谬的事实,就是完成一次有效的购票,需要进行1万多次请求和操作?或者另外一个事实,就是很多无效的请求,并不是由真正需要购票的人的操作而产生的(就是抢票机器人做的)。

这样,结合我们在高峰期间,使用12306的经验,我们应当能够发现:

12306系统应用性能问题的核心,并不是并发处理的能力,而是合理过滤和分配有效请求的能力。

理由很简单,即使是在高峰期,中国铁路的业务供给能力(能提供的火车票数量),和平常相比,其实并没有高出很多,票还是那么多,所不同的是抢票的人(或者机器人?)多了而已。所以,和普通的电子商务系统相比,虽然看起来12306的SKU很多,但实际上它的业务其实相对简单,起码产品或者组合的变化,并没有想象那么大。而且相对而言,参与商品购买的用户相对也比较稳定,不会出现商品和购买者差异过于悬殊的情况。

这样,笔者认为,这个业务系统的核心能力,并不应该是可以实际并发处理这么多的请求,而是把有效的请求过滤出来,同时快速的响应和处理那些无效的请求。这些能力,主要就是客户端的认证、请求验证、流量限制和转发等。对于核心处理能力,可以看到真正的也就是1000张/S左右,这对于现代化的电子商务系统而言,其实是一个很常规的规模和需求。

第二,我们以一天的处理场景为例。虽然每天有16000余个车次,提供1000万张左右的客票,实际的处理购买流程请求大约在2亿左右,看起来庞大复杂。但审视一下这些请求,就会发现,它们在底层的数据关联度并不是很大,请求是可以进行简单的在垂直维度上进行分组和分类的。也就是说,这个业务特性非常适合于进行分布式服务和横向扩展,通过简单的扩展硬件和服务模块,就可以以几乎线性的方式扩展系统的整体处理能力。理论上,我们可以为每一趟车次,都可以分配一个独立的应用接口来处理相关的操作,而不会对其他不是该趟车次的相关用户的操作产生影响。甚至在入口也可以进行类似的处理,比如为每个路局都架构单独的服务系统来实际处理由门户转发来的业务请求。

再有,就是真实用户的主要业务流程,也可以在大的框架下,分为查询-预定-支付等三个主要的阶段。前面已经提到,这里面并发规模最大的,其实就是这个查询了。但进一步分析,我们还会发现,查询的内容主要包括两类,固定的信息如车站车次时刻票价等,和动态的信息如余票等。固定信息很容易的可以由缓存和CDN等进行提供和加速;而动态的信息如余票和列车状态,才是真正需要实时处理的内容。在技术和实现上,其实也是可以将这两个部分分开,从而更好的进行处理资源的调配,从而在整体上提供更好的服务能力。

除此之外,鉴于铁路客票销售业务的一些特性,有很多工程和技术的手段,可以改善和优化所谓并发处理的性能问题。比如进行防刷限流,实施更好的请求验证,查询车次(基础信息)和查询余票(动态信息)分离处理,基于路局车站热门车次等因素进行分流,将前端应用和数据接口进行分离,分时段开放票池,预定排位通知异步操作,订票和支付分离处理等等,很多其实都已经以不同的方式,或者在不同的程度上实现了,也都是一些很常规的处理方式,鉴于篇幅限制,这里就不再一一深入展开探讨了。

关于业务系统中另一个核心的难题:“余票计算”,笔者其实也做了一些简单的研究和思考。有几种简单的处理方法。传统的方法就是简单粗暴的“预留制”,某个车厢或者一些席次就是固定的留给某些车站进行销售的;还有就是限制短途销售,只销售起始站和长途的席次,然后在列车上自行处理下车后的空位。复杂一点的就是精确计算所有空余的席次和始到站之间的关系,笔者会在另一篇博文中,讨论使用Postgres的Range技术进行的实现。

简单而言,如果将查询细化到每趟车每个席位的状态,其实这个查询的工作量并不是很大。因为从应用体验的平衡而言,可能并不需要精确的统计剩余空位,只需要提供一个宽松-紧张-售罄的状态就可以了,这样可以分层次的降低对系统统计和计算造成的负载。

以上,就是笔者对于12306核心业务的分析和思考。这样看来,确实也不像想象中的那么复杂和严峻。只需要做好合理的需求管理、架构设计和工程优化,现有的互联网技术体系完全可以很好的予以实现和支撑。

系统安全

在系统安全方面,作为一个外部观察者,而且并不是专业的信息系统安全方面的专业人士,笔者对12306和系统安全的认识,也主要还是从开发者的角度出发达成的。

首先说明,笔者在本项目中的设计操作,对于12306系统来说,都是一种“非正常”的应用方式,理论上是应该予以限制的。

用户认证

作为本项目的一个设定,笔者的程序和流程,没有使用任何一种认证方式和信息(但使用了稀疏请求的方式绕过了限流)。虽然作为开发和调试,或者公共信息服务是非常方便的,但从另一个角度,也给系统造成了一定的安全隐患,起码是更多的性能负载。

针对这个问题,可以有两种优化方案: 用户认证和API密钥。

用户认证很好理解,12306假设这些接口的请求都来自浏览器和APP客户端,在使用之前都进行了认证,并对带有时间限制的访问Token进行验证。如果12306考虑到需要为其他系统提供接口服务,就应当建立一个开发支持体系,然后给接入应用颁发API密钥,针对这些接入应用进行验证。

这两种方式,都可以大大的减少无效和恶意的接口请求的处理负载,为正常信息和数据服务提供更好的保证。

限流

项目中爬虫程序的运行过程,确实遇到了限流的情况,虽然其限流的机制和策略从外部来不好分析,但笔者还是应用了加入请求操作间隔的方式,部分解决了这个问题。

这起码说明12306看到了这个问题,并采取了相关的措施进行了缓解。

笔者认为,也许有更好的方式,在不影响正常应用和体验的情况下,阻止非正常的请求。就是前面提到的用户或者客户端认证,所有的请求,都应当由认证过的客户端系统或者组件发起,并且进行合理的验证,通过后才提供数据和服务。

验证码

这里顺便讨论一下。就是12306提供的验证码系统,实在是有点“反人类”,给普通用户的使用造成了很高的门槛。

我也来爬一爬12306 - Day7 总结和思考在本章节中,笔者根据自己在项目构造和操作的过程中。对12306的设计和实

可能是12306的设计者有点“矫枉过正”,也可能是他们觉得这是一种更有效的限流和过滤措施。通过很不容易达成的验证码输入,和很长的验证时间,他们大大的降低了用户的应用体验,延长了用户的操作时间,成功的削峰了请求数量,降低了系统的性能负载。

用户认证方式

这一部分是后来补充的,因为本来和实践项目内容没有直接的关联。但在本文成文后,笔者觉得这里可能会有一些问题,后来通过研究12306网站的登录认证过程,确实证实了前面的判断。简单说结论:就是如果没有理解和判断错误的话,当前(2024年8月)的12306网站的用户认证过程实现方式,有相当重大的缺陷。由于相关的内容笔者觉得比较敏感,这里就不再详细展开说明,有兴趣和能力的读者,可以自己研究,相信可以得出和笔者相同的结论。

对比案例

在本系列项目和文章快要收尾的时候,笔者看到了这个:

cnrail.geogv.org

我也来爬一爬12306 - Day7 总结和思考在本章节中,笔者根据自己在项目构造和操作的过程中。对12306的设计和实

从铁路专业和业务的角度来看,这个网站提供了更专业和全面的业务信息和数据,而且使用了非常专业和直观的方式来进行呈现。笔者本来以为,这是中国铁路行业内部提供的专业信息服务,但经过分析发现,它好像就是一个专业的铁路工业爱好者设计和运维的系统。如果是一个铁路爱好者,这个网站应该比12306更加适合你。难道这就是传说中“高手在民间”嘛?

例如,在这个系统中,包含了以下专业和关联的信息:

  • 线路地图,基于OpenStreetMap
  • 线路,包括类型,子线路,车站里程
  • 车站,包括车站和线路间的关系,车站开展的业务,所属路局和线路
  • 车站相关的车次,包括车次类型,运营时间,里程
  • 车次和时刻表/线路图

和能够在12306上访问的信息相比,这里的信息更加完整和专业。但是,虽然这个网站用了cnrail.geogv.org这个域名,但实际上应该不是官方网站,里面也提到相关的数据主要来自WIKI,而不是官方接口或者权威数据,只能作为参考。

小结

在本章节中,笔者根据自己在项目构造和操作的过程中。对12306的设计和实现技术进行了研讨。包括了作为普通用户使用12306过程中遇到和发现的问题,系统架构设计和实现方面的技术问题,网页和接口性能问题,系统安全问题等多个方面,并探讨了对12306性能设计方面的思考。

转载自:https://juejin.cn/post/7407259722430234635
评论
请登录