【中间件】Redis 实战之主从复制、高可用、分布式

目录

  • 简介
  • 持久化
  • 主从复制
  • 高可用 Redis-Sentinel
    • .NET Core开发
  • 分布式 Redis-Cluster
  • 配置说明
  • 常见问题

简介

本节内容基于 CentOS 7.4.1708,Redis 3.2.12 环境实验。node

Redis 是一个开源的高性能键值对数据库。redis

安装:yum install -y redisdocker

特性:数据库

  • 高性能 Key-Value 服务器
  • 多种数据结构
  • 丰富功能
    • 缓存(get|set)
    • 计数器(incre)
    • 消息队列(publish|subcrib)
  • 高可用(v2.8 redis-sentinel)
  • 分布式(v3.0 redis-cluster)

可执行文件:缓存

  • redis-server:服务端
  • redis-cli:客户端
  • redis-benchmark:性能测试工具
  • redis-check-aof:aof修复工具
  • redis-check-dump:rdb修复工具
  • redis-sentinel:sentinel服务端

启动方式:安全

  • 最简启动:默认配置直接启动redis-server
  • 动态参数启动:命令行指定配置启动redis-server
  • 配置文件启动(推荐):指定配置文件启动redis-server

启动验证:ruby

  • ps -ef|grep redis
  • redis-cli -h locahost -p 6379 ping

因为 redis 是单线程的,推荐在一台多核CPU机器上部署多个 redis 实例充分发挥。服务器

持久化

redis 持久化支持2种:网络

  1. RDB:快照方式,至关于 MySQL 中的 dump
  2. AOF:写日志方式,至关于 MySQL 中的 binlog,推荐使用

注意:数据结构

  1. 当同时开启 RDB 和 AOF 的时候,redis启动的时候会读取 AOF 还原数据。
  2. 推荐:关闭 RDB 持久化机制,开启 AOF

RDB

RDB是什么:

  1. RDB方式的持久化是经过快照(snapshortting)完成的,当符合必定条件时Redis会自动将内存中全部数据完整的压缩存储到硬盘上。
  2. RDB开启条件由2个参数 时间 和 改动次数构成。如:save 900 1
  3. RDB文件由2个参数 dir 和 dbfilename 分别指定目录 和 文件名
  4. RDB方式是Redis默认的持久化方式。

触发命令:

  1. save 命令(阻塞)
  2. bgsave 命令(fork过程阻塞)

主要触发方式:

  1. 自动触发规则(内部调用bgsave,不推荐开启)
  2. 全量复制(内部调用bgsave)

过程:

  1. 执行 save 或 bgsave 命令
  2. 生成新的 rdb 文件,如:temp-36985.rdb
  3. 覆盖 rdb 文件,如:dump-6379.rdb

优势:

  1. 启动速度快
  2. 占用空间小

缺点:

  1. 容易丢失数据
  2. 时间复杂度O(n)

关闭RDB方式:

redis-cli config set save ""

注意:

RDB并不能真正的关闭,在主从复制时主从都会生成RDB文件

AOF

AOF是什么:

  1. AOF是纯文本文件,会记录 Redis 的每次改动命令(不记录查询)。
  2. AOF开启条件:appendonly yes
  3. AOF文件由2个参数 dir 和 appendfilename 分别指定目录 和 文件名
  4. AOF方式 默认状况下Redis并无开启。

因为每次改动都会记录,产生2个问题:

  1. 每次改动都写入硬盘,普通硬盘只能承受几百次qps。经过写入策略来调整
  2. 对同1个key执行几回操做就记录几回,冗余量特别大。经过 aof 文件重写来调整

AOF文件有3种写入策略:

  1. always(每次写入都会fsync同步到硬盘)
  2. everysec(默认,1s写1次)
  3. no(并不是不写,交给系统控制预计30s写1次)

AOF重写:

  1. 重写方式
    1. 手动执行 bgrewriteaof 命令
    2. 自动触发规则(经过指定最小aof文件和aof增加率来自动内部调用 bgrewriteaof)
  2. 过程
    1. fork 出子进程
    2. 子进程执行 bgrewriteaof 命令
    3. 父进程将新接收的命令,同时写到 aof 文件和 aof_rewrite_buffer文件中。(在 aof 重写时,可配置关闭aof写入)
    4. 子进程将 aof_rewrite_buffer 文件追加到新 aof 文件中。
    5. 覆盖旧的 aof 文件

注意:

  1. 采用 everysec 方式,最多可能丢失 2s 的数据。

