Redis:

Redis是一款优秀的结构数据存储系统,由于出色的并发性能广为关注,可用作:数据库、缓存、消息队列;同类型的还有memcached,但是由于memcache支持的结构类型较少,并且不能够将数据持久化,慢慢的被redis所取代。

Redis支持的数据结构:字符串、列表(数组)、hashes(关联数组)、集合、有序集合、bitmaps、hyperloglogs、空间索引;本篇博客简单介绍redis对于实现高可用和持久化的四种redis工作模式,进入正题:

[TOC]

进入正题简单介绍

单机模式

单机模式下,对于数据的落地,根据业务数据的重要程度选择是RDB还是AOF备份;

RDB:snapshotting, 二进制格式;按事先定制的策略,周期性地将数据从内存同步至磁盘;数据文件默认为dump.rdb;
                    客户端显式使用SAVE或BGSAVE命令来手动启动快照保存机制;
                            SAVE:同步,即在主线程中保存快照,此时会阻塞所有客户端请求;
                            BGSAVE:异步;backgroud

AOF:Append Only File, fsync 记录每次写操作至指定的文件尾部实现的持久化;当redis重启时,可通过重新执行文件中的命令在内存中重建出数据库;
                    BGREWRITEAOF:AOF文件重写;
                    不会读取正在使用AOF文件,而是通过将内存中的数据以命令的方式保存至临时文件中,完成之后替换原来的AOF文件; 
    注意:持久机制本身不能取代备份;应该制订备份策略,对redis库定期备份;

    RDB与AOF同时启用: 
        (1) BGSAVE和BGREWRITEAOF不会同时进行;
        (2) Redis服务器启动时用持久化的数据文件恢复数据,会优先使用AOF;
RDB相关的配置:
    *save  

        save 900 1
        save 300 10
        save 60 10000
        save 5  200000

    表示:三个策略满足其中任意一个均会触发SNAPSHOTTING操作;900s内至少有一个key有变化,300s内至少有10个key有变化,60s内至少有1W个key发生变化;

        stop-writes-on-bgsave-error yes     ## dump操作出现错误时,是否禁止新的写入操作请求;    
        rdbcompression yes              ##启用压缩
        rdbchecksum yes                 ##检测完整性

        dbfilename dump.rdb:指定rdb文件名
        *dir /var/lib/redis:rdb文件的存储路径

AOF相关的配置
        *appendonly no      
        appendfilename "appendonly.aof"

        *appendfsync 
            Redis supports three different modes:
                no:redis不执行主动同步操作,而是OS进行决定何时同步;
                everysec:每秒一次;  ##推荐
                always:每语句一次;

            no-appendfsync-on-rewrite no
                是否在后台执行aof重写期间不调用fsync,默认为no,表示调用;

            auto-aof-rewrite-percentage 100
            auto-aof-rewrite-min-size 64mb  
                上述两个条件同时满足时,方会触发重写AOF;与上次aof文件大小相比,其增长量超过100%,且大小不少于64MB; 

            aof-load-truncated yes

主从复制模式

实现原理:主从模式实现比较简单,类似于MySQL的主从,选定一个主机当做master,在从节点配置指向master的地址端口及口令信息即可实现;master开启主从模式后,会对当前自身进行快照RDB,然后基于网络发送给slave节点,在此之前用户可以选择直接基于网络发送给slave快照还是先创建快照文件于磁盘中,而后将其发送给从节点;与此同时新接收的数据将放在repl-backlog-size中作为缓冲区,当slave接收RDB成功后,master将repl-backlog-size内容在陆续发送给slave

开始之前所有Redis首先进行基本安全加固:

vim /etc/redis.conf
    ...
    bind 192.168.2.128
    requirepass "ifan"
    ...

挑选一台Redis作为Master节点:

Slave:
    127.0.0.1:6379> SLAVEOF 192.168.2.128 6379
    127.0.0.1:6379> CONFIG SET masterauth "ifan"
    127.0.0.1:6379> CONFIG REWRITE

Master查看主从复制状态:

