daemonize yes pidfile /var/run/ngx_redis.pid bind 127.0.0.1 port 6379 #slaveof 60.191.93.173 6379 slaveof 192.168.152.82 6379 timeout 300 loglevel warning logfile stdout databases 16 #save 86400 1 rdbcompression yes dbfilename dump.rdb dir /usr/local/ngx_redis/ appendonly no appendfsync everysec no-appendfsync-on-rewrite no vm-enabled no vm-swap-file /tmp/redis.swap vm-max-memory 0 vm-page-size 32 vm-pages 134217728 vm-max-threads 4 glueoutputbuf yes hash-max-zipmap-entries 64 hash-max-zipmap-value 512 activerehashing yes
设置内存分配策略(可选,根据服务器的实际情况进行设置)
/proc/sys/vm/overcommit_memory
可选值:0、1、2。
注意:redis在dump数据的时候,会fork出一个子进程,理论上child进程所占用的内存和parent是一样的,比如parent占用的内存为8G,这个时候也要同样分配8G的内存给child,如果内存无法负担,往往会造成redis服务器的down机或者IO负载过高,效率下降。所以这里比较优化的内存分配策略应该设置为 1(表示内核允许分配所有的物理内存,而不管当前的内存状态如何)。
在Unix中,当一个用户进程使用malloc()函数申请内存时,假如返回值是NULL,则这个进程知道当前没有可用内存空间,就会做相应的处理工作。许多进程会打印错误信息并退出。
Linux使用另外一种处理方式,它对大部分申请内存的请求都回复“yes”,以便能跑更多更大的程序。因为申请内存后,并不会马上使用内存。这种技术叫做Overcommit。
当内存不足时,会发生OOM killer(OOM=out-of-memory)。它会选择杀死一些进程(用户态进程,不是内核线程),以便释放内存。
Linux下overcommit有三种策略(Documentation/vm/overcommit-accounting):
overcommit的策略通过vm.overcommit_memory设置。 overcommit的百分比由vm.overcommit_ratio设置。 # echo 2 > /proc/sys/vm/overcommit_memory # echo 80 > /proc/sys/vm/overcommit_ratio
当oom-killer发生时,linux会选择杀死哪些进程,选择进程的函数是oombadness函数(在mm/oomkill.c中),该函数会计算每个进程的点数(0~1000)。点数越高,这个进程越有可能被杀死。每个进程的点数跟oomscoreadj有关,而且oomscoreadj可以被设置(-1000最低,1000最高)。
在redis中,允许用户设置最大使用内存大小maxmemory (需要配合maxmemory-policy使用),设置为0表示不限制;
当redis内存数据集快到达maxmemory时,redis会实行数据淘汰策略。
volatile-lru:从已设置过期时间的内存数据集中挑选最近最少使用的数据 淘汰; volatile-ttl: 从已设置过期时间的内存数据集中挑选即将过期的数据 淘汰; volatile-random:从已设置过期时间的内存数据集中任意挑选数据 淘汰; allkeys-lru:从内存数据集中挑选最近最少使用的数据 淘汰; allkeys-random:从数据集中任意挑选数据 淘汰; no-enviction(驱逐):禁止驱逐数据。(默认淘汰策略。当redis内存数据达到maxmemory,在该策略下,直接返回OOM错误);
如
127.0.0.1:6379>CONFIG GET maxmemory 1) “maxmemory" 2) “0” 127.0.0.1:6379>CONFIG SET maxmemory 100MB OK 127.0.0.1:6379>CONFIG GET maxmemory “maxmemory" “104857600”
另外,对一个从属服务器执行命令 SLAVEOF NO ONE 将使得这个从属服务器关闭复制功能,并从从属服务器转变回主服务器,原来同步所得的数据集不会被丢弃。
利用『 SLAVEOF NO ONE 不会丢弃同步所得数据集』这个特性,可以在主服务器失败的时候,将从属服务器用作新的主服务器,从而实现无间断运行。
redis> SLAVEOF 127.0.0.1 6379 OK redis> SLAVEOF NO ONE OK
for host in `seq 11 18`;do for i in {0..8};do if [ $i -eq 7 ];then opt="-a NdkPFnvCy8Lrbjb8ZYPY" elif [ $i -eq 8 ];then opt="-a NrktSE1f34OHZ0x6FxY9" else opt="" fi cmd="SLAVEOF 192.168.13.248 102$i" echo redis-cli -h 192.168.12.$host -p 102$i $opt $cmd done done
redis> config set slave-read-only no
redis> info replication
redis-cli -h 192.168.13.168 -p 1020 client list
IPS=(192.168.12.11 192.168.12.12 192.168.12.13 192.168.12.14) check_redis() { ip="192.168.13.250" set_time=`date +%s` port=$1 /opt/redis/bin/redis-cli -h $ip -p $port set shy $set_time sleep 5 key=$(( $RANDOM % ${#IPS[*]} )) rhost=${IPS[$key]} get_time=`/opt/redis/bin/redis-cli -h $rhost -p $port get shy` if [ -z $get_time ];then key=$(( $RANDOM % ${#IPS[*]} )) rhost=${IPS[$key]} get_time=`/opt/redis/bin/redis-cli -h $rhost -p $port get shy` fi now_time=`date +%s` if [ $(( now_time - get_time )) -lt 30 ];then echo OK else send_warning 0 Redis $ip Redis_Check "Redis_Error!!" fi }
首先我们看下RDB快照方式,这种持久化方式主要分为手动触发和被动触发。
手动触发分为SAVE和BGSAVE,由于SAVE会阻塞Redis服务,直到RDB过程完成,所以生产环境我们主要使用BGSAVE操作,执行BGSAVE之后,Redis进程会执行fork子进程,让子进程负责,直到自动结束。
被动触发是由于我们配置文件进行了save配置选项,如果用户设置了多个配置save配置选项那么任何一个条件满足就会触发BGSAVE。
我们可以看下下面的一段配置:
1. save 60 10000 # 60s有10000次写入(修改)被动触发BGSAVE 2. save 900 10 # 900s有10次就触发BGSAVE,保存到硬盘(两个满足一个就触发) 3. stop-writes-on-bgsave-error no # 创建快照失败是否继续写命令 4. rdbcompression yes # 是否进行压缩(默认压缩) 5. dbfilename dump.rdb # 生成文件名
由于RDB快照持久化适合小规模或丢失一部分数据也不会造成问题的应用,但是大多数场景里面,我们需要尽量保证数据完整性,所以这里我们有了AOF这种方式。
AOF持久化会将执行的命令写到AOF文件的末尾,来记录数据发生的变化,所以只需要将AOF的文件命令在Redis重新执行一遍就能恢复AOF文件的数据。
但是AOF文件会随着命令的不断增加,文件会越来越大,所以Redis有了重写机制,通过重写机制来压缩控制文件大小。
重写机制的原理就是合并多个命令,比如同样 inc 操作了100次,那么就可以合并为1次进行,或者之前添加了某个数据,后面又删除了就可以删除之前无效的命令等等。
AOF重写这种方式和RDB触发同样分为手动触发和被动触发。
我们可以看下下面的配置:
appendonly no # 是否进行aof持久化 appendfsync everysec # 每秒同步一次,有always,no选项 no-appendfsync-on-rewrite no # aof重写期间是否同步 auto-aof-rewrite-percentage 100 # 当前文件是上一次大小的两倍的时候触发重写 auto-aof-rewrie-min-size 64mb # 文件最小体积触发重写
AOF重写和RDB快照方式一样,都会创建子进程,然后AOF子进程进行文件重写,所以快照持久化因为创建子进程导致性能和内存问题在AOF一样存在。
通过AOF的方式能相对更完善保留数据,让我们更为放心。
#!/bin/sh RDS_CMD='/opt/redis/bin/redis-cli' RDS_PORT="1020 1021 1022 1023 1024 1025 1026 1027 1028" BACK_TIME=`date +%Y%m%d_%H` BACK_DIR="/mnt" dingding(){ curl 'https://oapi.dingtalk.com/robot/send?access_token=0f0b2e4b29ccedbfa6ef2b609dbbc1dbda3aa074c4f198337acad55119f3cba5' \ -H 'Content-Type: application/json' \ -d '{"msgtype": "text", "text": { "content": "[Redis]:'"$1 $2"' " } }' } for port in $RDS_PORT;do echo ========== $port =========== DST="$BACK_DIR/backup_$port" mkdir -p $DST dir="/disk/ssd1/redisdb/rdb$port" $RDS_CMD -h 192.168.13.132 -p $port info | grep -q "master_link_status:up" [ $? = 0 ] || dingding $port "replication failed" BACK_FILE="${DST}/rdb${port}_${BACK_TIME}.tgz" tar zcvf $BACK_FILE $dir done
Sentinel(哨兵)是用于监控redis集群中Master状态的工具,是Redis 的高可用性解决方案,sentinel哨兵模式已经被集成在redis2.4之后的版本中。sentinel是redis高可用的解决方案,sentinel系统可以监视一个或者多个redis master服务,以及这些master服务的所有从服务;当某个master服务下线时,自动将该master下的某个从服务升级为master服务替代已下线的master服务继续处理请求。
判断完主库下线后,由哪个哨兵节点来执行主从切换呢?这里就需要哨兵集群的选举机制了。
为了避免哨兵的单点情况发生,所以需要一个哨兵的分布式集群。作为分布式集群,必然涉及共识问题(即选举问题);同时故障的转移和通知都只需要一个主的哨兵节点就可以了。
哨兵的选举机制是什么样的? 哨兵的选举机制其实很简单,就是一个Raft选举算法: 选举的票数大于等于num(sentinels)/2+1时,将成为领导者,如果没有超过,继续选举 Raft算法你可以参看这篇文章分布式算法 - Raft算法 任何一个想成为 Leader 的哨兵,要满足两个条件:
以 3 个哨兵为例,假设此时的 quorum 设置为 2,那么,任何一个想成为 Leader 的哨兵只要拿到 2 张赞成票,就可以了。
[program:srs-1025] command = /opt/redis/bin/redis-server /opt/redis/etc/srs-1025-sentinel.conf --sentinel [group:sentinel] programs = srs-1025 autostart = true autorestart = true stopsignal = QUIT stdout_logfile=None stderr_logfile=None
port 21025 daemonize no protected-mode no dir "/tmp" pidfile "/var/run/srs-sentinel.pid" logfile "/disk/ssd1/logs/srs-sentinel.log" sentinel client-reconfig-script srs-sentinel /opt/redis/change_master.sh sentinel resolve-hostnames no sentinel announce-hostnames no sentinel monitor srs-sentinel 192.168.13.250 1025 2 sentinel down-after-milliseconds srs-sentinel 60000
#!/bin/bash MASTER_IP=$6 #第六个参数是新主redis的ip地址 LOCAL_IP=`/sbin/ip addr | awk '/192.168./{gsub("/.*","");if($2!=""){print $2}}'|sort -u` VIP='192.168.13.248' NETMASK='24' INTERFACE='br100' if [[ "$LOCAL_IP" =~ "$MASTER_IP" ]]; then /sbin/ip addr add $VIP/$NETMASK dev $INTERFACE #将VIP绑定到该服务器上 /sbin/arping -q -c 3 -A $VIP -I $INTERFACE exit 0 else /sbin/ip addr del $VIP/$NETMASK dev $INTERFACE #将VIP从该服务器上删除 exit 0 fi exit 1 #如果返回1,sentinel会一直执行这个脚本
搭建redis哨兵的好处是只负责存活检测,负载很小,可以监控多个主,一旦故障,随时可以切换主从关系。
redis-cli -h 192.168.13.230 -p 26379 info sentinel for i in {230..233}; do redis-cli -h 192.168.13.$i -p 26379 SENTINEL monitor updns-sentinel 192.168.13.232 1023 2 done for i in {230..233}; do redis-cli -h 192.168.13.$i -p 26379 SENTINEL monitor robin-sentinel 192.168.13.232 1024 2 done
如果是centos7系统,要切换ruby虚拟环境:
source /opt/rh/rh-ruby23/enable
要让集群正常工作至少需要3个主节点,在这里我们要创建6个Redis节点,其中三个为主节点,三个为从节点,对应的redis节点的ip和端口对应关系如下
#!/bin/sh ports="6379" CIP="10.243.48.18 10.243.28.123 10.243.3.60 10.243.6.30 10.243.20.88 10.243.42.95 10.243.17.94 10.243.45.153 10.243.32.80 10.243.31.46" QUERY="/usr/local/redis/bin/redis-trib.rb create --replicas 1" for port in $ports;do for ip in $CIP;do #nc -vz $ip $port QUERY="$QUERY $ip:$port " done done echo $QUERY
磁盘损坏导致nodes.conf文件丢失而导致的noaddr error
#!/bin/bash #执行命令前,先要切换到这个ruby环境 #scl enable rh-ruby23 bash RDS_CMD='/opt/redis/bin/redis-cli' RDS_HOST='192.168.12.12' RDS_PORT=4000 # ==== 检查redis主从关系 ==== check(){ host=`echo $2|awk -F: '{print $1}'` port=`echo $2|awk -F: '{print $2}'` [ -z $host ] && host=$RDS_HOST [ -z $port ] && port=$RDS_PORT echo "------------------------------------------------" $RDS_CMD -c -h $host -p $port cluster info|grep --color cluster_state echo "------------------------------------------------" tmpfile=".redis.nodes" $RDS_CMD -c -h $host -p $port cluster nodes > $tmpfile i=1 for s in $(awk '/master/{print $1}' $tmpfile);do echo "No.$i Master ID: $s ------" echo -en " + Master: -> \t" awk '/'"$s"'.*master/{print $2}' $tmpfile echo -en " - Backup: -> \t" awk '/slave.*'"$s"'/{print $2}' $tmpfile|tr '\n' '\t' echo ((i++)) done echo "------------------------------------------------" echo "Fail Nodes:" echo "------------------------------------------------" $RDS_CMD -c -h $host -p $port cluster nodes| grep -iE 'handshake|fail' } addmaster(){ [ -z $2 ] && echo "$0 addmaster newnode:6379" && exit 0 host=`echo $2|awk -F: '{print $1}'` port=`echo $2|awk -F: '{print $2}'` [ -z $host ] || [ -z $port ] && echo "$2 must is 1.1.1.1:6379" && exit 0 echo "$RDS_CMD --cluster add-node $host:$port <集群现有节点IP>:<端口>" } # ==== 改变从节点的master ==== addslave(){ [ -z $2 ] && echo "$0 addslave newnode:6379" && exit 0 host=`echo $2|awk -F: '{print $1}'` port=`echo $2|awk -F: '{print $2}'` [ -z $host ] || [ -z $port ] && echo "$2 must is 1.1.1.1:6379" && exit 0 #echo "$RDS_CMD --cluster add-node $host:$port <集群现有节点IP>:<端口> --cluster-slave" echo "$RDS_CMD --cluster add-node $host:$port <集群现有节点IP>:<端口> --cluster-slave --cluster-master-id <目 标Master的NodeID>" } # ==== 清理脑裂的节点 ==== cleanall(){ [ -z $2 ] && echo "$0 addmaster newnode:6379" && exit 0 host=`echo $2|awk -F: '{print $1}'` port=`echo $2|awk -F: '{print $2}'` hosts=$($RDS_CMD -h $host -p $port cluster nodes|grep -v fail|awk '{print $2}') for zz in $hosts;do host=`echo $zz|awk -F: '{print $1}'` port=`echo $zz|awk -F: '{print $2}'` fail_nodes=$($RDS_CMD -h $host -p $port cluster nodes|grep -iE 'handshake|fail'| awk '{print $1}') if [ -z "${fail_nodes[@]}" ];then echo "$host:$port ---------------> Cluster: OK" else for nodeid in ${fail_nodes[@]}; do echo $RDS_CMD -h $host -p $port cluster forget $nodeid $RDS_CMD -h $host -p $port cluster forget $nodeid done fi done } case $1 in check) check $*;; cleanall) cleanall $*;; addmaster) addmaster $*;; addslave) addslave $*;; *) echo "check | cleanall | addmaster | addslave"; esac
conf.version = 1 id = redis-yupoo-cluster log.file = /disk/ssd1/logs/redis-shake/redis-shake.log log.level = info pid_path = /var/run/ system_profile = 4310 http_profile = 4320 parallel = 5 source.type = cluster source.address = 192.168.12.17:1020 source.password_raw = source.auth_type = auth source.tls_enable = false source.tls_skip_verify = false source.rdb.input = source.rdb.parallel = 0 source.rdb.special_cloud = target.type = cluster target.address = 192.168.12.17:1026 target.password_raw = target.auth_type = auth target.db = -1 target.dbmap = target.tls_enable = false target.tls_skip_verify = false target.rdb.output = local_dump target.version = fake_time = filter.db.whitelist = filter.db.blacklist = filter.key.whitelist = filter.key.blacklist = filter.slot = filter.command.whitelist = filter.command.blacklist = filter.lua = false metric = true metric.print_log = false scan.special_cloud = scan.key_file = resume_from_break_point = false replace_hash_tag = false sender.delay_channel_size = 65535 sender.size = 104857600 sender.count = 4095 scan.key_number = 20 qps = 200000 key_exists = rewrite keep_alive = 30 big_key_threshold = 524288000
for process in $(supervisorctl status|awk '/redis-/{if($0 ~ /FATAL/) print $1}');do state=$(supervisorctl status|awk '/'"$process"' /{print $2}') send_warning 0 Supervisord $LIP `hostname` $state "$process" #supervisorctl restart $process done
起因:主redis硬件故障重启不通
192.168.13.250 不通,导致从同步失效,redis-cli info replication 显示 master_link_status:down
处理过程:
for i in {11..18};do for j in {1023..1028};do /opt/redis/bin/redis-cli -h 192.168.12.$i -p $j REPLICAOF 192.168.13.232 $j; done; done