分布式数据库架构-程序员宅基地

技术标签: 数据库架构  分布式  docker  

分布式数据库架构

1、MySQL常见架构设计

对于mysql架构,一定会使用到读写分离,在此基础上有五种常见架构设计:一主一从或多从、主主复制、级联复制、主主与级联复制结合。

1.1、主从复制

这种架构设计是使用的最多的。在读写分离的基础上,会存在一台master作为写机,一个或多个slave作为读机。因为在实际的情况下,读的请求量一般是远远大于写请求的。
在这里插入图片描述
采用这种架构之后,当应用写入输入时,会把数据写入到master节点,然后由master节点将写入数据复制到slave节点上。

缺点:

  • master单机故障
  • 对master进行维护时,无法接收写请求
  • master复制延迟,查询数据延迟
  • slave提升为master后,可能会发生数据丢失(数据不一致)
1.1.1、 主从复制搭建

1、首先需要在两台机器上安装mysql镜像以及创建mysql容器

docker pull mysql:5.7

docker run --name mysql3307 -p 3307:3306 --privileged=true -ti -e MYSQL_ROOT_PASSWORD=123456 -e MYSQL_USER=user -e MYSQL_PASSWORD=pass -v /home/mysql/docker-data/3307/conf:/etc/mysql/conf.d -v /home/mysql/docker-data/3307/data/:/var/lib/mysql -v /home/mysql/docker-data/3307/logs/:/var/log/mysql -d mysql:5.7

2、需要在两台机器上的/home/mysql/docker-data/3307/conf目录下,需要创建mysql的配置文件my.cnf

my.cnf配置文件内容如下:

# For advice on how to change settings please see
# http://dev.mysql.com/doc/refman/5.7/en/server-configuration-defaults.html

[mysqld]
#
# Remove leading # and set to the amount of RAM for the most important data
# cache in MySQL. Start at 70% of total RAM for dedicated server, else 10%.
# innodb_buffer_pool_size = 128M
#
# Remove leading # to turn on a very important data integrity option: logging
# changes to the binary log between backups.
# log_bin
#
# Remove leading # to set options mainly useful for reporting servers.
# The server defaults are faster for transactions and fast SELECTs.
# Adjust sizes as needed, experiment to find the optimal values.
# join_buffer_size = 128M
# sort_buffer_size = 2M
# read_rnd_buffer_size = 2M
#datadir=/home/mysql/docker-data/3307/data
#socket=/home/mysql/docker-data/3307/mysql.sock

character_set_server=utf8
init_connect='SET NAMES utf8'

# Disabling symbolic-links is recommended to prevent assorted security risks
symbolic-links=0

#log-error=/home/mysql/docker-data/3307/logs/mysqld.log
#pid-file=/home/mysql/docker-data/3307/mysqld.pid
lower_case_table_names=1
#指定主机号,不允许出现重复
server-id=423307
#开启binlog
log-bin=mysql-bin
auto_increment_increment=2
auto_increment_offset=1

#rpl_semi_sync_master_enabled=1
#rpl_semi_sync_master_timeout=10000

3、在master的docker容器中添加mysql权限,开启备份机复制,并且设置备份用户信息。

#添加权限
GRANT REPLICATION SLAVE,FILE,REPLICATION CLIENT ON *.* TO 'repluser'@'%' IDENTIFIED BY '123456';

#刷新权限
FLUSH PRIVILEGES;

在这里插入图片描述
4、设置并刷新权限后,重启mysql服务器,可以查看master上的binlog信息。

show master status;

在这里插入图片描述
注意:至此上述步骤两台机器上都需要执行,参数上的设置按实际情况来定。

5、接着在slave中进入到mysql容器,设置master信息,用于标注当前slave的master是谁。

change master to master_host='localhost',master_port=3307,master_user='repluser',master_password='123456',master_log_file='mysql-bin.000002',master_log_pos=154;

参数解析

change master to master_host='master的ip',
master_port=master的端口号,
master_user='repluser',master_password='123456',
master_log_file='master中的binlob文件',
master_log_pos=master中的position位置信息;

在这里插入图片描述
6、设置完成后,还要开启slave中的IOSQL线程,这两个线程主要用于slave中进行数据备份,可以先查看slave中这两个线程的状态。

show slave status\G

在这里插入图片描述
开启slave中的IOSQL线程

start slave;

在这里插入图片描述
至此mysql主从复制搭建完成。

7、 相关状态信息的查看

查看slave中的binlog是否已经开启

show global variables like "%log%";

在这里插入图片描述
接着还可以查看master、slave中的进程信息

在master mysql中输入:

show processlist;

在这里插入图片描述
从上图中可以看出:master已经把所有的binlog发送给slave,并且等待更多的更新操作。

在slave mysql中输入:

show processlist;

在这里插入图片描述
从上图可以看出:在slave它已经连接到了master,正在等待master发送事件,并且slave已经读取了所有的relay log信息,并且正在等待更多的更新操作。

8、测试验证

连接主库,并在主库中创建数据库,创建数据库表以及添加行记录。此时会发现从库中也会创建相应的数据库和数据库表和行记录。

1.1.2、 MySQL复制原理

在mysql中,其有两种复制机制,分别是:异步复制半同步复制。默认采用异步复制。(上述主从复制操作为异步复制)

异步复制执行流程

在这里插入图片描述

  • 1、应用事务提交到master
  • 2、master接收到应用事务提交请求后,会更新内部的binlog日志,接着让mysql引擎执行事务操作,并返回给客户端执行结果信息。同时在master中会存在一个事件监听,其会一直监听master中binlog日志文件的改变,一旦发现日志文件发生改变,则会触发dump线程。
  • 3、dump线程被触发后,会通知slave中的IO线程现在有事务操作需要进行同步。
  • 4、slave中IO线程接收到通知后,会从slave中relay-log.info文件中获取slave中的binlog日志文件和pos位置信息。接着会把这部分信息发送给master的dump线程。
  • 5、master的dump线程接收到这些信息后,会根据slave发送的binlog日志文件和pos位置,将最新的binlog日志和pos位置后面的内容同步给slave的IO线程。
  • 6、slave的IO线程接收到这些信息后,会将这部分内容同步到slave的relay-bin文件中。
  • 7、当relay-bin文件发生改变后,会触发slave线程执行sql操作。(异步操作)
  • 8、当slave向relay-bin写入完成后,还会向master返回一个ACK消息,通知slave已经执行成功。

