redis使用详解

1. redis查看当前所有的key

复制代码代码如下:

KEYS *

2. 查看当前redis的配置信息
复制代码代码如下:

CONFIG GET *

3. MISCONF Redis is configured to save RDB snapshots, but is currently not able to persist on disk. Commands that may modify the data set are disabled. Please check Redis logs for details about the error.

强制停止redis快照导致,redis运行用户没有权限写rdb文件或者磁盘空间满了,解决办法:

复制代码代码如下:

config set stop-writes-on-bgsave-error no

例如:
复制代码代码如下:

set 'name' 'shenhui'
-MISCONF Redis is configured to save RDB snapshots, but is currently not able to persist on disk. Commands that may modify the data set are disabled. Please check Redis logs for details about the error.
config set stop-writes-on-bgsave-error no
+OK
set 'name' 'shenhui'
+OK

4. redis 127.0.0.1:6379> CONFIG SET logfile "/var/log/redis/redis-server.log"
(error) ERR Unsupported CONFIG parameter: logfile

logfile 不能通过set动态设置

5.(error) OOM command not allowed when used memory >
设置了maxmemory的选项,redis内存使用达到上限。
可以通过设置LRU算法来删除部分key,释放空间。
默认是按照过期时间的,如果set时候没有加上过期时间就会导致数据写满maxmemory。
如果不设置maxmemory或者设置为0 64位系统不限制内存,32位系统最多使用3GB内存。

volatile-lru -> 根据LRU算法生成的过期时间来删除。
allkeys-lru -> 根据LRU算法删除任何key。
volatile-random -> 根据过期设置来随机删除key。
allkeys->random -> 无差别随机删。
volatile-ttl -> 根据最近过期时间来删除(辅以TTL)
noeviction -> 谁也不删,直接在写操作时返回错误。

6. reids日志位置

logfile 日志记录方式,默认值为stdout,如果设置为stdout且以守护进程方式运行,那么日志会被重定向到/dev/null,也就是不记日志。

7. reids配置参数详解

复制代码代码如下:

#daemonize no  默认情况下, redis 不是在后台运行的,如果需要在后台运行,把该项的值更改为 yes
daemonize yes
#  当 redis 在后台运行的时候, Redis 默认会把 pid 文件放在 /var/run/redis.pid ,你可以配置到其他地址。
#  当运行多个 redis 服务时,需要指定不同的 pid 文件和端口
pidfile /var/run/redis_6379.pid
#  指定 redis 运行的端口,默认是 6379
port 6379
#  在高并发的环境中,为避免慢客户端的连接问题,需要设置一个高速后台日志
tcp-backlog 511
#  指定 redis 只接收来自于该 IP 地址的请求,如果不进行设置,那么将处理所有请求
# bind 192.168.1.100 10.0.0.1
# bind 127.0.0.1
#  设置客户端连接时的超时时间,单位为秒。当客户端在这段时间内没有发出任何指令,那么关闭该连接
# 0 是关闭此设置
timeout 0
# TCP keepalive
#  在 Linux 上,指定值(秒)用于发送 ACKs 的时间。注意关闭连接需要双倍的时间。默认为 0 。
tcp-keepalive 0
#  指定日志记录级别,生产环境推荐 notice
# Redis 总共支持四个级别: debug 、 verbose 、 notice 、 warning ,默认为 verbose
# debug     记录很多信息,用于开发和测试
# varbose   有用的信息,不像 debug 会记录那么多
# notice    普通的 verbose ,常用于生产环境
# warning   只有非常重要或者严重的信息会记录到日志
loglevel notice
#  配置 log 文件地址
#  默认值为 stdout ,标准输出,若后台模式会输出到 /dev/null 。
logfile /var/log/redis/redis.log
#  可用数据库数
#  默认值为 16 ,默认数据库为 0 ,数据库范围在 0- ( database-1 )之间
databases 16
################################ 快照#################################
#  保存数据到磁盘,格式如下 :
#   save  
#    指出在多长时间内,有多少次更新操作,就将数据同步到数据文件 rdb 。
#    相当于条件触发抓取快照,这个可以多个条件配合
#    比如默认配置文件中的设置,就设置了三个条件
#   save 900 1  900 秒内至少有 1 个 key 被改变
#   save 300 10  300 秒内至少有 300 个 key 被改变
#   save 60 10000  60 秒内至少有 10000 个 key 被改变
# save 900 1
# save 300 10
# save 60 10000
#  后台存储错误停止写。
stop-writes-on-bgsave-error yes
#  存储至本地数据库时(持久化到 rdb 文件)是否压缩数据,默认为 yes
rdbcompression yes
# RDB 文件的是否直接偶像 chcksum
rdbchecksum yes
#  本地持久化数据库文件名,默认值为 dump.rdb
dbfilename dump.rdb
#  工作目录
#  数据库镜像备份的文件放置的路径。
#  这里的路径跟文件名要分开配置是因为 redis 在进行备份时,先会将当前数据库的状态写入到一个临时文件中,等备份完成,
#  再把该该临时文件替换为上面所指定的文件,而这里的临时文件和上面所配置的备份文件都会放在这个指定的路径当中。
# AOF 文件也会存放在这个目录下面
#  注意这里必须制定一个目录而不是文件
dir /var/lib/redis-server/
################################# 复制 #################################
#  主从复制 . 设置该数据库为其他数据库的从数据库 .
#  设置当本机为 slav 服务时,设置 master 服务的 IP 地址及端口,在 Redis 启动时,它会自动从 master 进行数据同步
# slaveof 
#  当 master 服务设置了密码保护时 ( 用 requirepass 制定的密码 )
# slave 服务连接 master 的密码
# masterauth 
#  当从库同主机失去连接或者复制正在进行,从机库有两种运行方式:
# 1)  如果 slave-serve-stale-data 设置为 yes( 默认设置 ) ,从库会继续响应客户端的请求
# 2)  如果 slave-serve-stale-data 是指为 no ,出去 INFO 和 SLAVOF 命令之外的任何请求都会返回一个
#     错误 "SYNC with master in progress"
slave-serve-stale-data yes
#  配置 slave 实例是否接受写。写 slave 对存储短暂数据(在同 master 数据同步后可以很容易地被删除)是有用的,但未配置的情况下,客户端写可能会发送问题。
#  从 Redis2.6 后,默认 slave 为 read-only
slaveread-only yes
#  从库会按照一个时间间隔向主库发送 PINGs. 可以通过 repl-ping-slave-period 设置这个时间间隔,默认是 10 秒
# repl-ping-slave-period 10
# repl-timeout  设置主库批量数据传输时间或者 ping 回复时间间隔,默认值是 60 秒
#  一定要确保 repl-timeout 大于 repl-ping-slave-period
# repl-timeout 60
#  在 slave socket 的 SYNC 后禁用 TCP_NODELAY
#  如果选择“ yes ” ,Redis 将使用一个较小的数字 TCP 数据包和更少的带宽将数据发送到 slave , 但是这可能导致数据发送到 slave 端会有延迟 , 如果是 Linux kernel 的默认配置,会达到 40 毫秒 .
#  如果选择 "no" ,则发送数据到 slave 端的延迟会降低,但将使用更多的带宽用于复制 .
repl-disable-tcp-nodelay no
#  设置复制的后台日志大小。
#  复制的后台日志越大, slave 断开连接及后来可能执行部分复制花的时间就越长。
#  后台日志在至少有一个 slave 连接时,仅仅分配一次。
# repl-backlog-size 1mb
#  在 master 不再连接 slave 后,后台日志将被释放。下面的配置定义从最后一个 slave 断开连接后需要释放的时间(秒)。
# 0 意味着从不释放后台日志
# repl-backlog-ttl 3600
#  如果 master 不能再正常工作,那么会在多个 slave 中,选择优先值最小的一个 slave 提升为 master ,优先值为 0 表示不能提升为 master 。
slave-priority 100
#  如果少于 N 个 slave 连接,且延迟时间 <=M 秒,则 master 可配置停止接受写操作。
#  例如需要至少 3 个 slave 连接,且延迟 <=10 秒的配置:
# min-slaves-to-write 3
# min-slaves-max-lag 10
#  设置 0 为禁用
#   默认 min-slaves-to-write 为 0 (禁用), min-slaves-max-lag 为 10
################################## 安全 ###################################
#  设置客户端连接后进行任何其他指定前需要使用的密码。
#  警告:因为 redis 速度相当快,所以在一台比较好的服务器下,一个外部的用户可以在一秒钟进行 150K 次的密码尝试,这意味着你需要指定非常非常强大的密码来防止暴力破解
# requirepass foobared
#  命令重命名 .
#  在一个共享环境下可以重命名相对危险的命令。比如把 CONFIG 重名为一个不容易猜测的字符。
#  举例 :
# rename-command CONFIG b840fc02d524045429941cc15f59e41cb7be6c52
#  如果想删除一个命令,直接把它重命名为一个空字符 "" 即可,如下:
# rename-command CONFIG ""
################################### 约束###################################
#设置同一时间最大客户端连接数,默认无限制, 
#Redis 可以同时打开的客户端连接数为 Redis 进程可以打开的最大文件描述符数,
#如果设置  maxclients 0 ,表示不作限制。
#当客户端连接数到达限制时, Redis 会关闭新的连接并向客户端返回 max number of clients reached 错误信息
# maxclients 10000
#  指定 Redis 最大内存限制, Redis 在启动时会把数据加载到内存中,达到最大内存后, Redis 会按照清除策略尝试清除已到期的 Key
#  如果 Redis 依照策略清除后无法提供足够空间,或者策略设置为 ”noeviction” ,则使用更多空间的命令将会报错,例如 SET, LPUSH 等。但仍然可以进行读取操作
#  注意: Redis 新的 vm 机制,会把 Key 存放内存, Value 会存放在 swap 区
#  该选项对 LRU 策略很有用。
# maxmemory 的设置比较适合于把 redis 当作于类似 memcached 的缓存来使用,而不适合当做一个真实的 DB 。
#  当把 Redis 当做一个真实的数据库使用的时候,内存使用将是一个很大的开销
# maxmemory 
#  当内存达到最大值的时候 Redis 会选择删除哪些数据?有五种方式可供选择
# volatile-lru ->  利用 LRU 算法移除设置过过期时间的 key (LRU: 最近使用  Least RecentlyUsed )
# allkeys-lru ->  利用 LRU 算法移除任何 key
# volatile-random ->  移除设置过过期时间的随机 key
# allkeys->random -> remove a randomkey, any key
# volatile-ttl ->  移除即将过期的 key(minor TTL)
# noeviction ->  不移除任何可以,只是返回一个写错误
#  注意:对于上面的策略,如果没有合适的 key 可以移除,当写的时候 Redis 会返回一个错误
#  默认是 :  volatile-lru
# maxmemory-policy volatile-lru  
# LRU  和  minimal TTL 算法都不是精准的算法,但是相对精确的算法 ( 为了节省内存 ) ,随意你可以选择样本大小进行检测。
# Redis 默认的灰选择 3 个样本进行检测,你可以通过 maxmemory-samples 进行设置
# maxmemory-samples 3
############################## AOF###############################
#  默认情况下, redis 会在后台异步的把数据库镜像备份到磁盘,但是该备份是非常耗时的,而且备份也不能很频繁,如果发生诸如拉闸限电、拔插头等状况,那么将造成比较大范围的数据丢失。
#  所以 redis 提供了另外一种更加高效的数据库备份及灾难恢复方式。
#  开启 append only 模式之后, redis 会把所接收到的每一次写操作请求都追加到 appendonly.aof 文件中,当 redis 重新启动时,会从该文件恢复出之前的状态。
#  但是这样会造成 appendonly.aof 文件过大,所以 redis 还支持了 BGREWRITEAOF 指令,对 appendonly.aof 进行重新整理。
#  你可以同时开启 asynchronous dumps 和  AOF
appendonly no
# AOF 文件名称  ( 默认 : "appendonly.aof")
# appendfilename appendonly.aof
# Redis 支持三种同步 AOF 文件的策略 :
# no:  不进行同步,系统去操作  . Faster.
# always: always 表示每次有写操作都进行同步 . Slow, Safest.
# everysec:  表示对写操作进行累积,每秒同步一次 . Compromise.
#  默认是 "everysec" ,按照速度和安全折中这是最好的。
#  如果想让 Redis 能更高效的运行,你也可以设置为 "no" ,让操作系统决定什么时候去执行
#  或者相反想让数据更安全你也可以设置为 "always"
#  如果不确定就用  "everysec".
# appendfsync always
appendfsync everysec
# appendfsync no
# AOF 策略设置为 always 或者 everysec 时,后台处理进程 ( 后台保存或者 AOF 日志重写 ) 会执行大量的 I/O 操作
#  在某些 Linux 配置中会阻止过长的 fsync() 请求。注意现在没有任何修复,即使 fsync 在另外一个线程进行处理
#  为了减缓这个问题,可以设置下面这个参数 no-appendfsync-on-rewrite
no-appendfsync-on-rewrite no
# AOF  自动重写
#  当 AOF 文件增长到一定大小的时候 Redis 能够调用  BGREWRITEAOF  对日志文件进行重写
#  它是这样工作的: Redis 会记住上次进行些日志后文件的大小 ( 如果从开机以来还没进行过重写,那日子大小在开机的时候确定 )
#  基础大小会同现在的大小进行比较。如果现在的大小比基础大小大制定的百分比,重写功能将启动
#  同时需要指定一个最小大小用于 AOF 重写,这个用于阻止即使文件很小但是增长幅度很大也去重写 AOF 文件的情况
#  设置  percentage 为 0 就关闭这个特性
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
################################ LUASCRIPTING #############################
# 一个 Lua 脚本最长的执行时间为 5000 毫秒( 5 秒),如果为 0 或负数表示无限执行时间。
lua-time-limit 5000
################################LOW LOG################################
# Redis Slow Log  记录超过特定执行时间的命令。执行时间不包括 I/O 计算比如连接客户端,返回结果等,只是命令执行时间
#  可以通过两个参数设置 slow log :一个是告诉 Redis 执行超过多少时间被记录的参数 slowlog-log-slower-than( 微妙 ) ,
#  另一个是 slow log 的长度。当一个新命令被记录的时候最早的命令将被从队列中移除
#  下面的时间以微妙为单位,因此 1000000 代表一秒。
#  注意指定一个负数将关闭慢日志,而设置为 0 将强制每个命令都会记录
slowlog-log-slower-than 10000
#  对日志长度没有限制,只是要注意它会消耗内存
#  可以通过  SLOWLOG RESET 回收被慢日志消耗的内存
#  推荐使用默认值 128 ,当慢日志超过 128 时,最先进入队列的记录会被踢出
slowlog-max-len 128
################################  事件通知  #############################
#  当事件发生时, Redis 可以通知 Pub/Sub 客户端。
#  可以在下表中选择 Redis 要通知的事件类型。事件类型由单个字符来标识:
# K     Keyspace 事件,以 _keyspace@_ 的前缀方式发布
# E     Keyevent 事件,以 _keysevent@_ 的前缀方式发布
# g     通用事件(不指定类型),像 DEL, EXPIRE, RENAME, …
# $     String 命令
# s     Set 命令
# h     Hash 命令
# z     有序集合命令
# x     过期事件(每次 key 过期时生成)
# e     清除事件(当 key 在内存被清除时生成)
# A     g$lshzxe 的别称,因此 ”AKE” 意味着所有的事件
# notify-keyspace-events 带一个由 0 到多个字符组成的字符串参数。空字符串意思是通知被禁用。
#  例子:启用 list 和通用事件:
# notify-keyspace-events Elg
#  默认所用的通知被禁用,因为用户通常不需要改特性,并且该特性会有性能损耗。
#  注意如果你不指定至少 K 或 E 之一,不会发送任何事件。
notify-keyspace-events “”
##############################  高级配置  ###############################
#  当 hash 中包含超过指定元素个数并且最大的元素没有超过临界时,
# hash 将以一种特殊的编码方式(大大减少内存使用)来存储,这里可以设置这两个临界值
# Redis Hash 对应 Value 内部实际就是一个 HashMap ,实际这里会有 2 种不同实现,
#  这个 Hash 的成员比较少时 Redis 为了节省内存会采用类似一维数组的方式来紧凑存储,而不会采用真正的 HashMap 结构,对应的 valueredisObject 的 encoding 为 zipmap,
#  当成员数量增大时会自动转成真正的 HashMap, 此时 encoding 为 ht 。
hash-max-zipmap-entries 512
hash-max-zipmap-value 64  
#  和 Hash 一样,多个小的 list 以特定的方式编码来节省空间。
# list 数据类型节点值大小小于多少字节会采用紧凑存储格式。
list-max-ziplist-entries 512
list-max-ziplist-value 64
# set 数据类型内部数据如果全部是数值型,且包含多少节点以下会采用紧凑格式存储。
set-max-intset-entries 512
#  和 hashe 和 list 一样 , 排序的 set 在指定的长度内以指定编码方式存储以节省空间
# zsort 数据类型节点值大小小于多少字节会采用紧凑存储格式。
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
# Redis 将在每 100 毫秒时使用 1 毫秒的 CPU 时间来对 redis 的 hash 表进行重新 hash ,可以降低内存的使用
#  当你的使用场景中,有非常严格的实时性需要,不能够接受 Redis 时不时的对请求有 2 毫秒的延迟的话,把这项配置为 no 。
#  如果没有这么严格的实时性要求,可以设置为 yes ,以便能够尽可能快的释放内存
activerehashing yes
# 客户端的输出缓冲区的限制,因为某种原因客户端从服务器读取数据的速度不够快,
# 可用于强制断开连接(一个常见的原因是一个发布 / 订阅客户端消费消息的速度无法赶上生产它们的速度)。
#  可以三种不同客户端的方式进行设置:
# normal ->  正常客户端
# slave  -> slave 和 MONITOR 客户端
# pubsub ->  至少订阅了一个 pubsub channel 或 pattern 的客户端
#  每个 client-output-buffer-limit 语法 :
# client-output-buffer-limit   
#  一旦达到硬限制客户端会立即断开,或者达到软限制并保持达成的指定秒数(连续)。
#  例如,如果硬限制为 32 兆字节和软限制为 16 兆字节 /10 秒,客户端将会立即断开
#  如果输出缓冲区的大小达到 32 兆字节,客户端达到 16 兆字节和连续超过了限制 10 秒,也将断开连接。
#  默认 normal 客户端不做限制,因为他们在一个请求后未要求时(以推的方式)不接收数据,
#  只有异步客户端可能会出现请求数据的速度比它可以读取的速度快的场景。
#  把硬限制和软限制都设置为 0 来禁用该特性
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit slave 256mb 64mb60
client-output-buffer-limit pubsub 32mb 8mb60
# Redis 调用内部函数来执行许多后台任务,如关闭客户端超时的连接,清除过期的 Key ,等等。
#  不是所有的任务都以相同的频率执行,但 Redis 依照指定的“ Hz ”值来执行检查任务。
#  默认情况下,“ Hz ”的被设定为 10 。
#  提高该值将在 Redis 空闲时使用更多的 CPU 时,但同时当有多个 key 同时到期会使 Redis 的反应更灵敏,以及超时可以更精确地处理。
#  范围是 1 到 500 之间,但是值超过 100 通常不是一个好主意。
#  大多数用户应该使用 10 这个预设值,只有在非常低的延迟的情况下有必要提高最大到 100 。
hz 10  
#  当一个子节点重写 AOF 文件时,如果启用下面的选项,则文件每生成 32M 数据进行同步。
aof-rewrite-incremental-fsync yes

8.Redis官方文档对VM的使用建议:

当你的key很小而value很大时,使用VM的效果会比较好.因为这样节约的内存比较大.
当你的key不小时,可以考虑使用一些非常方法将很大的key变成很大的value,比如你可以考虑将key,value组合成一个新的value.
最好使用linux ext3 等对稀疏文件支持比较好的文件系统保存你的swap文件.
vm-max-threads这个参数,可以设置访问swap文件的线程数,设置最好不要超过机器的核数,如果设置为0,那么所有对swap文件的操作都是串行的.可能会造成比较长时间的延迟,但是对数据完整性有很好的保证.
有了VM功能,Redis终于摆脱了受内存容量限制的噩梦了,似乎我们可以称其为Redis数据库了,我们还可以想象又有多少新的用法可以产生.当然,希望这一功能不会对Redis原有的非常牛B的内存存储性能有所影响.

9. redis修改持久化路径和日志路径

复制代码代码如下:

vim redis.conf
logfile /data/redis_cache/logs/redis.log #日志路径

dir /data/redis_cache #持久化路径,修改后 记得要把dump.rdb持久化文件拷贝到/data/redis_cache下

先杀掉redis,拷贝dump.rdb,启动

10. 清redis缓存

复制代码代码如下:

./redis-cli    #进入
dbsize
flushall     #执行
exit

11. 删除redis当前数据库中的所有Key

复制代码代码如下:

flushdb

12.删除redis所有数据库中的key
复制代码代码如下:

flushall

redis3.0 cluster功能介绍


redis从3.0开始支持集群功能。redis集群采用无中心节点方式实现,无需proxy代理,客户端直接与redis集群的每个节点连接,根据同样的hash算法计算出key对应的slot,然后直接在slot对应的redis上执行命令。在redis看来,响应时间是最苛刻的条件,增加一层带来的开销是redis不原因接受的。因此,redis实现了客户端对节点的直接访问,为了去中心化,节点之间通过gossip协议交换互相的状态,以及探测新加入的节点信息。redis集群支持动态加入节点,动态迁移slot,以及自动故障转移。

集群架构

每个节点都会跟其他节点保持连接,用来交换彼此的信息。节点组成集群的方式使用cluster meet命令,meet命令可以让两个节点相互握手,然后通过gossip协议交换信息。如果一个节点r1在集群中,新节点r2加入的时候与r1节点握手,r1节点会把集群内的其他节点信息通过gossip协议发送给r2,r2会一一与这些节点完成握手,从而加入到集群中。
节点在启动的时候会生成一个全局的标识符,并持久化到配置文件,在节点与其他节点握手后,这些信息也都持久化下来。节点与其他节点通信,标识符是它唯一的标识,而不是IP、PORT地址。如果一个节点移动位置导致IP、PORT地址发生变更,集群内的其他节点能把该节点的IP、PORT地址纠正过来。

集群数据分布

集群数据以数据分布表的方式保存在各个slot上。默认的数据分布表默认含有16384个slot。
key与slot映射使用的CRC16算法,即:slot = CRC16(key) mod 16384。
集群只有在16384个slot都有对应的节点才能正常工作。slot可以动态的分配、删除和迁移。
每个节点会保存一份数据分布表,节点会将自己的slot信息发送给其他节点,发送的方式使用一个unsigned char的数组,数组长度为16384/8。每个bit标识为0或者1来标识某个slot是否是它负责的。

使用这样的方式传输,就能大大减少数据分布表的字节。这种方式使用的字节为2048,如果纯粹的传递数据分布表,那边一个slot至少需要2字节的slot值+2字节port+4字节ip,共8*16384=131072字节,或者以ip、port为键,把节点对应的slot放在后面,那么也至少需要2*16384加上节点的数量,也远远大于2048字节。由于节点间不停的在传递数据分布表,所以为了节省带宽,redis选择了只传递自己的分布数据。但这样的方式也会带来管理方面的麻烦,如果一个节点删除了自己负责的某个slot,这样该节点传递给其他节点数据分布表的slot标识为0,而redis采用了bitmapTestBit方法,只处理slot为1的节点,而并未把每个slot与收到的数据分布表对比,从而产生了节点间数据分布表视图的不一致。这种问题目前只能通过使用者来避免。

redis目前还支持slot的迁移,可以把一个slot从一个节点迁移到另一个节点,节点上的数据需要使用者通过cluster getkeysinslot去除迁移slot上的key,然后执行migrate命令一个个迁移到新节点。具体细节会在下面的slot迁移章节介绍。

集群访问

客户端在初始化的时候只需要知道一个节点的地址即可,客户端会先尝试向这个节点执行命令,比如“get key”,如果key所在的slot刚好在该节点上,则能够直接执行成功。如果slot不在该节点,则节点会返回MOVED错误,同时把该slot对应的节点告诉客户端。客户端可以去该节点执行命令。目前客户端有两种做法获取数据分布表,一种就是客户端每次根据返回的MOVED信息缓存一个slot对应的节点,但是这种做法在初期会经常造成访问两次集群。还有一种做法是在节点返回MOVED信息后,通过cluster nodes命令获取整个数据分布表,这样就能每次请求到正确的节点,一旦数据分布表发生变化,请求到错误的节点,返回MOVED信息后,重新执行cluster nodes命令更新数据分布表。

在访问集群的时候,节点可能会返回ASK错误。这种错误是在key对应的slot正在进行数据迁移时产生的,这时候向slot的原节点访问,如果key在迁移源节点上,则该次命令能直接执行。如果key不在迁移源节点上,则会返回ASK错误,描述信息会附上迁移目的节点的地址。客户端这时候要先向迁移目的节点发送ASKING命令,然后执行之前的命令。

这些细节一般都会被客户端sdk封装起来,使用者完全感受不到访问的是集群还是单节点。

集群支持hash tags功能,即可以把一类key定位到同一个节点,tag的标识目前支持配置,只能使用{},redis处理hash tag的逻辑也很简单,redis只计算从第一次出现{,到第一次出现}的substring的hash值,substring为空,则仍然计算整个key的值,这样对于foo{}{bar}、{foo}{bar}、foo这些冲突的{},也能取出tag值。使用者需遵循redis的hash tag规范。

127.0.0.1:6379> CLUSTER KEYSLOT foo{hash_tag}
(integer) 2515
127.0.0.1:6379> CLUSTER KEYSLOT fooadfasdf{hash_tag}
(integer) 2515
127.0.0.1:6379> CLUSTER KEYSLOT fooadfasdfasdfadfasdf{hash_tag}
(integer) 2515
集群版本的redis能够支持全部的单机版命令。不过还是有些命令会有些限制。

订阅在使用上与单机版没有任何区别。订阅功能通过集群间共享publish消息实现的,客户端可以向任意一个节点(包括slave节点)订阅消息,然后在一个节点上执行的publish命令,该节点会把该命令传播给集群内的每个节点,这样订阅该消息的客户端就能收到该命令。

redis集群版只使用db0,select命令虽然能够支持select 0。其他的db都会返回错误。
127.0.0.1:6379> select 0
OK
127.0.0.1:6379> select 1
(error) ERR SELECT is not allowed in cluster mode
redis集群版对多key命令的支持,只能支持多key都在同一个slot上,即使多个slot在一个节点上也不行。

127.0.0.1:6379> mget key7 key28
(error) CROSSSLOT Keys in request don't hash to the same slot
事务的支持只能在也一个slot上完成。MULTI命令之后的命令的key必须都在同一个slot上,如果某条命令的key对应不在相同的slot上,则事务直接回滚。迁移的时候,在迁移源节点执行命令的key必须在移原节点上存在,否则事务就会回滚。在迁移目的节点执行的时候需要先执行ASKING命令再执行MULTI命令,这样接下来该slot的命令都能被执行。可以看出,对于单key和相同hash tags的事务,集群还是能很好的支持。

在迁移的时候有个地方需要注意,对于多key命令在迁移目的节点执行时,如果多个key全在该节点上,则命令无法执行。如下所示,key和key14939对应的slot为12539,执行命令的节点是迁移目的节点:

127.0.0.1:6379> asking
OK
127.0.0.1:6379> mget key key14939
(error) TRYAGAIN Multiple keys request during rehashing of slot

集群消息

集群间互相发送消息,使用另外的端口,所有的消息在该端口上完成,可以成为消息总线,这样可以做到不影响客户端访问redis,可见redis对于性能的追求。目前集群有如下几种消息:

CLUSTERMSG_TYPE_PING:gossip协议的ping消息。
CLUSTERMSG_TYPE_PONG:gossip协议的pong消息。
CLUSTERMSG_TYPE_MEET:握手消息。
CLUSTERMSG_TYPE_FAIL:master节点检测到,超过半数master认为某master离线,则发送fail消息。
CLUSTERMSG_TYPE_PUBLISH:publish消息,向其他节点推送消息。
CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST:故障转移时,slave发送向其他master投票请求。
CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK:故障转移时,其他master回应slave的请求。
CLUSTERMSG_TYPE_UPDATE:通知某节点,它负责的某些slot被另一个节点替换。
CLUSTERMSG_TYPE_MFSTART:手动故障转移时,slave请求master停止访问,从而对比两者的数据偏移量,可以达到一致。
集群节点间相互通信使用了gossip协议的push/pull方式,ping和pong消息,节点会把自己的详细信息和已经和自己完成握手的3个节点地址发送给对方,详细信息包括消息类型,集群当前的epoch,节点自己的epoch,节点复制偏移量,节点名称,节点数据分布表,节点master的名称,节点地址,节点flag为,节点所处的集群状态。节点根据自己的epoch和对方的epoch来决定哪些数据需要更新,哪些数据需要告诉对方更新。然后根据对方发送的其他地址信息,来发现新节点的加入,从而和新节点完成握手。

节点默认每秒在集群中的其他节点选择一个节点,发送ping消息。选择节点步骤是:

随机 5 个节点。
跳过断开连接和已经在ping还没收到pong响应的节点。
从筛选的节点中选择最近一次接收pong回复距离现在最旧的节点。
除了常规的选择节点外,对于那些一直未随机到节点,redis也有所支持。当有节点距离上一次接收到pong消息超过节点超时配置的一半,节点就会给这些节点发送ping消息。

ping消息会带上其他节点的信息,选择其他节点步骤是:

最多选择3个节点。
最多随机遍历6个节点,如果因为一些条件不能被选出,可能会不满3个。
忽略自己。
忽略正在握手的节点。
忽略带有 NOADDR 标识的节点。
忽略连接断开而且没有负责任何slot的节点。
ping消息会把发送节点的ping_sent改成当前时间,直到接收到pong消息,才更新ping_sent为0。当ping消息发送后,超过节点超时配置的一半,就会把发送节点的连接断开。超过节点超时配置,就会认为该节点已经下线。

接收到某个节点发来的ping或者pong消息,节点会更新对接收节点的认识。比如该节点主从角色是否变化,该节点负责的slot是否变化,然后获取消息带上的节点信息,处理新节点的加入。

slot更新这里要细说下。假设这里是节点A接收到节点B的消息。A节点会取出保存的B节点的分布表与消息的分布表进行对比,B节点如果是slave,则比较的是A节点保存的B的master的分布表和消息中的分布表。比较只是使用memcmp简单的比较两份数据是否一样,一样则无需处理,不一样就处理不一致的slot。更新的时候,这里只是处理消息中分布表为1的slot。如果和A节点保持一致或者该slot正准备迁移到A节点,则继续处理。如果slot产生了冲突,则以epoch大的为准。如果冲突的slot中有A自己负责的节点,而且B比A的epoch大导致需要更新slot为B负责,此时A负责的slot为0的时候,可以认为B是A的slave。这种情况经常发生在A由原来的master变成slave,B提升为master的场景下。

前面说到slot更新的时候,如果B比A的epoch大,则A更新对slot的认识。如果A比B的epoch大, 在redis接下来的逻辑会再次处理,A会给B发送update消息,B收到A发送的update消息,执行slot更新方法。这种情况也经常发生在主从切换的时候。第一种情况发生在新master把数据分布表推给旧master。第二种情况发生在旧master给新master发消息的时候,新master给旧master发送update消息。

slot迁移

redis cluster支持slot的动态迁移,迁移需要按照指定步骤进行,不然可能破坏当前的集群数据分布表。cluster setslot <slot> IMPORTING <node ID>命令在迁移目的节点执行,表示需要把该slot迁移到本节点。redis的cluster setslot命令提供了对迁移的支持。cluster setslot <slot> MIGRATING <node ID>命令在迁移源节点执行,表示需要把该slot迁出。cluster setslot <slot> NODE <node ID>在迁移完成后在迁移源节点和迁移目的节点执行后,表示迁移完成,数据分布表恢复稳定。如果需要取消迁移操作,在迁移源节点和迁移目的节点上执行cluster setslot <slot> STABLE。

下面先来看一下完整的迁移流程:

在迁移目的节点执行cluster setslot <slot> IMPORTING <node ID>命令,指明需要迁移的slot和迁移源节点。
在迁移源节点执行cluster setslot <slot> MIGRATING <node ID>命令,指明需要迁移的slot和迁移目的节点。
在迁移源节点执行cluster getkeysinslot获取该slot的key列表。
在迁移源节点执行对每个key执行migrate命令,该命令会同步把该key迁移到目的节点。
在迁移源节点反复执行cluster getkeysinslot命令,直到该slot的列表为空。
在迁移源节点和目的节点执行cluster setslot <slot> NODE <node ID>,完成迁移操作。

迁移过程中该slot允许继续有流量进来,redis保证了迁移过程中slot的正常访问。在迁移过程中,对于该slot的请求,如果key在源节点,则表示该key还没有迁移到目的节点。源节点会返回ASK错误,告诉客户端去迁移目的节点请求。这样新的key就直接写入到迁移目的节点了。客户端写入目的节点前需要发送ASKING命令,告诉迁移目的节点我是写入增量数据,没有ASKING命令,迁移目的节点会不认这次请求,返回MOVED错误,告诉客户端去迁移源节点请求。

凭借ASK机制和migrate命令,redis能保证slot的全量数据和增量数据都能导入目的节点。因为对于源节点返回了ASK错误,就能保证该key不在源节点上,那么它只会出现在目的节点或者不存在。所以客户端获取ASK错误到向目的节点无需保证原子性。然后migrate命令是个原子操作,它会等待目的节点写入成功才在源节点返回,保证了迁移期间不接受其他请求,从一个外部客户端的视角来看,在某个时间点上,迁移的键要么存在于源节点,要么存在于目的节点,但不会同时存在于源节点和目的节点。

迁移过程中几乎不影响用户使用,除了在多key的命令在迁移目的节点无法执行,这个在集群访问已经说明。

不过由于migrate命令是同步阻塞执行的,所以如果key对应的value很大,会增加阻塞时间,特别对于list、set、zset等结构如果value很大的话,redis并不关心这些结构的长度,而是直接以key为单位一次性迁移。同时迁移过程中大量执行migrate命令,会增加客户端的响应时间。

迁移的时候在master出现异常的时候,迁移工作需要做些处理。
如果在迁移过程中,源节点宕机,此时需做如下调整:
目的节点执行cluster setslot <slot> IMPORTING <node ID>命令,node ID为源节点group的新master
源节点group内的新master执行cluster setslot <slot> MIGRATING <node ID>命令,迁移该slot到目的节点。
这样可以继续完成迁移操作。

如果在迁移过程中,目的节点宕机,此时需做如下调整:
目的节点group内的新master执行cluster setslot <slot> IMPORTING <node ID>命令,node ID为源节点。
源节点执行cluster setslot <slot> MIGRATING <node ID>命令,迁移该slot到目的节点group内的新master。
这样也可以继续完成迁移操作。

主从复制

集群间节点支持主从关系,复制的逻辑基本复用了单机版的实现。不过还是有些地方需要注意。
首先集群间节点建立主从关系不再使用原有的SLAVEOF命令和SLAVEOF配置,而是通过cluster replicate命令,这保证了主从节点需要先完成握手,才能建立主从关系。
集群是不能组成链式主从关系的,也就是说从节点不能有自己的从节点。不过对于集群外的没开启集群功能的节点,redis并不干预这些节点去复制集群内的节点,但是在集群故障转移时,这些集群外的节点,集群不会处理。
集群内节点想要复制另一个节点,需要保证本节点不再负责任何slot,不然redis也是不允许的。
集群内的从节点在与其他节点通信的时候,传递的消息中数据分布表和epoch是master的值。

故障转移


集群主节点出现故障,发生故障转移时,其他主节点会把故障主节点的从节点自动提为主节点,原来的主节点恢复后,自动成为新主节点的从节点。

这里先说明,把一个master和它的全部slave描述为一个group,故障转移是以group为单位的,集群故障转移的方式跟sentinel的实现很类似。某个节点一段时间没收到心跳响应,则集群内的master会把该节点标记为pfail,类似sentinel的sdown。集群间的节点会交换相互的认识,超过一半master认为该异常master宕机,则这些master把异常master标记为fail,类似sentinel的odown。fail消息会被master广播出来。group的slave收到fail消息后开始竞选成为master。竞选的方式跟sentinel选主的方式类似,都是使用了raft协议,slave会从其他的master拉取选票,票数最多的slave被选为新的master,新master会马上给集群内的其他节点发送pong消息,告知自己角色的提升。其他slave接着开始复制新master。等旧master上线后,发现新master的epoch高于自己,通过gossip消息交互,把自己变成了slave。大致就是这么个流程。自动故障转移的方式跟sentinel很像,

redis还支持手动的故障转移,即通过在slave上执行cluster failover命令,可以让slave提升为master。failover命令支持传入FORCE和TAKEOVER参数。

FORCE:使用FORCE参数与sentinel的手动故障转移流程基本类似,强制开始一次故障转移。
不传入额外参数:如果主节点异常,则不能进行failover,主节点正常的情况下需要先比较从节点和主节点的偏移量,此时会让主节点停止客户端请求,直到超时或者故障转移完成。主从偏移量相同后开始手动故障转移流程。
TAKEOVER:这种手动故障转移的方式比较暴力,slave直接提升自己的epoch为最大的epoch。并把自己变成master。这样在消息交互过程中,旧master能发现自己的epoch小于该slave,同时两者负责的slot一致,它会把自己降级为slave。

均衡集群的slave(Replica migration)


在集群运行过程中,有的master的slave宕机,导致了该master成为孤儿master(orphaned masters),而有的master有很多slave。此处孤儿master的定义是那些本来有slave,但是全部离线的master,对于那些原来就没有slave的master不能认为是孤儿master。redis集群支持均衡slave功能,官方称为Replica migration,而我觉得均衡集群的slave更好理解该概念。集群能把某个slave较多的group上的slave迁移到那些孤儿master上,该功能通过cluster-migration-barrier参数配置,默认为1。slave在每次定时任务都会检查是否需要迁移slave,即把自己变成孤儿master的slave。 满足以下条件,slave就会成为孤儿master的slave:

自己所在的group是slave最多的group。
目前存在孤儿master。
自己所在的group的slave数目至少超过2个,只有自己一个的话迁移到其他group,自己原来的group的master又成了孤儿master。
自己所在的group的slave数量大于cluster-migration-barrier配置。
与group内的其他slave基于memcmp比较node id,自己的node id最小。这个可以防止多个slave并发复制孤儿master,从而原来的group失去过多的slave。

网络分区说明


redis的集群模式下,客户端需要和全部的节点保持连接,这样可能出现网络分区问题,客户端和一些节点在一个网络分区,另一部分节点在另一个网络分区。在分区期间,客户端仍然能执行命令,直到集群经过cluster-node-timeout发现分区情况,节点探测到有slot无法提供服务,才开始禁止客户端执行命令。

这时候会出现一种现象,假设客户端和一个master在小分区,其他节点在大分区。超时后,其他节点共同投票把group内的一个slave提为master,等分区恢复。旧的master会成为新master的slave。这样在cluster-node-timeout期间对旧master的写入数据都会丢失。
这个问题可以通过设置cluster-node-timeout来减少不一致。如果对一致性要求高的应用还可以通过min-slaves-to-write配置来提高写入的要求。

slot保存key列表


redis提供了cluster countkeysinslot和cluster getkeysinslot命令,可以获得某个slot的全部key列表。通过该列表,可以实现slot的迁移。该功能是通过skiplist实现的,skiplist是redis内部用来实现zset的数据结构,在slot保持key的时候也派上了用场。redis所有在db层对hash表的操作,也会在skiplist上执行相应的操作。比如往hash表增加数据,redis也会往skiplist也写一份数据,该skiplist的score就是slot的值,value对应了具体的key。这等于是redis在数据分布表上冗余了所有的key。不过相比skiplist所带来迁移的方便,冗余的结果是可以接受的,这也期望客户端,不要使用过长的key,从而增加内存的消耗。

附录1:集群相关命令


cluster meet ip:port 

集群间相互握手,加入彼此所在的集群。(将指定节点加入到集群)

cluster nodes

获取集群间节点信息的列表,如下所示,格式为<node ID> <node IP:PORT> <node role> [master node ID|-] <node ping_sent> <node pong_received> <node epoch> <node status>。

127.0.0.1:6379> cluster nodes
a15705fdb7cac60e07ff699bf4c514e80f245a2c 10.180.157.205:6379 slave 2b5603326d0fca28031467727fae4558115a99d8 0 1450854214289 11 connected
6477541e4594e60e095c8f440882636236545936 10.180.157.202:6379 slave 9b35a393fa6623887215023b761d531dde452d3c 0 1450854211276 12 connected
ecf9ae60e87ea3358d9c5f1f269e0ed9a387ea40 10.180.157.201:6379 master - 0 1450854214788 5 connected 10923-16383
2b5603326d0fca28031467727fae4558115a99d8 10.180.157.200:6379 master - 0 1450854213283 11 connected 5461-10922
f31f6ce49b3a2f3a246b2d97349c8f8614cf3a2c 10.180.157.208:6379 slave ecf9ae60e87ea3358d9c5f1f269e0ed9a387ea40 0 1450854212286 9 connected
9b35a393fa6623887215023b761d531dde452d3c 10.180.157.199:6379 myself,master - 0 0 12 connected 0-5460

cluster myid

返回节点的id。
127.0.0.1:6379> cluster myid
"9b35a393fa6623887215023b761d531dde452d3c"

cluster slots

