分布式存储 - 含DB读写分离/主从同步/分库分表/ - 学习/实践

发布于:2023-01-14 ⋅ 阅读:(538) ⋅ 点赞:(0)

1.应用场景

主要用于解决高并发, 储存压力.这涉及到性能与容量问题,并不是说存储,就只是考虑容量,而不用考虑性能。不过通常是为了强调容量问题的解决。

2.学习/操作

1. 文档阅读

08 | 数据库优化方案(一):查询请求增加时,如何做主从分离?-极客时间

09 | 数据库优化方案(二):写入数据量增加时,如何实现分库分表?-极客时间

10 | 发号器:如何保证分库分表后ID的全局唯一性?-极客时间

11 | NoSQL:在高并发场景下,数据库和NoSQL如何做到互补?-极客时间

分库分表?如何做到永不迁移数据和避免热点?

MySQL - 分库/分表 - 学习/实践_william_n的博客-CSDN博客

35 | 答疑解惑(三):主流消息队列都是如何存储消息的?-极客时间

项目参考 [PHP+MySQL]
https://github.com/charmtrack/php-Subtable
https://github.com/1107012776/PHP-Sharding-PDO
PHP结合MySQL进行切片、分库分表 - lys的个人博客-慢生活

https://github.com/tekintian/mydb
https://github.com/zld7956/php_mysql

2. 整理输出

2.1 介绍

分布式存储两个核心问题: 数据冗余 与 数据分片

分布式存储并不单单指的是数据库层面,尤其是关系型数据库,而是涉及到任何组件的存储。

比如,NoSQL存储,消息队列消息存储,具体如何存储,即选择何种存储方案,就物理介质而言【存储的物理介质无非就是硬盘和内存,他们之间的区别,就不用多说了吧。】,是存储到硬盘【机械硬盘或者固态硬盘】,还是存储到内存,应该结合具体的经常来分析,不过通常涉及到分布式存储,都是大数据量高并发的存储方案。对大数据量的存储方案,完全采用内存存储对目前来说是不现实的,不可接受的。

另外,很多大厂在面试的时候,特别喜欢问各种二叉树、红黑树和哈希表这些你感觉平时都用不到的知识,原因是什么?

其实,无论是我们开发的应用程序,还是一些开源的数据库系统,在数据量达到一个量级之上的时候,决定你系统整体性能的往往就是,你用什么样的数据结构来存储这些数据。而大部分数据库,它最基础的存储结构不是树就是哈希表。

2.2 传统关系型数据库中是如何解决的

当我们面临高并发的查询数据请求时,可以使用主从读写分离的方式,部署多个从库分摊读压力;

当存储的数据量达到瓶颈时,我们可以将数据分片存储在多个节点上,降低单个存储节点的存储压力,

其中主从同步的机制:

2.3 NoSQL的数据存储

如:

ES, MongoDB,Redis,Memcached, DynamoDB

详情后续补充~

2.4 主流消息队列都是如何存储消息的?

如:Kafka,RabbitMQ,RocketMQ

35 | 答疑解惑(三):主流消息队列都是如何存储消息的?-极客时间

主流消息队列都是如何存储消息的?

现代的消息队列它本质上是一个分布式的存储系统

那决定一个存储系统的性能好坏,最主要的因素是什么?就是它的存储结构。

在所有的存储系统中,消息队列的存储可能是最简单的。

每个主题包含若干个分区,每个分区其实就是一个 WAL(Write Ahead Log),写入的时候只能尾部追加,不允许修改。读取的时候,根据一个索引序号进行查询,然后连续顺序往下读。

后续补充

...

3.问题/补充

1. 分库分表之后, 对于app端查询的问题还比较好解决。但是后端运营系统查询就麻烦,比如订单分库分表后,运营系统查询订单的时候可能根据多维度查询,这种方案您在工作中是怎么去解决的?我现在的做法就是同步到es里面。用ES去查。

作者回复: 可以的,也可以同步到一个大库中,不过性能有点儿差.

插入: 现在的公司应该也是这样做的. 查询订单分页数据.