总结:对于这一系列操作,可以发现master和slave在进行同步时是以异步的方式完成的,master写入完binlog后,会马上通过引擎进行事务提交并向客户端返回响应,对于与slave同步的操作,则是异步完成的。

虽然这种方式的RT很快,但是很容易出现数据不一致的情况。

半同步复制执行流程

在这里插入图片描述

  • 半同步复制与异步复制的工作流程大体相似,但不同的是,当master中的binlog日志写入完成后,其不会马上通过引擎进行事务提交,而会处于等待,等到slave同步完成向master返回ACK通知后,才会唤醒等待,继续向下执行。
  • 等待的时长,默认为10秒,但该时间可以配置。
  • 半同步复制尽量的避免的主从数据不一致的情况,但是会造成吞吐量的降低。

对于这个问题,mysql也进行了解决,假设使用半同步复制进行备份时,slave节点挂掉了,那么当master等待10秒后,仍然会进行引擎提交,同时会将半同步复制切换为异步复制。等到slave节点重启后,又会自动的从异步复制切换到半同步复制。

主从异步复制日志效果

Mysql在进行复制操作时,默认是基于异步复制完成的。那为了更好的体会异步复制的效果,可以通过mysql日志来查看具体的复制过程效果。

启动主从两台Mysql服务器。

查看master的Mysql日志信息

docker logs -f mysql3307 | grep binlog_dump

在这里插入图片描述
​根据当前查看的日志信息,在master中已经开启了dump线程连接到了id为273307的slave节点,并且该id就是在slave的mysql配置文件中设置的id。

同时pos内容包括当前的binlog日志和pos位置。

查看slave的mysql日志信息
在这里插入图片描述
根据slave中的日志信息,可以看到,当前slave中已经开启了relay-log日志,其对应文件信息就是xxxxx-relay-bin。其内部保存的就是slave中的相关binlog信息和pos位置信息。

同时在slave中也已经开启了SQL Thread,并且根据信息可以,它会从xxxx-relay-bin.000001文件的4位置开始复制。

同时在slave中也开启了IO Thread,其已经连接到master,并且会从master的binlog日志的154的位置开启复制。

查看master当前的binlog日志信息

#确定当前master正在使用的binlog日志文件
cat mysql-bin.index

#查看当前binlog日志文件内容
tail -f mysql-bin.000002

在这里插入图片描述
查看slave当前的日志信息

cat relay-log.info

cat master.info

在这里插入图片描述
在这里插入图片描述

cat xxxxxxxxxx-relay-bin.index

在这里插入图片描述
在这里插入图片描述
监控slave日志信息

tail -f 8122977f8b0a-relay-bin.000002

在这里插入图片描述
master中新增数据,触发主从同步

  • 查看master修改前后的binlog日志
cat mysql-bin.000002 

在这里插入图片描述

  • 查看slave复制前后的relay-bin日志
 tail -f 41dc8a520939-relay-bin.000002

在这里插入图片描述

1.1.3、 主从半同步复制搭建

1、配置
进入mysql容器,加载lib,主从节点都要配置,因为主从节点间会存在切换。

install plugin rpl_semi_sync_master soname 'semisync_master.so';

install plugin rpl_semi_sync_slave soname 'semisync_slave.so';

在这里插入图片描述
查看插件信息

show plugins;

