高性能架构模式
高性能架构模式
高性能数据库集群
读写分离
原理:将数据读写操作分散到不同的节点上
基本实现:
数据库服务器搭建主从集群,一主一从、一主多从都可以
数据库主机负责读写操作、从机只负责读操作
数据库主机通过复制将数据同步到主机,每台数据库服务器都存储了所有的业务数据
业务服务器将写操作发给数据库主机,将读操作发给数据库从机
设计复杂度
主从延迟
如果业务服务器将数据写入到数据库主服务器后立即进行读取,此时读操作访问的是从机,主机还没有将数据复制过来,到从机读取不是最新数据,业务就可能出现问题
解决主从复制延迟的常见问题
1、写操作后的读操作指定发给主服务器
问题:和业务强绑定,对业务的侵入和影响较大
2、读从机失败后再读一次主机
这就是通常所说的“二次读取”,二次读取和业务无绑定,只需要对底层数据库访问的API进行封装即可,实现代价较小,不足之处在于如果有很多二次读取,将大大增加主机的读操作压力。如黑客暴力**账号,主机可能顶不住压力而崩溃
3、关键业务读写操作全部指向主机,非关键业务采用读写分离
分配机制
程序代码封装
程序代码封装指再代码中抽象一个数据访问层(中间层封装)实现读写操作分离和数据库服务器连接的管理
特点
实现简单,而且可以根据业务做较多定制化的功能
每个编程语言都要自己实现一次,无法通用
故障情况下,如果主从发生切换,则可能需要所有系统都修改配置并重启
开源方案:淘宝TDDL,将所有功能封装在jar包中提供给业务代码调用。基本原理是一个基于集中式配置的-datasource实现
中间件封装
中间件封装指独立一套系统,实现读写分离和数据库和数据库服务器连接的管理。中间件对业务服务器提供SQL兼容的协议,业务服务器无须自己进行读写分离
特点
能够支持多种编程语言,因为数据库中间件对业务服务器提供的是标准SQL接口
数据库中间件要支持完整的SQL语法和数据库服务器的协议,实现比较复杂,细节特别多,很容易出现bug,需要较长时间才能稳定
数据库中间件自己不执行真正的读写操作,但所有的数据库操作请求都要经过中间件,中间件的性能要求也很高
数据库主从切换对业务管理器无感知,数据库中间件可以探测数据库服务器的主从状态
开源方案:MYSQL Proxy、MYSQL Router以及360公司的Atlas
分库分表
业务分库
业务分库指的是按照业务模块将数据分散到不同的数据库服务器
问题
join操作问题
事务问题
成本问题
分表
垂直拆分
记录数相同但包含不同的列
适合将表中某些不常用且占列大量空间的列拆分出去
复杂度主要提现在表操作的数量要增加
水平拆分
表的列相同,但包含不同的行数据
适合表行数特别大的表
复杂度
路由
水平分表后,某条数据属于哪个切分后的子表,需要增加路由算法进行计算
常见路由算法
范围路由
选取有序的列(整型、时间戳等)作为路由条件
设计复杂点主要提现在分段大小的选取上,分段太小会导致切分后子表数据过多,增加维护复杂度;分段太大依然存在性能问题,一般建议分段在100-2000万之间,具体需要根据业务选取合适的分段大小
优点是可以随着数据的增加平滑地扩充新的表
缺点是分布不均匀
Hash路由
选取某个列(或组合)的值进行Hash运算,然后根据Hash结合分散到不同数据库表中
复杂点主要体现在初始表数量的选取上,表数量太多维护比较麻烦,表数量太小又核能导致但表性能问题,而且Hash路由后,增加表数量所有数据都需要重新分布
优点是表分布比较均匀,缺点是扩充新表麻烦
配置路由
配置路由就是路由表,用一张独立的表来记录路由信息
优点:设计简单,使用起来非常灵活,尤其在扩充表时,只需迁移指定数据,然后修改路由表就可以了
缺点:必须多一次查询,会影响整体性能,路由表本身如果太大,性能同样可能成为瓶颈,需要分库分表
join操作
水平分表后,数据分散中多个表中,如果需要与其他表进行join查询,需要在业务代码或者数据库中间件中进行多次join查询,然后将结果进行合并
count操作
如获取记录总数用于分页或者展示
处理方法
count相加:对每个表进行count操作,然后将结果相加
记录数表:新加一张表,操作数据后都更新记录数表
优点:只需要一次简单查询就可以获取数据
缺点:增加了复杂度,对子表的操作要同步操作记录数表,可能出现数据不一致;增加了数据库写压力
order by操作
数据分散到多个子表中,排序操作无法在数据库中完成,只能由业务代码或者数据库中间件分别查询后进行汇总排序
高性能NoSQL
关系型数据库缺点
关系数据库存储的是行记录,无法存储数据结构
关系数据库的schema扩展很不方便
表结构的schema是强约束,操作不存在的列会报错,扩充列需要执行DDL
关系数据库在大数据场景下IO较高
如果对一些大量数据的表进行统计之类的运算,关系数据库的IO会很高,因为即使只针对其中一列进行越暖,关系数据库也会将整行数据从存储设备读入内存
关系数据库的全文搜索功能比较弱
NoSQL方案带来的优势,本质上是牺牲ACID中的某个或某几个特效。应该将NoSQL作为SQL的一个有礼补充
常见的NoSQL方案
文档数据库
解决关系数据库强schema约束问题,以MongoDB为代表
优点:可以存储和读取任意的数据,数据格式如json。1、增加字段简单 2、历史数据不回出错,3可以很容易存储复杂数据。适合电商和游戏类业务场景
缺点:1、不支持事务。某些对事务要求严格的场景不能使用文档数据库。无法实现关系数据库的join操作
K-V存储
解决关系型数据库无法存储数据结构的问题,以Redis为代表
缺点:并不支持完整的ACID事务,Redis提供只能保证隔离性和一致性,不能保证原子性和持久性
列式数据库
解决关系数据库大数据场景下的IO问题,以HBase为代表
列式数据库是按照列来存储数据的,传统数据库称为行式数据库
关系数据库的行式存储优势为
1、业务同时读取多个列时效率高,因为这些列都是按行存储在一起的,一次磁盘操作就能够把一行数据都读到内存中
2、能够一次性完成对一行中的多个列的写操作,保证了针对数据写操作的原子性和一致性
列式数据库优势
1、可以读取某个字段的值,不用将整行记录加载到内存中,节省IO
2、具有更高的存储压缩率,因为单个列的数据相似度比行来说更高,能够达到更高的压缩率
列式数据库缺点
1、列式存储将不同列存储在磁盘不连续的空间,导致更新多个列时是随机写,而行式存储同一行是在连续的空间,一次磁盘操作就可以完成
2、列存储高压缩率在更新场景下也会成为劣势,因为更新时需要将存储数据进行解压后更新,然后再压缩写入磁盘
全文搜索引擎
解决关系数据库全文搜索性能问题,以Elasticsearch为代表
优势
全文搜索的条件可以随意排列组合,如果通过索引来满足,则索引数量会非常多
全文搜索的模糊匹配方式,索引无法满足,只能用like查询,like是整表扫描,效率非常底
技术原理被称为“倒排索引”或者反向索引,基本原理是建立单词到文档的索引
正排索引适用于根据文档名称来查询文档内容
倒排索引适用于根据关键字来查询文档内容
全文搜索引擎的索引对象是单词和文档,而关系型数据库的索引对象是键和行
高性能缓存架构
存储系统的不足
需要经过复杂运算后得出的数据,出错系统无能为力
读多写少的数据
缓存原理:将坑重复使用的数据放到内存中,一次生成,多次使用,避免每次使用都去访问存储系统
架构设计要点
缓存穿透
指缓存没有发挥作用,业务系统虽然去缓存查询数据,但缓存中没有数据,需要再读取存储系统
原因和解决
存储数据不存在;解决方案:如果存储系统中未找到数据,则直接设置一个默认值放到缓存中,第二次读取缓存时会获取到默认值
缓存数据生成耗费大量时间或资源,存储系统中存在数据,但生成缓存数据需要耗费较长时间或耗费大量资源,如果刚好在业务访问时业务失效来,那么也会出现缓存没有发挥作用,访问压力全部集中再存储系统上的情况,如爬虫,通常方案是识别爬虫然后禁止访问,但是可能影响SEO和推广;要么做好监控,发现问题及时处理,因为爬虫不是攻击,不回进行暴力破坏,对系统的影响是逐步的,监控发现问题后有时间进行处理
缓存雪崩
指缓存失效(过期)后引起系统性能急剧下降的情况
解决方案
更新锁机制
对缓存更新操作进行加锁保护,保证只有一个线程进行缓存更新,未能获取更新锁的线程要么等待锁释放后重新读取缓存,要么返回空值或者默认值。如果采用分布式集群的业务系统,则需要用到分布式锁
后台更新机制
由后台线程来更新缓存,而不是由业务线程来更新缓存,缓存本身的有效期设置为永久,由后台线程定时更新缓存。既适合单机多线程场景,也适合分布式集群场景。需要考虑当缓存系统内存不够时,会“踢掉”一些缓存数据;
解决方案
后台线程除了定时更新缓存,还要频繁地读取缓存,如果发现缓存被“踢了”就立即更新缓存。实现简单,但读取时间间隔不能太长
业务发现缓存失效后,通过消息队列发送一条消息通知后台线程更新缓存。依赖消息队列,复杂度会高一些,但缓存更新更及时
缓存热点
虽然缓存系统本身性能比较高,但对于一些特别热点的数据,如果大部分甚至所有业务请求都命中同一份缓存数据,则这份数据存在的缓存服务器的压力也很大
解决方案:复制多份缓存副本,将请求分散到多个缓存服务器上,减轻缓存热点导致的单台缓存服务器压力
设计细节:不同缓存副本不要设置统一过期时间,否则会出现所有缓存副本同时失效问题,正确做法是设置一个过期范围,不同缓存副本的过期时间为指定范围内的随机值