网易分库分表数据库DDB十年演变之路

标签: bigdata | 发表时间:2017-01-12 08:00 | 作者:
出处:http://itindex.net/admin/pagedetail

大数据

文 | 马进

文章详解了DDB十年来三次服务模式的重大更迭,从最早的Driver模式,到后来的Proxy模式,再到近几年的云模式。

互联网时代,也是关系型数据库独领风骚的时代,从早期的Oracle独步天下,到现在MySQL蒸蒸日上,关系型数据库是大多数互联网应用在数据可靠性存储上的“命脉”。

随着互联网产品在体量和规模上日益膨胀,无论是Oracle还是MySQL,都会第一时间面临来自磁盘、CPU和内存等单机瓶颈,为此,产品方除了需要不断购买成本难以控制的高规格服务器,还要面临不断迭代的在线数据迁移。在这种情况下,无论是海量的结构化数据还是快速成长的业务规模,都迫切需要一种水平扩展的方法将存储成本分摊到成本可控的商用服务器上。同时,也希望通过线性扩容降低全量数据迁移对线上服务带来的影响,分库分表方案便应运而生。

分库分表的原理是将数据按照一定的分区规则Sharding到不同的关系型数据库中,应用再通过中间件的方式访问各个Shard中的数据。分库分表的中间件,隐藏了数据Sharding和路由访问的各项细节,使应用大多数场景下可以像使用单机数据库一样使用分库分表后的分布式数据库。业界中,网易DDB、阿里TDDL、Cobar、MyCat和HotDB等系统都是分库分表中间件中的佼佼者。

背景——十年一剑

DDB(全称Distributed Database)是网易杭研院立项最早、应用最为广泛的后台产品之一,也是国内最早出现的数据库分库分表中间件。

最早可以追溯到2006年,网易杭研成立之初,为了应对网易博客这个日活超过800W的大体量应用,由现任杭研院院长的汪源带队主导开发了DDB这套分库分表数据库,伴随着博客的成长,DDB集群也从最早的20+节点,到40+节点,最后到现在云端100+个RDS实例。除了博客外,十年来DDB也见证了很多其他大体量应用,如易信、云音乐、云阅读、考拉等。在大家耳熟能详的网易互联网产品中,几乎都可以看到DDB的身影。

经过10年的发展和演变,DDB的产品形态已全面趋于成熟,功能和性能得到了众多产品的充分验证,下面罗列一些大家比较关注的功能特性:

  1. 与SQL92标准的兼容度达90%以上
  2. 支持跨库JOIN和跨库事务,支持大部分标量函数
  3. 支持COUNT、SUM、AVG、MAX、CONCAT等常用聚合函数
  4. 支持与MySQL高度一致的用户管理
  5. 支持读写分离和数据节点高可用
  6. 支持数据节点在线扩缩容、在线更改表分布
  7. 提供完善的数据库管理工具、Web和命令行工具
  8. 数据节点支持Oracle和MySQL

目前DDB在网易内部有近50个产品使用,最大集群过百数据节点,大部分部署在云端,为应用提供透明、无侵入、MySQL标准协议的分库分表服务。

DDB演变之路

十年来,DDB经历了三次服务模式的重大更迭,从最早的Driver模式,到后来的Proxy模式,再到近几年的云模式,DDB服务模式的成长也深刻反映着互联网流行架构的变迁。

Driver模式

Driver模式的特点在于应用通过DDB提供的JDBC Driver来访问DDB,类似于通过MySQL的JDBC驱动访问MySQL。而对于MySQL的驱动Connector/J,只需要实现将SQL按照特定协议编码和转码即可。而DDB的驱动为了实现透明的分库分表,需要做很多额外的工作,如图1所示。

大数据

图1

DDB Driver执行一条SQL时,会经历以下几个步骤:

  1. 由语法解析器解析SQL,生成抽象语法树Parse Tree,并根据是否PreparedStatement决定是否进入PTC(Parse Tree Cache),PTC保存了SQL模式到语法树的映射,对PreparedStatement SQL,会优先进入PTC中查询语法树;
  2. 根据语法树和启发式规则生成分布式执行计划,这个过程会涉及到多个步骤的SQL转换和优化,如条件合并,JOIN拆分,LIMIT转化等;
  3. 由SQL执行器按照执行计划和语法树生成下发给每个数据节点的真实SQL,然后通过标准数据库驱动将SQL下发给各个数据节点,这个过程为并发执行;
  4. 将各个数据节点返回的结果按照执行计划进行合并,并返回上层。具体的合并操作可能在应用调用结果时动态执行。