在这里插入图片描述
2、启用半同步(务必先启用从库,再启用主库

#先启用从库,再启用主库

#从库:
set global rpl_semi_sync_slave_enabled= {0|1};  # 1:启用,0:禁止

#主库:
set global rpl_semi_sync_master_enabled= {0|1}; # 1:启用,0:禁止

set global rpl_semi_sync_master_timeout=10000;  # 单位为ms

在这里插入图片描述
在这里插入图片描述
3、重启从库IO Thread

stop slave io_thread;
start slave io_thread;

4、截止到此已经完成半同步开启配置,可以查看主库状态信息和参数信息

#查询状态信息
show global status like "%sync%";

#查询参数信息
show global variables like '%sync%';

show global status like “%sync%”;
在这里插入图片描述
show global variables like ‘%sync%’;
在这里插入图片描述
根据上述的配置,当前主从两台服务器的复制方式已经改为半同步复制。接下来就可以来查看具体的效果。

  • 正常的向master中添加数据,slave可以进行正常数据更新。

master打印日志信息如下: 开启半同步复制,关闭异步复制
在这里插入图片描述

  • 关闭slave的IO Thread或者停止salve服务

再次向master中添加数据。此时可以发现,当进行数据提交时,会出现等待,过了十秒后,会对数据进行保存。同时slave中不会同步的进行数据更新。
在这里插入图片描述
在这里插入图片描述
如上图所示,超过时间后,半同步复制会转化为异步复制。此时复制机制就会由半同步复制转换为异步复制,当再次向master中添加数据,不会再次出现等待。

  • slave中重新开启IO Thread。

异步复制会再次转换为半同步复制,其次,在slave IO Tthread关闭这段时间内的数据,会同步到slave中,不会出现数据丢失。

1.2、主主复制

对于主从复制来说,其内部会存在一台master以及一台或多台slave。但有一个非常明显的问题,master是单点存在。一旦master宕机,则无法进行数据的写入。为了解决这个问题,可以使用主主复制架构。

在主主复制架构中,会存在两台master,没有slave。并且会对这两台master进行读写分离,两台master会进行相互的复制。

主主复制架构图
在这里插入图片描述
在此架构中,两台master会进行双向复制,为什么这么做呢? 因为假设现在负责写的master宕机了,那么写的工作则会交给之前负责读的服务器来完成,相当于它即负责写又负责读。等到原先负责写的master恢复了,其在继续负责写工作。 反之亦然。因此才需要两者间进行双向复制。

此时缺点也非常明显,虽然master不存在单点了,但是对于读来说,如果并发量大的话,它肯定扛不住。对于主主复制架构来说,应用较少。

1.2.1、主主复制搭建

主主复制的搭建和主从非常类似,只不过主主复制会进行互指。

1、参照主从完成搭建。(按照上述主从复制结构搭建)

2、原slave端也要开启权限

#添加权限
GRANT REPLICATION SLAVE,FILE,REPLICATION CLIENT ON *.* TO 'repluser'@'%' IDENTIFIED BY '123456';

#刷新权限
FLUSH PRIVILEGES;

#重启mysql服务并查看binlog信息
show master status

3、在master这一端也要配置slave的相关配置

change master to master_host='localhost',master_port=3308,master_user='repluser',master_password='123456',master_log_file='mysql-bin.000002',master_log_pos=154;

start slave;

4、查看master和slave的进程列表:show processlist。可以发现他们现在互为主备。

master
在这里插入图片描述
slave在这里插入图片描述

5、测试
当在两台服务器中添加数据,都可以完成双向同步。

1.3、级联复制架构

当读压力现在增大并且还想减小主从复制的性能消耗,可以采用级联复制架构。
在这里插入图片描述
写请求的入口仍为一个,但当master向slave进行复制时,对于slave可以分为多层, master只要向其中两台slave复制即可,然后再由slave将其数据复制到后面更多的slave中。

通过这种方式可以减轻master向slave复制的IO压力。

但是这种架构也存在一个弊端:slave的延迟会加大。

1.4、双主与级联复制结合架构

对于master在前面几种架构设计中,都存在单点问题, 对于master单点问题的解决,可以采用当前的架构。通过这种架构不仅可以解决master单点的问题,也可以解决slave延迟的问题。
在这里插入图片描述

2、Mysql高可用实践

以主主架构为例,现在不管写或者读,只要其中一个宕机,则会把它本身工作交给另外一台服务器完成。此时就需要对IP进行一个自动的指向。而且这种服务器IP切换,对于上层应用来说,应该是完全隐藏的,其无需知道当前是由谁来完成具体工作,其只需要来连接一个IP就可以。

对于这种需求,就需要通过keepAlived来完成IP的自动切换。
在这里插入图片描述
对于keepalived会在多台mysql服务器进行安装, 同时keepalived间也分为master和slave, 同时master会虚拟化一个VIP供应用进行连接。 如果一旦master挂掉后,会由slave节点继续工作,同时slave节点也会虚拟出相同VIP,供应用进行连接。

2.1、keepAlived高可用配置

1、安装keepalived

1. 下载keepalied安装包 http://www.keepalived.org/download.html
2. yum -y install openssl-devel gcc gcc-c++
3. mkdir /etc/keepalived
4. 上传安装包并解压  tar -zxvf keepalived-2.0.18.tar.gz
5. mv keepalived-2.0.18 /usr/local/keepalived
6. cd /usr/local/keepalived
7. ./configure && make && make install
8.创建启动文件
cp  -a /usr/local/etc/keepalived   /etc/init.d/
cp  -a /usr/local/etc/sysconfig/keepalived    /etc/sysconfig/
cp  -a /usr/local/sbin/keepalived    /usr/sbin/

2、编写执行shell脚本

进入/etc/keepalived。创建chk.sh,同时赋予执行权限:chmod +x chk.sh

#! /bin/bash
mysql -h 127.0.0.1 -u root -p123456 -P 3312 -e "show status;" >/dev/null 2>&1
if [ $? == 0 ]
then
    echo " $host mysql login successfully "
    exit 0
else
    echo "  mysql login faild"
    killall keepalived
    exit 2
fi

3、编写keepAlived配置文件

cd /etc/keepalived

vi keepalived.conf

! Configuration File for keepalived
#简单的头部,这里主要可以做邮件通知报警等的设置,此处就暂不配置了;
global_defs {
    #notificationd LVS_DEVEL
    router_id MYSQL_4   #唯一标识不允许出现重复
    script_user root
    enable_script_security
}
#预先定义一个脚本,方便后面调用,也可以定义多个,方便选择;
vrrp_script chk_haproxy {
    script "/etc/keepalived/chk.sh"
    interval 2  #脚本循环运行间隔
}
#VRRP虚拟路由冗余协议配置
vrrp_instance VI_1 {   #VI_1 是自定义的名称;
    state BACKUP    #MASTER表示是一台主设备,BACKUP表示为备用设备【我们这里因为设置为开启不抢占,所以都设置为备用】
    nopreempt      #开启不抢占
    interface ens33   #指定VIP需要绑定的物理网卡
    virtual_router_id 11   #VRID虚拟路由标识,也叫做分组名称,该组内的设备需要相同
    priority 130   #定义这台设备的优先级 1-254;开启了不抢占,所以此处优先级必须高于另一台

    advert_int 1   #生存检测时的组播信息发送间隔,组内一致
    authentication {    #设置验证信息,组内一致
        auth_type PASS   #有PASS 和 AH 两种,常用 PASS
        auth_pass 111111    #密码
    }
    virtual_ipaddress {
        192.168.200.200    #指定VIP地址,组内一致,可以设置多个IP
    }
    track_script {    #使用在这个域中使用预先定义的脚本,上面定义的
        chk_haproxy
    }
}

4、启动keepAlived

systemctl start keepalived

5、查看keepAlived执行状态

ps -ef|grep keepalived

在这里插入图片描述
6、可以通过tail -f /var/log/messages

7、查看ip信息,此时可以发现出现了配置的虚拟ip

ip a

8、测试
通过navicat使用虚拟IP连接mysql,当前连接IP为VIP。可以连接成功。

3、数据切分核心思想

3.1、为什么要进行数据切分?

当前微服务架构非常流行,很多都会采用微服务架构对其系统进行拆分。 而虽然产生了多个微服务,但因为其用户量和数据量的问题,很有可能仍然使用的是同一个数据库。
在这里插入图片描述
但是随着用户量和数据量增加,就会出现很多影响数据库性能的因素,如:数据存储量、IO瓶颈、访问量瓶颈等。此时就需要将数据进行拆分,从一个库拆分成多个库。

3.2、数据拆分方式

垂直拆分

垂直拆分是按照业务将表进行分类并分布到不同的数据节点上。在初始进行数据拆分时,使用垂直拆分是非常直观的一种方式。
在这里插入图片描述
垂直拆分的优点:

  • 拆分规则明确,按照不同的功能模块或服务分配不同的数据库。
  • 数据维护与定位简单。

垂直拆分的缺点:

  • 对于读写极其频繁且数据量超大的表,仍然存在存储与性能瓶颈。简单的索引此时已经无法解决问题。
  • 会出现跨库join。
  • 需要对代码进行重构,修改原有的事务操作。
  • 某个表数据量达到一定程度后扩展起来较为困难。
水平拆分

​为了解决垂直拆分出现的问题,可以使用水平拆分继续横向扩展,首先,可以如果当前数据库的容量没有问题的话,可以对读写极其频繁且数据量超大的表进行分表操作。由一张表拆分出多张表。

在一个库中,拆分出多张表,每张表存储不同的数据,这样对于其操作效率会有明显的提升。而且因为处于同一个库中,也不会出现分布式事务的问题。
在这里插入图片描述

而拆分出多张表后,如果当前数据库的容量已经不够了,但是还要继续拆分的话,就可以进行分库操作,产生多个数据库,然后在扩展出的数据库中继续扩展表。
在这里插入图片描述

水平拆分的优点:

  • 尽量的避免了跨库join操作。
  • 不会存在超大型表的性能瓶颈问题。
  • 事务处理相对简单。
  • 只要拆分规则定义好,很难出现扩展性的限制。

水平拆分的缺点:

  • 拆分规则不好明确,规则一定会和业务挂钩,如根据id、根据时间等。
  • 不好明确数据位置,难以进行维护。
  • 多数据源管理难度加大,代码复杂度增加。
  • 也会存在分布式事务问题
  • 数据库维护成本增加
数据切分带来的问题
  • 按照用户ID求模,将数据分散到不同的数据库,具有相同数据用户的数据都被分散到一个库中。
  • 按照日期,将不同月甚至日的数据分散到不同的库中。
  • 按照某个特定的字段求模,或者根据特定范围段分散到不同的库中。

数据切分带来的核心问题

  • 产生引入分布式事务的问题。
  • 跨节点 Join 的问题。
  • 跨节点合并排序分页问题。

3.3、Mycat中间件使用

当对数据拆分后会产生诸多的问题,对于这些问题的解决,可以借助于数据库中间件来进行解决,现在时下比较流行的是使用Mycat。

Mycat是一款数据库中间件,对于应用程序来说是完全透明化的,不管底层的数据如何拆分,应用只需要连接Mycat即可完成对数据的操作。同时它还支持MySQL、SQL Server、Oracle、DB2、PostgreSQL等主流数据库。但是Mycat不会进行数据存储,它只是用于数据的路由。

其底层是基于拦截思想实现,其会拦截用户发送过来的SQL语句,首先对SQL语句做了一些特定的分析:如分片分析、路由分析、读写分离分析、缓存分析等,然后将此SQL发往后端的真实数据库,并将返回的结果做适当的处理,最终再返回给用户。
在这里插入图片描述

Mycat特性
  • 支持SQL92标准
  • 遵守Mysql原生协议,跨语言,跨平台,跨数据库的通用中间件代理。
  • 基于心跳的自动故障切换,支持读写分离,支持MySQL主从,以及galera cluster集群。
  • 支持Galera for MySQL集群,Percona Cluster或者MariaDB cluster
  • 基于Nio实现,有效管理线程,高并发问题。
  • 支持数据的多片自动路由与聚合,支持sum,count,max等常用的聚合函数。
  • 支持单库内部任意join,支持跨库2表join。
  • 支持通过全局表,ER关系的分片策略,实现了高效的多表join查询。
  • 支持多租户方案。
  • 支持分布式事务(弱xa)。
  • 支持全局序列号,解决分布式下的主键生成问题。
  • 分片规则丰富,插件化开发,易于扩展。
  • 强大的web,命令行监控。
  • 支持前端作为mysq通用代理,后端JDBC方式支持Oracle、DB2、SQL Server 、 mongodb 。
  • 支持密码加密
  • 支持服务降级
  • 支持IP白名单
  • 支持SQL黑名单、sql注入攻击拦截
  • 支持分表(1.6)
  • 集群基于ZooKeeper管理,在线升级,扩容,智能优化,大数据处理(2.0开发版)。
Mycat源码的本地部署运行

**源码下载:**https://codeload.github.com/MyCATApache/Mycat-Server/zip/Mycat-server-1675-release

默认端口:8066

配置启动参数:

-DMYCAT_HOME=D:\workspace\Mycat-Server-Mycat-server-1675-release\src\main
#设置堆外内存大小
-XX:MaxDirectMemorySize=512M 

注意:为什么要设置堆外内存:当使用mycat对非分片查询时,会把所有的数据查询出来,然后把这部分数据放在堆外内存中

在Mycat有核心三个配置文件,分别为:sever.xml、schema.xml、rule.xml

  • server.xml:是Mycat服务器参数调整和用户授权的配置文件。
  • schema.xml:是逻辑库定义和表以及分片定义的配置文件
  • rule.xml:是分片规则的配置文件,分片规则的具体一些参数信息单独存放为文件,也在这个目录下,配置文件修改需要重启MyCAT。
MyCat核心概念

在学习Mycat首先需要先对其内部一些核心概念有足够的了解。

  • 逻辑库:Mycat中的虚拟数据库。对应实际数据库的概念。在没有使用mycat时,应用需要确定当前连接的数据库等信息,那么当使用mycat后,也需要先虚拟一个数据库,用于应用的连接。
  • 逻辑表:mycat中的虚拟数据表。对应时间数据库中数据表的概念。
  • 非分片表:没有进行数据切分的表。
  • 分片表:已经被数据拆分的表,每个分片表中都有原有数据表的一部分数据。多张分片表可以构成一个完整数据表。
  • ER表:子表的记录与所关联的父表记录存放在同一个数据分片上,即子表依赖于父表,通过表分组(Table Group)保证数据Join不会跨库操作。表分组(Table Group)是解决跨分片数据join的一种很好的思路,也是数据切分规划的重要一条规则
  • 全局表:可以理解为是一张数据冗余表,如状态表,每一个数据分片节点又保存了一份状态表数据。数据冗余是解决跨分片数据join的一种很好的思路,也是数据切分规划的另外一条重要规则。
  • 分片节点(dataNode):数据切分后,每一个数据分片表所在的数据库就是分片节点。
  • 节点主机(dataHost):数据切分后,每个分片节点(dataNode)不一定都会独占一台机器,同一机器上面可以有多个分片数据库,这样一个或多个分片节点(dataNode)所在的机器就是节点主机(dataHost),为了规避单节点主机并发数限制,尽量将读写压力高的分片节点(dataNode)均衡的放在不同的节点主机(dataHost)。
  • 分片规则(rule):按照某种业务规则把数据分到某个分片的规则就是分片规则。
  • 全局序列号(sequence):也可以理解为分布式id。数据切分后,原有的关系数据库中的主键约束在分布式条件下将无法使用,因此需要引入外部机制保证数据唯一性标识,这种保证全局性的数据唯一标识的机制就是全局序列号(sequence),如UUID、雪花算法等。

4、Mycat企业级应用实践

4.1、环境参数配置

在server.xml 文件中的system标签下配置所有的参数,全部为环境参数,可以根据当前需要进行开启和配置,如:设置mycat连接端口号

<property name="serverPort">8066</property>

在这里插入图片描述

4.2、数据非分片

4.2.1、配置初始化信息

应用连接mycat的话,也需要设置用户名、密码、被连接数据库信息,要配置这些信息的话,可以修改server.xml,在其内部添加内容如下:

<!--配置自定义用户信息-->
<!--连接用户名-->
<user name="mycat">
    <!--连接密码-->
    <property name="password">mycat</property>
    <!--创建虚拟数据库-->
    <property name="schemas">userdb</property>
    <!--指定该库是否只读-->
    <!--<property name="readOnly">true</property>-->
</user>
4.2.2、配置虚拟数据库&表
<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">

	<!--配置虚拟数据库-->
	<!--name:虚拟逻辑数据库名称,对应server.xml中的schemas属性值-->
	<!--dataNode:逻辑库中逻辑表的默认数据节点-->
	<!--sqlMaxLimit:类似于SQL上添加limit,如schema为非分片库,则该属性无效-->
	<schema name="userdb" checkSQLschema="true" dataNode="localdn" sqlMaxLimit="500">
		<!--配置虚拟逻辑表-->
		<!--name:逻辑表名称,必须唯一-->
		<!--dataNode:逻辑表所处的数据节点,值必须与dataNode标签中的name属性对应。如果值过多可以用$连接,如:dn$1-99,dn$200-400-->
		<!--primaryKey:逻辑表对应的真实表的主键id的字段名-->
		<table name="tb_user" dataNode="localdn" primaryKey="user_id"/>
	</schema>

	<!--配置dataNode信息-->
	<!--name:当前datanode名称-->
	<!--dataHost:分片节点所处的节点主机,该值必须与dataHost标签中的name属性对应-->
	<!--database:当前数据节点所对应的实际物理数据库-->
	<dataNode name="localdn" dataHost="localdh" database="user"/>

	<!--配置节点主机-->
	<!--balance:用于进行读操作指向,有三个值可选
		0:所有读操作都发送到当前可用的writeHost上
		1:所有读操作都随机的发送到readHost上
		2:所有读操作都随机发送在writeHost与readHost上
	-->
	<!--maxCon:指定每个读写实例连接池的最大连接。也就是说,标签内嵌套的writeHost、readHost标签都会使用这个属性的值来实例化出连接池的最大连接数-->
	<!--minCon:指定每个读写实例连接池的最小连接,初始化连接池的大小-->
	<!--name:当前节点主机名称,不允许出现重复-->
	<!--dbType:当时使用的数据库类型-->
	<!--dbDriver:当前使用的数据库驱动-->
	<!--writeType:用于写操作指向,有三个值可选
		0:所有写操作都发送到可用的writeHost上
		1:所有写操作都随机发送到readHost上
		2:所有写操作都随机发送在writeHost与readHost上
	-->
	<!--readHost是从属于writeHost的,即意味着它从那个writeHost获取同步数据。
		因此,当它所属的writeHost宕机了,则它也不会再参与到读写分离中来,即“不工作了”。这是因为此时,它的数据已经“不可靠”了。
		基于这个考虑,目前mycat 1.3和1.4版本中,若想支持MySQL一主一从的标准配置,并且在主节点宕机的情况下,从节点还能读取数据。
		则需要在Mycat里配置为两个writeHost并设置banlance=1。”-->
	<!--switchType:设置节点切换操作,有三个值可选
		-1:不自动切换
		1:自动切换,默认值
		2:基于mysql主从同步的状态决定是否切换
	-->
	<!--slaveThreshold:主从同步状态决定是否切换,延迟超过该值就不切换-->
	<dataHost balance="0" maxCon="100" minCon="10" name="localdh" dbType="mysql" dbDriver="jdbc" writeType="0" switchType="1" slaveThreshold="1000">
		<!--查询心跳-->
		<heartbeat>select user()</heartbeat>
		<!--配置写节点实际物理数据库信息-->
		<writeHost url="jdbc:mysql://localhost:3306" host="host1" password="root" user="root"></writeHost>
	</dataHost>
</mycat:schema>
4.2.3 测试

通过navicat创建本地数据库连接并创建对应数据库,同时创建mycat连接。 在mycat连接中操作表,添加数据,可以发现,本地数据库中同步的也新增了对应的数据。

4.3、根据ID取模数据分片

当一个数据表中的数据量非常大时,就需要考虑对表内数据进行分片,拆分的规则有很多种,比较简单的一种就是,通过对id进行取模,完成数据分片。

1)修改schema.xml

