2024-07-13
Docker
00

目录

安装MySQL主从复制
新建主服务器容器和配置
新建主服务器容器和配置
安装redis集群
哈希取余分区
一致性哈希算法分区
哈希槽分区
3主3从redis集群配置
主从容错切换迁移案例
主从扩容案例
主从缩容案例

安装MySQL主从复制

新建主服务器容器和配置

  1. 新建主服务器容器示例3307

    bash
    docker run -p 3307:3306 --name mysql-master --privileged=true \ -v /mydata/mysql-master/log:/var/log/mysql \ -v /mydata/mysql-master/data:/var/lib/mysql \ -v /mydata/mysql-master/conf:/etc/mysql/conf.d \ -e MYSQL_ROOT_PASSWORD=root -d mysql:5.7
  2. 进入/mydata/mysql-master/conf目录下新建my.cnf,编辑配置文件:vim my.cnf

    bash
    [mysqld] ## 设置server_id,同一局域网中需要唯一 server_id=101 ## 指定不需要同步的数据库名称 binlog-ignore-db=mysql ## 开启二进制日志功能 log-bin=mall-mysql-bin ## 设置二进制日志使用内存大小(事务) binlog_cache_size=1M ## 设置使用的二进制日志格式(mixed,statement,row) binlog_format=mixed ## 二进制日志过期清理时间。默认值为0,表示不自动清理。 expire_logs_days=7 ## 跳过主从复制中遇到的所有错误或指定类型的错误,避免slave端复制中断。 ## 如:1062错误是指一些主键重复,1032错误是因为主从数据库数据不一致 slave_skip_errors=1062
  3. 修改完配置后重启master实例

    bash
    docker restart mysql-master
  4. 进入mysql-master容器

    bash
    # 进入容器 docker restart mysql-master # 登录mysql mysql -uroot -p
  5. master容器实例内创建创建数据同步用户

    bash
    # 创建用户 create user 'slave'@'%' identified by '123456'; # 给用户授权 grant replication slave,replication client on *.* to 'slave'@'%';

