5.1 特辑|为何显示有票你却抢不到?技术揭秘 12306 如何保证车票不超卖

869 阅读11分钟

五一抢票,靠的可不仅是运气哦!点击查看,为你揭开背后的关键技术~~

摘要:每逢节假日,当全国几百万小伙伴同时查票、订票时,12306 是如何保证余票显示准、车票不超卖的?为你揭开背后的关键技术:数据强一致性。

本文分享自华为云社区《5.1特辑 | 为什么显示有票你却抢不到?技术揭秘12306如何保证车票不超卖》,原文作者:技术火炬手 。

眼瞅着五一小长假就要来了,小云还在为没有抢到高铁票心急如焚。

她再次打开 APP,一遍遍刷新,发现还有余票 2 张,赶紧下单,一顿操作猛如虎,结果还是没抢到。

但仔细想想也能理解:从勾选乘车人到正式下单,整个流程至少需要 10 秒,如果“见者有份”,恐怕这两个座位大家要挤挤共用了。

那么,每逢节假日,当全国几百万小伙伴同时查票、订票时,12306 是如何保证余票显示准、车票不超卖的?

于是,按捺不住好奇心的我们,进行了一番深入研究。原来问题背后隐藏着一个分布式数据库领域极其关键的技术——数据强一致性保障

1. 什么是强一致?

在介绍概念之前,我们不妨先来模拟一场球赛直播。

假设笔者做了一款 APP,后台使用上图的主从数据库。比分写入主节点,从节点分担用户查询。比赛中,Alice 惊呼比赛结束,Bob 闻声刷新 APP,却显示比赛仍在继续!Bob 体验到了明显的数据不一致,于是默默给 APP 打了个差评……

那么,产生不一致的原因究竟是什么?

异步复制时,主节点不等待从节点写入就直接返回了。由于网络延迟等原因,从节点无法保证更新时间。Alice 和 Bob 明明在同时同地查询同一系统,得到正确结果却有先有后。其实这就是典型的弱一致性。

实际上,为解决单点故障、增强吞吐性能,分布式数据库内部都会对同一份数据进行复制,把冗余副本分散保存到不同节点上。简单的异步复制只能构建出弱一致系统,很难满足业务要求。

那么,究竟什么样的一致性才靠谱?有哪些类别?下面我们就来认识这个神秘家族!

1.1 强一致性/线性一致性(Linearizability)

强一致性/线性一致性是一致性的最高标准,实现难度最高。核心要求是:一旦写操作完成,随后任意客户端的查询都必须返回这一新值。以下图为例,一旦“写入 b”完成,必须保证读到 b。而写入过程中,认为值的跳变可能发生在某一瞬间,因此读到 a 或 b 都是可能的。

从业务角度来说,强一致性带来的体验简直可以用丝滑来形容!因为它内部的数据“仿佛”只有一份,即使并发访问不同节点,每个操作也都能原子有序。正因如此,强一致数据库在业务架构中往往被用在关键位置。

etcd 是强一致俱乐部里的元老。它基于 Raft 共识算法,真正实现了强一致,也因此在 Leader 选举、服务发现等场景起到重要作用。GaussDB(for Redis)作为一款分布式云数据库,凭借多年潜心打磨,也是强一致的代言人。

1.2 顺序一致性(SequentialConsistency)

顺序一致性弱于线性一致,不保证操作的全局时序,但保证每个客户端操作能按顺序被执行。下图中,A 先写 x=10,后写 x=20;B 先写 x=99,后写 x=999。当 C 读取时,顺序一致性保证了 10 先于 20 被读到、99 先于 999 被读到。

Zookeeper 基于 ZAB 协议,所有写操作都经由主节点协调,实现了顺序一致性。

1.3 因果一致性(CausalConsistency)

​进一步放宽要求,因果一致性只对并发访问中具有因果关系的操作保序。例如:

​A 写入 3,B 读到后乘以 100 再更新它。在这个场景下,由于“A 写入 3”与“B 写入 300”有着明确因果关系,因果一致性保证 300 晚于 3 被读到。

因果一致性多用于各种博客的评论系统、社交软件等。毕竟,我们回复某条评论的内容,不应早于评论本身被显示出来。

1.4 最终一致性(EventualConsistency)

它指的是停止写入并等待一段时间,最终所有客户端都能读到相同的新数据,但具体时限不作保证。许多分布式数据库都满足最终一致性,如 MySQL 主从集群等。

然而,这其实是一个非常弱的保证。由于不确定系统内部过多久才能收敛一致,在此之前,用户随时可能体验到数据不一致。因此最终一致性有天然的局限性,经常会给业务逻辑带来混乱。

1.5 弱一致性(WeakConsistency)

​说弱一致性最为“厚脸皮”也不为过,因为它连数据写入后将来被读到都不能保证。弱一致性实现技术门槛低,应用场景也不多。严格来说,单纯的开源 Redis 主从集群就属于这一类别。

OK,一致性家族的各位成员已经跟大家打过照面。显然,一致性越强的数据库系统,能够支撑的业务场景越多。有的业务同学小声说,强一致技术再牛,可我业务简单,不用也没关系吧。实际上恰恰相反:

强一致不仅仅是技术问题,它更是一个不可忽视的业务需求、运维需求!

接下来我们就先来聊一聊:业务上那些只有强一致才能搞定的事儿!

2.强一致是业务刚需

2.1 计数器/限流器

计数服务是典型的强一致应用场景。电商在秒杀活动中,往往会搭建 Redis 主从集群给下层 MySQL 做缓存。因为要抗住超大流量,需要 Redis 的计数器功能做限流。简单讲,我们初始化 counter=5000。随后每次业务访问都执行 DECR 命令,当 counter 归零就阻塞后续请求。此外,每隔一个时间段重置 counter=5000,通过这样的手段来实现“细水长流”。

