The (Near) Future of Database | TiDB DevCon 2019

在 TiDB DevCon 2019 上,我司联合创始人兼 CTO 黄东旭分享了对数据库行业大趋势以及将来数据库技术的见解。如下是演讲实录,enjoy~git

我司联合创始人兼 CTO 黄东旭

你们今天在这里看到了 TiDB 社区用户实践分享和咱们本身的一些技术进展和展望,还有很是好玩的 Demo Show,正好在大会结束以前,我想跟你们聊一聊我心目中将来的 Database 应该是一个什么样子。github

其实咱们并非一个特别擅长发明名词的公司,我记得咱们第一次去用 HTAP 这个词的时候,应该是 2016 左右。在使用 HTAP 这个词的时候,咱们市场部同事还跟咱们说 HTAP 这个词历来没人用过,都是论文里的词,你们都不知道,你把大家公司的产品定位改为这个别人都不知道怎么办?咱们后来仔细想,仍是以为 HTAP 这个方向是一个更加适合咱们的方向,因此仍是选了 HTAP 这个词。如今很欣喜的看到如今各类友商、后来的一些数据库,都开始争相说 HTAP,就是说获得了同行的承认。算法

那么在 HTAP 的将来应该是一个什么样子,我但愿可以在今年这个 Talk 里面先说一说,可是这个题目起的有点不太谦虚,因此我特意加了一个「Near」, 分享一下这一年、两年、三年咱们想作什么,和对行业大趋势的展望。数据库

图1

今天咱们的分享的一个主题就是:「咱们只作用户想要的东西,并非要去作一个完美的东西」。其实不少工程师包括咱们本身,都会有一个小小的心理的洁癖,就是想要作一个超级快、超级牛的东西,可是作出来一个数据库,单机跑分一百万 TPS ,其实用户实际业务就须要 3000,而后全部的用户还会说我须要这些东西,好比须要 Scalability(弹性扩展), Super Large 的数据量,最好是个人业务一行代码都不用改,并且 ACID 可以彻底的知足,怎么踹都踹不坏,机器坏了能够高可用,业务层彻底不用动, 另外能够在跑 OLTP 的同时,彻底不用担忧任何资源隔离地跑 OLAP(这里不是要说你们的愿望不切实际,而是很是切实际,咱们也以为数据库自己就应该是这样的。因此你们记住这几个要点,而后慢慢看 TiDB 究竟是不是朝着这个方向发展的)。本质上来讲用户的需求就是「大一统」。看过《魔戒》的同窗都知道这句话 :ONE RING TO RULE THEM ALL,就是一套解决方案去解决各类问题缓存

过去不少,包括一些行业的大佬以前说在各类环境下都要出一个数据库来解决特定的一个问题,可是其实看上去咱们想走的方案仍是尽量在一个平台里面,尽量大范围去解决用户的问题。由于不一样的产品之间去作数据的交互和沟通,实际上是蛮复杂的。网络

图 2 理想中的「赛道」

这张图(图 2)什么意思呢?就是不少人设计系统的时候,老是会陷入跑分思惟,就是说这个东西在实验室或者说在一个特定的 Workload 下,跑得巨快无比。若是你们去看一下大概 2000 年之后关于数据库的论文,不少在作一个新的模型或者新的系统的时候,都会说 TPCC 可以跑到多大,而后把 Oracle 摁在地上摩擦,这样的论文有不少不少不少。可是你们回头看看 Oracle 仍是王者。因此大多数实验室的产品和工程师本身作的东西都会陷入一个问题,就是想象中的个人赛道应该是一个图 2 那样的,但实际上用户的业务环境是下面这样的(图 3)。不少你们在广告上看到特别牛的东西,一放到生产环境或者说放到本身的业务场景里面就不对了,而后陷入各类各样的比较和纠结的烦恼之中。数据结构

图 3 实际上用户的业务环境

TiDB 的定位或者说咱们想作的事情,并非在图 2 那样的赛道上,跑步跑得巨快,全世界没人在短跑上跑得过我,咱们不想作这样。或者说,咱们其实也能跑得很快,可是并不想把全部优点资源全都投入到一个用户可能一生都用不到的场景之中。咱们其实更像是作铁人三项的,由于用户实际应用场景可能就是一个土路。这就是为何 TiDB 的设计放在第一位的是「稳定性」架构