192.168.2.128:6379> INFO Replication
# Replication
role:master
connected_slaves:1
slave0:ip=192.168.2.129,port=6379,state=online,offset=981,lag=1
master_repl_offset:981
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:2
repl_backlog_histlen:980
192.168.2.128:6379> 
  1. Redis的主从复制支持多slave,同样支持链式复制,只需要各级slave指定自己的master即可

  2. Master以非阻塞方式同步数据至slave主机;

主从复制调优参数:repl(主从复制)

slave-serve-stale-data yes  
    # 表明slave会继续应答来自client的请求,但这些数据可能已经过期(因为连接中断导致无法从master同步)。若配置为no,则slave除正常应答"INFO"和"SLAVEOF"命令外,其余来自客户端的请求命令均会得到"SYNC with master in progress"的应答,直到该slave与master的连接重建成功或该slave被提升为master。

slave-read-only yes 
    # 从状态下,不可写
repl-diskless-sync no   
    # no,则是Disk-backend主节点新创建快照文件于磁盘中,而后将其发送给从节点;
    # yes,主节点新创建快照后直接通过网络套接字文件发送给从节点;为了实现并行复制,通常需要在复制启动前延迟一个时间段;

repl-diskless-sync-delay 5
    # 配置当收到第一个请求时,等待多个5个slave一起来请求之间的间隔时间。
repl-ping-slave-period 10
    # 配置master与slave的心跳间隔10秒
repl-timeout 60
    # 主从复制的超时时间
repl-disable-tcp-nodelay no
    # 禁止Nagle算法,允许小包的发送,不等待一有变化就发送。对于延时敏感型,同时数据传输量比较小的应用,
repl-backlog-size 1mb
    # 设置备份的工作储备大小。工作储备是一个缓冲区,当从站断开一段时间的情况时,它替从站接收存储数据,因此当从站重连时,通常不需要完全备份,只需要一个部分同步就可以,即把从站断开时错过的一部分数据接收。工作储备越大,从站可以断开并稍后执行部分同步的断开时间就越长。

slave-priority 100
    # 复制集群中,主节点故障时,sentinel应用场景中的主节点选举时使用的优先级;数字越小优先级越高,但0表示不参与选举; 
min-slaves-to-write 3
    # 主节点仅允许其能够通信的从节点数量大于等于此处的值时接受写操作;
min-slaves-max-lag 10
    # 从节点延迟时长超出此处指定的时长时,主节点会拒绝写入操作;

哨兵模式(3台)

原理实现:哨兵模式的实现是基于主从模式的一种补充,在主从模式下slave是不允许写入的,那么当master宕机后,整个业务将不能够继续写入,而哨兵则是为此做的了一个补充,当master出现故障后,每个redis服务都会工作一个sentinel(哨兵)时刻监控redis6379端口,一旦发生故障,基于quorum机制投票决定进而实现故障转移,重新选举master提供写操作,sentinel拥有调用外部脚本实现报警的能力

注意:配置哨兵之前清先配置主从复制

[root@node1 ~]#  vim /etc/redis-sentinel.conf
...
port 26379
bind 0.0.0.0            ##绑定端口,否则会被进入安全模式,仅允许127.0.0.1链接
sentinel monitor mymaster 192.168.2.129 6379 2      ##集群名,master地址,端口,投票2票及以上才进行故障转移
sentinel down-after-milliseconds mymaster 10000     ##监控到指定的集群的主节点异常状态持续多久方才将标记为“故障”;
sentinel failover-timeout mymaster 120000           ##sentinel必须在此指定的时长内完成故障转移操作,否则,将视为故障转移操作失败;
sentinel auth-pass mymaster ifan                   ##哨兵集群,配置链接6379端口密码
# sentinel notification-script    ##调用脚本报警功能
...

哨兵模式管理指令

redis-cli -h SENTINEL_HOST -p SENTINEL_PORT 
redis-cli> 
            SENTINEL masters                ##查看当前master信息
            SENTINEL slaves     ##查看master下的从节点信息
            SENTINEL failover   ##手动进行故障转移,需要指定集群名
            SENTINEL get-master-addr-by-name   ##查看当前master的ip信息

集群模式(6台)

