数据库架构演进之路

最初的数据库架构

如上图用户库,直接单库,读写都在这一个库上面

这种架构最先遇到的问题就是,随着业务增长带来的读瓶颈,因为大多数都是读多写少的问题

如何解决?

主从同步,读写分离,扩充读性能

主从会碰到什么问题?主从数据不一致

解决什么问题?分组+分片, 解决存储容量的问题,提升读性能,提升写性能

带来什么问题? sql扩展性的问题,牺牲掉sql的一些特性

没有解决什么问题?

1.高可用的问题

2.路由的问题

1.范围做路由: 1-1kw在0库,1kw-2kw在1库等等,

一、会导致负载不均衡等等,

假如uid前1kw的都是活跃用户,导致0库负载高。扩展性高

2.哈希:解决负载不均,数据量负载不均,访问不均,只要哈希的key比较均衡。

一、扩展性差,从2库到4库,需要迁移数据。

二、业务方需要关注路由规则。

解法一:搞个路由服务,解耦,

缺点:额外的一次调用服务

垂直拆分

user单库拆分成 user_base和user_ext, 访问频繁但是比较短的数据放到user_base库里面,访问比较少,数据比较多的放到user_ext

解决什么问题?

带来读性能的提升,为啥?数据buffer的缓存,关系型数据库以行存储的,数据少缓存的更多。

带来什么问题?

多次数据库访问,在业务层做聚合

没有解决什么问题?

高可用,扩展 

垂直拆分和业务联系比较紧密,具体业务具体分析

二、高可用

典型的服务架构

判断系统是否高可用:关掉其中一台机器,试试访问是否正常,嘿嘿嘿

看其他层的高可用是如何保证的,对于数据层是否有启示?

反向代理层nginx高可用

web层高可用

server层高可用

缓存高可用

可以看出基本都是复制冗余

如何保证读库的高可用?

存在什么问题:

主从延迟的问题,数据冗余会引发一致性问题

怎么解决:

下面

如何保证写库的高可用?

存在什么问题?

数据一致问题,写复杂

怎么解决?

下面

读写高可用

读写一致性问题,缓解

带来问题:

读性能缓解不了

如何提升读的性能?

1,索引

一、写性能降低

二、索引占用内存大,数据命中率低,读缓存也会有问题,太多索引也会引起读性能问题

有什么优化方案?

一主多从, 如果后台有大数据量的访问需求,拿出其中一个从库做备库,按自己的需要建立索引,读分离,减少对线上的影响,类似mongo的隐藏节点。

现在建索引都是在主库键,然后同步到从库上去。

主库其实没必要建索引:如果只写的话,带来的问题是配置不一致

2.提升读性能,增加从库

存在什么问题?

(1)从库越多,同步越慢

(2)数据不一致

怎么优化?

见下面

3.增加缓存

未服务化

已经服务化

带来的问题:

1.数据不一致 2.业务修改

缓存的最佳实践

Cache Aside Patern

对于读请求

1.先读cache,再读db

2.如果cache hit,则直接返回数据

3.如果cache miss,则访问db,并将数据set回缓存

写请求

1.淘汰缓存,而不是更新缓存

2.写操作先操作数据库,再淘汰缓存:

为什么淘汰缓存,而不是更新缓存

淘汰缓存带来的成本:

1.是多一次cache miss

更新缓存:

1.写并发的一致性问题

2.回写成本可能比较高,比较复杂的业务,可能需要取回db或者缓存里的数据,计算完再set回去。

为什么先写数据库,再淘汰缓存?

假设先淘汰缓存,会出现并发读写(概率大),可能不一致,但其实不根治

Cache Aside Patern存在什么问题?

如果先操作数据库,再淘汰缓存,在原子性被破坏时:

(1)修改数据库成功了

(2)淘汰缓存失败了

导致,数据库与缓存的数据不一致,原子性被破坏(概率小),可能不一致

四、一致性优化

为什么会出现?

这个不一致其实很短的

二、行业内的优化方案

方案一:忽略

任何脱离业务的架构设计都是耍流氓,绝大部分业务,例如:百度搜索,淘宝订单,QQ消息,58帖子都允许短时间内不一致

方案二:强制读主,缓存也会用的

方案三:选择性读主

1.写时记录

2.读时判断

如何实现?

中间件知道哪个库,哪个表,哪个key是写操作或者读操作。 对于需要比较强一致的读请求的话,由这个中间层去读主库,而不是去读从库,但是不需要比较一致性的读请求,还落到从库上。

使用redis或者memcache,将写请求的key设置一个比较短的过期时间(主从同步延时时间)。 读的话先去查看缓存里有没有,有的话表示发生过改动,去读主库。也就是db和缓存双写。

缓存不一致

为什么会出现这个问题?

写后立即读问题,主从同步延迟问题,脏数据进缓存,且一直不淘汰

优化方案:二次淘汰缓存,消除主从不一致带来的副作用

如何解决?

1.异步淘汰缓存,确保从库已经同步成功

2.设定超时时间,极限情况下有机会修正-》设置比较合适的过期时间,过期了会自动修正

3.获取自己在service层定时异步 二次淘汰

如何知道从库已经同步成功?

监听从库的binlog,当业务的某一个key在从库上写成功了,再去淘汰cache

五、扩展性优化

典型为服务架构数据库扩容

典型场景

特点:数据量大/吞吐量大/高可用

系统架构:微服务

数据层如何高可用

数据层如何扩展

综合情况

要解决什么问题?

