微服务的拆分一直是一门学问,拆的好,服务会具有很好的闭合性和延展性,拆的不好,拆分一时爽开发运维两行泪。
老规矩,先以灵魂拷问来梳理一下今天要聊的主题:
微服务到底该怎么拆?
微服务架构有哪些常见的模型?
微服务、中台、领域驱动设计之间有什么关系?
微服务的业务边界怎么设计?
在分布式架构下,为了保证微服务的单一职责和合理拆分,我们通常的做法是“高内聚、低耦合”。
高内聚:把高度相关的服务进行收敛聚合,如果要修改某个服务,最好只在一处修改。
低耦合:低耦合的服务应该尽可能少的知道上下游的服务信息。
从单体应用向分布式架构的转型,不仅仅是技术架构的更新,更要有组织、文化、理念和设计方法的配套更新。
“高内聚、低耦合”的架构设计到底该怎么做呢?我整理了一下几种典型的微服务架构模型。
微服务架构模型
整洁架构
在整洁架构里,同心圆代表各种不同领域的软件。一般来说,越深入代表你的软件层次越高。外圆是战术实现机制,内圆的是战略核心策略。
使此体系架构能够工作的关键是依赖规则。这条规则规定源代码只能向内依赖,在最里面的部分对外面一点都不知道,也就是内部不依赖外部,而外部依赖内部。这种依赖包含代码名称,或类的函数,变量或任何其他命名软件实体。
同样,在外面圈中使用的数据格式不应被内圈中使用,特别是如果这些数据格式是由外面一圈的框架生成的。我们不希望任何外圆的东西会影响内圈层。
整洁架构各层主要职能如下:
实体Entities:实体封装的是企业业务规则,一个实体能是一个带有方法的对象,或者是一系列数据结构和函数,只要这个实体能够被不同的应用程序使用即可。
如果你没有编写企业软件,只是编写简单的应用程序,这些实体就是应用的业务对象,它们封装着最普通的高级别业务规则,你不能希望这些实体对象被一个页面的分页导航功能改变,也不能被安全机制改变,操作实现层面的任何改变不能影响实体层,只有业务需求改变了才可以改变实体。
用例Use Cases:在这个层的软件包含应用指定的业务规则,它封装和实现系统的所有用例,这些用例会混合各种来自实体的各种数据流程,并且指导这些实体使用企业规则来完成用例的功能目标。
我们并不期望改变这层会影响实体层. 我们也不期望这层被更外部如数据库 UI或普通框架影响,这层也是因为关注而外部分离的。
我们期望应用层面的技术操作都不能影响用例层,如果需求中用例发生改变,这个层的代码才会发生改变。
接口适配器Interface Adapters:这一层的软件基本都是一些适配器,主要用于将用例和实体中的数据转换为外部系统如数据库或Web使用的数据,在这个层次,可以包含一些GUI的MVC架构,表现视图 控制器都属于这个层,模型Model是从控制器传递到用例或从用例传递到视图的数据结构。
通常在这个层数据被转换,从用例和实体使用的数据格式转换到持久层框架使用的数据,主要是为了存储到数据库中,这个圈层的代码是一点和数据库没有任何关系,如果数据库是一个SQL数据库, 这个层限制使用SQL语句以及任何和数据库打交道的事情。
框架和驱动Frameworks and Drivers:最外面一圈通常是由一些框架和工具组成,如数据库Database, Web框架等. 通常你不必在这个层不必写太多代码,而是写些胶水性质的代码与内层进行粘结通讯。这个层是细节所在,Web技术是细节,数据库是细节,我们将这些实现细节放在外面以免它们对我们的业务规则造成影响伤害。
六边形架构
Hexagonal Architecture(六角形或六边形) 于2005年由Alistair Cockburn撰写,是一个具有许多优势的软件架构,自2015年以来又重新引起了人们的兴趣。
六边架构的初衷是:允许应用程序同样由用户,程序,自动化测试或批处理脚本驱动,并与最终的运行时设备和数据库隔离开发和测试。
六角形架构允许隔离应用程序的核心业务,并自动测试其行为,而不依赖于其他任何事情。这可能是该架构引起域驱动设计(DDD)从业者关注的原因。但要小心,DDD和六边形结构是两个相当不同的概念,它们可以相互加强,但不一定一起使用。
六边形体系结构基于三个原则和技术:
明确区分应用程序,领域和基础结构三个层
依赖关系是从应用程序和基础结构再到领域
我们使用端口和适配器隔离它们的边界
分层架构与领域驱动设计DDD:
架构分层的一个重要原则是每层只能与位于其下方的层发生依赖。
首先,合理的系统分层使得我们可以专注于本层的设计,而不必关心其他层的设计,也不必担心自己的设计会影响其它层,层与层之间的独立性,可以提高软件的质量。另外,分层架构使得代码结构清晰,升级和维护都比较容易,更改某层的代码,只要本层的接口保持稳定,其他层可以不必修改。即使本层的接口发生变化,也只影响相邻的上层,修改工作量小且错误可以控制,不会带来意外的风险。
要保持程序分层架构的优点,就必须保证层与层之间的低耦合特性。架构设计时,应先明确需要划分的层次,以及该层的入口和出口路径。对每一层的设计,要尽量保持层间的隔离,保证层与层之间的依赖性,仅使用下层提供的接口。
领域驱动设计分层架构各层定义与职能:
展现层:负责向用户显示信息和解释用户命令。这里指的用户可以是另一个计算机系统,不一定是使用用户界面的人。
应用层:它是很薄的一层,定义软件要完成的任务,并且指挥表达领域概念的对象来解决问题。这一层所负责的工作对业务来说意义重大,也是与其它系统的应用层进行交互的必要渠道。应用层要尽量简单,不包含业务规则或者知识,而只为下一层中的领域对象协调任务,分配工作,使它们互相协作。它没有反映业务情况的状态,但是却可以具有另外一种状态,为用户或程序显示某个任务的进度。
领域层:负责表达业务概念,业务状态信息以及业务规则。尽管保存业务状态的技术细节是由基础设施层实现的,但是反映业务情况的状态是由本层控制并且使用的。领域层是业务软件的核心,领域模型位于这一层。
基础设施层:向其他层提供通用的技术能力:为应用层传递消息,为领域层提供持久化机制,为用户界面层绘制屏幕组件,等等。基础设施层还能够通过架构框架来支持四个层次间的交互模式。
架构模型对比和分析
虽然整洁架构、六边形架构以及 DDD 分层架构三种架构模型展现方式以及解决问题的出发点不一样,但其架构思想与微服务架构高内聚低耦合的设计原则高度一致。
突破现象看本质,这些架构产生的系统特点是:
独立的框架. 这样的架构并不依赖与应用软件的具体库包,这样可以将框架作为工具,而不必将你的系统都胡乱混合在一起。
可测试. 业务规则能够在没有UI和数据库 或Web服务器的情况下被测试。
UI的独立性. UI改变变得容易,不必改变系统的其余部分,一个Web UI能被一个控制台或专门的图形UI替代, 这些读不必更改业务核心规则。
数据库的独立性. 你能够在Oracle或SQL Server Mongo, BigTable, CouchDB,或之间切换, . 你的业务规则不会和数据库绑定
独立的外部代理,其实你的业务规则可以对其外面的技术世界毫无所知,比如是否使用了MVC或DCI都可以不关心。
中台和微服务设计的关键在于合理的分层和领域模型的设计!
聚焦领域模型
中台属于后端业务领域逻辑范畴,重点关注领域内业务逻辑的实现,通过实现公共需求为前台应用提供共享服务能力。按 DDD 的方法,在领域模型建立的过程中会对业务和应用进行清晰的逻辑和物理边界划分。领域模型的设计结果会影响到后续的系统模型、架构模型和领域层代码模型的设计,最终影响到微服务的拆分和项目落地实施。
合理的架构分层
不要把与领域无关的业务逻辑放在领域层,避免领域业务逻辑被污染,保证领域层的纯洁,只有这样才能降低领域逻辑受外部变化的影响。在领域和架构模型建立后,代码模型的逻辑分层和微服务拆分要具体情况具体分析,根据自身研发和运维能力综合考虑。
对于企业级多中台应用,多个中台应用通过 API 网关对外发布 API 服务。核心域业务中台在调用支撑域和通用域中台服务时通过核心域应用层完成多中台服务的组合和编排,为前台应用提供 API 服务。核心域中台的应用层是否独立成微服务部署,需考虑的情况与单应用系统相似。
服务的管理
应用层、领域层和基础设施层都有对应的服务,各司其职提供服务,其中基础设施层的服务通过依赖反转模式为领域层和应用层提供基础设施资源服务。应用层和领域层服务发布在 API 网关,通过 API 网关适配,为前台提供用户无差异化(应用 app、批处理或自动化测试)的服务。
资源的适配和解耦
由于上述架构模型中定义的外层只能依赖内层的架构原则,对于像数据库、缓存、文件系统等的外部基础设施资源,往往采用依赖反转的模式对外提供资源服务,实现应用层、领域层与基础设施层资源的解耦。在设计中应考虑资源层的代码适配逻辑,一旦基础设施资源出现变更(如换数据库),可以屏蔽资源变更对业务代码带来的影响,切断业务逻辑对基础资源的依赖,降低由于资源变更对业务逻辑的影响。
前台应用
从核心业务逻辑来看,中台实现了主要的业务逻辑,属于标准化的重量级应用。前台应用聚焦于界面交互以及业务流程等,属于轻量级应用,前台应用可以有个性的业务逻辑、流程和配置数据,甚至数据库,通过调用中台 API 服务完成交互界面和业务全流程。
中台、领域驱动设计及微服务
分析和设计模式的演进
软件分析和设计方法经历了三个阶段的演进:
第一阶段是单体应用时代:采用面向过程的设计方法,系统包括 UI 层和数据库两层,采用 C/S 架构模式,整个系统围绕数据库驱动设计和开发,新项目总是从设计数据库及其字段开始。
第二阶段是集中式架构时代:采用面向对象的设计方法,系统包括 UI 层、业务逻辑层和数据库层,采用经典的三层架构,也有部分应用采用传统的 SOA 架构,这种架构易使服务变得臃肿,难于维护拓展,伸缩性能差。这个阶段系统分析、软件设计和开发大多是分阶段进行的。
第三阶段是分布式架构时代:由于微服务架构的流行,采用领域驱动设计方法,应用系统包括 UI 层、应用层、领域层和基础层。这个阶段融合了分析和设计阶段,通过建立领域模型,划分领域边界,做到领域模型既设计,代码与设计保持一致。
领域驱动设计主要优势:1. 业务导向。2. 业务逻辑内聚,应用边界清晰。3. 建立领域模型优先。4. 分析、设计、代码和数据有机结合。5. 代码即设计。6. 扩展性好。
数据驱动设计主要特点:1. 技术导向。2. 数据库优先。3. 代码不能反映业务和设计。4. 业务逻辑分散。5. 扩展性不好。
领域驱动设计(DDD)是一种处理高度复杂域的设计思想,试图分离技术实现的复杂性,围绕业务概念构建领域模型来控制业务的复杂性,以解决软件难以理解,难以演化等问题。团队利用它可以成功的开发复杂业务软件系统,在系统变大时仍能保持敏捷性。
领域驱动设计分为两个阶段:
1. 以一种领域专家、设计人员、开发人员都能理解的通用语言作为相互交流的工具,在交流的过程中发现领域概念,然后将这些概念设计成一个领域模型;
2. 由领域模型驱动软件设计,用代码来实现该领域模型。
领域驱动设计的核心诉求是让业务架构和系统架构形成绑定关系,当我们去响应业务变化调整业务架构时,系统架构的改变也会随之发生。在领域驱动设计中业务架构的梳理和系统架构的梳理是同步进行的,其结果是设计出的业务上下文和系统模块结构是绑定的。同时技术架构也是解耦的,可以根据划分出来的业务上下文的系统架构选择最合适的实现技术。
领域驱动设计包括战略设计和战术设计两个部分。战略设计主要关注按领域定义,在限界上下文内形成统一语言,提升业务和技术的沟通效率;战术设计主要关注领域设计在落地时与设计模型及实现模型的差异性,减小业务和技术之间的鸿沟。
领域驱动设计的优点:
1、领域驱动设计是一套完整而系统的设计方法,它能带给你从战略设计到战术设计的规范过程,使得你的设计思路能够更加清晰,设计过程更加规范。
2、领域驱动设计尤其善于处理与领域相关的高复杂度业务的产品研发,通过它可以为你的产品建立一个核心而稳定的领域模型内核,有利于领域知识的传递与传承。
3、领域驱动设计强调团队与领域专家的合作,能够帮助团队建立一个沟通良好的团队组织,构建一致的架构体系。领域驱动设计强调对架构与模型的精心打磨,尤其善于处理系统架构的演进设计。
4、领域驱动设计的思想、原则与模式有助于提高团队成员的架构设计能力。
5、领域驱动设计与微服务架构天生匹配,无论是在新项目中设计微服务架构,还是将系统从单体架构演进到微服务设计,都可以遵循领域驱动设计的架构原则。
为什么领域驱动设计是微服务架构的最佳设计方法?
领域驱动设计作为一种架构设计方法,微服务作为一种架构风格,两者从本质上都是为追求高响应力目标而从业务视角去分离复杂度的手段。两者都强调从业务出发,其核心要义强调根据业务发展,合理划分领域边界,持续调整现有架构,优化现有代码,以保持架构和代码的生命力(演进式架构) 。
领域驱动设计主要关注:业务领域,划分领域边界;构建通用语言,高效沟通;对业务进行抽象,建立领域模型;维持业务和代码的逻辑一致性。
微服务主要关注:运行时进程间通信,能够容错和故障隔离;去中心化管理数据和去中心化治理;服务可以独立的开发、测试、构建和部署,按业务组织全功能团队;高内聚低耦合,职责单一。
如果你的业务焦点在领域和领域逻辑,那么你就可以选择 DDD 进行微服务架构设计。
中台、DDD 与微服务
中台的定义来源于阿里的中台战略(详见《企业 IT 架构转型之道:阿里巴巴中台战略思想与架构实战》钟华编著)。详见我的另一篇介绍中台的文章《深入浅出中台化建设》。
中台的本质是提炼各个业务条线的共同需求,并将这些功能打造成组件化产品,然后以 API 接口的形式提供给前台各业务部门使用。前台要做什么业务,需要什么资源可以直接找中台,不需要每次去改动自己的底层,而是在底层不变动的情况下,在更丰富灵活的“大中台”基础上获取支持,让“小前台”更加灵活敏捷。
领域驱动设计中领域的定义:一个领域本质上可以理解为就是一个问题域,只要是同一个领域,那问题域就相同。所以只要我们确定了系统所属的领域,那这个系统的核心业务,即要解决的关键问题、问题的范围边界就基本确定了。领域的本质是问题域,问题域可能根据需要逐层细分,因此领域可分解为子域,子域或可继续分为子子域。。。
在领域驱动设计中根据重要性与功能属性将领域分为三类子域,分别是:核心子域、支撑子域和通用子域。决定产品和企业独特竞争力的子域是核心子域,它是业务成功的主要因素和企业的核心竞争力。没有个性化的诉求,属于通用功能的子域是通用子域,如登陆认证。还有一种所提供的功能是必须的,但不是通用也不是企业核心竞争力的子域是支撑子域,如单证。
DDD: 核心域、支撑域和通用域
中台、领域以及微服务属于不同层面的内容,稍作分解我们理清他们之间的关系。
专属业务中台是企业的核心竞争力,对应 DDD 的核心子域。通用中台对应 DDD 支撑子域和通用子域。不同领域可根据领域大小进一步细分多个子域,多个子域可对应到一个业务中台,一个业务中台也可能会分解成多个子域。
中台、领域以及微服务
微服务是技术实现和部署的范畴,实现领域或中台的业务逻辑,为前台应用提供服务。领域根据限界上下文可以设计为多个微服务,而如果限界上下文过大,一个微服务也可能会包含多个子领域。
中台是由多个业务条线的共同需求所构成,是需要共享的业务功能和服务单元的集合,一个中台可由一个微服务来实现,也可根据领域驱动设计和微服务拆分原则细分为多个微服务,多个微服务功能集合共同组成一个中台。
基于 DDD 的微服务设计方法
DDD 设计包括战略设计和战术设计两个部分。在战略设计阶段主要完成领域建模和服务地图。在战术设计阶段,通过聚合、实体、值对象以及不同层级的服务,完成微服务的建设和实施。通过 DDD 可以保证业务模型、系统模型、架构模型以及代码模型的一致。
本部分主要讨论领域设计方法,如对战术设计和开发方法感兴趣可查阅 DDD 战术设计相关资料。
DDD 领域设计过程包括产品愿景、场景分析、领域建模和服务地图阶段,也可根据需要裁剪不必要的阶段和参与角色。领域驱动设计一般经历 2-6 周的时间,领域模型设计完成后,即可投入微服务实施。
1、产品愿景
产品愿景是对产品的顶层价值设计,对产品目标用户、核心价值、差异化竞争点等策略层信息达成一致,避免产品在演进过程中偏离方向。
阶段输入:产品初衷、用户研究、竞品知识和差异性想法 。
参与角⾊:业务需求方、产品经理、开发组长和产品发起人。
阶段产出:电梯演讲画布。
2、场景分析
场景分析是针对核心用户及顶层服务的一种定性分析,从⽤户视角出发,探索问题域中的典型场景分析。同时也是从用户视角对问题域的探索,产出问题域中需要支撑的场景分类及典型场景,用以支撑领域建模阶段。
阶段输⼊:核⼼干系人和服务价值定位。
参与角色:产品经理、开发组长和测试组长。
阶段产出:场景分类清单。
3、领域建模
领域建模是通过对业务和问题域进⾏分析,建⽴领域模型,向上通过限界上下⽂指导微服务的边界设计,向下通过聚合指导实体的对象设计。领域建模主要采用事件风暴方法。
阶段输入:业务领域知识和场景分类清单。
参与角色:领域专家、架构师、产品经理、开发组长和测试组长。
阶段产出:聚合模型和限界上下⽂地图。
4、服务地图
服务地图是整个产品服务架构的体现。结合业务与技术因素,对服务的粒度、边界划分、集 成关系进⾏梳理,得到反映系统微服务层面设计的服务地图。
阶段输⼊:限界上下⽂地图。
参与角⾊:产品经理、开发组长、测试组长和产品发起人。
阶段产出:服务地图。
在进行服务地图设计时需要考虑以下要素:1. 围绕限界上下⽂边界。2. 考虑不同业务变化速度 / 相关度、发布频率。3. 考虑系统非功能性需求,如系统弹性伸缩要求、安全性要求和可⽤性要求。4. 考虑团队组织和沟通效率。5. 软件包限制。6. 技术和架构的异构。
微服务的边界设计
为什么要细分业务和代码逻辑边界?
在从单体向微服务演进后,随着新需求的出现,新的微服务会开始慢慢的膨胀起来,有一天你会发现膨胀的微服务有一部分业务能力需要拆分出去时,如果没有提前进行逻辑边界的细分,微服务内代码的过度耦合将会让你无从下手,你是否还需要再做一次从单体向微服务的拆分?
如果你在微服务设计时已经根据业务领域边界提前进行了领域代码的分层和逻辑隔离,在微服务再次拆分时,分别对逻辑分离的领域代码打包,同步进行数据库拆分,就可以快速完成微服务的拆分,而不需要重复从单体应用向微服务痛苦的演进过程。
当然,在同一个微服务内逻辑隔离的代码,在内部领域服务之间调用以及数据访问设计上需要有合理的松耦合的设计和开发规范,否则也不能很快的完成微服务再次拆分。
总之,我们需要内外部逻辑边界清晰的微服务,而不是从一个大单体重构为多个小单体。
微服务的设计和拆分
微服务拆分方法
绞杀者模式
绞杀者模式类似建筑拆迁,在新建筑分阶段建设完成入住后,分步拆除旧建筑物。
“绞杀者模式”是在遗留系统外围,将新功能用新的方式构建为新的服务 。通过在新的应⽤中实现新特性,保持和现有系统的松耦合,随着时间的推移,新的服务逐渐“绞杀”老的系统。以此逐步地替换原有系统。对于那些老旧庞大难以更改的遗留系统,推荐采用绞杀者模式。
修缮者模式
修缮者模式类似文物修复,将存在问题的部分建筑重建或者修复后,重新加入到原有的建筑中,保持建筑原貌。
“修缮者模式”是在既有系统的基础上,通过剥离新业务和功能,逐步“释放”现有系统耦合度,解决遗留系统质量不稳定和 Bug 多的问题。就如修房或修路一样,将老旧待修缮的部分进行隔离,用新的方式对其进行单独修复。修复的同时,需保证与其他部分仍能协同功能。修缮模式适用于需求变更频率不高的存量系统。
微服务拆分原则
微服务拆分过程中需严格遵守高内聚、低耦合原则,同时结合项目的实际情况,综合考虑业务领域、功能稳定性、应用性能、团队以及技术等因素。
1、基于业务领域拆分,在领域模型设计时需对齐限界上下⽂,围绕业务领域按职责单一性、功能完整性进行拆分,避免过度拆分造成跨微服务的频繁调用。
2、基于业务变化频率和业务关联拆分,识别系统中的业务需求变动较频繁的功能,考虑业务变更频率与相关度,并对其进行拆分,降低敏态业务功能对稳态业务功能的影响。
3、基于应用性能拆分,考虑系统⾮功能性需求,识别系统中性能压力较大的模块,并优先对其进行拆分,提升整体性能,缩小潜在性能瓶颈模块的影响范围。
4、基于组织架构和团队规模,提高团队沟通效率。
5、基于软件包大小,软件包过大,不利用微服务的弹性伸缩。
6、基于不同功能的技术和架构异构以及系统复杂度。
分布式架构设计的关注点
企业一旦采用分布式架构和微服务技术体系,在设计时需要关注商业模式、业务边界、数据体系、微服务设计、前台交互以及多活容灾等多领域的协同。
1、数据是本难念的经
分布式架构下数据面临的问题远比集中式架构复杂。诸如:分布式数据库的选型、数据的分库和分表、数据的同步与异步、跨库和联表查询、数据的分布与集中、在线业务数据与统计分析数据的协同、集中式数据库向分布式数据库的迁移以及面向场景的集中数据复制等。
2、中台和微服务要处理好边界
条条道路通罗马,不管走哪条路,凭感觉或拍脑袋也可以设计出微服务,拆分结果可能与按照 DDD 方法出来的结果类似。但是如果有好的理论和方法指导,不但做事情有矩可循的,而且可以避免走弯路。由于 DDD 在设计的时候已经做好了逻辑的边界划分,在微服务需要组合和重新拆分时也会变得容易得多。
还是有必要提一下:中台和微服务设计可以借鉴 DDD 的设计原则和理念,不过战术设计部分由于过于复杂和学习成本过高,可以参考使用。
3、前、中台协同和前台数据的按需加载
前台应用未来可能多采用单页面(SPA)的微前端(对应于微服务的前端展现,一个微服务对应一个微前端)方式,通过前端集成框架(类似门户)实现多页面组合,提供统一的用户体验,在微服务和数据库设计时也需要协同考虑前端页面逻辑。
为减轻跨微服务的访问,前端页面展示时应以清单数据方式按需加载,后端数据设计时也应同步考虑如何组合前端数据展示。如需要展示明细数据,通过调用 API 服务的方式获取全量数据,减少不必要的跨微服务调用。
另外,符合条件的应用也可考虑页面的动静分离和路由接入,将静态页面通过 CDN 的技术,部署在靠近用户的机房,降低交互次数,减少跨广域网访问带来的网络延迟。
4、容灾和多活的全局考虑
分布式架构的高可用是在应用、数据和基础设施的分布式技术升级后,通过多数据中心协同来实现的。
为了容灾和多活,在设计方面需要考虑:1)合适的分布式数据库。2)合理的数据分库主键设计,数据的多副本和同步技术。3)单元化架构设计,处理好通用中台和专属中台的部署和依赖关系,实现业务的自包含,减少跨数据中心调用。4)访问层的接入,对外部访问进行路由、限流以及灰度发布。5)统一的全局配置数据,每个数据中心都有实时同步的全量配置数据,实现容灾和多活的一键切换。
5、避免过度拆分和硬件依赖
过度过细的微服务拆分带来更多的软件维护成本和运维压力,过多的分布式事务也会带来性能和数据一致性的压力。在进行设计时,要在保证逻辑边界清晰的情况下,严控微服务的过度拆分和采用过多的分布式事务。
分布式架构的自动的弹性伸缩大多是通过软件的方式去实现的,为保证应用的弹性伸缩能力,在设计中应实现去硬件的无中心化(如可采用软负载,就不用 F5 之类的硬负载),尽量通过软件实现弹性伸缩。因为一旦绑定硬件设备,在硬件遇到瓶颈需要自动弹性伸缩的时候,就需要人工干预,无法自动弹性伸缩。