主从复制

为何须要主从复制:

经过持久化保证 Redis 在服务器重启的状况下数据也不会丢失。但数据在一台服务器上,若是服务器的硬盘坏了,也会致使数据丢失。为了不单点故障,Redis 提供了主从复制高可用方案。

主从复制结构:

  1. 1个 master 能够有多个 slave
  2. 1个 slave 只能有1个 master
  3. 数据流向单向 master -> slave

开启复制:

  1. 命令:--slaveof ip port
  2. 配置:slaveof ip port(默认配置都是master)

关闭复制:

slaveof no one

复制类型:

  1. 全量复制(首次 或者 网络断开时间比较长)
  2. 部分复制(在网络抖动必定范围的状况下,v2.8以上可配置复制缓存区repl-backlog-size)

全量复制过程:

  1. slave 节点 发起 psync runid offset:psync ? -1
  2. master 节点 返回 fullresync runid offset
  3. master 节点 bgsave 保存当前数据到 rdb
  4. master 节点 在此期间接收到新的数据存储到 buffer 中
  5. master 节点 send RDB、send buffer
  6. slave 节点 flush old data
  7. slave 节点 load RDB、load buffer

在master重启(master 的run_id更新)和slave重启(slave 的run_id丢失)时都会发生全量复制,经过 info server 能够查看run_id。

部分复制过程:

  1. slave 节点 发起 psync runid offset
  2. master 节点 确认 runid 和 offset没问题后,发送增量数据
  3. slave 节点 接收同步数据。

当全量复制完成 或 网络抖动必定范围 时,master 至关于 slave 的 client 进行增量更新数据。

Redis Sentinel

Redis-Sentinel是什么?

  1. Redis-Sentinel是Redis官方推荐的高可用性(HA)解决方案
  2. Redis-Sentinel自己也是一个独立运行的进程,它能监控多个 master-slave 集群,发现 master宕机 后能进行自动故障转移。

sentinel工做原理:

  1. 准备多个Redis Sentinel节点(建议至少3个节点,避免单点故障)
  2. 多个 Sentinel 节点发现并确认 master 主观下线
  3. 超过 quorum 个 sentinel 断定确认 客观下线
  4. 选出 1个 sentinel 节点做为领导
  5. 选出 1个 slave 节点做为 master
  6. 切换 slave 节点的 master 为新的 master
  7. 通知 client 主从变化
  8. 等待故障的 master 复活成为新的 slave
  9. Client 不直接链接 Redis 节点,应该链接 Sentinel 节点获取 Redis Info

2种下线断定:

  1. sdown(subjectively down,主观下线):每一个 sentinel 断定 redis 节点下线。
  2. odown(objectively down,客观下线):超过 quorum 个 sentinel 断定 redis 节点下线。

启动方式:

  1. redis-sentinel /path/to/sentinel.conf
  2. redis-server /path/to/sentinel.conf --sentinel

三个定时任务:

  1. 每一个 Sentinel 节点每秒经过 redis 的 __sentinel__:hello 发布一条消息,宣布本身的存在。同时也订阅来肯定其余的 Sentinel 节点。
  2. 每一个 Sentinel 节点每秒对其余 redis 节点执行 ping。肯定是否下线。
  3. 每一个 Sentinel 节点每10秒 对 master 和 slave 执行 info,肯定 slaves。

配置模拟:

  1. 配置 Redis 开启主从复制
  2. 配置 Sentinel 监控主节点
echo "中止当前全部redis-server + redis-sentinel";
ps -x | grep redis | grep -v grep | awk '{print $1}' | xargs -r kill
echo "生成并启动3个 redis 配置";
for port in 6379 6380 6381 ;do
echo -e "daemonize yes\nport $port\npidfile /var/run/redis-$port.pid\nlogfile /var/log/redis/redis-$port.log\n" > /etc/redis/redis-$port.conf
if [ $port != 6379 ];then
    echo "slaveof 127.0.0.1 6379" >> /etc/redis/redis-$port.conf
fi
redis-server /etc/redis/redis-$port.conf
done
echo "生成并启动3个 redis-sentinel 配置";
for port in 26379 26380 26381 ;do
echo -e "daemonize yes\nport $port\ndir /tmp\nsentinel monitor mymaster 127.0.0.1 6379 2\nsentinel down-after-milliseconds mymaster 3000\nsentinel parallel-syncs mymaster 1\nsentinel failover-timeout mymaster 60000\nlogfile /var/log/redis/sentinel-$port.log\n" > /etc/redis/redis-sentinel-$port.conf
redis-sentinel /etc/redis/redis-sentinel-$port.conf
done
echo "结束";