table标签新增属性:subTables、rule

<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">

	<!--配置虚拟数据库-->
	<!--name:虚拟逻辑数据库名称,对应server.xml中的schemas属性值-->
	<!--dataNode:逻辑库中逻辑表的默认数据节点-->
	<!--sqlMaxLimit:类似于SQL上添加limit,如schema为非分片库,则该属性无效-->
	<schema name="userdb" checkSQLschema="true" dataNode="localdn" sqlMaxLimit="500">
		<!--配置虚拟逻辑表-->
		<!--name:逻辑表名称,必须唯一-->
		<!--dataNode:逻辑表所处的数据节点,值必须与dataNode标签中的name属性对应。如果值过多可以用$连接,如:dn$1-99,dn$200-400-->
		<!--primaryKey:逻辑表对应的真实表的主键id的字段名-->
		<!--subTables:分表的名称。可以存在多个,tb_user1,tb_user2,tb_user3.如果分表较多,可以通过$连接:tb_user$1-3-->
		<!--rule:分片规则,对应rule.xml中配置-->
		<table name="tb_user" dataNode="localdn" primaryKey="user_id" subTables="tb_user$1-3" rule="mod-long"/>
	</schema>

	<!--配置dataNode信息-->
	<!--name:当前datanode名称-->
	<!--dataHost:分片节点所处的节点主机,该值必须与dataHost标签中的name属性对应-->
	<!--database:当前数据节点所对应的实际物理数据库-->
	<dataNode name="localdn" dataHost="localdh" database="user"/>

	<!--配置节点主机-->
	<!--balance:用于进行读操作指向,有三个值可选
		0:所有读操作都发送到当前可用的writeHost上
		1:所有读操作都随机的发送到readHost上
		2:所有读操作都随机发送在writeHost与readHost上
	-->
	<!--maxCon:指定每个读写实例连接池的最大连接。也就是说,标签内嵌套的writeHost、readHost标签都会使用这个属性的值来实例化出连接池的最大连接数-->
	<!--minCon:指定每个读写实例连接池的最小连接,初始化连接池的大小-->
	<!--name:当前节点主机名称,不允许出现重复-->
	<!--dbType:当时使用的数据库类型-->
	<!--dbDriver:当前使用的数据库驱动-->
	<!--writeType:用于写操作指向,有三个值可选
		0:所有写操作都发送到可用的writeHost上
		1:所有写操作都随机发送到readHost上
		2:所有写操作都随机发送在writeHost与readHost上
	-->
	<!--readHost是从属于writeHost的,即意味着它从那个writeHost获取同步数据。
		因此,当它所属的writeHost宕机了,则它也不会再参与到读写分离中来,即“不工作了”。这是因为此时,它的数据已经“不可靠”了。
		基于这个考虑,目前mycat 1.3和1.4版本中,若想支持MySQL一主一从的标准配置,并且在主节点宕机的情况下,从节点还能读取数据。
		则需要在Mycat里配置为两个writeHost并设置banlance=1。”-->
	<!--switchType:设置节点切换操作,有三个值可选
		-1:不自动切换
		1:自动切换,默认值
		2:基于mysql主从同步的状态决定是否切换
	-->
	<!--slaveThreshold:主从同步状态决定是否切换,延迟超过该值就不切换-->
	<dataHost balance="0" maxCon="100" minCon="10" name="localdh" dbType="mysql" dbDriver="jdbc" writeType="0" switchType="1" slaveThreshold="1000">
		<!--查询心跳-->
		<heartbeat>select user()</heartbeat>
		<!--配置写节点实际物理数据库信息-->
		<writeHost url="jdbc:mysql://localhost:3306" host="host1" password="root" user="root"></writeHost>
	</dataHost>