2. 多个相同问题汇总

1.老师我想请教下就是多库join的问题,如果采用在业务代码中进行处理不太妥吧,数据量太大了,如果有分页或排序的需求,这是要把各个库的数据都查出来,在内存中进行操作,这样会想当耗费内存,且性能低,老师有啥好办法吗?


2.如果一个订单库采用了买家id做为分区键,这样查询买家的订单非常容易,那要查询卖家的订单是不是和文中根据昵称查询一样,建立一个卖家和买家的映射表解决?
 

3.文中老师说如果要做分库分表留言一次性做到位,但这样在开始会很浪费空间,所以一般公司还是会采取慢慢扩容的方式,这样就引入了不停机迁移数据的问题,针对这种情况,老师是怎么做的呢?
谢谢老师

作者回复:

1.多表join一般不会是全量数据,是分页数据,所以只有一少部分
2.建议是订单ID分库分表,然后建立买家ID和卖家ID和订单ID的映射
3. 一般是先双写两个库,然后校验数据,然后灰度切读,最后全量切读

3. Q: 老师说的道理 我都明白 只是如果现在有一张上亿的表 并且存在特定属性更新 那么如何不停机 进行分库分表 有木有具体的实践

作者回复: 可以搭建新的库之后,先在业务上双写,然后校验两边的数据,再灰度切读,再全量切读

4. 公司小业务少时,不可能一开始就规划很多库和表(如16*64),就像很多项目开始都只有一个库,但是我们做架构时可以预先考虑到后面可能会分库分表。请问老师,能不能讲一下最开始设计数据库时需要为今后分库分表考虑哪些因素,和一旦扩容后数据迁移的方案和注意点。谢谢。

作者回复:

主要考虑数据的增长情况,数据迁移一般是先双写旧库和新库,然后校验数据,然后灰度切读,最后全量切读,注意点就是数据校验过程,会比较繁琐

5. 老师,请问下昵称和 ID 的映射表怎么建立,是按照昵称进行分库分表吗,即先查询这个昵称在哪个库哪个表,然后找到 ID,根据 ID 所在的库和表进行查询吗?

作者回复:

是的,没错

插入:  个人问题: 几百万用户的系统, 登录怎么处理, 应该就是上面的思路; 另外补充: 配合缓存一起使用呢   ---  曾经面试遇到过

6. 如果是因读性能引起的分库分表,可考虑ES或MongoDB、HBase的数据重构方式,避免在MySQL做文章
如果是写性能引起的分库分表,可按老师上面的这些原则进行实践和改造

作者回复: 是的

7. 网友解决

分库分表如何做:

1、对实体表,路由规则可以是id取模,计算得到数据真正存放的表。可以降低单表的规模,平均每个表的数据量。
2、对时间倒排的列表,比如微博内容或者订单,可以根据时间字段水平分表,将近期少部热点数据集中到一起。

分库分表所引发的问题的解决方案:

1、由于分区规则的原因,查询无论如何都必须拿到分区键。可以对某些高频非分区键字段建立二级映射,模拟mysql主键和二级索引的解决方式(二级索引的叶子数据存放的是主键索引)。比如根据name查询用户,可以建立name和uid的映射,查询时先根据name拿到uid,再用uid做后续查询。
2、数据库拆分后,针对联表查询,要么少做联表,要么做数据冗余(表字段冗余,或者其他nosql数据冗余)。
3、分布式事务
a) 数据库中间件
b) mq事务消息
c) 将分布式大事务转化成多个本地小事务,通过异步通知+定时补偿+幂等实现最终一致性

8. 老师查询conut怎么做冗余,那种有where条件的   --- 上面已经回答过

作者回复: 可以考虑用es

类似问题:

老师,你好!
订单表分库分表之后,像后台 OA 系统,带查询条件订单分页列表,带查询条件count 订单数量,这些需求该如何实现吖?

作者回复: 一般会同步到一个非分库分表的存储中,比如elasticsearch,或者单个mysql,因为后台的请求量不大,所以还好

