数据库瓶颈

1)为什么对数据库进行优化

在整个系统架构中,最难提升性能,最容易出现性能瓶颈的中间件往往都是数据库,为此我们会使用一些中间件来减轻数据库压力,如 redis、MQ流量削峰等,但是 数据库本身性能如果得不到提升,依旧是架构中的短板。

为此我们需要对数据库进行一定的性能优化,如分库分表,数据库集群等。

值得注意的是,不要为了分库分表而去分库分表,并不是字面上的将数据分到多个库或者多个表这么简单,而是要根据实际的应用场景能解决某些问题,才去分库分表。

2)MySQL主从同步原理

  • 主从同步的目的:保证主库和从库之间的数据库保持一致
  • 默认情况下,MySQL是不会开启BinLog日志的,需要在配置文件中手动开启
    • 开启BinLog日志后,MySQL对数据库的所有操作行为都会写入到BinLog日志中
  • 同步的过程都是由子线程完成操作,并不会影响到主线程的正常运作

主从同步的中间件和方法非常多,我们下面使用的方法是他自身提供的一套通过Binlog日志在多个MySQL服务之间进行同步的集群方案,我们可以在这套方案基础之上完善 读写分离、故障转移等其他功能。

MySQL主从同步原理

主从同步流程

  1. slave启动时,会通过IO现场与master建立TCP连接
  2. 当master修改了数据库后,往BinLog写入日志时,会通过IO Dump线程把 BinLog日志通过TCP连接发送给slave
  3. slave收到BinLog日志后会存储在本地的RelayLog中,在通过子线程 读取RelayLog 同步数据

搭建一主一从架构

1)Master节点

1.修改配置文件

  • master节点的配置文件地址: /etc/my.cnf ,如果没有该文件可以自己创建一个
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[mysqld]
# mysql节点唯一标识
server-id=1
#开启binlog
log_bin=master-bin
log_bin-index=master-bin.index
# 设置连接端口
port=3306
# 允许最大连接数
max_connections=200
# 允许连接失败的次数。
max_connect_errors=10
# 服务端使用的字符集默认为UTF8
character-set-server=utf8
# 创建新表时将使用的默认存储引擎
default-storage-engine=INNODB
# 默认使用“mysql_native_password”插件认证
#mysql_native_password
default_authentication_plugin=mysql_native_password

配置说明

server-id:mysql节点的唯一标识,集群中的每个服务都要保持唯一

log_bin:打开Binlog日志记录,并指定文件名。

log_bin-index:Binlog日志文件

配置完后,我们需要重启MySQL服务 service mysqld restart

2.master节点分配同步权限

  • 完成第一步的配置后,我们还需要在master节点上的mysql client下给root用户分配一个replication slave的权限。

  • 在实际生产环境中,通常不会直接使用root用户,而会创建一个拥有全部权限的用户来负责主从同步。

1
2
3
4
5
6
7
8
9
10
11
12
13
# 进入client
[root@S1 data]# mysql -uroot -p

# 分配 slave复制权限
mysql> GRANT REPLICATION SLAVE ON *.* TO 'root'@'%';
#Query OK, 0 rows affected (0.00 sec)

# 刷新缓存(把user和privilige的最新信息缓存到内存)
mysql> flush privileges;
#Query OK, 0 rows affected (0.00 sec)

#查看主节点同步状态:
mysql> show master status;

查看主节点同步状态

mysql> show master status;

主从同步状态

image-20230312095958151

如图所示,File和Position分别表示了 BinLog文件名和文件的下标(最新的记录位置)。

开启BinLog日志后,所有的操作记录都会被记录到文件中

2)Slave节点

1.修改配置文件

slave节点的配置文件地址: /etc/my.cnf ,如果没有该文件可以自己创建一个

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[mysqld]
server-id=2
#打开MySQL中继日志
relay-log-index=slave-relay-bin.index
relay-log=slave-relay-bin
#打开从服务二进制日志
log-bin=mysql-bin
#使得更新的数据写进二进制日志中
log-slave-updates=1
# 设置3306端口
port=3306
# 允许最大连接数
max_connections=200
# 允许连接失败的次数。
max_connect_errors=10
# 服务端使用的字符集默认为UTF8
character-set-server=utf8
# 创建新表时将使用的默认存储引擎
default-storage-engine=INNODB
# 默认使用“mysql_native_password”插件认证
#mysql_native_password
default_authentication_plugin=mysql_native_password

