企业架构笔记3 应用架构-存储层1:高性能存储
数据库:存储与处理 1(性能篇)
读写分离
一主多从,读写分离,主从同步,是一种常见的数据库架构,一般来说:
主库,提供数据库写服务;从库,提供数据库读服务
主从之间,通过某种机制同步数据,例如mysql的binlog
一个主从同步集群通常称为一个“分组”
将数据库的读写操作分散到不同的节点上
设计关键点:解决主从复制延迟的问题
♦ 将写操作后的读操作 发给主服务器
● 和业务强绑定,对业务的入侵和影响大
♦ 二次读取:读从机失败后再读一次主机
● 无须和业务绑定,但会增加主机的读操作压力
♦ 关键业务读写操作全部指向主机,非关键业务采用读写分离
读写分离架构的优缺点分析
■ 分散了读写操作的压力,但没有分散存储压力
■ 当数据量达到千万甚至上亿条的时候,单台数据库服务器的存储能力会成为系统的瓶颈
■ 为满足业务数据的存储要求,单个数据库服务器的存储数据量有必要控制在一定范围内,有必要将存储分散到多台数据库服务器上
■ 常见的分散存储方法:分库、分表
什么时候考虑分库分表
■ 能不分就不分
■ 数据量过大,正常运维影响业务访问
♦ 如果单表太大,备份时需要大量的磁盘 IO 和网络 IO
♦ 对一个大表做 DDL,MySQL 会长时间锁住整个表,这段时间业务不能访问此表,影响大
♦ 大表经常访问和更新,有可能出现锁等待
■ 垂直拆分:随着业务发展,将不常访问或者更新频率低的字段从大表中分离出去
■ 水平切分:随着业务和数据量快速增长,单表中的数据量持续增长,当性能接近瓶颈时,可考虑水平拆分
■ 水平分库:以字段为依据,按照一定策略(哈希、范围等),
将一个库中的数据拆分到多个库中
♦ 每个库的结构一样
♦ 每个库中的数据不一样,没有交集
♦ 所有库的数据并集是全量数据
■ 场景:系统并发量大,分表难以根本上解决问题,且没有明显的业务归属来垂直分库
■ 数据库增多,可缓解IO 和 CPU 的压力
■ 垂直分表:将表中某些不常用且占据大量空间的列拆分出去
■ 水平分表:单表超过5000万行就应分表
■ 设计关键点
♦ 表路由:范围路由、Hash路由、配置路由等算法
♦ 连接(join)操作
♦ 计数(count)操作
♦ 排序(order by)操作
■ 优缺点分析
♦ 分散存储压力,提升性能
♦ 表操作的数量增加,原本一次查询,现在需要两次
水平分表
■ 以字段为对象,按照一定策略(哈希、范围等),将一个表中的数据拆分到多个表中
♦ 每个表的结构都一样
♦ 每个表的数据不一样,没有交集,所有表的并集是全量数据
■ 场景:系统并发量不大,只是单表的数据量多,影响 SQL 效率,加重 CPU 负担,以至成为瓶颈,考虑水平分表
■ 单表的数据量减少,提高了单次执行 SQL执行效率,减轻 CPU 的负担
垂直分库
■ 以表为对象,按照业务归属不同,将不同的表拆分到不同的库中
♦ 每个库的结构都不一样
♦ 每个库的数据也不一样,没有交集
♦ 所有库的并集是全量数据
■ 场景:系统并发量大,可以抽象出单独的业务模块
■ 一些公用的配置表、字典表等可以服务化,将这些表拆到单独的库中
垂直分表
■ 以字段为对象,按照字段的活跃性,
将字段拆到不同的表中(主表和扩展表)
♦ 每个表的结构不一样,每个表的数据也不一样
● 每个表的字段至少有一列交集,一般是主键,用于关联数据
♦ 所有表的并集是全量数据
■ 场景:系统并发量不大,表的记录不多,但是字段多,热点数据和非热点数据在一起,由于单行数据所需的存储空间较大,数据库缓存的数据行减少,查询时读磁盘产生大量随机读 IO,产生 IO 瓶颈
■ 将热点数据作为主表,非热点数据作为扩展表,以缓存更多的热点数据,减少随机读 IO
■ 拆分之后,应在服务层分别获取主表和扩展表的数据,用关联字段关联得到全部数据
读写分离和分库分表的实现方法
程序代码封装
在代码中抽象一个数据访问层来实现读写分离、分库分表
■ 优缺点分析:
♦ 实现简单,可以根据业务定制化
♦ 通用性差,每个编程语言都需要自己实现一次
♦ 在故障情况下,如果主从发生切换,则可能需要所有系统都修改配置并重启
■ 开源的实现方案:淘宝的TDDL
中间件封装
■ 用一套专门的系统来实现读写分离和分库分表操作,该系统对业务服务器提供SQL兼容协议
■ 优缺点分析
♦ 支持多种编程语言
♦ 性能要求高
♦ 数据库主从切换对业务服务器无感知
■ 开源的实现方案
♦ mysql-proxy、MySQL Router、Atlas、Sharding-jdbc、MyCat
实现复杂度
■ 读写分离:通过SELECT, UPDATE, INSERT, DELETE等关键字SQL操作是“读操作”还是“写操作”,即可实现
■ 分库分表:除了操作类型,还要判断SQL中的具体需要操作的表、操作函数(count, order by, group by),然后进行不同的处理
♦ 例如,order by 操作需要先从多个库中查询各库的数据,然后重新执行 order by 才能得到最终的结果
分布式数据库中间件
代理模式:将select和update语句发送给代理,由其操作底层数据库
♦ 代理本身需要保证高可用
♦ MyCat、DBProxy等 ■ 客户端模式:在连接池上做一层封装,内部与不同的库连接,SQL交给smart-client处理
♦ 通常仅支持一种语言,需要开发多语言客户端
♦ TDDL、ShardingJDBC等
分库分表带来的好处与问题
■ 既可以分散访问压力,又可以分散存储压力
■ 能有效缓解单机和单表带来的性能瓶颈和压力,突破网络 IO、硬件资源、连接数的瓶颈,同时也带来一些问题
■ 原来在一个数据库上就能完成的写操作,可能就会跨多个数据库,于是产生了跨数据库事务问题
Key-Value存储
■ 数据以一对Key-Value的形式存储在内存Hash表中
■ Key是数据的标识(主键),Value是具体数据(数据结构)
♦ 数据结构包括:string, hash, list, set, sorted set, bitmap, hyperloglog,并提供了这些数据结构对应的操作
♦ 也称为“数据结构服务器” ■ Hash表数据读写的时间复杂度为O(1) ,不支持完整的ACID事务,只能保证隔离性和一致性
■ 适用场合:session、购物车
■ 代表产品:Redis
文档数据库
■ 为解决关系数据库模式的修改不便而产生,代表产品mongoDB
■ 不定义表结构,没有模式或动态模式,可存储和读取任意模式的数据;使用 JSON 或 XML 格式、以树形结构存储数据
■ 优缺点
♦ 新增字段简单、查询历史数据时不会出错、易于存储复杂数据
♦ 不支持事务
♦ 无法实现关系数据库中的连接(join)操作
■ 适用场合:作为关系数据库的补充,不适合对事务要求严格的业务场景
♦ 例如,使用关系数据库存储商品的库存信息、订单基础信息,而使用文档数据库mongoDB来存储商品详细信息
缓存的关键设计点1:缓存穿透
■ 问题描述:如果因为不恰当的业务、或者恶意攻击持续高并发地请求某个不存在的数据,由于缓存没有保存该数据,所有的请求都会落到数据库上,会对数据库造成很大压力,甚至崩溃
■ 解决方案:
(1)将不存在的数据也缓存起来(其value值为null);
(2)如果数据库中存在数据,但是在业务访问时缓存失效了(导致缓存没有发挥作用),而生成缓存数据需要耗费较长时间和大量资源,这时也没有好办法,做好日常监控,发现问题后及时处理
缓存的关键设计点2:缓存雪崩
■ 问题描述:当缓存(系统)不可用后,众线程争相访问数据库,数据库因压力过大而宕机,进而导致整个系统不可用
■ 解决方案1:更新锁机制
♦ 对缓存更新操作加锁,保证每台服务器只有一个线程能更新缓存
♦ 对于分布式缓存集群:使用「分布式更新锁」,如ZooKeeper
■ 解决方案2:后台更新机制
♦ 放弃由业务线程来更新缓存的做法,改由后台线程定时更新缓存
缓存的关键设计点3:缓存预热
■ 问题描述:缓存中存放的是热点数据,热点数据又是缓存系统利用LRU (最近最久未用算法) 对不断访问的数据筛选淘汰出来的,这个过程需要花费较长的时间
■ 新启动的缓存系统如果没有任何数据,在重建缓存数据的过程中,系统的性能和数据库负载都不太好
■ 解决方案:在缓存系统启动时,把相关的数据(如城市地名列表、类目信息等元数据)直接加载到缓存系统,这个缓存预加载手段叫作缓存预热(warmup)
缓存的关键设计点4:缓存热点
■ 问题描述:对于一些特别热点的数据,如果大部分业务请求都命中同一份缓存数据,会对这份数据所在的缓存服务器造成过大压力
■ 解决方案:复制多份缓存,将请求分散到多个缓存服务器上,减轻缓存热点导致的单台缓存服务器的压力
♦ 对缓存的 key 进行编号加以区分,每次读缓存时都随机读取其中某份缓存
列式数据库
■ 传统的关系数据库按照行来存储
♦ 业务同时读取多个列时,效率高
♦ 能够一次性完成一行中的多个列的写操作,保证了针对行数据写操作的原子性和一致性
♦ 不适合海量数据统计的场景
■ 列式数据库按照列来存储数据,代表产品 Hbase、Cassandra
♦ 业务同时读取多行的同一列时,效率高;业务需要频繁更新多个列时,效率低
♦ 具备更高的存储压缩比,能够节省更多的存储空间
■ 列式存储的适用场景:离线的大数据分析、统计场景、日志、Web点击流
■ 在关系型数据库中,通过WHERE子句来完成对具有特定特征的数据进行筛选及操作
SELECT * FROM customers WHERE country=‘Mexico’;
时间序列数据库
时间序列数据是基于时间的一系列数据
■ 序列 :标识符(维度),方便搜索和筛选
■ 数据点:时间戳和数值构成的数组
♦ 行存:一个数组包含多个点
♦ 列存:两个数组,一个存时间戳,一个存数值
♦ 一般情况下,列存有更好的压缩率和查询性能
■ 适用场合:IoT设备的传感数据、服务器监控日志的处理与存储
■ 代表产品:InfluxDB、TimeScale等
全文搜索引擎
■ 将关系型数据按照对象的形式转换为JSON文档,然后将JSON文档输入全文搜索引擎进行索引
■ 适用场合:ELK (ElasticSearch, Logstash, Kbana) 日志处理三件套,以近乎实时的方式提供日志分析与处理
■ 代表产品:ElasticSearch、solr、Hermes
♦ ElasticSearch为每个字段都设置了倒排索引,以加快检索速度
♦ 索引技术是关键
混合持久化
现在大多数 WEB 应用,在存储上基本上都会涉及几类数据库,使用不同的数据库来解决不同的问题,为每种问题选择最合适的数据库
■ MySQL: 存储应用核心数据
■ MongoDB: 存储一些不合适用关系型数据库存储的数据,如图片、文档等
■ Redis: 缓存热点数据,如用户cookies,应用中的计数器等
混合持久化的缺点
■ 复杂性增加,谨慎考虑部署复杂度
■ 需要了解每种数据存储的性能瓶颈
■ 每种数据存储机制都有其学习成本,选择正确的数据存储也有决策成本
本章小结
■ 高性能数据库集群的第一种方式是“读写分离”,其本质是将访问压力分散到集群中的多个节点,但是没有分散存储压力
■ 数据库读写分离需要考虑 “复制延迟” 带来的复杂性
■ 数据库读写分离的分配机制有两种实现方式:程序代码封装和中间件封装
■ 高性能数据库集群的第二种方式是 “分库分表”,既可以分散访问压力,又可以分散存储压力
■ 业务分库是指按照业务模块将数据分散到不同的数据库服务器
♦ 业务分库会引入连接查询、事务等复杂度
■ 数据库分表分为垂直分表和水平分表
♦ 垂直分表引入的复杂性主要体现在表操作的数量要增加
♦ 水平分表引入了路由、join 操作、count操作、order by 操作等复杂度问题
■ K-V存储在数据结构方面相比关系型数据库具备较大的优势
■ 文档数据库最大的特点就是no-schema,可以存储和读取任意的数据
■ 列式存储在某些场景下能够大大节省I/O
♦ 列式存储具备很高的压缩比,能够节省存储空间
■ 缓存穿透是指当业务系统查询的数据在存储系统中没有的时候,每次查询都会访问存储系统
■ 缓存雪崩是指当缓存失效 (过期) 后引起系统性能急剧下降的情况
■ 缓存热点指大部分甚至所有业务请求都命中同一份缓存数据