Mysql高可用高性能存储应用系列4 - 分库分表、中间件

2023-03-31 09:04:34 浏览数 (2)

概述

为什么要分库的原因:1)很多时候接口性能慢都是数据库造成的,2)并发量比较大时,大量的数据库请求,会带来磁盘I/O的性能瓶颈,3)来越多,导致sql查询数据,即使走了索引也比较慢。

分库分表的场景

分库和分表是不同的两个概念,解决的问题也不同。

  • 并发量很大,但数据量比较小,可以只分库,不分表。
  • 并发量不大,但数据量比较大,可以只分表,不分库。
  • 并发量很大,数据量也比较多时,既要分库,也要分表。

垂直分库分表

分库垂直分库,针对一个系统的不同业务进行拆分,比如:用户拆到User库,文章拆到Novel库,拆分后放到不同的服务器上,在高并发一定程度能够给解决I/O链接数,硬件资源瓶颈等。

垂直分表

垂直分表,整体策略就是大表拆小表,基于表中字段拆分,将不常用的,数据较大的拆分到扩展表,一般针对几百列的大表进行拆分。

特点:

  • 每个库/表的结构都不一样
  • 每个库/表的数据至少一列一样
  • 每个库/表的并集是全量数据

垂直拆分:

优点:拆分后业务清晰,数据维护简单,按照业务放到不同的服务器中。

缺点:

  • 单表数据量大时,写读压力大
  • 受业务影响,热门业务压力大,冷门业务造成资源浪费。

比如我们常用的user表,由2部分主要的功能组成,用户和密码主要是用来鉴权用户,一部分是展示用户信息,这个表就可以拆分成2个表。

代码语言:txt复制
mysql> desc user;
 ---------------------- --------------------- ------ ----- ------------ ---------------- 
| Field                | Type                | Null | Key | Default    | Extra          |
 ---------------------- --------------------- ------ ----- ------------ ---------------- 
| user_id              | int(4)              | NO   | PRI | NULL       | auto_increment |
| user_name            | varchar(16)         | NO   | MUL | NULL       |                |
| user_psw             | varchar(50)         | NO   |     | NULL       |                |
| user_status          | tinyint(1)          | NO   |     | 1          |                |
| user_display         | tinyint(4)          | NO   |     | 1          |                |
| user_nickname        | varchar(50)         | YES  | MUL | NULL       |                |
| user_email           | varchar(50)         | NO   | MUL | NULL       |                |
| user_qq              | varchar(15)         | YES  |     | NULL       |                |
| user_tel             | varchar(18)         | YES  | MUL | NULL       |                |
| user_sex             | int(1)              | NO   |     | 1          |                |
| user_birthday        | date                | NO   |     | 1991-10-24 |                |
| user_head            | varchar(200)        | NO   |     | NULL       |                |
| user_sign            | varchar(225)        | NO   |     | NULL       |                |
| user_ticket_act      | int(11)             | NO   |     | 0          |                |
| user_gold2           | decimal(12,3)       | NO   |     | 0.000      |                |
| user_gold2_frozen    | int(11)             | NO   |     | 0          |                |
| user_ticket_rec      | int(11)             | NO   |     | 0          |                |
| user_ischarge        | tinyint(1)          | NO   |     | 0          |                |
| user_exp             | int(4)              | NO   |     | 0          |                |
| user_browse          | int(4)              | NO   |     | 0          |                |
| user_logintime       | int(4)              | NO   |     | 0          |                |
| user_regtime         | int(4)              | NO   | MUL | 0          |                |
| user_regip           | varchar(15)         | NO   |     | 0.0.0.0    |                |
| user_displaytime     | int(4)              | NO   |     | 0          |                |
| user_nickname_before | text                | YES  |     | NULL       |                |
| user_auth            | tinyint(1)          | NO   |     | 0          |                |
| delete_time          | int(11)             | NO   |     | 0          |                |
| refresh_token        | varchar(50)         | NO   |     |            |                |
| nation_code          | int(10) unsigned    | NO   |     | 86         |                |
| credit               | int(11)             | NO   |     | 0          |                |
| close_time           | int(10) unsigned    | NO   |     | 0          |                |
| finance_id           | int(11)             | NO   |     | 0          |                |
| user_underage_status | tinyint(1)          | NO   |     | 0          |                |
| attr_limit           | varchar(20)         | NO   |     | NULL       |                |
| identity             | tinyint(3) unsigned | NO   |     | 1          |                |
 ---------------------- --------------------- ------ ----- ------------ ---------------- 
35 行于数据集 (0.08 秒)

水平拆分分库分表

  • 水平分表,针对数据量巨大的单表,按照某种规则,拆分到多个表中,但是这些表还是在一个库中。
  • 水平库分表,按照某种规则,把拆分的表再拆到不同的库中去。

水平分库的分表规则

  • RANGE, 按照范围拆分,比如0-10000一个表,10001到20000一个表
  • HASH取模,比如通过用户ID取模,然后分配到不同的库表中。
  • 地理区域,比如按照华北,东北等区域区分。
  • 时间拆分,比如将6个月前的数据拆出去放到一张表,随着时间的流逝,这些表的数据查询的几率很小,这也是冷热数据分离。

水平拆分

优点:

  • 单库/表的数据减少,有利性能
  • 库/表结构相同,程序改动小

缺点:

  • 数据库扩容难度大,比如取模值变了

水平拆分的好处在于使用一个业务场景的划分值,比如user_id进行取模进行分表,但是在后台管理数据的时候会遇到一个问题,数据聚合和保持一致性的问题,比如我们现在业务中的评论数据就很多,user_id 对 128进行取模,再后台管理的时候,把数据再存储在MongoDB中一份做数据管理使用,所有的方法都会有优缺点的,看你怎么设计更符合业务场景。

一致性Hash算法

普通取模方法

根据10取模,数据1保存到节点上,数据2保存在节点2上,当节点数发生变化,分子发生了变化,需要同步旧数据的值。

一致性Hash算法

  • hash值是非负的整数,值的范围构成一个圆环,值为2^32
  • 集群节点按照一定的规则求hash值,然后放在环中
  • 对数据K求hash值,然后放入环中,在按照顺时针方向找到最近的节点,保存到上面

一致性Hash算法是怎么解决节点上的存储不均的情况呢?

在节点上创建虚拟节点,让虚拟节点对应真实节点,让数据的存储尽量均衡,这样就解决了数据不均的情况。

当添加新节点时,在普通取模算法中会影响到其他数据,而在一致性Hash算法中,首先会停止服务,防止数据乱掉,先把属于新增节点D的数据迁移到D节点中,然后添加节点,放入环中,启动服务。

当移除节点时,首先停止服务,把D节点移除之后,根据顺时针原则,把属于D节点的数据迁移在节点A上,把D节点服务移除,启动服务,整个节点受到影响的只有D节点的数据,不会影响其他的服务节点。

中间件

中间件的范围比较笼统,我理解的中间件就是在数据处理流程里,加入了一层逻辑处理,用规定好的规则下发在下一个流程中,主要介绍Mysql的2种中间件,ShardingSphere(直连模式) 和 MyCat(代理模式)。

  • ShardingSphere:Jdbc直连,相当于增强JDBC包,对所有的数据库操作语句按照特定的要求分发到不同的库中,这部分操作在jdbc中直接完成了。
  • MyCat:proxy代理,把sql发送给代理服务,代理服务去数据库完成相应的操作后再返回给web应用。

0 人点赞