分布式存储算法介绍

章节一:传统的 Hash 存储算法

1.1 传统的 Hash 存储算法简介

  将数据进行切片,对每份切片进行 Hash 取值,并对获取的 Hash 值除以存储节点的数量以取余,余数是多少就将此切片存在第几个 OSD 节点里,主要是 Swift 在使用。

1.2 传统的 Hash 存储算法的缺点

  如果要增加存或减少存储节点,需要对所有已存储数据切片的 Hash 值重新取余,大概 90% 的数据需要重新均衡数据(rebalance)。

章节二:一致性 Hash 算法

2.1 一致性 Hash 算法简介

  1) 给电脑也计算 Hash 值(可以是给电脑名计算 Hash 值,也可以给 IP 地址计算 Hash 值)
  2) 再给数据也计算 Hash 值,将数据存到比它的 Hash 值大,且与它的差值最小的电脑上,如果没有 Hash 值比它大的电脑就直接将数据存在 Hash 值最小的电脑上
  3) 整个架构类似一个环

2.2 一致性 Hash 算法的缺点

  1) 电脑太少时切换数据也会有较大的数据量,但是可以多设置几个虚拟节点,给以后新增加的节点使用,虚拟节点里的数据会影射到对应的物理节点里面去
  2) 电脑太少时,两台电脑的 Hash 值比较接近导致,数据分配极度不平均

(注意:在开始创建数据架构时,要评估未来数据的规模,如果最后要添加的电脑数量超过了虚拟节点数量,那么这个架构就不能使用了。此时只能备份数据,然后新建 1 个架构出来)

章节三:CRUSH

3.1 CRUSH 简介

  CRUSH(Controlled Replication Under Scalable Hashing)算法,在可扩展 Hash 算法下的可控制复制,主要是 Ceph 在使用。

3.2 CRUSH 算法

3.2.1 CRUSH 算法的第 1 层

  由 Ceph 的 OSD(Object Storage Deivces)组成。

3.2.2 CRUSH 算法的第 2 层
3.2.2.1 CRUSH 算法的第 2 层的组成

  由 Ceph 的 PG(Placement Group)归置组组成。

3.2.2.2 CRUSH 算法的第 2 层的由来

  在 OSD 节点上虚拟出多个 PG,每个 PG 默认会被指定对应 3 个 OSD 节点(每个 OSD 节点同时可以属于多个 PG),其中第一个 OSD 节点为主要(primary)的硬盘,其他两 OSD 节点为从(second)硬盘,PG 会对应几个 OSD 节点取决于 Ceph 的存储副本被设置了几份。

3.2.2.3 CRUSH 算法的第 2 层的算法

  1) 给每个 OSD 节点设置一个权重值,OSD 节点的容量越大则其权重值越大
  2) 主要(primary)硬盘的 OSD 节点:将 PG 的 ID 值和 OSD 的 ID 值组合在一起并计算 Hash 值,将得到的 Hash 值乘以此 OSD 节点的权重,当最终获得的值最大时,此 PG 就和此 OSD 绑定在一起
  3) 第 1 个从(second)硬盘的 OSD 节点:将 PG 的 ID 值逐一和 OSD 的 ID 值和 1 个随机的常数组合在一起并计算 Hash 值(这个值在 Ceph 的代码里被叫做 draw),将得到的 Hash 值乘以此 OSD 节点的权重,当最终获得的值最大时(这个值在 Ceph 的源代码里叫做 straw)则此 PG 就和此 OSD 绑定在一起
  4) 第 2 个从(second)硬盘的 OSD 节点:将 PG 的 ID 值逐一和 OSD 的 ID 值和上 1 个随机常数加 1 的和组合在一起并计算 Hash 值(这个值在 Ceph 的代码里被叫做 draw),将得到的 Hash 值乘以此 OSD 节点的权重,当最终获得的值最大时(这个值在 Ceph 的源代码里叫做 straw),则此 PG 就和此 OSD 绑定在一起(如果找到的 OSD 节点和前面的 OSD 节点重复,则将这个随机常数再加 1 并进行重复操作,最终获得和前面不通的 OSD 节点为止)
……

3.3 CRUSH 算法的第 3 层

3.3.1 CRUSH 算法的第 3 层的组成

  由池组成。

3.3.2 CRUSH 算法的第 3 层的由来

  1) 在 PG 上虚拟出多个池,每个池对应多个 PG,数据可以存储到指定的池里
  2) 总硬盘容量有多大,每个池最大可以使用的容量就有多大,但是如果如果 1 个池使用了一部分容量,其他的池就要少使用一部分容量

3.4 CRUSH 算法的第四层

3.4.1 CRUSH 算法的第四层的组成

  由数据组成。

3.4.2 CRUSH 算法的第四层的算法

  1) 对要放入某个池里的数据进行切片,默认每片 4M
  2) 对每份切片进行 Hash 取值,并对获取的 Hash 值除以这个池里 PG 节点的数量以取余,余数是多少就存在第几个 OSD 节点里

[内容] Ceph 介绍

内容一:Ceph 简介

Ceph 是一种分布式存储架构和技术。此项目是 2004 年由 Sage Weil 在加州大学 Santa Cruz 分校攻读博士期间的创建和研究的课题,并于 2006 年将其开源,同时成立 Inktank 公司专注 Ceph 的研发。2014 年 5 月 Inktank 公司被 Red Hat 收购。

内容二:Ceph 的特点