咱们一直在想能不能作一个数据库,怎么踹都踹不坏,而后全部的异常的情况,或者它的 Workload  都是可预期的。我以为不少人远远低估了这个事情的困难程度,其实咱们本身也特别低估了困难程度。大概 4 年前出来创业的时候,咱们就是想作这么一个数据库出来,我跟刘奇、崔秋三我的也就三个月作出来了。可是到如今已经 4 年过去了,咱们的目标跟当年仍是如出一辙。不忘初心,不是忘不掉,而是由于初心还没达到,怎么忘?其实把一个数据库作稳,是很难很难的。并发

图 4 近年来硬件的发展

并且咱们这个团队的平均年龄可能也就在二十到三十岁之间,为何咱们如此年轻的一个团队,可以去作数据库这么古老的一件事情。其实也是得益于整个 IT 行业这几年很是大的发展。图 4 是这几年发展起来的 SSD,内存愈来愈大,万兆的网卡,还有各类各样的多核的 CPU,虚拟化的技术,让过去不少不可能的事情变成了可能。框架

举一个例子吧,好比极端一点,你们可能在上世纪八九十年代用过这种 5 寸盘、3 寸盘,我针对这样的磁盘设计一个数据结构,如今看上去是个笑话是吧?由于你们根本没有人用这样的设备了。在数据库这个行业里面不少的假设,在如今新的硬件的环境下其实都是不成立的。好比说,为何 B-Tree 就必定会比 LSM-Tree 要快呢?不必定啊,我跑到 Flash 或者 NVMe SSD 、Optane 甚至将来的持久化内存这种介质上,那数据结构设计彻底就发生变化了。过去可能须要投入不少精力去作的数据结构,如今暴力就行了。

图 5 近年来软件变革

同时在软件上也发生了不少不少的变革,图 5 左上角是 Wisckey 那篇论文里的一个截图,还有一些分布式系统上的新的技术,好比 2014 年 Diego 发表了 Raft 这篇论文,另外 Paxos 这几年在各类新的分布式系统里也用得愈来愈多。

因此我以为这几年咱们遇上了一个比较好的时代,就是无论是软件仍是硬件,仍是分布式系统理论上,都有了一些比较大突破,因此咱们基础才可以打得比较好

图 6 Data Type

除了有这样的新的硬件和软件以外,我以为在业务场景上也在发生一些比较大变化。过去,可能十年前就是我刚开始参加工做的时候,线上的架构基本就是在线和离线两套系统,在线是 Oracle 和 MySQL,离线是一套 Hadoop 或者一个纯离线的数据仓库。但最近这两年愈来愈多的业务开始强调敏捷、微服务和中台化,因而产生了一个新的数据类型,就是 warm data,它须要像热数据这样支持 transaction、支持实时写入,可是须要海量的数据都能存在这个平台上实时查询, 并非离线数仓这种业务

因此对 warm data 来讲,过去在 TiDB 以前,实际上是并无太好的办法去很优雅的作一层大数据中台架构的,「the missing part of modern data processing stack」,就是在 warm data 这方面,TiDB 正好去补充了这个位置,因此才能有这么快的增加。固然这个增加也是得益于 MySQL 社区的流行。

图 7 应用举例

想象一下,咱们若是在过去要作这样很简单的业务(图 7),好比在美国的订单库跟在中国的订单库可能都是在不一样的数据库里,用户库多是另一个库,而后不一样的业务多是操做不一样的库。若是我想看看美国的消费者里面有哪些在中国有过消费的,就是这么一条 SQL。过去若是没有像 TiDB 这样的东西,你们想象这个东西该怎么作?

图 8 过去的解决方案

假如说这两边的数据量都特别大,而后已经分库分表了。过去可能只能次日才能够看到前一天的数据,由于中间好比说一个 T+1  要作一个 ETL 到一个 data ware house 里。或者厉害一点的架构师可能会说,我能够作一套实时的 OLAP 来作这个事情,怎么作呢?好比说 MySQL 中间经过一个 MQ 再经过 Hadoop 作一下 ETL,而后再导到 Hadoop 上作一个冷的数据存储,再在上面去跑一个 OLAP 作实时的分析。先不说这个实时性到底有多「实时」,你们仔细算一算,这套架构须要的副本数有多少,好比 M 是个人业务数,N 是每个系统会存储的 Replica,拍脑壳算一下就是下面这个数字(图 9 中的 R )。

