C. 高性能架构

C. 高性能架构
	基本思路
		操作
			并发读
			并发写
		技术
			性能优化
			限流、降级
	单服务器高性能模式
		关键技术
			服务器采取的并发模型
				问题
					服务器如何管理连接。
					服务器如何处理请求。
				解决方案
					I/O 模型:阻塞、非阻塞、同步、异步。
					进程模型:单进程、多进程、多线程。
			连接/请求数量
				1. 海量连接(成千上万)海量请求:例如抢购,双十一等
				2. 常量连接(几十上百)海量请求:例如中间件
				3. 海量连接常量请求:例如门户网站
				4. 常量连接常量请求:例如内部运营系统,管理系统
		PPC:PPC 是 Process Per Connection 的缩写,其含义是指每次有新的连接就新建一个进程去专门处理这个连接的请求,这是传统的 UNIX 网络服务器所采用的模型。
		TPC:TPC 是 Thread Per Connection 的缩写,其含义是指每次有新的连接就新建一个线程去专门处理这个连接的请求。与进程相比,线程更轻量级,创建线程的消耗比进程要少得多;同时多线程是共享进程内存空间的,线程通信相比进程通信更简单。因此,TPC 实际上是解决或者弱化了 PPC fork 代价高的问题和父子进程通信复杂的问题。
		Reactor
			I/O 多路复用技术归纳起来有两个关键实现点:
				当多条连接共用一个阻塞对象后,进程只需要在一个阻塞对象上等待,而无须再轮询所有连接,常见的实现方式有 select、epoll、kqueue 等。
				当某条连接有新的数据可以处理时,操作系统会通知进程,进程从阻塞状态返回,开始进行业务处理。
			解决方案
				单 Reactor 单进程 / 线程。
				单 Reactor 多线程。
				多 Reactor 多进程 / 线程。
		Proactor:Reactor 是非阻塞同步网络模型,因为真正的 read 和 send 操作都需要用户进程同步操作。这里的“同步”指用户进程在执行 read 和 send 这类 I/O 操作的时候是同步的,如果把 I/O 操作改为异步就能够进一步提升性能,这就是异步网络模型 Proactor。
	高性能负载均衡
	高性能数据库集群
		读写分离
			读写分离的基本实现是
				数据库服务器搭建主从集群,一主一从、一主多从都可以。
				数据库主机负责读写操作,从机只负责读操作。
				数据库主机通过复制将数据同步到从机,每台数据库服务器都存储了所有的业务数据。
				业务服务器将写操作发给数据库主机,将读操作发给数据库从机。
			复杂度
				主从复制延迟
					解决方案
						1. 写操作后的读操作指定发给数据库主服务器
						2. 读从机失败后再读一次主机
						3. 关键业务读写操作全部指向主机,非关键业务采用读写分离
				分配机制
			实现方式
				1. 程序代码封装
					实现简单,而且可以根据业务做较多定制化的功能。
					每个编程语言都需要自己实现一次,无法通用,如果一个业务包含多个编程语言写的多个子系统,则重复开发的工作量比较大。
					故障情况下,如果主从发生切换,则可能需要所有系统都修改配置并重启。
				2. 中间件封装
					能够支持多种编程语言,因为数据库中间件对业务服务器提供的是标准 SQL 接口。
					数据库中间件要支持完整的 SQL 语法和数据库服务器的协议(例如,MySQL 客户端和服务器的连接协议),实现比较复杂,细节特别多,很容易出现 bug,需要较长的时间才能稳定。
					数据库中间件自己不执行真正的读写操作,但所有的数据库操作请求都要经过中间件,中间件的性能要求也很高。
					数据库主从切换对业务服务器无感知,数据库中间件可以探测数据库服务器的主从状态。例如,向某个测试表写入一条数据,成功的就是主机,失败的就是从机。
		分库分表
			问题
				数据量太大,读写的性能会下降,即使有索引,索引也会变得很大,性能同样会下降。
				数据文件会变得很大,数据库备份和恢复需要耗费很长时间。
				数据文件越大,极端情况下丢失数据的风险越高(例如,机房火灾导致数据库主备机都发生故障)。
			解决方案
				业务分库:业务分库指的是按照业务模块将数据分散到不同的数据库服务器。
					问题
						1.join 操作问题:业务分库后,原本在同一个数据库中的表分散到不同数据库中,导致无法使用 SQL 的 join 查询。
						2. 事务问题:原本在同一个数据库中不同的表可以在同一个事务中修改,业务分库后,表分散到不同的数据库中,无法通过事务统一修改。虽然数据库厂商提供了一些分布式事务的解决方案(例如,MySQL 的 XA),但性能实在太低,与高性能存储的目标是相违背的。
						3. 成本问题:业务分库同时也带来了成本的代价,本来 1 台服务器搞定的事情,现在要 3 台,如果考虑备份,那就是 2 台变成了 6 台。
				分表
					垂直分表:从上往下切就是垂直切分,因为刀的运行轨迹与蛋糕是垂直的,这样可以把蛋糕切成高度相等(面积可以相等也可以不相等)的两部分,对应到表的切分就是表记录数相同但包含不同的列。例如,示意图中的垂直切分,会把表切分为两个表,一个表包含 ID、name、age、sex 列,另外一个表包含 ID、nickname、description 列。
					水平分表:从左往右切就是水平切分,因为刀的运行轨迹与蛋糕是平行的,这样可以把蛋糕切成面积相等(高度可以相等也可以不相等)的两部分,对应到表的切分就是表的列相同但包含不同的行数据。例如,示意图中的水平切分,会把表分为两个表,两个表都包含 ID、name、age、sex、nickname、description 列,但是一个表包含的是 ID 从 1 到 999999 的行数据,另一个表包含的是 ID 从 1000000 到 9999999 的行数据。
						路由
							范围路由:选取有序的数据列(例如,整形、时间戳等)作为路由的条件,不同分段分散到不同的数据库表中。以最常见的用户 ID 为例,路由算法可以按照 1000000 的范围大小进行分段,1 ~ 999999 放到数据库 1 的表中,1000000 ~ 1999999 放到数据库 2 的表中,以此类推。
							Hash 路由:
							配置路由:
				问题
					join 操作:水平分表后,数据分散在多个表中,如果需要与其他表进行 join 查询,需要在业务代码或者数据库中间件中进行多次 join 查询,然后将结果合并。
					count() 操作:水平分表后,虽然物理上数据分散到多个表中,但某些业务逻辑上还是会将这些表当作一个表来处理。例如,获取记录总数用于分页或者展示,水平分表前用一个 count() 就能完成的操作,在分表后就没那么简单了。常见的处理方式有下面两种:
						count() 相加
						记录数表
					order by 操作
		优化步骤
			1.做硬件优化,例如从机械硬盘改成使用固态硬盘,当然固态硬盘不适合服务器使用,只是举个例子
			2.先做数据库服务器的调优操作,例如增加索引,oracle有很多的参数调整;
			3.引入缓存技术,例如Redis,减少数据库压力
			4.程序与数据库表优化,重构,例如根据业务逻辑对程序逻辑做优化,减少不必要的查询;
			5.在这些操作都不能大幅度优化性能的情况下,不能满足将来的发展,再考虑分库分表,也要有预估性
	高性能NoSql
		行式数据库:关系型数据库 缺点
			关系数据库存储的是行记录,无法存储数据结构
			关系数据库的 schema 扩展很不方便
			关系数据库在大数据场景下 I/O 较高
			关系数据库的全文搜索功能比较弱
		NoSql类型
			K-V数据库:Redis
				Redis 的缺点主要体现在并不支持完整的 ACID 事务,Redis 虽然提供事务功能,但 Redis 的事务和关系数据库的事务不可同日而语,Redis 的事务只能保证隔离性和一致性(I 和 C),无法保证原子性和持久性(A 和 D)。
			文档数据库:MongoDB
				优势
					1. 新增字段简单
					2. 历史数据不会出错
					3. 可以很容易存储复杂数据
			列式数据库
				优势
					业务同时读取多个列时效率高,因为这些列都是按行存储在一起的,一次磁盘操作就能够把一行数据中的各个列都读取到内存中。
					能够一次性完成对一行中的多个列的写操作,保证了针对行数据写操作的原子性和一致性;否则如果采用列存储,可能会出现某次写操作,有的列成功了,有的列失败了,导致数据不一致。
					除了节省 I/O,列式存储还具备更高的存储压缩比,能够节省更多的存储空间。普通的行式数据库一般压缩率在 3:1 到 5:1 左右,而列式数据库的压缩率一般在 8:1 到 30:1 左右,因为单个列的数据相似度相比行来说更高,能够达到更高的压缩率。
			全文搜索引擎数据库
				倒排
	高性能缓存架构
		问题场景
			需要经过复杂运算后得出的数据,存储系统无能为力
			读多写少的数据,存储系统有心无力
		技术点
			缓存穿透:是指缓存没有发挥作用,业务系统虽然去缓存查询数据,但缓存中没有数据,业务系统需要再次去存储系统查询数据。通常情况下有两种情况:
				1. 存储数据不存在
				2. 缓存数据生成耗费大量时间或者资源
					分页缓存的有效期设置为 1 天,因为设置太长时间的话,缓存不能反应真实的数据。
					通常情况下,用户不会从第 1 页到最后 1 页全部看完,一般用户访问集中在前 10 页,因此第 10 页以后的缓存过期失效的可能性很大。
					竞争对手每周来爬取数据,爬虫会将所有分类的所有数据全部遍历,从第 1 页到最后 1 页全部都会读取,此时很多分页缓存可能都失效了。
					由于很多分页都没有缓存数据,从数据库中生成缓存数据又非常耗费性能(order by limit 操作),因此爬虫会将整个数据库全部拖慢。
			缓存雪崩:是指当缓存失效(过期)后引起系统性能急剧下降的情况。
				解决方法
					1. 更新锁:对缓存更新操作进行加锁保护,保证只有一个线程能够进行缓存更新,未能获取更新锁的线程要么等待锁释放后重新读取缓存,要么就返回空值或者默认值。
					2. 后台更新:由后台线程来更新缓存,而不是由业务线程来更新缓存,缓存本身的有效期设置为永久,后台线程定时更新缓存。
						后台定时机制需要考虑一种特殊的场景,当缓存系统内存不够时,会“踢掉”一些缓存数据,从缓存被“踢掉”到下一次定时更新缓存的这段时间内,业务线程读取缓存返回空值,而业务线程本身又不会去更新缓存,因此业务上看到的现象就是数据丢了。解决的方式有两种:
							后台线程除了定时更新缓存,还要频繁地去读取缓存(例如,1 秒或者 100 毫秒读取一次),如果发现缓存被“踢了”就立刻更新缓存,这种方式实现简单,但读取时间间隔不能设置太长,因为如果缓存被踢了,缓存读取间隔时间又太长,这段时间内业务访问都拿不到真正的数据而是一个空的缓存值,用户体验一般。
							业务线程发现缓存失效后,通过消息队列发送一条消息通知后台线程更新缓存。可能会出现多个业务线程都发送了缓存更新消息,但其实对后台线程没有影响,后台线程收到消息后更新缓存前可以判断缓存是否存在,存在就不执行更新操作。这种方式实现依赖消息队列,复杂度会高一些,但缓存更新更及时,用户体验更好。
					对于缓存雪崩问题,我们采取了双key策略:要缓存的key过期时间是t,key1没有过期时间。每次缓存读取不到key时就返回key1的内容,然后触发一个事件。这个事件会同时更新key和key1。
			缓存热点:缓存热点的解决方案就是复制多份缓存副本,将请求分散到多个缓存服务器上,减轻缓存热点导致的单台缓存服务器压力
		数据不一致的解决方案
			没法保证,这类数据允许一定的不一致,一定范围内的对用户也没有影响,不要只从技术的角度考虑问题,结合业务考虑技术
			同步刷新缓存:当更新了某些信息后,立刻让缓存失效。这种做法的优点是用户体验好,缺点是修改一个数据可能需要让很多缓存失效
			适当容忍不一致:例如某东的商品就是这样,我查询的时候显示有货,下单的时候提示我没货了
			关键信息不缓存:库存,价格等不缓存,因为这类信息查询简单,效率高,关系数据库查询性能也很高
			如果发现某个业务的缓存可能存在不一致问题,如何批量失效这些缓存呢,因为不能拿到所有的key。
				没有太好的办法,一种是缓存服务器重启,一种是将key前缀配置在文件中,全部失效的时候换一下配置就可以
		注意事项
			缓存集群间一般不要跨数据中心同步,存储可以用跨数据中心同步
	数据库选型思路汇总
		1.管理型系统,如运营类系统,首选关系型。
		2.大流量系统,如电商单品页的某个服务,后台选关系型,前台选内存型。
		3.日志型系统,原始数据选列式,日志搜索选倒排索引。
		4.搜索型系统,指站内搜索,非通用搜索,如商品搜索,后台选关系型,前台选倒排索引。
		5.事务型系统,如库存、交易、记账,选关系型+缓存+一致性协议,或新型关系数据库。
		6.离线计算,如大量数据分析,首选列式,关系型也可以。
		7.实时计算,如实时监控,可以选时序数据库,或列式数据库。

猜你喜欢

转载自blog.csdn.net/micklongen/article/details/89763100
今日推荐