配置说明

server-id:mysql节点的唯一标识,集群中的每个服务都要保持唯一

relay-log:打开从服务的relay-log日志。

log-bin:打开从服务的bin-log日志记录。

配置完后,我们需要重启MySQL服务 service mysqld restart

2.配置主从同步

我们需要在slave节点上,配置master 节点的同步信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[root@S2 ~]# mysql -uroot -p

# 设置同步主节点信息
mysql> CHANGE MASTER TO
MASTER_HOST='10.211.55.12',
MASTER_PORT=3306,
MASTER_USER='root',
MASTER_PASSWORD='123123',
MASTER_LOG_FILE='master-bin.000003',
MASTER_LOG_POS=157,GET_MASTER_PUBLIC_KEY=1;

#开启slave
mysql> start slave;

#查看主从同步状态
mysql> show slave status \G;

主从状态

Slave_IO_State:等待master节点发送同步

Slave_IO_Running:slave IO正常

Slave_SQL_Running:slave sql正常

如何判断主从复制的状态是否正常?

  • 只要上面2个状态,slave IO和slave SQL状态都是YES,代表主从复制状态正常,如果有一个为NO,则不正常。

关于虚拟机克隆问题

如果在本地做实验时,在虚拟机上安装了MySQL后直接克隆成一主一从的话,MySQL给虚拟机分配的UUID会一模一样,导致主从同步时出现问题(Slave_IO_Running=no),为此我们需要为给虚拟机分配唯一的UUID。

  1. 查找出UUID的配置文件
1
2
[root@S2 ~]# find / -iname "auto.cnf"
/usr/local/mysql-8.0.31-linux-glibc2.17-aarch64/data/auto.cnf
  1. 删除所有查询出来的UUID文件,删除之后重启MySQL,MySQL会重新为其创建唯一的UUID
1
[root@S2 ~]# rm -rf /usr/local/mysql-8.0.31-linux-glibc2.17-aarch64/data/auto.cnf
  1. 进入client,重启slave
1
2
3
4
5
6
7
# 重启mysql
[root@S2 ~]# service mysqld restart
[root@S2 ~]# mysql -uroot -p
mysql> stop slave;
mysql> start slave;
# 查看主从同步状态
mysql> show slave status \G;

关于BinLog文件同步问题

Master_Log_File和Read_Master_Log_Pos的信息,要与master节点的 File和Position一致,如果不一致就会出现数据不一致的问题。

关于BinLog文件同步问题

事务回滚导致同步失败问题

如果在slave从服务上查看slave状态,发现Slave_SQL_Running=no,就表示主从同步失败了。这有可能是因为在从数据库上进行了写操作,与同步过来的SQL操作冲突了,也有可能是slave从服务重启后有事务回滚了。

如果是因为slave从服务事务回滚的原因,可以按照以下方式重启主从同步:

1
2
3
mysql> stop slave ;
mysql> set GLOBAL SQL_SLAVE_SKIP_COUNTER=1;
mysql> start slave ;

而另一种解决方式就是重新记录主节点的binlog文件消息

1
2
3
4
5
6
7
8
9
10
11
12
13
# 停止slave
mysql> stop slave ;

mysql> CHANGE MASTER TO
MASTER_HOST='10.211.55.12',
MASTER_PORT=3306,
MASTER_USER='root',
MASTER_PASSWORD='123123',
MASTER_LOG_FILE='master-bin.000002',
MASTER_LOG_POS=157,GET_MASTER_PUBLIC_KEY=1;

# 启动slave
mysql> start slave ;

但是这种方式要注意binlog的文件和位置,如果修改后和之前的同步接不上,那就会丢失部分数据。所以不太常用。

3)主从集群测试

测试之前,我们先通过命令 查看2个节点之中的数据库情况

数据库对比

在master节点上创建一个数据库:

1
2
mysql> create database synctest;
#Query OK, 1 row affected (0.02 sec)

随后在通过命令观察,发现master新创建的数据库成功同步到了slave。

数据库对比

在master节点上创建一个表:

1
2
3
4
5
6
7
mysql> use synctest;
#Database changed
mysql> create table syncTestTable(id int not null);
#Query OK, 0 rows affected (0.05 sec)