1) 高性能(硬盘越多性能越高,所有硬盘可以同时读写)
2) 高可用(硬盘越多高可用越高)

内容三:Ceph 使用的方式

1) 自己写程序:通过 C C++ Java Python Ruby PHP 等语言写程序调用 Ceph 底层存储 LIBRADOS,此方法性能最高
2) 自己写脚本:写对象脚本,通过 RGW(RADOSGW)对象存储网关的 Rest API 接口去访问 Ceph 的底层存储 LIBRADOS,此方法性能第二高
3) 挂载块存储:通过 Linux 内核或者 KVM 等虚拟机存储驱动访问 Ceph 的块存储,此方法性能第三高
4) 挂载文件系统:通过 Linux 内核(POSIX 命令)挂载 Ceph 的文件系统存储,此方法性能最弱

内容四:Ceph 的组成

1) OSD(Object Storage Deivces):负责存储、复制、恢复数据等,默认要有 3 台以上才能实现高可用,因为 Ceph 默认有三副本
2) MON(Monitor):负责监控集群状态制作和更新存储地图(map),供客户端从下载,在生产环境里必须要有 3 台以上,且最好是奇数台,因为必须遵循过半原则
3) MDS(Metadata Servers):实现文件系统存储,允许客户端通过 Linux 内核(POSIX 命令)挂载 Ceph 的文件系统存储
4) RGW(RADOSGW):实现对象存储网关,允许客户端通过 RGW(RADOSGW)对象存储网关的 Rest API 接口去访问 Ceph 的底层存储 LIBRADOS
5) 客户端:使用从 MON 下载和更新的存储地图,通过算法,直接从 OSD 访问数据

内容五:Ceph 架构

5.1 Ceph 使用架构

5.1.1 Ceph 的上层

自己写程序、自己写脚本、挂载块存储、挂载文件系统 4 种使用方式。

5.1.2 Ceph 的下层

RADOS,基于对象的存储(比我们平时所说的对象存储更原始,更底层),通过软件实现自我检查、自我备份和自我修复的功能。

5.2 Ceph 组成架构

                                  File

                  Cut1(Objects1) Cut2(Objects2) Cut3(Objects3)......

                              choice Pool

              Pool1                                   Pool2
     PG1                PG2                  PG2               PG3
OSD1 OSD2 OSD3    OSD2 OSD5 OSD3        OSD1 OSD4 OSD3    OSD4 OSD5 OSD3
Disk Disk Disk    Disk Disk Disk        Disk Disk Disk    Disk Disk Disk

内容六:Ceph 的算法:CRUSH

6.1 CRUSH 简介

CRUSH(Controlled Replication Under Scalable Hashing)算法,在可扩展 Hash 算法下的可控制复制

6.2 CRUSH 算法的第 1 层

由 OSD(Object Storage Deivces)组成。

6.3 CRUSH 算法的第 2 层

6.3.1 CRUSH 算法的第 2 层的组成

由 PG(Placement Group)归置组组成。

6.3.2 CRUSH 算法的第 2 层的由来

在 OSD 节点上虚拟出多个 PG,每个 PG 默认会被指定对应 3 个 OSD 节点(每个 OSD 节点同时可以属于多个 PG),其中第 1 个 OSD 节点为主要(primary)的硬盘,其他两 OSD 节点为从(second)硬盘,PG 会对应几个 OSD 节点取决于 Ceph 的存储副本被设置了几份。

6.3.3 CRUSH 算法的第 2 层的算法

1) 给每个 OSD 节点设置 1 个权重值,OSD 节点的容量越大则其权重值越大
2) 主要(primary)硬盘的 OSD 节点:将 PG 的 ID 值和 OSD 的 ID 值组合在一起并计算 Hash 值,将得到的 Hash 值乘以此 OSD 节点的权重,当最终获得的值最大时,此 PG 就和此 OSD 绑定在一起
3) 第 1 个从(second)硬盘的 OSD 节点:将 PG 的 ID 值逐一和 OSD 的 ID 值和 1 个随机的常数组合在一起并计算 Hash 值(这个值在 Ceph 的代码里被叫做 draw),将得到的 Hash 值乘以此 OSD 节点的权重,当最终获得的值最大时(这个值在 Ceph 的源代码里叫做 straw)则此 PG 就和此 OSD 绑定在一起
4) 第 2 个从(second)硬盘的 OSD 节点:将 PG 的 ID 值逐一和 OSD 的 ID 值和上一个随机常数加 1 的和组合在一起并计算 Hash 值(这个值在 Ceph 的代码里被叫做 draw),将得到的 Hash 值乘以此 OSD 节点的权重,当最终获得的值最大时(这个值在 Ceph 的源代码里叫做 straw),则此 PG 就和此 OSD 绑定在一起(如果找到的 OSD 节点和前面的 OSD 节点重复,则将这个随机常数再加 1 并进行重复操作,最终获得和前面不通的 OSD 节点为止)
5) 第 3 个从(second)硬盘的 OSD 节点:仿照第 2 个从(second)硬盘的 OSD 节点按此类方式以此类推

6.4 CRUSH 算法的第3层

6.4.1 CRUSH 算法的第3层的组成

由池组成。

6.4.2 CRUSH 算法的第3层的由来

1) 在 PG 上虚拟出多个池,每个池对应多个 PG,数据可以存储到指定的池里
2) 总硬盘容量有多大,每个池最大可以使用的容量就有多大,但是如果如果 1 个池使用了一部分容量,其他的池就要少使用一部分容量

