代码改变世界

Redis集群高可用

2020-08-14 12:51  ☆野生架构师☆  阅读(289)  评论(0编辑  收藏  举报

简介

  Redis 是一个开源的使用 ANSI C 语言编写、遵守 BSD 协议、支持网络、可基于内存亦可持久化的日志型、Key-Value 数据库,并提供多种语言的 API的非关系型数据库。

  Redis作为基于键值对的NoSQL数据库,具有高性能、丰富的数据结构、持久化、高可用、分布式等特性,同时Redis本身非常稳定,已经得到业界的广泛认可和使用。  

  Github 源码:https://github.com/antirez/redis

  Redis 官网:https://redis.io/

  目前很多项目都在使用它,我的项目基本都在用,另外6.0版本开始支持多线程,大伙可以研究下。

  Redis支持的数据类型?   

    1、String字符串:

      格式: set key value

      string类型是二进制安全的。意思是redis的string可以包含任何数据。比如jpg图片或者序列化的对象 。

      string类型是Redis最基本的数据类型,一个键最大能存储512MB。 

    2、Hash(哈希)

      格式: hmset name  key1 value1 key2 value2

      Redis hash 是一个键值(key=>value)对集合。

      Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象。 

    3、List(列表)

      Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)

      格式: lpush  name  value

      在 key 对应 list 的头部添加字符串元素

      格式: rpush  name  value

      在 key 对应 list 的尾部添加字符串元素

      格式: lrem name  index

      key 对应 list 中删除 count 个和 value 相同的元素

      格式: llen name  

      返回 key 对应 list 的长度 

    4、Set(集合)

      格式: sadd  name  value

      Redis的Set是string类型的无序集合。

      集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。 

    5、zset(sorted set:有序集合)

      格式: zadd  name score value

      Redis zset 和 set 一样也是string类型元素的集合,且不允许重复的成员。

      不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。

      zset的成员是唯一的,但分数(score)却可以重复。

  在Redis中,有两种持久化方式

    1、RDB:

      rdb是Redis DataBase缩写

      功能核心函数rdbSave(生成RDB文件)和rdbLoad(从文件加载内存)两个函数

    2、AOF:

      Aof是Append-only file缩写      

      每当执行服务器(定时)任务或者函数时flushAppendOnlyFile 函数都会被调用, 这个函数执行以下两个工作

      AOF写入保存:

        WRITE:根据条件,将 aof_buf 中的缓存写入到 AOF 文件

        SAVE:根据条件,调用 fsync 或 fdatasync 函数,将 AOF 文件保存到磁盘中。  

    存储:

        内容是redis通讯协议(RESP )格式的命令文本存储。

    比较:

      1、aof文件比rdb更新频率高,优先使用aof还原数据。

      2、aof比rdb更安全也更大

      3、rdb性能比aof好

      4、如果两个都配了优先加载AOF

