Redis哨兵(sentinel)
前言
背景:测试环境的redis集群被运维回收,无奈之下只能自己动手,Redis高可用集群架构的搭建。
原理
Redis的哨兵(sentinel)系统用于管理多个redis服务器,该系统执行以下三个任务:
-
监控(Monitoring):哨兵(sentinel)会不断地检查你的Master和Slave是否运作正常。
-
提醒(Notification):当被监控的某个Redis出现问题时,哨兵(sentinel)可以通过API向管理员或者其他应用程序发送通知。
-
自动故障迁移(Automatic failover):当一个Master不能正常工作时,哨兵(sentinel)会开始一次自动故障迁移操作,它会将失效Master的其中一个Slave升级为新的Master,并让失效Master的其他Slave改为复制新的Master;当客户端试图连接失效的Master时,集群也会向客户端返回新Master的地址,使得集群可以使用Master代替失效Master,客户端实际获取的链接是哨兵返回的。
哨兵(sentinel)是一个分布式系统,你可以在一个架构中运行多个哨兵(sentinel)进程,这些进程使用流言协议(gossipprotocols)来接收关于Master是否下线的信息,并使用投票协议(agreement protocols)来决定是否执行自动故障迁移,以及选择哪个Slave作为新的Master。
每个哨兵(sentinel)会向其它哨兵(sentinel)、master、slave定时发送消息,以确认对方是否”活”着,如果发现对方在指定时间(可配置)内未回应,则暂时认为对方已挂(所谓的”主观认为宕机” Subjective Down,简称sdown).
若“哨兵群”中的多数sentinel,都报告某一master没响应,系统才认为该master"彻底死亡"(即:客观上的真正down机,Objective Down,简称odown),通过一定的vote算法,从剩下的slave节点中,选一台提升为master,然后自动修改相关配置.
虽然哨兵(sentinel)释出为一个单独的可执行文件redis-sentinel ,但实际上它只是一个运行在特殊模式下的Redis服务器,你可以在启动一个普通Redis服务器时通过给定--sentinel选项来启动哨兵(sentinel).
哨兵(sentinel)的一些设计思路和zookeeper非常类似。
架构图
下载
首页地址:http://redis.io/
最新稳定版下载地址:http://download.redis.io/releases/redis-3.2.9.tar.gz
# tar -xvf redis-3.2.9.tar.gz
# cd redis-3.2.9
# make install
# make test
命令
redis-server -h
Usage: ./redis-server [/path/to/redis.conf] [options]
./redis-server - (read config from stdin)
./redis-server -v or --version
./redis-server -h or --help
./redis-server --test-memory
Examples:
./redis-server (run the server with default conf)
./redis-server /etc/redis/6379.conf
./redis-server --port 7777
./redis-server --port 7777 --slaveof 127.0.0.1 8888
./redis-server /etc/myredis.conf --loglevel verbose
Sentinel mode:
./redis-server /etc/sentinel.conf --sentinel
配置
daemonize yes ###daemon后台运行模式
pidfile /var/run/redis.pid ###pidfile
port 6379 ###默认6379端口
tcp-backlog 511 ###tcp三次握手等待确认ack最大的队列数
bind 192.168.65.128 127.0.0.1 ###绑定的监听的ip,可同时写多个ip,不写默认监控0.0.0.0
timeout 0 ###关闭空闲客户端,0为disable
tcp-keepalive 0 ###是否开启tcp长连接,定义socket连接timeout时长
loglevel notice ###定义日志级别为notice(输出必要的日志)
logfile "/var/log/redis/redis.log" ###定义日志输出目录
databases 16 ###允许redis定义的最大的db簇
###开启rdb并应用如下数据保存策略,aof和rdb可同时启用,aof强烈建议开启
save 900 1
save 300 10
save 60 10000
stop-writes-on-bgsave-error yes ###有任何问题导致的bgsave失败都停止redis的对外服务
rdbcompression yes ###rdb写压缩
rdbchecksum yes ###rdb文件正确性检查
dbfilename dump.rdb ###rdb文件名称(dump.rdb可任意起)
dir /var/lib/redis/ ###rdb文件写目录
# slaveof ###如果是slave需要写明master
# masterauth ###master密码验证(因为redis基于内存,作用不大)
slave-serve-stale-data no ###如果master宕机,slave是否还正常对外服务no->停止服务
slave-read-only yes ###salve是否只读(默认只读)
repl-diskless-sync no ### Disk-backed 启动新进程写rdb到disk,然后增量传输到salves 方式二:Diskless 直接写salve sockers,而不用写disk,当网络好的时候建议使用,但因为用到的是M/S+哨兵的架构,随时可能会进行主从切换,这个方式暂时不用
repl-diskless-sync-delay 5 ##每5s传输一次diskless开启后生效
repl-ping-slave-period 3 ###slave每3s ping测试master是否存活
repl-timeout 10 ###定义replicationtxuq timeout时间,这里的时间要大于repl-ping-slave-period里的时间,不然后一直发生low traffic between the master and the slave问题
repl-disable-tcp-nodelay no ###yes 一条线40milliseconds发送一次数据包,意识着更小的tcp packets和更小的带宽去发送到slave,但在slave会有相应的数据延迟. No,刚相反.一般上我们期望是低延迟,所以最选择no是个不错的选择,但如果在复杂的网络情况或m/s之间有很多中继网络,这里建议修改为yes
slave-priority 100 ###slave优先级别,数字越小优先级别越高,当master宕机时优先选取slave
# min-slaves-to-write 3 ###当最少有3个slave延迟<= 10s时,master才正常提供服务
# min-slaves-max-lag 10 ###当最少有3个slave延迟<= 10s时,master才正常提供服务
maxmemory 1536000000 #1.5G ###单位 ,redis最大占用内存数,当超过1.5G时redis会根据策略清理内存key
#############6种清理策略
# volatile-lru -> remove the key with an expire set using an LRU algorithm ##推荐使用
# allkeys-lru -> remove any key according to the LRU algorithm
# volatile-random -> remove a random key with an expire set
# allkeys-random -> remove a random key, any key
# volatile-ttl -> remove the key with the nearest expire time (minor TTL)
# noeviction -> don't expire at all, just return an error on write operations
maxmemory-policy volatile-lru ###使用LRU算法清理过期key
# maxmemory-samples 3 ###LRU算法和TTL算法均为模糊算法,该精确算法redis会选择3个keys选择最少使用的一个key进行删除, 个人不建议使用
appendonly yes ###aof持久化,增量写disk,比aof慢,但更可靠.企业的默认选择,有的会两者均开启
appendfilename "appendonly.aof" ###aof文件名
# appendfsync always
appendfsync everysec ###建议使用该方式
# appendfsync no
no-appendfsync-on-rewrite no ##建议no, 主要用来缓和redis调用fsync()写数据时间长的问题.当BGSAVE或BGREWRITEAOF被调用期间,fsync()进程将阻止被调用,即相当于
auto-aof-rewrite-percentage 100 ###当文件大小达到64mb的100%大小时开始rewrite aof文件
auto-aof-rewrite-min-size 64mb ###当文件大小达到64mb的100%大小时开始rewrite aof文件
aof-load-truncated yes ###当aof文件被损坏时,redis将返回错误并退出
lua-time-limit 5000 ###LUA scripts最大执行时间,单位(milliseconds),超出后返回查询错误并写日志
slowlog-log-slower-than 10000 ###单位microseconds(毫秒) 1000000 microseconds=1 s,记录执行时长超过10000 microseconds的执行命令
slowlog-max-len 128 ###最大长度为128
latency-monitor-threshold 0 ###监控相关,关闭就好
notify-keyspace-events "" ###空表示关闭,发布相关key的操作记录到所有 client
######下面是高级设置,个人保持为默认配置
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
list-max-ziplist-entries 512
list-max-ziplist-value 64
set-max-intset-entries 512
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
hll-sparse-max-bytes 3000
activerehashing yes
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit slave 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60
hz 10
aof-rewrite-incremental-fsync yes
启动
# redis-server /etc/redis/redis.conf
查看
# redis-cli -h Mrds
Mrds:6379> info
# Server
redis_version:2.8.19 ###redis版本号
redis_git_sha1:00000000 ###git SHA1
redis_git_dirty:0 ###git dirty flag
redis_build_id:78796c63e58b72dc
redis_mode:standalone ###redis运行模式
os:Linux 2.6.32-431.el6.x86_64 x86_64 ###os版本号
arch_bits:64 ###64位架构
multiplexing_api:epoll ###调用epoll算法
gcc_version:4.4.7 ###gcc版本号
process_id:25899 ###服务器进程PID
run_id:eae356ac1098c13b68f2b00fd7e1c9f93b1c6a2c ###Redis的随机标识符(用于sentinel和集群)
tcp_port:6379 ###Redis监听的端口号
uptime_in_seconds:6419 ###Redis运行时长(s为单位)
uptime_in_days:0 ###Redis运行时长(天为单位)
hz:10
lru_clock:10737922 ###以分钟为单位的自增时钟,用于LRU管理
config_file:/etc/redis/redis.conf ###redis配置文件
# Clients
connected_clients:1 ###已连接客户端的数量(不包括通过从属服务器连接的客户端)
client_longest_output_list:0 ###当前连接的客户端中最长的输出列表
client_biggest_input_buf:0 ###当前连接的客户端中最大的输出缓存
blocked_clients:0 ###正在等待阻塞命令(BLPOP、BRPOP、BRPOPLPUSH)的客户端的数量 需监控
# Memory
used_memory:2281560 ###由 Redis 分配器分配的内存总量,以字节(byte)为单位
used_memory_human:2.18M ###以更友好的格式输出redis占用的内存
used_memory_rss:2699264 ###从操作系统的角度,返回 Redis 已分配的内存总量(俗称常驻集大小)。这个值和 top 、 ps 等命令的输出一致
used_memory_peak:22141272 ### Redis 的内存消耗峰值(以字节为单位)
used_memory_peak_human:21.12M ###以更友好的格式输出redis峰值内存占用
used_memory_lua:35840 ###LUA引擎所使用的内存大小
mem_fragmentation_ratio:1.18 ###used_memory_rss 和 used_memory 之间的比率
mem_allocator:jemalloc-3.6.0
###在理想情况下, used_memory_rss 的值应该只比 used_memory 稍微高一点儿。当 rss > used ,且两者的值相差较大时,表示存在(内部或外部的)内存碎片。内存碎片的比率可以通过 mem_fragmentation_ratio 的值看出。
当 used > rss 时,表示 Redis 的部分内存被操作系统换出到交换空间了,在这种情况下,操作可能会产生明显的延迟。
# Persistence
loading:0 ###记录服务器是否正在载入持久化文件
rdb_changes_since_last_save:0 ###距离最近一次成功创建持久化文件之后,经过了多少秒
rdb_bgsave_in_progress:0 ###记录了服务器是否正在创建 RDB 文件
rdb_last_save_time:1420023749 ###最近一次成功创建 RDB 文件的 UNIX 时间戳
rdb_last_bgsave_status:ok ###最近一次创建 RDB 文件的结果是成功还是失败
rdb_last_bgsave_time_sec:0 ###最近一次创建 RDB 文件耗费的秒数
rdb_current_bgsave_time_sec:-1 ###如果服务器正在创建 RDB 文件,那么这个域记录的就是当前的创建操作已经耗费的秒数
aof_enabled:1 ###AOF 是否处于打开状态
aof_rewrite_in_progress:0 ###服务器是否正在创建 AOF 文件
aof_rewrite_scheduled:0 ###RDB 文件创建完毕之后,是否需要执行预约的 AOF 重写操作
aof_last_rewrite_time_sec:-1 ###最近一次创建 AOF 文件耗费的时长
aof_current_rewrite_time_sec:-1 ###如果服务器正在创建 AOF 文件,那么这个域记录的就是当前的创建操作已经耗费的秒数
aof_last_bgrewrite_status:ok ###最近一次创建 AOF 文件的结果是成功还是失败
aof_last_write_status:ok
aof_current_size:176265 ###AOF 文件目前的大小
aof_base_size:176265 ###服务器启动时或者 AOF 重写最近一次执行之后,AOF 文件的大小
aof_pending_rewrite:0 ###是否有 AOF 重写操作在等待 RDB 文件创建完毕之后执行
aof_buffer_length:0 ###AOF 缓冲区的大小
aof_rewrite_buffer_length:0 ###AOF 重写缓冲区的大小
aof_pending_bio_fsync:0 ###后台 I/O 队列里面,等待执行的 fsync 调用数量
aof_delayed_fsync:0 ###被延迟的 fsync 调用数量
# Stats
total_connections_received:8466 ###服务器已接受的连接请求数量
total_commands_processed:900668 ###服务器已执行的命令数量
instantaneous_ops_per_sec:1 ###服务器每秒钟执行的命令数量
total_net_input_bytes:82724170
total_net_output_bytes:39509080
instantaneous_input_kbps:0.07
instantaneous_output_kbps:0.02
rejected_connections:0 ###因为最大客户端数量限制而被拒绝的连接请求数量
sync_full:2
sync_partial_ok:0
sync_partial_err:0
expired_keys:0 ###因为过期而被自动删除的数据库键数量
evicted_keys:0 ###因为最大内存容量限制而被驱逐(evict)的键数量。
keyspace_hits:0 ###查找数据库键成功的次数。
keyspace_misses:500000 ###查找数据库键失败的次数。
pubsub_channels:0 ###目前被订阅的频道数量
pubsub_patterns:0 ###目前被订阅的模式数量
latest_fork_usec:402 ###最近一次 fork() 操作耗费的毫秒数
# Replication
role:master ###如果当前服务器没有在复制任何其他服务器,那么这个域的值就是 master ;否则的话,这个域的值就是 slave 。注意,在创建复制链的时候,一个从服务器也可能是另一个服务器的主服务器
connected_slaves:2 ###2个slaves
slave0:ip=192.168.65.130,port=6379,state=online,offset=1639,lag=1
slave1:ip=192.168.65.129,port=6379,state=online,offset=1639,lag=0
master_repl_offset:1639
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:2
repl_backlog_histlen:1638
# CPU
used_cpu_sys:41.87 ###Redis 服务器耗费的系统 CPU
used_cpu_user:17.82 ###Redis 服务器耗费的用户 CPU
used_cpu_sys_children:0.01 ###后台进程耗费的系统 CPU
used_cpu_user_children:0.01 ###后台进程耗费的用户 CPU
# Keyspace
db0:keys=3101,expires=0,avg_ttl=0 ###keyspace 部分记录了数据库相关的统计信息,比如数据库的键数量、数据库已经被删除的过期键数量等。对于每个数据库,这个部分都会添加一行以下格式的信息
Sentinel(哨兵)配置
vi sentinel.conf
启动前:
port 26379
dir "/var/lib/redis/tmp" ###定义目录存放
sentinel auth-passmymaster vhreal ##密码
sentinel monitor mymaster 192.168.65.128 6379 2 ###监控mymaster(可自定义-但只能包括A-z 0-9和”._-”)
sentinel down-after-milliseconds mymaster 30000 ###mymaster多久不响应认为SDOWN
sentinel parallel-syncs mymaster 1 ###指定最大同时同步新maser配置的salve数量
sentinel failover-timeout mymaster 180000 ###2次failover切换时间
启动后(redis.con配置文件完全由sentinel控制,请不要再随意手动改动):
port 26379
dir "/var/lib/redis/tmp"
sentinel monitor mymaster 192.168.65.128 6379 2
sentinel config-epoch mymaster 18 ###确认mymater SDOWN时长
sentinel leader-epoch mymaster 18 ###同时一时间最多18个slave可同时更新配置,建议数字不要太大,以免影响正常对外提供服务
sentinel known-slave mymaster 192.168.65.129 6379 ###已知的slave
sentinel known-slave mymaster 192.168.65.130 6379 ###已知的slave
sentinel known-sentinel mymaster 192.168.65.130 26379 be964e6330ee1eaa9a6b5a97417e866448c0ae40 ###已知slave的唯一id
sentinel known-sentinel mymaster 192.168.65.129 26379 3e468037d5dda0bbd86adc3e47b29c04f2afe9e6 ###已知slave的唯一id
sentinel current-epoch 18 ####当前可同时同步的salve数最大同步阀值
开启sentinel
开启sentinel后会后redis.conf会由sentinel进行管理
# redis-server /etc/redis/sentinel.conf --sentinel &> /var/log/redis/sentinel.log &
常用命令
redis-cli
redis-server
auth
set key value
get key
Q&A
Redis主从配置异常解决:Error condition on socket for SYNC: Connection refused
Redis主从集群时,从服务器上的redis日志报错:
32677:S08Feb16:14:38.947*ConnectingtoMASTER172.168.10.70:637932677:S08Feb16:14:38.948*MASTER<->SLAVEsyncstarted32677:S08Feb16:14:38.948#ErrorconditiononsocketforSYNC:Connectionrefused32677:S08Feb16:14:39.950*ConnectingtoMASTER172.168.10.70:637932677:S08Feb16:14:39.950*MASTER<->SLAVEsyncstarted32677:S08Feb16:14:39.950#ErrorconditiononsocketforSYNC:Connectionrefused32677:S08Feb16:14:40.952*ConnectingtoMASTER172.168.10.70:637932677:S08Feb16:14:40.952*MASTER<->SLAVEsyncstarted32677:S08Feb16:14:40.953#ErrorconditiononsocketforSYNC:Connectionrefused
- 解决方案:
在redis主服务器上的redis.conf中修改bind字段,将
bind 127.0.0.1
修改为
bind 0.0.0.0
又或者直接注释掉bind字段
# bind 127.0.0.1
- 原因:
如果redis主服务器绑定了127.0.0.1,那么跨服务器IP的访问就会失败,从服务器用IP和端口访问主的时候,主服务器发现本机6379端口绑在了127.0.0.1上,也就是只能本机才能访问,外部请求会被过滤,这是Linux的网络安全策略管理的。如果bind的IP地址是172.168.10.70,那么本机通过localhost和127.0.0.1、或者直接输入命令redis-cli登录本机redis也就会失败了。只能加上本机ip才能访问到。
所以,在研发、测试环境可以考虑bind 0.0.0.0,线上生产环境建议绑定IP地址。
(DENIED Redis is running in protected mode)
Java程序中使用JedisSentinelPool建立redis连接池
Set sentinels = new HashSet();
sentinels.add("172.17.16.7:26379");
sentinels.add("172.17.16.8:26379");
sentinels.add("172.17.16.9:26379");
JedisSentinelPool sentinelPool = new JedisSentinelPool("mymaster", sentinels, "123456");
报错:
DENIED Redis is running in protected mode because protected mode is enabled, no bind address was specified, no authentication password is requested to clients.......
使用redis-cli -h 127.0.0.1 -p 26379连接sentinel可以执行命令,而使用redis-cli -h 172.17.16.7 -p 26379连接sentinel执行命令则会报同上的错误。
查遍了网上没有找到相关的问题分析和解决的案例,所以只能自己猜测和排查,初步怀疑是通过172.17.16.7访问sentinel时受限。
由于此错误和redis server的protect-mode为yes的访问错误颇为相似,官方在redis.conf的注释说明中有protected-mode这一配置项,但sentinel.conf的注释中完全没有提到过该配置项,我很疑惑,但还是尝试在sentinel.conf中加入
protected-mode no
之后保存并重新启动sentinel,之后用Java程序建立连接池,没有报错,且可以对redis server进行数据处理,问题解决。