架构师之路总结01
1. 互联网架构设计如何进行容量评估:
【步骤一:评估总访问量】 -> 询问业务、产品、运营
【步骤二:评估平均访问量QPS】-> 除以时间,一天算4w秒
【步骤三:评估高峰QPS】 -> 根据业务曲线图来
【步骤四:评估系统、单机极限QPS】 -> 压测很重要
【步骤五:根据线上冗余度回答两个问题】 -> 估计冗余度与线上冗余度差值
2. 单点系统架构的可用性与性能优化
1)单点系统存在的问题:可用性问题,性能瓶颈问题
2)shadow-master是一种常见的解决单点系统可用性问题的方案
3)减少与单点的交互,是存在单点的系统优化的核心方向,常见方法有批量写,客户端缓存
4)水平扩展也是提升单点系统性能的好方案
3. 负载均衡
负载均衡(Load Balance)是分布式系统架构设计中必须考虑的因素之一,它通常是指,将请求/数据【均匀】分摊到多个操作单元上执行,负载均衡的关键在于【均匀】。
1)【客户端层】到【反向代理层】的负载均衡,是通过“DNS轮询”实现的
2)【反向代理层】到【站点层】的负载均衡,是通过“nginx”实现的
3)【站点层】到【服务层】的负载均衡,是通过“服务连接池”实现的
4)【数据层】的负载均衡,要考虑“数据的均衡”与“请求的均衡”两个点,常见的方式有“按照范围水平切分”与“hash水平切分”
4. 如何实施异构服务器的负载均衡及过载保护?
1)service的负载均衡、故障转移、超时处理通常是RPC-client连接池层面来实施的
2)异构服务器负载均衡,最简单的方式是静态权重法,缺点是无法自适应动态调整
3)动态权重法,可以动态的根据service的处理能力来分配负载,需要有连接池层面的微小改动 (权重值,成功 +1,失败 -10)
4)过载保护,是在负载过高时,service为了保护自己,保证一定处理能力的一种自救方法()
5)动态权重法,还可以用做service的过载保护
动态权重是用来标识每个service的处理能力的一个值,它是RPC-client客户端连接池层面的一个东东。服务端处理超时,客户端RPC-client连接池都能够知道,这里只要实施一些策略,就能够对“疑似过载”的服务器进行降压,
而不用服务器“抛弃请求”这么粗暴的实施过载保护。
应该实施一些什么样的策略呢,例如:
1)如果某一个service的连接上,连续3个请求都超时,即连续-10分三次,客户端就可以认为,服务器慢慢的要处理不过来了,得给这个service缓一小口气,于是设定策略:接下来的若干时间内,
例如1秒(或者接下来的若干个请求),请求不再分配给这个service;
2)如果某一个service的动态权重,降为了0(像连续10个请求超时,中间休息了3次还超时),客户端就可以认为,服务器完全处理不过来了,得给这个service喘一大口气,于是设定策略:
接下来的若干时间内,例如1分钟(为什么是1分钟,根据经验,此时service一般在发生fullGC,差不多1分钟能回过神来),请求不再分配给这个service;
3)可以有更复杂的保护策略…
这样的话,不但能借助“动态权重”来实施动态自适应的异构服务器负载均衡,还能在客户端层面更优雅的实施过载保护,在某个下游service快要响应不过来的时候,给其喘息的机会。
需要注意的是:要防止客户端的过载保护引起service的雪崩,如果“整体负载”已经超过了“service集群”的处理能力,怎么转移请求也是处理不过来的,还得通过抛弃请求来实施自我保护。
5. 主从DB与cache一致性
在“异常时序”或者“读从库”导致脏数据入缓存时,可以用二次异步淘汰的“缓存双淘汰”法来解决缓存与数据库中数据不一致的问题,具体实施至少有三种方案:
1)timer异步淘汰(本文没有细讲,本质就是起个线程专门异步二次淘汰缓存)
2)总线异步淘汰
3)读binlog异步淘汰
6. DB主从一致性架构优化4种方法
为了解决主从数据库读取旧数据的问题,常用的方案有四种:
1)半同步复制:
a. 系统先对DB-master进行了一个写操作,写主库
b. 等主从同步完成,写主库的请求才返回
c. 读从库,读到最新的数据(如果读请求先完成,写请求后完成,读取到的是“当时”最新的数据)
方案优点:利用数据库原生功能,比较简单
方案缺点:主库的写请求时延会增长,吞吐量会降低
2)强制读主
如果不使用“增加从库”的方式来增加提升系统的读性能,完全可以读写都落到主库,这样就不会出现不一致了:
方案优点:“一致性”上不需要进行系统改造
方案缺点:只能通过cache来提升系统的读性能,这里要进行系统改造
3)数据库中间件
如果有了数据库中间件,所有的数据库请求都走中间件,这个主从不一致的问题可以这么解决:
a. 有的读写都走数据库中间件,通常情况下,写请求路由到主库,读请求路由到从库
b. 记录所有路由到写库的key,在经验主从同步时间窗口内(假设是500ms),如果有读请求访问中间件,此时有可能从库还是旧数据,就把这个key上的读请求路由到主库
c. 经验主从同步时间过完后,对应key的读请求继续路由到从库
方案优点:能保证绝对一致
方案缺点:数据库中间件的成本比较高
4)缓存记录写key
使用缓存,当写请求发生的时候:
a. 将某个库上的某个key要发生写操作,记录在cache里,并设置“经验主从同步时间”的cache超时时间,例如500ms
b. 修改数据库
而读请求发生的时候:
a. 先到cache里查看,对应库的对应key有没有相关数据
b. 如果cache hit,有相关数据,说明这个key上刚发生过写操作,此时需要将请求路由到主库读最新的数据
c. 如果cache miss,说明这个key上近期没有发生过写操作,此时将请求路由到从库,继续读写分离
方案优点:相对数据库中间件,成本较低
方案缺点:为了保证“一致性”,引入了一个cache组件,并且读写数据库时都多了一步cache操作
7. mysql并行复制降低主从同步延时的思路与启示
从mysql并行复制缩短主从同步时延的思想可以看到,架构的思路是相同的:
(1)多线程是一种常见的缩短执行时间的方法
(2)多线程并发分派任务时必须保证幂等性:mysql的演进思路,提供了“按照库幂等”,“按照commit_id幂等”两种方式,思路大伙可以借鉴
另,mysql在并行复制上的逐步优化演进:
mysql5.5 -> 不支持并行复制,对大伙的启示:升级mysql吧
mysql5.6 -> 按照库并行复制,对大伙的启示:使用“多库”架构吧
mysql5.7 -> 按照GTID并行复制
8. 即使删了全库,保证半小时恢复
保证数据的安全性是DBA第一要务,需要进行:
(1)全量备份+增量备份,并定期进行恢复演练,但该方案恢复时间较久,对系统可用性影响大
(2)1小时延时从,双份1小时延时从能极大加速数据库恢复时间
(3)个人建议1小时延时从足够,后台只读服务可以连1小时延时从,提高资源利用率
9. 啥,又要为表增加一列属性?
1)方案一:版本号+通用列
2)方案二:通过扩展行的方式来扩展属性
10. 这才是真正的表扩展方案
1)常见“新表+触发器+迁移数据+rename”方案(pt-online-schema-change),这是业内非常成熟的扩展列的方案
以user(uid, name, passwd)
扩展到user(uid, name, passwd, age, sex)为例
基本原理是:
(1)先创建一个扩充字段后的新表user_new(uid, name, passwd, age, sex)
(2)在原表user上创建三个触发器,对原表user进行的所有insert/delete/update操作,都会对新表user_new进行相同的操作
(3)分批将原表user中的数据insert到新表user_new,直至数据迁移完成
(4)删掉触发器,把原表移走(默认是drop掉)
(5)把新表user_new重命名(rename)成原表user
扩充字段完成。
优点:整个过程不需要锁表,可以持续对外提供服务
操作过程中需要注意:
(1)变更过程中,最重要的是冲突的处理,一条原则,以触发器的新数据为准,这就要求被迁移的表必须有主键(这个要求基本都满足)
(2)变更过程中,写操作需要建立触发器,所以如果原表已经有很多触发器,方案就不行(互联网大数据高并发的在线业务,一般都禁止使用触发器)
(3)触发器的建立,会影响原表的性能,所以这个操作建议在流量低峰期进行
pt-online-schema-change是DBA必备的利器,比较成熟,在互联网公司使用广泛。
2)哪些方案一定是不行的
(1)alter table add column
要坚持这个方案的,也不多解释了,大数据高并发情况下,一定不可行
(2)通过增加表的方式扩展,通过外键join来查询
大数据高并发情况下,join性能较差,一定不可行
(3)通过增加表的方式扩展,通过视图来对外
一定不可行。大数据高并发情况下,互联网不怎么使用视图,至少58禁止使用视图
(4)必须遵循“第x范式”的方案
一定不可行。互联网的主要矛盾之一是吞吐量,为了保证吞吐量甚至可能牺牲一些事务性和一致性,通过反范式的方式来确保吞吐量的设计是很常见的,例如:冗余数据。互联网的主要矛盾之二是可用性,
为了保证可用性,常见的技术方案也是数据冗余。在互联网数据库架构设计中,第x范式真的没有这么重要
3)哪些方案可行,但文章未提及
(1)提前预留一些reserved字段
这个是可以的。但如果预留过多,会造成空间浪费,预留过少,不一定达得到扩展效果。
(2)通过增加表的方式扩展列,上游通过service来屏蔽底层的细节
这个也是可以的。Jeff同学提到的UserExt(uid, newCol1, newCol2)就是这样的方案(但join连表和视图是不行的)
11. 一分钟掌握数据库垂直拆分
当一个表属性很多时,如何来进行垂直拆分呢?如果没有特殊情况,拆分依据主要有几点:
1)将长度较短,访问频率较高的属性尽量放在一个表里,这个表暂且称为主表
2)将字段较长,访问频率较低的属性尽量放在一个表里,这个表暂且称为扩展表
如果1和2都满足,还可以考虑第三点:
3)经常一起访问的属性,也可以放在一个表里
优先考虑1和2,第3点不是必须。另,如果实在属性过多,主表和扩展表都可以有多个。
一般来说,数据量并发量比较大时,数据库的上层都会有一个服务层。需要注意的是,当应用方需要同时访问主表和扩展表中的属性时,服务层不要使用join来连表访问,而应该分两次进行查询:
原因是,大数据高并发互联网场景下,一般来说,吞吐量和扩展性是主要矛盾:
1)join更消损耗数据库性能
2)join会让base表和ext表耦合在一起(必须在一个数据库实例上),不利于数据量大时拆分到不同的数据库实例上(机器上)。毕竟减少数据量,提升性能才是垂直拆分的初衷。
为什么要这么这么拆分
为何要将字段短,访问频率高的属性放到一个表内?为何这么垂直拆分可以提升性能?因为:
(1)数据库有自己的内存buffer,会将磁盘上的数据load到内存buffer里(暂且理解为进程内缓存吧)
(2)内存buffer缓存数据是以row为单位的
(3)在内存有限的情况下,在数据库内存buffer里缓存短row,就能缓存更多的数据
(4)在数据库内存buffer里缓存访问频率高的row,就能提升缓存命中率,减少磁盘的访问
举个例子就很好理解了:
假设数据库内存buffer为1G,未拆分的user表1行数据大小为1k,那么只能缓存100w行数据。
如果垂直拆分成user_base和user_ext,其中:
(1)user_base访问频率高(例如uid, name, passwd, 以及一些flag等),一行大小为0.1k
(2)user_ext访问频率低(例如签名, 个人介绍等),一行大小为0.9k
那边内存buffer就就能缓存近乎1000w行user_base的记录,访问磁盘的概率会大大降低,数据库访问的时延会大大降低,吞吐量会大大增加。