大道至简
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 . 保持产品的竞争力