</mycat:schema>

2)修改rule.xml

在schema.xml中已经指定规则为mod-long。因此需要到该文件中修改对应信息。

<tableRule name="mod-long">
    <rule>
        <!--当用用于id取模的字段-->
        <columns>user_id</columns>
        <algorithm>mod-long</algorithm>
    </rule>
</tableRule>

<!--修改当前的分片数量-->
<function name="mod-long" class="io.mycat.route.function.PartitionByMod">
		<!-- how many data nodes -->
		<!-- 根据datanode数量进行取模分片,也就是要模几。 -->
		<property name="count">3</property>
	</function>

3)测试

  • 向数据库中插入一千条数据,可以发现,其会根据id取模,放入不同的三张表中。

  • 当根据id查询时,会通过对id的取模,确定当前要查询的分片。并且首先会先查询mycat中的ehcache缓存,再来查询数据分片。

  • 当查询所有数据时,会查询所有数据分片。

4)缺陷

通过id取模分片这种方式实际中应用较少。主要因为两点问题:

根据id取模,1)散列不均匀,出现数据倾斜。2)动态扩容时,存在rehash,出现数据丢失。

1)数据散列不均匀,容易出现数据倾斜。每张表中的数据量差距较大。

2)动态扩容后,当需要新增表时,需要对模数修改,有可能就会造成当查询某个分片时,在该分片中找不到对应数据。

