架构设计的原则

时间:2021-07-08 10:27:40

大道至简

1 . 避免过度设计

  • 产品的设计和实施超过实际需求

eg 空调的室内高温设计到可以最高达到300F(约140C)
这样会严重的影响开发进度和研发的成本,功能也不会被使用到

  • 把事情做得过于复杂

eg select * from...当我们需要某个或者某些字段的值时, 却查询全部,然后把其它的值丢弃,类似超时购物时,我们挑选了一购物车,结算时,却只要其中的一件

  • 功能设计过于冗余

eg 在微博系统添加导出为pdf文件的功能、这个是完全不太可能会被用到的功能,平白的增添系统开发的周期和系统维护的成本,又没有任何实际的作用

  • 简化代码的书写

在可以使用简单的代码实现的情况下,不使用偏僻的东西去实现,代码应该让每个人都读的懂,而不是只有自己才能明白

2 . 方案中包含扩展

提供及时可扩展的DID方法(Design、Implement、Deploy)(也就是设计、实施、部署)

  • 设计之初就考虑系统扩展

可能刚开始系统流量很小,快速开发,快速上线,但必须思考流量增加时系统可以从哪方面去扩展

  • 实施

设计系统现有能力的10-100倍,实施的时候,只需要保留3~20倍的动态扩展力即可

  • 部署

实际部署的能力只需要和预算的流量承载相符,略大1~2倍应付突发状况即可,这样成本也是最低的

3 . 三次简化方案

在设计复杂系统时,从项目的范围,设计和实施角度简化方案

  • 简化不必要的功能
  • 在已有开源项目时,可以在开源项目的基础上修改,没必要完全自己开发

4 . 减少域名解析

从用户角度减少域名解析次数,尽量使用本地文件,使用网络文件时,尽量使用同一域的

5 . 减少页面目标

尽可能的减少网页上的对象数量

减少或者合并对象,但要平衡最大连接并发数(图片合并CSS定位,浏览器自身可以并行下载,把对象数拆分成浏览器可并行下载的数量,不浪费其并行下载的能力,对于每一个子域名,浏览器允许拥有自己的最大连接并发数,子域会被认为是不同的域,js合并,但要注意全部合并后会出现js下载完成之前所有的js效果都不可用的现象,各种组合需要通过测试不断去平衡)
寻找机会减少对象的重量
不断测试确保性能的提升
CSS放到页面的顶部,js放到页面的底部、缩小文件、使用缓存、延迟加载

6 . 采用同构网络

应该让使用到的路由器和交换机是属于同一个供应商的

分而治之

7 . X轴扩展

通常叫水平扩展,通过复制服务或者数据库以分散事务处理带来的负载(负载均衡、读写分离)

8 . Y轴拆分

在服务或资源的边界拆分数据集、交易或者技术团队
eg:根据行为去拆分 登录、注册、商品查询等对数据库不同方向上的需求去连接不同服务的数据库
团队过大时,沟通交流的成本会明显增加,可以根据服务拆分团队,拆分代码库

9 . Z轴拆分

根据用户的独特属性,(ID/name/location等)进行拆分,方便进行按域进行服务治理

7、8、9综合起来也就是主从+负载均衡拆分不同东西(服务拆分)、拆分类似东西(根据地域或者用户来拆分)

水平扩展

10 . 向外扩展

向外扩张是用复制或者服务拆分来解决问题,向上扩张是增加硬件配置,就好比是增加服务器的数量和增大服务器的配置的问题

11 . 尽可能的采用微微超出需求的设备和系统,而不是预留很多空间,大部分时候会造成浪费,需要扩展的时候去扩展即可

12 . 托管方案扩展

可以使用自有设备、托管或者云计算等不同方式,实现可用性增加和容灾备份

数据多活的设计:

动态路由到附近的数据中心是客户的响应效率更高
在Saas环境中产品有更大的灵活性
使用Paas/Saas时,可以快速的动态扩展或者收缩
但是,操作的复杂度增高,人员数量要增加,出差和网络成本增加
应注意:尽量减少对状态的需要,减少动态调用,按地区路由用户、多研究数据库和状态的复制技术

13 . 利用云

可以通过简单的购买配置,快速的实现服务的动态扩展和收缩
比如:批量处理、测试环境、峰值容量等

先利其器

当你只有一个锤子的时候,任何东西看起来都是一颗钉子

14 . 选择合适的数据库
15 . 合理使用防火墙而不是滥用
16 . 使用日志文件

画龙点睛

17 . 避免画蛇添足
18 . 尽量减少重定向(尽量使用conf文件而不是.htaccess文件)
19 . 放宽时间约束,不必要求同步完成显示

缓存为王

20 . 利用CDN缓存
21 . 灵活管理缓存
22 . 利用Ajax缓存

keep-alive:允许多个http请求使用同一个tcp连接
expires
cache-control
last-modified | if-modified-since
Etag | if-none-match

