学自:(沈剑,2019中国系统架构师大会)
作为架构师,在数据库架构设计上,至少四个方面是需要系统性考虑的:
一、如何保证数据库的高可用
(1)读库高可用,如何保证?
(2)写库单点,如何消除?
(3)服务层,站点层,如何高可用?
二、如何提升数据库的读写性能
(1)索引为何会降低读性能?
(2)一主多从真的好么?
(3)数据库写入性能如何线性提升?
三、如何保证数据的一致性
(1)主从有延时,如何保证一致性?
(2)缓存与数据库,如何保证一致性?
四、如何保证数据库的扩展性
(1)表要增加一个属性,如何扩展?
(2)数据量又暴涨了,该怎么办?
(3)数据要迁移了,如何不停机?
(4)分库之后,跨库分页如何实现?
任何脱离业务的架构设计,都是耍流氓:
1、依据“业务模式”设计库结构、表结构
2、依据“访问模式”设计索引结构
此外,数据库工程架构,还要设计些什么呢?
1、高可用
2、读性能
3、一致性
4、扩展性
创业公司初期都是单库,比如一个库里有300个表。
但后期一个库并不能很容易的拆成多个库,因为多个表有join操作,join操作不能跨库。
所以,单库在最初就要考虑后续的拆分问题。
关于路由规则,常见的路由规则有:
1、范围路由:
Server1: 1 ~ 1亿
Server2: 1亿 ~ 2亿
Server3:…
问题:每台server的存储和访问的负载都不均衡
优点:扩展方便
2、hash(一致性hash,hashcode对n取模)
可解决:存储和访问的负载均衡
带来问题:迁移、扩展的问题。
实际上路由规则和业务是耦合的。
把表拆分成user_base和user_ext两类:
去线上随便关一台机器,看对用户是否有影响。
理论上,对于要求高可用的系统,系统的每一层都需要高可用。
Jedis会自动支持主从高可用:主挂了,会自动调用从。
例如google CFS也是复制了3份文件
缓存的本质也是数据冗余。
数据层冗余会引发一致性问题
读数据库时,数据库连接池会自动做到把请求发送给可用的读库。
带来的问题:
读写都放在主库上,同时同步到从库,主库故障时从库顶上。这样读写一致性问题会得到缓解。
过多的索引会导致写性能降低、且索引占用内存大导致命中率低:因为数据库的内存缓存buffer是有限的,所以过多,导致内存buffer缓存不下,这样在查询索引数据时,仍需要读磁盘,从而导致性能下降
对于一主二从的场景:
增加从库会带来什么问题?
常见玩法:app–>service–>cache–>mysql-m–><–mysql s(m)
Cache Aside Pattern最经典的缓存+数据库读写的模式。
术语标准解释:
术语白话解释:
如果更新缓存,在并发写时,可能出现数据不一致。
如上图所示,如果采用set缓存:
在1和2两个并发写发生时,由于无法保证时序,此时不管先操作缓存还是先操作数据库,都可能出现:
导致,数据库与缓存之间的数据不一致。
所以,Cache Aside Pattern建议,delete缓存,而不是set缓存。
答:如果先操作数据库,再淘汰缓存,在原子性被破坏时:
1)修改数据库成功了
2)淘汰缓存失败了
会导致,数据库与缓存数据不一致。
绝大多数业务,都允许主库和从库短时间内不一致。
“写后立即读”问题:
"先写DB,再删除缓存“,只能缓解该问题,但不能根治。
消除“主从延时”导致的不一致:
从binlog触发一次“二次淘汰”,
也可以在service层异步触发“二次淘汰”。
即写数据时在写完DB后,删除了缓存;这时有读请求到从库,此时主库还没有完成向从库的同步,读请求读到的从库不是最新数据,而更新了缓存。那么,当主库同步完从库后,会通过binlog或service层异步触发“二次淘汰”来更新缓存。
特点:数据量大、吞吐量达、高可用
系统架构:微服务
思考:
1)数据层如何高可用
2)数据层如何扩展
Step1、记录日志:对新的操作记录日志到文件
Step2、数据迁移:把原数据库的数据迁移到新库
Step3、数据补齐:把旧库的日志补齐到新库
Step4、数据检验:通过工具检验新库与旧库的数据是否一致,不一致通过手动等方式补齐。
Step1、双写数据(服务升级)
Step2、数据迁移(通过小工具)
Step3、数据检验(通过小工具)
Step1、改配置
Step2、reload配置
Step3、收尾
问题:如何拆?按哪个属性拆?
下面的场景几乎涵盖了互联网90%的场景。
用户库:10亿数据量
user(uid, uname, passwd, age, sex, create_time, … )
业务需求如下:
结论:根据uid来拆分库,即把uid作为负载均衡的key, 把用户平均存到n个库中。
结论:“1对多”场景,使用“1”分库,例如帖子库中一个uid对应多个tid, 则采用uid进行分库。
好友库:friend(uid, friend_uid, nick, memo, XXOO)
业务需求如下:
订单库:10亿数据量
order(oid, buyer_id, seller_id, order_info, xxoo)
业务需求如下:
查询订单信息:80%请求
select * from order where oid=xxx
查询我买的东西:19%请求
select * from order where buyer_id=xxx
查询我卖出的东西:1%请求
select * from order where seller_id=xxx
结论:“多key”场景一般有两种方案:
方案一:采用2和3综合的方案
方案二:1%的请求采用多库查询
1、数据库工程架构,要考虑:
2、保证高可用的思路:复制冗余
但数据冗余会引发一致性问题
3、提升读性能的场景方案是:
4、旁路缓存最佳实践,Cache Aside Pattern:
5、数据冗余带来的一致性问题优化:
6、增加数据库实例、增大数据库容量的扩展性实践:
7、用户库拆分实践:
8、帖子库拆分实践:
9、好友库拆分实践:
10、订单库拆分实践: