分步式架构设计分析.docx
- 文档编号:3501155
- 上传时间:2022-11-23
- 格式:DOCX
- 页数:23
- 大小:985.70KB
分步式架构设计分析.docx
《分步式架构设计分析.docx》由会员分享,可在线阅读,更多相关《分步式架构设计分析.docx(23页珍藏版)》请在冰豆网上搜索。
分步式架构设计分析
前Oracle架构师:
如何实现分布式平台的内核设计
本文是第一期InfoQ「大咖说」视频直播内容整理:
前贝尔实验室、Oracle架构师董健基于多年工作经验,站在内核实现的角度去看如何设计一个分布式平台。
微信后台回复关键词「内核」获取视频回放链接。
文末下期分享预告!
引言
我们今天的分享的内容都是从具体的产品实现中抽象出来的。
这些设计方法在操作系统、中间件、数据库、分布式平台和大数据平台中都在使用,大部分其实就是我们在具体产品中使用的设计。
托尔斯泰曾经说过一句名言:
“优秀的架构都是相似的,垃圾的架构各有各的垃圾之处”。
分布式平台作为容器产品,需要解决的问题都是类似的,每一个问题的解决方案其实就那么几种,每个方案都有其优缺点,然后做取舍做选择。
通过这场分享,希望帮助大家去了解你们正在使用的平台的定位,解决了哪些问题,都是如何解决的,哪些问题有潜在的坑不能踩。
没有解决的问题的延伸解决方案都是什么,和其他平台如何融合等等。
图1:
分享提纲
分布式架构为弹性应用解困
任何有点规模的应用,都无法回避分布式架构。
分布式架构的存在就是为了解决大规模的应用。
大规模的应用不好走,小规模应用无缝过渡到大型规模应用更不好做,从大型规模应用无缝降级为小规模应用则更难做。
这就是我们经常说的弹性应用。
弹性应用架构的特点就是能够利用弹性的计算资源来解决问题,分布式必然成为架构中最核心的因素。
我们一起看一下分布式如何在这种架构中贯穿和应用。
1、弹性应用面临的困境
图2
仔细总结一下,我们会发现我们平常遇到的企业应用和互联网应用大致可以分为三类。
1.经典的OLTP应用,也可以理解成RPC应用,简单来说就是要求算1+1=2,但是它的难点是10万,100万个人同时请求这种计算。
它的特点就是简单任务的高并发执行。
它的后台通常都是关系型数据库支撑,数据库绝对是永远的痛,只能靠提升数据库性能,或者通过应用隔离,分库分表来扩容。
2.会话型应用,就是不同的独立请求之间是关联的,系统是有状态的。
首先它需要有丰富的通讯模式,简单的RPC是不满足的,同时多个独立的请求之间可以在一个会话内实现数据共享和交互。
这种应用的数据形式和数据存储形式、灵活性和性能都很重要。
3.包括并行计算、分布式计算、大数据,它们通常是比较复杂的任务,需要利用大量的计算资源共同完成,需要有复杂任务切分机制,同时数据量也比较大,数据形式复杂,数据存储和处理能力依然是性能瓶颈。
我们会发现,这三种经典应用的核心问题都是:
计算能力不够、存储空间不够、磁盘访问效率低、持久化数据能力不够。
最终只有一个方案:
分布式。
2、困境中的答案
图3
那么到底什么是分布式?
分布式是一个比较广义的命题。
简单的来讲,分布式就是利用软件,通过scaleout的方式解脱单一计算节点上的无法从硬件上无限突破的两大性能瓶颈,CPU和磁盘。
解决CPU的问题就是分布式计算,解决磁盘的问题就是分布式存储。
先看分布式计算。
分布式计算的目的就是提升参与计算的节点个数以提高计算能力。
用浅显的话说,只要是同一类(记住,是同一类,不是同一件)的任务由超过一个CPU(准确是CPU的核心)完成都算是分布式,记住我用的是CPU,不是机器。
计算节点并不一定是一台独立的计算机,它可以是线程、进程、机器、集群等,分布式应用也不一定必须是多机应用,多进程、多线程一样是分布式。
我们知道单一硬件资源计算能力的增长早已经停止了,多核和多机架构其实没有本质区别。
通过分布式计算,其实相当于通过软件实现所谓的“摩尔定律”的回归,而且,分布式解决的不单是多核,还可以解决多CPU,多机的配合,继续提高系统的处理能力。
分布式存储则是在维持单位存储容量的管理成本不变的前提下,提升参与存储的节点数以提升存储容量和存储访问能力。
分布式不是一个新技术,是一种应用框架,是一种设计模式。
因此,针对分布式的设计模式,衍生出很多技术来支撑,比如路由、负载均衡、任务调度、并行计算、资源竞争、线程间通讯、进程间通讯、网络通讯,等等。
同时,衍生出新的软件设计需求,RASP。
RASP是分布式系统中一个非常重要的要素,我们后面会反复提及。
介绍了什么是分布式,那么把分布式的设计模式融入到应用中后的应用架构应该是什么样的呢?
。
毕竟任何一个系统的核心是应用业务逻辑。
3、理想的分布式应用架构长什么样?
图4
这里我们看一下站在业务角度思考问题时,理想的分布式应用架构长什么样最合理。
本质上讲,分布式架构就是把应用逻辑改成分布式执行的方式。
不同的应用类型的处理方式又是不一样的。
所以,一个非常直观的设计方式就是先将应用还原成一个工作流,就是每个软件系统的流程图。
工作流中的每个步骤就是一个功能模块,每个步骤就是分布式的基础,它可以对应原来的一个函数或方法,针对每个步骤进行独立的分布式,再保持每个步骤的正常连接。
我们可以针对不同的应用类型选用不同的分布式设计方法和目标。
比如图一,这是一个单任务串行的应用,它的步骤之间没有依赖关系,它的分布式方法就是单任务并行。
于是多个步骤被更多计算节点同时执行,从而降低单个请求或者任务的完成时间。
对于应用,它的步骤之间是有依赖关系的,它的分布式方法就是由多任务串行变成多任务并行,通过流水线的形式实现多个任务的同一个步骤可以并发执行,在保持响应时间的前提下提高系统的吞吐量。
另外,从图3可以看出,应用的每一个步骤可能是一个函数,也可能是一个子系统,而这个子系统内部又包含了很多分布式执行的步骤。
因此,基于步骤分布式的粒度可以非常灵活。
通过粒度选择可以调整应用的吞吐量,从而在性能和管理成本中间选择一个平衡点。
中国有句成语叫做“分而治之”,很好的描述了分布式的目标,“分”是将任务拆分成多个步骤串行或者并行执行,“治”是多个步骤之间的连接和配合。
从上述的例子我们可以看出,具体被“分”的内容和“治”的方法是业务逻辑,是应用行为。
因此理想的分布式应用设计方法论应该站在应用角度去“分”和“治”,“分”和“治”的行为应该是站在原有应用逻辑的设计角度上去实现分布式。
这样任何应用都可以用这种模式去轻松实现分布式,而且最贴合应用的原始设计。
那么,为了支撑这样的理想的分布式应用设计方法,底层的分布式平台应该做什么呢?
4、理想的分布式平台该做什么?
图5
一个完整的分布式平台会包含开发态(分布式框架)和运行态(分布式平台)两部分,共同帮助将应用“分而治之”,分”和“治”的内容是应用本身,一定是应用决定如何“分”和“治”。
开发态首先是提供编程范式和API支撑应用的“分”和“治”。
关于分,框架采用和原有应用设计类似的拆分方法来拆分出可以分布式执行的粒度,比如应用靠函数/对象来实现模块化,还应该保持这种拆分粒度,任何粒度的应用步骤都可以被封装成分布式的单元。
关于治,框架保持原有步骤之间的连接,但提供了多种连接形式,比如原来函数肯定是同步调用,现在可以改成同步或者异步调用。
作为框架,它的首要目标是尽量不改变原有编程模式,引入最少的新知识和技术。
同时,它必须是更低层面的语义抽象,必须非常简单和轻量,这样才能适应更多的应用类型,让“分”透明,让“治”简单灵活。
现在有很多分布式框架,以分布式为导向,再让应用去适应,这其实是不正确的。
再看运行态,它是提供“分”和“治”的运行容器支撑,对应用是完全透明的,能够让应用运行时无缝地在多线程、多进程、多机环境下透明并行/并发执行。
当然,提供RASP,也是平台提供的核心功能。
施乐的首席科学家,普适计算之父,MarkWeiser,曾经说过,“最高深的技术是那些令人无法察觉的技术,这些技术不停地把他们自己编织进日常生活,直到你无从发现为止”。
同理,最好用的分布式计算框架/平台也应该尽量淡化自己的存在,在应用中尽量无缝的融入自己,而不是改变应用原有的设计。
分布式平台核心要素的设计剖析
介绍完了什么是分布式系统、分布式系统能解决什么问题、理想的分布式平台的定位和设计原则以后,我们来一起看一下如何设计一个完善的分布式平台,剖析一下分布式系统的核心要素有哪些,而这些设计要素又有哪些设计选择。
分布式架构设计的点非常多,我们在设计的过程中在每个点上都有非常多的考虑和创新,我会结合我们的分布式平台的设计实现,尽可能把关键点都囊括到,并针对一些核心点的具体设计方案展开详细讨论。
这个剖析也从开发态和运行态分别展开介绍。
1、开发态(框架)的核心要素
图6
先看开发态。
作为开发态的分布式框架,它辅助的是应用开发过程,它的核心是如何用最小的成本设计灵活、高效的分布式应用。
它包含的范畴也比较广。
这里我列出了最核心的八点。
首先是支持的编程范式,比如RPC、对话、EDA、Map-Reduce、批处理、并行计算。
它直接决定该框架能够支持的业务类型。
下一个是通讯协议,它决定调用者和被调用者之间的信息交换形式。
分布式框架支持的通讯协议在任何两个节点间必须是统一的,比如客户端与服务器、服务实例间、线程间、进程间、机器间、集群间。
通讯协议对应的是编程范式,应用开发应该只和编程范式打交道,最后被平台转换成通讯协议,这样,运行时可以动态调整通讯协议而不重新开发应用。
比如对话的编程范式可以被配置成HTTPchunking,而应用对HTTPchunking一无所知。
和通讯协议密切相关的是数据协议。
它决定调用者和被调用者之间的业务数据交换形式。
它主要解决的就是离散态的内存序列化的问题,同样适用于业务数据持久化。
一般来说,解决序列化无非三种办法:
IDL、数据类型自描述、应用自主控制(IDL的升级版)。
数据协议对应的是数据类型,和通讯协议一样,数据协议也应该对应用透明,应用开发完全不知道数据协议的存在。
配合透明的通讯协议和数据协议,可以轻松把应用中的一个服务发布成一个REST服务,通讯协议使用HTTP,数据协议使用JSON,而这同一个服务同时还可以被内部子系统调用,使用高速的二进制协议。
一个服务能够透明支持各种通讯协议和数据协议,这是“服务化”架构中最基础的一个功能。
通过这样的支持,通讯协议和数据协议才可以变成可插拔、可扩展的模块,并对应用完全透明,应用开发只需要关注跟业务逻辑相关的事情。
很多的分布式框架或者RPC框架中都没有实现的这么灵活。
会话的支持指的是多个独立请求之间关联形成一个虚拟连接,实现它们之间的数据共享。
它主要是为了支持复杂的会话类应用。
多语言互操作主要是借助透明的通讯协议和数据协议,实现进程内、进程间、节点间的多语言互操作。
透明跨操作系统这里主要针对的是不需要解释器或者虚拟机的编程语言在OS的API层面提供跨OS支撑,隐藏OS和CPU体系结构的差异性,这也是一个分布式框架必须要做到的。
作为分布式开发框架,必然需要提供各种灵活的辅助工具包。
通常包括丰富的容器数据类型支持,同时一个重要功能就是内存管理和数据共享。
我们在分布式框架中为包括C/C++语言开发者提供了一套包括内存垃圾回收的灵活的内存管理框架,将内存的生命周期和可见范围结合,一方面简化开发人员对内存的使用,同时让内存在第一时间自动回收,并保证内存在不该被访问的范围内不可见。
服务实例间通信也应该包含在分布式开发框架的工具包中。
通常服务实例应该是完全独立的,但是服务实例间通信对于有依赖关系的任务配合非常重要。
这里,我们针对分布式开发框架中最重要的部分,便捷的编程模型,做一个重点介绍。
2、便捷的编程模型
图7
讨论编程模型,其实就是讨论编程框架对于应用的入侵问题。
自从开发web应用开始,程序的主循环逻辑就由框架管理了,程序员已经习惯了各种框架,很少有应用开发人员自己写程序的入口了。
分布式应用的主逻辑肯定是被平台控制的,编程模型基本都是在开发各种callback。
所以本质上分布式平台对于应用肯定是入侵的。
比如左图是一个典型的分布式应用开发的一个简单场景,里面包含着应用和框架的分工和配合。
灰色是框架的模块,蓝绿色是应用的代码。
框架有初始化模块,然后应用初始化,框架的主逻辑在接受到一个请求后,会调用应用的服务逻辑,应用的服务逻辑中会通过编程范式请求另一个服务,这时候利用框架提供的路由和负载均衡功能定位目标服务,通过数据协议和通讯协议完成调用获取结果,最后当前应用服务生成返回结果,完成一个服务的调用。
所以,讨论编程模型的本质就是讨论callback的开发方法,多个callback之间的交互方法,一个callback的多个instance之间的交互方法。
一个编程模型的优劣直接决定了开发人员的工作量和出错几率,同时也会影响运行时的灵活程度,编程模型也会直接影响RASP,比如分布式粒度的选择直接决定了未来高可用的级别。
理想的编程模型,应该尽可能少的引入新的语义,尽量让开发人员用单机单用户单线程的思路来开发分布式应用,让分布式对开发者透明,将一切分布式的语义都封装在原始程序开发之外,将框架尽可能隐藏。
同时,对于跟运行相关的内容,尽可能不要在编程阶段引入,比如服务运行在多线程、多进程还是多机通过配置的形式来驱动。
一个应用中必然包含多个子系统,里面用到的编程范式也不相同。
如果能够通过一套编程模型来同时支持多种业务,则可以实现子系统间的资源协调和配合,提高可用性和可伸缩性,降低交互成本。
这个时候编程模型需要尽可能用同一种原语解决中间件、通讯交换机、Hadoop/Spark解决的多种问题。
3、典型的分布式编程模型
图8
这里,我们介绍几种典型的分布式编程模型。
首先是请求级分布式应用。
一般的web应用都是这种模型。
它的最小分布式单位是请求,通常通过线程、进程、机器作为计算节点承载并发请求。
这种编程模型的应用和框架之间是有条件的隔离的,HTTPsession和HTTP协议相关的处理都是典型的入侵,除此外应用基本不改变设计方法。
它的容器通常有两种。
一种是分层容器,包含运行容器和代码运行时两部分,比如Apache+PHP的组合。
这种架构通常都是开发态和运行态相对分离的架构选择,编程框架只和运行时打交道,可以无缝替换容器或者运行时。
另一种是统一容器,比如JEE的应用服务器,容器是一个整体,自身提供编程接口和范式。
编程框架完全入侵应用,必须按照这个编程开发范式开发应用。
请求级分布式应用模型的请求的粒度很大,请求间隔离很差。
单请求的等待过程中占用的资源无法释放导致新的请求无法进入,并发处理能力弱。
存储/数据库性能瓶颈会导致前端的分布式努力付之东流。
通常配备负载均衡来解决单机处理容量低的问题。
这种编程模型仅适合OLTP类型的业务。
另外一个就是非常热门的Map-Reduce框架。
它是一个纯粹的并行编程的框架,以分布式为主体,完全的入侵应用。
所有的应用都需要按照框架来重构,属于一个逆向思维的分布式架构。
大数据时代,数据量太多,传统数据库不能解决,数据类型变多,没有数据库可以存储,需要计算的工作太多,分布式程序不容易写,也没有合适的运行容器。
Map-reduce是一个在合适的时间出现的针对特定问题的分布式应用解决方案,确实很伟大,解决这些问题很擅长,而且可以利用廉价的计算资源,并且开源,受欢迎程度可想而知。
但是它解决的是针对性的问题,在分布式处理方面的模式也是相对单一的,比较适合批处理形式的任务,绝不是分布式领域的银弹,这些软件的作者和开源社区都对它有清晰的定位,但是经常被夸大成了超级武器,在大数据时代无处不在,甚至开始在各种应用中无处不在。
4、服务型分布式计算框架——分布式遇上SOA
图9
这里再介绍一种我们在自己的分布式平台中使用的分布式编程模型,我们称之为“服务型分布式计算框架”。
服务和SOA是永恒的设计话题。
在应用架构领域,服务的设计应该是一个始终贯彻的原则。
由于后台业务系统的复杂性不断增加,现在“服务化”的论调又在被大幅提倡。
“服务型分布式计算框架”其实就是将服务和分布式进行有机的结合。
回想一下前面我们介绍的理想的分布式应用架构应该长什么样。
那个时候我们提出应该先将应用还原成一个工作流,将工作流中的每个步骤作为分布式的基础。
对应这里的三张流程图,单任务并行、多任务并发、灵活的分布式粒度。
我们的“服务型分布式计算框架”其实就是把这些步骤封装成服务,而它原来可能是函数、方法。
于是,这套编程框架的核心就是保留原业务系统的所有设计流程,将每个需要分布式的步骤解耦、封装,在不改变业务流程的基础上,将每个步骤提炼成一个独立的服务。
而所谓的服务封装其实并没有做什么事情,还保留原有的函数或者方法的样子,只是它们在运行时可以变成分布式平台调度的运行单位。
服务的连接方法也没有什么改变,依然是函数/方法调用,运行时的连接是通过配置变成多线程、多进程、多机、多集群。
异步决定着整个系统的性能,但是异步开发很繁琐。
我们在框架中引入了异常简单的异步开发框架,并提供便捷的现场保存/恢复机制,从而实现所有函数/方法的异步调用。
这套编程框架的核心是业务导向,应用开发几乎感觉不到框架的存在,仅有的应用入侵就是全异步开发,但是它带来的回报相对于它的简单投入是异常值得的,而且这还不是强制的。
通过将每个需要分布式执行的步骤封装成服务,运行态和开发态实现完全隔离。
开发态看到的是虚拟的计算资源,和面对单机单用户没有区别,具体的分布式策略完全在运行时通过配置决定,分布式变成了运维的事而不是开发的事。
借助这样的分布式应用架构,开发人员可以完全专注于业务开发,最大程度的保留最适合业务的应用架构,不用关心哪些服务需要对外发布,未来可以通过轻松的配置把一个内部服务发布成外部服务。
基于服务的粒度可以让应用的架构非常灵活,运行效率非常高效。
重要的是,每个应用的分布式模型是不一样的,是依赖于业务流程的,于是每个应用都是一个彻底的个性化分布式应用。
借助这个平台,我们开发了实时流媒体平台,开发了分布式数据库,开发了大数据分析平台。
因此,我们称之为自上而下、自内而外的的全SOA架构。
它根本不需要服务化,因为它天生就是服务化的。
刚才我们介绍了分布式框架的几个核心要素,重点介绍了编程模型。
我们再来看看另一面,运行态的分布式平台。
5、运行态(平台)的核心要素
图10
作为运行态的分布式平台,它是分布式应用的运行支撑容器。
分布式应用一般都是服务端应用,我们可以通过一个服务端应用的一个简单运行流程来看一下分布式平台如何在各个环节发挥作用。
看这里的流程图,我们会看到一个服务器从启动开始到提供服务,在接受到一个请求后,开始处理请求,同时这个请求又调用另一个服务。
于是作为调用者和被调用者的角色都会在这个流程图中体现。
我们结合这个流程图来分别介绍每个环节涉及的核心要素。
首先我们看到的是动态服务发布,它指的是计算节点上无停机发布新的服务。
它可以用于无停机升级,同时,在一些大规模计算过程中,有时候需要动态调度一些闲置计算节点临时参与某个任务,不需要提前部署,这时候也需要用到这个功能。
有动态发布,当然也有动态禁用。
选中的计算节点接受到任务后,需要通过任务调度模块进行任务的处理,任务调度是整个分布式平台的核心,是弹性可伸缩的关键。
当该任务被调度器选中进行处理时,选中的计算节点需要完成运行时的服务绑定。
前面讲到的动态服务发布是声明能力,动态绑定是真正的加载能力。
当这个服务作为调用者需要调用别的服务的时候,首先需要发现服务,这时候就会用到的第一个功能就是路由和负载均衡功能。
然后选择好提供服务的计算节点后,需要通过通讯协议和数据协议的运行时支持完成请求和响应数据的封装和传输。
在分布式平台的处理过程中,因为有多个节点同时参与计算,于是有很多数据需要在多个节点间共享和流动,因此数据一致性是分布式系统非常核心的一个要素,几乎贯穿在各个地方,渗透在所有环节中。
而我们反复提及的RASP则是分布式系统的命脉。
每个点都需要完整的设计。
比如可靠性的一个核心功能就是系统的健康性监控和问题隔离。
监控应该提供应用级监控和操作系统级监控两个级别。
一旦发现应用故障,可靠性需要完成自动隔离和恢复以降低系统的抖动。
可用性是分布式系统区别于单机系统的一个重要特征。
一旦实现高可用,不论再怎么强调“sharenothing”,“无中心”,必然会出现中心节点,那么中心节点的可用性问题就更加复杂,随之而来的选举制度、数据同步、时间窗口都需要考虑。
可伸缩性包括两个方面。
一个是水平可伸缩,实现在一个平台上无限拓展支撑的业务类型,这时候就需要分布式平台本身足够薄,普适性强,支持更多类型的应用,必须是“微内核”架构。
另一个是垂直可伸缩,针对同一类业务实现计算资源的无停机扩展/压缩。
可伸缩后,核心管理节点的瓶颈问题非常严峻,如何解决它的高吞吐量是需要重点考量的。
性能则是分布式的终极目标了。
分布式平台本质上就是为性能和高可用服务的。
OA&M,Monitoring是作为一个分布式平台必备的管理和运维功能。
上面我们针对分布式平台的各个环节涉及的主要元素应该做的事情以及部分的实现方法做了个简单的介绍,我们选择其中三点再展开重点介绍,其它点以后有机会再和大家探讨。
6、任务分配:
路由和负载均衡
图11
先看一下路由和负载均衡。
分布式系统的核心目标就是分配尽可能多的任务给尽可能多的计算节点同时完成。
这个分配工作包含两部分:
路由和负载均衡。
路由解决的是谁能干的问题。
它包括静态路由、发现动态发布的服务、数据驱动路由、会话驱动路由、时间驱动路由、应用定制路由,负载驱动路由。
负载驱动路由就是负载均衡,它是路由的后半程,决定谁干最合适,只有很多人能干才有负载均衡。
负载均衡可以是集群、机器、进程、线程、服务、连接级别的。
路由和负载均衡的算法并不复杂,复杂的是支撑这些算法的数据从哪里来,就是路由和负载数据。
围绕这些数据,会有两种管理角色在分布式平台中存在:
数据协调者和任务分配者。
数据协调者负责维护路由和负载信息的共享。
它的架构有几种。
一种是单中心节点,很多集群都采用这种方案。
它的缺点很明显,单点故障,性能有瓶颈。
于是会衍生出一写一读,或者一写多读,单点故障变成多点故障。
带来的问题就是数据同步
还有一种方案是多中心节点,多写多读。
这种架构会产生若干冲突和时间窗口,对于路由这样的需要绝对精准的数据,基本无法采用。
但是对于类似负载的数据,它更新形式都是替换,而且允许数据不完全一致,是可以采用的。
再有的方案是多数据分区中心节点,和数据库的数据库分片一样。
但是会产生数据间依赖、死锁、分布式事务等问题,逻辑也更加复杂。
围绕路由和负载数据的另一个角色是任务分配者。
它根据共享的路由和负载信息分配任务,大部分系统是把路由和负载均衡的分配放在一起的。
它同样可以有单中心节点,弊端也是一样的。
任务分配者的另一种设计是无中心自主分配,所有节点都是读节点。
这种设计的好处是只要系统还剩下一个节点,业务交易都不会停止,客户端完成路由、负载均衡,更灵活高效,可用性更高。
但是数据同步问题同样严峻,而且除了节点间有数据同步,节点内也有数据同步,更加复杂。
我们发现一旦为了提高性能和可用性,都不可避免的产生数据一致性的问题。
这个是分布式系统中最头疼的问题之一。
我们先通过几个具体的场景看一下都有哪些数据一致性问题。
7、典型的数据一致性问题
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 分步 架构 设计 分析