DBI模块作为DDB提供给应用的JDBC 驱动,包含了完整的透明分库分表逻辑,是DDB最为核心的组件,除此之外,DDB中还有用于元数据管理和同步的Master组件、数据库管理工具DBAdmin,和命令行工具ISQL,DDB的Driver模式整体架构如图2所示。

大数据

图2

管理操作以建表为例:

DBA通过DBAdmin的窗口创建表,或者用ISQL执行建表语句后,向Master发起实际建表请求,Master完成用户认证和合法性校验后,先在各个数据节点上创建新表,然后将新表元数据记录在系统库中,最后由Master将新表元数据同步给各个DBI模块。

对于建表语句中DDB特有的语法,会由ISQL或DBAdmin在解析DDL时完成相应处理,如自增ID的设置。

在DDB中,Master用于元数据管理、同步和报警监控。DBI模块启动时,会第一时间向Master注册,并拉取元数据,之后Master对元数据的同步保障了DBI模块元数据的更新。在DBI执行SQL,以及创建DB连接的过程中,不会涉及到与Master的交互。

在分库分表中间件中,与DDB Driver模式同样类型的还有阿里TDDL,优势是部署简单、成本较低、容易理解和上手。劣势也非常明显:只支持Java客户端、版本难以管理、问题难以追踪、DB连接难以归敛等,另外一点是,中间件与应用绑定在一起,对应用本身是个巨大侵入,而且分库分表的过程比较耗费CPU资源,所以在Driver模式下,无论是运维还是性能开销上都存在不可控的因素。

Proxy模式

相比于Driver模式在多语言,版本管理,运维风险上存在的问题,Proxy模式很好地弥补了这些缺陷。所谓Proxy,就是在DDB中搭建了一组代理服务器来提供标准的MySQL服务,在代理服务器内部实现分库分表的逻辑。本质上说,DDB Proxy作为一组独立服务,实现了MySQL标准通信协议,任何语言的MySQL驱动都可以访问,而在Proxy内部,依赖DBI组件实现分库分表,Proxy与DBI的关系如图3所示。

大数据

图3

应用通过标准数据库驱动访问DDB Proxy,Proxy内部通过MySQL解码器将请求还原为SQL,并由DDB Driver,也就是DBI模块执行得到结果,最后通过MySQL编码器返回给应用。

从图3可以看出,Proxy在DBI上架设了MySQL编解码模块,从而形成独立标准的MySQL服务,而在MySQL编解码模块之上,DDB Proxy也提供了很多特色命令支持,例如:

  • show processlist:查看Proxy所有连接状态,与MySQL相关命令高度一致
  • show connection_pool:查询Proxy到数据节点的连接池状态
  • showtopsql:查询按照SQL模式聚合的各项统计结果,如执行次数,平均执行时间
  • count..from:查询过去各个时间段内的吞吐量

此外,DDB Proxy内还提供了Slow Log等辅助功能,给运维带来很大的便利。

DDB Proxy模式完整架构如图4所示。

大数据

图4

与Driver模式架构相比,除了QS(DDBProxy的内部称谓,下同)取代了DBI的位置,还在多个QS节点之上部署了LVS或HAProxy + Keepalived的组合做负载均衡,从而实现多个DDBProxy节点的热备,由于DDBProxy无状态,或者说状态统一由Master同步,在数据库节点没有达到瓶颈时,可以通过简单地增设QS服务器实现服务线性扩展。

私有云模式

在网易私有云项目启动之前,DDB一直以一个个独立集群为不同业务提供服务,不同DDB各自为政毫不相干,这样的好处是业务之间完全隔离,互不影响。不好之处在于随着使用DDB的产品数目不断增多,一个DBA往往同时运维数个甚至数十个DDB集群,而之前我们一直缺乏一个平台化的管理系统,DBA在各个集群之间应接不暇时,我们没有平台化的统筹运维帮助应用及早发现问题,或是优化一些使用方法。

