设为首页 - 加入收藏 ASP站长网(Aspzz.Cn)- 科技、建站、经验、云计算、5G、大数据,站长网!
热搜: 创业者 数据 手机
当前位置: 首页 > 站长学院 > MySql教程 > 正文

别再问“分库分表”了,再问就崩溃了!(5)

发布时间:2019-12-20 09:47 所属栏目:115 来源:站长网
导读:这样的好处是:毫秒数在高位,生成的 ID 整体上按时间趋势递增;不依赖第三方系统,稳定性和效率较高。 理论上 QPS 约为 409.6w/s(1000*2^12),并且整个分布式系统内不会产生 ID 碰撞;可根据自身业务灵活分配 bit 位

别再问“分库分表”了,再问就崩溃了!

这样的好处是:毫秒数在高位,生成的 ID 整体上按时间趋势递增;不依赖第三方系统,稳定性和效率较高。

理论上 QPS 约为 409.6w/s(1000*2^12),并且整个分布式系统内不会产生 ID 碰撞;可根据自身业务灵活分配 bit 位。

不足就在于:强依赖机器时钟,如果时钟回拨,则可能导致生成 ID 重复。

综上结合数据库和 Snowflake 的唯一 ID 方案,可以参考业界较为成熟的解法:Leaf——美团点评分布式 ID 生成系统,并考虑到了高可用、容灾、分布式下时钟等问题:

https://tech.meituan.com/2017/04/21/mt-leaf.html 

数据迁移、扩容问题

当业务高速发展,面临性能和存储的瓶颈时,才会考虑分片设计,此时就不可避免的需要考虑历史数据迁移的问题。

一般做法是先读出历史数据,然后按指定的分片规则再将数据写入到各个分片节点中。

此外还需要根据当前的数据量和 QPS,以及业务发展的速度,进行容量规划,推算出大概需要多少分片(一般建议单个分片上的单表数据量不超过 1000W)。

如果采用数值范围分片,只需要添加节点就可以进行扩容了,不需要对分片数据迁移。如果采用的是数值取模分片,则考虑后期的扩容问题就相对比较麻烦。

什么时候考虑切分

下面讲述一下什么时候需要考虑做数据切分。

①能不切分尽量不要切分

并不是所有表都需要进行切分,主要还是看数据的增长速度。切分后会在某种程度上提升业务的复杂度,数据库除了承载数据的存储和查询外,协助业务更好的实现需求也是其重要工作之一。

不到万不得已不用轻易使用分库分表这个大招,避免"过度设计"和"过早优化"。

分库分表之前,不要为分而分,先尽力去做力所能及的事情,例如:升级硬件、升级网络、读写分离、索引优化等等。当数据量达到单表的瓶颈时候,再考虑分库分表。

②数据量过大,正常运维影响业务访问

这里说的运维指:

对数据库备份,如果单表太大,备份时需要大量的磁盘 IO 和网络 IO。例如 1T 的数据,网络传输占 50MB 时候,需要 20000 秒才能传输完毕,整个过程的风险都是比较高的。

对一个很大的表进行 DDL 修改时,MySQL 会锁住全表,这个时间会很长,这段时间业务不能访问此表,影响很大。

如果使用 pt-online-schema-change,使用过程中会创建触发器和影子表,也需要很长的时间。在此操作过程中,都算为风险时间。将数据表拆分,总量减少,有助于降低这个风险。

大表会经常访问与更新,就更有可能出现锁等待。将数据切分,用空间换时间,变相降低访问压力。

③随着业务发展,需要对某些字段垂直拆分

举个例子,假如项目一开始设计的用户表如下:

id bigint #用户的IDname varchar #用户的名字last_login_time datetime #最近登录时间personal_info text #私人信息..... #其他信息字段

在项目初始阶段,这种设计是满足简单的业务需求的,也方便快速迭代开发。

而当业务快速发展时,用户量从 10w 激增到 10 亿,用户非常的活跃,每次登录会更新 last_login_name 字段,使得 user 表被不断 update,压力很大。

而其他字段:id,name,personal_info 是不变的或很少更新的,此时在业务角度,就要将 last_login_time 拆分出去,新建一个 user_time 表。

personal_info 属性是更新和查询频率较低的,并且 text 字段占据了太多的空间。这时候,就要对此垂直拆分出 user_ext 表了。

④数据量快速增长