6.5 CRUSH 算法的第四层

6.5.1 CRUSH 算法的第四层的组成

由数据组成。

6.5.2 CRUSH 算法的第四层的算法

1) 对要放入某个池里的数据进行切片,默认每片 4M
2) 对每份切片进行 Hash 取值,并对获取的 Hash 值除以这个池里 PG 节点的数量以取余,余数是多少就存在第几个 OSD 节点里

内容七:Ceph 的工作流程

1) 客户端从 MON 上下载最新的存储地图(map)
2) 存储地图(map)把集群里所有 MON、OSD 和 MDS 的信息告诉客户端,但是客户端依然不知道想要找的数据存放在哪
3) 客户端通过 CRUSH 计算出所需要读写的数据存放的 OSD 节点位置
4) 客户端直接在 OSD 节点位置上读写数据
5) 用户只需要把数据数据写入主要 OSD 节点硬盘上,然后 Ceph 自动同步给其他的从 OSD 节点硬盘上

内容八:Ceph 的维护

1) PG 的个数肯定要大于 OSD 节点的数量,在生产的环境中 PG 设计的数量往往会远远大于 OSD 节点的数量,以满足未来可能几年的需求,可能会在 3 个硬盘上添加上百个 PG
2) 当增加存或减少存储节点时,PG 的数量不会发生变化,只有 PG 对应 OSD 节点有变化的数据才会需要重新均衡数据(rebalance)的数据
3) 当增加存或减少 PG 数量时,就需要像传统的 Hash 存储算法那样,对所有已存储数据切片的 Hash 值重新取余,大概 90 % 的数据需要重新均衡数据(rebalance)

[实验] Nginx + Keepalived 网站服务负载均衡加高可用的实现

纪念:站主于 2021 年 2 月完成了此开源实验,并将过程中的所有命令经过整理和注释以后,形成以下教程

步骤一:拓扑图

1.1 服务器列表

client enp1s0: 172.16.1.99

proxy1 enp1s0: 172.16.0.101
enp7s0: 172.16.1.101
virtual IP: 172.16.1.100

proxy2 enp1s0: 172.16.0.102
enp7s0: 172.16.1.102

web1 enp1s0: 172.16.0.11

web2 enp1s0: 172.16.0.12

1.2 拓扑图

                      proxy1                                       web1
                      enp7s0:172.16.1.101 enp1s0:172.16.0.101      enp1s0:172.16.0.11
                      virtual IP:172.16.1.100
client
enp1s0:172.16.1.99
                      proxy2                                       web2
                      enp7s0:172.16.1.102 enp1s0:172.16.0.102      enp1s0:172.16.0.12

1.3 拓扑图简介

1) web1 安装 Nginx,web2 安装 Apache 实现网站服务
2) proxy1 和 proxy2 安装 Nginx 实现网站代理,轮询代理 web1、web2 上的网站服务实现负载均衡
3) 虚拟 IP 172.16.1.90 通过 Keepalived 默认放在 proxy1 的 enp7s0 网卡上,如果 proxy1 宕机或者检测到自己 Nginx 代理进程死掉,则虚拟 IP 172.16.1.90 则挂在 proxy2 的 enp7s0 网卡上实现高可用
4) 如果 web1 和 web2 中有一台服务器宕机,则 proxy1 和 proxy2 会自动不再向这台服务器请求网站服务,直到它恢复正常
5) 最终达到的效果是 client 向虚拟 IP 请求网站服务,此时如果 proxy1 正常就代表虚拟 IP 轮询调度 web1 和 web2 上的网站服务,再返回给 client。如果 proxy1 宕机则由 proxy2 代表虚拟 IP 完成次操作

步骤二: 系统环境要求

1) 所有服务器的系统都需要是 CentOS 8 版本
2) 所有服务器都要关闭防火墙
3) 所有服务器都要关闭 SELinux
4) 所有服务器系统都要配置好可用的软件源
5) 需要按照拓扑图给对应的服务器配置好 IP 地址和主机名
6) client 的 enp1s0 网卡、proxy1 的 enp7s0 网卡和 proxy2 的 enp7s0 网卡要可以相互 ping 通自己和对方的 IP
7) proxy1 的 enp1s0 网卡、proxy2 的 enp1s0 网卡、web1 的 enp1s0 网卡和 web2 的 enp1s0 网卡要可以相互 ping 通自己和对方的 IP 地址

步骤三:搭建网站服务

3.1 在 web1 上搭建网站服务

3.1.1 在 web1 上安装 Nginx

(只在 web1 上执行以下步骤)

# yum -y install nginx
3.1.2 给 web1 制定网页

(只在 web1 上执行以下步骤)

# echo web1 > /usr/share/nginx/html/index.html
3.1.3 启动 Nginx 并将它设置为开机自启

(只在 web1 上执行以下步骤)

# systemctl enable --now nginx

3.2 在 web2 上搭建网站服务

3.2.1 在 web2 上安装 Apache

(只在 web2 上执行以下步骤)

# yum -y install httpd
3.2.2 给 web2 制定网页

(只在 web2 上执行以下步骤)

# echo web2 > /var/www/html/index.html
3.2.3 启动 Apache 并将它设置为开机自启

(只在 web2 上执行以下步骤)

# systemctl enable --now httpd

步骤四:搭建代理服务

4.1 安装 Nginx

(分别在 proxy1 和 proxy2 上执行以下步骤)

# yum -y install nginx

4.2 修改 Nginx 配置文件

(分别在 proxy1 和 proxy2 上执行以下步骤)

# vi /etc/nginx/nginx.conf

将部分内容修改如下:

......
http {
    upstream webserver {
        server 172.16.0.11:80;
        server 172.16.0.12:80;
    }
......
    server {
        listen       80;

        location / {
        proxy_pass http://webserver;
        }
    }
......
}

4.3 启动 Nginx 并将它设置为开机自启

(分别在 proxy1 和 proxy2 上执行以下步骤)

# systemctl enable --now nginx

步骤五:搭建高可用服务

5.1 安装 Keepalived

(分别在 proxy1 和 proxy2 上执行以下步骤)

# yum -y install keepalived

5.2 创建 Keepalived 检查脚本

5.2.1 创建 Keepalived 检查脚本

(分别在 proxy1 和 proxy2 上执行以下步骤)

# vi /etc/keepalived/nginx_check.sh

创建以下内容:

#!/bin/bash

if [ `ps -C nginx --no-header | wc -l` -eq 0 ];then
    systemctl stop nginx
    sleep 5
    if [ `ps -C nginx --no-header | wc -l` -eq 0 ];then
        killall keepalived
    fi
fi

(补充:这里以检测 Nginx 没启动就启动 Nginx,5 秒后 Nginx 要是还没有启动就关闭 keepalived 为例)

5.2.2 给 Keepalived 检查脚本执行权限

(分别在 proxy1 和 proxy2 上执行以下步骤)

# chmod u+x /etc/keepalived/nginx_check.sh

5.3 修改 proxy1 上的 Keepalived 配置文件

(只在 proxy1 上执行以下步骤)

# vim /etc/keepalived/keepalived.conf

将全部内容修改如下:

! Configuration File for keepalived

global_defs {
   notification_email {
     acassen@firewall.loc
     failover@firewall.loc
     sysadmin@firewall.loc
   }
   notification_email_from Alexandre.Cassen@firewall.loc
   smtp_server 192.168.200.1
   smtp_connect_timeout 30
   router_id proxy1
   vrrp_skip_check_adv_addr
   vrrp_strict
   vrrp_garp_interval 0
   vrrp_gna_interval 0
}

vrrp_script chk_nginx {
    script "/etc/keepalived/nginx_check.sh"
    interval 2
    weight 20
}

vrrp_instance VI_1 {
    state MASTER
    interface enp7s0
    virtual_router_id 90
    priority 101
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass 1111
    }
    track_script {
    chk_nginx
    }
    virtual_ipaddress {
        172.16.1.100
    }
}


补充:
1) script “/etc/keepalived/nginx_check.sh” 代表使用的检测脚本是 /etc/keepalived/nginx_check.sh
2) interface enp7s0 代表虚拟 IP 将挂载在 enp7s0 网卡上
3) priority 代表修建级是 101,数字越大优先级越高
4) 172.16.1.100 代表虚拟 IP 是 172.16.1.100

5.4 修改 proxy2 上的 Keepalived 配置文件

(只在 proxy2 上执行以下步骤)

# vim /etc/keepalived/keepalived.conf

将全部内容修改如下:

! Configuration File for keepalived

global_defs {
   notification_email {
     acassen@firewall.loc
     failover@firewall.loc
     sysadmin@firewall.loc
   }
   notification_email_from Alexandre.Cassen@firewall.loc
   smtp_server 192.168.200.1
   smtp_connect_timeout 30
   router_id proxy1
   vrrp_skip_check_adv_addr
   vrrp_strict
   vrrp_garp_interval 0
   vrrp_gna_interval 0
}

vrrp_script chk_nginx {
     script "/etc/keepalived/nginx_check.sh"
     interval 2
     weight 20
}

vrrp_instance VI_1 {
    state BACKUP
    interface enp7s0
    virtual_router_id 90
    priority 99
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass 1111
    }
    track_script {
    chk_nginx
    }
    virtual_ipaddress {
        172.16.1.100
    }
}


补充:
1) script “/etc/keepalived/nginx_check.sh” 代表使用的检测脚本是 /etc/keepalived/nginx_check.sh
2) interface enp7s0 代表虚拟 IP 将挂载在 enp7s0 网卡上
3) priority 代表修建级是 99,数字越大优先级越高
4) 172.16.1.100 代表虚拟 IP 是 172.16.1.100

5.5 启动 Keepalived 并将它设置为开机自启

(分别在 proxy1 和 proxy2 上执行以下步骤)

# systemctl enable --now keepalived.service

步骤六:测试网站负载均衡加高可用

6.1 正常情况下测试网站服务

(只在 client 上执行以下步骤)

# curl 172.16.1.100

(补充:重复以上命令会发现重复显示 web1 和 web2)

6.2 在单节点故障的情况下测试网站服务

6.2.1 关闭 proxy1、proxy2、web1、web2 中的任意一台服务器

(只在 proxy1、proxy2、web1、web2 中的任意一台服务器上执行以下步骤)

# poweroff
6.2.2 测试网站服务

(只在 client 上执行以下步骤)

# curl 172.16.1.100

(补充:重复以上命令会发现重复显示 web1 和 web2)

[实验] HAproxy 代理的设置

纪念:站主于 2021 年 2 月完成了此开源实验,并将过程中的所有命令经过整理和主是以后,形成以下教程

步骤一:拓扑规划