9. 感谢老师分享,对于分表有点疑问: ---- 可以结合上面的5一起思考, 实践

1.如果是用户信息表需要分表,数据量大的前提下,需要准备一个映射表来存储昵称+uid的对应关系,文中提到了映射表也可以做分库分表,基本的思路是什么?用户在做登录相关操作的时候,都不知道昵称+uid的映射关系在哪张表中,难道是通过昵称算出hash值来确定分区键?
2.如果hash分表的策略又达到了瓶颈,需要更多的容量呢?基于对业务影响最小的方案是采用数据冗余+新的分区表还是重建分表规则做数据迁移?这一部分没有讲到哦,后面能否专门讲解下,一般应该是前者吧,因为后者在数据量大的情况下做一次数据迁移成本太高了?
3.对于文中提到的,16个库每个库中64张表,1024个张表,这个分表策略的理由是什么?个人感觉这个分表规则显得有些太浮夸了,因为有些业务压根用不到这么多表,甚至有时候分表操作是分表策略(局部分表)+当前模式(局部不分表)公用的方式来协调的,一步一步迭代过来的?不是很理解文中提到的这个策略的容量是如何计算出来的?如果数据量压根用不到这么多表,数据过于分散,对于管理和维护成本来讲有点小题大做了吧?

另外有一点,

文中提到的总计数的问题,用redis存储的前提是当前的业务逻辑不是敏感的,用redis可以提升性能,如果是敏感业务的话,在更新数据库后还没有写入redis中的这个时间差,请求并发没办法估量和控制,所以最后的数据总量仅仅是最终的数据是一致的,但是逻辑是不一致的,核心原因是redis和mysql是属于不同的存储系统,无法做到两个系统公共支持一个分布式事物,无法拿到精确一致的视图,当然如果是非敏感业务,在保证性能的前提下,逻辑不一致可以容忍的话是可以考虑这种方案的。

作者回复:

1. 是对昵称做hash,登陆的时候不需要知道昵称呀,可以针对手机号做hash,昵称是用来判断昵称是否存在
2. 不太清楚数据冗余 + 新的分区表的意思,是增加新的分区表吗?那么就要改分库分表的规则,那这样原先的数据就读不到了?是要做数据迁移?
3. 是需要一步步迭代,这里是说这些库表是足够了,如果业务没有那么大数据量,可以按照业务来
4. 计数是最终一致就好了

10. 请教老师,为什么id要先做hash再做取余计算分库位置呢?直接用id取余不可以吗?

作者回复: 直接取余也好,只是怕ID会不均匀

11. 这个东西能不用就不用。毕竟很多老系统还有超多join操作,你一开始分库分表,所有代码都要重写。我倒觉得换es,mongodb是个好思路

作者回复: 如果有运维能力也可

12. 老师,我们现在面临一个问题,如果我们在使用某个业务字段哈希之后分了64张表之后,后面又发现分表后性能瓶颈,要把64张表分成128张表,这种操作就需要把原来的哈希规则重制,有什么好的办法解决吗

作者回复:

要么一次分足够多的表,要么可以采用类似时间范围这样不需要hash的分表方式    ----- 具体如何实现 ? TBD

网友 - 2021年薪资目标35K: 提供一个思路:哈希环,迁移数据只需要迁移部分节点数据

13. 老师好,我这辈子做过的最大系统,不仅仅用上分库分表和读写分离了。很简单就是在100个MySQL,每个MySQL有100个表,这样根据id后四位就可以定位到它应该放在哪个MySQL和哪个表。但是因为每天可能有20亿的事务量,长此以往的数据积累,单表超过2000万时增改查性能都急剧下降,而且还有大数据团队要从这里导数据出去,低峰时还要删数据。那么我们就在时间纬度上也做了“分库分表”的思想:这一套分库分表乘以31,每天一套表来做日切,于是避免了单表过大,线上导数据风险大的问题,但业务上只能实时查询的31天内的数据,就是成本好大运维压力挺大。

作者回复: 👍能解决问题就好