集群方案

    1、主从复制

      同Mysql主从复制的原因一样,Redis虽然读取写入的速度都特别快,但是也会产生读压力特别大的情况。为了分担读压力,Redis支持主从复制,读写分离。一个Master可以有多个Slaves。

      优点

        1、数据备份

        2、读写分离,提高服务器性能

      缺点

        1、不能自动故障恢复,RedisHA系统(需要开发)

        2、无法实现动态扩容

    2、哨兵机制      

      Redis Sentinel是社区版本推出的原生高可用解决方案,其部署架构主要包括两部分:Redis Sentinel集群和Redis数据集群。

      其中Redis Sentinel集群是由若干Sentinel节点组成的分布式集群,可以实现故障发现、故障自动转移、配置中心和客户端通知。Redis Sentinel的节点数量要满足2n+1(n>=1)的奇数个。 

     

      优点

        1、自动化故障恢复

      缺点

        1、Redis 数据节点中 slave 节点作为备份节点不提供服务

        2、无法实现动态扩容

    3、Cluster      

      Redis Cluster是社区版推出的Redis分布式集群解决方案,主要解决Redis分布式方面的需求,比如,当遇到单机内存,并发和流量等瓶颈的时候,Redis Cluster能起到很好的负载均衡的目的。

      Redis Cluster着眼于提高并发量。

      群集至少需要3主3从,且每个实例使用不同的配置文件。

      在redis-cluster架构中,redis-master节点一般用于接收读写,而redis-slave节点则一般只用于备份, 其与对应的master拥有相同的slot集合,若某个redis-master意外失效,则再将其对应的slave进行升级为临时redis-master。

      在redis的官方文档中,对redis-cluster架构上,有这样的说明:在cluster架构下,默认的,一般redis-master用于接收读写,而redis-slave则用于备份,当有请求是在向slave发起时,会直接重定向到对应key所在的master来处理。 但如果不介意读取的是redis-cluster中有可能过期的数据并且对写请求不感兴趣时,则亦可通过readonly命令,将slave设置成可读,然后通过slave获取相关的key,达到读写分离。具体可以参阅redis官方文档等相关内容

      优点

        1、解决分布式负载均衡的问题。具体解决方案是分片/虚拟槽slot。

        2、可实现动态扩容

        3、P2P模式,无中心化

      缺点

        1、为了性能提升,客户端需要缓存路由表信息

        2、Slave在集群中充当“冷备”,不能缓解读压力

环境

  此次搭建一个6节点的Redis集群,包括3个主节点和3个从节点。

部署

  之前我在通过docker安装redis集群的时候,一步一步的来安装,但是docker虽然简化了一些繁琐的步骤,但不可避免的还是有一些步骤需要自己手动写命令,太老火了,所以我就在网上找了一个shell脚本,通过一个命令即可完成redis集群的部署。

#!/bin/bash

# 创建redis挂载目录
echo "step 1 -> 创建redis安装位置------"
mkdir -p /data/redis-cluster
cd /data/redis-cluster

echo "step 2 -> 创建redis-cluster.tmpl模板------"
cat <<'EOF'> redis-cluster.tmpl
port ${PORT}
protected-mode no
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
cluster-announce-ip ${CLUSTER_ANNOUNCE_IP}
cluster-announce-port ${PORT}
cluster-announce-bus-port 1${PORT}
EOF

#for port in `seq 6391 6396`; do \
#firewall-cmd --zone=public --add-port=${port}/tcp --permanent
#done

echo "step 3 -> 创建redis数据配置挂载目录------"

CLUSTER_ANNOUNCE_IP=192.168.1.30
echo ${CLUSTER_ANNOUNCE_IP}

for port in `seq 6391 6396`; do \
mkdir -p ./${port}/conf \
&& PORT=${port} CLUSTER_ANNOUNCE_IP=${CLUSTER_ANNOUNCE_IP}  envsubst < ./redis-cluster.tmpl > ./${port}/conf/redis.conf \
&& mkdir -p ./${port}/data; \
done