3)动态扩容后,要进行rehash操作。

4.4、全局序列号

当进行数据切分后,数据会存放在多张表中,如果仍然通过数据库自增id的方式,就会出现ID重复的问题,造成数据错乱。所以当拆分完数据后,需要让每一条数据都有自己的ID,并且在多表中不能出现重复。比较常见的会使用雪花算法来生成分布式id。

在Mycat中也提供了四种方式来进行分布式id生成:基于文件、基于数据库、基于时间戳和基于ZK。

4.4.1、基于本地文件方式生成

优点:本地加载,读取速度较快。

缺点:当MyCAT重新发布后,配置文件中的sequence会恢复到初始值。

​生成的id没有含义,如时间。

​MyCat如果存在多个,会出现id重复问题。

1)修改sequence_conf.properties

USER.HISIDS=  #使用过的历史分段,可不配置
USER.MINID=1  #最小ID值
USER.MAXID=200000  #最大ID值
USER.CURID=1000  #当前ID值

2)修改server.xml

<!--设置全局序号生成方式
   0:文件
   1:数据库
   2:时间戳
   3:zookeeper
  -->
<property name="sequnceHandlerType">0</property>
<!--进入序列匹配流程, 必须带有MYCATSEQ_或者 mycatseq_-->
<property name="sequnceHandlerPattern">(?:(\s*next\s+value\s+for\s*MYCATSEQ_(\w+))(,|\)|\s)*)+</property>
<property name="sequenceHanlderClass">io.mycat.route.sequence.handler.HttpIncrSequenceHandler</property>

3)测试

重启mycat,并查询是否修改成功

show @@sysparam

在这里插入图片描述

通过navicat插入数据

insert into tb_user(user_id,user_name) values('next value for MYCATSEQ_USER','wangwu')

通过程序插入数据

@Insert("insert into tb_user(user_id,user_name) values('next value for MYCATSEQ_USER',#{userName})")
void addUser(User user);
4.4.2、基于数据库生成