14. 老师分库分表之后join操作怎么弄,比如用户分表之后,是先查出来用户数据然后循环查询关联的信息吗

作者回复: 互联网项目基本不会有join

15. 老师可以讲一下为什么互联网公司都用mysql而少用pg吗?不都是开源软件么?

作者回复: mysql更成熟,会的人多,好招人:)

16. 垂直拆分的含义是:一个库里的多个表分散到多个库里?
垂直拆分难道不是指:把某些特定的列划分到特定的分区,减少表的宽度,每个分区都保存了其中列所在的行。

作者回复: 嗯 两种都是垂直拆分的方式

17. Redis也是通过数据分片来解决水平扩展的。对于用户昵称查询,总数啥的,可以直接load到ES中吗?然后查询时直接查询ES就可以了?这种方案会不会有什么问题?

作者回复: 这样也可以的

18. 如果分库分表后 又增加了一个库来存储。那么原来的数据岂不是都不能用了?所有的数据再需要重新的分一遍吗?
据说一致性hash能解决这问题?老师可以具体说说吗

作者回复: 一致性hash解决不了这个问题,如果要增加库的话,只能重新分配,所以会比较麻烦

19. 师关于分表的问题,比如您文章中说的 用户分表 根据uid 进行hash运算,分了一共16个库(0-15)我要获取某个用户的信息 ,可以根据uid 进行hash运算 找到对应的用户表,这个能理解,但是在添加的时候呢? 分了16个库,来一个注册用户,这时候这个用户的注册数据应该怎么进行hash计算,让用户数据写入到对应的分库中?

作者回复: uid可以用发号器生成,然后就可以根据uid知道写哪一个库了

20. 网友见解:

😅分库分表核心在于突破单机性能和容量瓶颈,代价是关联表查询基本废啦!查询的时候需要带上分区字段,否则查询只能遍历所有库表,性能好不了。
所有,想突破单机性能和容量瓶颈的存储系统,基本没得选,也必须进行“分库分表”,通常会采用哈希或者范围的方式进行数据分片。
只分表性能比分库又分表差一些,不过也能解决一些问题吧?而且渐进的方式也是先分表再分库分表吧?
这节能引起共鸣,也正好需要,很有借鉴意义。

作者回复: 👍

21. 老师您好!在高并发的场景下,如何满足数据库的大量写操作呢?能不能先道缓存再写入库中,但是这样又会造成数据丢失的风险,希望老师能够解答

作者回复: 可以使用消息队列

22. 用redis记录总数,逻辑上可能会出现不一致

作者回复: 会有几率,可以手动修复

23. 老师,分库是一个库对应一台机器吗, 还是一台机器上建多个库

作者回复: 基于成本考虑,是可以一台机器多个库的

24. 感觉一个模块中所有的都得用同样的颗粒度来划分,从而做到某个用户相关的数据都在同一个库上?不然似乎不好处理事务。

作者回复:

不可以的,用户的账户数据和内容数据要拆分到不同的库中,这样可以做到模块之间错误的隔离
事务的话如果万不得已可以用分布式事务,否则一般情况下是不需要关心事务的

25. 老师好,想问下对于用户表分库分表,一般是按照主键id分,但是根据昵称查,需要在建一张id-昵称映射关系表。我看还有另外一种叫因子法的策略,就是按照算法把昵称计算成固定整数,以这个为分表键,然后前面拼接id后当作主键,这样根据id和昵称都可以计算出分表键。两个问题:1.这两种方案,哪一种更好呢,或者各适应哪类情况。2.还有就是把昵称转化为整数的算法一般用什么呢?有什么要求。

作者回复:

1. 我觉得我那种更好,比方说如果按照年龄查要怎么办呢?再拼接年龄进去吗
2. 一般的hash函数都好,比如crc32

26. 老师说如果我们发现系统时钟不准,就可以让发号器暂时拒绝发号,直到时钟准确为止。我们的程序本身就是运行在系统中的,如何来判断系统中的时间是否准确呢?

作者回复: 可以暂时记录上次发好的时间,然后和这次的时间比较

27. 网友见解