新建主服务器容器和配置

  1. 新建从服务器容器示例3308

    bash
    docker run -p 3308:3306 --name mysql-slave --privileged=true \ -v /mydata/mysql-slave/log:/var/log/mysql \ -v /mydata/mysql-slave/data:/var/lib/mysql \ -v /mydata/mysql-slave/conf:/etc/mysql/conf.d \ -e MYSQL_ROOT_PASSWORD=root -d mysql:5.7
  2. 进入/mydata/mysql-slave/conf目录下新建my.cnf,内如如下:

    bash
    [mysqld] ## 设置server_id,同一局域网中需要唯一 server_id=102 ## 指定不需要同步的数据库名称 binlog-ignore-db=mysql ## 开启二进制日志功能,以备Slave作为其它数据库实例的Master时使用 log-bin=mall-mysql-slave1-bin ## 设置二进制日志使用内存大小(事务) binlog_cache_size=1M ## 设置使用的二进制日志格式(mixed,statement,row) binlog_format=mixed ## 二进制日志过期清理时间。默认值为0,表示不自动清理。 expire_logs_days=7 ## 跳过主从复制中遇到的所有错误或指定类型的错误,避免slave端复制中断。 ## 如:1062错误是指一些主键重复,1032错误是因为主从数据库数据不一致 slave_skip_errors=1062 ## relay_log配置中继日志 relay_log=mall-mysql-relay-bin ## log_slave_updates表示slave将复制事件写进自己的二进制日志 log_slave_updates=1 ## slave设置为只读(具有super权限的用户除外) read_only=1
  3. 修改完配置后重启slave实例

    bash
    docker restart mysql-slave
  4. 在主数据库中查看主从同步状态

    bash
    mysql> show master status; +-----------------------+----------+--------------+------------------+-------------------+ | File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set | +-----------------------+----------+--------------+------------------+-------------------+ | mall-mysql-bin.000001 | 617 | | mysql | | +-----------------------+----------+--------------+------------------+-------------------+
  5. 进入mysql-slave容器

    bash
    # 进入从库容器 docker exec -it mysql-slave /bin/bash # 进入mysql mysql -uroot -p
  6. 在从数据库中配置主从复制

    bash
    change master to master_host='192.168.30.100', master_user='slave', master_password='123456', master_port=3307, master_log_file='mall-mysql-bin.000001', master_log_pos=617, master_connect_retry=30;

    主从复制命令参数说明

    bash
    master_host:主数据库的IP地址; master_port:主数据库的运行端口; master_user:在主数据库创建的用于同步数据的用户账号; master_password:在主数据库创建的用于同步数据的用户密码; master_log_file:指定从数据库要复制数据的日志文件,通过查看主数据的状态,获取File参数; master_log_pos:指定从数据库从哪个位置开始复制数据,通过查看主数据的状态,获取Position参数; master_connect_retry:连接失败重试的时间间隔,单位为秒。
  7. 在从数据库中查看主从同步状态

    这里我们主要看一下两个参数,No说明还没开始同步

    Slave_IO_Running: No Slave_SQL_Running: No

    bash
    mysql> show slave status \G; *************************** 1. row *************************** Slave_IO_State: Master_Host: 127.0.0.1 Master_User: slave Master_Port: 3307 Connect_Retry: 30 Master_Log_File: mall-mysql-bin.000001 Read_Master_Log_Pos: 617 Relay_Log_File: mall-mysql-relay-bin.000001 Relay_Log_Pos: 4 Relay_Master_Log_File: mall-mysql-bin.000001 Slave_IO_Running: No Slave_SQL_Running: No Replicate_Do_DB: Replicate_Ignore_DB: Replicate_Do_Table: Replicate_Ignore_Table: Replicate_Wild_Do_Table: Replicate_Wild_Ignore_Table: Last_Errno: 0 Last_Error: Skip_Counter: 0 Exec_Master_Log_Pos: 617 Relay_Log_Space: 154 Until_Condition: None Until_Log_File: Until_Log_Pos: 0 Master_SSL_Allowed: No Master_SSL_CA_File: Master_SSL_CA_Path: Master_SSL_Cert: Master_SSL_Cipher: Master_SSL_Key: Seconds_Behind_Master: NULL Master_SSL_Verify_Server_Cert: No Last_IO_Errno: 0 Last_IO_Error: Last_SQL_Errno: 0 Last_SQL_Error: Replicate_Ignore_Server_Ids: Master_Server_Id: 0 Master_UUID: Master_Info_File: /var/lib/mysql/master.info SQL_Delay: 0 SQL_Remaining_Delay: NULL Slave_SQL_Running_State: Master_Retry_Count: 86400 Master_Bind: Last_IO_Error_Timestamp: Last_SQL_Error_Timestamp: Master_SSL_Crl: Master_SSL_Crlpath: Retrieved_Gtid_Set: Executed_Gtid_Set: Auto_Position: 0 Replicate_Rewrite_DB: Channel_Name: Master_TLS_Version: 1 row in set (0.00 sec)
  8. 在从数据库中开启主从同步

    bash
    mysql> start slave; Query OK, 0 rows affected (0.01 sec)
  9. 查看从数据库状态是否已经同步

    bash
    mysql> show slave status \G; Slave_IO_Running: Yes Slave_SQL_Running: Yes
  10. 主从复制测试

    主库先创建数据库,插入数据

    bash
    mysql> create database db01; Query OK, 1 row affected (0.00 sec) mysql> use db01; Database changed mysql> create table t1 (id int, name varchar(20)); Query OK, 0 rows affected (0.01 sec) mysql> insert into t1 values(1,'abcd'); Query OK, 1 row affected (0.01 sec) mysql> select * from t1; +------+------+ | id | name | +------+------+ | 1 | abcd | +------+------+ 1 row in set (0.00 sec)

    在从库查询是否和主库同步

    bash
    mysql> show databases; +--------------------+ | Database | +--------------------+ | information_schema | | db01 | | mysql | | performance_schema | | sys | +--------------------+ 5 rows in set (0.00 sec) mysql> use db01; Reading table information for completion of table and column names You can turn off this feature to get a quicker startup with -A Database changed mysql> select * from t1; +------+------+ | id | name | +------+------+ | 1 | abcd | +------+------+ 1 row in set (0.00 sec)

安装redis集群

假设有1~2亿条数据要缓存,我们应该如何设计这个存储案例?

一般有3种解决方案:

哈希取余分区