一.吞吐量持续增大,如何进一步增加实例?

二.数据量持续增大,如何进一步水平扩展?

1.停服扩容。最简单的

2.追日志

1.记录日志(服务升级),对db写操作的要写一行日志,打印时间,db,table,primary_key等等

2.数据迁移(小工具1)

3.数据补齐(小工具2),日志里记录的增量数据

4.数据校验(小工具3)

3.双写

假设某一条记录在迁移,突然这条数据被删了,旧库没了,新库还在迁移,所以就又把这条数据写进去了,所以我们需要迁移校验工具

4.双倍扩容法,只支持2库变4库,4库变8库等双倍扩容,但不需要改代码

1.改配置

2.reload配置

3.收尾

4.最终结果

各类业务场景水平切分实践

如何拆?用哪个属性拆分?

四类典型场景

单key 用户库如何拆分:user(uid,xxoo)

1对多 帖子库如何拆分:tiezi(tid,uid,xxoo)

多对多 好友库如何拆分:friend(uid,friend_uid,xxoo)

多key 订单库如何拆分:order(oid, buyer_id,seller_id, xxoo)

用户库,10亿数据量

user(uid,uname,passwd,age,sex,create_time,...)

业务需求

(1)1%登录请求 =》where uname=xxx and passwd=xxx

(2)99%查询请求 =》where uid=xxx

方案:按uid分库分表

那login_name上的查询怎么样?

用户中心实践

1.索引表

建立个索引表有2个字段,uid和login_name

多了一次数据库访问

索引表存不下,再按login_name分库

2.缓存映射法

原理相同,使用redis等记录

3.login_name生成uid

会有冲突,很少这么干

4.基因法:应用场景比较局限, 基因不可变。不支持多种登录方式:邮箱,手机号等等。

这个3位最多支持8个库,应该多考虑最多的库数,决定多少位。

除了login_name后台复杂,批量,分页的查询怎么办?

前台与后台分离:

通过数据层解耦

通过服务层解耦

帖子库拆分?

帖子库,15亿数据量

tiezi(tid, uid, title, content, time);

业务需求如下:

(1)查询帖子详情(90%请求)

select * from tiezi where tid = $tid

(2)查询用户所有发帖(10%请求)

select *from tiezi where uid=$uid

帖子中心实战

按照uid分库

同一个uid的所有tid在一个库

tid中融入uid基因

当然也可以和上面引入临时表来解决,存储的就比较多了

除了uid/tid,标题/内容的查询怎么办?使用搜索引擎

引入的问题: 数据不一致

定期全量检测并修复数据

好友库拆分实战

好友库,1亿数据量

friend(uid,friend_uid,nick,memo,xxoo);

业务需求如下:

(1)查询我的好友(50%请求)=》用于界面展示

select friend_uid from friend where uid = $my_uid

(2)查询加我为好友的用户(50%请求)=》用户反向通知

select uid from friend where friend_uid = $my_uid

不管通过uid/fuid哪个分库,只能满足一个查询,这时候的基因法不好用了,因为我们控制不了fuid,意思就是我们不知道哪个uid会加好友,所以加不了基因。

好友中心实践:数据冗余

服务同步冗余

服务异步冗余

线下异步冗余

数据冗余有什么副作用?

数据不一致

如何保证数据一致性?

最终一致性是常见实践

实践:

1.线下扫全库:

缺点:慢, 一致性的数据会被多次扫描

2.线下扫增量

3.线上实时检测

订单库如何拆分?

订单库,10亿数据量

order(oid,buyer_id,seller_id, order_info, xxoo)

业务需求如下:

(1)查询订单信息(80%请求)

select * from order where oid = $oid

(2)查询我买的东西(19%请求)

select * from order where buyer_id = $my_uid

(3)查询我卖出的东西(1%请求)

select * from order where seller_id = $my_uid

基因法和数据冗余综合应用

---------------------------------------------------------------------------------以下内容废弃----------------------------------------------------------------------------------------

1.中间件 

中间件知道哪个库,哪个表,哪个key是写操作或者读操作。 对于需要比较强一致的读请求的话,由这个中间层去读主库,而不是去读从库,但是不需要比较一致性的读请求,还落到从库上。

如何实现?

使用redis或者memcache,将写请求的key设置一个比较短的过期时间(主从同步延时时间)。 读的话先去查看缓存里有没有,有的话表示发生过改动,去读主库。也就是db和缓存双写。

2.强制读主库 

这个从库就没啥意义了,读写都在写库上

会有什么问题?

主库单点问题,不高可用,也没有解决读性能

怎么解决?

使用双主, 主主之间同步。

如何解决读性能问题

服务化+缓存

有什么问题? 缓存不一致

缓存不一致,如何解决?

1.Cache Aside Patern

淘汰缓存,而不是更新缓存

读请求有缓存先读缓存,如果数据命中直接返回,未命中读从库

写请求,先写数据库,再淘汰缓存:

为什么会不一致?

问题1:先写数据库,再淘汰缓存,立刻发生了个读操作, 主从同步延时,从库还是旧数据,又把脏数据放到了缓存里面。

如何解决?

异步淘汰缓存,确保从库已经同步成功

设定超时时间,极限情况下有机会修正-》设置比较合适的过期时间,过期了会自动修正

如何知道从库已经同步成功?

监听从库的binlog,当业务的某一个key在从库上写成功了,再去淘汰cache

发布了47 篇原创文章 · 获赞 39 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/qq_28119741/article/details/105143052