Q: 不仅仅是分库分表后的数据库,很多业务场景都需要分布式发号器,使用snowflake是个很好的选择,不过一般都是用的snowflake雪花算法,实现上会有所差异,比如机器位数和序号位数的选取就不同,1+41位时间戳+10机器区间位+12号递增或随机的数字,类似这种。uuid长度过长,也不递增,使用受限。不过snowflake算法有个问题就是服务器时间回拨的问题,就是时间可能不准,这个时候不能停止发号,我觉得可以采取的方式是:每个服务器存储最新的一个maxNewId,起个线程监控服务器时间是否正确,不正确就从maxNewId递增1获取,同事调准服务器时间,直到服务器时间正确。

作者回复: 应该可行

28. 网友见解

个人觉得“微信序列号生成器”的方法更简单,因为:
Snowflake
1. Snowflake算法是基于二进制的,对于像我这样基础不扎实的理解起来还是比较困难。
2. Snowflake集群环境下需要保证时钟同步,对运维能力有一定要求;一旦时钟错乱,又刚好是高并发时,会导致大量异常序号。
3. 如果公司运维能力有限,不适合用Snowflake。
4. 百度开源的UidGenerator(仅支持单机部署)使用Snowflake算法,单机QPS可达600万。项目说明:https://github.com/baidu/uid-generator/blob/master/README.zh_cn.md 。
5. 美团Leaf(分布式ID生成系统),QPS近5万。项目地址:https://tech.meituan.com/2017/04/21/mt-leaf.html 。

微信序列号生成器
文档地址:https://www.infoq.cn/article/wechat-serial-number-generator-architecture
1. 递增但不连续的数字序列解决方案。
2. 设计目标QPS1000万以上。
3. 通过在递增过程中使用“步长”将每秒磁盘写入由1000万级降至1万。
4. 设计原理相对于Snowflake更通俗易懂。
5. 可以使用hash的负载均衡策略组建集群。
6. 缺点:需要自己实现集群中机器增减后更新负载均衡策略的逻辑。
7. Java版最简单Demo():使用spring boot搭建一个web工程,使用Controller调用Service实现数字递增
Service类
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.util.concurrent.atomic.AtomicLong;

@Service
public class GeneratorService {

    private AtomicLong id;

    @PostConstruct
    private void init(){
        id = new AtomicLong(0);
    }

    public long getId(){
        return id.incrementAndGet();
    }
}
单机测试QPS 3万(测试工程、测试脚本在同一机器运行。)
硬件信息:CPU 2.7 GHz Intel Core i7 | 内存 16 GB 2133 MHz LPDDR3
测试工具:JMeter

29. 但是当数据库分库分表后,使用自增字段就无法保证 ID 的全局唯一性了?
1. 使用数据库的自增,设置起始值和步长不一样,不是一样可以实现吗?
2. 预估每天的数据量,预先生成ID存入缓存(比如Redis)里面,然后去取,这种方法也简单?

作者回复:

其实很难预估数据量,某一天有活动咋办?不同的起始值也可,只是增加人工成本,增加了库表咋办?忘了设置咋办?

30. Snowflake不能保证单调递增吧?首先,服务器的时钟可能有快有慢;其次,同一时刻,机器号大的机器生成的ID总是大于机器号小的机器,但他的请求可能是先到达了数据库。个人观点:主键还是要用数据库的自增id,另外再加个全局唯一的code作为业务主键。

作者回复:

首先,服务器的时钟一般是对时的
其次,如果是单独部署的发号器,没有机器ID是可以保证单调递增的

31. 网友见解

1.数据库自增的全局唯一键。可以在设计出按一定步进生成id。比如分库为3台,每台的主键id初始值分别为0、1、2自增步进为3。这样也可以唯一。不过数据库作为整个系统的吊车尾。还是别拿它搞事了。
2.如果业务没有id带有实时字段的要求,那么可以用预生成备用的方式。客户端服务每次按一定步进来拉取id集合,并缓存到客户端本地内存。如此也能有效率的提升。(哪怕有实时业务段,也可以将非业务的其他部分生成好,到客户端用时再拼接)