图 9 过去解决方案里须要的 Replica 数量

因此你们其实一开始在过去说,TiDB 这个背后这么多 Replica  很差,但其实你想一想,你本身在去作这个业务的时候,你们在过去又能怎么样呢?因此我以为 TiDB 在这个场景下去统一一个中台,是一个大的趋势。今天在社区实践分享上也看到不少用户都要提到了 TiDB 在中台上很是好的应用。

图 10 如今的解决方案

回顾完行业和应用场景近年来的一些变化以后,咱们再说说将来。假设要去作一个面向将来的数据库,会使用哪些技术?

1. Log is the new database

第一个大的趋势就是日志,「log is the new database」 这句话应该也是业界的一个共识吧。如今若是有一个分布式数据库的复制协议,仍是同步一个逻辑语句过去,或者作 binlog 的复制,那其实还算比较 low 的。

图 11 Log is the new database

上面图 11 左半部分是 Hyper,它是慕尼黑工业大学的一个实验性数据库项目,它作了一些分析,第一个柱形是正常的 SQL 语句的执行时间,好比说直接把一语句放到另一个库里去执行,耗时这么多。第二个柱形是用逻辑日志去存放,耗时大概能快 23%,第三个柱形能看到若是是存放物理日志能快 56%。因此你们仔细想一想,TiDB 的架构里的 TiFlash 其实同步的是 Raft 日志,而并非同步 Binlog 或者其余的

上面图 11 右半部分是 Aurora,它的架构就不用说了,同步的都是 redo log 。其实他的好处也很明显,也比较直白,就是 I/O 更小,网络传输的 size 也更小,因此就更快。

而后在这一块 TiDB 跟传统的数据库有点不同的就是,其实若是不少同窗对 TiDB 的基础架构不太理解的话就以为, Raft 不是一个必定要有 Index 或者说是必定强顺序的一个算法吗?那为何能作到这样的乱序的提交?其实 TiDB 并非单 Raft 的架构,而是一个多 Raft 的架构,I/O 能够发生在任何一个 Raft Group 上。传统的单机型数据库,就算你用更好的硬件都不可能达到一个线性扩展,由于不管怎么去作,都是这么一个架构不可改变。好比说我单机上 Snapshot 加 WAL,无论怎么写, 老是在 WAL 后面加,I/O 老是发生在这。但 TiDB 的 I/O 是分散在多个 Raft Group、多个机器上,这是一个很本质的变化,这就是为何在一些场景下,TiDB 可以获取更好的吞吐。

2. Vectorized

第二个大趋势是全面的向量化。向量化是什么意思?我举个简单的例子。好比我要去算一个聚合,从一个表里面去求某一列的总量数据,若是我是一个行存的数据库,我只能把这条记录的 C 取出来,而后到下一条记录,再取再取再取,整个 Runtime 的开销也好,还有去扫描、读放大的每一行也好,都是颇有问题的。可是若是在内存里面已是一个列式存储,是很紧凑的结构的话,那会是很是快的。

图 12 TiDB 向量化面临的挑战

这里面其实也有一些挑战。咱们花了大概差很少 2018 年一年的时间去作向量化的改造,其实还挺难的。为何?首先 TiDB SQL 引擎是用了 Volcano 模型,这个模型很简单,就是遍历一棵物理计划的树,不停的调 Next,每一次 Next 都是调用他的子节点的 Next,而后再返回结果。这个模型有几个问题:第一是每一次都是拿一行,致使 CPU 的 L一、L2 这样的缓存利用率不好,就是说没有办法利用多 CPU 的 Cache。第二,在真正实现的时候,它内部的架构是一个多级的虚函数调用。你们知道虚函数调用在 Runtime 自己的开销是很大的,在《MonetDB/X100: Hyper-Pipelining Query Execution》里面提到,在跑 TPC-H 的时候,Volcano 模型在 MySQL 上跑,大概有 90% 的时间是花在 MySQL 自己的 Runtime 上,而不是真正的数据扫描。因此这就是 Volcano 模型一个比较大的问题。第三,若是使用一个纯静态的列存的数据结构,你们知道列存特别大问题就是它的更新是比较麻烦的, 至少过去在 TiFlash 以前,没有一个列存数据库可以支持作增删改查。那在这种状况下,怎么保证数据的新鲜?这些都是问题。