mysql> insert into syncTestTable value(1);
#Query OK, 1 row affected (0.02 sec)

数据库对比

如图所示,master创建表并插入了一条数据,也成功同步到了slave。

4)全库同步和部分数据库同步

  • 在实际环境中,很多时候不会进行全库同步,而只对部分重要的数据库进行同步。

上面的配置是针对全部数据库进行同步,我们也可以指定 只同步部分数据库或指定 不同步 哪些数据库。

在MySQL Client中,我们通过 mysql> show master status; 可以看到只同步的数据库(Do_DB)及忽略同步的数据库(Ignore_DB)。

show master status

在master端 配置my.cnf文件,指定同步哪些数据库:

1
2
3
4
5
6
7
8
#需要同步的二进制数据库名
binlog-do-db=synctest
##只保留7天的二进制日志,以防磁盘被日志占满(可选)
expire-logs-days = 7
##不备份的数据库,因为mysql自带的一些数据没必要同步
binlog-ignore-db=information_schema
binlog-ignore-db=performation_schema
binlog-ignore-db=sys

在slave端 配置my.cnf文件:

1
2
3
4
5
6
7
8
9
#如果salve库名称与master库名相同,使用本配置
replicate-do-db = synctest

#如果master库名[synctest]与salve库名[synctest01]不同,使用以下配置[需要做映射]
#replicate-rewrite-db = synctest -> synctest01

#如果不是要全部同步[默认全部同步],则指定需要同步的表
#replicate-wild-do-table=synctest.t_dict
#replicate-wild-do-table=synctest.t_num

两边都配置成功后,重启MySQL service mysqld restart后,我们在master上创建一个新的数据库,该数据库不会同步给slave。

创建数据库

再次查看master的同步状态,可以看到Binlog_Do_DB和Ignore_DB列就是我们刚才在my.cnf中所配置的数据库。

show master status

5)GTID同步集群

上面的集群方式是通过BinLog日志实现的,在MySQL5.6后 新增了GTID同步模式,我们show master status 会有一个Executed_Grid_Set列是空的,该列就是代表GTID同步的位置。

GTID的本质也是基于Binlog来实现主从同步,只是他会基于一个全局的事务ID来标识同步进度。GTID即全局事务ID,全局唯一并且趋势递增,他可以保证为每一个在主节点上提交的事务在复制集群中可以生成一个唯一的ID 。

在基于GTID的复制中,首先从服务器会告诉主服务器已经在从服务器执行完了哪些事务的GTID值,然后主库会有把所有没有在从库上执行的事务,发送到从库上进行执行,并且使用GTID的复制可以保证同一个事务只在指定的从库上执行一次,这样可以避免由于偏移量的问题造成数据不一致。

他的搭建方式跟我们上面的主从架构整体搭建方式差不多。只是需要在my.cnf中修改一些配置。

Master:

1
2
3
4
gtid_mode=on
enforce_gtid_consistency=on
log_bin=on
binlog_format=row

Slave:

1
2
3
gtid_mode=on
enforce_gtid_consistency=on
log_slave_updates=1

然后分别重启主服务和从服务,就可以开启GTID同步复制方式。

集群扩容与MySQL数据迁移

想把一主一从扩展成一主多从得分2个情况:

  1. 集群还没有开始运行
    • 如果集群还没有开始运行,只需要在slave做相同的配置 就可以了,具体可以参考上面的slave节点配置
  2. 集群已经运行过一段时间
    • 如果集群已经运行过一段时间,那么之前的数据就无法通过BinLog来恢复,这个时候如果要扩容slave节点,就得先做全量复制

master数据备份恢复

MySQL的数据备份恢复操作相对比较简单,可以通过SQL语句直接来完成。具体操作可以使用mysql的bin目录下的mysqldump工具。

1
2
# 把master的数据库导出成sql
mysqldump -u root -p --all-databases > backup.sql

通过这个指令,就可以将整个数据库的所有数据导出成backup.sql,然后把这个backup.sql分发到新的MySQL服务器上,并执行下面的指令将数据全部导入到新的MySQL服务中。

1
mysql -u root -p < backup.sql

这样新的MySQL服务就已经有了所有的历史数据,然后就可以再按照上面的slave步骤,配置Slave从服务的数据同步了。

主从集群半同步复制

因为主节点发送BinLog时是通过异步线程且没有ack机制,所以在主从集群中会存在丢失数据的安全隐患。