1.1 服务器列表

client eth0:10.0.0.10/24
proxy eth0:10.0.0.5/24
eth1:10.0.1.5/24
web1 eth1:10.0.1.100/24
web2 eht1:10.0.1.200/24

1.2 拓扑图

                                           web1
                                           eth1:10.0.1.100/24
     client                proxy     
eth0:10.0.0.10/24    eth0:10.0.0.5/24  
                     eth1:10.0.1.5/24
                                           web2
                                           eht2:10.0.1.200/24

1.3 拓扑图简介

(1)client 向 proxy 的 eth0:10.0.0.5/24 发送 web 请求
(2)proxy 收到 web 请求后通过 eth1:10.0.1.5/24 将请求发往 web1 或 web2
(3)web1 或 web2 回应 web 请求,并通过 proxy 返回给 client
(4)最终实现单点代理器,双网站热备份

步骤二: 系统环境要求

(1)所有服务器的系统都需要是 CentOS 8 版本
(2)所有服务器都要关闭防火墙
(3)所有服务器都要关闭 SELinux
(4)所有服务器系统都要配置好可用的软件源
(5)需要按照拓扑图给对应的服务器配置好 IP 地址和主机名
(6)所有服务器都要可以相互 ping 通自己和对方的 IP 地址

步骤三:部署集群环境

3.1 在 proxy 上安装 HAporxy

(只在 proxy 上执行以下步骤)

# yum -y install haproxy

3.2 在 web1 上安装 web 服务

3.2.1 安装 httpd

(只在 web1 上执行以下步骤)

# yum -y install httpd
3.2.2 创建网页文件

(只在 web1 上执行以下步骤)

# echo "10.0.1.100" > /var/www/html/index.html
3.2.3 启动 web 服务并设置为开机自启

(只在 web1 上执行以下步骤)

# systemctl start httpd ; systemctl enable httpd

3.3 在 web2 上安装 web 服务

3.3.1 安装 httpd

(只在 web2 上执行以下步骤)

# yum -y install httpd
3.3.2 创建网页文件

(只在 web2 上执行以下步骤)

# echo "10.0.1.200" > /var/www/html/index.html
3.3.3 启动 web 服务并设置为开机自启

(只在 web2 上执行以下步骤)

# systemctl start httpd ; systemctl enable httpd

步骤四:配置 HAproxy 实现 web 负载均衡代理集群

4.1 开启 proxy 的路由转发

4.1.1 在 sysctl.conf 文件里添加路由转发功能

(只在 proxy 上执行以下步骤)

# vim /etc/sysctl.conf

添加以下内容:

net.ipv4.ip_forward = 1
4.1.2 使刚刚添加的功能生效

(只在 proxy 上执行以下步骤)

# sysctl -p

4.2 修改 proxy 上的 HAproxy 配置文件

(只在 proxy 上执行以下步骤)

# vim /etc/haproxy/haproxy.cfg

将全部内容修改如下:

#---------------------------------------------------------------------
# Example configuration for a possible web application.  See the
# full configuration options online.
#
#   https://www.haproxy.org/download/1.8/doc/configuration.txt
#
#---------------------------------------------------------------------

#---------------------------------------------------------------------
# Global settings
#---------------------------------------------------------------------
global
    # to have these messages end up in /var/log/haproxy.log you will
    # need to:
    #
    # 1) configure syslog to accept network log events.  This is done
    #    by adding the '-r' option to the SYSLOGD_OPTIONS in
    #    /etc/sysconfig/syslog
    #
    # 2) configure local2 events to go to the /var/log/haproxy.log
    #   file. A line like the following can be added to
    #   /etc/sysconfig/syslog
    #
    #    local2.*                       /var/log/haproxy.log
    #
    log         127.0.0.1 local2

    chroot      /var/lib/haproxy
    pidfile     /var/run/haproxy.pid
    maxconn     4000
    user        haproxy
    group       haproxy
    daemon

    # turn on stats unix socket
    stats socket /var/lib/haproxy/stats

    # utilize system-wide crypto-policies
    ssl-default-bind-ciphers PROFILE=SYSTEM
    ssl-default-server-ciphers PROFILE=SYSTEM

#---------------------------------------------------------------------
# common defaults that all the 'listen' and 'backend' sections will
# use if not designated in their block
#---------------------------------------------------------------------
defaults
    mode                    http
    log                     global
    option                  httplog
    option                  dontlognull
    option http-server-close
    option forwardfor       except 127.0.0.0/8
    option                  redispatch
    retries                 3
    timeout http-request    10s
    timeout queue           1m
    timeout connect         10s
    timeout client          1m
    timeout server          1m
    timeout http-keep-alive 10s
    timeout check           10s
    maxconn                 3000

#---------------------------------------------------------------------
# main frontend which proxys to the backends
#---------------------------------------------------------------------
frontend main
    bind *:80
    acl url_static       path_beg       -i /static /images /javascript /stylesheets
    acl url_static       path_end       -i .jpg .gif .png .css .js

    use_backend static          if url_static
    default_backend             app

#---------------------------------------------------------------------
# static backend for serving up images, stylesheets and such
#---------------------------------------------------------------------
backend static
    balance     roundrobin
    server      static 127.0.0.1:80 check

#---------------------------------------------------------------------
# round robin balancing between the various backends
#---------------------------------------------------------------------
backend app
    balance     roundrobin
    server  app1 10.0.1.100:80 check
    server  app2 10.0.1.200:80 check