返回集群间节点负责的数据分布表。
127.0.0.1:6379> cluster slots
1) 1) (integer) 10923
   2) (integer) 16383
   3) 1) "10.180.157.201"
      2) (integer) 6379
   4) 1) "10.180.157.208"
      2) (integer) 6379
2) 1) (integer) 5461
   2) (integer) 10922
   3) 1) "10.180.157.200"
      2) (integer) 6379
   4) 1) "10.180.157.205"
      2) (integer) 6379
3) 1) (integer) 0
   2) (integer) 5460
   3) 1) "10.180.157.199"
      2) (integer) 6379
   4) 1) "10.180.157.202"
      2) (integer) 6379

cluster flushslots

清空该节点负责slots,必须在节点负责的这些slot都没有数据的情况下才能执行,该命令需要谨慎使用,由于之前说的bitmapTestBit方法,redis只比较负责的节点,清空的slots信息无法被其他节点同步。

cluster addslots [slot]

在当前节点上增加slot。(将指定的一个或多个slot 指派给当前节点)

cluster delslots [slot]

在节点上取消slot的负责。这也会导致前面说的slot信息无法同步,而且一旦集群有slot不负责,配置cluster-require-full-coverage为yes的话,该节点就无法提供服务了,所以使用也需谨慎。

cluster setslot <slot> MIGRATING <nodeid>

把本节点负责的某个slot设置为迁移到目的节点。(即将本节点的slot指派给或叫迁移到指定的节点)

cluster setslot <slot> IMPORTING <nodeid>

设置某个slot为从迁移源节点迁移标志。(即将指定节点的slot指派给或叫迁移到本节点)

cluster setslot <slot> STABLE

设置某个slot为从迁移状态恢复为正常状态。(取消slot的导入(importing)或迁移(migrating))

cluster setslot <slot> NODE <nodeid>

设置某个slot为某节点负责。该命令使用也需要注意,cluster setslot的四个命令需要配置迁移工具使用,单独使用容易引起集群混乱。该命令在集群出现异常时,需要指定某个slot为某个节点负责时,最好在每个节点上都执行一遍,至少要在迁移的节点和最高epoch的节点上执行成功。(将指定的slot指派给指定的节点,如果该slot已经指派给另一个节点,则要另一个节点先删除该slot)


cluster info

集群的一些info信息。
127.0.0.1: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
cluster_size:3
cluster_current_epoch:12
cluster_my_epoch:12
cluster_stats_messages_sent:1449982
cluster_stats_messages_received:1182698

cluster saveconfig

保存集群的配置文件,集群默认在配置修改的时候会自动保存配置文件,该方法也能手动执行命令保存。

cluster keyslot

可以查询某个key对应的slot地址。
127.0.0.1:6379> cluster keyslot key
(integer) 12539

cluster countkeysinslot

可以查询该节点负责的某个slot内部key的数量。
127.0.0.1:6379> cluster countkeysinslot 13252
(integer) 2

cluster getkeysinslot <slot> <count>

可以查询该节点负责的某个slot内部指定数量的key列表。
127.0.0.1:6379> cluster getkeysinslot 13252 10
1) "key0"
2) "key2298"

cluster forget

把某个节点加入黑名单,这样就无法完成握手。黑名单的过期时为60s,60s后两节点又会继续完成握手。

cluster replicate <nodeid>

负责某个节点,成为它的slave。(将当前节点设置为指定nodeid节点的从节点)

cluster slaves

列出某个节点slave列表。
127.0.0.1:6379> cluster slaves 2b5603326d0fca28031467727fae4558115a99d8
1) "a15705fdb7cac60e07ff699bf4c514e80f245a2c 10.180.157.205:6379 slave 2b5603326d0fca28031467727fae4558115a99d8 0 1450854932667 11 connected"

cluster count-failure-reports

列出某个节点的故障转移记录的长度。

cluster failover [FORCE|TAKEOVER]

手动执行故障转移。


cluster set-config-epoch

设置节点epoch,只有在节点加入集群前才能设置。


cluster reset [SOFT|HARD]

重置集群信息,soft是清空其他节点的信息,但不修改自己的id。hard还会修改自己的id。不传该参数则使用soft方式。

readonly

在slave上执行,执行该命令后,可以在slave上执行只读命令。

readwrite

在slave上执行,执行该命令后,取消在slave上执行命令。

附录2:集群相关配置

cluster-enabled

说明:集群开关,默认是不开启集群模式。
默认值:no。
是否可以动态修改:no。
值的范围:yes|no。

cluster-config-file

说明:集群配置文件的名称,每个节点都有一个集群相关的配置文件,持久化保存集群的信息。
默认值:”nodes.conf”。
是否可以动态修改:no。
值的范围:文件路径。


cluster-node-timeout

说明:节点的超时时间,单位是毫秒。
默认值:15000。
是否可以动态修改:yes。
值的范围:大于0。

cluster-slave-validity-factor

说明:在进行故障转移的时候,group的全部slave都会请求申请为master,但是有些slave可能与master断开连接一段时间了,导致数据过于陈旧,这样的slave不应该被提升为master。该参数就是用来判断slave节点与master断线的时间是否过长。判断方法是比较slave断开连接的时间和(node-timeout * slave-validity-factor) + repl-ping-slave-period。
默认值:10。
是否可以动态修改:yes。
值的范围:大于等于0。

cluster-migration-barrier

说明:master的slave数量大于该值,slave才能迁移到其他孤儿master上,具体说明见均衡集群的slave章节。
默认值:1。
是否可以动态修改:yes。
值的范围:大于等于0。

cluster-require-full-coverage

说明:默认情况下,集群全部的slot有节点负责,集群状态才为ok,才能提供服务。设置为no,可以在slot没有全部分配的时候提供服务。不建议打开该配置,这样会造成分区的时候,小分区的master一直在接受写请求,而造成很长时间数据不一致。
默认值:yes。
是否可以动态修改:yes。
值的范围:yes|no。
正因为当初对未来做了太多的憧憬,所以对现在的自己尤其失望。生命中曾经有过的所有灿烂,终究都需要用寂寞来偿还。
原文地址:https://www.cnblogs.com/candlia/p/11920209.html