架构设计思考-1
Posted 勇敢的菜鸡
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了架构设计思考-1相关的知识,希望对你有一定的参考价值。
目录
应用的服务化改造
应用分层设计
- 通常从垂直方向划分应用,分成服务层、业务逻辑层和数据层,每一层尽量做到解耦,上层依赖下层,而下层不要反向依赖上层
- 分层涉及最怕超级数据结构,如传递一个对象,然后把这个对象一直传递下去,而且每个层都可能修改这个对象,这种做法会导致两个问题:
- 一旦该对象修改,所有层都要进行修改
- 无法知道该对象在哪一层被修改过,排查问题比较复杂
因此,在设计接口时尽量使用原生数据类型,如int, string等
大中台、小前台
系统规模的发展
第一阶段:单系统
早期业务简单,几台机器撑起一个业务系统。所有的业务逻辑都部署在一台几台(甚至1台)机器上
第二阶段:分布式业务系统
单系统所有业务逻辑杂糅,问题百出,功能开发和问题排查困难
分布式业务系统:将原来的单一系统拆分成多个高内聚、低耦合的中心化系统。比如用户中心、商品中心、交易中心等等。。。
“高内聚、低耦合”:个人理解,功能高度内聚,中心与中心之间低耦合
第三阶段:平台化阶段
对于每个独立的高内聚业务系统,为了快速应对业务需求,很多时候都是通过代码来写业务逻辑;但是不同的业务,对应的业务逻辑必然会有所不同,就会导致代码层面逻辑复杂,业务之间逻辑耦合严重;最终影响研发效率,平台化应运而生
什么是平台? 就是要把基础能力和每个业务方的特性拆分,隔离业务和业务之间的逻辑。比如说两个相似的业务方有可能是冲突的,但他们需要在同一个平台上执行,这时我们必须把业务的逻辑分开。
平台化最核心的要点,是业务抽象建模和系统架构的开放性;业务抽象解决80%的共性问题,系统架构的开放解决20%的个性化问题
平台化通过合理的业务抽象建模,可以解决80%的共性问题;往往新业务的接入,可以复用这80%的共性,大大提高研发效率
个人理解:
- 平台化最核心就是业务抽象建模;“高内聚”必然可以抽象出很多通用的逻辑
- 平台只是解决了领域内部的问题
第四阶段:业务中台阶段
平台化的问题在于,每个业务都是跨几个甚至十几个领域的,而且相互关联,时间久了之后没人能够说清全局,单纯通过开发人员翻查代码了解细节,成本极高;最终往往形成需求评估效率低,业务响应速度差等问题;根源在于没人了解整个链路,非技术问题,而是复杂生态的协作问题
业务中台化在平台的基础上需要解决协作问题,主要通过三样东西
- 协议标准,运行机制
- 满足标准的分布式执行单元
- 中心化的控制单元
业务中台是一套由业务能力标准、运行机制、业务分析方法论、配置管理、执行系统以及运营服务团队构成的体系,能够给各业务方提供快速、低成本的接入和创新能力
个人理解:
- 中台必须要构建自己的一套协议标准,业务方接入是要按照中台的协议来的
- 中台是一套完整的“运产研测”体系,运营、产品在宏观上把控中台的发展方向,研发测试从技术角度规范化中台能力
中台
中台的定位
所谓定位就是要告诉别人我能做什么、我需要什么和我不要什么。
- 我能做什么,这是中台的基本盘。中台一定是提供了明确的、高内聚的功能,业务方有这方面的功能第一时间想到的就是这个中台
- 我需要什么,这是中台必须有的协议和规范;不论是新业务的接入,还是老业务的功能迭代,一定是基于这套规范进行;接入中台需要哪些数据、提供什么样的交互方式等等,要按照中台的规范来做
- 我不要什么,这是中台需要明确的界限;中台可以提供什么样的服务是有界限的。
中台的效率提升
- 沟通效率问题
- 统一术语;尤其是在运营产品和开发人员的沟通中,术语不统一会造成非常大的沟通障碍(需要关注一些形成数据的技巧,比如起一个好名字)
- 结构化表达需求:把产品需求通过一系列的术语、图表、页面等可以更好理解和呈现的方式在同一个语境中表达出来,让对方可以更好的理解
- 统一业务身份
- 开发效率
- 测试效率
- 运维效率
应用程序优化: 代码级优化
了解系统瓶颈
一个请求到服务端后会消耗各种系统资源,服务器本身会消耗CPU、内存、网络、磁盘等资源,第三方会消耗数据库连接数、redis连接数等,同时数据库、缓存本身也有性能问题需要考虑;正是这些方方面面的资源存在上限,并发请求量到达一定数量之后,某个资源可能被消耗完,从而导致系统崩盘;
作为一个系统架构师,必须了解自己系统的承载能力,以及系统短板(出现问题后可以迅速定位短板并解决问题)
一些重要的系统指标
- CPU
- 内存
- 网络(网卡流量)
- IO
- QPS
- 接口响应时间(平均、P90、P95、P99等)
- 数据库
- 客户端连接池连接数
- 服务端连接数
- QPS
- CPU
- 锁冲突
- 锁等待
- 慢查询
- redis连接数
- 客户端连接池连接数
- 服务端连接数
- QPS
- 内存使用率
- 慢查询
- 大key
大部分指标都可以从监控系统中查到;作为系统架构师(开发工程师),日常应该多关注这些指标,了解系统能力,发现系统问题
语言层面的编码建议
- 尽量使用局部变量
- 局部变量在栈内创建,速度快,且函数执行完后即销毁;不占内存,不用额外的垃圾回收
- 减少方法调用
- 每一次方法调用都会产生函数栈,开销相对较大;尽量让其成为局部变量,可减少函数调用次数
- 减少并发冲突
- 减少序列化
- 序列化与反序列化消耗较大
- 使用长连接
- 不用新建连接,减少连接使用成本
小钢的架构思考:架构设计
原创文章,转载请注明:转载自Keegan小钢
并标明原文链接:http://keeganlee.me/post/architecture/20160621
微信订阅号:keeganlee_me
写于2016-06-21
小钢的架构思考:什么是架构
小钢的架构思考:架构规划
小钢的架构思考:架构设计
最近一个多月因为忙于工作上的项目重构,所以文章一直没能更新。现在,重构终于暂时告一段落,于是,赶紧抽时间把文章写完更新发布。下面进入正文。
当架构规划的结果,整理出一堆不同优先级的需求,尤其是质量需求之后,接下来就要思考如何才能最大限度地实现这些需求,这就是架构设计要解决的问题。那么,如何进行架构设计呢?设计到什么程度才合适呢?我从架构思维和架构原则方面来思考架构设计的问题。
架构思维
这里说的架构思维是指进行架构设计时最高层级的思考方式,比如:面向过程、面向对象、面向切面、面向服务等。
面向过程(Procedure Oriented)
面向过程的设计思路就是将问题分解成一个个步骤,按照步骤一步步执行之后,问题就解决了。每一个步骤就是一个子过程,也可以称为一个模块,子过程还可以继续拆分成更多更细的子过程。因此,面向过程的设计核心就是过程分析、功能分解,一般采用自顶向下、逐步求精的分解方式。一个大的程序可以分解成多个子程序,子程序再分解成多个大模块,大模块再分解成多个小模块,最终分解成一个个函数。
在此我想借用一个象棋对战的例子,例子来源于一篇很老的文章:架构师之路(4)---详解面向对象。以下是采用面向过程的设计思路分解的对战流程图:
将以上每个流程分别用函数实现,问题就解决了。
面向过程的优点主要有两个:一是流程清晰简单;二是性能比较高。尤其是性能,这也是为什么至今很多单片机开发、驱动程序开发、或其他与硬件相关的系统开发等对性能要求很高的软硬件程序依然在用面向过程的方式进行设计和开发。
面向过程的缺点也很明显:一是主程序太重,主程序与模块承担的任务不均衡;二是函数不易扩展,导致其可扩展性、可复用性、可维护性相对都比较差;三是上下层级模块之间的联系太紧密,耦合高,所以模块也难以复用。
面向对象(Object Oriented)
面向过程的思路是“怎么做”,关注于实现细节;而面向对象的思路是“谁来做”,关注于抽象的对象。对象的封装、继承和多态等特性,让我们以更接近现实世界的方式来思考程序设计。面向对象相比面向过程容易实现更好的分离,相应地可扩展性、可复用性、可维护性也会比较高,但同时会牺牲掉一些性能。不过,也因为硬件发展迅猛,所以牺牲的那点性能也不算什么了。
面向对象设计的难点在于抽象,从问题域中抽象出一个个对象,并找出它们之间的关系。好在有SOLID原则和一大堆设计模式指导我们如何更好地设计。也有领域驱动设计的方法论指导我们怎么进行领域建模。
还是象棋对战的例子,用面向对象的设计思路,可以抽象出以下三种对象:
- 棋手:负责行棋,红黑两方行为一致。
- 棋盘:负责绘制棋盘画面。
- 裁判:负责判定吃子、犯规和输赢等。
三者关系如下图:
棋手对象行棋后,棋盘对象根据棋子布局的变化刷新棋盘画面,裁判对象则对棋局进行判定。
面向切面(Aspect Oriented)
面向切面,也就是AOP,是对面向对象的一种扩展,为了弥补面向对象的局限性。面向对象设计主要是对业务领域进行抽象封装,但对于业务领域之外的内容,比如日志记录、权限检查、事务支持等,在没有AOP之前,只能将实现这些功能的代码散布在所有对象层次中,但这些代码与所散布的对象的核心业务功能是没任何关系的。这种做法也导致了大量重复的代码,而且难以复用。AOP就是为了解决这种问题而产生的,将这些与业务领域无关的部分分离出来,以横切面的方式注入系统,从而减少重复代码、减低耦合度、增强扩展性和维护性。
将日志记录、权限检查、事务支持等等使用横切技术分别独立成一个个服务模块,这些模块也称为“横切面”,这样就可以将这些与业务无关的服务从业务核心中解耦出来,就可以将系统划分为两部分:业务核心和通用服务。业务核心依然采用面向对象的思路去设计,而通用服务则可以采用面向切面的思想来实现。
Spring就大量使用了AOP技术,OkHttp的Interceptor也是AOP设计的一种实现。很多场景都可以使用AOP的思想去设计,比如添加统一的Http Request Header,添加统一的登录验证,添加统一的缓存,添加统一的错误处理,等等,只要是通用的功能点基本都可以使用AOP的思想去设计和实现。
面向服务(Service Oriented)
不管是SOA还是现在流行的微服务架构,都是采用面向服务的思维方式。说到面向服务,需要先了解一个概念:Monolith,也称为单体架构。在没有SOA思想之前,软件系统将所有功能整合成一个独立的软件包,然后部署在单一的平台上。比如,在J2EE平台,一个软件系统最终会打成一个包含所有功能的WAR包,然后部署到Web容器中。若要扩展的话,则通过复制这个WAR包部署到多个Web容器来实现。这种方式,如果程序需要改动,不管多么微小的改动,都需要重新打包个新的WAR包,并替换掉所有Web容器的旧WAR包。
面向服务的架构思想则是,将系统的不同功能分离成一个个单独的应用程序或组件,统称为服务,不同服务部署在不同容器中,不同服务之间通过一些轻量级的交互机制来通信,如HTTP,RPC等。这样,相比单体架构,功能服务之间明显是松耦合的,扩展也会灵活很多。而且,不同服务还可以用不同编程语言实现,部署到不同平台。
不管是面向过程,面向对象,面向切面,还是面向服务,最本质的区别还是在于看问题的角度不同。而在实际应用中,也不会只使用一种架构思维,而是综合考虑的,系统的不同方面或不同层级可能会用不同的架构思维去思考。比如,一个庞大的复杂系统,整体上可能用面向服务的架构思维去拆解各种服务,业务核心方面的服务可能再用面向对象的架构思维进行建模,通用功能服务还是用面向切面的架构思维来设计,事务流程当然是采用面向过程的架构思维最直观。
架构原则
架构思维从面向过程,到现在的面向服务,以后也不知道还会出现什么新的思维方式。但无论是何种思维方式,都存在一些共通性的架构原则,可以指导我们如何设计出一个合适的架构。从另一方面来说,架构设计,不管是面向过程、面向对象、面向切面,还是面向服务,无一例外,主要都是在对复杂的系统进行分解。那么,相应地,就需要思考三个问题:分解为哪些?如何分解?分解到什么程度?相对应地,有三个重要原则可以分别为解答这三个问题提供指引。
关注点分离原则
关注点分离原则主要就是为了解决将复杂系统分解为哪些部分的问题,分解出来的部分就是关注点。过程、对象、切面、服务,只是分解的角度(也是关注点)不同而已。将复杂的问题根据不同的关注点分解为多个相对简单的问题,再对每个简单的问题进行分别处理,这就是关注点分离。分离之后,各个关注点相对独立,每个关注点的变化基本不会影响到其他的关注点,即使需要改变,改变的部分也很小。需要扩展时,影响也将会最小化。
关注点分离,最难的在于如何识别出有哪些关注点。要识别出有哪些关注点,需要将复杂系统不同的方方面面抽象成一个个具有清晰明确的边界的概念模型,或为“对象”,或为“组件”,或“切面”,或“服务”,以将复杂问题分解为一个个相对简单的问题。
从不同维度,可以有不同的分离方案。除了上面提到的面向过程、面向对象、面向切面、面向服务等思维角度之外,还有如下图所示的其他几种不同维度,该图引自《软件架构设计》一书中的【2.1.1 关注点分离之道】一节:
上图分别从功能职责、通用性、大小粒度的不同维度进行分离。从职责维度进行分离,就可以分为三层架构:展现层、业务层、数据层,相应的关注点就是:数据展示、数据加工、数据管理。另外,数据层还可以再分离为网络层和缓存层。从通用性维度来看,就可以分离出技术通用部分、领域通用部分、特定应用部分。一般,使用框架技术就可以用于分离各种不同的通用部分。从大小粒度的维度考虑,无非就是将复杂系统分离为各个子系统,再分离为不同模块,再细分到不同类。
在实际应用中,并不会只采用一种维度,而是多种维度综合考虑,不同部分采用不同维度的分离方案。比如,也许,整体上按职责分离为多层架构,然后,在某些层级根据大小粒度再进行分离,例如将业务层按照不同业务模块进行分离。另外,也会将不同的通用部分进行分离,例如可将技术通用部分的日志记录、领域通用部分的权限检查分别分离出来。
《架构就是关注点分离》这篇文章则描述了更多关注点分离的例子。
高内聚低耦合原则
系统应该如何分解?或者说关注点应该如何分离?高内聚低耦合原则就可以为该问题提供设计指引。
内聚是指模块内部的功能和元素之间的紧密程度,而耦合则是指模块与模块之间的关联程度。
内聚可分为好多种:功能内聚、顺序内聚、通信内聚、过程内聚、时间内聚、逻辑内聚、偶然内聚。功能内聚是最强最好的内聚,模块内各元素共同协作完成一个单一的功能,这些元素紧密联系、缺一不可。顺序内聚则是指,模块中各个处理元素和同一个功能密切相关,而且这些处理必须顺序执行,通常前一个处理元素的输出时后一个处理元素的输入。顺序内聚的内聚度也比较高,但相比功能内聚,缺点就是可维护性相对差些。偶然内聚则是最弱的内聚,模块内的各元素之间没有任何联系,只是偶然地被凑到一起。
耦合也分为好多种:非直接耦合、数据耦合、标记耦合、控制耦合、外部耦合、公共耦合、内容耦合。非直接耦合表示两个模块之间没有直接关系,它们之间的联系完全是通过主模块的控制和调用来实现的,其耦合度是最弱的,模块独立性最强。数据耦合表示调用模块和被调用模块之间只传递简单的数据项参数,相当于高级语言中的值传递。标记耦合也称为特征耦合,表示调用模块和被调用模块之间传递的不是简单数据,而是数据结构,像高级语言中的数据名、记录名和文件名等数据结果,这些名字即为标记,其实传递的是地址。控制耦合则表示模块之间传递的不是数据信息,而是控制信息例如标志、开关等,一个模块控制了另一个模块的功能。外部耦合则是指一组模块都访问同一全局简单变量,而且不通过参数表传递该全局变量的信息。内容耦合则是一个模块直接访问另一模块的内容,这是最强的耦合。
高内聚的设计原则是说:一个模块只完成一个单一的功能,尽可能使模块达到功能内聚。
低耦合的设计原则是说:若模块间必须存在耦合,应尽量使用数据耦合,少用控制耦合,慎用或有控制地使用公共耦合,并限制公共耦合的范围,尽量避免内容耦合。
适度设计
适度设计原则关注的就是系统分解到什么程度的问题。适度设计就是指设计不要过度,也不要不足。那么,怎样才算设计过度?怎样才算设计不足?一句话,设计过度就是想太多,设计不足就是想太少。感觉好虚,是吧?我也这么觉得。因为,如何判断一个设计是否过度或不足,并没有标准的可量化指标。因此,设计是否适度,更多在于主观的判断。而如何避免设计过度或不足,更多的也在于个人经验积累所形成的直觉。
设计不足相对还比较容易判断,导致设计不足的原因主要有两个:一是因为新手的设计经验不足而导致;二是因为一味追求快速实现产品功能而跳过或大幅度减少了设计而导致。
也有些设计过度比较明显的例子,比如Uncle Bob提出的Clean架构,每个关注点都有着清晰明确的边界,架构真的很清晰,可维护性、可测试性都非常不错,高内聚低耦合。但是,如果将其应用到一个只有两三个开发人员的小团队的小项目中,就会明显发现代码量大而且复杂,每需要添加一个小功能,却需要编写大量代码。这对一个小团队小项目来说,明显不适合。Clean架构比较适用于人员较多的团队,和中大型项目。
因此,判断设计是否适度,不能脱离团队和项目的现状。另外,还有其他现状因素,包括各种商业需求、功能需求和质量需求。大部分情况下,形成过度设计的原因在于:一是过多地考虑了未来可能发生的变化;二是为了追求设计而设计。适度设计,首先应该着眼于当下,当下的需求、当下的开发成本、当下的人员和项目现状;其次才是适当考虑如何应对未来的变化。对于未来的变化,也不是任何可能都要考虑,只需考虑在可预见的未来里有非常大的几率会发生的变化即可,这个非常大的几率可以达到90%以上。比如,已经确定要实现的需求,只是因为优先级问题而稍微延后;比如,已经确定的人员扩充计划;比如,双11要搞活动,交易量将会激增;等等。
也就是说,适度设计的原则,可以总结为:设计应该优先满足当前确定的需求,再满足可预见未来里几乎可以确定会发生的需求。只满足当前需求而不考虑未来,就容易导致设计不足;而过多地考虑未来可能发生的需求,就容易导致设计过度。因此,适度设计需要在当前需求和未来需求之间做好平衡,而我觉得只考虑当前需求和未来几乎确定会发生的需求是最好的平衡点。
写在最后
本来计划还想再谈谈架构风格,比如分层架构、MVC\MVP\MVVM、RESTFul、Clean架构等等。但因为这篇文章的进度已经拖了一个多月,不能再拖了。因此,决定本文就此收手,那些架构风格以后有机会再讲吧。
扫描以下二维码即可关注订阅号。
以上是关于架构设计思考-1的主要内容,如果未能解决你的问题,请参考以下文章