2亿条记录就是2亿个k,v,我们单机不行必须要分布式多机,假设有3台机器构成一个集群,用户每次读写操作都是根据公式:hash(key) % N个机器台数,计算出哈希值,用来决定数据映射到哪一个节点上。

优点:

简单粗暴,直接有效,只需要预估好数据规划好节点,例如3台、8台、10台,就能保证一段时间的数据支撑。使用Hash算法让固定的一部分请求落到同一台服务器上,这样每台服务器固定处理一部分请求(并维护这些请求的信息),起到负载均衡+分而治之的作用。

缺点:

原来规划好的节点,进行扩容或者缩容就比较麻烦了额,不管扩缩,每次数据变动导致节点有变动,映射关系需要重新进行计算,在服务器个数固定不变时没有问题,如果需要弹性扩容或故障停机的情况下,原来的取模公式就会发生变化:Hash(key)/3会变成Hash(key) /?。此时地址经过取余运算的结果将发生很大变化,根据公式获取的服务器也会变得不可控。 某个redis机器宕机了,由于台数数量变化,会导致hash取余全部数据重新洗牌。

一致性哈希算法分区

  • 是什么?

    一致性Hash算法背景,一致性哈希算法在1997年由麻省理工学院中提出的,设计目标是为了解决分布式缓存数据变动和映射问题,某个机器宕机了,分母数量改变了,自然取余数不OK了。

  • 能干嘛? 提出一致性Hash解决方案。 目的是当服务器个数发生变动时, 尽量减少影响客户端到服务器的映射关系。

3大步骤

  1. 算法构建一致性哈希环

    一致性哈希算法必然有个hash函数并按照算法产生hash值,这个算法的所有可能哈希值会构成一个全量集,这个集合可以成为一个hash空间[0,2^32-1],这个是一个线性空间,但是在算法中,我们通过适当的逻辑控制将它首尾相连(0 = 2^32),这样让它逻辑上形成了一个环形空间。

    它也是按照使用取模的方法,前面笔记介绍的节点取模法是对节点(服务器)的数量进行取模。而一致性Hash算法是对2^32取模,简单来说,一致性Hash算法将整个哈希值空间组织成一个虚拟的圆环,如假设某哈希函数H的值空间为0-2^32-1(即哈希值是一个32位无符号整形),整个哈希环如下图:整个空间按顺时针方向组织,圆环的正上方的点代表0,0点右侧的第一个点代表1,以此类推,2、3、4、……直到2^32-1,也就是说0点左侧的第一个点代表2^32-1, 0和2^32-1在零点中方向重合,我们把这个由2^32个点组成的圆环称为Hash环。

    image.png

  2. 服务器IP节点映射

    将集群中各个IP节点映射到环上的某一个位置。

    将各个服务器使用Hash进行一个哈希,具体可以选择服务器的IP或主机名作为关键字进行哈希,这样每台机器就能确定其在哈希环上的位置。假如4个节点NodeA、B、C、D,经过IP地址的哈希函数计算(hash(ip)),使用IP地址哈希后在环空间的位置如下:

    image.png

  3. key落到服务器的落键规则

    • 当我们需要存储一个kv键值对时,首先计算key的hash值,hash(key),将这个key使用相同的函数Hash计算出哈希值并确定此数据在环上的位置,从此位置沿环顺时针“行走”,第一台遇到的服务器就是其应该定位到的服务器,并将该键值对存储在该节点上。

    • 如我们有Object A、Object B、Object C、Object D四个数据对象,经过哈希计算后,在环空间上的位置如下:根据一致性Hash算法,数据A会被定为到Node A上,B被定为到Node B上,C被定为到Node C上,D被定为到Node D上。

    !image.png

    优点:

    一致性哈希算法的容错性

    假设Node C宕机,可以看到此时对象A、B、D不会受到影响,只有C对象被重定位到Node D。一般的,在一致性Hash算法中,如果一台服务器不可用,则受影响的数据仅仅是此服务器到其环空间中前一台服务器(即沿着逆时针方向行走遇到的第一台服务器)之间数据,其它不会受到影响。简单说,就是C挂了,受到影响的只是B、C之间的数据,并且这些数据会转移到D进行存储。

    一致性哈希算法的扩展性

    数据量增加了,需要增加一台节点NodeX,X的位置在A和B之间,那收到影响的也就是A到X之间的数据,重新把A到X的数据录入到X上即可,不会导致hash取余全部数据重新洗牌。

    缺点:

    一致性哈希算法的数据倾斜问题

    一致性Hash算法在服务节点太少时,容易因为节点分布不均匀而造成数据倾斜(被缓存的对象大部分集中缓存在某一台服务器上)问题,例如系统中只有两台服务器:

    image.png

    小总结

    为了在节点数目发生改变时尽可能少的迁移数据将所有的存储节点排列在收尾相接的Hash环上,每个key在计算Hash后会顺时针找到临近的存储节点存放。而当有节点加入或退出时仅影响该节点在Hash环上顺时针相邻的后续节点。

    优点

    加入和删除节点只影响哈希环中顺时针方向的相邻的节点,对其他节点无影响。

    缺点

    数据的分布和节点的位置有关,因为这些节点不是均匀的分布在哈希环上的,所以数据在进行存储时达不到均匀分布的效果。