例如版本管理,2013年我们在一个大版本中做了个Hotfix,并通知所有DBA将相关版本进行升级,但是最后由于管理疏漏,有个别集群没有及时上线,为业务带来了损失。当时如果我们有平台化的管理方案,可以提供一些运维手段帮助和提醒运维人员及时更新所有有问题集群,另外,平台化的管理工具也可以定制一些自动化功能,如自动备份、报警组等。

网易私有云的出现为DDB的思变提供了契机,从2012年开始,我们就在基于网易私有云开发一套平台化的管理工具Cloudadmin,为此,我们将DDB中原先Master的功能打散,一部分分库相关功能集成到Proxy中,如分库管理、表管理、用户管理等,一部分中心化功能集成到Cloudadmin中,如报警监控,此外,Cloudadmin中提供了一键部署、自动和手动备份、版本管理等平台化功能。私有云DDB的整体架构如图5所示。

大数据

图5

在云DDB解决方案中,还打包了网易私有云LVS服务,Cloudadmin通过DDBAgent实现一键部署和报警监控。到目前为止,网易80%以上的DDB集群都已部署云端,云DDB的出现极大减轻了运维人员的负担。

DDB特性介绍

分布式执行计划

分布式执行计划定义了SQL在分库分表环境中各个数据库节点上执行的方法、顺序和合并规则,是DDB实现中最为复杂的一环。

如SQL:select * from user order by id limit 10 offset 10;

这个SQL要查询ID排名在10—20之间的user信息,这里涉及到两个合并操作:全局ID排序和全局LIMIT OFFSET。对全局ID排序,DDB的做法是将ID排序下发给各个数据库节点,在DBI层再进行一层归并排序,这样可以充分利用数据库节点的计算资源,同时将中间件层的排序复杂度降到最低,例如一些需要用到临时文件的排序场景,如果在中间件做全排序会导致极大开销。

对全局LIMIT OFFSET,DDB的做法是将OFFSET累加到LIMIT中下发,因为单个数据节点中的OFFSET没有意义,且会造成错误的数据偏移,只有在中间件层的全局OFFSET才能保证OFFSET的准确性。

所以最后下发给各个DBN的SQL变为:select * from user order by id limit 20。

又如SQL:select avg(age) from UserTet group by name

可以通过EXPLAIN语法得到SQL的执行计划,如图6所示。

大数据

图6

上述SQL包含GROUP BY分组和AVG聚合两种合并操作,与全局ORDER BY类似,GROUP BY也可以下发给数据节点、中间件层做一个归并去重,但是前提要将GROUP BY的字段同时作为ORDER BY字段下发,因为归并的前提是排序。对AVG聚合,不能直接下发,因为得到所有数据节点各自的平均值,不能求出全局平均值,需要在DBI层把AVG转化为SUM和COUNT再下发,在结果集合并时再求平均。

DDB执行计划的代价取决于DBI中的排序、过滤和连接,在大部分场景下,排序可以将ORDER BY下发简化为一次性归并排序,这种情况下代价较小,但是对GROUP BY和ORDER BY同时存在的场景,需要优先下发GROUP BY字段的排序,以达到归并分组的目的,这种情况下,就需要将所有元素做一次全排序,除非GROUP BY和ORDER BY字段相同。

DDB的连接运算有两种实现,第一种是将连接直接下发,若连接的两张表数据分布完全相同,并且在分区字段上连接,则满足连接直接下发的条件,因为在不同数据节点的分区字段必然没有相同值,不会出现跨库连接的问题。若不满足连接下发条件,会在DBI内部执行Nest Loop算法,驱动表的顺序与FROM表排列次序一致,此时若出现ORDER BY表次序与表排列次序不一致,则不满足ORDER BY下发条件,也需要在DBI内做一次全排序。

分库分表的执行计划代价相比单机数据库而言,更加难以掌控,即便是相同的SQL模式,在不同的数据分布和分区字段使用方式上,也存在很大的性能差距,DDB的使用要求开发者和DBA对执行计划的原理具有一定认识。

如分库分表在分区字段的使用上很有讲究:一般建议应用中80%以上的SQL查询通过分区字段过滤,使SQL可以单库执行。对于那些没有走分区字段的查询,需要在所有数据节点中并行下发,这对线程和CPU资源是一种极大的消耗,伴随着数据节点的扩展,这种消耗会越来越剧烈。另外,基于分区字段跨库不重合的原理,在分区字段上的分组、聚合、DISTINCT、连接等操作,都可以直接下发,这样对中间件的代价往往最小。