4.3 设置开机自动启动 HAproxy

(只在 proxy 上执行以下步骤)

# systemctl start haproxy ; systemctl enable haproxy

步骤五:测试 Haproxy 代理集群

(只在 client 上执行以下步骤)

# curl 10.10.10.5

(注意:这一步需要多做几次)

[实验] Redis 数据库集群 Redis 数据库的添加和删除

纪念:站主于 2019 年 11 月完成了此开源实验,并将过程中的所有命令经过整理和注释以后,形成以下教程

注意:

在给 Redis 数据库集群添加和删除 Redis 数据库之前要先搭建 Redis 数据库集群

软件准备:

在 Redis 的官网上下载软件 Redis:

https://redis.io/

在 rubygems 的官网上下载软件 rubygems

https://rubygems.org

正文:

步骤一:规划拓扑

1.1 服务器列表

现有的 Redis 集群
redis7 IP 地址:192.168.1.57 端口号:1057
redis8 IP 地址:192.168.1.58 端口号:1058

(补充:在本次实验中现有的 redis 集群管理服务器是 redis1,IP 地址是 192.168.1.57,端口号是 1057)

1.2 服务器列表简介

redis7 作为主库 redis8 作为从库加入到一个现有的 Redis 集群中

步骤二:系统环境要求

1) 所有服务器的系统都需要是 CentOS 7 版本
2) 所有服务器都要关闭防火墙
3) 所有服务器都要关闭 SELinux
4) 所有服务器系统都要配置好可用的软件源
5) 需要按照拓扑图给对应的服务器配置好 IP 地址和主机名
6) 所有服务器都要可以相互 ping 通自己和对方的 IP 地址和主机名

(注意:现有的 Redis 集群因为已经是创建好了的,所以不用执行以上操作)

步骤三:所有数据库服务器安装 Redis 数据库

3.1 安装 Redis 数据库的相关依赖包

(分别在 redis7 和 redis8 上执行以下步骤)

# yum -y install gcc gcc-c++ make

3.2 安装 Redis 数据库

3.2.1 解压安装包

(分别在 redis7 和 redis8 上执行以下步骤)

# tar -zxf redis-5.0.5.tar.gz

(补充:这里要安装的 Redis 版本是 5.0.5)

3.2.2 进入安装包目录

(分别在 redis7 和 redis8 上执行以下步骤)

# cd redis-5.0.5/

(补充:这里要安装的 Redis 版本是 5.0.5)

3.2.3 编译安装包

(分别在 redis7 和 redis8 上执行以下步骤)

# make
3.2.4 安装软件包

(分别在 redis7 和 redis8 上执行以下步骤)

# make install
3.2.5 进入配置目录

(分别在 redis7 和 redis8 上执行以下步骤)

# cd utils/
3.2.6 安装软件包

(分别在 redis7 和 redis8 上执行以下步骤)

# ./install_server.sh
Welcome to the redis service installer
This script will help you easily set up a running redis server
Please select the redis port for this instance: [6379] 
Selecting default: 6379
Please select the redis config file name [/etc/redis/6379.conf] 
Selected default - /etc/redis/6379.conf
Please select the redis log file name [/var/log/redis_6379.log] 
Selected default - /var/log/redis_6379.log
Please select the data directory for this instance [/var/lib/redis/6379] 
Selected default - /var/lib/redis/6379
Please select the redis executable path [/usr/local/bin/redis-server] 
Selected config:
Port           : 6379
Config file    : /etc/redis/6379.conf
Log file       : /var/log/redis_6379.log
Data dir       : /var/lib/redis/6379
Executable     : /usr/local/bin/redis-server
Cli Executable : /usr/local/bin/redis-cli
Is this ok? Then press ENTER to go on or Ctrl-C to abort.
Copied /tmp/6379.conf => /etc/init.d/redis_6379
Installing service...
Successfully added to chkconfig!
Successfully added to runlevels 345!
Starting Redis server...
Installation successful!

步骤四:将 Redis 数据库添加到别的集群

4.1 修改所有服务器上的 Redis 数据库配置文件

(只在 redis7 上执行以下步骤)

# vim /etc/redis/6379.conf

将部分内容修改如下:

......
#bind 127.0.0.1
bind 192.168.1.57
......
port 1057
......
daemonize yes
......
pidfile /var/run/redis_1057.pid
......
cluster-enabled yes
......
cluster-config-file nodes-1057.conf
......
cluster-node-timeout 5000
......


补充:
1) 这里的 #bind 127.0.0.1 代表取消数据库可以被本地登录
2) 这里的 bind 192.168.1.57 是本机的 IP 地址
3) 这里的 port 1057 代表数据库使用到的端口是 1057,集群里的各个数据库端口号不能一样
4) 这里的 daemonize yes 代表以进程的形式启动
5) 这里的 pidfile /var/run/redis_1057.pid 代表使用的 PID 文件是 /var/run/redis_1057.pid,集群里的各个数据库 PID 文件不能一样
6) 这里的 cluster-enabled yes 代表启用集群,但是前面的 daemonize 必须也启用
7) 这里的 cluster-config-file nodes-1057.conf 代表使用的数据库配置文件是 nodes-1057.conf,集群里的各个数据库的配置文件不能一样
8) 这里的 cluster-node-timeout 5000 代表集群通信超时时间为 5000

(只在 redis8 上执行以下步骤)

# vim /etc/redis/6379.conf