哈希槽分区

哈希槽实质就是一个数组,数组[0,2^14 -1]形成hash slot空间。

解决均匀分配的问题,在数据和节点之间又加入了一层,把这层称为哈希槽(slot),用于管理数据和节点之间的关系,现在就相当于节点上放的是槽,槽里放的是数据。

image.png

多少个hash槽

一个集群只能有16384个槽,编号0-16383(0-2^14-1)。这些槽会分配给集群中的所有主节点,分配策略没有要求。可以指定哪些编号的槽分配给哪个主节点。集群会记录节点和槽的对应关系。解决了节点和槽的关系后,接下来就需要对key求哈希值,然后对16384取余,余数是几key就落入对应的槽里。slot = CRC16(key) % 16384。以槽为单位移动数据,因为槽的数目是固定的,处理起来比较容易,这样数据移动问题就解决了。

哈希槽计算

Redis 集群中内置了 16384 个哈希槽,redis 会根据节点数量大致均等的将哈希槽映射到不同的节点。当需要在 Redis 集群中放置一个 key-value时,redis 先对 key 使用 crc16 算法算出一个结果,然后把结果对 16384 求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,也就是映射到某个节点上。如下代码,key之A 、B在Node2, key之C落在Node3上

image.png

3主3从redis集群配置

  1. 关闭防火墙+启动docker服务:systemctl start docker

  2. 新建6个docker容器实例

    bash
    docker run -d --name redis-node-1 --net host --privileged=true -v /data/redis/share/redis-node-1:/data redis --cluster-enabled yes --appendonly yes --port 6381 docker run -d --name redis-node-2 --net host --privileged=true -v /data/redis/share/redis-node-2:/data redis --cluster-enabled yes --appendonly yes --port 6382 docker run -d --name redis-node-3 --net host --privileged=true -v /data/redis/share/redis-node-3:/data redis --cluster-enabled yes --appendonly yes --port 6383 docker run -d --name redis-node-4 --net host --privileged=true -v /data/redis/share/redis-node-4:/data redis --cluster-enabled yes --appendonly yes --port 6384 docker run -d --name redis-node-5 --net host --privileged=true -v /data/redis/share/redis-node-5:/data redis --cluster-enabled yes --appendonly yes --port 6385 docker run -d --name redis-node-6 --net host --privileged=true -v /data/redis/share/redis-node-6:/data redis --cluster-enabled yes --appendonly yes --port 6386

    命令解释:

    • docker run :创建并运行docker容器实例

    • --name redis-node-1:容器名字

    • --net host:使用宿主机的IP和端口,默认

    • --privileged=true:获取宿主机root权限

    • -v /data/redis/share/redis-node-1:/data:容器卷,宿主机地址

      内部地址

    • redis:redis镜像,没有加版本号,默认最新版本

    • --cluster-enabled yes:开启redis集群

    • --appendonly yes:开启持久化

    • --port 6381:redis端口号

  3. 进入容器redis-node-1并为6台机器构建集群关系

    进入容器

    bash
    docker exec -it redis-node-1 /bin/bash

    构建主从关系,--cluster-replicas 1表示为每个master创建一个slave节点

    bash
    redis-cli --cluster create 192.168.30.100:6381 192.168.30.100:6382 192.168.30.100:6383 192.168.30.100:6384 192.168.30.100:6385 192.168.30.100:6386 --cluster-replicas 1
  4. 链接进入6381作为切入点,查看集群状态

    bash
    # 进入redis redis-cli -p 6381 # 查看redis集群信息 cluster info # 查看本集群有哪些节点 cluster nodes