实现原理:redis cluster是一种无中心的分布式服务,最小由3台即可实现集群(但不能保证高可用的能力)3台为master节点,3台为slave节点(master正常时不提供服务),当客户端的存请求到达redis集群时会将数据进行hash取模,根据redis集群建立划分的slot进行存储,取的时候根据取模的slot进行取值,cluster模式下无论客户端请求到达集群的任何节点都能够被提供服务,但是在非集群(-c选项)要求客户端是智能客户端,因为客户端到达节点存取数据进行取模得到数据可存放和读取的服务器位置,由客户端自行发起第二次访问

关于集群实现其他方案:Codis / cerberus.
这里我们实现以下Redis的Cluster集群模式,很多博客在最后集群实现时采用了ruby的集群工具实现槽位自动划分,考虑到工具还要大费周折的布置环境,这里我们全部手动来实现

地址 服务1 服务2 系统版本
192.168.2.128(node1) 6379(master) 6380(slave) CentOS7.2
192.168.2.129(node2) 6379(master) 6380(slave) CentOS7.2
192.168.2.130(node3) 6379(master) 6380(slave) CentOS7.2

三台机器分别安装redis:

[root@node1 ~]# ntpdate -u ntp.aliyun.com
[root@node1 ~]# yum install redis -y
[root@node1 ~]# cp /etc/redis.conf /etc/redis_6379.conf
[root@node1 ~]# cp /etc/redis.conf /etc/redis_6380.conf
[root@node1 ~]# egrep -v "^#|^$" /etc/redis_6379.conf
    ...
    bind 0.0.0.0
    port 6379       
    pidfile /var/run/redis_6379.pid
    logfile /var/log/redis/redis_6379.log
    cluster-enabled yes
    cluster-config-file nodes-6379.conf
    cluster-node-timeout 15000
    cluster-slave-validity-factor 10
    cluster-migration-barrier 1
    ...

注:为了完成多实例,请将/etc/redis_6380.conf和6379配置保持一致,端口除外

优化内存相关选项:

echo "vm.overcommit_memory=1" >> /etc/sysctl.conf ; sysctl -p
echo "511" > /proc/sys/net/core/somaxconn
echo never > /sys/kernel/mm/transparent_hugepage/enabled 

启动服务:

[root@node1 ~]# redis-server /etc/redis_6379.conf &
[root@node1 ~]# redis-server /etc/redis_6380.conf &

检查日志是否有异常信息:

[root@node1 ~]# tail -f /var/log/redis/redis_6379.log
[root@node1 ~]# tail -f /var/log/redis/redis_6380.log

添加机器到集群:

redis-cli -h 192.168.2.128 -p 6379 -c
192.168.2.128:6379> CLUSTER MEET 192.168.2.129 6379
192.168.2.128:6379> CLUSTER MEET 192.168.2.130 6379

查看集群状态:

192.168.2.128:6379> CLUSTER INFO            ##查看集群状态
    cluster_state:fail                     ##集群状态,之所以会失败是因为我们还没有对集群进行槽位的划分
    cluster_slots_assigned:0 #分配的slot数
    cluster_slots_ok:0       #正确的slot数
    cluster_slots_pfail:0
    cluster_slots_fail:0
    cluster_known_nodes:3    #当前的节点数
    cluster_size:0
    cluster_current_epoch:2
    cluster_my_epoch:0
    cluster_stats_messages_sent:171
    cluster_stats_messages_received:171

192.168.2.128:6379> CLUSTER NODES
    2a9f356a7362c535056f4311057d78269c7aa6d4 192.168.2.129:6379 master - 0 1551782791382 1 connected 5462-10922
    af69cb43def99109d4e9a2e15b0fa41b1998bb02 192.168.2.130:6379 master - 0 1551782790373 0 connected 10923-16383
    32db6806120a01ce90fa8641bd15abd3c6a55408 192.168.2.128:6379 myself,master - 0 0 2 connected 0-5461

划分槽位(根据master的数量进行划分,一共16384个槽位):

[root@node1 ~]# for k in {0..5461};do redis-cli -h 192.168.2.128 -p 6379 CLUSTER ADDSLOTS $k;done
[root@node1 ~]# for k in {5462..10922};do redis-cli -h 192.168.2.129 -p 6379 CLUSTER ADDSLOTS $k;done
[root@node1 ~]# for k in {10923..16383};do redis-cli -h 192.168.2.130 -p 6379 CLUSTER ADDSLOTS $k;done

再次查看集群状态:

192.168.2.128:6379> CLUSTER INFO            ##查看集群状态
    cluster_state:ok
    cluster_slots_assigned:16384            ##划分的总槽位
    cluster_slots_ok:16384                  ##成功划分的槽位
    cluster_slots_pfail:0
    cluster_slots_fail:0
    cluster_known_nodes:3
    cluster_size:3
    cluster_current_epoch:2
    cluster_my_epoch:0
    cluster_stats_messages_sent:3495
    cluster_stats_messages_received:3495

添加从节点

redis-cli -h 192.168.2.128 -p 6379 -c
192.168.2.128:6379> CLUSTER MEET 192.168.2.128 6380
192.168.2.128:6379> CLUSTER MEET 192.168.2.129 6380
192.168.2.128:6379> CLUSTER MEET 192.168.2.130 6380

划分槽位(根据master的数量进行划分,一共16384个槽位):

[root@node1 ~]# for k in {0..5461};do redis-cli -h 192.168.2.128 -p 6380 CLUSTER ADDSLOTS $k;done
[root@node1 ~]# for k in {5462..10922};do redis-cli -h 192.168.2.129 -p 6380 CLUSTER ADDSLOTS $k;done
[root@node1 ~]# for k in {10923..16383};do redis-cli -h 192.168.2.130 -p 6380 CLUSTER ADDSLOTS $k;done

查看集群节点数:

192.168.2.128:6379> CLUSTER INFO            ##查看集群状态
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6    ##所有节点数6个
cluster_size:3          ##主节点数3个
cluster_current_epoch:5
cluster_my_epoch:0
cluster_stats_messages_sent:4388
cluster_stats_messages_received:4388

查看状态:

192.168.2.128:6379> CLUSTER NODES
2a9f356a7362c535056f4311057d78269c7aa6d4 192.168.2.129:6379 master - 0 1551782791382 1 connected 5462-10922
af69cb43def99109d4e9a2e15b0fa41b1998bb02 192.168.2.130:6379 master - 0 1551782790373 0 connected 10923-16383
30744bd131bb249850d0f1ff5a91fae751b86fe3 192.168.2.130:6380 master - 0 1551782788357 5 connected
65d1453da3e1e3dc2a1e4bdefac375c4890e8555 192.168.2.128:6380 master - 0 1551782789362 3 connected
85461b05a18125b2301a615d217af4dacf35879c 192.168.2.129:6380 master - 0 1551782792388 4 connected
32db6806120a01ce90fa8641bd15abd3c6a55408 192.168.2.128:6379 myself,master - 0 0 2 connected 0-5461

注意:此时的六个节点全部是以master的身份,我们需要将后需添加的3个变更为从节点,并基于master进行复制

修改从节点状态:

128(6380) -- 复制 --> 129 (6379)
129(6380) -- 复制 --> 130 (6379)
130(6380) -- 复制 --> 128 (6379)

# redis-cli -h 192.168.2.128 -p 6380 -c CLUSTER REPLICATE 2a9f356a7362c535056f4311057d78269c7aa6d4
# redis-cli -h 192.168.2.129 -p 6380 -c CLUSTER REPLICATE af69cb43def99109d4e9a2e15b0fa41b1998bb02
# redis-cli -h 192.168.2.130 -p 6380 -c CLUSTER REPLICATE 32db6806120a01ce90fa8641bd15abd3c6a55408

再次查看节点状态(此时的主从关系已经形成):

192.168.2.128:6379> CLUSTER NODES
2a9f356a7362c535056f4311057d78269c7aa6d4 192.168.2.129:6379 master - 0 1551786306436 1 connected 5462-10922
af69cb43def99109d4e9a2e15b0fa41b1998bb02 192.168.2.130:6379 master - 0 1551786305391 0 connected 10923-16383
30744bd131bb249850d0f1ff5a91fae751b86fe3 192.168.2.130:6380 slave 32db6806120a01ce90fa8641bd15abd3c6a55408 0 1551786308514 5 connected
65d1453da3e1e3dc2a1e4bdefac375c4890e8555 192.168.2.128:6380 slave 2a9f356a7362c535056f4311057d78269c7aa6d4 0 1551786307471 3 connected
85461b05a18125b2301a615d217af4dacf35879c 192.168.2.129:6380 slave af69cb43def99109d4e9a2e15b0fa41b1998bb02 0 1551786303303 4 connected
32db6806120a01ce90fa8641bd15abd3c6a55408 192.168.2.128:6379 myself,master - 0 0 2 connected 0-5461