1)为什么会丢失数据

image

  • 由于主节点是通过异步线程发送BinLog日志,且没有ack机制,所以主节点并不知道BinLog日志是否发送成功,如果在发送的过程中 主节点宕机了,那么就会存在数据丢失的问题,所以我们就得通过一种机制 半同步复制机制,来尽可能的避免 数据丢失问题。

通过半同步复制解决问题

MySQL半同步复制

  • 主节点发送BinLog日志后,会等待slave的ack
    • 如果收到ack就代会返回成功响应
    • 如果收到错误的ack,就会返回客户端失败响应
    • 如果超过一定时间后没有收到ack,就会直接不管了,返回成功响应
  • 使用该方法,用时间换数据安全,但是只能尽可能的避免数据丢失(无法说百分百不丢失数据)
  • 性能相对来说,所有的DDL和DML语句(除了数据查询语句select) 操作速度会下降

2)配置半同步复制

半同步复制基于MySQL5.5新增的插件,该插件在lib/plugin目录下的semisync_master.so和semisync_slave.so两个文件中。需要在主服务上安装semisync_master模块,在从服务上安装semisync_slave模块。

file

Master 安装semisync_master模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
mysql> install plugin rpl_semi_sync_master soname 'semisync_master.so';
Query OK, 0 rows affected (0.01 sec)

# 默认状态是OFF 没开启的
mysql> show global variables like 'rpl_semi%';
+-------------------------------------------+------------+
| Variable_name | Value |
+-------------------------------------------+------------+
| rpl_semi_sync_master_enabled | OFF |

#半同步复制时等待应答的最长等待时间,默认是10秒,可以根据情况自行调整
| rpl_semi_sync_master_timeout | 10000 |
| rpl_semi_sync_master_trace_level | 32 |
| rpl_semi_sync_master_wait_for_slave_count | 1 |
| rpl_semi_sync_master_wait_no_slave | ON |

# rpl_semi_sync_master_wait_point 半同步模式
| rpl_semi_sync_master_wait_point | AFTER_SYNC |
+-------------------------------------------+------------+
6 rows in set, 1 warning (0.02 sec)

# 开启master半同步复制
mysql> set global rpl_semi_sync_master_enabled=ON;
Query OK, 0 rows affected (0.00 sec)

rpl_semi_sync_master_timeout 等待ACK应答超时时间

  • 半同步复制时等待应答的最长等待时间,默认是10秒,可以根据情况自行调整。

rpl_semi_sync_master_wait_point 半同步模式

AFTER_SYNC

  • 默认模式,主库把日志写入binlog,并且复制给从库,然后开始等待从库的响应。从库返回成功后,主库再提交事务,接着给客户端返回一个成功响应。

AFTER_COMMIT

  • 主库写入binlog后,等待binlog复制到从库,主库就提交自己的本地事务,再等待从库返回给自己一个成功响应,然后主库再给客户端返回响应。

Slave安装安装smeisync_slave模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
mysql> install plugin rpl_semi_sync_slave soname 'semisync_slave.so';
Query OK, 0 rows affected (0.01 sec)

mysql> show global variables like 'rpl_semi%';
+---------------------------------+-------+
| Variable_name | Value |
+---------------------------------+-------+
| rpl_semi_sync_slave_enabled | OFF |
| rpl_semi_sync_slave_trace_level | 32 |
+---------------------------------+-------+
2 rows in set, 1 warning (0.01 sec)

# 开启slave的半同步模式
mysql> set global rpl_semi_sync_slave_enabled = on;
Query OK, 0 rows affected (0.00 sec)

mysql> show global variables like 'rpl_semi%';
+---------------------------------+-------+
| Variable_name | Value |
+---------------------------------+-------+
| rpl_semi_sync_slave_enabled | ON |
| rpl_semi_sync_slave_trace_level | 32 |
+---------------------------------+-------+
2 rows in set, 1 warning (0.00 sec)

# 停止slave
mysql> stop slave;
Query OK, 0 rows affected (0.01 sec)

# 启动slave
mysql> start slave;
Query OK, 0 rows affected (0.01 sec)
  • slave安装完插件后一定要重启slave服务,否则不会生效。

MySQL 其他高可用集群方案

上面描述的主从架构,无法解决 主从切换的问题,称不上高可用架构,所以需要借助一下三方的工具和脚本来实现,如:mycat、ShardingSphere。