随着业务的快速发展,单表中的数据量会持续增长,当性能接近瓶颈时,就需要考虑水平切分,做分库分表了。此时一定要选择合适的切分规则,提前预估好数据容量。

⑤安全性和可用性

鸡蛋不要放在一个篮子里。在业务层面上垂直切分,将不相关的业务的数据库分隔,因为每个业务的数据量、访问量都不同,不能因为一个业务把数据库搞挂而牵连到其他业务。

利用水平切分,当一个数据库出现问题时,不会影响到 100% 的用户,每个库只承担业务的一部分数据,这样整体的可用性就能提高。

案例分析

用户中心业务场景

用户中心是一个非常常见的业务,主要提供用户注册、登录、查询/修改等功能,其核心表为:

User(uid, login_name, passwd, sex, age, nickname) 

 

uid为用户ID,  主键 

login_name, passwd, sex, age, nickname,  用户属性 

任何脱离业务的架构设计都是耍流氓,在进行分库分表前,需要对业务场景需求进行梳理:

用户侧:前台访问,访问量较大,需要保证高可用和高一致性。

主要有两类需求:

用户登录:通过 login_name/phone/email 查询用户信息,1% 请求属于这种类型。

用户信息查询:登录之后,通过 uid 来查询用户信息,99% 请求属这种类型。

运营侧:后台访问,支持运营需求,按照年龄、性别、登陆时间、注册时间等进行分页的查询。是内部系统,访问量较低,对可用性、一致性的要求不高。

水平切分方法

当数据量越来越大时,需要对数据库进行水平切分,上文描述的切分方法有"根据数值范围"和"根据数值取模"。

"根据数值范围":以主键 uid 为划分依据,按 uid 的范围将数据水平切分到多个数据库上。

例如:user-db1 存储 uid 范围为 0~1000w 的数据,user-db2 存储 uid 范围为 1000w~2000w uid 数据。

优点是:扩容简单,如果容量不够,只要增加新 DB 即可。

不足是:请求量不均匀,一般新注册的用户活跃度会比较高,所以新的 user-db2 会比 user-db1 负载高,导致服务器利用率不平衡。

"根据数值取模":也是以主键 uid 为划分依据,按 uid 取模的值将数据水平切分到多个数据库上。

例如:user-db1 存储 uid 取模得 1 的数据,user-db2 存储 uid 取模得 0 的 uid 数据。

优点是:数据量和请求量分布均匀。

不足是:扩容麻烦,当容量不够时,新增加 DB,需要 rehash。需要考虑对数据进行平滑的迁移。

非 uid 的查询方法

水平切分后,对于按 uid 查询的需求能很好的满足,可以直接路由到具体数据库。

而按非 uid 的查询,例如 login_name,就不知道具体该访问哪个库了,此时需要遍历所有库,性能会降低很多。

对于用户侧,可以采用"建立非 uid 属性到 uid 的映射关系"的方案;对于运营侧,可以采用"前台与后台分离"的方案。

①建立非 uid 属性到 uid 的映射关系

映射关系:例如:login_name 不能直接定位到数据库,可以建立 login_name→uid 的映射关系,用索引表或缓存来存储。

当访问 login_name 时,先通过映射表查询出 login_name 对应的 uid,再通过 uid 定位到具体的库。

映射表只有两列,可以承载很多数据,当数据量过大时,也可以对映射表再做水平切分。

这类 kv 格式的索引结构,可以很好的使用 cache 来优化查询性能,而且映射关系不会频繁变更,缓存命中率会很高。

基因法:分库基因,假如通过 uid 分库,分为 8 个库,采用 uid%8 的方式进行路由,此时是由 uid 的最后 3bit 来决定这行 User 数据具体落到哪个库上,那么这 3bit 可以看为分库基因。

上面的映射关系的方法需要额外存储映射表,按非 uid 字段查询时,还需要多一次数据库或 cache 的访问。

如果想要消除多余的存储和查询,可以通过 f 函数取 login_name 的基因作为 uid 的分库基因。

生成 uid 时,参考上文所述的分布式唯一 ID 生成方案,再加上最后 3 位 bit 值=f(login_name)。

当查询 login_name 时,只需计算 f(login_name)%8 的值,就可以定位到具体的库。

不过这样需要提前做好容量规划,预估未来几年的数据量需要分多少库,要预留一定 bit 的分库基因。

(编辑:ASP站长网)

网友评论
推荐文章
    热点阅读