作者回复: 👍

32. 请问下同一时间位,同一机器,在生成序列号时,是要上锁的吧?

作者回复: 是的 不过像redis那样单线程处理就好了

33. 想知道那个机器ID如何设置,如果节点重启就再分配一个机器ID的话,那10位的机器ID也支撑不了多少次的重启啊

作者回复: ID在同一时间是可以重复的,所以每次重启选择一个可用的,或者一台机器用一个固定的就好了

34. 老师,不太明白,mycat也能生成唯一自增ID,为什么还要实现发号器,请问这俩有什么区别

作者回复: mycat只能给数据库用,发号器更通用

35. 老师,序列号设置为10位每毫秒生成1024个序号,拿评论来说,也就是每秒支持1024*1000的并发评论么,另外,雪花算法生成的主键也会作为分库表的一列保存在数据库里么

作者回复: 是的

36. 为什么snowflake的第一位一定是0?

作者回复: 标准实现是的

37. 老师好呀,发号器生成id当主键的话,由于位数较多,对数据库索引性能影响大么

作者回复: 不会的

38. 老师,有相关的示例代码不?我的理解是每一个毫秒将下41时间戳加1,10位的机器不变,12的序列号先随机生成一个数字,然后再在这个基础上生成这一毫秒所需要的全局id的数量。不知道我理解的对不对。打卡09.

作者回复: 是的,没错

39. 问2个问题
1.为什么第一位要保留不用?
2.如何知道时钟不对,让程序自动拒绝了?

作者回复:

1. 我觉得是为之后扩展考虑
2. 可以记录上次发号的时间,然后和这次发号时间比对

40. 老师好,“如果我们发现系统时钟不准,就可以让发号器暂时拒绝发号,直到时钟准确为止。”
1、那不就中断业务了?
2、话说时钟回拨是什么怎么回事呀?我看网上说是硬件层面导致的,但是还是不明白具体是怎么导致的。
3、如果发生回拨,回拨的时间大概是多少呀?
谢谢

作者回复:

1. 是的,此时算是发号器故障
2. 应该是对时不准
3. 时间是不固定的,不过可以通过ntp对时

41. 关于41位的时间戳的可支撑时间问题, 如果时间戳是从0开始计算则约可以支持69年, 但如果以当前时间开始算, 则可用的只有不到20年了(69-(2019-1970))

作者回复: 你实现时可以不以1970年为基准时间

42. UidGenerator和leaf-segment是不是推特雪花算法原理?

作者回复: uidgenaerator是的,leaf-segment是基于数据库的,leaf-snowflake是的

43. 请问老师,数据库的分布式与分布式数据库有什么区别没有

作者:数据库分布式=你在数据库的基础上实现分布式
分布式数据库=数据库自己实现了分布式

例如ES,就属于分布式存储,它本身实现了分片,副本,切换,复制,故障恢复等功能; 而MySQL只实现了集群复制。

15 | 可编程的互联网世界-极客时间

44. 1900: 对象存储作为目前新颖的一种存储类型,它相对于传统网络存储类型的优势在哪里呢?另外它也有什么不足或者局限么?

作者回复: 后面服务端开发会讨论这个问题。最大的区别其实不是协议的选择,而是编程模型的改变。传统网络存储延续了本地文件系统的习惯,基本上都是filesystem的树状元数据组织方式,对象存储是key-value这种平面结构。

4.参考

08 | 数据库优化方案(一):查询请求增加时,如何做主从分离?-极客时间

09 | 数据库优化方案(二):写入数据量增加时,如何实现分库分表?-极客时间

10 | 发号器:如何保证分库分表后ID的全局唯一性?-极客时间

11 | NoSQL:在高并发场景下,数据库和NoSQL如何做到互补?-极客时间

分库分表?如何做到永不迁移数据和避免热点?

MySQL - 分库/分表 - 学习/实践_william_n的博客-CSDN博客

后续补充

...


网站公告

今日签到

点亮在社区的每一天
去签到