主从容错切换迁移案例

数据读写存储

对6381新增两个key

bash
# -c参数是集群连接 redis-cli -p 6381 -c # 增加key set k1 v1 set k2 v2

查看集群信息

bash
redis-cli --cluster check 192.168.30.100:6381

容错切换迁移

主机6381和从机切换,先停止主机6381

bash
# 先查看节点关系 127.0.0.1:6381> cluster nodes 96efcb52f63cfd6bd9d827fa8222a0cd8b27df37 192.168.30.100:6383@16383 master - 0 1720528325000 3 connected 10923-16383 f42266e907772969893df42775a04f937d888932 192.168.30.100:6381@16381 myself,master - 0 1720528324000 1 connected 0-5460 90f518ac231ab00614f4bd3c3bcea8ad0bac047d 192.168.30.100:6382@16382 master - 0 1720528325959 2 connected 5461-10922 d2752f431f947bc6ab814a3c4344f0061bb1976a 192.168.30.100:6384@16384 slave 90f518ac231ab00614f4bd3c3bcea8ad0bac047d 0 1720528325000 2 connected c0eb0232fb6bf291119dd93f2ccaedbfdcf13398 192.168.30.100:6385@16385 slave 96efcb52f63cfd6bd9d827fa8222a0cd8b27df37 0 1720528324950 3 connected 16d0bc12696d6a1d476b8bf107d30e44f42f087a 192.168.30.100:6386@16386 slave f42266e907772969893df42775a04f937d888932 0 1720528326966 1 connected # 退出容器,停止6381容器 docker stop redis-node-1

再次查看集群信息,发现6381失败了后,之前的6386的slave已经变成了master节点

bash
127.0.0.1:6382> cluster nodes 90f518ac231ab00614f4bd3c3bcea8ad0bac047d 192.168.30.100:6382@16382 myself,master - 0 1720578621000 2 connected 5461-10922 f42266e907772969893df42775a04f937d888932 192.168.30.100:6381@16381 master,fail - 1720528441663 1720528436000 1 disconnected 96efcb52f63cfd6bd9d827fa8222a0cd8b27df37 192.168.30.100:6383@16383 master - 0 1720578620000 3 connected 10923-16383 c0eb0232fb6bf291119dd93f2ccaedbfdcf13398 192.168.30.100:6385@16385 slave 96efcb52f63cfd6bd9d827fa8222a0cd8b27df37 0 1720578621312 3 connected 16d0bc12696d6a1d476b8bf107d30e44f42f087a 192.168.30.100:6386@16386 master - 0 1720578621000 7 connected 0-5460 d2752f431f947bc6ab814a3c4344f0061bb1976a 192.168.30.100:6384@16384 slave 90f518ac231ab00614f4bd3c3bcea8ad0bac047d 0 1720578622321 2 connected

先还原之前的3主3从

bash
# 把6381容器起来 docker start redis-node-1 # 进入容器里的redis,再次查看集群状态,发现6381变为了slave 127.0.0.1:6382> cluster nodes f42266e907772969893df42775a04f937d888932 192.168.30.100:6381@16381 slave 16d0bc12696d6a1d476b8bf107d30e44f42f087a 0 1720592004186 7 connected

主从扩容案例

新建6387、6388两个节点

bash
docker run -d --name redis-node-7 --net host --privileged=true -v /data/redis/share/redis-node-7:/data redis --cluster-enabled yes --appendonly yes --port 6387 docker run -d --name redis-node-8 --net host --privileged=true -v /data/redis/share/redis-node-8:/data redis --cluster-enabled yes --appendonly yes --port 6388

进入6387容器实例内部

bash
docker exec -it redis-node-7 /bin/bash

将新增的6387节点(空槽位)作为master节点加入原集群

bash
# 6387 就是作为master节点加入集群 # 6381 就是原来的集群节点里的领路人 redis-cli --cluster add-node 192.168.30.100:6387 192.168.30.100:6381