常见的MySQL集群方案有三种: MMM、MHA、MGR。这三种高可用框架都有一些共同点:

  • 对主从复制集群中的Master节点进行监控
  • 自动的对Master进行迁移,通过VIP。
  • 重新配置集群中的其它slave对新的Master进行同步

1)MMM

MMM(Master-Master replication managerfor Mysql,Mysql主主复制管理器)是一套由Perl语言实现的脚本程序,可以对mysql集群进行监控和故障迁移。他需要两个Master,同一时间只有一个Master对外提供服务,可以说是主备模式。

他是通过一个VIP(虚拟IP)的机制来保证集群的高可用。整个集群中,在主节点上会通过一个VIP地址来提供数据读写服务,而当出现故障时,VIP就会从原来的主节点漂移到其他节点,由其他节点提供服务。

MMM

优点:

  • 提供了读写VIP的配置,使读写请求都可以达到高可用

  • 工具包相对比较完善,不需要额外的开发脚本

  • 完成故障转移之后可以对MySQL集群进行高可用监控

缺点:

  • 故障简单粗暴,容易丢失事务,建议采用半同步复制方式,减少失败的概率
  • 目前MMM社区已经缺少维护,不支持基于GTID的复制

适用场景:

  • 读写都需要高可用的
  • 基于日志点的复制方式

2)MHA

Master High Availability Manager and Tools for MySQL。是由日本人开发的一个基于Perl脚本写的工具。这个工具专门用于监控主库的状态,当发现master节点故障时,会提升其中拥有新数据的slave节点成为新的master节点,在此期间,MHA会通过其他从节点获取额外的信息来避免数据一致性方面的问题。MHA还提供了mater节点的在线切换功能,即按需切换master-slave节点。MHA能够在30秒内实现故障切换,并能在故障切换过程中,最大程度的保证数据一致性。在淘宝内部,也有一个相似的TMHA产品。

MHA是需要单独部署的,分为Manager节点和Node节点,两种节点。其中Manager节点一般是单独部署的一台机器。而Node节点一般是部署在每台MySQL机器上的。 Node节点得通过解析各个MySQL的日志来进行一些操作。

Manager节点会通过探测集群里的Node节点去判断各个Node所在机器上的MySQL运行是否正常,如果发现某个Master故障了,就直接把他的一个Slave提升为Master,然后让其他Slave都挂到新的Master上去,完全透明。

MHA

优点:

  • MHA除了支持日志点的复制还支持GTID的方式
  • 同MMM相比,MHA会尝试从旧的Master中恢复旧的二进制日志,只是未必每次都能成功。如果希望更少的数据丢失场景,建议使用MHA架构。

缺点:

MHA需要自行开发VIP转移脚本。

MHA只监控Master的状态,未监控Slave的状态

3)MGR(用的最多,推荐)

MGR:MySQL Group Replication。 是MySQL官方在5.7.17版本正式推出的一种组复制机制。主要是解决传统异步复制和半同步复制的数据一致性问题。

由若干个节点共同组成一个复制组,一个事务提交后,必须经过超过半数节点的决议并通过后,才可以提交。引入组复制,主要是为了解决传统异步复制和半同步复制可能产生数据不一致的问题。MGR依靠分布式一致性协议(Paxos协议的一个变体),实现了分布式下数据的最终一致性,提供了真正的数据高可用方案(方案落地后是否可靠还有待商榷)。

支持多主模式,但官方推荐单主模式:

  • 多主模式下,客户端可以随机向MySQL节点写入数据
  • 单主模式下,MGR集群会选出primary节点负责写请求,primary节点与其它节点都可以进行读请求处理.

MGR

优点

  • 高一致性,基于原生复制及paxos协议的组复制技术,并以插件的方式提供,提供一致数据安全保证;
  • 高容错性,只要不是大多数节点坏掉就可以继续工作,有自动检测机制,当不同节点产生资源争用冲突时,不会出现错误,按照先到者优先原则进行处理,并且内置了自动化脑裂防护机制;
  • 高扩展性,节点的新增和移除都是自动的,新节点加入后,会自动从其他节点上同步状态,直到新节点和其他节点保持一致,如果某节点被移除了,其他节点自动更新组信息,自动维护新的组信息;
  • 高灵活性,有单主模式和多主模式,单主模式下,会自动选主,所有更新操作都在主上进行;多主模式下,所有server都可以同时处理更新操作。