经常使用的channel:

  • +switch-master:切换主节点
  • +convert-to-slave:切换从节点
  • +sdown:主观下线

.NET Core环境开发:

dotnet add package StackExchange.Redis

var options = new ConfigurationOptions()
{
    CommandMap = CommandMap.Sentinel,
    EndPoints = { { "192.168.0.51", 26379}, {"192.168.0.51", 26381}, {"192.168.0.51", 26380} },
    AllowAdmin = true,
    TieBreaker = "",
    ServiceName = "mymaster",
    SyncTimeout = 5000
};

var sentinelConn = ConnectionMultiplexer.Connect(options);
var master = sentinelConn.GetServer("192.168.0.51",26381).SentinelGetMasterAddressByName("mymaster");
// ...
var conn = ConnectionMultiplexer.Connect(master);
sentinelConn.GetSubscriber().Subscribe("+switch-master", (channel, message) =>
{
    // mymaster 192.168.0.51 6380 192.168.0.51 6381
    Console.WriteLine((string)message);
    // ...
    conn = ConnectionMultiplexer.Connect(ip);
    conn.GetDatabase().StringSet("hello","故障切换后值");
});
sentinelConn.GetSubscriber().Subscribe("+convert-to-slave", (channel, message) =>
{
    // slave 192.168.0.51:6379 192.168.0.51 6379 @ mymaster 192.168.0.51 6380
    Console.WriteLine((string)message);
});

conn.GetDatabase().StringSet("hello","原始值");

注意:

  1. 全部Sentinel和Redis不能在同一个节点

Redis Cluster

实际上大部分场景下,Redis Sentinel已经足够好。请根据实际状况采用 Redis Cluster。

Redis Cluster 采用虚拟槽分区方式(16384个虚拟槽)。

缘由:

  • 须要更高的qps(超过 10w/s)
  • 须要更高的数据量(超过 500G)
  • 须要更高的带宽(超过 1000M)

经常使用命令:

  1. redis-cli -h localhost -p 6382 cluster info:查看集群基本信息
  2. redis-cli -h localhost -p 6382 cluster slots:查看集群slot信息
  3. redis-cli -h localhost -p 6382 cluster nodes:查看集群node信息
  4. redis-cli -c:move自动跳转执行
  5. yum install -y redis-trib:官方提供了基于 ruby 的工具方便部署

搭建 Cluster 过程:

  1. 配置
    cluster-enabled:yes
    cluster-node-timeout 15000
    cluster-require-full-coverage no
    cluster-config-file node-${port}.conf
  2. meet
    redis-cli cluster meet ip port
  3. 分配槽(0-16383)
    redis-cli cluster addslots {0....5461}
  4. 分配主从(node-id)
    redis-cli cluster replicate {nodeid}

redis-cli 搭建:

echo "中止当前全部redis-server + redis-sentinel";
mkdir /etc/redis
ps -x | grep redis | grep -v grep | awk '{print $1}' | xargs -r kill
sleep 1
echo "启动6个 redis + meet";
for port in 7000 7001 7002 7003 7004 7005;do
echo -e "daemonize yes\nport $port\npidfile /var/run/redis-$port.pid\nlogfile /var/log/redis/redis-$port.log\ncluster-enabled yes\ncluster-config-file nodes-$port.conf\ncluster-require-full-coverage no" > /etc/redis/redis-$port.conf
redis-server /etc/redis/redis-$port.conf
done

for port in 7000 7001 7002 7003 7004 7005;do
    redis-cli -p $port FLUSHALL
    redis-cli -p $port cluster reset soft
    if [ $port != 7000 ];then
        redis-cli -p 7000 cluster meet 127.0.0.1 $port
    fi
done
sleep 1

echo "分配 16383 槽";
redis-cli -p 7000 cluster addslots {0..5461}
redis-cli -p 7001 cluster addslots {5462..10922}
redis-cli -p 7002 cluster addslots {10922..16383}

echo "配置 replication"
redis-cli -p 7003 cluster replicate `redis-cli -p 7000 cluster nodes | grep myself | awk '{print $1}'`
redis-cli -p 7004 cluster replicate `redis-cli -p 7001 cluster nodes | grep myself | awk '{print $1}'`
redis-cli -p 7005 cluster replicate `redis-cli -p 7002 cluster nodes | grep myself | awk '{print $1}'`