到此Redis3.2版本的集群已经搭建完成,redis-cluster需要智能客户端,在写入时需要智能判断

# for k in {1..10};do redis-cli -h 192.168.2.128 -p 6379 SET key$k val$k;done   ##我们先不以集群模式来寸数据,由于redis的分片机制,会提醒你本次数据要交给xx主机存
(error) MOVED 9189 192.168.2.129:6379
OK
OK
(error) MOVED 13120 192.168.2.130:6379
(error) MOVED 9057 192.168.2.129:6379
OK
OK
(error) MOVED 13004 192.168.2.130:6379
(error) MOVED 8941 192.168.2.129:6379
(error) MOVED 5850 192.168.2.129:6379

# for k in {11..20};do redis-cli -c -h 192.168.2.128 -p 6379 SET key$k val$k;done  ##我们以集群模式去存,则全部存成功
OK
OK
OK
OK
OK
OK
OK
OK
OK
OK

# for k in {11..20};do redis-cli -h 192.168.2.128 -p 6379 GET key$k;done   ##不以集群模式读取
"val11"
(error) MOVED 13976 192.168.2.130:6379
(error) MOVED 9913 192.168.2.129:6379
(error) MOVED 5726 192.168.2.129:6379
"val15"
(error) MOVED 13852 192.168.2.130:6379
(error) MOVED 9789 192.168.2.129:6379
(error) MOVED 6098 192.168.2.129:6379
"val19"
"val20"

# redis-cli -h 192.168.2.130 -p 6379 GET key12  ##根据上面提示的数据存储位置去读取,则读取成功
"val12"

模拟故障:

# ps -ef|grep redis
root       2805      1  0 18:31 ?        00:00:09 redis-server 0.0.0.0:6379 [cluster]
root       2809      1  0 18:31 ?        00:00:06 redis-server 0.0.0.0:6380 [cluster]
# kill -9 2805
杀掉进程后先是先是集群状态fail稍后slave上线对外提供服务

192.168.2.128:6380> cluster nodes
2a9f356a7362c535056f4311057d78269c7aa6d4 192.168.2.129:6379 master - 0 1551787432472 1 connected 5462-10922
32db6806120a01ce90fa8641bd15abd3c6a55408 192.168.2.128:6379 master,fail - 1551787261748 1551787257954 2 disconnected
85461b05a18125b2301a615d217af4dacf35879c 192.168.2.129:6380 slave af69cb43def99109d4e9a2e15b0fa41b1998bb02 0 1551787433495 4 connected
30744bd131bb249850d0f1ff5a91fae751b86fe3 192.168.2.130:6380 master - 0 1551787431446 7 connected 0-5461
65d1453da3e1e3dc2a1e4bdefac375c4890e8555 192.168.2.128:6380 myself,slave 2a9f356a7362c535056f4311057d78269c7aa6d4 0 0 3 connected
af69cb43def99109d4e9a2e15b0fa41b1998bb02 192.168.2.130:6379 master - 0 1551787427638 0 connected 10923-16383

注:当集群中master出现宕机时,slave才会对外提供服务,否则slave接收请求时会提示该数据能够提供服务的位置,当6台机器的master的全部宕机,slave上线,除非宕机超过一半,集群不再成立,机器下线后,重新上线以slave的身份工作

宕机前:
# redis-cli -h 192.168.2.128 -p 6380 GET key13      ##宕机前129的备节128(6380)点不提供服务,提示该数据能提供服务的服务器位置
(error) MOVED 9913 192.168.2.129:6379       
# redis-cli -h 192.168.2.129 -p 6379 GET key13      ##根据提示,129的6379能够提供服务
"val13"

模拟宕机后:
# redis-cli -h 192.168.2.129 -p 6379 GET key13      ##129的6379不能够提供服务
Could not connect to Redis at 192.168.2.129:6379: Connection refused

# redis-cli -h 192.168.2.128 -p 6380 GET key13      ##129的备节点128(6380)提供服务
"val13"