图 13 TiDB SQL 引擎向量化

TiDB 已经迈出了第一步,咱们已经把 TiDB SQL 引擎的 Volcano 模型,从一行一行变成了一个 Chunk 一个 Chunk,每一个 Chunk 里面是一个批量的数据,因此聚合的效率会更高。并且在 TiDB 这边作向量化以外,咱们还会把这些算子推到 TiKV 来作,而后在 TiKV 也会变成一个全向量化的执行器的框架。

3. Workload Isolation

另一个比较大的话题,是 Workload Isolation。今天咱们在演示的各类东西都有一个中心思想,就是怎么样尽量地把 OLTP 跟 OLAP 隔离开。这个问题在业界也有不一样的声音,包括咱们的老前辈 Google Spanner,他们实际上是想作一个新的数据结构,来替代 Bigtable-Like SSTable 数据结构,这个数据结构叫 Ressi,你们去看 2018 年 《Spanner: Becoming a SQL System》这篇 Paper 就能看到。它其实表面上看仍是行存,但内部也是一个 Chunk 变成列存这样的一个结构。但咱们以为即便是换一个新的数据结构,也没有办法很好作隔离,由于毕竟仍是在一台机器上,在同一个物理资源上。最完全的隔离是物理隔离。

图 14 TiFlash 架构

咱们在 TiFlash 用了好几种技术来去保证数据是更新的。一是增长了 Raft Leaner,二是咱们把 TiDB 的 MVCC 也实如今了 TiFlash 的内部。第三在 TiFlash 这边接触了更新(的过程),在 TiFlash 内部还有一个小的 Memstore,来处理更新的热数据结果,最后查询的时候,是列存跟内存里的行存去 merge 并获得最终的结果。TiFlash 的核心思想就是经过 Raft 的副原本作物理隔离

这个有什么好处呢?这是咱们今天给出的答案,可是背后的思考,究竟是什么缘由呢?为何咱们不能直接去同步一个 binlog 到另一个 dedicate 的新集群上(好比 TiFlash 集群),而必定要走 Raft log?最核心的缘由是,咱们认为 Raft log 的同步能够水平扩展的。由于 TiDB 内部是 Mult-Raft 架构,Raft log 是发生在每个 TiKV 节点的同步上。你们想象一下,若是中间是经过 Kafka 沟通两边的存储引擎,那么实时的同步会受制于中间管道的吞吐。好比图 14 中绿色部分一直在更新,另外一边并发写入每秒两百万,可是中间的 Kafka 集群可能只能承载 100 万的写入,那么就会致使中间的 log 堆积,并且下游的消费也是不可控的。而经过 Raft 同步, Throughput 能够根据实际存储节点的集群大小,可以线性增加。这是一个特别核心的好处

4. SIMD

说完了存储层,接下来讲一说执行器。TiDB 在接下来会作一个很重要的工做,就是全面地 leverage  SIMD 的计算。我先简单科普一下 SIMD 是什么。

图 15 SIMD 原理举例(1/2)

如图 15,在作一些聚合的时候,有这样一个函数,我要去作一个求和。正常人写程序,他就是一个 for 循环,作累加。可是在一个数据库里面,若是有一百亿条数据作聚合,每一次执行这条操做的时候,CPU 的这个指令是一次一次的执行,数据量特别大或者扫描的行数特别多的时候,就会很明显的感觉到这个差异。

图 16 SIMD 原理举例(2/2)

现代的 CPU 会支持一些批量的指令,好比像 _mm_add_epi32,能够一次经过一个32 位字长对齐的命令,批量的操做 4 个累加。看上去只是省了几个 CPU 的指令,但若是是在一个大数据量的状况下,基本上能获得 4 倍速度的提高。

