• MySQL 数据库平滑扩容的6 种方案剖析


    1. 扩容方案剖析

    1.1 扩容问题

    在项目初期,我们部署了三个数据库 A、B、C,此时数据库的规模可以满足我们的业务需求。为了将数据做到平均分配,我们在 Service 服务层使用 uid%3 进行取模分片,从而将数据平均分配到三个数据库中。

    如图所示:

    后期随着用户量的增加,用户产生的数据信息被源源不断的添加到数据库中,最终达到数据库的最佳存储容量。如果此时继续向数据库中新增数据,会导致数据库的 CRUD 等基本操作变慢,进而影响整个服务的响应速度。

    这时,我们需要增加新的节点,对数据库进行水平扩容,那么加入新的数据库 D 后,数据库的规模由原来的 3 个变为 4 个。

    如图所示:

    此时由于分片规则发生了变化(uid%3 变为 uid%4),导致大部分的数据,无法命中原有的数据,需要重新进行分配,要做大量的数据迁移处理。

    比如之前 uid 如果是 uid=3 取模 3%3=0, 是分配在 A 库上,新加入 D 库后, uid=3 取模 3%4=3,分配在 D 库上;

    新增一个节点, 大概会有 90% 的数据需要迁移, 这样会面临大量的数据压力,并且对服务造成极大的不稳定性。

    1.2 停机方案

    1. 发布公告

      为了进行数据的重新拆分,在停止服务之前,我们需要提前通知用户,比如:我们的服务会在 yyyy-MM-dd 进行升级,给您带来的不便敬请谅解。

    2. 停止服务

      关闭 Service

    3. 离线数据迁移(拆分,重新分配数据)

      将旧库中的数据按照 Service 层的算法,将数据拆分,重新分配数据

    4. 数据校验

      开发定制一个程序对旧库和新库中的数据进行校验,比对

    5. 更改配置

      修改 Service 层的配置算法,也就是将原来的 uid%3 变为 uid%4

    6. 恢复服务

      重启 Service 服务

    7. 回滚预案

      针对上述的每个步骤都要有数据回滚预案,一旦某个环节(如:数据迁移,恢复服务等)执行失败,立刻进行回滚,重新再来

    停止服务之后, 能够保证迁移工作的正常进行, 但是服务停止,伤害用户体验, 并造成了时间压力, 必须在指定的时间内完成迁移。

    1.3 停写方案

    1. 支持读写分离

      数据库支持读写分离,在扩容之前,每个数据库都提供了读写功能,数据重新分配的过程中,将每个数据库设置为只读状态,关闭写的功能

    2. 升级公告

      为了进行数据的重新拆分,在停写之前,我们需要提前通知用户,比如:我们的服务会在 yyyy-MM-dd 进行升级,给您带来的不便敬请谅解。

    3. 中断写操作,隔离写数据源(或拦截返回统一提示)

      在 Service 层对所有的写请求进行拦截,统一返回提示信息,如:服务正在升级中,只对外提供读服务

    4. 数据同步处理

      将旧库中的数据按照 Service 层的算法,将数据重新分配,迁移(复制数据)

    5. 数据校验

      开发定制一个程序对旧库中的数据进行备份,使用备份的数据和重新分配后的数据进行校验,比对

    6. 更改配置

      通过配置中心,修改 Service 层的配置算法,也就是将原来的 uid%3 变为 uid%4,这个过程不需要重启服务

    7. 恢复写操作

      设置数据库恢复读写功能,去除 Service 层的拦截提示

    8. 数据清理

      使用 delete 语句对冗余数据进行删除

    9. 回滚预案

      针对上述的每个步骤都要有数据回滚预案,一旦某个环节(如:数据迁移等)执行失败,立刻进行回滚,重新再来

    缺点:在数据的复制过程需要消耗大量的时间,停写时间太长,数据需要先复制,再清理冗余数据

    1.4 日志方案

    核心是通过日志进行数据库的同步迁移, 主要操作步骤如下:

    1. 数据迁移之前, 业务应用访问旧的数据库节点。

    1. 日志记录

      在升级之前, 记录 “对旧数据库上的数据修改” 的日志(这里修改包括增、删、改),这个日志不需要记录详细的数据信息,主要记录:

      (1)修改的库;

      (2)修改的表;

      (3)修改的唯一主键;

      (4)修改操作类型。

    日志记录不用关注新增了哪些信息,修改的数据格式,只需要记录以上数据信息,这样日志格式是固定的, 这样能保证方案的通用性。

    服务升级日志记录功能风险较小:

    写和修改接口是少数, 改动点少;

    升级只是增加了一些日志,采用异步方式实现, 对业务功能没有太多影响。

    1. 数据迁移:

      研发定制数据迁移工具, 作用是把旧库中的数据迁移至新库中。

    整个过程仍然采用旧库进行对外服务。

    数据同步工具实现复杂度不高。

    只对旧库进行读取操作, 如果同步出现问题, 都可以对新库进行回滚操作。

    可以限速或分批迁移执行, 不会有时间压力。

    数据迁移完成之后, 并不能切换至新库提供服务。

    因为旧库依然对线上提供服务, 库中的数据随时会发生变化, 但这些变化的数据并没有同步到新库中, 旧库和新库数据不一致, 所以不能直接进行切换, 需要将数据同步完整。

    1. 日志增量迁移

    研发一个日志迁移工具,把上面迁移数据过程中的差异数据追平,处理步骤:

    读取 log 日志,获取具体是哪个库、表和主键发生了变化修改;

    把旧库中的主键记录读取出来

    根据主键 ID,把新库中的记录替换掉

    这样可以最大程度的保障数据的一致性。风险分析:

    整个过程, 仍然是旧库对线上提供服务;

    日志迁移工具实现的复杂度较低;

    任何时间发现问题, 可以重新再来,有充分的容错空间;

    可以限速重放处理日志, 处理过程不会因为对线上影响造成时间压力。

    但是, 日志增量同步完成之后, 还不能切换到新的数据库。

    因为日志增量同步过程中,旧库中可能有数据发生变化, 导致数据不一致,所以需要进一步读取日志, 追平数据记录; 日志增量同步过程随时可能会产生新的数据, 新库与旧库的数据追平也会是一个无限逼近的过程。

    1. 数据校验

      准备好数据校验工具,将旧库和新库中的数据进行比对,直到数据完全一致。

    1. 切换新库

      数据比对完成之后, 将流量转移切换至新库, 至此新库提供服务, 完成迁移。

    但是在极限情况下, 即便通过上面的数据校验处理, 也有可能出现 99.99% 数据一致, 不能保障完全一致,这个时候可以在旧库做一个 readonly 只读功能, 或者将流量屏蔽降级,等待日志增量同步工具完全追平后, 再进行新库的切换。

    至此,完成日志方案的迁移扩容处理, 整个过程能够持续对线上提供服务, 只会短暂的影响服务的可用性。

    这种方案的弊端,是操作繁琐,需要适配多个同步处理工具,成本较高, 需要制定个性化业务的同步处理, 不具备普遍性,耗费的时间周期也较长。

    1.5 双写方案(中小型数据)

    双写方案可通过 canal 或 mq 做实现。

    1. 增加新库,按照现有节点, 增加对应的数量。

    2. 数据迁移:避免增量影响, 先断开主从,再导入(耗时较长), 同步完成并做校验

    3. 增量同步:开启 Canal 同步服务, 监听从节点数据库, 再开启主从同步,从节点收到数据后会通过 Canal 服务, 传递至新的 DB 节点。

    4. 切换新库:通过 Nginx,切换访问流量至新的服务。

    5. 修复切换异常数据:在切换过程中, 如果出现,Canal 未同步,但已切换至新库的请求(比如下单,修改了资金, 但还未同步 ), 可以通过定制程序, 读取检测异常日志,做自动修复或人工处理。

      针对此种情况, 最好是在凌晨用户量小的时候, 或专门停止外网访问,进行切换,减少异常数据的产生。

    6. 数据校验:为保障数据的完全一致, 有必要对数据的数量完整性做校验。

    1.6 平滑 2N 方案(大数据量)

    1. 线上数据库,为了保障其高可用,一般每台主库会配置一台从库,主库负责读写,从库负责读取。下图所示,A,B 是主库,A0 和 B0 是从库。

    1. 当需要扩容的时候,我们把 A0 和 B0 升级为新的主库节点,如此由 2 个分库变为 4 个分库。同时在上层的分片配置,做好映射,规则如下:

      把 uid%4=0 和 uid%4=2 的数据分别分配到 A 和 A0 主库中

      把 uid%4=1 和 uid%4=3 的数据分配到 B 和 B0 主库中

    1. 因为 A 和 A0 库的数据相同,B 和 B0 数据相同,此时无需做数据迁移。只需调整变更一下分片配置即可,通过配置中心更新,不需要重启。

    由于之前 uid%2 的数据是分配在 2 个库里面,扩容之后需要分布到 4 个库中,但由于旧数据仍存在(uid%4=0 的节点,还有一半 uid%4=2 的数据),所以需要对冗余数据做一次清理。

    这个清理,并不会影响线上数据的一致性,可以随时随地进行。

    1. 处理完成之后,为保证数据的高可用,以及将来下一步的扩容需求。

      可以为现有的主库再次分配一个从库。

    2. 平滑 2N 扩容方案实践

    2.1 实现应用服务级别的动态扩容

    扩容前部署架构:

    2.1.1 MariaDB 服务安装

    1. 切换阿里云镜像服务(YUM 安装过慢可以切换)

      1. yum -y install wget
      2. ## 备份CentOS-Base.repo
      3. mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.bak
      4. wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo
      5. wget -P /etc/yum.repos.d/ http://mirrors.aliyun.com/repo/epel-7.repo
      6. yum clean all
      7. yum makecache
    2. 配置 YUM 源

      vi /etc/yum.repos.d/mariadb-10.2.repo 
      

      增加以下内容:

      1. [mariadb]
      2. name = MariaDB
      3. baseurl = https://mirrors.ustc.edu.cn/mariadb/yum/10.2/centos7-amd64
      4. gpgkey=https://yum.mariadb.org/RPM-GPG-KEY-MariaDB
      5. gpgcheck=1
    3. 执行安装

      yum -y install mariadb mariadb-server MariaDB-client  MariaDB-common
      
    4. 如果之前已经安装, 需要先删除(如果之前没有安装, 可以忽略此步骤)

      • 停止 Mariadb 服务

        1. [root@localhost yum.repos.d]# ps -ef | grep mysql
        2. root 1954 1 0 Oct04 ? 00:05:43 /usr/sbin/mysqld --wsrep-new-cluster --user=root
        3. root 89521 81403 0 07:40 pts/0 00:00:00 grep --color=auto mysql
        4. [root@localhost yum.repos.d]# kill 1954
      • 卸载 Mariadb 服务

        yum -y remove Maria*
        
      • 删除数据与配置:

        1. rm -rf /var/lib/mysql/*
        2. rm -rf /etc/my.cnf.d/
        3. rm -rf /etc/my.cnf
    5. 启动 MariaDB 后,执行安全配置向导命令,可根据安全配置向导提高数据库的安全性

      1. systemctl start mariadb
      2. mysql_secure_installation
    6. 开启用户远程连接权限

      将连接用户 root 开启远程连接权限;

      mysql -uroot -p654321
      

      进入 MySQL 服务, 执行以下操作:

      1. use mysql;
      2. delete from user;
      3. ## 配置root用户使用密码654321从任何主机都可以连接到mysql服务器
      4. GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY '654321' WITH GRANT OPTION;
      5. FLUSH PRIVILEGES;

    2.1.2 MariaDB 双主同步

    1. 在 Server1 增加配置:

      在 /etc/my.cnf 中添加以下配置:

      1. [mysqld]
      2. server-id = 1
      3. log-bin=mysql-bin
      4. relay-log = mysql-relay-bin
      5. ## 忽略mysql、information_schema库下对表的操作
      6. replicate-wild-ignore-table=mysql.%
      7. replicate-wild-ignore-table=information_schema.%
      8. ## 默认的情况下mysql是关闭的;
      9. log-slave-updates=on
      10. ## 复制过程中,有任何错误,直接跳过
      11. slave-skip-errors=all
      12. auto-increment-offset=1
      13. auto-increment-increment=2
      14. ## binlog的格式:STATEMENT,ROW,MIXED
      15. binlog_format=mixed
      16. ## 自动过期清理binlog,默认0天,即不自动清理
      17. expire_logs_days=10

      注意, Server1 自增为奇数位:

      auto-increment-offset=1 主键自增基数,从 1 开始。

      auto-increment-increment=2 主键自增偏移量,每次为 2。

    2. 在 Server2 增加配置:

      修改 /etc/my.cnf:

      1. [mysqld]
      2. server-id = 2
      3. log-bin=mysql-bin
      4. relay-log = mysql-relay-bin
      5. replicate-wild-ignore-table=mysql.%
      6. replicate-wild-ignore-table=information_schema.%
      7. log-slave-updates=on
      8. slave-skip-errors=all
      9. auto-increment-offset=2
      10. auto-increment-increment=2
      11. binlog_format=mixed
      12. expire_logs_days=10

      Server2 自增为偶数位:

      auto-increment-offset=2 主键自增基数,从 2 开始。

      auto-increment-increment=2 主键自增偏移量,每次为 2。

      配置修改完成后, 重启数据库。

    3. 同步授权配置

      在 Server1 创建 replica 用于主从同步的用户:

      1. MariaDB [(none)]> grant replication slave, replication client on *.* to 'replica'@'%' identified by 'replica';
      2. mysql> flush privileges;

      查询日志文件与偏移量,开启同步时需使用:

      1. MariaDB [(none)]> show master status;
      2. +------------------+----------+--------------+------------------+
      3. | File | Position | Binlog_Do_DB | Binlog_Ignore_DB |
      4. +------------------+----------+--------------+------------------+
      5. | mysql-bin.000001 | 663 | | |
      6. +------------------+----------+--------------+------------------+

      同样, 在 Server2 创建 replica 用于主从同步的用户:

      1. MariaDB [(none)]> grant replication slave, replication client on *.* to 'replica'@'%' identified by 'replica';
      2. mysql> flush privileges;

      查询日志文件与偏移量:

      1. MariaDB [(none)]> show master status;
      2. +------------------+----------+--------------+------------------+
      3. | File | Position | Binlog_Do_DB | Binlog_Ignore_DB |
      4. +------------------+----------+--------------+------------------+
      5. | mysql-bin.000001 | 663 | | |
      6. +------------------+----------+--------------+------------------+
    4. 配置主从同步信息

      在 Server1 中执行:

      1. MariaDB [(none)]> change master to master_host='192.168.116.141',master_user='replica', master_password='replica', master_port=3306, master_log_file='mysql-bin.000007', master_log_pos=374, master_connect_retry=30;

      在 Server2 中执行:

      1. MariaDB [(none)]> change master to master_host='192.168.116.140',master_user='replica', master_password='replica', master_port=3306, master_log_file='mysql-bin.000015', master_log_pos=374, master_connect_retry=30;
    5. 开启双主同步

      在 Server1 和 Server2 中分别执行:

      1. MariaDB [(none)]> start slave;
      2. Query OK, 0 rows affected (0.00 sec)

      在 Server1 查询同步信息:

      1. MariaDB [(none)]> show slave status\G;
      2. *************************** 1. row ***************************
      3. Slave_IO_State: Waiting for master to send event
      4. Master_Host: 10.10.20.126
      5. Master_User: replica
      6. Master_Port: 3306
      7. Connect_Retry: 30
      8. Master_Log_File: mysql-bin.000001
      9. Read_Master_Log_Pos: 663
      10. Relay_Log_File: mysql-relay-bin.000002
      11. Relay_Log_Pos: 555
      12. Relay_Master_Log_File: mysql-bin.000001
      13. Slave_IO_Running: Yes
      14. Slave_SQL_Running: Yes
      15. ...

      在 Server2 查询同步信息:

      1. MariaDB [(none)]> show slave status\G;
      2. *************************** 1. row ***************************
      3. Slave_IO_State: Waiting for master to send event
      4. Master_Host: 10.10.20.125
      5. Master_User: replica
      6. Master_Port: 3306
      7. Connect_Retry: 30
      8. Master_Log_File: mysql-bin.000001
      9. Read_Master_Log_Pos: 663
      10. Relay_Log_File: mysql-relay-bin.000002
      11. Relay_Log_Pos: 555
      12. Relay_Master_Log_File: mysql-bin.000001
      13. Slave_IO_Running: Yes
      14. Slave_SQL_Running: Yes
      15. ...

      Slave_IO_Running 和 Slave_SQL_Running 都是 Yes,说明双主同步配置成功。

    2.1.3 KeepAlived 安装与高可用配置

    1. 在 Server1 与 Server2 两台节点安装 keepalived:

      yum -y install keepalived
      
    2. 关闭防火墙

      1. systemctl stop firewalld
      2. systemctl disable firewalld
    3. 设置主机名称:

      Server1 节点:

      hostnamectl set-hostname vip1
      

      Server2 节点:

      hostnamectl set-hostname vip2
      
    4. Server1 节点配置

      /etc/keepalived/keepalived.conf:

      1. global_defs {
      2. router_id vip1 # 机器标识,和主机名保持一致,运行keepalived服务器的一个标识
      3. }
      4. vrrp_instance VI_1 { #vrrp实例定义
      5. state BACKUP #lvs的状态模式,MASTER代表主, BACKUP代表备份节点
      6. interface ens33 #绑定对外访问的网卡,vrrp实例绑定的网卡
      7. virtual_router_id 111 #虚拟路由标示,同一个vrrp实例采用唯一标示
      8. priority 100 #优先级,100代表最大优先级, 数字越大优先级越高
      9. advert_int 1 #master与backup节点同步检查的时间间隔,单位是秒
      10. authentication { #设置验证信息
      11. auth_type PASS #有PASS和AH两种
      12. auth_pass 6666 #验证密码,BACKUP密码须相同
      13. }
      14. virtual_ipaddress { #KeepAlived虚拟的IP地址
      15. 192.168.116.150
      16. }
      17. }
      18. virtual_server 192.168.116.150 3306 { #配置虚拟服务器IP与访问端口
      19. delay_loop 6 #健康检查时间
      20. lb_algo rr #负载均衡调度算法, rr代表轮询
      21. lb_kind DR #负载均衡转发规则 DR/NAT/
      22. persistence_timeout 0 #会话保持时间,这里要做测试, 所以设为0, 实际可根据session有效时间配置
      23. protocol TCP #转发协议类型,支持TCP和UDP
      24. real_server 192.168.116.140 3306 { #配置服务器节点VIP1
      25. notify_down /usr/local/shell/mariadb.sh #当服务挂掉时, 会执行此脚本,结束keepalived进程
      26. weight 1 #设置权重,越大权重越高
      27. TCP_CHECK { #状态监测设置
      28. connect_timeout 10 #超时配置, 单位秒
      29. retry 3 #重试次数
      30. delay_before_retry 3 #重试间隔
      31. connect_port 3306 #连接端口, 和上面保持一致
      32. }
      33. }
      34. }

      创建关闭脚本 mariadb.sh

      /usr/local/shell/mariadb.sh:

      pkill keepalived
      

      加入执行权限:

      chmod a+x mariadb.sh
      
    5. Server2 节点配置:

      1. global_defs {
      2. router_id vip2 # 机器标识,和主机名保持一致,运行keepalived服务器的一个标识
      3. }
      4. vrrp_instance VI_1 { #vrrp实例定义
      5. state BACKUP #lvs的状态模式,MASTER代表主, BACKUP代表备份节点
      6. interface ens33 #绑定对外访问的网卡
      7. virtual_router_id 111 #虚拟路由标示,同一个vrrp实例采用唯一标示
      8. priority 98 #优先级,100代表最大优先级, 数字越大优先级越高
      9. advert_int 1 #master与backup节点同步检查的时间间隔,单位是秒
      10. authentication { #设置验证信息
      11. auth_type PASS #有PASS和AH两种
      12. auth_pass 6666 #验证密码,BACKUP密码须相同
      13. }
      14. virtual_ipaddress { #KeepAlived虚拟的IP地址
      15. 192.168.116.150
      16. }
      17. }
      18. virtual_server 192.168.116.150 3306 { #配置虚拟服务器IP与访问端口
      19. delay_loop 6 #健康检查时间
      20. lb_algo rr #负载均衡调度算法, rr代表轮询, 可以关闭
      21. lb_kind DR #负载均衡转发规则, 可以关闭
      22. persistence_timeout 0 #会话保持时间,这里要做测试, 所以设为0, 实际可根据session有效时间配置
      23. protocol TCP #转发协议类型,支持TCP和UDP
      24. real_server 192.168.116.141 3306{ #配置服务器节点VIP2
      25. notify_down /usr/local/shell/mariadb.sh #当服务挂掉时, 会执行此脚本,结束keepalived进程
      26. weight 1 #设置权重,越大权重越高
      27. TCP_CHECK { #r状态监测设置
      28. connect_timeout 10 #超时配置, 单位秒
      29. retry 3 #重试次数
      30. delay_before_retry 3 #重试间隔
      31. connect_port 3306 #连接端口, 和上面保持一致
      32. }
      33. }
      34. }

      和 Server1 的差异项:

      1. router_id vip2 # 机器标识,和主机名保持一致
      2. priority 98 #优先级,100代表最大优先级, 数字越大优先级越高
      3. real_server 10.10.20.126 3306 #配置服务器节点VIP2

      注意, 两台节点都设为 BACKUP

      1. virtual_router_id 111 #同一个vrrp实例采用唯一标示
      2. state BACKUP

      如果不想重启后, 争夺备用节点的 VIP, 可以设置此项

      nopreempt #不主动抢占资源
      

      注意:这个配置只能设置在 backup 主机上,而且这个主机优先级要比另外一台高

    6. 验证高可用

      停止主节点 MariaDB 服务, 验证是否自动切换。

    2.1.4 搭建应用服务工程

    1. ShardingJDBC 的介绍

      是 ShardingSphere 下的一个产品

      定位为轻量级 Java 框架,在 Java 的 JDBC 层提供的额外服务。 它使用客户端直连数据库,以 jar 包形式提供服务,无需额外部署和依赖,可理解为增强版的 JDBC 驱动,完全兼容 JDBC 和各种 ORM 框架。

      • 适用于任何基于 JDBC 的 ORM 框架,如:JPA, Hibernate, Mybatis, Spring JDBC Template 或直接使用 JDBC。

      • 支持任何第三方的数据库连接池,如:DBCP, C3P0, BoneCP, Druid, HikariCP 等。

      • 支持任意实现 JDBC 规范的数据库,目前支持 MySQL,Oracle,SQLServer,PostgreSQL 以及任何遵循 SQL92 标准的数据库

    2. ShardingJDBC 初始化流程

      1)配置 ShardingRuleConfiguration 对象

      2)配置表分片规则 TableRuleConfiguration 对象,设置分库、分表策略

      3)通过 Factory 对象将 Rule 对象与 DataSource 对象装配

      4)ShardingJDBC 使用 DataSource 对象进行分库

    1. ShardingJDBC 集成配置

      1)maven 依赖

      2)规则配置 application.yml

      3)创建 DataSource

    2. 验证应用服务动态扩容

      1. 配置两个数据源,分别指向 Server1 和 Server2
      2. 分片只配置一个数据源
      3. 动态增加另一个数据源
      1. // 动态数据源配置实现扩容
      2. Properties properties = loadPropertiesFile("datasource1.properties");
      3. try {
      4. log.info("load datasource config url: " + properties.get("url"));
      5. DruidDataSource druidDataSource = (DruidDataSource) DruidDataSourceFactory.createDataSource(properties);
      6. druidDataSource.setRemoveAbandoned(true);
      7. druidDataSource.setRemoveAbandonedTimeout(600);
      8. druidDataSource.setLogAbandoned(true);
      9. // 设置数据源错误重连时间
      10. druidDataSource.setTimeBetweenConnectErrorMillis(60000);
      11. druidDataSource.init();
      12. OrchestrationShardingDataSource dataSource = SpringContextUtil.getBean("tradeSystemDataSource", OrchestrationShardingDataSource.class);
      13. Map<String, DataSource> dataSourceMap = dataSource.getDataSource().getDataSourceMap();
      14. dataSourceMap.put(DatasourceEnum.DATASOURCE_2.getValue(), druidDataSource);
      15. Map<String, DataSourceConfiguration> dataSourceConfigMap = new HashMap<String, DataSourceConfiguration>();
      16. for(String key : dataSourceMap.keySet()) {
      17. dataSourceConfigMap.put(key, DataSourceConfiguration.getDataSourceConfiguration(dataSourceMap.get(key)));
      18. }
      19. String result = SHARDING_RULE_TABLE_ORDER.replace(SHARDING_RULE_DATASOURCE, newRule);
      20. replaceActualDataNodes(result);
      21. SHARDING_RULE_DATASOURCE = newRule;
      22. dataSource.renew(new DataSourceChangedEvent(
      23. "/" + DruidSystemDataSourceConfiguration.DYNAMIC_SHARDING + "/config/schema/logic_db/datasource",
      24. dataSourceConfigMap));
      25. return;
      26. } catch (Exception e) {
      27. log.error(e.getMessage(), e);
      28. }
    3. 注意事项

      Sharding JDBC, Mycat, Drds 等产品都是分布式数据库中间件,相比直接的数据源操作,会存在一些限制,Sharding JDBC 在使用时,要注意以下问题:

      • 有限支持子查询
      • 不支持 HAVING
      • 不支持 OR,UNION 和 UNION ALL
      • 不支持特殊 INSERT
      • 每条 INSERT 语句只能插入一条数据,不支持 VALUES 后有多行数据的语句
      • 不支持 DISTINCT 聚合
      • 不支持 dual 虚拟表查询
      • 不支持 SELECT LAST_INSERT_ID (), 不支持自增序列
      • 不支持 CASE WHEN

    2.2 实现数据库的秒级平滑 2N 扩容

    ** 扩容部署架构: **

    2.2.1 新增数据库 VIP

    1. 在 Server2 节点, 增加 VIP

      修改 /etc/keepalived/keepalived.conf

      1. global_defs {
      2. router_id vip2
      3. }
      4. vrrp_instance VI_1 { #vrrp实例定义
      5. state BACKUP #lvs的状态模式,MASTER代表主, BACKUP代表备份节点
      6. interface ens33 #绑定对外访问的网卡
      7. virtual_router_id 112 #虚拟路由标示,同一个vrrp实例采用唯一标示
      8. priority 100 #优先级,100代表最大优先级, 数字越大优先级越高
      9. advert_int 1 #master与backup节点同步检查的时间间隔,单位是秒
      10. authentication { #设置验证信息
      11. auth_type PASS #有PASS和AH两种
      12. auth_pass 6666 #验证密码,BACKUP密码须相同
      13. }
      14. virtual_ipaddress { #KeepAlived虚拟的IP地址
      15. 192.168.116.151
      16. }
      17. }
      18. virtual_server 192.168.116.151 3306 { #配置虚拟服务器IP与访问端口
      19. delay_loop 6 #健康检查时间
      20. persistence_timeout 0 #会话保持时间,这里要做测试, 所以设为0, 实际可根据session有效时间配置
      21. protocol TCP #转发协议类型,支持TCP和UDP
      22. real_server 192.168.116.141 3306{ #配置服务器节点VIP1
      23. notify_down /usr/local/shell/mariadb.sh
      24. weight 1 #设置权重,越大权重越高
      25. TCP_CHECK { #r状态监测设置
      26. connect_timeout 10 #超时配置, 单位秒
      27. retry 3 #重试次数
      28. delay_before_retry 3 #重试间隔
      29. connect_port 3306 #连接端口, 和上面保持一致
      30. }
      31. }
      32. }

      注意配置项:

      1. virtual_router_id 112 #虚拟路由标示,同一个vrrp实例采用唯一标示
      2. priority 100 #优先级,100代表最大优先级, 数字越大优先级越高

    2.2.2 应用服务增加动态数据源

    1. 修改应用服务配置, 增加新的数据源, 指向新设置的 VIP: 192.168.116.151

    2. 通过应用服务接口, 动态扩容调整

    2.2.3 解除原双主同步

    mysql -uroot -p654321

    1. 进入 Server1:

      MariaDB [(none)]> stop slave;
      
    2. 进入 Server2:

      MariaDB [(none)]> stop slave;
      
    3. 通过应用服务接口验证数据是否解除同步

    2.2.4 安装 MariaDB 扩容服务器

    1. 新建两台虚拟机, 分别为 Server3 和 Server4。

    2. 在 Server3 和 Server4 两台节点上安装 MariaDB 服务

      参考 2.1.1 MariaDB 服务安装

    3. 配置 Server3 与 Server1,实现新的双主同步

      1. Server3 节点, 修改 /etc/my.cnf:
      1. [mysqld]
      2. server-id = 3
      3. log-bin=mysql-bin
      4. relay-log = mysql-relay-bin
      5. replicate-wild-ignore-table=mysql.%
      6. replicate-wild-ignore-table=information_schema.%
      7. log-slave-updates=on
      8. slave-skip-errors=all
      9. auto-increment-offset=2
      10. auto-increment-increment=2
      11. binlog_format=mixed
      12. expire_logs_days=10
      1. 重启 Server3 数据库
      service mariadb restart
      
      1. 创建 replica 用于主从同步的用户:
      1. MariaDB [(none)]> grant replication slave, replication client on *.* to 'replica'@'%' identified by 'replica';
      2. mysql> flush privileges;
      1. 在 Server1 节点,进行数据全量备份:
      1. mysqldump -uroot -p654321 --routines --single_transaction --master-data=2 --databases smooth > server1.sql
      1. 查看并记录 master status 信息:
      1. ...
      2. --
      3. -- Position to start replication or point-in-time recovery from
      4. --
      5. -- CHANGE MASTER TO MASTER_LOG_FILE='mysql-bin.000002', MASTER_LOG_POS=17748;
      6. ...
      1. 将备份的 server1.sql 通过 scp 命令拷贝至 Server3 节点。
      scp server1.sql root@192.168.116.142:/usr/local/
      
      1. 将数据还原至 Server3 节点:
      mysql -uroot -p654321 < /usr/local/server1.sql
      
      1. 配置主从同步信息

      根据上面的 master status 信息, 在 Server3 中执行:

      1. MariaDB [(none)]> change master to master_host='192.168.116.140',master_user='replica', master_password='replica', master_port=3306, master_log_file='mysql-bin.000016', master_log_pos=1754, master_connect_retry=30;
      2. Query OK, 0 rows affected (0.01 sec)
      1. 开启主从同步:
      1. MariaDB [(none)]> start slave;
      2. Query OK, 0 rows affected (0.00 sec)

      如果出现问题, 复原主从同步信息:

      1. MariaDB [(none)]> reset slave;
      2. Query OK, 0 rows affected (0.01 sec)
      1. 检查同步状态信息:
      1. MariaDB [(none)]> show slave status \G
      2. *************************** 1. row ***************************
      3. Slave_IO_State: Waiting for master to send event
      4. Master_Host: 10.10.20.125
      5. Master_User: replica
      6. Master_Port: 3306
      7. Connect_Retry: 30
      8. Master_Log_File: mysql-bin.000004
      9. Read_Master_Log_Pos: 11174
      10. Relay_Log_File: mysql-relay-bin.000002
      11. Relay_Log_Pos: 1746
      12. Relay_Master_Log_File: mysql-bin.000004
      13. Slave_IO_Running: Yes
      14. Slave_SQL_Running: Yes
      1. 配置 Server1 与 Server3 节点的同步

      查看 Server3 的日志信息:

      1. MariaDB [(none)]> show master status;
      2. +------------------+----------+--------------+------------------+
      3. | File | Position | Binlog_Do_DB | Binlog_Ignore_DB |
      4. +------------------+----------+--------------+------------------+
      5. | mysql-bin.000001 | 4781 | | |
      6. +------------------+----------+--------------+------------------+

      在 Server1 节点, 配置同步信息:

      1. MariaDB [(none)]> reset slave;
      2. Query OK, 0 rows affected (0.00 sec)
      3. MariaDB [(none)]> change master to master_host='192.168.116.142',master_user='replica', master_password='replica', master_port=3306, master_log_file='mysql-bin.000005', master_log_pos=6931, master_connect_retry=30;
      4. MariaDB [(none)]> start slave;
      5. Query OK, 0 rows affected (0.00 sec)
    4. 配置 Server4 与 Server2 的双主同步

      1. Server4 节点, 修改 /etc/my.cnf:
      1. [mysqld]
      2. server-id = 4
      3. log-bin=mysql-bin
      4. relay-log = mysql-relay-bin
      5. replicate-wild-ignore-table=mysql.%
      6. replicate-wild-ignore-table=information_schema.%
      7. log-slave-updates=on
      8. slave-skip-errors=all
      9. auto-increment-offset=2
      10. auto-increment-increment=2
      11. binlog_format=mixed
      12. expire_logs_days=10
      1. 重启 Server4 数据库
      service mariadb restart
      
      1. 创建 replica 用于主从同步的用户:
      1. MariaDB [(none)]> grant replication slave, replication client on *.* to 'replica'@'%' identified by 'replica';
      2. mysql> flush privileges;
      1. 在 Server2 节点,进行数据全量备份:
      1. mysqldump -uroot -p654321 --routines --single_transaction --master-data=2 --databases smooth > server2.sql
      1. 查看并记录 master status 信息:
      1. ...
      2. --
      3. -- Position to start replication or point-in-time recovery from
      4. --
      5. -- CHANGE MASTER TO MASTER_LOG_FILE='mysql-bin.000003', MASTER_LOG_POS=4208;
      6. ...
      1. 将备份的 server2.sql 通过 scp 命令拷贝至 Server4 节点。
      scp server2.sql root@192.168.116.143:/usr/local/
      
      1. 将数据还原至 Server4 节点:
      mysql -uroot -p654321 < /usr/local/server2.sql
      
      1. 配置主从同步信息

      根据上面的 master status 信息, 在 Server4 中执行:

      1. MariaDB [(none)]> change master to master_host='192.168.116.141',master_user='replica', master_password='replica', master_port=3306, master_log_file='mysql-bin.000007', master_log_pos=3006, master_connect_retry=30;
      2. Query OK, 0 rows affected (0.01 sec)
      1. 开启主从同步:
      1. MariaDB [(none)]> start slave;
      2. Query OK, 0 rows affected (0.00 sec)

      注意, 如果出现问题, 复原主从同步信息:

      1. MariaDB [(none)]> reset slave;
      2. Query OK, 0 rows affected (0.01 sec)
      1. 检查同步状态信息:
      1. MariaDB [(none)]> show slave status \G
      2. *************************** 1. row ***************************
      3. Slave_IO_State: Waiting for master to send event
      4. Master_Host: 10.10.20.125
      5. Master_User: replica
      6. Master_Port: 3306
      7. Connect_Retry: 30
      8. Master_Log_File: mysql-bin.000004
      9. Read_Master_Log_Pos: 11174
      10. Relay_Log_File: mysql-relay-bin.000002
      11. Relay_Log_Pos: 1746
      12. Relay_Master_Log_File: mysql-bin.000004
      13. Slave_IO_Running: Yes
      14. Slave_SQL_Running: Yes
      1. 配置 Server2 与 Server4 节点的同步

      查看 Server4 的日志信息:

      1. MariaDB [(none)]> show master status;
      2. +------------------+----------+--------------+------------------+
      3. | File | Position | Binlog_Do_DB | Binlog_Ignore_DB |
      4. +------------------+----------+--------------+------------------+
      5. | mysql-bin.000001 | 3696 | | |
      6. +------------------+----------+--------------+------------------+

      在 Server2 节点, 配置同步信息:

      1. MariaDB [(none)]> reset slave;
      2. Query OK, 0 rows affected (0.00 sec)
      3. MariaDB [(none)]> change master to master_host='192.168.116.143',master_user='replica', master_password='replica', master_port=3306, master_log_file='mysql-bin.000005', master_log_pos=5787, master_connect_retry=30;
      4. MariaDB [(none)]> start slave;
      5. Query OK, 0 rows affected (0.00 sec)

    2.2.5 增加 KeepAlived 服务实现高可用

    1. 确保新增的 Server3 和 Server4 节点安装 Keepalived 服务。

    2. 修改 Server3 节点配置

      1. global_defs {
      2. router_id vip3 # 机器标识,一般设为hostname,故障发生时,邮件通知会使用到。
      3. }
      4. vrrp_instance VI_1 { #vrrp实例定义
      5. state BACKUP #lvs的状态模式,MASTER代表主, BACKUP代表备份节点
      6. interface ens33 #绑定对外访问的网卡
      7. virtual_router_id 111 #虚拟路由标示,同一个vrrp实例采用唯一标示
      8. priority 98 #优先级,100代表最大优先级, 数字越大优先级越高
      9. advert_int 1 #master与backup节点同步检查的时间间隔,单位是秒
      10. authentication { #设置验证信息
      11. auth_type PASS #有PASS和AH两种
      12. auth_pass 6666 #验证密码,BACKUP密码须相同
      13. }
      14. virtual_ipaddress { #KeepAlived虚拟的IP地址
      15. 192.168.116.150
      16. }
      17. }
      18. virtual_server 192.168.116.150 3306 { #配置虚拟服务器IP与访问端口
      19. delay_loop 6 #健康检查时间
      20. persistence_timeout 0 #会话保持时间,这里要做测试, 所以设为0, 实际可根据session有效时间配置
      21. protocol TCP #转发协议类型,支持TCP和UDP
      22. real_server 192.168.116.142 3306{ #配置服务器节点VIP3
      23. notify_down /usr/local/shell/mariadb.sh
      24. weight 1 #设置权重,越大权重越高
      25. TCP_CHECK { #r状态监测设置
      26. connect_timeout 10 #超时配置, 单位秒
      27. retry 3 #重试次数
      28. delay_before_retry 3 #重试间隔
      29. connect_port 3306 #连接端口, 和上面保持一致
      30. }
      31. }
      32. }

      注意里面 IP 配置正确, 修改完成后重启服务。

      创建关闭脚本 mariadb.sh

      /usr/local/shell/mariadb.sh:

      pkill keepalived
      

      加入执行权限:

      chmod a+x mariadb.sh
      
    3. 修改 Server4 节点配置

      1. global_defs {
      2. router_id vip4 # 机器标识,一般设为hostname,故障发生时,邮件通知会使用到。
      3. }
      4. vrrp_instance VI_1 { #vrrp实例定义
      5. state BACKUP #lvs的状态模式,MASTER代表主, BACKUP代表备份节点
      6. interface ens33 #绑定对外访问的网卡
      7. virtual_router_id 112 #虚拟路由标示,同一个vrrp实例采用唯一标示
      8. priority 98 #优先级,100代表最大优先级, 数字越大优先级越高
      9. advert_int 1 #master与backup节点同步检查的时间间隔,单位是秒
      10. authentication { #设置验证信息
      11. auth_type PASS #有PASS和AH两种
      12. auth_pass 6666 #验证密码,BACKUP密码须相同
      13. }
      14. virtual_ipaddress { #KeepAlived虚拟的IP地址
      15. 192.168.116.151
      16. }
      17. }
      18. virtual_server 192.168.116.151 3306 { #配置虚拟服务器IP与访问端口
      19. delay_loop 6 #健康检查时间
      20. persistence_timeout 0 #会话保持时间,这里要做测试, 所以设为0, 实际可根据session有效时间配置
      21. protocol TCP #转发协议类型,支持TCP和UDP
      22. real_server 192.168.116.143 3306{ #配置服务器节点VIP4
      23. notify_down /usr/local/shell/mariadb.sh
      24. weight 1 #设置权重,越大权重越高
      25. TCP_CHECK { #r状态监测设置
      26. connect_timeout 10 #超时配置, 单位秒
      27. retry 3 #重试次数
      28. delay_before_retry 3 #重试间隔
      29. connect_port 3306 #连接端口, 和上面保持一致
      30. }
      31. }
      32. }

      创建关闭脚本 mariadb.sh

      /usr/local/shell/mariadb.sh:

      pkill keepalived
      

      给所有的用户组加入执行权限:

      chmod a+x mariadb.sh
      
    4. 修改完后重启 Keepalived 服务。

    2.2.6 清理数据并验证

    1. 通过应用服务动态扩容接口做调整和验证

    2. 在 Server1 节点清理数据

      根据取模规则, 保留 accountNo 为偶数的数据

      delete from t_trade_order where accountNo % 2 != 0
      
    3. 在 Server2 节点清理数据

      根据取模规则, 保留 accountNo 为奇数的数据

      delete from t_trade_order where accountNo % 2 != 1
      

    3.keepalived 高可用配置大全

    在 Server1(192.168.116.140)中执行:

    1. MariaDB [(none)]> change master to master_host='192.168.116.141',master_user='replica', master_password='replica', master_port=3306, master_log_file='mysql-bin.000005', master_log_pos=3207, master_connect_retry=30;

    在 Server2(192.168.116.141)中执行:

    1. MariaDB [(none)]> change master to master_host='192.168.116.140',master_user='replica', master_password='replica', master_port=3306, master_log_file='mysql-bin.000012', master_log_pos=1951, master_connect_retry=30;

    在 Server3(192.168.116.142)中执行:

    1. MariaDB [(none)]> change master to master_host='192.168.116.140',master_user='replica', master_password='replica', master_port=3306, master_log_file='mysql-bin.000013', master_log_pos=2781, master_connect_retry=30;
    2. Query OK, 0 rows affected (0.01 sec)

    在 Server4(192.168.116.143)中执行:

    1. MariaDB [(none)]> change master to master_host='192.168.116.141',master_user='replica', master_password='replica', master_port=3306, master_log_file='mysql-bin.000005', master_log_pos=7358, master_connect_retry=30;
    2. Query OK, 0 rows affected (0.01 sec)

    Server1 和 Server2 双主关系

    Server1: keepalived.conf

    vi /etc/keepalived/keepalived.conf

    1. global_defs {
    2. router_id vip1
    3. }
    4. vrrp_instance VI_1 {
    5. state BACKUP
    6. interface ens33
    7. virtual_router_id 111
    8. priority 100
    9. advert_int 1
    10. authentication {
    11. auth_type PASS
    12. auth_pass 6666
    13. }
    14. virtual_ipaddress {
    15. 192.168.116.150
    16. }
    17. }
    18. virtual_server 192.168.116.150 3306 {
    19. delay_loop 6
    20. lb_algo rr
    21. lb_kind DR // NAT|DR|TUN
    22. persistence_timeout 0
    23. protocol TCP
    24. real_server 192.168.116.140 3306 {
    25. notify_down /usr/local/shell/mariadb.sh
    26. weight 1
    27. TCP_CHECK {
    28. connect_timeout 10
    29. retry 3
    30. delay_before_retry 3
    31. connect_port 3306
    32. }
    33. }
    34. }

    Server2:keepalived.conf

    vi /etc/keepalived/keepalived.conf

    1. global_defs {
    2. router_id vip2
    3. }
    4. vrrp_instance VI_1 {
    5. state BACKUP
    6. interface ens33
    7. virtual_router_id 111
    8. priority 98
    9. advert_int 1
    10. authentication {
    11. auth_type PASS
    12. auth_pass 6666
    13. }
    14. virtual_ipaddress {
    15. 192.168.116.150
    16. }
    17. }
    18. virtual_server 192.168.116.150 3306 {
    19. delay_loop 6
    20. lb_algo rr
    21. lb_kind DR
    22. persistence_timeout 0
    23. protocol TCP
    24. real_server 192.168.116.141 3306{
    25. notify_down /usr/local/shell/mariadb.sh
    26. weight 1
    27. TCP_CHECK {
    28. connect_timeout 10
    29. retry 3
    30. delay_before_retry 3
    31. connect_port 3306
    32. }
    33. }
    34. }

    新增数据库 VIP

    Server2:keepalived.conf

    vi /etc/keepalived/keepalived.conf

    1. global_defs {
    2. router_id vip2
    3. }
    4. vrrp_instance VI_1 {
    5. state BACKUP
    6. interface ens33
    7. virtual_router_id 112
    8. priority 100
    9. advert_int 1
    10. authentication {
    11. auth_type PASS
    12. auth_pass 6666
    13. }
    14. virtual_ipaddress {
    15. 192.168.116.151
    16. }
    17. }
    18. virtual_server 192.168.116.151 3306 {
    19. delay_loop 6
    20. persistence_timeout 0
    21. protocol TCP
    22. real_server 192.168.116.141 3306{
    23. notify_down /usr/local/shell/mariadb.sh
    24. weight 1
    25. TCP_CHECK {
    26. connect_timeout 10
    27. retry 3
    28. delay_before_retry 3
    29. connect_port 3306
    30. }
    31. }
    32. }

    Server1 和 Server3 双主关系

    Server3: keepalived.conf

    vi /etc/keepalived/keepalived.conf

    1. global_defs {
    2. router_id vip3
    3. }
    4. vrrp_instance VI_1 {
    5. state BACKUP
    6. interface ens33
    7. virtual_router_id 111
    8. priority 98
    9. advert_int 1
    10. authentication {
    11. auth_type PASS
    12. auth_pass 6666
    13. }
    14. virtual_ipaddress {
    15. 192.168.116.150
    16. }
    17. }
    18. virtual_server 192.168.116.150 3306 {
    19. delay_loop 6
    20. lb_algo rr
    21. lb_kind DR
    22. persistence_timeout 0
    23. protocol TCP
    24. real_server 192.168.116.142 3306 {
    25. notify_down /usr/local/shell/mariadb.sh
    26. weight 1
    27. TCP_CHECK {
    28. connect_timeout 10
    29. retry 3
    30. delay_before_retry 3
    31. connect_port 3306
    32. }
    33. }
    34. }

    Server2 和 Server4 双主关系

    Server4: keepalived.conf

    vi /etc/keepalived/keepalived.conf

    1. global_defs {
    2. router_id vip4
    3. }
    4. vrrp_instance VI_1 {
    5. state BACKUP
    6. interface ens33
    7. virtual_router_id 112
    8. priority 98
    9. advert_int 1
    10. authentication {
    11. auth_type PASS
    12. auth_pass 6666
    13. }
    14. virtual_ipaddress {
    15. 192.168.116.151
    16. }
    17. }
    18. virtual_server 192.168.116.151 3306 {
    19. delay_loop 6
    20. lb_algo rr
    21. lb_kind DR
    22. persistence_timeout 0
    23. protocol TCP
    24. real_server 192.168.116.143 3306{
    25. notify_down /usr/local/shell/mariadb.sh
    26. weight 1
    27. TCP_CHECK {
    28. connect_timeout 10
    29. retry 3
    30. delay_before_retry 3
    31. connect_port 3306
    32. }
    33. }
    34. }
  • 相关阅读:
    C++&QT day4
    【机器学习】之第五章——神经网络
    达梦数据守护搭建测试遇到create link to dmwatcher() error问题
    【CentOS】Linux 安装 Anaconda 及配置 Jupyter
    Vue 3 第二十二章:组件十(组件高级特性-组件的渲染函数和JSX/TSX语法)
    kube-scheduler源码分析(2)-核心处理逻辑分析
    Decimal.ToString()堆栈溢出异常
    数据库-DQL分组查询-where 和 having的区别
    动画系统的前世今生(一)
    C/C++ sizeof运算符的使用
  • 原文地址:https://blog.csdn.net/u012181546/article/details/127919320