分布式事务

分布式事务是个历久弥新的话题,分库分表、分布式事务的目的是保障分库数据一致性,而跨库事务会遇到各种不可控制的问题,如个别节点永久性宕机,如此像单机事务一样的ACID是无法奢望的。另外,业界著名的CAP理论也告诉我们,对分布式系统,需要将数据一致性和系统可用性、分区容忍性放在天平上一起考虑。

两阶段提交协议(简称2PC)是实现分布式事务较为经典的方案,适用于中间件这种数据节点无耦合的场景。

2PC的核心原理是通过提交分阶段和记日志的方式,记录下事务提交所处的阶段状态,在组件宕机重启后,可通过日志恢复事务提交的阶段状态,并在这个状态节点重试,如Coordinator重启后,通过日志可以确定提交处于Prepare还是PrepareAll状态,若是前者,说明有节点可能没有Prepare成功,或所有节点Prepare成功但还没有下发Commit,状态恢复后给所有节点下发RollBack;若是PrepareAll状态,需要给所有节点下发Commit,数据库节点需要保证Commit幂等。

与很多其他一致性协议相同,2PC保障的是最终一致性。

2PC整个过程如图7所示。

大数据

图7

在DDB中,DBI和Proxy组件都作为Coordinator存在,2PC实现时,记录Prepare和PrepareAll的日志必须sync,以保障重启后恢复状态正确,而Coordinator最后的Commit日志主要作用是回收之前日志,可异步执行。

由于2PC要求Coordinator记日志,事务吞吐率受到磁盘I/O性能的约束,为此DDB实现了GROUP I/O优化,可极大程度提升2P C的吞吐率。2PC本质上说是一种阻塞式协议,两阶段提交过程需要大量线程资源,因此CPU和磁盘都有额外消耗,与单机事务相比,2PC在响应时间和吞吐率上相差很多,从CAP角度出发,可以认为2PC在一定程度上成全了C,牺牲了A。

另外,目前MySQL最流行的5.5和5.6版本中,XA事务日志无法Replicate到从节点,这意味着主库一旦宕机,切换到从库后,XA的状态会丢失,可能造成数据不一致,这方面MySQL 5.7已经有所改善。

虽然2PC有诸多不足,我们依然认为在DDB中有实现价值,DDB作为中间件,其迭代周期要比数据库这种底层服务频繁很多,若没有2PC,一次更新或重启就可能造成应用数据不一致。从应用角度看,分布式事务的现实场景常常无法规避,在有能力给出其他解决方案前,2PC也是一个不错的选择。

对购物转账等电商和金融业务,中间件层的2PC最大问题在于业务不可见,一旦出现不可抗力或意想不到的一致性破坏,如数据节点永久性宕机,业务难以根据2PC的日志进行补偿。

金融场景下,数据一致性是命根,业务需要对数据有百分之百的掌控力,建议使用TCC这类分布式事务模型,或基于消息队列的柔性事务框架,这两种方案都在业务层实现,业务开发者具有足够掌控力,可以结合SOA框架来架构。原理上说,这两种方案都是大事务拆小事务,小事务变本地事务,最后通过幂等的Retry来保障最终一致性。

弹性扩缩容

分库分表数据库中,在线数据迁移也是核心需求,会用在以下两种场景:

数据节点弹性扩容

随着应用规模不断增长,DDB现有的分库可能有一天不足以支撑更多数据,要求DDB的数据节点具有在线弹性扩容的能力,而新节点加入集群后,按照不同的Sharding策略,可能需要将原有一些数据迁入新节点,如HASH分区,也有可能不需要在线数据迁移,如一些场景下的Range分区。无论如何,具备在线数据迁移是DDB支持弹性扩容的前提。

数据重分布

开发者在使用DDB过程中,有时会陷入困局,比如一些表的分区字段一开始没考虑清楚,在业务已经初具规模后才明确应该选择其它字段。又如一些表一开始认为数据量很小,单节点分布足以,而随着业务变化,需要转变为多节点Sharding。这两种场景都体现了开发者对DDB在线数据迁移功能的潜在需求。