检查集群情况第1次

bash
redis-cli --cluster check 192.168.30.100:6381 # 可以看到集群新增了一台master节点 M: ff567323c8193045ef58f6ea350024e31d7f1d2f 192.168.30.100:6387 slots: (0 slots) master

重新分配槽号

bash
redis-cli --cluster reshard 192.168.30.100:6381 # 会提示需要迁移多少个槽位,迁移槽位个数计算一般是:16384/master个数,满足平均分配 How many slots do you want to move (from 1 to 16384)? 4096 # 接受slot槽节点的ID,也就是新加入的节点ID What is the receiving node ID? ff567323c8193045ef58f6ea350024e31d7f1d2f # 哪些节点需要导出,all是自动分配,done是手动分配,输入all自动分配即可 Please enter all the source node IDs. Type 'all' to use all the nodes as source nodes for the hash slots. Type 'done' once you entered all the source nodes IDs. Source node #1: all Do you want to proceed with the proposed reshard plan (yes/no)? yes

检查集群情况第2次

bash
redis-cli --cluster check 192.168.30.100:6381 # 可以查看6387节点已有槽位了 M: ff567323c8193045ef58f6ea350024e31d7f1d2f 192.168.30.100:6387 slots:[0-1364],[5461-6826],[10923-12287] (4096 slots) master

为主节点6387分配从节点6388,语法格式:redis-cli --cluster add-node ip:新slave端口 ip:新master端口 --cluster-slave --cluster-master-id 新master的ID

bash
redis-cli --cluster add-node 192.168.30.100:6388 192.168.30.100:6387 --cluster-slave --cluster-master-id ff567323c8193045ef58f6ea350024e31d7f1d2f

检查集群情况第3次

bash
redis-cli --cluster check 192.168.30.100:6381 # 现在可以看到6387和6388集群的主从关系了 S: 84b158f8c2e90a95a0d522a52bd3e458b2734caf 192.168.30.100:6388 slots: (0 slots) slave replicates ff567323c8193045ef58f6ea350024e31d7f1d2f M: ff567323c8193045ef58f6ea350024e31d7f1d2f 192.168.30.100:6387 slots:[0-1364],[5461-6826],[10923-12287] (4096 slots) master 1 additional replica(s)

主从缩容案例

检查集群情况第一次,获得6388的节点ID

bash
S: 84b158f8c2e90a95a0d522a52bd3e458b2734caf 192.168.30.100:6388 slots: (0 slots) slave replicates ff567323c8193045ef58f6ea350024e31d7f1d2f

将6388删除,从集群中将4号节点,命令:redis-cli --cluster del-node ip:从机端口 从机6388节点ID

bash
# 执行完命令,集群就没有这个6388节点了 redis-cli --cluster del-node 192.168.30.100:6388 84b158f8c2e90a95a0d522a52bd3e458b2734caf

将6387的槽位清空,重新分配,本例将清出来的槽号都给6381

bash
redis-cli --cluster reshard 192.168.30.100:6381 # 要分配多少个槽位 How many slots do you want to move (from 1 to 16384)? 4096 # 这里输入要接收槽位的节点ID What is the receiving node ID? 16d0bc12696d6a1d476b8bf107d30e44f42f087a Please enter all the source node IDs. Type 'all' to use all the nodes as source nodes for the hash slots. Type 'done' once you entered all the source nodes IDs. # 这里输入需要下掉的master节点ID Source node #1: ff567323c8193045ef58f6ea350024e31d7f1d2f Source node #2: done Do you want to proceed with the proposed reshard plan (yes/no)? yes

检查集群情况第二次

bash
redis-cli --cluster check 192.168.30.100:6381 # 发现6387没有槽位了 S: ff567323c8193045ef58f6ea350024e31d7f1d2f 192.168.30.100:6387 slots: (0 slots) slave replicates 16d0bc12696d6a1d476b8bf107d30e44f42f087a

将6387删除,命令:redis-cli --cluster del-node ip:从机端口 6387节点ID

bash
redis-cli --cluster del-node 192.168.30.100:6387 ff567323c8193045ef58f6ea350024e31d7f1d2f

本文作者:柯南

本文链接:

版权声明:©2024 柯南 All rights reserved.