目录

Redis 极快的内存KV数据库

参考资料

Redis配置文件

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

redis overcommit_memory

overcommit_memory参数说明:

设置内存分配策略(可选,根据服务器的实际情况进行设置)

/proc/sys/vm/overcommit_memory

可选值:0、1、2。

注意:redis在dump数据的时候,会fork出一个子进程,理论上child进程所占用的内存和parent是一样的,比如parent占用的内存为8G,这个时候也要同样分配8G的内存给child,如果内存无法负担,往往会造成redis服务器的down机或者IO负载过高,效率下降。所以这里比较优化的内存分配策略应该设置为 1(表示内核允许分配所有的物理内存,而不管当前的内存状态如何)。

什么是Overcommit和OOM

在Unix中,当一个用户进程使用malloc()函数申请内存时,假如返回值是NULL,则这个进程知道当前没有可用内存空间,就会做相应的处理工作。许多进程会打印错误信息并退出。

Linux使用另外一种处理方式,它对大部分申请内存的请求都回复“yes”,以便能跑更多更大的程序。因为申请内存后,并不会马上使用内存。这种技术叫做Overcommit。

当内存不足时,会发生OOM killer(OOM=out-of-memory)。它会选择杀死一些进程(用户态进程,不是内核线程),以便释放内存。

Overcommit的策略

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和淘汰策略

在redis中,允许用户设置最大使用内存大小maxmemory (需要配合maxmemory-policy使用),设置为0表示不限制;

当redis内存数据集快到达maxmemory时,redis会实行数据淘汰策略。

Redis提供6种数据淘汰策略

  1. volatile-lru:从已设置过期时间的内存数据集中挑选最近最少使用的数据 淘汰;
  2. volatile-ttl: 从已设置过期时间的内存数据集中挑选即将过期的数据 淘汰;
  3. volatile-random:从已设置过期时间的内存数据集中任意挑选数据 淘汰;
  4. allkeys-lru:从内存数据集中挑选最近最少使用的数据 淘汰;
  5. allkeys-random:从数据集中任意挑选数据 淘汰;
  6. no-enviction(驱逐):禁止驱逐数据。(默认淘汰策略。当redis内存数据达到maxmemory,在该策略下,直接返回OOM错误);
  • 关于maxmemory设置,通过在redis.conf中maxmemory参数设置,或者通过命令CONFIG SET动态修改
  • 关于数据淘汰策略的设置,通过在redis.conf中的maxmemory-policy参数设置,或者通过命令CONFIG SET动态修改

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
注意,在64bit系统下,maxmemory设置为0表示不限制Redis内存使用,在32bit系统下,maxmemory不能超过3GB;

从同步到主机

另外,对一个从属服务器执行命令 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的连接情况

redis-cli -h 192.168.13.168 -p 1020 client list

检查redis的同步健康

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  
}

快照和 AOF备份

RDB 快照持久化

首先我们看下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 # 生成文件名

AOF 追加持久化

由于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的方式能相对更完善保留数据,让我们更为放心。

服务重启之后Redis默认加载流程,优先加载AOF文件,如果没有再考虑加载RDB文件

定时备份脚本

#!/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

Redis Sentinel的配置和实战

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 的哨兵,要满足两个条件:

  1. 第一,拿到半数以上的赞成票;
  2. 第二,拿到的票数同时还需要大于等于哨兵配置文件中的 quorum 值。

以 3 个哨兵为例,假设此时的 quorum 设置为 2,那么,任何一个想成为 Leader 的哨兵只要拿到 2 张赞成票,就可以了。

supervisord托管的配置

[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

redis-sentinel哨兵配置

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

哨兵failover切换脚本

#!/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

Redis Cluster部署

本文使用的redis版本为5.0.3,不适用4.x/3.x版本,4.x/3.x版本请使用redis-trib.rb脚本

如果是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

Redis Cluster 扩容和迁移

一定要先提前从 consul 或者 lvs 负载均衡中摘掉要迁移的节点,再进行 slots 迁移

磁盘损坏导致nodes.conf文件丢失而导致的noaddr error

redis-cli-ops.sh

#!/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

Redis-shake同步集群

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
redis shake有死机情况,需要监控重启
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故障修复笔记

起因:主redis硬件故障重启不通

192.168.13.250 不通,导致从同步失效,redis-cli info replication 显示 master_link_status:down

处理过程:

  1. 提升主机 192.168.13.233 为新主, 先在线切换角色:REPLICAOF NO ONE,然后删除文件里的replicaof配置
  2. 对192.168.12.11~18一批主机,批量修改 REPLICAOF→新主机,优先在线修改,生效速度快,再在配置里加上replicaof配置
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
  1. 要重新配置consul中的redis-new.service.upyun的解析
  2. 注意防火墙规则,发现 /etc/rc.local软链错误,没有加载正确的白名单被拦截了
  3. 还有一些容器里的程序,或者写死ip的,都需要修改后重启
  4. 13.250变成从库后,会导致写入失败,读取正常