顺便说一句,有一个很大的趋势是 I/O 已经不是瓶颈了,你们必定要记住我这句话。再过几年,若是想去买一块机械磁盘,除了在那种冷备的业务场景之外,我相信你们可能都要去定制一块机械磁盘了。将来必定 I/O 不会是瓶颈,那瓶颈会是什么?CPU。咱们怎么去用新的硬件,去尽量的把计算效率提高,这个才是将来我以为数据库发展的重点。好比说我怎么在数据库里 leverage GPU 的计算能力,由于若是 GPU 用的好,其实能够很大程度上减小计算的开销。因此,若是在单机 I/O 这些都不是问题的话,下一个最大问题就是怎么作好分布式,这也是为何咱们一开始就选择了一条看上去更加困难的路:我要去作一个 Share-nothing 的数据库,并非像 Aurora 底下共享一个存储。

5. Dynamic Data placement

图 17 Dynamic Data placement (1/2)分库分表方案与 TiDB 对比

在今天你们其实看不到将来十年数据增加是怎样的,回想十年前你们能想到如今咱们的数据量有这么大吗?不可能的。因此新的架构或者新的数据库,必定要去面向咱们未知的 Scale 作设计。好比你们想象如今有业务 100T 的数据,目前看可能还挺大的,可是有没有办法设计一套方案去解决 1P、2P 这样数据量的架构?在海量的数据量下,怎么把数据很灵活的分片是一个很大的学问

为何分库分表在对比 TiDB 的时候,咱们会以为分库分表是上一代的方案。这个也很好理解,核心的缘由是分库分表的 Router 是静态的。若是出现分片不均衡,好比业务可能按照 User ID 分表,可是发现某一地方/某一部分的 User ID 特别多,致使数据不均衡了,这时 TiDB 的架构有什么优点呢?就是 TiDB 完全把分片这个事情,从数据库里隔离了出来,放到了另一个模块里。分片应该是根据业务的负载、根据数据的实时运行状态,来决定这个数据应该放在哪儿。这是传统的静态分片不能相比的,无论传统的用一致性哈希,仍是用最简单的对机器数取模的方式去分片(都是不能比的)

在这个架构下,甚至将来咱们还能让 AI 来帮忙。把分片操做放到 PD 里面,它就像一个 DBA 同样,甚至预测 Workload 给出数据分布操做。好比课程报名数据库系统,系统发现可能明天会是报名高峰,就事先把数据给切分好,放到更好的机器上。这在传统方案下是都须要人肉操做,其实这些事情都应该是自动化的。

图 18 Dynamic Data placement (2/2)

Dynamic Data placement 好处首先是让事情变得更 flexible ,对业务能实时感知和响应。另外还有一点,为何咱们有了 Dynamic Placement 的策略,还要去作 Table Partition(今天上午申砾也提到了)?Table Partition 在背后实现其实挺简单的。至关于业务这边已经告诉咱们数据应该怎么分片比较好,咱们还能够作更多针对性的优化。这个 Partition 指的是逻辑上的 Partition ,是可能根据你的业务相关的,好比说我这张表,就是存着 2018 年的数据,虽然我在底下仍是 TiDB 这边,经过 PD 去调度,可是我知道你 Drop 这个 Table 的时候,必定是 Drop 这些数据,因此这样会更好,并且更加符合用户的直觉。

但这样架构仍然有比较大的挑战。固然这个挑战在静态分片的模型上也都会有。好比说围绕着这个问题,咱们一直在去尝试解决怎么更快的发现数据的热点,好比说咱们的调度器,若是最好能作到,好比忽然来个秒杀业务,咱们立刻就发现了,就赶忙把这块数据挪到好的机器上,或者把这块数据赶忙添加副本,再或者把它放到内存的存储引擎里。这个事情应该是由数据库自己去作的。因此为何咱们这么期待 AI 技术可以帮咱们,是由于虽然在 TiDB 内部,用了不少规则和方法来去作这个事情,但咱们不是万能的。

6. Storage and Computing Seperation

图 19 存储计算分离

还有大的趋势是存储计算分离。我以为如今业界有一个特别大的问题,就是把存储计算分离给固化成了某一个架构的特定一个指代,好比说只有长的像 Aurora 那样的架构才是存储计算分离。那么 TiDB 算存储计算分离吗?我以为其实算。或者说存储计算分离本质上带来的好处是什么?就是咱们的存储依赖的物理资源,跟计算所依赖的物理资源并不同。这点其实很重要。就用 TiDB 来举例子,好比计算可能须要不少 CPU,须要不少内存来去作聚合,存储节点可能须要不少的磁盘和 I/O,若是全都放在一个组件里 ,调度器就会很难受:我到底要把这个节点做为存储节点仍是计算节点?其实在这块,可让调度器根据不一样的机型(来作决定),是计算型机型就放计算节点,是存储型机型就放存储节点。