缺点

  • 仅支持InnoDB引擎,并且每张表一定要有一个主键,用于做write set的冲突检测;
  • 必须打开GTID特性,二进制日志格式必须设置为ROW,用于选主与write set;主从状态信息存于表中(–master-info-repository=TABLE 、–relay-log-info-repository=TABLE),–log-slave-updates打开;
  • COMMIT可能会导致失败,类似于快照事务隔离级别的失败场景
  • 目前一个MGR集群最多支持9个节点
  • 不支持外键于save point特性,无法做全局间的约束检测与部分事务回滚

适用的业务场景

  • 对主从延迟比较敏感
  • 希望对对写服务提供高可用,又不想安装第三方软件
  • 数据强一致的场景

分库分表方案介绍

1)为什么要分库分表

  • 分库分表其目的是为了解决单数据库或单表数据量巨大所导致的 性能下降问题。
    • 如:单表数据量如果达到1000W以上,必须进行分表否则数据库性能很低
  • 将一个数据库拆分成多个数据库、将一张数据量大的表拆分成多张小表,使得单一数据库、单一数据表数据量变小,达到提升性能的目的

在微服务架构中,我们都会为每个单独的微服务分配一个数据库,这就是分库,而对一些日志表、历史记录表、订单表等数据量特别大的表可以拆分成多张小表,这就是分表。

2)分库分表的方式

分库和分表,都可以统称为 数据分片,从拆分的角度上来看,可以分为 垂直分片、水平分片,可根据实际的应用场景采用拆分角度。

1.垂直分片(纵向分片)

垂直分片:按业务来对数据表进行拆分,核心理念就是专库专用。

拆分之前一个数据库会有多张数据表,每个表对应不同的业务。拆分之后,按照业务边界进行划分,分布到不同的数据库中。

拆分前

​ shop 数据库有5张数据表,有用户表,角色表,订单表等。

数据库

拆分后

​ 根据业务边界进行拆分,订单表 迁移到 订单数据库,用户表迁移到 用户数据库中。

image-1image-1

注意:垂直拆分可以划分业务边界, 缓解高并发下访问的压力,但是无法真正解决 单表数据量巨大的问题,得通过 水平拆分来解决。

2.水平分片(横向分片)

水平分片:它不再根据业务进行划分,而是根据某种规则将数据分散至多个库或表中,每个分片仅包含数据的一部分。

水平分片

常用的分片策略有:

​ 取余\取模 : 优点 均匀存放数据,缺点 扩容非常麻烦

​ 按照范围分片 : 比较好扩容, 数据分布不够均匀

​ 按照时间分片 : 比较容易将热点数据区分出来。

​ 按照枚举值分片 : 例如按地区分片

​ 按照目标字段前缀指定进行分区:自定义业务规则分片

水平分片从理论上突破了单机数据量处理的瓶颈,并且扩展相对自由,是分库分表的标准解决方案。

一般来说,在系统设计阶段就应该根据业务耦合松紧来确定垂直分库,垂直分表方案,在数据量及访问压力不是特别大的情况,首先考虑缓存、读写分离、索引技术等方案。若数据量极大,且持续增长,再考虑水平分库水平分表方案。

问题一:取模分配策略为什么扩容非常麻烦

如果只根据取模策略进行分片,如果要进行扩容的话,会存在数据迁移的问题。

数据迁移问题:

数据库迁移问题

如上图,我们一共有2个节点,3%2=1 ,就会存入第一个节点。当我们扩容到4个节点后,3%4=3,当我们id = 3的数据就会到第三个节点中去查找,如果没有做数据迁移 肯定是查不到的,因为扩容之前 id=3的数据是存储在 第一个节点上的。

问题二:按时间分片是如何分片的

如,根据月份来分片,1月份的数据都存于1月的数据库,2月份的数据都存于2月的数据库,以此类推。

不需要数据迁移的取模分片扩容方案