优点:能够进行id批量生成,在分布式下,可以避免id重复问题。

缺点:ID没有意义,对数据库有压力。

1)在实际数据库执行dbseq.sql中的sql语句,执行完毕后,会创建一张表。
在这里插入图片描述

2)修改sequence_db_conf.properties

TB_USER=localdn

3)修改server.xml文件,修改全局序列号生成方式为数据库方式

<property name="sequnceHandlerType">1</property>

4)修改schema.xml。在table中添加自增属性

<table name="tb_user" dataNode="localdn" primaryKey="id" subTables="tb_user$1-3" rule="mod-long" autoIncrement="true"/>

5)测试

通过navicat新增记录

insert into tb_user(user_id,user_name) values('next value for MYCATSEQ_TB_USER','wangwu')

在这里插入图片描述

4.4.3、基于zookeeper生成

1)修改server.xml,更改生成模式

<property name="sequenceHandlerType">3</property>

2)修改myid.properties,配置zk连接信息

loadZk=true
zkURL=192.168.200.131:2181
clusterId=01
myid=mycat_fz_01
clusterNodes=mycat_fz_01
#server  booster  ;   booster install on db same server,will reset all minCon to 1
#type=server
#boosterDataHosts=localhost1

3)修改sequence_distributed_conf.properties

INSTANCEID=ZK #声明使用zk生成
CLUSTERID=01

4)测试

启动mycatServer后,通过zk客户端查看节点信息。会发现新增了一个mycat节点

./zkCli.sh

ls /

在这里插入图片描述
插入数据

insert into tb_user(user_id,user_name) values('n
ext value for MYCATSEQ_TB_USER12','heima')

next value for MYCATSEQ_ 后的内容可以随意指定。
在这里插入图片描述

5)特性:

ID 结构:long 64 位,ID 最大可占 63 位

* |current time millis(微秒时间戳 38 位,可以使用 17 年)|clusterId(机房或者 ZKid,通过配置文件配置 5位)|instanceId(实例 ID,可以通过 ZK 或者配置文件获取,5 位)|threadId(线程 ID,9 位)|increment(自增,6 位)

* 一共 63 位,可以承受单机房单机器单线程 1000*(2^6)=640000 的并发。

* 无悲观锁,无强竞争,吞吐量更高

7.4.4)基于时间戳生成

优点:不存在上面两种方案因为mycat的重启导致id重复的现象,ID= 64 位二进制 (42(毫秒)+5(机器 ID)+5(业务编码)+12(重复累加),每毫秒可以并发 12 位二进制的累加。

缺点:数据类型太长,建议采用bigint(最大取值18446744073709551615)

1)修改server.xml。更改生成方式

<property name="sequenceHandlerType">2</property>

2)修改sequence_time_conf.properties

#sequence depend on TIME
#WORKID与DATAACENTERID: 0-31 任意整数。多mycat节点下,每个节点的WORKID、DATAACENTERID不能重复,组成唯一标识,总共支持32*32=1024 种组合
WORKID=01
DATAACENTERID=01

3)测试

新增数据

insert into tb_user(user_id,user_name) values('next value for MYCATSEQ_TB_USER12','heima')

next value for MYCATSEQ_ 后的内容可以随意指定。
在这里插入图片描述

5、MyCat分库&读写分离

之前已经基于id取模完成了分表操作,但是一个数据库的容量毕竟是有限制的,如果数据量非常大,分表已经满足不了的话,就会进行分库操作。

​当前分库架构如下:
在这里插入图片描述

现在存在两个主库,并且各自都有从节点。 当插入数据时,根据id取模放入不同的库中。同时主从间在进行写时复制的同时,还要完成主从读写分离的配置。

1)修改schema.xml。配置多datenode与datahost。同时配置主从读写分离。

<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">

    <schema name="userdb" checkSQLschema="true" dataNode="dn09" sqlMaxLimit="500">
        <table name="tb_user" dataNode="dn09,dn10" primaryKey="user_id" rule="mod-long"/>
    </schema>

    
    <dataNode name="dn09" dataHost="dh09" database="user"/>
    <dataNode name="dn10" dataHost="dh10" database="user"/>

    <dataHost name="dh09" balance="1" maxCon="100" minCon="10"  dbType="mysql" dbDriver="jdbc" writeType="0" switchType="1" slaveThreshold="1000">
        <!--查询心跳-->
        <heartbeat>select user()</heartbeat>
        <!--配置写节点实际物理数据库信息-->
        <writeHost url="jdbc:mysql://192.168.200.142:3309" host="host1"  user="root" password="123456">
            <!--配置读节点实际物理数据库信息-->
            <readHost host="host2" url="jdbc:mysql://192.168.200.145:3309" user="root" password="123456" ></readHost>
        </writeHost>
    </dataHost>

    <dataHost name="dh10" balance="1" maxCon="100" minCon="10"  dbType="mysql" dbDriver="jdbc" writeType="0" switchType="1" slaveThreshold="1000">
        <!--查询心跳-->
        <heartbeat>select user()</heartbeat>
        <!--配置写节点实际物理数据库信息-->
        <writeHost url="jdbc:mysql://192.168.200.142:3310" host="host1"  user="root" password="123456">
            <!--配置读节点实际物理数据库信息-->
            <readHost host="host2" url="jdbc:mysql://192.168.200.145:3310" user="root" password="123456" ></readHost>
        </writeHost>
    </dataHost>
</mycat:schema>

2)修改rule.xml。配置取模时的模数

<function name="mod-long" class="io.mycat.route.function.PartitionByMod">
    <!-- how many data nodes -->
    <!-- 根据datanode数量进行取模分片,也就是要模几。 -->
    <property name="count">2</property>
</function>

3)进行批量数据添加,可以发现数据落在了不同的库中。
在这里插入图片描述
在这里插入图片描述

4)读写分离验证

设置log4j2.xml的日志级别为DEBUG

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="DEBUG">
		........
        <asyncRoot level="DEBUG" includeLocation="true">
			........
        </asyncRoot>
    </Loggers>
</Configuration>

基于mysql服务进行数据查看,观察控制台信息,可以看到对于read请求的数据源,分别使用的是配置文件的配置。

在这里插入图片描述
在这里插入图片描述

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/NeverFG/article/details/131360699

智能推荐