redis-trib搭建:

  1. 准备节点
  2. 使用redis-trib搭建
    redis-trib create --replicas 1 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005

redis-trib create 会自动meet、addslots、replicate。

配置

查看去除注释的配置:cat /etc/redis.conf | grep -v '^#' | grep -v '^$'

设置配置:config set key value

查询全部配置:config get *

基础配置

配置项 默认值 推荐值 说明
daemonize no yes(docker环境例外) 是否以守护进程方式启动
port 6379 - redis服务监听端口
pidfile /var/run/redis.pid /var/run/redis-{port}.pid pid文件
logfile /var/log/redis/redis.log /var/log/redis/redis-{port}.log 日志文件名:redis工做时产生的日志。
dir /var/lib/redis - rdb文件和aof文件目录。推荐使用大文件目录。(不指定则为当前目录)
protected-mode yes - 限制为127.0.0.1访问。启用条件:没有bindIP 和 没有设置密码

RDB配置

配置项 默认值 推荐值 说明
dbfilename dump.rdb dump-{port}.rdb rdb文件名
rdbcompression yes yes 压缩格式
stop-writes-on-bgsave-error yes yes 出现错误时,中止新的写入
rdbchecksum yes yes 数据完整性校验

AOF配置

配置项 默认值 推荐值 说明
appendonly no yes 是否开启 aof 模式
appendfilename "appendonly.aof" "appendonly-{port}.aof" aof文件名
appendfsync everysec everysec fsync方式
no-appendfsync-on-rewrite no(安全) yes(高性能) 在 aof 重写时,是否中止fsync
auto-aof-rewrite-min-size 64mb - aof文件重写的最小大小
auto-aof-rewrite-percentage 100 - aof文件增加率
aof-load-truncated yes yes 当 aof 文件不完整的时候,将完整的部分加载

主从复制配置

配置项 默认值 推荐值 说明
slowlog-max-len 128 1000 慢查询队列长度
slowlog-log-slow-than 10000 1000(qps1w) 慢查询阈值(单位:微秒)
slaveof ip port - 主从复制配置
slave-read-only yes yes 从节点只读
repl-backlog-size 1048576 10M 复制缓存区,能够再原有基础上稍微增长

Sentinel配置

配置项 默认值 推荐值 说明
daemonize no yes 是否以守护进程方式启动
port 26379 {port} sentinel监听端口
dir /tmp - 工做目录
sentinel monitor mymaster 127.0.0.1 6379 2 - odown(objectively down,客观下线)规则:masterName ip port quorum
sentinel down-after-milliseconds mymaster 30000 - sdown(subjectively down,主观下线)规则:masterName timeout(单位:毫秒)
sentinel parallel-syncs mymaster 1 - 并发同步数量
sentinel failover-timeout mymaster 180000 - 多长时间内再也不故障转移(单位:毫秒)
logfile /var/log/redis/sentinel.log /var/log/redis/sentinel-{port}.log 日志文件

Cluster配置

配置项 默认值 推荐值 说明
cluster-enabled no yes 开启cluster模式
cluster-node-timeout 15000 - 故障转移时间,主观下线超时时间
cluster-config-file nodes-{port}.conf cluster配置
cluster-require-full-coverage yes no cluster 全部节点所有在线才提供服务

常见问题

redis是单线程吗?为何这么快?

redis其实不是单线程(fsync,bgsave),一次只能执行一条命令。

慢查询

查询慢查询队列:slowlog get

客户端请求的生命周期:

  1. 发送命令
  2. 排队
  3. 执行命令
  4. 返回结果

慢查询发送在第三个阶段(执行命令),客户端超时不必定是慢查询。

fork

  1. fork自己是同步操做
  2. 内存越大耗时越长
  3. info:latest_fork_usec

规避全量复制

  • 首次全量复制:不可避免
  • runid 不匹配:故障转移
  • 复制缓冲区不足:配置repl_backlog_size调整大

经常使用命令

  • KEYS pattern :查询keys
  • DBSIZE :查询全部键的数量
  • EXISTS key :查询指定key是否存在
  • TYPE key :查询key的类型
  • DEL key :删除指定key
  • INFO :查看server 信息如:INFO memory

INFO 信息:

  • used_memory redis 当前使用的内存总量
  • used_memory_rss redis 当前使用的内存总量(包含内存碎片)
  • used_memory_peak redis 使用的内存总量峰值
相关文章
相关标签/搜索