结合取模和时间的优点,就可以解决 数据迁移且 平均存放数据的问题。

  1. 先根据范围策略(如 按月份)进行分片,根据范围分片的话,可以方便扩容
  2. 为了确保数据能平均存放在不同的服务器,我们在 范围内 进行取模分片
    1. 我们可以在redis中根据月份维护一个字段 –> 每个月的数据库节点,当我们新增设备时,就调整该数量即可
    2. 先根据时间分片后,为了每个月的数据能平均保存在每个节点上,我们可以根据redis中的 每个月数据库节点数量 进行取模
    3. 为了尽可能的平均分配,新增的节点我们可以设置权重,让新增的节点多 分片存放数据

先按时间范围,如月份 进行水平分片,这样的话扩容就比较容易,但是数据分布就会不均匀,为了解决分布不均匀的问题,可以在此基础之上增加一个方案:redis 维护 每个月的数据库节点。

如:先按月份水平分片,假设1月份的时候有3台节点,就按3台节点进行取模后存储在三个节点上,当业务体量上来后,2月份增加到了5台,那存储数据时就可以按5台节点进行取模,如果3月份体量减少就可以减少到3台节点,那存储时 %3。

不需要数据迁移的取模分片扩容方案

3)分库分表必然面临的问题

如果我们对数据库进行了分库分表,虽然能提升数据库的性能,但是也会面临新的问题。

  • 事务一致性问题

    原本单机数据库有很好的事务机制能够帮我们保证数据一致性。但是分库分表后,由于数据分布在不同库甚至不同服务器,不可避免会带来分布式事务问题。

  • 跨节点关联查询问题

    在没有分库时,我们可以进行很容易的进行跨表的关联查询。但是在分库后,表被分散到了不同的数据库,就无法进行关联查询了。

    这时就需要将关联查询拆分成多次查询,然后将获得的结果进行拼装。

  • 跨节点分页、排序函数

    跨节点多库进行查询时,limit分页、order by排序等问题,就变得比较复杂了。需要先在不同的分片节点中将数据
    进行排序并返回,然后将不同分片返回的结果集进行汇总和再次排序。

    这时非常容易出现内存崩溃的问题。

  • 主键避重问题

    在分库分表环境中,由于表中数据同时存在不同数据库中,主键值平时使用的自增长将无用武之地,某个分区数据
    库生成的ID无法保证全局唯一。因此需要单独设计全局主键,以避免跨库主键重复问题。

  • 公共表处理

    实际的应用场景中,参数表、数据字典表等都是数据量较小,变动少,而且属于高频联合查询的依赖表。这一类表一般就需要在每个数据库中都保存一份,并且所有对公共表的操作都要分发到所有的分库去执行。

  • 运维工作量

    面对散乱的分库分表之后的数据,应用开发工程师和数据库管理员对数据库的操作都变得非常繁重。对于每一次数据读写操作,他们都需要知道要往哪个具体的数据库的分表去操作,这也是其中重要的挑战之一。

4)什么时候需要分库分表?

按阿里巴巴开发手册来说,建议单表数据量达到500W条记录或单表容量达到2GB就应该进行分库分表。

但是实际应当根据场景,酌情考虑是否分库分表,因为分库分表是一件非常麻烦的事情且会产生很多问题。

在设计数据库和数据表时,明确能预知到 数据量巨大的表,我们可以提前进行分库分表,这样就可以避免后期数据量大的时候进行分库分表,对代码的入侵也会比较小。

5)常见的分库分表组件

分库分表会带来很多问题,我们可以通过中间件来解决这些问题,常见的中间件有以下3种:

shardingsphere 官网地址:https://shardingsphere.apache.org/document/current/cn/overview/

Sharding-JDBC是当当网研发的开源分布式数据库中间件,他是一套开源的分布式数据库中间件解决方案组成的生态圈,它由Sharding-JDBC、Sharding-Proxy和Sharding-Sidecar(计划中)这3款相互独立的产品组成。 他们均提供标准化的数据分片、分布式事务和
数据库治理功能,可适用于如Java同构、异构语言、容器、云原生等各种多样化的应用场景。

推荐使用shardingsphere,因为它比mycat更加的强大,完善。

mycat 官网地址: http://www.mycat.org.cn/

基于阿里开源的Cobar产品而研发,MyCAT虽然是从阿里的技术体系中出来的,但是跟阿里其实没什么关系。

DBLE 官网地址:https://opensource.actionsky.com/

DBLE是较为小众的产品,该网站包含几个重要产品。其中分布式中间件可以认为是MyCAT的一个增强版,专注于MySQL的集群化管理。另外还有数据传输组件和分布式事务框架组件可供选择。