echo "step 4 -> 创建redis docker-compose.yaml 模板------"
cat <<EOF > docker-compose.yaml
version: '3'
services:
  redis-6391:
    image: redis:latest
    container_name: redis-6391
    command: redis-server /usr/local/etc/redis/redis.conf --appendonly yes
    privileged: true
    restart: always
    volumes:
      - /data/redis-cluster/6391/data:/data
      - /data/redis-cluster/6391/conf/redis.conf:/usr/local/etc/redis/redis.conf
    ports:
      - 6391:6391
      - 16391:16391
  redis-6392:
    image: redis:latest
    container_name: redis-6392
    command: redis-server /usr/local/etc/redis/redis.conf --appendonly yes
    privileged: true
    restart: always
    volumes:
      - /data/redis-cluster/6392/data:/data
      - /data/redis-cluster/6392/conf/redis.conf:/usr/local/etc/redis/redis.conf
    ports:
      - 6392:6392
      - 16392:16392
  redis-6393:
    image: redis:latest
    container_name: redis-6393
    command: redis-server /usr/local/etc/redis/redis.conf --appendonly yes
    privileged: true
    restart: always
    volumes:
      - /data/redis-cluster/6393/data:/data
      - /data/redis-cluster/6393/conf/redis.conf:/usr/local/etc/redis/redis.conf
    ports:
      - 6393:6393
      - 16393:16393
  redis-6394:
    image: redis:latest
    container_name: redis-6394
    command: redis-server /usr/local/etc/redis/redis.conf --appendonly yes
    privileged: true
    restart: always
    volumes:
      - /data/redis-cluster/6394/data:/data
      - /data/redis-cluster/6394/conf/redis.conf:/usr/local/etc/redis/redis.conf
    ports:
      - 6394:6394
      - 16394:16394
  redis-6395:
    image: redis:latest
    container_name: redis-6395
    command: redis-server /usr/local/etc/redis/redis.conf --appendonly yes
    privileged: true
    restart: always
    volumes:
      - /data/redis-cluster/6395/data:/data
      - /data/redis-cluster/6395/conf/redis.conf:/usr/local/etc/redis/redis.conf
    ports:
      - 6395:6395
      - 16395:16395
  redis-6396:
    image: redis:latest
    container_name: redis-6396
    command: redis-server /usr/local/etc/redis/redis.conf --appendonly yes
    privileged: true
    restart: always
    volumes:
      - /data/redis-cluster/6396/data:/data
      - /data/redis-cluster/6396/conf/redis.conf:/usr/local/etc/redis/redis.conf
    ports:
      - 6396:6396
      - 16396:16396
EOF

echo "docker-compose redis 生成成功!"

echo "step 5 -> 运行docker-compose 部署启动redis容器------"
# 运行docker-compose启动redis容器
docker-compose -f docker-compose.yaml up -d

exist=$(docker inspect --format '{{.State.Running}}' redis-6391)

if [[${exist}!='true']];
then
    sleep 3000
else
    echo 'redis容器启动成功!'
    IP_RESULT=""
    CONTAINER_IP=""
    for port in `seq 6391 6396`;
    do
    #CONTAINER_IP=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' redis-${port})
    IP_RESULT=${IP_RESULT}${CLUSTER_ANNOUNCE_IP}":"${port}" "
    done
fi
echo "获取redis容器ip和端口号:" ${IP_RESULT}

echo "step 6 -> redis 执行集群指令------"
docker run --rm -it inem0o/redis-trib create --replicas 1 ${IP_RESULT}

注:CLUSTER_ANNOUNCE_IP:要换成你的公网ip,就是你可以访问到的ip,同时执行脚本前,先开放6391~6396 以及16391~16396端口

测试

  1、进入某个节点

    docker exec -it redis-6391 redis-cli -p 6391 -c

  2、输入测试命令

    查看集群信息 cluster info

    查看集群状态 cluster nodes

    查看slots分片 cluster slots

容灾

  1、停掉主节点redis-6391,看节点redis-6394是否会接替它的位置,由slave变成master节点。

    docker stop redis-6391

    docker exec -it redis-6392 redis-cli -p 6392 -c

    cluster nodes 查看是否切换成功

  2、启动6391,它将自动切换为slave节点。

    docker start redis-6391

    docker exec -it redis-6392 redis-cli -p 6392 -c

    cluster nodes 查看是否切换成功

  注:我今天重新搭建了环境测试了下是没问题的。

管理

  1、创建

    docker-compose up -d

  2、销毁

    docker-compose down 

  3、查看

    docker-compose ps  

  4、启动

    docker-compose start  

  5、停止

    docker-compose stop

总结

  redis在很多项目中都在使用,建议研究下,单机、群集、备份、数据类型等。