Sklearn中predict_proba函数用法及原理详解-程序员宅基地

文章浏览阅读4k次,点赞3次,收藏11次。Sklearn中predict_proba函数用法及数学原理详解(以logistic回归为例)_predict_prob

MVCC解决什么问题?原理是什么?_mvcc解决了什么问题-程序员宅基地

文章浏览阅读1.4k次。MVCC解决什么问题?原理是什么?_mvcc解决了什么问题

(邱维声)高等代数课程笔记:解线性方程组的矩阵消元法_比较消元法和初等变换求线性方程组的异同,并阐述自己的收获-程序员宅基地

文章浏览阅读383次。根据邱维声老师的高等代数课程,整理的笔记。_比较消元法和初等变换求线性方程组的异同,并阐述自己的收获

android商城首页demo,FanZhengxi-程序员宅基地

文章浏览阅读281次。Android HyBridge 开发一、三种App开发方式对比1. Native App特点:UI元素、数据内容、逻辑架构都安装在手机终端,导致不可跨平台,每次版本升级都要重新打包。缺点:无法跨平台、升级麻烦、开发成本高(指跨平台开发成本高)优点:速度快,用户体验好。2. Web App定义:可理解为移动端的网站,将网页部署在服务器上,用户通过各大浏览器来访问。缺点:页面访问速度慢、用户体验差。..._安卓应用商店app demo

好烦!快让ChatGPT停止道歉!SD创作宣传图的超细教程;教你在PH冷启动薅流量;CSDN举办AI应用创新大赛 | ShowMeAI日报_inscode deecamp x csdn ai应用创新大赛-程序员宅基地

文章浏览阅读318次。Stable Diffusion 图生图知识思维导图;使用 5W1H 框架启动一个可控的AI项目;培生集团将生成式AI学习工具引入在线高等教育平台;前 Meta AI 高管离职创业,做教育类 ChatGPT 应用……点击阅读全文_inscode deecamp x csdn ai应用创新大赛

【21天算法挑战赛】查找算法——顺序查找_普通查找是顺序查找么-程序员宅基地

文章浏览阅读814次,点赞9次,收藏7次。作者简介:我目前是一个在校学生,现在不敢说自己擅长什么,但是我想通过自己的学习努力让自己的技术、知识都慢慢提升,希望我们一起学习呀~。有话想说:写博客、记笔记并不是一种自我感动,把学到的东西记在脑子里才是最重要的,在这个过程中,不要浮躁,希望我们都可以越来越优秀!由于算法不会改变原有的元素集合,只需要一个额外的变量控制索引变化,所以空间复杂度为常数级:O(1)️兴趣领域:目前偏向于前端学习 算法学习。语言说明:代码实现我会用Python/C++~空间复杂度:O(1)

随便推点

Debruijn问题 超星吉林大学高级程序设计作业-程序员宅基地

文章浏览阅读353次,点赞2次,收藏5次。Debruijn问题 超星作业题目描述:输入输出要求:解题:备注:题目描述:输入输出要求:输入:n(n<=4)输出:按照字典序输出符合的答案(当出现多组本质不同的解时,仅输出字典序中最小的那个序列);每行数字间以一个西文空格间隔,行末有一个换行符。样例1:输入:3输出:0 0 0 1 0 1 1 1解题://准备#include<stdio.h>#include<math.h>int m[16]={0}; //数组 int fl_debruijn

JAVA常用实用类-程序员宅基地

文章浏览阅读1.1k次,点赞33次,收藏21次。javaAPI(java Application Programming Interface)即java应用程序编程接口,它是运行库的集合,预先定义了一些接口和类,程序员可以直接使用这些已经被打包的接口和类开发具体的应用来节约大量的时间和精力。API除了有"应用程序编程接口"的意思,还特指API的说明文档,也被称为API帮助文档。java语言的强大之处在于它提供了多种多样的类库,从而大大提高了程序的编程效率和质量,javaAPI提供常用的包如下。

常见python库学习(一)Tkinter库_GUI界面设计_python窗体类库-程序员宅基地

文章浏览阅读541次。提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档文章目录前言一、tkinter窗口1.简介2.Tkinter 控件二、1.引入库2.读入数据总结前言提示:这里可以添加本文要记录的大概内容:例如:随着人工智能的不断发展,机器学习这门技术也越来越重要,很多人都开启了学习机器学习,本文就介绍了机器学习的基础内容。提示:以下是本篇文章正文内容,下面案例可供参考一、tkinter窗口1.简介Tkinter 是使用 python 进行窗口视窗设计的模块,是 python 自带的、可_python窗体类库

通过mtd读写flash_基于STM32F7通过cube软件配置:读写QSPI接口64M flash和64M PSRAM参考教程...-程序员宅基地

文章浏览阅读825次。基于STM32F7通过cube软件配置:读写QSPI接口64M flash和64M PSRAM参考教程核心板:NUCLEO-F767ZIFlash:NM25Q64EB(64M)PSRAM:IPS6404L(64M)配置文件请使用stm32cubemx打开程序请使用keil5MDK打开NUCLEO-F767ZI上引出的QSPI引脚如下,NM25Q64EB和IPS6404L都是分别接到同样..._stm32f7 单片机 64引脚

获取栅格图层(Raster)的属性表_r raster getvalue-程序员宅基地

文章浏览阅读388次。pNewRaster是你的Raster图层IRasterBandCollection pRasterBC =(IRasterBandCollection ) pNewRaster;IRasterBand pRasterBand = pRasterBC.Item(0);ITable pTable = pRasterBand.AttributeTable;IQueryFilter pQueryFilter=new QueryFilterClass ();pQueryFilter .WhereClau._r raster getvalue

python反编译class文件_python反编译之字节码-程序员宅基地

文章浏览阅读226次。如果你曾经写过或者用过 Python,你可能已经习惯了看到 Python 源代码文件;它们的名称以.Py 结尾。你可能还见过另一种类型的文件是 .pyc 结尾的,它们就是 Python “字节码”文件。(在 Python3 的时候这个 .pyc 后缀的文件不太好找了,它在一个名为__pycache__的子目录下面。).pyc文件可以防止Python每次运行时都重新解析源代码,该文件大大节省了时间。..._python反编译class文件

推荐文章

热门文章

相关标签