将部分内容修改如下:

......
#bind 127.0.0.1
bind 192.168.1.58
......
port 1058
......
daemonize yes
......
pidfile /var/run/redis_1058.pid
......
cluster-enabled yes
......
cluster-config-file nodes-1058.conf
......
cluster-node-timeout 5000
......


补充:
1) 这里的 #bind 127.0.0.1 代表取消数据库可以被本地登录
2) 这里的 bind 192.168.1.58 是本机的 IP 地址
3) 这里的 port 1058 代表数据库使用到的端口是 1058,集群里的各个数据库端口号不能一样
4) 这里的 daemonize yes 代表以进程的形式启动
5) 这里的 pidfile /var/run/redis_1058.pid 代表使用的 PID 文件是 /var/run/redis_1058.pid,集群里的各个数据库 PID 文件不能一样
6) 这里的 cluster-enabled yes 代表启用集群,但是前面的 daemonize 必须也启用
7) 这里的 cluster-config-file nodes-1058.conf 代表使用的数据库配置文件是 nodes-1058.conf,集群里的各个数据库的配置文件不能一样
8) 这里的 cluster-node-timeout 5000 代表集群通信超时时间为 5000

4.2 重启所有服务器上的 Redis 数据库

4.2.1 关闭 Redis 数据库

(分别在 redis7 和 redis8 上执行以下步骤)

# redis-cli shutdown
4.2.2 开启 Redis 数据库

(分别在 redis7 和 redis8 上执行以下步骤)

# /etc/init.d/redis_6379 start

4.3 将 redis7 和 redis8 添加到现有的 Redis 集群中

4.3.1 显示现有集群的状况

(只在 redis1 上执行以下步骤)

# redis-cli --cluster check 192.168.1.51:1051
4.3.2 添加 redis7 并将其视为主数据库

(只在 redis1 上执行以下步骤)

# redis-cli --cluster add-node 192.168.1.57:1057 192.168.1.51:1051
4.3.3 添加 redis8 并将其视为从数据库

(只在 redis1 上执行以下步骤)

# redis-cli --cluster add-node 192.168.1.58:1058 192.168.1.51:1051 --cluster-slave
4.3.4 确认 redis7 和 redis8 已经加入到了集群中

(只在 redis1 上执行以下步骤)

# redis-cli --cluster check 192.168.1.51:1051
192.168.1.51:1051 (5d030ec0...) -> 1 keys | 5461 slots | 1 slaves.
192.168.1.53:1053 (c4f884e7...) -> 2 keys | 5461 slots | 1 slaves.
192.168.1.52:1052 (7477c04d...) -> 1 keys | 5462 slots | 1 slaves.
192.168.1.57:1057 (10bb6a57...) -> 0 keys | 0 slots | 1 slaves.
[OK] 4 keys in 4 masters.
0.00 keys per slot on average.
>>> Performing Cluster Check (using node 192.168.1.51:1051)
M: 5d030ec05f9de86ebeedc1b035b2122addaa61d8 192.168.1.51:1051
   slots:[0-5460] (5461 slots) master
   1 additional replica(s)
S: eac6a0586ad00375bea9aa352951c784be57e9ad 192.168.1.55:1055
   slots: (0 slots) slave
   replicates 5d030ec05f9de86ebeedc1b035b2122addaa61d8
S: 93d8988475c754a3b58d5172522163664c391da2 192.168.1.58:1058
   slots: (0 slots) slave
   replicates 10bb6a5732f629ee62801417cb44ddb670e99e86
S: a5cddda6c1bc7c6d3397e17e1ba29571bb7a1657 192.168.1.54:1054
   slots: (0 slots) slave
   replicates c4f884e7e4ce6adb4f5bc4f6eb398680beb26089
M: c4f884e7e4ce6adb4f5bc4f6eb398680beb26089 192.168.1.53:1053
   slots:[10923-16383] (5461 slots) master
   1 additional replica(s)
M: 7477c04d8ebf9d498ed5586d5f4e6d513fdb3c30 192.168.1.52:1052
   slots:[5461-10922] (5462 slots) master
   1 additional replica(s)
M: 10bb6a5732f629ee62801417cb44ddb670e99e86 192.168.1.57:1057
   slots: (0 slots) master
   1 additional replica(s)
S: fd973bbcc376bfccf5888ba06dba97feb9ef1273 192.168.1.56:1056
   slots: (0 slots) slave
   replicates 7477c04d8ebf9d498ed5586d5f4e6d513fdb3c30
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.

4.4 让新加入的 redis 数据库也能存储数据

4.4.1 重新分配集群的存储块

(只在 redis1 上执行以下步骤)

# redis-cli --cluster reshard 192.168.1.51:1051
......
How many slots do you want to move (from 1 to 16384)? 4096
What is the receiving node ID? 10bb6a5732f629ee62801417cb44ddb670e99e86
......
Source node #1: all
......
Do you want to proceed with the proposed reshard plan (yes/no)? yes
......
4.4.2 确认集群的存储块已经覆盖所有主数据库

(只在 redis1 上执行以下步骤)