7. Everything is Pluggable

图 20 Everything is Pluggable

今天因为时间关系没有给你们演示的插件平台。将来 TiDB 会变成一个更加灵活的框架,像图 20 中 TiFlash 是一个 local storage,咱们其实也在秘密研发一个新的存储的项目叫 Unitstore,可能明年的 DevCon 就能看到它的 Demo 了。在计算方面,每一层咱们将来都会去对外暴露一个很是抽象的接口,可以去 leverage 不一样的系统的好处。今年我其实很喜欢的一篇 Paper 是 F1 Query 这篇论文,基本表述了我对一个大规模的分布式系统的期待,架构的切分很是漂亮。

8. Distributed Transaction

图 21 Distributed Transaction(1/2)

说到分布式事务,我也分享一下个人观点。目前看上去,ACID 事务确定是必要的。咱们仍然尚未太多更好的办法,除了 Google 在这块用了原子钟,Truetime 很是牛,咱们也在研究各类新型的时钟的技术,可是要把它推广到整个开源社区也不太可能。固然,时间戳,无论是用硬件仍是软件分配,仍然是咱们如今能拥有最好的东西, 由于若是要摆脱中心事务管理器,时间戳仍是很重要的。因此在这方面的挑战就会变成:怎么去减小两阶段提交带来的网络的 round-trips?或者若是有一个时钟的 PD 服务,怎么能尽量的少去拿时间戳?

图 22 Distributed Transaction(2/2)

咱们在这方面的理论上有一些突破,咱们把 Percolator 模型作了一些优化,可以在数学上证实,能够少拿一次时钟。虽然咱们目前尚未在 TiDB 里去实现,可是咱们已经把数学证实的过程已经开源出来了,咱们用了 TLA+ 这个数学工具去作了证实。此外在 PD 方面,咱们也在思考是否是全部的事务都必须跑到 PD 去拿时间戳?其实也不必定,咱们在这上面也已有一些想法和探索,可是如今尚未成型,这个不剧透了。另外我以为还有一个很是重要的东西,就是 Follower Read。不少场景读多写少,读的业务压力不少时候是要比写大不少的,Follower Read 可以帮咱们线性扩展读的性能,并且在咱们的模型上,由于没有时间戳 ,因此可以在一些特定状况下保证不会去牺牲一致性。

9. Cloud-Native Architecture

图 23 Cloud-Native

另一点就是 Cloud-Native。刚刚中午有一个社区小伙伴问我,大家为何不把多租户作在 TiDB 的系统内部?**我想说「数据库就是数据库」,它并非一个操做系统,不是一个容器管理平台。咱们更喜欢模块和结构化更清晰的一个作事方式。**并且 Kubernetes 在这块已经作的足够好了 ,我相信将来 K8s 会变成集群的新操做系统,会变成一个 Linux。好比说若是你单机时代作一个数据库,你会在你的数据库里面内置一个操做系统吗?确定不会。因此这个模块抽象的边界,在这块我仍是比较相信 K8s 的。《Large-scale cluster management at Google with Borg》这篇论文里面提到了一句话,BigTable 其实也跑在 Borg 上。

图 24 TiDB 社区小伙伴的愿望列表

固然最后,你们听完这一堆东西之后,回头看咱们社区小伙伴们的愿望列表(图 24),就会发现对一下 TiDB 好像还都能对得上 :D

谢谢你们。

1 月 19 日 TiDB DevCon 2019 在北京圆满落幕,超过 750 位热情的社区伙伴参加了这次大会。会上咱们首次全面展现了全新存储引擎 Titan、新生态工具 TiFlash 以及 TiDB 在云上的进展,同时宣布 TiDB-Lightning Toolset & TiDB-DM 两大生态工具开源,并分享了  TiDB 3.0 的特性与将来规划,描述了咱们眼中将来数据库的模样。此外,更有 11 位来自一线的 TiDB 用户为你们分享了实践经验与踩过的「坑」。同时,咱们也为新晋 TiDB Committer 授予了证书,并为 2018 年最佳社区贡献我的、最佳社区贡献团队颁发了荣誉奖杯。

相关文章
相关标签/搜索