无论是弹性扩容,还是表重分布,都可当做DDB以表或库为单位的一次完整在线数据迁移。可分为两个阶段:全量迁移和增量迁移:全量迁移是将原库或原表中需要迁移的数据DUMP出来,并使用工具按照分区策略Apply到新库新表中。增量迁移是要将全量迁移过程中产生的增量数据更新按照分区策略Apply到新库新表。

全量迁移的方案相对简单,使用DDB自带工具按照特定分区策略DUMP和Load即可。对增量迁移,DDB实现了一套独立的迁移工具Hamal来订阅各个数据节点的增量更新,Hamal内部又依赖DBI模块将增量更新Apply到新库新表,如图8所示。

大数据

图8

Hamal作为独立服务,与Proxy一样由DDB统一配置和管理,每个Hamal进程负责一个数据节点的增量迁移,启动时模拟Slave向原库拉取Binlog存储本地,之后实时通过DBI模块Apply到新库新表,除了基本的迁移功能外,Hamal具备以下两个特性:

并行复制:Hamal的并行复制组件,通过在增量事件之间建立有向无环图,实时判断哪些事件可以并行执行,Hamal的并行复制与MySQL的并行复制相比快10倍以上;

断点续传:Hamal的增量Apply具有幂等性,在网络中断或进程重启之后可以断点续传。

全局表

考虑一种场景:City表记录了国内所有城市信息,应用中有很多业务表需要与City做联表查询,如按照城市分组统计一些业务信息。假设City的主键和分区键都是CityId,若连接操作发生在中间件层,代价较高,为了将连接操作下发数据节点,需要让联接的业务表同样按照CityId分区,而大多数业务表往往不能满足这个条件。

联接直接下发需要满足两个条件,数据分布相同和分区键上联接,除此之外,其实还有一种解法,可以把City表冗余到所有数据节点中,这样各个数据节点本地联接的集合便是所求结果。DDB将这种类型的表称之为全局表。

全局表的特点是更新极少,通过2PC保障各个节点冗余表的一致性。可以通过在建表语句添加相关 Hint指定全局表类型,在应用使用DDB过程中,全局表的概念对应用不可见。

未来——独立平台,与云共舞

DDB作为网易浓缩了10年技术经验与精华的分库分表数据库,近一两年除了满足内部产品使用外,也渐渐开始帮助外部企业客户解决海量结构化数据存储的难题。随着公有云技术的大力发展和日趋成熟,各种IaaS和PaaS平台如雨后春笋层出不穷,如网易蜂巢的推出,为应用开发、部署和运维提供了极大便利。而随着IaaS层和PaaS平台的普及,各种SaaS服务也会慢慢为广大开发者所接纳,未来DDB也将重点为网易蜂巢客户打包DDB的SaaS服务,与蜂巢一同构建一套更加丰富的数据存储生态系统。

我们对DDB的SaaS服务化无比坚定,同时DDB的公有云之路绝非私有云的生搬硬套,在与蜂巢一同帮助企业客户解决分库分表难题的同时,未来我们也会更加注重平台独立,首先要做的是将DDB的SaaS层与底层PaaS和IaaS层解耦,实现将DDB平台所依赖的PaaS和IaaS以插件方式注入。这样既可以为客户提供更灵活的服务方式,也可以极大程度降低DDB平台本身的开发和运维成本:一套平台管理工具,适用所有内外部DDB用户,这是我们正在进行并将持续优化的目标。

36大数据(www.36dsj.com)成立于2013年5月,是中国访问量最大的大数据网站。36大数据(微信号:dashuju36)以独立第三方的角度,为大数据产业生态图谱上的需求商 、应用商、服务商、技术解决商等相关公司及从业人员提供全球资讯、商机、案例、技术教程、项目对接、创业投资及专访报道等服务。

End.

转载请注明来自36大数据(36dsj.com): 36大数据» 网易分库分表数据库DDB十年演变之路

相关 [网易 数据库 ddb] 推荐:

网易分库分表数据库DDB十年演变之路

- - IT瘾-bigdata
文章详解了DDB十年来三次服务模式的重大更迭,从最早的Driver模式,到后来的Proxy模式,再到近几年的云模式. 互联网时代,也是关系型数据库独领风骚的时代,从早期的Oracle独步天下,到现在MySQL蒸蒸日上,关系型数据库是大多数互联网应用在数据可靠性存储上的“命脉”. 随着互联网产品在体量和规模上日益膨胀,无论是Oracle还是MySQL,都会第一时间面临来自磁盘、CPU和内存等单机瓶颈,为此,产品方除了需要不断购买成本难以控制的高规格服务器,还要面临不断迭代的在线数据迁移.