# redis-cli --cluster check 192.168.1.51:1051
192.168.1.51:1051 (5d030ec0...) -> 0 keys | 4096 slots | 1 slaves.
192.168.1.53:1053 (c4f884e7...) -> 1 keys | 4096 slots | 1 slaves.
192.168.1.52:1052 (7477c04d...) -> 1 keys | 4096 slots | 1 slaves.
192.168.1.57:1057 (10bb6a57...) -> 2 keys | 4096 slots | 1 slaves.
[OK] 4 keys in 4 masters.
0.00 keys per slot on average.
>>> Performing Cluster Check (using node 192.168.1.51:1051)
M: 5d030ec05f9de86ebeedc1b035b2122addaa61d8 192.168.1.51:1051
   slots:[1365-5460] (4096 slots) master
   1 additional replica(s)
S: eac6a0586ad00375bea9aa352951c784be57e9ad 192.168.1.55:1055
   slots: (0 slots) slave
   replicates 5d030ec05f9de86ebeedc1b035b2122addaa61d8
S: 93d8988475c754a3b58d5172522163664c391da2 192.168.1.58:1058
   slots: (0 slots) slave
   replicates 10bb6a5732f629ee62801417cb44ddb670e99e86
S: a5cddda6c1bc7c6d3397e17e1ba29571bb7a1657 192.168.1.54:1054
   slots: (0 slots) slave
   replicates c4f884e7e4ce6adb4f5bc4f6eb398680beb26089
M: c4f884e7e4ce6adb4f5bc4f6eb398680beb26089 192.168.1.53:1053
   slots:[12288-16383] (4096 slots) master
   1 additional replica(s)
M: 7477c04d8ebf9d498ed5586d5f4e6d513fdb3c30 192.168.1.52:1052
   slots:[6827-10922] (4096 slots) master
   1 additional replica(s)
M: 10bb6a5732f629ee62801417cb44ddb670e99e86 192.168.1.57:1057
   slots:[0-1364],[5461-6826],[10923-12287] (4096 slots) master
   1 additional replica(s)
S: fd973bbcc376bfccf5888ba06dba97feb9ef1273 192.168.1.56:1056
   slots: (0 slots) slave
   replicates 7477c04d8ebf9d498ed5586d5f4e6d513fdb3c30
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.

步骤五:将部分 Redis 数据库从集群中删除

(这里以删除 redis7 和 redis8 为例)

5.1 将存储块从要被删除的 redis 主数据库里拿走

(只在 redis1 上执行以下步骤)

# redis-cli --cluster reshard 192.168.1.51:1051
......
How many slots do you want to move (from 1 to 16384)? 4096
What is the receiving node ID? 5d030ec05f9de86ebeedc1b035b2122addaa61d8
......
Source node #1: 10bb6a5732f629ee62801417cb44ddb670e99e86
Source node #2: done
......
Do you want to proceed with the proposed reshard plan (yes/no)? yes
......

5.2 将部分 Redis 数据库从集群中删除

5.2.1 将作为主库的 Redis 数据库从集群中删除

(只在 redis1 上执行以下步骤)

# redis-cli --cluster del-node 192.168.1.57:1057 10bb6a5732f629ee62801417cb44ddb670e99e86
>>> Removing node 10bb6a5732f629ee62801417cb44ddb670e99e86 from cluster 192.168.1.57:1057
>>> Sending CLUSTER FORGET messages to the cluster...
5.2.2 将作为从库的 Redis 数据库从集群中删除

(只在 redis1 上执行以下步骤)

# redis-cli --cluster del-node 192.168.1.58:1058 023abbc600cd4fb1ca8bb7ce8c45099e186041f8
>>> Removing node 023abbc600cd4fb1ca8bb7ce8c45099e186041f8 from cluster 192.168.1.58:1058
>>> Sending CLUSTER FORGET messages to the cluster...
>>> SHUTDOWN the node.

5.3 确认部分 Redis 数据库已经从集群中删除

# redis-cli --cluster check 192.168.1.51:1051
192.168.1.51:1051 (5d030ec0...) -> 0 keys | 4096 slots | 1 slaves.
192.168.1.53:1053 (c4f884e7...) -> 1 keys | 4096 slots | 1 slaves.
192.168.1.52:1052 (7477c04d...) -> 1 keys | 4096 slots | 1 slaves.
[OK] 4 keys in 3 masters.
0.00 keys per slot on average.
>>> Performing Cluster Check (using node 192.168.1.51:1051)
M: 5d030ec05f9de86ebeedc1b035b2122addaa61d8 192.168.1.51:1051
   slots:[0-6826],[10923-12287] (4096 slots) master
   1 additional replica(s)
S: eac6a0586ad00375bea9aa352951c784be57e9ad 192.168.1.55:1055
   slots: (0 slots) slave
   replicates 5d030ec05f9de86ebeedc1b035b2122addaa61d8
   replicates 10bb6a5732f629ee62801417cb44ddb670e99e86
S: a5cddda6c1bc7c6d3397e17e1ba29571bb7a1657 192.168.1.54:1054
   slots: (0 slots) slave
   replicates c4f884e7e4ce6adb4f5bc4f6eb398680beb26089
M: c4f884e7e4ce6adb4f5bc4f6eb398680beb26089 192.168.1.53:1053
   slots:[12288-16383] (4096 slots) master
   1 additional replica(s)
M: 7477c04d8ebf9d498ed5586d5f4e6d513fdb3c30 192.168.1.52:1052
   slots:[6827-10922] (4096 slots) master
   1 additional replica(s)
S: fd973bbcc376bfccf5888ba06dba97feb9ef1273 192.168.1.56:1056
   slots: (0 slots) slave
   replicates 7477c04d8ebf9d498ed5586d5f4e6d513fdb3c30
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.