然而,完美的假设还不够!

开源 Redis 采用异步复制,如遇网络不畅,经常发生主节点复制 buffer 堆积。这将导致从节点 counter 偏大很多。此时,一旦主节点宕机,切换到从节点继续执行 DECR 命令,压力很容易超出阈值,全部落到下层脆弱的 MySQL,随时可能引起系统雪崩!

因此,在限流场景下,只有真正的强一致才能提供可靠的计数器。

2.2 Leader 选举

当业务部署的节点较多、可用性要求高时,往往要用到 Leader 选举。etcd 作为强一致 KV 存储,能完美 cover 这一场景。etcd 依赖两大功能实现 Leader 选举:

1)TTL:给 key 设置有效期,到期后 key 自动删除。

2)CAS:对 key 的原子操作 (这一功能只有强一致数据库才能实现) ,使用 etcd 搭建 Leader 选举服务的设计如下:

1)约定 key,用于选举时抢占。其 value 用于保存 Leader 节点名称。

2)约定 TTL,用于给 key 设定有效期。

3)启动时:每个参与节点尝试 cas create key&设置 TTL。在 etcd 集群强一致 CAS 机制保障下,只有一个节点能执行成功。该节点成为 Leader 并将名称写入 value;其余节点成为 Follower。

4)运行中:每个节点定期 TTL/2 尝试 getkey,将 value 与自身名称对比:

如相同,说明已是 Leader,此后只需每隔 TTL/2 刷新 key 的 TTL 即可。

如不同,说明是 Follower,接下来要每隔 TTL/2 执行 cas create key&设置 TTL。

5)当 Leader 节点异常退出,无法刷新 TTL,key 会很快过期。此时,其余 Follow 之中便会有新的 Leader 产生。

从原理上能看出,强一致能力是 Leader 选举的根基。类似的“刚需”业务场景还有很多,强一致不可或缺。

好了,业务上的事儿就聊到这里,接下来让我们听听运维怎么说。

3. 强一致为运维减负

3.1 辅助组件架构复杂、问题难定位

后台架构中,MySQL 主从热备也是常见的部署方式。由于数据保存在本地磁盘中,当主库发生严重故障,仅仅依靠 MySQL 自身同步机制,主从切换后无法保证所提供数据与之前状态完全一致。于是出现了“重量级”的辅助组件——MHA(Master High Availability)。我们来看一下它的部署方式:

MHA 负责在故障转移过程中,帮助从库尽量追平主库最新状态,提供近似一致的数据。但这一能力需要额外的 Manager 节点,同时还要在每一个 MySQL 节点上部署 Node 服务。故障切换时,Manager 先为从库补充落后的数据,再通过切换 VIP 恢复用户访问,过程可能长达数十秒。

这样的 HA 系统部署和后期维护都很复杂。如未能顺利执行故障切换或发生数据丢失,运维面临的场面都将很棘手。其实运维同学何尝不希望手中的系统稳定运行呢?要是数据库自身能提供强一致保障,何苦再依赖复杂的辅助组件!

读到这里,对强一致的看法,相信各位读者心里已经有了自己的一杆秤。让我们再一次划重点:

强一致不仅仅是技术问题,它更是一个不可忽视的业务需求、运维需求!

从产品选型角度出发,开源 Redis 提供的一致性保证很弱。而 etcd 虽有强一致能力,但它单点写入性能不足,也未能提供 hash、sorted set、stream 等诱人的数据结构……

此时,有人会陷入纠结,到底选择哪一种,GaussDB(for Redis)应声而起——我,都可以。

4.GaussDB(for Redis)与强一致

自设计之初,GaussDB(for Redis)(后文简称高斯 Redis)给自己的定位就是“强一致 KV 数据库”,因此彻底摒弃了开源 Redis 的异步复制机制。借助华为云 GaussDB 系列先进的“存算分离”架构,将全量数据下沉到强一致存储层(DFV Pool),从核心技术上超越了传统开源产品的极限。

让我们来一起认识一下高斯 Redis 的强悍:

用户购买的实例作为一个整体,提供强一致 KV 存储。

用户业务统一通过 Proxy 集群接入高斯 Redis,不用考虑内部复杂逻辑。多点并发访问实例,读写操作满足强一致性,再也不必担心开源 Redis 异步复制的不一致隐患。

计算层智能处理数据分片、动态故障转移,将数据全量下沉到共享存储池。

cfgsvr 集群统一管理 ShardServer 节点,自动对海量数据进行分片。并能够在故障场景实现秒级接管,严格防止任何中间态下的数据不一致。

存储层通过 RDMA 高速网络实现高性能分布式数据持久化,三副本冗余保证强一致、零丢失。

DFV Pool 是强一致、高性能的分布式存储系统。这是华为内部自研的公司级 Data Lake,它能够稳定支撑各类全栈数据服务。高斯 Redis 突破了开源 Redis“小格局”的内存架构,将数据全量下沉,基于 DFV Pool 强大的一致性保障能力,给用户业务带来更广阔的拓展空间。

5. 结语

试想,当处在关键位置的数据库“不给力”,业务层就要忙于为系统添加复杂、易出错的一致性保障逻辑。与此同时,运维还要时刻担心故障引发的数据落后问题…这样的系统真的“香”吗?

试想如果 12306 显示你抢到票了,上车的时候却发现有四五个人和你是同一个座位,太诡异了。

所以,专业的事情交给专业的团队来做。GaussDB(for Redis)自研发初期就持续关注数据强一致性设计,借助强一致存储池 DFV Pool,它可以提供真正强一致的海量 KV 存储解决方案。

点击关注,第一时间了解华为云新鲜技术~