坐下跑不掉,DDB Auckland的“大腿广告”

- 文玺 - 理想生活实验室
好的广告创意可是没那么容易想出来的,基本上你能想的人家早就想到了,不过最近,新西兰的 DDB Auckland 广告公司带来了一个够狠的创意,为了满足客户 Superette(新西兰服装连锁品牌)的需要,DDB Auckland 找当地的公园等公共场所,改造了这些地方的露天椅子,在椅子加上了反向阳刻的文字,这下,姑娘们只要坐下来,广告可就跑不掉了.

DDB Worldwide大佬为你指点互联网营销的本质

- - MADBRIEF | 疯狂简报
《互联网营销的本质:点亮社群》的确是本难得的好书. 书中介绍了个体的力量和社群的力量、剖析数字社群、告别盲人,迎接自主信息时代、为什么速度会成为新宠、吸引社群的招数、首席社群官“品牌的新代言人、消费者驱动型社会的蓝图等内容. 下面是书中一些精彩的观点,为了读者阅读的舒适,所有“社群”都已经被替换成“社区”.

网易分布式数据库多活架构的演进与实践

- -
大家好,今天给大家分享一些网易近几年在数据库多活方向上的工作. 我将简单介绍下为什么我们要做数据库多活,再从三个阶段介绍网易在数据库多活上做的工作. 数据库多活的目标包括“容灾”和“提升处理能力”两方面. 容灾可以简单理解为当系统由于外部或内部原因出现部分不可用时,仍然能在短时间内恢复可用. 而容灾最常用的手段即是备份,在数据库领域不仅需要对计算能力做备份也要对数据做备份.

数据库sharding

- - 数据库 - ITeye博客
当团队决定自行实现sharding的时候,DAO层可能是嵌入sharding逻辑的首选位置,因为在这个层面上,每一个DAO的方法都明确地知道需要访问的数据表以及查询参数,借助这些信息可以直接定位到目标shard上,而不必像框架那样需要对SQL进行解析然后再依据配置的规则进行路由. 另一个优势是不会受ORM框架的制约.

数据库索引

- - CSDN博客推荐文章
索引是由用户创建的、能够被修改和删除的、实际存储于数据库中的物理存在;创建索引的目的是使用户能够从整体内容直接查找到某个特定部分的内容. 一般来说,索引能够提高查询,但是会增加额外的空间消耗,并且降低删除、插入和修改速度. 1.聚集索引:表数据按照索引的顺序来存储的. 2.非聚集索引:表数据存储顺序与索引顺序无关.

数据库事务

- - 数据库 - ITeye博客
事务传播发生在类似以下情形:. 假设methodB的配置是:. 如果methodA在事务里,那么methodB也在这个事务中运行. 如果methodA不在事务里,那么methodB重新建立一个事务运行. 如果methodA在事务里,那么methodB也在这个事务中运行. 如果methodA不在是事务里,那么methodB在非事务中运行.

数据库优化

- - 数据库 - ITeye博客
程序运行效率,优化应用程序,在SP编写过程中应该注意以下几点: . a) SQL的使用规范: .   i.尽量避免大事务操作,慎用holdlock子句,提高系统并发能力.   ii.尽量避免反复访问同一张或几张表,尤其是数据量较大的表,可以考虑先根据条件提取数据到临时表中,然后再做连接.   iii.尽量避免使用游标,因为游标的效率较差,如果游标操作的数据超过1万行,那么就应该改写;如果使用了游标,就要尽量避免在游标循环中再进行表连接的操作.

数据库调优

- - 数据库 - ITeye博客
1、1、调整数据结构的设计. 这一部分在开发信息系统之前完成,程序员需要考虑是否使用ORACLE数据库的分区功能,对于经常访问的数据库表是否需要建立索引等. 这一部分也是在开发信息系统之前完成,程序员在这一步需要考虑应用程序使用什么样的体系结构,是使用传统的Client/Server两层体系结构,还是使用Browser/Web/Database的三层体系结构.