架构技术的思维与衡量指标
一、ROI
ROI一般指投资回报率,投资回报率=年利润或年均利润/投资总额×100%,比如投入100元,获得利润10,000元,那ROI就是10,000/100*100%=10,000%,如果ROI小于100%,那意味着投入100元,获得的利润少于100元,大家都知道,这是一个不划算的买卖。
此外,ROI可以作为企业生产的一个基础指标,我们可以把ROI理解为投入产出比——在做任何决策之前,先考虑清楚这件事值不值得去做,我们可以通过经验预判或数据分析得出结论,前辈告诉我们:在错误的方向上努力只会让你输得更惨,所以为什么说选择比努力更重要,其中就是这个道理。
比如说,如果花费一个月时间去优化一个简单的客服系统,只是为了让客服体验更好,并没有对客户留存、转化交易起任何效果,也没有大幅提升客服效率,那做这个事情就不划算,ROI小于1,价值点不高。
当然,ROI指标的不足之处是缺乏全局观念,因为我们还需要考虑投资的隐性回报,比如营销部门在一次营销活动中投放CPS信息流,投入总成本10万元,共赚取利润10万元,并获客1万,单从金钱成本上看是没有任何效益的,但这次营销活动相当于免费获客1万,这些客户后续是可以在商城内二次交易的,那么从全局上看,总的ROI就大于100%。
ROI在架构上常用于判断技术决策、项目决策等等在当前阶段值不值得投入人力、时间成本去执行。
二、SOLID
SOLID原则包括以下五个:
1、单一职责原则(SRP)
表明一个类有且只有一个职责。一个类就像容器一样,它能添加任意数量的属性、方法等。
2、开放封闭原则(OCP)
一个类应该对扩展开放,对修改关闭。这意味一旦创建了一个类并且应用程序的其他部分开始使用它,就不应该修改它。简单地说:就是当别人要修改软件功能的时候,使得他不能修改我们原有代码,只能新增代码实现软件功能修改的目的。
3、里氏替换原则(LSP)
派生的子类应该是可替换基类的,也就是说任何基类可以出现的地方,子类一定可以出现。值得注意的是,当通过继承实现多态行为时,如果派生类没有遵守LSP,可能会让系统引发异常。
4、接口隔离原则(ISP)
表明类不应该被迫依赖他们不使用的方法,也就是说一个接口应该拥有尽可能少的行为,它是精简的,也是单一的。
5、依赖倒置原则(DIP)
表明高层模块不应该依赖低层模块,相反,他们应该依赖抽象类或者接口。这意味着不应该在高层模块中使用具体的低层模块。
我们就来盘一盘他们之间的关系:
- 单一职责是所有设计原则的基础,开闭原则是设计的终极目标。
- 里氏替换原则强调的是子类替换父类后程序运行时的正确性,它用来帮助实现开闭原则。
- 而接口隔离原则用来帮助实现里氏替换原则,同时它也体现了单一职责。
- 依赖倒置原则是过程式编程与面向对象编程的分水岭,同时它也被用来指导接口隔离原则。
简单来说,就是——
依赖倒置原则告诉我们要面向接口编程;当我们面向接口编程之后,接口隔离原则和单一职责原则又告诉我们要注意职责的划分,不要什么东西都塞在一起;当我们职责捋得差不多的时候,里氏替换原则告诉我们在使用继承的时候,要注意遵守父类的约定;而上面说的这四个原则,它们的最终目标都是为了实现开闭原则。
三、拆分/解耦思维
拆分常用于描述组织拆分、业务拆分、数据拆分、服务拆分等,其中的拆分依据和拆分粒度是我们需要关注的重点,也就是说——为什么这么拆?要拆成什么样才算合理?
“合起来不是挺好的吗,为什么要拆?拆了还会有一堆问题,整体复杂度还变高了!”
乍一听觉得这么质疑挺合理的,但咱们从整体上考虑,或许就变得不那么合理了。
我们从团队、产品、交付、技术以及业务方面分析一下系统拆分的需求:
1、组织结构变化
从最初的一个团队逐渐成长并拆分为几个团队,团队按照业务线不同进行划分,为了减少各个业务系统和代码间的关联和耦合,几个团队不再可能共同向一个代码库中提交代码,必须对原有系统进行拆分,以减少团队间的干扰。
2、安全性
这里所指的安全不是系统级别的安全,而是指代码或成果的安全,尤其是对于很多具有核心算法的系统,为了代码不被泄露,需要对相关系统进行模块化拆分,隔离核心功能,保护知识产权。
3、替换性
有些产品为了提供差异化的服务,需要产品具有可定制功能,根据用户的选择自由组合为一个完整的系统,比如一些模块,免费用户使用的功能与收费用户使用的功能肯定是不一样的,这就需要这些模块具有替换性,判断是免费用户还是收费用户使用不同的模块组装,这也需要对系统进行模块化拆分。
4、交付速度
单体程序最大的问题在于系统错综复杂,牵一发而动全身,也许一个小的改动就造成很多功能没办法正常工作,极大的降低了软件的交付速度,因为每次改动都需要大量的回归测试确保每个模块都能正确工作,因为我们不清楚改动会影响到什么,所以需要做大量重复工作,增加了测试成本,这时候就需要对系统进行拆分,理清各个功能间的关系并解耦。
5、技术需求
1)单体应用由于技术栈固定,尤其是比较庞大的系统,不能很方便地进行技术升级,或者说对引入新技术或框架等处于封闭状态,而每种语言都有自己的特点,单体程序没有办法享受到其它语言带来的便利,对应到团队中,团队技术相对比较单一。
2)相比于基于业务的垂直拆分,基于技术的横向拆分也很重要,使用数据访问层可以很好的隐藏对数据库的直接访问、减少数据库连接数、增加数据使用效率等;横向拆分可以极大地提高各个层级模块的重用性。
6、业务需求
由于业务上的某些特殊要求,比如对某个功能或模块的高可用性、高性能、可伸缩性等方面的要求,虽然也可以将单体整体部署到分布式环境中实现高可用、高性能等,但是从系统维护的角度来考虑,每次改动都要重新部署所有节点,显然会增加很多潜在的风险和不确定性因素,所以有时候不得不选择将那些有特殊要求的功能从系统中抽取出来,独立部署和扩展。
从更大的范围来看,拆分可以分为两种:纵向和横向。
纵向拆分主要从业务角度进行,根据业务分割为不同的子系统;而横向拆分侧重于技术的分层,每个层级的技术侧重点不同,可以充分发挥和培养团队中每个人的技术特长。
四、隔离思维
我们再来聊聊隔离思维,我们做技术的一定要有隔离思维,不要把多件事情混为一谈,这样我们才能聚焦重点。
隔离设置,源于轮船的设计,在轮船设计中,我们常常会设计多个船舱,每个船舱都是独立的空间,这样子,当轮船在行驶过程中,即便某个船舱遭受破坏进水,也有船舱能够正常工作,从而保证整个轮船不会沉没。
每一位架构师、程序员、运维工程师都必须懂得隔离设计,在分布式系统中,隔离设计的实现有两种不同的方式,一是系统隔离,二是用户隔离。
1、系统隔离
在分布式系统中,我们常常把不同的模块部署到不同的机器上面,避免不同的模块彼此之间受到影响(每台计算机的资源都是有限的,特别是IO密集型、CPU密集型的模块,容易拖垮其他业务)。
除此之外,我们还要对底层的存储与上层的接入层进行分离。
在实际的应用中,我们通常会对不同的不同业务的存储进行数据库拆分,而在接入层,常常为了节约成本,而使用限流设计。
2、用户隔离
另外一种方式,我们常常根据用户进行隔离,不同的用户访问不同的运行实例,这种在大型互联网公司也是非常常见的,例如阿里巴巴有北京、上海、杭州、深圳等多个不同的数据中心,不同的用户访问不同的系统实例。
不同的用户群访问不同的实例群,可以让隔离做得更加彻底,但同时也是伴随着非常大的挑战,比如我们会面临着存储、不同实例间的通信等多种问题。
而隔离的底层逻辑,映射到计算机系统层面,都是对CPU、内存、网络、IO或存储层面的隔离,避免计算机资源互相干扰。
在我们日常的技术交流中,我们通过隔离性来判断业务、系统以及代码之间会不会互相产生影响、系统的边界是否清晰,最终让我们实现的系统更加稳定。
五、ACID
ACID指的是事务的四个特征,分别是原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation) 和持久性(Durability),简称为事务的ACID特性,狭义的ACID指的是数据库事务的ACID。
1、原子性(Atomicity)
一个事务必须被视为一个不可分割的最小工作单元,整个事务中的所有操作要么全部提交成功,要么全部失败回滚,不可能停滞在中间某个环节,对于一个事务来说,不可能只执行其中的一部分操作。
2、一致性(Consistency)
数据库总是从一个一致性的状态转换到另一个一致性的状态。
3、隔离性(Isolation)
通常来说,一个事务所做的修改在最终提交以前,对其他事务是不可见的(在并发环境中,并发的事务是相互隔离的,事务之间互不干扰),隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。
针对不同的业务需求,隔离性分为4个级别:读未提交、读已提交、可重复读、串行化。
这4个级别的隔离性依次增强,事务隔离级别越高,就越能保证数据的完整性和一致性,但同时对并发性能的影响也越大。
4、持久性(Durability)
通常来说,一旦事务提交,则其所做的修改会永久保存到数据库,即使系统崩溃,修改的数据也不会丢失。
在我们的实际应用中,我们大可以通过数据库事务的ACID特性来满足实际业务场景,所以咱们做技术的一定要把ACID牢记于心,随时调用。
六、CAP、BASE
有一句话不知道大家有没有听过,叫做“CAP走天下”,CAP为何如此重要呢?
这里先科普一下:
CAP定理表示在分布式系统中的三大特性:一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance),三者之间形成一个不可能三角,即在一个分布式系统内不能同时满足强一致、高可用和分区容错。
CAP定理
CAP定理在分布式架构上指导我们,系统应该如何在CP和AP中进行权衡,因为分布式系统做了分区,一定是先满足分区容错性的。
比如说常用的分布式配置中心与服务注册中心,我们允许系统之间存在短暂的数据不一致,也要保证系统的高可用,以免影响系统的正常运作,所以这种分布式系统可以采用AP+最终一致的方案去设计;至于涉及到交易场景的业务,比如银行转账这类交易场景,以CP的方式去设计更加严谨。
那CP系统就不能保证高可用了吗?当然不是。
咱们对于可用性也有别的方法去实现,比如在CP之上嵌套使用AP系统负责CP系统的高可用,在一个大型分布式系统中,都是CP+AP相互结合使用的。
那BASE又是什么呢?
BASE理论是Basically Available(基本可用),Soft State(软状态)和Eventually Consistent(最终一致性)三个短语的缩写,它是对CAP定理中一致性和可用性权衡的结果,其来源于对大规模互联网分布式系统实践的总结。
BASE理论的底层逻辑是这样的:通过基本可用、软状态来牺牲CAP中的可用性,通过最终一致性来牺牲CAP中的强一致性。
BASE是基于CAP逐步演化而来的:即使无法做到强一致性,但应用可以采用适合的方式达到最终一致性,CAP中提到的一致性是强一致性,所谓“牺牲一致性”指牺牲强一致性保证弱一致性。
七、分布式事务
随着架构的演进,系统与数据库进行了不同程度的拆分,系统之间由进程内通讯变成通过网络IO通讯,相应地,事务的ACID也被散落到各个单体应用中,不再能够服务于有ACID需求的业务场景。
我们此时就需要有一个能满足ACID特性的分布式事务解决方案,来解决业务场景。
怎么办呢,数据库的ACID是在进程内通讯的,因为在一个单体应用中,这很好办到,那在分布式系统也可以做到类似的设计,区别在于通讯方式由进程内通讯变成了网络IO通讯,由此前辈们提出了一些分布式事务解决方案。
我们把事务的范围上升到分布式系统的一个“上帝视角”,那么单体事务就变成了全局事务,以下是X/Open组织提出的DTP模型:
DTP模型
在DTP分布式事务模型中,XA规范除了定义的RM-TM交互的接口,即TM与数据库之间的接口规范,TM还用它来通知数据库事务的开始、结束以及提交、回滚等;而XA接口函数由数据库厂商提供。
分布式通信协议XA规范:
- 第一步:AP创建了RM1 RM2的JDBC连接。
- 第二步:AP通知生成全局事务ID,并把RM1 RM2注册到全局事务ID。
- 第三步:执行二阶段协议中的第一阶段prepare。
- 第四步:根据第一阶段中的prepare情况,决定整体提交或回滚。
在XA规范中大致分为两部分:事务管理器和本地资源管理器。
其中本地资源管理器往往由数据库实现,比如Oracle、DB2这些商业数据库都实现了XA接口,而事务管理器作为全局的调度者,负责各个本地资源的提交和回滚。
两阶段提交(2PC)、三段式提交(3PC)就是基于XA规范落地的,此外还有TCC协议,也能满足我们实际的业务场景。
八、系统容量预估
系统容量指系统所能承受的最大访问量,而系统容量预估则是在峰值流量到达之前系统架构师所给出的若干技术指标值,它是架构师必备的技能之一。
常用的技术指标值有:QPS、PV、UV、并发量、带宽、CPU使用率、内存硬盘占用率等。
QPS(Query Per Second)表示每秒查询量,在分布式系统中 QPS 的定义是,单个进程每秒请求服务器的成功次数,QPS一般可以通过压力测试工具测得,例如 LoadRunner、Apache JMeter、NeoLoad、http_load等。
QPS = 总请求数 / 进程总数 / 请求时间 = 总请求数 / ( 进程总数 * 请求时间 )
UV(Unique Visitor)表示独立访客数量,指一定时间范围内站点访问所来自的IP数量,同一IP多次访问站点只计算一次,一般以24小时计算。
PV(Page View)表示页面访问量,指一定时间范围内打开或刷新页面的次数,一般以24小时计算。
那并发量、带宽这些具体有没有可量化的公式呢?答案是YES。
1、带宽计算
平均带宽的计算公式为:
平均带宽 = 总流量数(bit) / 产生这些流量的时长(秒)=(PV * 页面平均大小 * 8) / 统计时间(秒)
公式中的 8 指的是将 Byte 转换为 bit,即 8b/B,因为带宽的单位是 bps(比特率),即bit per second,每秒二进制位数,而容量单位一般使用 Byte。
假设某站点的日均 PV 是 10w,页面平均大小 0.4 M,那么其平均带宽需求是:
平均带宽 = (10w * 0.4M * 8) / (60 * 60 * 24) = 3.7 Mbps
以上计算的仅仅是平均带宽,我们在进行容量预估时需要的是峰值带宽,即必须要保证站点在峰值流量时能够正常运转。
假设峰值流量是平均流量的5倍,这个5倍称为峰值因子,按照这个计算,实际需要的带宽大约在 3.7 Mbps * 5=18.5 Mbps 。
带宽需求 = 平均带宽 * 峰值因子
2、并发量计算
并发量,也称为并发连接数,一般是指单台服务器每秒处理的连接数。
平均并发连接数的计算公式是:
平均并发连接数 = (站点 PV * 页面平均衍生连接数)/(统计时间 * web 服务器数量)
页面平均衍生连接数是指,一个页面请求所产生的 http 连接数量,如对静态资源的 css、 js、 images 等的请求数量,这个值需要根据实际情况而定。
例如,一个由5台web主机构成的集群,其日均PV是50w,每个页面平均30个衍生连接,则其平均并发连接数为:
平均并发量 = (50w * 30) / (60 * 60 * 24 * 5) = 35
若峰值因子为 6,则峰值并发量为:
峰值并发量 = 平均并发量 * 峰值因子 = 35 * 6 = 210
3、服务器预估量
根据往年同期活动获得的日均 PV、并发量、页面衍生连接数,及公司业务扩展所带来的流量增长率,就可以计算出服务器预估值。
服务器预估值 = 站点每秒处理的总连接数 / 单机并发连接数 = (PV * 页面衍生连接数*(1 + 增长率)) / 统计时间 / 单机并发连接数
注:统计时间,即 PV 的统计时间,一般为一天。