依旧主要参考沈剑大佬的多篇博文,以及数位网友的优秀分享,文末是完整参考。
1、高可用方案(HA,High Availability)
缓存是通过双写和双读主备、或者利用缓存的集群数据同步,故障自动转移来实现的
数据库的读是通过读写分离(MHA,Master High Availability),分库冗余多份来实现的;写是通过主从双备,keepalived+virtual IP 自动故障转移来实现的。
2、高并发方案
读多写少,读并发高就主从分离,写并发高就水平分库,如果读写请求并发都很大,那就先水平分库,然后对每个库进行按照主从集群部署。解决读并发高还有一个方案是用缓存。
2.1 读写分离,主从复制,分组架构
三个名词代指同个架构模型。
主从复制(也称 AB 复制)指的是将一个MySQL 主服务器的数据复制到一个或多个MySQL从服务器(从服务器)。也常被称为分组架构、读写分离架构。
主库提供数据库写服务,从库提供数据库读服务。一般是一主多从。
读写分离架构在解决高可用问题时一般被简称为 MHA(Master High Availability)。
2.1.1 读写分离架构的优势
- 读写分离,使数据库能支持更大的读并发和吞吐量。由于写库单独拥有一个独立的库,所以也能一定程度上提高写性能
- 读写数据库相互独立,通过消除读写锁冲突提升数据库写性能
- 通过冗余从库实现数据的“读高可用”
- 有利于架构的扩展,通过增加从库数量可以明显提高读性能。
2.1.2 mysql 主从复制过程(异步)
master 开启 binlog 功能,binlog 日志文件用于记录数据库的读写增删。 需要开启3个线程,master IO线程,slave开启 IO线程 SQL线程,
1、MySQL master 将数据变更写入二进制日志( binary log,就是我们常说的 binlog)
2、Slave 通过IO线程连接master,并且请求某个bin-log,position之后的内容。
3、MASTER服务器收到slave IO线程发来的日志请求信息,io线程去将bin-log内容,position返回给slave IO线程。
4、slave服务器收到bin-log日志内容,将bin-log日志内容写入relay-log中继日志,创建一个master.info的文件,该文件记录了master ip 用户名 密码 master bin-log名称,bin-log position。
5、slave端开启SQL线程,实时监控relay-log日志内容是否有更新,解析文件内容,生成SQL语句,在slave数据库中执行。
2.2 分库分表
2.2.1 水平切分(也被称为分片架构)
2.2.1.1 分片架构的优势
- 线性提升数据库写性能,需要注意的是,分组架构是不能线性提升数据库写性能的
- 降低单库数据容量
一句话总结,分片解决的是“数据库数据量大,写操作有瓶颈”问题,所实施的架构设计。
常见的水平拆分方式有范围法和哈希法两种。两种路由和拆分方式都非常简单。有各自的优势和缺点:
2.2.1.2 按照范围水平拆分
好处是
(1)能保持数据原有的顺序;
(2)能够准确控制每台服务器存储的数据量,从而使得存储空间的利用率最大。
(3)比较容易扩展,不需要移动现有数据,可以随时加一个uid[2kw,3kw]的数据服务;
不足是:
(1)请求的负载不一定均衡,一般来说,新注册的用户会比老用户更活跃,大range的服务请求压力会更大;
2.2.1.3 按照哈希水平拆分
好处:
(1)数据量分布均衡性较好;
(3)服务器请求负载均匀性较好;
不足:
不容易扩展,扩展一个数据服务,hash方法改变时候,可能需要进行数据迁移;但是可以通过一致性哈希来一定程度上缓解这个问题。分片数量成倍扩展,迁移成本也还行,不需要移动全部的数据。
一致性哈希参考:一致性哈希算法原理详解
2.2.1.4 哈希+范围混合分片
先做哈希,然后对哈希结果做范围分片(或者先范围分片再哈希分片),一种折中方案,数据和请求量都较为均衡。
2.2.2 垂直切分
分成垂直分表和垂直分库。
2.2.2.1 垂直分表:
将表中使用频率低或者字段长度较大的字段放到扩展表。这是因为,数据库会以行(row)为单位,将数load到内存(buffer)里,在内存容量有限的情况下,长度短且访问频度高的属性,内存能够load更多的数据,命中率会更高,磁盘IO会减少,数据库的性能会提升。
2.2.2.2 垂直分库:
直接按照业务将一个库拆成两个独立的库,可以降低单库的数据量。与业务结合比较紧密,并不是所有业务都能够进行垂直切分的。
2.3 读写分离 + 分库分表
读写分离和分库分表的结合体,性能更加强大,但是架构也更加复杂。
3、问题
问:读写分离跟水平分库的区别
- 单个服务器的数据量不一样:主从分离每个服务器上存储的数据量相同,都是全集;水平分库后每个服务器上存储的数据量是总量的1/n,每个服务器的数据没有交集,所有服务器的数据的并集是全集。
- 目标和作用不一样:读写分离是为了扩展读性能,主要是解决读并发高问题,因为写库单独拥有一个独立的库,所以也能一定程度上提高写性能;水平分库主要扩展写性能,主要解决写并发问题,扩展之后读写性能都能提高。
- 应用场景不一样:读多写少,读并发高就主从分离,写并发高就水平分库,如果读写请求并发都很大,那就先分库,然后对每个库进行按照主从集群部署。解决读并发高还有一个方案是用缓存。
问:主从分离有什么问题吗?
写仍然是单点,所以要做主从双备,辅助 keepalived+virtual IP 自动故障转移来实现高可用。
问:数据库架构如何选型
- 业务初期用单库
- 读压力大,读高可用,用分组
- 数据量大,写线性扩容,用分片
- 属性短,访问频度高的属性,垂直拆分到一起
问:水平切分,到底是分库还是分表?
答:强烈建议分库,而不是分表,因为:
- 分表依然公用一个数据库文件,仍然有磁盘IO的竞争
- 分库能够很容易的将数据迁移到不同数据库实例,甚至数据库机器上,扩展性更好
问:平常流量不高,但由于业务问题,会出现瞬时高并发怎么解决
通过 MQ 消息队列来削峰填谷降流。
问:水平分库后,业务接入代码需要改动吗?数据访问层需要改动吗
如果以前的单库和现在的分片库都是通过代理来访问的话,那只需要切换数据源代理即可,业务层不感知分片。
数据访问层可能需要增加一些分片路由的代码,以及一些多库遍历聚合数据的代码,可以考虑抽取出一个数据库中间件专门来干这个事,减少对数据访问层的代码改动。
问:水平分库后,非 patition key的查询怎么办?如何同时查询多个库的数据?怎么做join或者全表查询操作?怎么保证事务特性?
网络异常,图片无法展示
|
patition key 走 db 或者 cache,非 patition key 走搜索
问:高并发指的只并发处理还是并行处理
一般一直并行处理的能力,吞吐量。比如某服务能同时处理10个请求,但是每个请求执行 5s,那么每秒的吞吐量只有 2 个,所以我们一般称并发能力为 2,吞吐量为 2,QPS 为 2。
问:分库带来的分布式事务怎么做的?
解法一:分表而不是分库,这样就没有分布式事务的问题了,但是不建议分表。
问:水平分库后,根据用户名登录,此时不知道uid,只知道用户名,怎么知道查哪个分片库?非 partition key 属性的查询?
1、全库扫描法
缺点:需要扫描所有的分片库,聚合数据,效率低下(其实细想一下其实效率还行,如果用户名每个分片库都有索引的话,单库查询很快,而遍历所有的库的话可以多线程并发遍历来加快速度)
2、用户名跟主键建立映射关系(假设用户名具有唯一性):索引法
建立用户名映射到主键的映射表或者把映射关系存在缓存中,映射表需要存在同一个库里,如果量太大,单个缓存实例存不下,可以通过用户名对缓存进行水平分片。
缺点:需要多查一次数据库或者缓存
3、用户名跟 hash 规则产生关系,也称为基因法
设计函数通过用户名生成用来决定 hash 分片的位数的几个bit。比如hash分片是对 8 取模,那么根据用户名需要生成 3 个bit,将这三个比特位作为主键的最后三位,这样就能根据用户名计算出用户所在分片了。
缺点:① 需要提前容量规划,否则如果刚开始8个分片,用户名生成了3个bit,但是后来扩容到了16个分片,这时根据用户名又无法确定分片了,如果刚开始根据用户名生成6个比特位,那就算扩展到 64 个分片,也还是能通过用户名确定分片。② 设计用户名生成比特位时,需要设计均匀分布,否则会导致分库数据库不均衡。
注:如果用户名是唯一且不可修改的,那么可以同时用上面的三种方式,如果用户允许修改,那么只能用前 2 中方式。
4、直接根据用户名来生成整个主键
缺点:有uid生成冲突风险,且需要设计末尾几个 bit 均匀分布,否则会导致分库数据库不均衡。
类似的业务场景还有订单ID的生成(使用用户ID基因),保证同一个用户的所有订单在同一个分表。
问:多个非 partition key 上的查询怎么办
选择其中一个不会发生修改的字段可以用基因法打入 partition key,其他的可以用映射索引法
问:patition key上的批量查询怎么做的
比如用户列表 uid 上的IN查询,通过 patition key 查询,但是每次返回多行记录,难点是 partition key 值有多个,不一定都在同一个分片库里。
法一:数据访问层访问所有库,把结果集进行合并
法二:数据访问层分析路由规则,按需访问
数据访问层对每个 uid 进行路由分析,只访问对应的分片库然后进行数据聚合。
问:水平分库后,跨库分页怎么做
问:水平分库后,需要用用户名进行模糊查询 、时间范围查询
根据用户名关键词或者时间范围等查 ES
问:通常多大的数据量需要分库?
没有复杂查询,5kw 到 1亿 条数据。复杂查询,1kw-2kw。
问:数据库分表,索引如何工作
分表不分表,对索引没有影响,哪个属性上有在线查询,哪个属性索引
问:分片后,怎么生成唯一主键
参考“分布式id生成器”
问:水平分库后,需要使用数据库中间件来实现分库的路由吗
可以用数据库中间件来屏蔽分库细节,也可以自己 hash 实现路由到指定的分库。
问:什么时候需要分布式事务
同时修改多个库的数据
问:主从分离中写库有多个,
建议遵循一主多从,一个写库,多个从库,如果单点写有瓶颈,可以水平扩展多个写库,但是每个分片写库都需要配置多个从库,这样从库可以随时取代主库,读始终高可用。
读操作需要路由到指定集群,然后路由到指定读库实例。写操作需要路由到指定集群的指定写库。
如果需要多主多从,最好每个写库都保留全量的数据,通过路由将请求路由到不同的写库,写库之间需要互相同步数据。
多分片主多从有哪些缺陷:
1、写库只包含部分数据,但是读库包含全部数据,数据不对等
问:缓存在主从分离架构中的作用是什么,为什么有了很多读库还需要缓存
缓存用来存储高频访问的数据,不是数据库中所有的数据都会被加载到缓存,只有某几类数据中的高频且修改频率低的数据才会被加载到缓存,提高服务的响应速度,降低延迟,降低数据库的压力。从库供业务方请求不在缓存中的数据,比如热点数据发生变更从缓存中淘汰了,或者大部分数据压根就没存在缓存中。
问:数据库主从切换会丢数据吗
有可能会。
有两种可能导致丢数据
- 正常来说如果如果主库数据还没完全同步到从库,主库就发生宕机的话,是会丢数据的。还在找不丢数据的方案。
- 脑裂,2个主库,老主库因为网络连接不上LB,没有被设置为只读,也没有断开老连接,仍能接收写请求,导致这部分数据丢失。
丢失的数据是怎么找回来
binlog 存在定时备份,通过恢复备份的 binlog 人工介入找回丢失的数据。
半同步复制,但是当出现网络波动等情况时,半同步复制会退化为异步复制,也坑无法保证数据至少在一个从库上完成同步。
怎么避免丢数据
不使用主从切换,而是主库做双主热备,keepalived+虚拟ip
切换前设置主库为只读,阻塞写入。
想了解更多,参考:MySQL 主从切换异常导致数据丢失
问:单库宕机重启会丢数据吗
不会,重启时会再次执行保存好的redo log日志,恢复还没有刷盘的写操作。
问:数据库切换时是否会主动断开连接?
切换时可以自定义是否主动断开连接,当前切换逻辑中没有选择断开连接,长连接也不会主动断开。
比如对于 MHA,可以通过修改对应脚本实现切换时断开连接。
问:分库和分表选择的依据是什么,什么时候应该分表,什么时候应该分库
根据当前的瓶颈点来选择究竟是分库还是分表。
由于单个表数据量太大导致单个 sql 延迟高,分表
由于数据库整体请求量达到单个实例 qps 上限,且因为 qps 太高导致数据库或者数据行加锁冲突严重,请求延迟增加,则分库,这样可以降低单个数据库的数据量且每秒请求量,通过水平分库提高整体请求量。
问:主从 binlog 消息是推还是拉
在基于位点的主从关系中,一开始创建主备关系时, 由备库需要开始同步的 binlog 文件位置,主库从该位置开始发送 binlog 至从库。而主备复制关系搭建完成以后,只要主库有生成新的日志,会立刻主动发给备库。
提示:主从用于 binlog 传输的 IO 线程通过 TCP 建立了长连接,所以可以持续发送数据。
4、完整参考
依旧强推沈剑大佬的公众号“架构师之路”