23 . 利用页面缓存

使用反向代理、反向代理缓存、反向代理服务器

24 . 利用应用缓存

要最大化缓存的影响,先考虑拆分应用架构

25 . 利用对象缓存

有重复查询或者计算的时候
缓存查询结果、计算结果等

26 . 独立对象缓存

在架构中采用单独的对象缓存层
分离应用服务器和缓存服务器,防止缓存占用大量的内存资源,这样也方便水平扩展

前车之鉴

27 . 失败是成功之母

抓住每一次可以学习的机会,从别人或者自己失败的案例中成长

28 . 依靠QA发现问题,但不依赖于QA

优秀的QA团队可以快速的发现系统故障,也降低了开发工程师的测试时间

29 . 不能回滚注定失败

数据库变更脚本化,数据语义变更必须在代码变更之后
上下线代码必须可以随时回滚

30 . 事务处理尽量少的事
31 . 注意昂贵的关系

数据库最初设计时应该考虑到水平拆分和随业务拆分的能力

32 . 正确使用数据库锁

它会影响到最大数据库的并发和吞吐量

锁的类型 基本描述
暗锁 数据库在代表用户执行某些事务时产生的锁,一般
都在必要时为某些DML任务产生
明锁 用户自己在程序内部定义
行锁 会锁定正在读取更新或者创建的行
页锁 会锁定正在更新的一行或者是一组行锁属的整个页面
区间锁 通常锁定页面组,向数据库添加空间时,区间锁会用到
表锁 锁定整个表
数据库锁 锁定数据库中所有的实例和关系

数据库锁的种类很多,还有键锁和索引锁等

33 . 禁用分阶段提交

最常用的是两阶段提交和三阶段提交

两阶段提交:首先主存储通知所有其它存储可以提交、然后第二阶段是所有成员开始提交事务,整个过程中,若有一个成员提交失败,向所有成员发出回滚的信号,告知事务失败,假设此时主存储(协调员)失效,其它成员将永远完成事务处理,一直被锁定

34 . 慎用select for update

for update 会产生行锁,可能会减缓事务处理的速度

35 . 避免选择所有列

在查询语句中声明要选择或者插入的数据列,避免不必要的数据传输
insert的时候插入列名,避免不必要的麻烦

有备无患

36 . 使用泳道进行故障隔离

故障隔离包括根除故障隔离域见的同步请求,限制异步调用和处理同步调用失败,以及避免泳道之间的数据和服务共享
泳道之间不共享,包括网络组件、数据库、服务器
泳道之间不同步调用,因为同步调用会捆绑服务,调用失败会蔓延到其它系统
限制泳道之间的同步调用,限制跨越泳道界限的事务处理数量
必要时,实现跨泳道的异步通信
eg 商店服务和订单服务的拆分,可以使得订单服务故障时,商店依然可以下单,并将订单信息放入队列,故障恢复后可以处理订单;商店故障时,订单服务依然可以处理订单,只是暂时不能接收新的订单消息

37 . 拒绝单点故障

使用负载均衡,在不同的服务实例之间实现流量平衡,对需要使用单例的情形,可以直接使用控制服务

38 . 避免系统串联

串联组件会使服务故障的可能性加大,且具有蔓延性

39 . 启用和禁用功能

搭建一个框架来启用或者禁用功能,尽量避免手动操作,减少误操作的几率
eg 基于超时的自动关闭,给予服务失败的自动切换,基于特殊情况的人工可控,利用配置文件修改服务,利用文件关闭、运行时数量的改变

超然物外

40 . 力求无状态

在设计新的系统或者重新设计现有系统的时候,进来选择无状态方案,有状态可能会限制系统的扩展性,降低可用性并且增加系统成本

必须使用有状态时
41 . 在浏览器中保持会话数据

优:系统不需要存储数据,但是会给存储和检索带来大量的额外负担
来自浏览器的请求可以由任何服务器来处理,需要延X轴水平扩展web服务器时,可以很容易
但是,这样每次和服务器的交互就需要传输大量的数据,cookie也容易被劫持和盗用

42 . 利用分布式缓存处理状态

任何需要存储会话数据,又不能在浏览器中存储的情况
不要把缓存放在实际执行任务的服务器上,我们不能同时失去服务器信息和相关信息的缓存
不要使用状态或者会话复制来产生不同系统上的数据副本,会话的修改需要传播到多个节点,这样会导致可扩展性变差

异步通信

43 . 尽可能早的实现异步通信
44 . 扩展消息总线
44 . 避免总线过度拥挤

意犹未尽

46 . 警惕第三方方案

扩展自己的系统,不要依赖于供应商来实现扩展性

47 . 阶梯存储策略

将存储成本与数据价值相匹配,删除价值低于存储价值的数据

48 . 分类处理不同的负载

49 . 完善监控系统,及时发现异常服务

50 . 保持产品的竞争力