redis

本文详述了Redis数据库的发展历程,重点介绍了Redis的特性、安装步骤(涵盖Windows与Linux环境)、配置文件详解以及集群的搭建、伸缩与故障转移等核心概念。此外,还探讨了Redis的键值命令、数据结构操作以及如何利用HashTag优化数据分布。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

第一  先说一下nosql的发展史:

1:单机但数据库时代

2:Memcached(缓存)+MySQL+垂直拆分

3:Mysql主从读写分离

4: 分表分库+水平拆分+mysql集群

5:现在部署

 

redis

特点:

1,Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用

2.Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储

3.Redis支持数据的备份,即master-slave模式的数据备份

作用:

1.内存存储和持久化:redis支持异步将内存中的数据写到硬盘上,同时不影响继续服务

2.取最新N个数据的操作,如:可以将最新的10条评论的ID放在Redis的List集合里面

3.模拟类似于HttpSession这种需要设定过期时间的功能

4.发布、订阅消息系统

5.定时器、计数器

下载地址:  Http://www.redis.cn/     Http://redis.io/

redis安装

Window 下安装
下载地址:https://github.com/dmajkic/redis/downloads
下载到的Redis支持32bit和64bit。根据自己实际情况选择,将64bit的内容cp到自定义盘符安装目录取名redis。 如 C:\reids
打开一个cmd窗口 使用cd命令切换目录到 C:\redis 运行 redis-server.exe redis.conf 。
如果想方便的话,可以把redis的路径加到系统的环境变量里,这样就省得再输路径了,后面的那个redis.conf可以省略,
如果省略,会启用默认的。输入之后,会显示如下界面:

这时候另启一个cmd窗口,原来的不要关闭,不然就无法访问服务端了。
切换到redis目录下运行 redis-cli.exe -h 127.0.0.1 -p 6379 。
设置键值对 set myKey abc
取出键值对 get myKey

 

Linux版安装

1、下载获得redis-3.0.4.tar.gz后将它放入我们的Linux目录/opt

2、/opt目录下,解压命令:tar -zxvf redis-3.0.4.tar.gz

3、进入目录:cd redis-3.0.4

4、在redis-3.0.4目录下执行make命令

5、如果make完成后继续执行make install

6、

7、将默认的redis.conf拷贝到自己定义好的一个路径下,比如/myconf

8、修改redis.conf文件将里面的daemonize no 改成 yes,让服务在后台启动

9、启动  

10、检测是否启动  ps -ef | grep redis

11、进入redis客户端  redis-cli 

 第一种:交互式方式     

redis-cli -h {host} -p {port}方式连接,然后所有的操作都是在交互的方式实现,不需要再执行redis-cli了。

$redis-cli -h 127.0.0.1-p 6379

127.0.0.1:6379>set hello world

OK

127.0.0.1:6379>get hello

"world"

第二种方式:命令方式

redis-cli -h {host} -p {port} {command}直接得到命令的返回结果。

$redis-cli -h 127.0.0.1-p 6379 get hello

"world"

redis-cli包含很多参数,如-h,-p,要了解全部参数,可用redis-cli -help命令。

 

12、关闭redis  redis-cli shutdown

常用命令:Http://redisdoc.com/

 

 

 

Redis 键(key) 常用命令:

举例:

 

Redis字符串(String)常用命令:

Redis列表(List)常用命令:

Redis集合(Set)常用命令:

Redis哈希(Hash)常用命令:

Redis有序集合Zset(sorted set) 常用命令:

 redis.conf配置详解

# Redis配置文件样例

# Note on units: when memory size is needed, it is possible to specifiy
# it in the usual form of 1k 5GB 4M and so forth:
#
# 1k => 1000 bytes
# 1kb => 1024 bytes
# 1m => 1000000 bytes
# 1mb => 1024*1024 bytes
# 1g => 1000000000 bytes
# 1gb => 1024*1024*1024 bytes
#
# units are case insensitive so 1GB 1Gb 1gB are all the same.

# Redis默认不是以守护进程的方式运行,可以通过该配置项修改,使用yes启用守护进程
# 启用守护进程后,Redis会把pid写到一个pidfile中,在/var/run/redis.pid
daemonize no

# 当Redis以守护进程方式运行时,Redis默认会把pid写入/var/run/redis.pid文件,可以通过pidfile指定
pidfile /var/run/redis.pid

# 指定Redis监听端口,默认端口为6379
# 如果指定0端口,表示Redis不监听TCP连接
port 6379

# 绑定的主机地址
# 你可以绑定单一接口,如果没有绑定,所有接口都会监听到来的连接
# bind 127.0.0.1

# Specify the path for the unix socket that will be used to listen for
# incoming connections. There is no default, so Redis will not listen
# on a unix socket when not specified.
#
# unixsocket /tmp/redis.sock
# unixsocketperm 755

# 当客户端闲置多长时间后关闭连接,如果指定为0,表示关闭该功能
timeout 0

# 指定日志记录级别,Redis总共支持四个级别:debug、verbose、notice、warning,默认为verbose
# debug (很多信息, 对开发/测试比较有用)
# verbose (many rarely useful info, but not a mess like the debug level)
# notice (moderately verbose, what you want in production probably)
# warning (only very important / critical messages are logged)
loglevel verbose

# 日志记录方式,默认为标准输出,如果配置为redis为守护进程方式运行,而这里又配置为标准输出,则日志将会发送给/dev/null
logfile stdout

# To enable logging to the system logger, just set 'syslog-enabled' to yes,
# and optionally update the other syslog parameters to suit your needs.
# syslog-enabled no

# Specify the syslog identity.
# syslog-ident redis

# Specify the syslog facility.  Must be USER or between LOCAL0-LOCAL7.
# syslog-facility local0

# 设置数据库的数量,默认数据库为0,可以使用select <dbid>命令在连接上指定数据库id
# dbid是从0到‘databases’-1的数目
databases 16

################################ SNAPSHOTTING  #################################
# 指定在多长时间内,有多少次更新操作,就将数据同步到数据文件,可以多个条件配合
# Save the DB on disk:
#
#   save <seconds> <changes>
#
#   Will save the DB if both the given number of seconds and the given
#   number of write operations against the DB occurred.
#
#   满足以下条件将会同步数据:
#   900秒(15分钟)内有1个更改
#   300秒(5分钟)内有10个更改
#   60秒内有10000个更改
#   Note: 可以把所有“save”行注释掉,这样就取消同步操作了

save 900 1
save 300 10
save 60 10000

# 指定存储至本地数据库时是否压缩数据,默认为yes,Redis采用LZF压缩,如果为了节省CPU时间,可以关闭该选项,但会导致数据库文件变的巨大
rdbcompression yes

# 指定本地数据库文件名,默认值为dump.rdb
dbfilename dump.rdb

# 工作目录.
# 指定本地数据库存放目录,文件名由上一个dbfilename配置项指定
# 
# Also the Append Only File will be created inside this directory.
# 
# 注意,这里只能指定一个目录,不能指定文件名
dir ./

################################# REPLICATION #################################

# 主从复制。使用slaveof从 Redis服务器复制一个Redis实例。注意,该配置仅限于当前slave有效
# so for example it is possible to configure the slave to save the DB with a
# different interval, or to listen to another port, and so on.
# 设置当本机为slav服务时,设置master服务的ip地址及端口,在Redis启动时,它会自动从master进行数据同步
# slaveof <masterip> <masterport>


# 当master服务设置了密码保护时,slav服务连接master的密码
# 下文的“requirepass”配置项可以指定密码
# masterauth <master-password>

# When a slave lost the connection with the master, or when the replication
# is still in progress, the slave can act in two different ways:
#
# 1) if slave-serve-stale-data is set to 'yes' (the default) the slave will
#    still reply to client requests, possibly with out of data data, or the
#    data set may just be empty if this is the first synchronization.
#
# 2) if slave-serve-stale data is set to 'no' the slave will reply with
#    an error "SYNC with master in progress" to all the kind of commands
#    but to INFO and SLAVEOF.
#
slave-serve-stale-data yes

# Slaves send PINGs to server in a predefined interval. It's possible to change
# this interval with the repl_ping_slave_period option. The default value is 10
# seconds.
#
# repl-ping-slave-period 10

# The following option sets a timeout for both Bulk transfer I/O timeout and
# master data or ping response timeout. The default value is 60 seconds.
#
# It is important to make sure that this value is greater than the value
# specified for repl-ping-slave-period otherwise a timeout will be detected
# every time there is low traffic between the master and the slave.
#
# repl-timeout 60

################################## SECURITY ###################################

# Warning: since Redis is pretty fast an outside user can try up to
# 150k passwords per second against a good box. This means that you should
# use a very strong password otherwise it will be very easy to break.
# 设置Redis连接密码,如果配置了连接密码,客户端在连接Redis时需要通过auth <password>命令提供密码,默认关闭
# requirepass foobared

# Command renaming.
#
# It is possilbe to change the name of dangerous commands in a shared
# environment. For instance the CONFIG command may be renamed into something
# of hard to guess so that it will be still available for internal-use
# tools but not available for general clients.
#
# Example:
#
# rename-command CONFIG b840fc02d524045429941cc15f59e41cb7be6c52
#
# It is also possilbe to completely kill a command renaming it into
# an empty string:
#
# rename-command CONFIG ""

################################### LIMITS ####################################

# 设置同一时间最大客户端连接数,默认无限制,Redis可以同时打开的客户端连接数为Redis进程可以打开的最大文件描述符数,
# 如果设置maxclients 0,表示不作限制。当客户端连接数到达限制时,Redis会关闭新的连接并向客户端返回max Number of clients reached错误信息
# maxclients 128

# Don't use more memory than the specified amount of bytes.
# When the memory limit is reached Redis will try to remove keys with an
# EXPIRE set. It will try to start freeing keys that are going to expire
# in little time and preserve keys with a longer time to live.
# Redis will also try to remove objects from free lists if possible.
#
# If all this fails, Redis will start to reply with errors to commands
# that will use more memory, like SET, LPUSH, and so on, and will continue
# to reply to most read-only commands like GET.
#
# WARNING: maxmemory can be a good idea mainly if you want to use Redis as a
# 'state' server or cache, not as a real DB. When Redis is used as a real
# database the memory usage will grow over the weeks, it will be obvious if
# it is going to use too much memory in the long run, and you'll have the time
# to upgrade. With maxmemory after the limit is reached you'll start to get
# errors for write operations, and this may even lead to DB inconsistency.
# 指定Redis最大内存限制,Redis在启动时会把数据加载到内存中,达到最大内存后,Redis会先尝试清除已到期或即将到期的Key,
# 当此方法处理后,仍然到达最大内存设置,将无法再进行写入操作,但仍然可以进行读取操作。
# Redis新的vm机制,会把Key存放内存,Value会存放在swap区
# maxmemory <bytes>

# MAXMEMORY POLICY: how Redis will select what to remove when maxmemory
# is reached? You can select among five behavior:
# 
# volatile-lru -> remove the key with an expire set using an LRU algorithm
# allkeys-lru -> remove any key accordingly 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
# 
# Note: with all the kind of policies, Redis will return an error on write
#       operations, when there are not suitable keys for eviction.
#
#       At the date of writing this commands are: set setnx setex append
#       incr decr rpush lpush rpushx lpushx linsert lset rpoplpush sadd
#       sinter sinterstore sunion sunionstore sdiff sdiffstore zadd zincrby
#       zunionstore zinterstore hset hsetnx hmset hincrby incrby decrby
#       getset mset msetnx exec sort
#
# The default is:
#
# maxmemory-policy volatile-lru

# LRU and minimal TTL algorithms are not precise algorithms but approximated
# algorithms (in order to save memory), so you can select as well the sample
# size to check. For instance for default Redis will check three keys and
# pick the one that was used less recently, you can change the sample size
# using the following configuration directive.
#
# maxmemory-samples 3

############################## APPEND ONLY MODE ###############################

# 
# Note that you can have both the async dumps and the append only file if you
# like (you have to comment the "save" statements above to disable the dumps).
# Still if append only mode is enabled Redis will load the data from the
# log file at startup ignoring the dump.rdb file.
# 指定是否在每次更新操作后进行日志记录,Redis在默认情况下是异步的把数据写入磁盘,如果不开启,可能会在断电时导致一段时间内的数据丢失。
# 因为redis本身同步数据文件是按上面save条件来同步的,所以有的数据会在一段时间内只存在于内存中。默认为no
# IMPORTANT: Check the BGREWRITEAOF to check how to rewrite the append
# log file in background when it gets too big.

appendonly no

# 指定更新日志文件名,默认为appendonly.aof
# appendfilename appendonly.aof

# The fsync() call tells the Operating System to actually write data on disk
# instead to wait for more data in the output buffer. Some OS will really flush 
# data on disk, some other OS will just try to do it ASAP.

# 指定更新日志条件,共有3个可选值:
# no:表示等操作系统进行数据缓存同步到磁盘(快)
# always:表示每次更新操作后手动调用fsync()将数据写到磁盘(慢,安全)
# everysec:表示每秒同步一次(折衷,默认值)

appendfsync everysec
# appendfsync no

# When the AOF fsync policy is set to always or everysec, and a background
# saving process (a background save or AOF log background rewriting) is
# performing a lot of I/O against the disk, in some Linux configurations
# Redis may block too long on the fsync() call. Note that there is no fix for
# this currently, as even performing fsync in a different thread will block
# our synchronous write(2) call.
#
# In order to mitigate this problem it's possible to use the following option
# that will prevent fsync() from being called in the main process while a
# BGSAVE or BGREWRITEAOF is in progress.
#
# This means that while another child is saving the durability of Redis is
# the same as "appendfsync none", that in pratical terms means that it is
# possible to lost up to 30 seconds of log in the worst scenario (with the
# default Linux settings).
# 
# If you have latency problems turn this to "yes". Otherwise leave it as
# "no" that is the safest pick from the point of view of durability.
no-appendfsync-on-rewrite no

# Automatic rewrite of the append only file.
# Redis is able to automatically rewrite the log file implicitly calling
# BGREWRITEAOF when the AOF log size will growth by the specified percentage.
# 
# This is how it works: Redis remembers the size of the AOF file after the
# latest rewrite (or if no rewrite happened since the restart, the size of
# the AOF at startup is used).
#
# This base size is compared to the current size. If the current size is
# bigger than the specified percentage, the rewrite is triggered. Also
# you need to specify a minimal size for the AOF file to be rewritten, this
# is useful to avoid rewriting the AOF file even if the percentage increase
# is reached but it is still pretty small.
#
# Specify a precentage of zero in order to disable the automatic AOF
# rewrite feature.

auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

################################## SLOW LOG ###################################

# The Redis Slow Log is a system to log queries that exceeded a specified
# execution time. The execution time does not include the I/O operations
# like talking with the client, sending the reply and so forth,
# but just the time needed to actually execute the command (this is the only
# stage of command execution where the thread is blocked and can not serve
# other requests in the meantime).
# 
# You can configure the slow log with two parameters: one tells Redis
# what is the execution time, in microseconds, to exceed in order for the
# command to get logged, and the other parameter is the length of the
# slow log. When a new command is logged the oldest one is removed from the
# queue of logged commands.

# The following time is expressed in microseconds, so 1000000 is equivalent
# to one second. Note that a negative number disables the slow log, while
# a value of zero forces the logging of every command.
slowlog-log-slower-than 10000

# There is no limit to this length. Just be aware that it will consume memory.
# You can reclaim memory used by the slow log with SLOWLOG RESET.
slowlog-max-len 1024

################################ VIRTUAL MEMORY ###############################

### WARNING! Virtual Memory is deprecated in Redis 2.4
### The use of Virtual Memory is strongly discouraged.

### WARNING! Virtual Memory is deprecated in Redis 2.4
### The use of Virtual Memory is strongly discouraged.

# Virtual Memory allows Redis to work with datasets bigger than the actual
# amount of RAM needed to hold the whole dataset in memory.
# In order to do so very used keys are taken in memory while the other keys
# are swapped into a swap file, similarly to what operating systems do
# with memory pages.
# 指定是否启用虚拟内存机制,默认值为no,
# VM机制将数据分页存放,由Redis将访问量较少的页即冷数据swap到磁盘上,访问多的页面由磁盘自动换出到内存中
# 把vm-enabled设置为yes,根据需要设置好接下来的三个VM参数,就可以启动VM了
vm-enabled no
# vm-enabled yes

# This is the path of the Redis swap file. As you can guess, swap files
# can't be shared by different Redis instances, so make sure to use a swap
# file for every redis process you are running. Redis will complain if the
# swap file is already in use.
#
# Redis交换文件最好的存储是SSD(固态硬盘)
# 虚拟内存文件路径,默认值为/tmp/redis.swap,不可多个Redis实例共享
# *** WARNING *** if you are using a shared hosting the default of putting
# the swap file under /tmp is not secure. Create a dir with access granted
# only to Redis user and configure Redis to create the swap file there.
vm-swap-file /tmp/redis.swap

# With vm-max-memory 0 the system will swap everything it can. Not a good
# default, just specify the max amount of RAM you can in bytes, but it's
# better to leave some margin. For instance specify an amount of RAM
# that's more or less between 60 and 80% of your free RAM.
# 将所有大于vm-max-memory的数据存入虚拟内存,无论vm-max-memory设置多少,所有索引数据都是内存存储的(Redis的索引数据就是keys)
# 也就是说当vm-max-memory设置为0的时候,其实是所有value都存在于磁盘。默认值为0
vm-max-memory 0

# Redis swap文件分成了很多的page,一个对象可以保存在多个page上面,但一个page上不能被多个对象共享,vm-page-size是要根据存储的数据大小来设定的。
# 建议如果存储很多小对象,page大小最后设置为32或64bytes;如果存储很大的对象,则可以使用更大的page,如果不确定,就使用默认值
vm-page-size 32

# 设置swap文件中的page数量由于页表(一种表示页面空闲或使用的bitmap)是存放在内存中的,在磁盘上每8个pages将消耗1byte的内存
# swap空间总容量为 vm-page-size * vm-pages
#
# With the default of 32-bytes memory pages and 134217728 pages Redis will
# use a 4 GB swap file, that will use 16 MB of RAM for the page table.
#
# It's better to use the smallest acceptable value for your application,
# but the default is large in order to work in most conditions.
vm-pages 134217728

# Max number of VM I/O threads running at the same time.
# This threads are used to read/write data from/to swap file, since they
# also encode and decode objects from disk to memory or the reverse, a bigger
# number of threads can help with big objects even if they can't help with
# I/O itself as the physical device may not be able to couple with many
# reads/writes operations at the same time.
# 设置访问swap文件的I/O线程数,最后不要超过机器的核数,如果设置为0,那么所有对swap文件的操作都是串行的,可能会造成比较长时间的延迟,默认值为4
vm-max-threads 4

############################### ADVANCED CONFIG ###############################

# Hashes are encoded in a special way (much more memory efficient) when they
# have at max a given numer of elements, and the biggest element does not
# exceed a given threshold. You can configure this limits with the following
# configuration directives.
# 指定在超过一定的数量或者最大的元素超过某一临界值时,采用一种特殊的哈希算法
hash-max-zipmap-entries 512
hash-max-zipmap-value 64

# Similarly to hashes, small lists are also encoded in a special way in order
# to save a lot of space. The special representation is only used when
# you are under the following limits:
list-max-ziplist-entries 512
list-max-ziplist-value 64

# Sets have a special encoding in just one case: when a set is composed
# of just strings that happens to be integers in radix 10 in the range
# of 64 bit signed integers.
# The following configuration setting sets the limit in the size of the
# set in order to use this special memory saving encoding.
set-max-intset-entries 512

# Similarly to hashes and lists, sorted sets are also specially encoded in
# order to save a lot of space. This encoding is only used when the length and
# elements of a sorted set are below the following limits:
zset-max-ziplist-entries 128
zset-max-ziplist-value 64

# Active rehashing uses 1 millisecond every 100 milliseconds of CPU time in
# order to help rehashing the main Redis hash table (the one mapping top-level
# keys to values). The hash table implementation redis uses (see dict.c)
# performs a lazy rehashing: the more operation you run into an hash table
# that is rhashing, the more rehashing "steps" are performed, so if the
# server is idle the rehashing is never complete and some more memory is used
# by the hash table.
# 
# The default is to use this millisecond 10 times every second in order to
# active rehashing the main dictionaries, freeing memory when possible.
#
# If unsure:
# use "activerehashing no" if you have hard latency requirements and it is
# not a good thing in your environment that Redis can reply form time to time
# to queries with 2 milliseconds delay.
# 指定是否激活重置哈希,默认为开启
activerehashing yes

################################## INCLUDES ###################################

# 指定包含其他的配置文件,可以在同一主机上多个Redis实例之间使用同一份配置文件,而同时各实例又拥有自己的特定配置文件
# include /path/to/local.conf
# include /path/to/other.conf

常见配置说明
redis.conf 配置项说明如下:
1. Redis默认不是以守护进程的方式运行,可以通过该配置项修改,使用yes启用守护进程
  daemonize no
2. 当Redis以守护进程方式运行时,Redis默认会把pid写入/var/run/redis.pid文件,可以通过pidfile指定
  pidfile /var/run/redis.pid
3. 指定Redis监听端口,默认端口为6379,作者在自己的一篇博文中解释了为什么选用6379作为默认端口,因为6379在手机按键上MERZ对应的号码,而MERZ取自意大利歌女Alessia Merz的名字
  port 6379
4. 绑定的主机地址
  bind 127.0.0.1
5.当 客户端闲置多长时间后关闭连接,如果指定为0,表示关闭该功能
  timeout 300
6. 指定日志记录级别,Redis总共支持四个级别:debug、verbose、notice、warning,默认为verbose
  loglevel verbose
7. 日志记录方式,默认为标准输出,如果配置Redis为守护进程方式运行,而这里又配置为日志记录方式为标准输出,则日志将会发送给/dev/null
  logfile stdout
8. 设置数据库的数量,默认数据库为0,可以使用SELECT <dbid>命令在连接上指定数据库id
  databases 16
9. 指定在多长时间内,有多少次更新操作,就将数据同步到数据文件,可以多个条件配合
  save <seconds> <changes>
  Redis默认配置文件中提供了三个条件:
  save 900 1
  save 300 10
  save 60 10000
  分别表示900秒(15分钟)内有1个更改,300秒(5分钟)内有10个更改以及60秒内有10000个更改。
 
10. 指定存储至本地数据库时是否压缩数据,默认为yes,Redis采用LZF压缩,如果为了节省CPU时间,可以关闭该选项,但会导致数据库文件变的巨大
  rdbcompression yes
11. 指定本地数据库文件名,默认值为dump.rdb
  dbfilename dump.rdb
12. 指定本地数据库存放目录
  dir ./
13. 设置当本机为slav服务时,设置master服务的IP地址及端口,在Redis启动时,它会自动从master进行数据同步
  slaveof <masterip> <masterport>
14. 当master服务设置了密码保护时,slav服务连接master的密码
  masterauth <master-password>
15. 设置Redis连接密码,如果配置了连接密码,客户端在连接Redis时需要通过AUTH <password>命令提供密码,默认关闭
  requirepass foobared
16. 设置同一时间最大客户端连接数,默认无限制,Redis可以同时打开的客户端连接数为Redis进程可以打开的最大文件描述符数,如果设置 maxclients 0,表示不作限制。当客户端连接数到达限制时,Redis会关闭新的连接并向客户端返回max number of clients reached错误信息
  maxclients 128
17. 指定Redis最大内存限制,Redis在启动时会把数据加载到内存中,达到最大内存后,Redis会先尝试清除已到期或即将到期的Key,当此方法处理 后,仍然到达最大内存设置,将无法再进行写入操作,但仍然可以进行读取操作。Redis新的vm机制,会把Key存放内存,Value会存放在swap区
  maxmemory <bytes>
18. 指定是否在每次更新操作后进行日志记录,Redis在默认情况下是异步的把数据写入磁盘,如果不开启,可能会在断电时导致一段时间内的数据丢失。因为 redis本身同步数据文件是按上面save条件来同步的,所以有的数据会在一段时间内只存在于内存中。默认为no
  appendonly no
19. 指定更新日志文件名,默认为appendonly.aof
   appendfilename appendonly.aof
20. 指定更新日志条件,共有3个可选值: 
  no:表示等操作系统进行数据缓存同步到磁盘(快) 
  always:表示每次更新操作后手动调用fsync()将数据写到磁盘(慢,安全) 
  everysec:表示每秒同步一次(折衷,默认值)
  appendfsync everysec
 
21. 指定是否启用虚拟内存机制,默认值为no,简单的介绍一下,VM机制将数据分页存放,由Redis将访问量较少的页即冷数据swap到磁盘上,访问多的页面由磁盘自动换出到内存中(在后面的文章我会仔细分析Redis的VM机制)
   vm-enabled no
22. 虚拟内存文件路径,默认值为/tmp/redis.swap,不可多个Redis实例共享
   vm-swap-file /tmp/redis.swap
23. 将所有大于vm-max-memory的数据存入虚拟内存,无论vm-max-memory设置多小,所有索引数据都是内存存储的(Redis的索引数据 就是keys),也就是说,当vm-max-memory设置为0的时候,其实是所有value都存在于磁盘。默认值为0
   vm-max-memory 0
24. Redis swap文件分成了很多的page,一个对象可以保存在多个page上面,但一个page上不能被多个对象共享,vm-page-size是要根据存储的 数据大小来设定的,作者建议如果存储很多小对象,page大小最好设置为32或者64bytes;如果存储很大大对象,则可以使用更大的page,如果不 确定,就使用默认值
   vm-page-size 32
25. 设置swap文件中的page数量,由于页表(一种表示页面空闲或使用的bitmap)是在放在内存中的,,在磁盘上每8个pages将消耗1byte的内存。
   vm-pages 134217728
26. 设置访问swap文件的线程数,最好不要超过机器的核数,如果设置为0,那么所有对swap文件的操作都是串行的,可能会造成比较长时间的延迟。默认值为4
   vm-max-threads 4
27. 设置在向客户端应答时,是否把较小的包合并为一个包发送,默认为开启
  glueoutputbuf yes
28. 指定在超过一定的数量或者最大的元素超过某一临界值时,采用一种特殊的哈希算法
  hash-max-zipmap-entries 64
  hash-max-zipmap-value 512
29. 指定是否激活重置哈希,默认为开启(后面在介绍Redis的哈希算法时具体介绍)
  activerehashing yes
30. 指定包含其它的配置文件,可以在同一主机上多个Redis实例之间使用同一份配置文件,而同时各个实例又拥有自己的特定配置文件
  include /path/to/local.conf

 

 

redis 极限配置

Maxclients   设置redis同时可以与多少个客户端进行连接。默认情况下为10000个客户端。当你
无法设置进程文件句柄限制时,redis会设置为当前的文件句柄限制值减去32,因为redis会为自
身内部处理逻辑留一些句柄出来。如果达到了此限制,redis则会拒绝新的连接请求,并且向这
些连接请求方发出“max number of clients reached”以作回应。

Maxmemory  设置redis可以使用的内存量。一旦到达内存使用上限,redis将会试图移除内部数据,移除规则可以通过maxmemory-policy来指定。如果redis无法根据移除规则来移除内存中的数据,或者设置了“不允许移除”,
那么redis则会针对那些需要申请内存的指令返回错误信息,比如SET、LPUSH等。
 但是对于无内存申请的指令,仍然会正常响应,比如GET等。如果你的redis是主redis(说明你的redis有从redis),那么在设置内存使用上限时,需要在系统中留出一些内存空间给同步队列缓存,只有在你设置的是“不移除”的情况下,才不用考虑这个因素

Maxmemory-policy  

 (1)volatile-lru:使用LRU算法移除key,只对设置了过期时间的键
(2)allkeys-lru:使用LRU算法移除key
(3)volatile-random:在过期集合中移除随机的key,只对设置了过期时间的键
(4)allkeys-random:移除随机的key
(5)volatile-ttl:移除那些TTL值最小的key,即那些最近要过期的key
(6)noeviction:不进行移除。针对写操作,只是返回错误信息

LRU 是 (Least recently used,最近最少使用)

Maxmemory-samples   设置样本数量,LRU算法和最小TTL算法都并非是精确的算法,而是估算值,所以你可以设置样本的大小,
redis默认会检查这么多个key并选择其中LRU的那

Redis的持久化

1:RDB(Redis DataBase)

在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是行话讲的Snapshot快照,它恢复时是将快照文件直接读到内存里。Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失。

Fork    的作用是复制一个与当前进程一样的进程。新进程的所有数据(变量、环境变量、程序计数器等)
数值都和原进程一致,但是是一个全新的进程,并作为原进程的子进程

Rdb 保存的是dump.rdb文件

配置文件如下;官方默认配置

这个会按照一定规则去触发  如果想自己手动触发 用  命令save或者是bgsave

Save:save时只管保存,其它不管,全部阻塞

BGSAVE:Redis会在后台异步进行快照操作,快照同时还可以响应客户端请求。可以通过lastsave命令获取最后一次成功执行快照的时间

执行flushall命令,也会产生dump.rdb文件,但里面是空的,无意义

数据如何恢复:

将备份文件 (dump.rdb) 移动到 redis 安装目录并启动服务即可

动态停止所有RDB保存规则的方法:redis-cli config set save ""

2:AOF(Append Only File)

以日志的形式来记录每个写操作,将Redis执行过的所有写指令记录下来(读操作不记录),只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作

Aof  保存的是appendonly.aof文件

配置位置

修复:

 

redis 事务管理

 可以一次执行多个命令,本质是一组命令的集合。一个事务中的所有命令都会序列化,按顺序地串行化执行而不会被其它命令插入,不许加塞

常用命令:

 

watch监控:

悲观锁(Pessimistic Lock),就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁 

 乐观锁(Optimistic Lock),就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,
 乐观锁策略:提交版本必须大于记录当前版本才能执行更新

unwatch指令 取消

      Watch指令,类似乐观锁,事务提交时,如果Key的值已被别的客户端改变,比如某个list已被别的客户端push/pop过了,整个事务队列都不会被执行。通过WATCH命令在事务执行之前监控了多个Keys,倘若在WATCH之后有任何Key的值发生了变化,EXEC命令执行的事务都将被放弃,同时返回Nullmulti-bulk应答以通知调用者事务执行失败

redis主存复制

主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master),后者称为从节点(slave);数据的复制是单向的,只能由主节点到从节点。

默认情况下,每台Redis服务器都是主节点;且一个主节点可以有多个从节点(或没有从节点),但一个从节点只能有一个主节点。

主从复制的作用

主从复制的作用主要包括:

  1. 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
  2. 故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余。
  3. 负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。
  4. 高可用基石:除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础。

1. 建立复制

需要注意,主从复制的开启,完全是在从节点发起的;不需要我们在主节点做任何事情。

从节点开启主从复制,有3种方式:

(1)配置文件

在从服务器的配置文件中加入:slaveof <masterip> <masterport>

(2)启动命令

redis-server启动命令后加入 --slaveof <masterip> <masterport>

(3)客户端命令

Redis服务器启动后,直接通过客户端执行命令:slaveof <masterip> <masterport>,则该Redis实例成为从节点。

上述3种方式是等效的,下面以客户端命令的方式为例,看一下当执行了slaveof后,Redis主节点和从节点的变化。

 

实例

准备工作:启动两个节点

方便起见,实验所使用的主从节点是在一台机器上的不同Redis实例,其中主节点监听6379端口,从节点监听6380端口;从节点监听的端口号可以在配置文件中修改:

启动后可以看到:

两个Redis节点启动后(分别称为6379节点和6380节点),默认都是主节点。

建立复制

此时在6380节点执行slaveof命令,使之变为从节点:

观察效果

下面验证一下,在主从复制建立后,主节点的数据会复制到从节点中。

(1)首先在从节点查询一个不存在的key:

(2)然后在主节点中增加这个key:

(3)此时在从节点中再次查询这个key,会发现主节点的操作已经同步至从节点:

(4)然后在主节点删除这个key:

(5)此时在从节点中再次查询这个key,会发现主节点的操作已经同步至从节点:

3. 断开复制

通过slaveof <masterip> <masterport>命令建立主从复制关系以后,可以通过slaveof no one断开。需要注意的是,从节点断开复制后,不会删除已有的数据,只是不再接受主节点新的数据变化。

从节点执行slaveof no one后,打印日志如下所示;可以看出断开复制后,从节点又变回为主节点。

主节点打印日志如下:

 哨兵模式(sentinel)

1.  作用

在介绍哨兵之前,首先从宏观角度回顾一下Redis实现高可用相关的技术。它们包括:持久化、复制、哨兵和集群,其主要作用和解决的问题是:

  • 持久化:持久化是最简单的高可用方法(有时甚至不被归为高可用的手段),主要作用是数据备份,即将数据存储在硬盘,保证数据不会因进程退出而丢失。
  • 复制:复制是高可用Redis的基础,哨兵和集群都是在复制基础上实现高可用的。复制主要实现了数据的多机备份,以及对于读操作的负载均衡和简单的故障恢复。缺陷:故障恢复无法自动化;写操作无法负载均衡;存储能力受到单机的限制。
  • 哨兵:在复制的基础上,哨兵实现了自动化的故障恢复。缺陷:写操作无法负载均衡;存储能力受到单机的限制。
  • 集群:通过集群,Redis解决了写操作无法负载均衡,以及存储能力受到单机限制的问题,实现了较为完善的高可用方案。

 

下面说回哨兵。

Redis Sentinel,即Redis哨兵,在Redis 2.8版本开始引入。哨兵的核心功能是主节点的自动故障转移。下面是Redis官方文档对于哨兵功能的描述:

  • 监控(Monitoring):哨兵会不断地检查主节点和从节点是否运作正常。
  • 自动故障转移(Automatic failover):当主节点不能正常工作时,哨兵会开始自动故障转移操作,它会将失效主节点的其中一个从节点升级为新的主节点,并让其他从节点改为复制新的主节点。
  • 配置提供者(Configuration provider):客户端在初始化时,通过连接哨兵来获得当前Redis服务的主节点地址。
  • 通知(Notification):哨兵可以将故障转移的结果发送给客户端。

其中,监控和自动故障转移功能,使得哨兵可以及时发现主节点故障并完成转移;而配置提供者和通知功能,则需要在与客户端的交互中才能体现。

这里对“客户端”一词在文章中的用法做一个说明:在前面的文章中,只要通过API访问redis服务器,都会称作客户端,包括redis-cli、Java客户端Jedis等;为了便于区分说明,本文中的客户端并不包括redis-cli,而是比redis-cli更加复杂:redis-cli使用的是redis提供的底层接口,而客户端则对这些接口、功能进行了封装,以便充分利用哨兵的配置提供者和通知功能。

2.  架构

典型的哨兵架构图如下所示:

它由两部分组成,哨兵节点和数据节点:

  • 哨兵节点:哨兵系统由一个或多个哨兵节点组成,哨兵节点是特殊的redis节点,不存储数据。
  • 数据节点:主节点和从节点都是数据节点。

二、部署

这一部分将部署一个简单的哨兵系统,包含1个主节点、2个从节点和3个哨兵节点。方便起见:所有这些节点都部署在一台机器上(局域网IP:192.168.92.128),使用端口号区分;节点的配置尽可能简化。

1.  部署主从节点

哨兵系统中的主从节点,与普通的主从节点配置是一样的,并不需要做任何额外配置。下面分别是主节点(port=6379)和2个从节点(port=6380/6381)的配置文件,配置都比较简单,不再详述。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

#redis-6379.conf

port 6379

daemonize yes

logfile "6379.log"

dbfilename "dump-6379.rdb"

 

#redis-6380.conf

port 6380

daemonize yes

logfile "6380.log"

dbfilename "dump-6380.rdb"

slaveof 192.168.92.128 6379

 

#redis-6381.conf

port 6381

daemonize yes

logfile "6381.log"

dbfilename "dump-6381.rdb"

slaveof 192.168.92.128 6379

配置完成后,依次启动主节点和从节点:

1

2

3

redis-server redis-6379.conf

redis-server redis-6380.conf

redis-server redis-6381.conf

节点启动后,连接主节点查看主从状态是否正常,如下图所示:

2.  部署哨兵节点

哨兵节点本质上是特殊的Redis节点。

3个哨兵节点的配置几乎是完全一样的,主要区别在于端口号的不同(26379/26380/26381),下面以26379节点为例介绍节点的配置和启动方式;配置部分尽量简化,更多配置会在后面介绍。

1

2

3

4

5

#sentinel-26379.conf

port 26379

daemonize yes

logfile "26379.log"

sentinel monitor mymaster 192.168.92.128 6379 2

其中,sentinel monitor mymaster 192.168.92.128 6379 2 配置的含义是:该哨兵节点监控192.168.92.128:6379这个主节点,该主节点的名称是mymaster,最后的2的含义与主节点的故障判定有关:至少需要2个哨兵节点同意,才能判定主节点故障并进行故障转移。

哨兵节点的启动有两种方式,二者作用是完全相同的:

1

2

redis-sentinel sentinel-26379.conf

redis-server sentinel-26379.conf --sentinel

按照上述方式配置和启动之后,整个哨兵系统就启动完毕了。可以通过redis-cli连接哨兵节点进行验证,如下图所示:可以看出26379哨兵节点已经在监控mymaster主节点(即192.168.92.128:6379),并发现了其2个从节点和另外2个哨兵节点。

此时如果查看哨兵节点的配置文件,会发现一些变化,以26379为例:

其中,dir只是显式声明了数据和日志所在的目录(在哨兵语境下只有日志);known-slave和known-sentinel显示哨兵已经发现了从节点和其他哨兵;带有epoch的参数与配置纪元有关(配置纪元是一个从0开始的计数器,每进行一次领导者哨兵选举,都会+1;领导者哨兵选举是故障转移阶段的一个操作,在后文原理部分会介绍)。

3.  演示故障转移

哨兵的4个作用中,配置提供者和通知需要客户端的配合,本文将在下一章介绍客户端访问哨兵系统的方法时详细介绍。这一小节将演示当主节点发生故障时,哨兵的监控和自动故障转移功能。

(1)首先,使用kill命令杀掉主节点:

(2)如果此时立即在哨兵节点中使用info Sentinel命令查看,会发现主节点还没有切换过来,因为哨兵发现主节点故障并转移,需要一段时间。

(3)一段时间以后,再次在哨兵节点中执行info Sentinel查看,发现主节点已经切换成6380节点。

但是同时可以发现,哨兵节点认为新的主节点仍然有2个从节点,这是因为哨兵在将6380切换成主节点的同时,将6379节点置为其从节点;虽然6379从节点已经挂掉,但是由于哨兵并不会对从节点进行客观下线(其含义将在原理部分介绍),因此认为该从节点一直存在。当6379节点重新启动后,会自动变成6380节点的从节点。下面验证一下。

(4)重启6379节点:可以看到6379节点成为了6380节点的从节点。

(5)在故障转移阶段,哨兵和主从节点的配置文件都会被改写。

对于主从节点,主要是slaveof配置的变化:新的主节点没有了slaveof配置,其从节点则slaveof新的主节点。

对于哨兵节点,除了主从节点信息的变化,纪元(epoch)也会变化,下图中可以看到纪元相关的参数都+1了。

4.  总结

哨兵系统的搭建过程,有几点需要注意:

(1)哨兵系统中的主从节点,与普通的主从节点并没有什么区别,故障发现和转移是由哨兵来控制和完成的。

(2)哨兵节点本质上是redis节点。

(3)每个哨兵节点,只需要配置监控主节点,便可以自动发现其他的哨兵节点和从节点。

(4)在哨兵节点启动和故障转移阶段,各个节点的配置文件会被重写(config rewrite)。

(5)本章的例子中,一个哨兵只监控了一个主节点;实际上,一个哨兵可以监控多个主节点,通过配置多条sentinel monitor即可实现。

三、客户端访问哨兵系统

上一小节演示了哨兵的两大作用:监控和自动故障转移,本小节则结合客户端演示哨兵的另外两个作用:配置提供者和通知。

1.  代码示例

在介绍客户端的原理之前,先以Java客户端Jedis为例,演示一下使用方法:下面代码可以连接我们刚刚搭建的哨兵系统,并进行各种读写操作(代码中只演示如何连接哨兵,异常处理、资源关闭等未考虑)。

1

2

3

4

5

6

7

8

9

10

11

12

public static void testSentinel() throws Exception {

         String masterName = "mymaster";

         Set<String> sentinels = new HashSet<>();

         sentinels.add("192.168.92.128:26379");

         sentinels.add("192.168.92.128:26380");

         sentinels.add("192.168.92.128:26381");

 

         JedisSentinelPool pool = new JedisSentinelPool(masterName, sentinels); //初始化过程做了很多工作

         Jedis jedis = pool.getResource();

         jedis.set("key1""value1");

         pool.close();

}

2.  客户端原理

Jedis客户端对哨兵提供了很好的支持。如上述代码所示,我们只需要向Jedis提供哨兵节点集合和masterName,构造JedisSentinelPool对象;然后便可以像使用普通redis连接池一样来使用了:通过pool.getResource()获取连接,执行具体的命令。

在整个过程中,我们的代码不需要显式的指定主节点的地址,就可以连接到主节点;代码中对故障转移没有任何体现,就可以在哨兵完成故障转移后自动的切换主节点。之所以可以做到这一点,是因为在JedisSentinelPool的构造器中,进行了相关的工作;主要包括以下两点:

(1)遍历哨兵节点,获取主节点信息:遍历哨兵节点,通过其中一个哨兵节点+masterName获得主节点的信息;该功能是通过调用哨兵节点的sentinel get-master-addr-by-name命令实现,该命令示例如下:

一旦获得主节点信息,停止遍历(因此一般来说遍历到第一个哨兵节点,循环就停止了)。

(2)增加对哨兵的监听:这样当发生故障转移时,客户端便可以收到哨兵的通知,从而完成主节点的切换。具体做法是:利用redis提供的发布订阅功能,为每一个哨兵节点开启一个单独的线程,订阅哨兵节点的+switch-master频道,当收到消息时,重新初始化连接池。

3.  总结

通过客户端原理的介绍,可以加深对哨兵功能的理解:

(1)配置提供者:客户端可以通过哨兵节点+masterName获取主节点信息,在这里哨兵起到的作用就是配置提供者。

需要注意的是,哨兵只是配置提供者,而不是代理。二者的区别在于:如果是配置提供者,客户端在通过哨兵获得主节点信息后,会直接建立到主节点的连接,后续的请求(如set/get)会直接发向主节点;如果是代理,客户端的每一次请求都会发向哨兵,哨兵再通过主节点处理请求。

举一个例子可以很好的理解哨兵的作用是配置提供者,而不是代理。在前面部署的哨兵系统中,将哨兵节点的配置文件进行如下修改:

1

2

3

sentinel monitor mymaster 192.168.92.128 6379 2

改为

sentinel monitor mymaster 127.0.0.1 6379 2

然后,将前述客户端代码在局域网的另外一台机器上运行,会发现客户端无法连接主节点;这是因为哨兵作为配置提供者,客户端通过它查询到主节点的地址为127.0.0.1:6379,客户端会向127.0.0.1:6379建立redis连接,自然无法连接。如果哨兵是代理,这个问题就不会出现了。

(2)通知:哨兵节点在故障转移完成后,会将新的主节点信息发送给客户端,以便客户端及时切换主节点。

四、基本原理

前面介绍了哨兵部署、使用的基本方法,本部分介绍哨兵实现的基本原理。

1.  哨兵节点支持的命令

哨兵节点作为运行在特殊模式下的redis节点,其支持的命令与普通的redis节点不同。在运维中,我们可以通过这些命令查询或修改哨兵系统;不过更重要的是,哨兵系统要实现故障发现、故障转移等各种功能,离不开哨兵节点之间的通信,而通信的很大一部分是通过哨兵节点支持的命令来实现的。下面介绍哨兵节点支持的主要命令。

(1)基础查询:通过这些命令,可以查询哨兵系统的拓扑结构、节点信息、配置信息等。

  • info sentinel:获取监控的所有主节点的基本信息
  • sentinel masters:获取监控的所有主节点的详细信息
  • sentinel master mymaster:获取监控的主节点mymaster的详细信息
  • sentinel slaves mymaster:获取监控的主节点mymaster的从节点的详细信息
  • sentinel sentinels mymaster:获取监控的主节点mymaster的哨兵节点的详细信息
  • sentinel get-master-addr-by-name mymaster:获取监控的主节点mymaster的地址信息,前文已有介绍
  • sentinel is-master-down-by-addr:哨兵节点之间可以通过该命令询问主节点是否下线,从而对是否客观下线做出判断

(2)增加/移除对主节点的监控

sentinel monitor mymaster2 192.168.92.128 16379 2:与部署哨兵节点时配置文件中的sentinel monitor功能完全一样,不再详述

sentinel remove mymaster2:取消当前哨兵节点对主节点mymaster2的监控

(3)强制故障转移

sentinel failover mymaster:该命令可以强制对mymaster执行故障转移,即便当前的主节点运行完好;例如,如果当前主节点所在机器即将报废,便可以提前通过failover命令进行故障转移。

2.  基本原理

关于哨兵的原理,关键是了解以下几个概念。

(1)定时任务:每个哨兵节点维护了3个定时任务。定时任务的功能分别如下:通过向主从节点发送info命令获取最新的主从结构;通过发布订阅功能获取其他哨兵节点的信息;通过向其他节点发送ping命令进行心跳检测,判断是否下线。

(2)主观下线:在心跳检测的定时任务中,如果其他节点超过一定时间没有回复,哨兵节点就会将其进行主观下线。顾名思义,主观下线的意思是一个哨兵节点“主观地”判断下线;与主观下线相对应的是客观下线。

(3)客观下线:哨兵节点在对主节点进行主观下线后,会通过sentinel is-master-down-by-addr命令询问其他哨兵节点该主节点的状态;如果判断主节点下线的哨兵数量达到一定数值,则对该主节点进行客观下线。

需要特别注意的是,客观下线是主节点才有的概念;如果从节点和哨兵节点发生故障,被哨兵主观下线后,不会再有后续的客观下线和故障转移操作。

(4)选举领导者哨兵节点:当主节点被判断客观下线以后,各个哨兵节点会进行协商,选举出一个领导者哨兵节点,并由该领导者节点对其进行故障转移操作。

监视该主节点的所有哨兵都有可能被选为领导者,选举使用的算法是Raft算法;Raft算法的基本思路是先到先得:即在一轮选举中,哨兵A向B发送成为领导者的申请,如果B没有同意过其他哨兵,则会同意A成为领导者。选举的具体过程这里不做详细描述,一般来说,哨兵选择的过程很快,谁先完成客观下线,一般就能成为领导者。

(5)故障转移:选举出的领导者哨兵,开始进行故障转移操作,该操作大体可以分为3个步骤:

  • 在从节点中选择新的主节点:选择的原则是,首先过滤掉不健康的从节点;然后选择优先级最高的从节点(由slave-priority指定);如果优先级无法区分,则选择复制偏移量最大的从节点;如果仍无法区分,则选择runid最小的从节点。
  • 更新主从状态:通过slaveof no one命令,让选出来的从节点成为主节点;并通过slaveof命令让其他节点成为其从节点。
  • 将已经下线的主节点(即6379)设置为新的主节点的从节点,当6379重新上线后,它会成为新的主节点的从节点。

 

通过上述几个关键概念,可以基本了解哨兵的工作原理。为了更形象的说明,下图展示了领导者哨兵节点的日志,包括从节点启动到完成故障转移。

五、配置与实践建议

1.  配置

下面介绍与哨兵相关的几个配置。

(1) sentinel monitor {masterName} {masterIp} {masterPort} {quorum}

sentinel monitor是哨兵最核心的配置,在前文讲述部署哨兵节点时已说明,其中:masterName指定了主节点名称,masterIp和masterPort指定了主节点地址,quorum是判断主节点客观下线的哨兵数量阈值:当判定主节点下线的哨兵数量达到quorum时,对主节点进行客观下线。建议取值为哨兵数量的一半加1。

(2) sentinel down-after-milliseconds {masterName} {time}

sentinel down-after-milliseconds与主观下线的判断有关:哨兵使用ping命令对其他节点进行心跳检测,如果其他节点超过down-after-milliseconds配置的时间没有回复,哨兵就会将其进行主观下线。该配置对主节点、从节点和哨兵节点的主观下线判定都有效。

down-after-milliseconds的默认值是30000,即30s;可以根据不同的网络环境和应用要求来调整:值越大,对主观下线的判定会越宽松,好处是误判的可能性小,坏处是故障发现和故障转移的时间变长,客户端等待的时间也会变长。例如,如果应用对可用性要求较高,则可以将值适当调小,当故障发生时尽快完成转移;如果网络环境相对较差,可以适当提高该阈值,避免频繁误判。

(3) sentinel parallel-syncs {masterName} {number}

sentinel parallel-syncs与故障转移之后从节点的复制有关:它规定了每次向新的主节点发起复制操作的从节点个数。例如,假设主节点切换完成之后,有3个从节点要向新的主节点发起复制;如果parallel-syncs=1,则从节点会一个一个开始复制;如果parallel-syncs=3,则3个从节点会一起开始复制。

parallel-syncs取值越大,从节点完成复制的时间越快,但是对主节点的网络负载、硬盘负载造成的压力也越大;应根据实际情况设置。例如,如果主节点的负载较低,而从节点对服务可用的要求较高,可以适量增加parallel-syncs取值。parallel-syncs的默认值是1。

(4) sentinel failover-timeout {masterName} {time}

sentinel failover-timeout与故障转移超时的判断有关,但是该参数不是用来判断整个故障转移阶段的超时,而是其几个子阶段的超时,例如如果主节点晋升从节点时间超过timeout,或从节点向新的主节点发起复制操作的时间(不包括复制数据的时间)超过timeout,都会导致故障转移超时失败。

failover-timeout的默认值是180000,即180s;如果超时,则下一次该值会变为原来的2倍。

(5)除上述几个参数外,还有一些其他参数,如安全验证相关的参数,这里不做介绍。

2.  实践建议

(1)哨兵节点的数量应不止一个,一方面增加哨兵节点的冗余,避免哨兵本身成为高可用的瓶颈;另一方面减少对下线的误判。此外,这些不同的哨兵节点应部署在不同的物理机上。

(2)哨兵节点的数量应该是奇数,便于哨兵通过投票做出“决策”:领导者选举的决策、客观下线的决策等。

(3)各个哨兵节点的配置应一致,包括硬件、参数等;此外,所有节点都应该使用ntp或类似服务,保证时间准确、一致。

(4)哨兵的配置提供者和通知客户端功能,需要客户端的支持才能实现,如前文所说的Jedis;如果开发者使用的库未提供相应支持,则可能需要开发者自己实现。

(5)当哨兵系统中的节点在docker(或其他可能进行端口映射的软件)中部署时,应特别注意端口映射可能会导致哨兵系统无法正常工作,因为哨兵的工作基于与其他节点的通信,而docker的端口映射可能导致哨兵无法连接到其他节点。例如,哨兵之间互相发现,依赖于它们对外宣称的IP和port,如果某个哨兵A部署在做了端口映射的docker中,那么其他哨兵使用A宣称的port无法连接到A。

 

一、集群的作用

集群,即Redis Cluster,是Redis 3.0开始引入的分布式存储方案。

集群由多个节点(Node)组成,Redis的数据分布在这些节点中。集群中的节点分为主节点和从节点:只有主节点负责读写请求和集群信息的维护;从节点只进行主节点数据和状态信息的复制。

集群的作用,可以归纳为两点:

1、数据分区:数据分区(或称数据分片)是集群最核心的功能。

集群将数据分散到多个节点,一方面突破了Redis单机内存大小的限制,存储容量大大增加;另一方面每个主节点都可以对外提供读服务和写服务,大大提高了集群的响应能力。

Redis单机内存大小受限问题,在介绍持久化和主从复制时都有提及;例如,如果单机内存太大,bgsave和bgrewriteaof的fork操作可能导致主进程阻塞,主从环境下主机切换时可能导致从节点长时间无法提供服务,全量复制阶段主节点的复制缓冲区可能溢出……。

2、高可用:集群支持主从复制和主节点的自动故障转移(与哨兵类似);当任一节点发生故障时,集群仍然可以对外提供服务。

本文内容基于Redis 3.0.6。

二、集群的搭建

这一部分我们将搭建一个简单的集群:共6个节点,3主3从。方便起见:所有节点在同一台服务器上,以端口号进行区分;配置从简。3个主节点端口号:7000/7001/7002,对应的从节点端口号:8000/8001/8002。

集群的搭建有两种方式:(1)手动执行Redis命令,一步步完成搭建;(2)使用Ruby脚本搭建。二者搭建的原理是一样的,只是Ruby脚本将Redis命令进行了打包封装;在实际应用中推荐使用脚本方式,简单快捷不容易出错。下面分别介绍这两种方式。

1. 执行Redis命令搭建集群

集群的搭建可以分为四步:(1)启动节点:将节点以集群模式启动,此时节点是独立的,并没有建立联系;(2)节点握手:让独立的节点连成一个网络;(3)分配槽:将16384个槽分配给主节点;(4)指定主从关系:为从节点指定主节点。

实际上,前三步完成后集群便可以对外提供服务;但指定从节点后,集群才能够提供真正高可用的服务。

(1)启动节点

集群节点的启动仍然是使用redis-server命令,但需要使用集群模式启动。下面是7000节点的配置文件(只列出了节点正常工作关键配置,其他配置(如开启AOF)可以参照单机节点进行):

1

2

3

4

5

6

7

#redis-7000.conf

port 7000

cluster-enabled yes

cluster-config-file "node-7000.conf"

logfile "log-7000.log"

dbfilename "dump-7000.rdb"

daemonize yes

其中的cluster-enabled和cluster-config-file是与集群相关的配置。

cluster-enabled yesRedis实例可以分为单机模式(standalone)和集群模式(cluster);cluster-enabled yes可以启动集群模式。在单机模式下启动的Redis实例,如果执行info server命令,可以发现redis_mode一项为standalone,如下图所示:

集群模式下的节点,其redis_mode为cluster,如下图所示:

cluster-config-file该参数指定了集群配置文件的位置。每个节点在运行过程中,会维护一份集群配置文件;每当集群信息发生变化时(如增减节点),集群内所有节点会将最新信息更新到该配置文件;当节点重启后,会重新读取该配置文件,获取集群信息,可以方便的重新加入到集群中。也就是说,当Redis节点以集群模式启动时,会首先寻找是否有集群配置文件,如果有则使用文件中的配置启动,如果没有,则初始化配置并将配置保存到文件中。集群配置文件由Redis节点维护,不需要人工修改。

编辑好配置文件后,使用redis-server命令启动该节点:

1

redis-server redis-7000.conf

节点启动以后,通过cluster nodes命令可以查看节点的情况,如下图所示。

其中返回值第一项表示节点id,由40个16进制字符串组成,节点id与 主从复制 一文中提到的runId不同:Redis每次启动runId都会重新创建,但是节点id只在集群初始化时创建一次,然后保存到集群配置文件中,以后节点重新启动时会直接在集群配置文件中读取。

其他节点使用相同办法启动,不再赘述。需要特别注意,在启动节点阶段,节点是没有主从关系的,因此从节点不需要加slaveof配置。

(2)节点握手

节点启动以后是相互独立的,并不知道其他节点存在;需要进行节点握手,将独立的节点组成一个网络。

节点握手使用cluster meet {ip} {port}命令实现,例如在7000节点中执行cluster meet 192.168.72.128 7001,可以完成7000节点和7001节点的握手;注意ip使用的是局域网ip而不是localhost或127.0.0.1,是为了其他机器上的节点或客户端也可以访问。此时再使用cluster nodes查看:

在7001节点下也可以类似查看:

同理,在7000节点中使用cluster meet命令,可以将所有节点加入到集群,完成节点握手:

1

2

3

4

cluster meet 192.168.72.128 7002

cluster meet 192.168.72.128 8000

cluster meet 192.168.72.128 8001

cluster meet 192.168.72.128 8002

执行完上述命令后,可以看到7000节点已经感知到了所有其他节点:

通过节点之间的通信,每个节点都可以感知到所有其他节点,以8000节点为例:

(3)分配槽

在Redis集群中,借助槽实现数据分区,具体原理后文会介绍。集群有16384个槽,槽是数据管理和迁移的基本单位。当数据库中的16384个槽都分配了节点时,集群处于上线状态(ok);如果有任意一个槽没有分配节点,则集群处于下线状态(fail)。

cluster info命令可以查看集群状态,分配槽之前状态为fail:

分配槽使用cluster addslots命令,执行下面的命令将槽(编号0-16383)全部分配完毕:

1

2

3

redis-cli -p 7000 cluster addslots {0..5461}

redis-cli -p 7001 cluster addslots {5462..10922}

redis-cli -p 7002 cluster addslots {10923..16383}

此时查看集群状态,显示所有槽分配完毕,集群进入上线状态:

(4)指定主从关系

集群中指定主从关系不再使用slaveof命令,而是使用cluster replicate命令;参数使用节点id。

通过cluster nodes获得几个主节点的节点id后,执行下面的命令为每个从节点指定主节点:

1

2

3

redis-cli -p 8000 cluster replicate be816eba968bc16c884b963d768c945e86ac51ae

redis-cli -p 8001 cluster replicate 788b361563acb175ce8232569347812a12f1fdb4

redis-cli -p 8002 cluster replicate a26f1624a3da3e5197dde267de683d61bb2dcbf1

此时执行cluster nodes查看各个节点的状态,可以看到主从关系已经建立。

至此,集群搭建完毕。

2. 使用Ruby脚本搭建集群

在{REDIS_HOME}/src目录下可以看到redis-trib.rb文件,这是一个Ruby脚本,可以实现自动化的集群搭建。

(1)安装Ruby环境

以Ubuntu为例,如下操作即可安装Ruby环境:

1

2

apt-get install ruby #安装ruby环境

gem install redis #gem是ruby的包管理工具,该命令可以安装ruby-redis依赖

(2)启动节点

与第一种方法中的“启动节点”完全相同。

(3)搭建集群

redis-trib.rb脚本提供了众多命令,其中create用于搭建集群,使用方法如下:

1

./redis-trib.rb create --replicas 1 192.168.72.128:7000 192.168.72.128:7001 192.168.72.128:7002 192.168.72.128:8000 192.168.72.128:8001 192.168.72.128:8002

其中:--replicas=1表示每个主节点有1个从节点;后面的多个{ip:port}表示节点地址,前面的做主节点,后面的做从节点。使用redis-trib.rb搭建集群时,要求节点不能包含任何槽和数据。

执行创建命令后,脚本会给出创建集群的计划,如下图所示;计划包括哪些是主节点,哪些是从节点,以及如何分配槽。

输入yes确认执行计划,脚本便开始按照计划执行,如下图所示。

至此,集群搭建完毕。

3. 集群方案设计

设计集群方案时,至少要考虑以下因素:

(1)高可用要求:根据故障转移的原理,至少需要3个主节点才能完成故障转移,且3个主节点不应在同一台物理机上;每个主节点至少需要1个从节点,且主从节点不应在一台物理机上;因此高可用集群至少包含6个节点。

(2)数据量和访问量:估算应用需要的数据量和总访问量(考虑业务发展,留有冗余),结合每个主节点的容量和能承受的访问量(可以通过benchmark得到较准确估计),计算需要的主节点数量。

(3)节点数量限制:Redis官方给出的节点数量限制为1000,主要是考虑节点间通信带来的消耗。在实际应用中应尽量避免大集群;如果节点数量不足以满足应用对Redis数据量和访问量的要求,可以考虑:(1)业务分割,大集群分为多个小集群;(2)减少不必要的数据;(3)调整数据过期策略等。

(4)适度冗余:Redis可以在不影响集群服务的情况下增加节点,因此节点数量适当冗余即可,不用太大。

三、集群的基本原理

上一章介绍了集群的搭建方法和设计方案,下面将进一步深入,介绍集群的原理。集群最核心的功能是数据分区,因此首先介绍数据的分区规则;然后介绍集群实现的细节:通信机制和数据结构;最后以cluster meet(节点握手)、cluster addslots(槽分配)为例,说明节点是如何利用上述数据结构和通信机制实现集群命令的。

1. 数据分区方案

数据分区有顺序分区、哈希分区等,其中哈希分区由于其天然的随机性,使用广泛;集群的分区方案便是哈希分区的一种。

哈希分区的基本思路是:对数据的特征值(如key)进行哈希,然后根据哈希值决定数据落在哪个节点。常见的哈希分区包括:哈希取余分区、一致性哈希分区、带虚拟节点的一致性哈希分区等。

衡量数据分区方法好坏的标准有很多,其中比较重要的两个因素是(1)数据分布是否均匀(2)增加或删减节点对数据分布的影响。由于哈希的随机性,哈希分区基本可以保证数据分布均匀;因此在比较哈希分区方案时,重点要看增减节点对数据分布的影响。

(1)哈希取余分区

哈希取余分区思路非常简单:计算key的hash值,然后对节点数量进行取余,从而决定数据映射到哪个节点上。该方案最大的问题是,当新增或删减节点时,节点数量发生变化,系统中所有的数据都需要重新计算映射关系,引发大规模数据迁移。

(2)一致性哈希分区

一致性哈希算法将整个哈希值空间组织成一个虚拟的圆环,如下图所示,范围为0-2^32-1;对于每个数据,根据key计算hash值,确定数据在环上的位置,然后从此位置沿环顺时针行走,找到的第一台服务器就是其应该映射到的服务器。

图片来源:https://www.cnblogs.com/lpfuture/p/5796398.html

与哈希取余分区相比,一致性哈希分区将增减节点的影响限制在相邻节点。以上图为例,如果在node1和node2之间增加node5,则只有node2中的一部分数据会迁移到node5;如果去掉node2,则原node2中的数据只会迁移到node4中,只有node4会受影响。

一致性哈希分区的主要问题在于,当节点数量较少时,增加或删减节点,对单个节点的影响可能很大,造成数据的严重不平衡。还是以上图为例,如果去掉node2,node4中的数据由总数据的1/4左右变为1/2左右,与其他节点相比负载过高。

(3)带虚拟节点的一致性哈希分区

该方案在一致性哈希分区的基础上,引入了虚拟节点的概念。Redis集群使用的便是该方案,其中的虚拟节点称为槽(slot)。槽是介于数据和实际节点之间的虚拟概念;每个实际节点包含一定数量的槽,每个槽包含哈希值在一定范围内的数据。引入槽以后,数据的映射关系由数据hash->实际节点,变成了数据hash->槽->实际节点。

在使用了槽的一致性哈希分区中,槽是数据管理和迁移的基本单位。槽解耦了数据和实际节点之间的关系,增加或删除节点对系统的影响很小。仍以上图为例,系统中有4个实际节点,假设为其分配16个槽(0-15); 槽0-3位于node1,4-7位于node2,以此类推。如果此时删除node2,只需要将槽4-7重新分配即可,例如槽4-5分配给node1,槽6分配给node3,槽7分配给node4;可以看出删除node2后,数据在其他节点的分布仍然较为均衡。

槽的数量一般远小于2^32,远大于实际节点的数量;在Redis集群中,槽的数量为16384。

下面这张图很好的总结了Redis集群将数据映射到实际节点的过程:

图片修改自:https://blog.csdn.net/yejingtao703/article/details/78484151

(1)Redis对数据的特征值(一般是key)计算哈希值,使用的算法是CRC16。

(2)根据哈希值,计算数据属于哪个槽。

(3)根据槽与节点的映射关系,计算数据属于哪个节点。

2. 节点通信机制

集群要作为一个整体工作,离不开节点之间的通信。

两个端口

在哨兵系统中,节点分为数据节点和哨兵节点:前者存储数据,后者实现额外的控制功能。在集群中,没有数据节点与非数据节点之分:所有的节点都存储数据,也都参与集群状态的维护。为此,集群中的每个节点,都提供了两个TCP端口:

  • 普通端口:即我们在前面指定的端口(7000等)。普通端口主要用于为客户端提供服务(与单机节点类似);但在节点间数据迁移时也会使用。
  • 集群端口:端口号是普通端口+10000(10000是固定值,无法改变),如7000节点的集群端口为17000。集群端口只用于节点之间的通信,如搭建集群、增减节点、故障转移等操作时节点间的通信;不要使用客户端连接集群接口。为了保证集群可以正常工作,在配置防火墙时,要同时开启普通端口和集群端口。

Gossip协议

节点间通信,按照通信协议可以分为几种类型:单对单、广播、Gossip协议等。重点是广播和Gossip的对比。

广播是指向集群内所有节点发送消息;优点是集群的收敛速度快(集群收敛是指集群内所有节点获得的集群信息是一致的),缺点是每条消息都要发送给所有节点,CPU、带宽等消耗较大。

Gossip协议的特点是:在节点数量有限的网络中,每个节点都“随机”的与部分节点通信(并不是真正的随机,而是根据特定的规则选择通信的节点),经过一番杂乱无章的通信,每个节点的状态很快会达到一致。Gossip协议的优点有负载(比广播)低、去中心化、容错性高(因为通信有冗余)等;缺点主要是集群的收敛速度慢。

消息类型

集群中的节点采用固定频率(每秒10次)的定时任务进行通信相关的工作:判断是否需要发送消息及消息类型、确定接收节点、发送消息等。如果集群状态发生了变化,如增减节点、槽状态变更,通过节点间的通信,所有节点会很快得知整个集群的状态,使集群收敛。

节点间发送的消息主要分为5种:meet消息、ping消息、pong消息、fail消息、publish消息。不同的消息类型,通信协议、发送的频率和时机、接收节点的选择等是不同的。

  • MEET消息:在节点握手阶段,当节点收到客户端的CLUSTER MEET命令时,会向新加入的节点发送MEET消息,请求新节点加入到当前集群;新节点收到MEET消息后会回复一个PONG消息。
  • PING消息:集群里每个节点每秒钟会选择部分节点发送PING消息,接收者收到消息后会回复一个PONG消息。PING消息的内容是自身节点和部分其他节点的状态信息;作用是彼此交换信息,以及检测节点是否在线。PING消息使用Gossip协议发送,接收节点的选择兼顾了收敛速度和带宽成本,具体规则如下:(1)随机找5个节点,在其中选择最久没有通信的1个节点(2)扫描节点列表,选择最近一次收到PONG消息时间大于cluster_node_timeout/2的所有节点,防止这些节点长时间未更新。
  • PONG消息:PONG消息封装了自身状态数据。可以分为两种:第一种是在接到MEET/PING消息后回复的PONG消息;第二种是指节点向集群广播PONG消息,这样其他节点可以获知该节点的最新信息,例如故障恢复后新的主节点会广播PONG消息。
  • FAIL消息:当一个主节点判断另一个主节点进入FAIL状态时,会向集群广播这一FAIL消息;接收节点会将这一FAIL消息保存起来,便于后续的判断。
  • PUBLISH消息:节点收到PUBLISH命令后,会先执行该命令,然后向集群广播这一消息,接收节点也会执行该PUBLISH命令。

3. 数据结构

节点需要专门的数据结构来存储集群的状态。所谓集群的状态,是一个比较大的概念,包括:集群是否处于上线状态、集群中有哪些节点、节点是否可达、节点的主从状态、槽的分布……

节点为了存储集群状态而提供的数据结构中,最关键的是clusterNode和clusterState结构:前者记录了一个节点的状态,后者记录了集群作为一个整体的状态。

clusterNode

clusterNode结构保存了一个节点的当前状态,包括创建时间、节点id、ip和端口号等。每个节点都会用一个clusterNode结构记录自己的状态,并为集群内所有其他节点都创建一个clusterNode结构来记录节点状态。

下面列举了clusterNode的部分字段,并说明了字段的含义和作用:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

typedef struct clusterNode {

    //节点创建时间

    mstime_t ctime;

 

    //节点id

    char name[REDIS_CLUSTER_NAMELEN];

 

    //节点的ip和端口号

    char ip[REDIS_IP_STR_LEN];

    int port;

 

    //节点标识:整型,每个bit都代表了不同状态,如节点的主从状态、是否在线、是否在握手等

    int flags;

 

    //配置纪元:故障转移时起作用,类似于哨兵的配置纪元

    uint64_t configEpoch;

 

    //槽在该节点中的分布:占用16384/8个字节,16384个比特;每个比特对应一个槽:比特值为1,则该比特对应的槽在节点中;比特值为0,则该比特对应的槽不在节点中

    unsigned char slots[16384/8];

 

    //节点中槽的数量

    int numslots;

 

    …………

 

} clusterNode;

除了上述字段,clusterNode还包含节点连接、主从复制、故障发现和转移需要的信息等。

clusterState

clusterState结构保存了在当前节点视角下,集群所处的状态。主要字段包括:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

typedef struct clusterState {

 

    //自身节点

    clusterNode *myself;

 

    //配置纪元

    uint64_t currentEpoch;

 

    //集群状态:在线还是下线

    int state;

 

    //集群中至少包含一个槽的节点数量

    int size;

 

    //哈希表,节点名称->clusterNode节点指针

    dict *nodes;

  

    //槽分布信息:数组的每个元素都是一个指向clusterNode结构的指针;如果槽还没有分配给任何节点,则为NULL

    clusterNode *slots[16384];

 

    …………

     

} clusterState;

除此之外,clusterState还包括故障转移、槽迁移等需要的信息。

4. 集群命令的实现

这一部分将以cluster meet(节点握手)、cluster addslots(槽分配)为例,说明节点是如何利用上述数据结构和通信机制实现集群命令的。

cluster meet

假设要向A节点发送cluster meet命令,将B节点加入到A所在的集群,则A节点收到命令后,执行的操作如下:

1)  A为B创建一个clusterNode结构,并将其添加到clusterState的nodes字典中

2)  A向B发送MEET消息

3)  B收到MEET消息后,会为A创建一个clusterNode结构,并将其添加到clusterState的nodes字典中

4)  B回复A一个PONG消息

5)  A收到B的PONG消息后,便知道B已经成功接收自己的MEET消息

6)  然后,A向B返回一个PING消息

7)  B收到A的PING消息后,便知道A已经成功接收自己的PONG消息,握手完成

8)  之后,A通过Gossip协议将B的信息广播给集群内其他节点,其他节点也会与B握手;一段时间后,集群收敛,B成为集群内的一个普通节点

通过上述过程可以发现,集群中两个节点的握手过程与TCP类似,都是三次握手:A向B发送MEET;B向A发送PONG;A向B发送PING。

cluster addslots

集群中槽的分配信息,存储在clusterNode的slots数组和clusterState的slots数组中,两个数组的结构前面已做介绍;二者的区别在于:前者存储的是该节点中分配了哪些槽,后者存储的是集群中所有槽分别分布在哪个节点。

cluster addslots命令接收一个槽或多个槽作为参数,例如在A节点上执行cluster addslots {0..10}命令,是将编号为0-10的槽分配给A节点,具体执行过程如下:

1)  遍历输入槽,检查它们是否都没有分配,如果有一个槽已分配,命令执行失败;方法是检查输入槽在clusterState.slots[]中对应的值是否为NULL。

2)  遍历输入槽,将其分配给节点A;方法是修改clusterNode.slots[]中对应的比特为1,以及clusterState.slots[]中对应的指针指向A节点

3)  A节点执行完成后,通过节点通信机制通知其他节点,所有节点都会知道0-10的槽分配给了A节点

四、客户端访问集群

在集群中,数据分布在不同的节点中,客户端通过某节点访问数据时,数据可能不在该节点中;下面介绍集群是如何处理这个问题的。

1. redis-cli

当节点收到redis-cli发来的命令(如set/get)时,过程如下:

(1)计算key属于哪个槽:CRC16(key) & 16383

集群提供的cluster keyslot命令也是使用上述公式实现,如:

(2)判断key所在的槽是否在当前节点:假设key位于第i个槽,clusterState.slots[i]则指向了槽所在的节点,如果clusterState.slots[i]==clusterState.myself,说明槽在当前节点,可以直接在当前节点执行命令;否则,说明槽不在当前节点,则查询槽所在节点的地址(clusterState.slots[i].ip/port),并将其包装到MOVED错误中返回给redis-cli。

(3)redis-cli收到MOVED错误后,根据返回的ip和port重新发送请求。

下面的例子展示了redis-cli和集群的互动过程:在7000节点中操作key1,但key1所在的槽9189在节点7001中,因此节点返回MOVED错误(包含7001节点的ip和port)给redis-cli,redis-cli重新向7001发起请求。

上例中,redis-cli通过-c指定了集群模式,如果没有指定,redis-cli无法处理MOVED错误:

2. Smart客户端

redis-cli这一类客户端称为Dummy客户端,因为它们在执行命令前不知道数据在哪个节点,需要借助MOVED错误重新定向。与Dummy客户端相对应的是Smart客户端。

Smart客户端(以Java的JedisCluster为例)的基本原理:

(1)JedisCluster初始化时,在内部维护slot->node的缓存,方法是连接任一节点,执行cluster slots命令,该命令返回如下所示:

(2)此外,JedisCluster为每个节点创建连接池(即JedisPool)。

(3)当执行命令时,JedisCluster根据key->slot->node选择需要连接的节点,发送命令。如果成功,则命令执行完毕。如果执行失败,则会随机选择其他节点进行重试,并在出现MOVED错误时,使用cluster slots重新同步slot->node的映射关系。

下面代码演示了如何使用JedisCluster访问集群(未考虑资源释放、异常处理等):

1

2

3

4

5

6

7

8

9

10

11

12

public static void test() {

   Set<HostAndPort> nodes = new HashSet<>();

   nodes.add(new HostAndPort("192.168.72.128"7000));

   nodes.add(new HostAndPort("192.168.72.128"7001));

   nodes.add(new HostAndPort("192.168.72.128"7002));

   nodes.add(new HostAndPort("192.168.72.128"8000));

   nodes.add(new HostAndPort("192.168.72.128"8001));

   nodes.add(new HostAndPort("192.168.72.128"8002));

   JedisCluster cluster = new JedisCluster(nodes);

   System.out.println(cluster.get("key1"));

   cluster.close();

}

注意事项如下:

(1)JedisCluster中已经包含所有节点的连接池,因此JedisCluster要使用单例。

(2)客户端维护了slot->node映射关系以及为每个节点创建了连接池,当节点数量较多时,应注意客户端内存资源和连接资源的消耗。

(3)Jedis较新版本针对JedisCluster做了一些性能方面的优化,如cluster slots缓存更新和锁阻塞等方面的优化,应尽量使用2.8.2及以上版本的Jedis。

五、实践须知

前面介绍了集群正常运行和访问的方法和原理,下面是一些重要的补充内容。

1. 集群伸缩

实践中常常需要对集群进行伸缩,如访问量增大时的扩容操作。Redis集群可以在不影响对外服务的情况下实现伸缩;伸缩的核心是槽迁移:修改槽与节点的对应关系,实现槽(即数据)在节点之间的移动。例如,如果槽均匀分布在集群的3个节点中,此时增加一个节点,则需要从3个节点中分别拿出一部分槽给新节点,从而实现槽在4个节点中的均匀分布。

增加节点

假设要增加7003和8003节点,其中8003是7003的从节点;步骤如下:

(1)启动节点:方法参见集群搭建

(2)节点握手:可以使用cluster meet命令,但在生产环境中建议使用redis-trib.rb的add-node工具,其原理也是cluster meet,但它会先检查新节点是否已加入其它集群或者存在数据,避免加入到集群后带来混乱。

1

2

redis-trib.rb add-node 192.168.72.128:7003 192.168.72.128 7000

redis-trib.rb add-node 192.168.72.128:8003 192.168.72.128 7000

(3)迁移槽:推荐使用redis-trib.rb的reshard工具实现。reshard自动化程度很高,只需要输入redis-trib.rb reshard ip:port (ip和port可以是集群中的任一节点),然后按照提示输入以下信息,槽迁移会自动完成:

    • 待迁移的槽数量:16384个槽均分给4个节点,每个节点4096个槽,因此待迁移槽数量为4096
    • 目标节点id:7003节点的id
    • 源节点的id:7000/7001/7002节点的id

(4)指定主从关系:方法参见集群搭建

减少节点

假设要下线7000/8000节点,可以分为两步:

(1)迁移槽:使用reshard将7000节点中的槽均匀迁移到7001/7002/7003节点

(2)下线节点:使用redis-trib.rb del-node工具;应先下线从节点再下线主节点,因为若主节点先下线,从节点会被指向其他主节点,造成不必要的全量复制。

1

2

redis-trib.rb del-node 192.168.72.128:7001 {节点8000的id}

redis-trib.rb del-node 192.168.72.128:7001 {节点7000的id}

ASK错误

集群伸缩的核心是槽迁移。在槽迁移过程中,如果客户端向源节点发送命令,源节点执行流程如下:

图片来源:《Redis设计与实现》

客户端收到ASK错误后,从中读取目标节点的地址信息,并向目标节点重新发送请求,就像收到MOVED错误时一样。但是二者有很大区别:ASK错误说明数据正在迁移,不知道何时迁移完成,因此重定向是临时的,SMART客户端不会刷新slots缓存;MOVED错误重定向则是(相对)永久的,SMART客户端会刷新slots缓存。

2. 故障转移

在 哨兵 一文中,介绍了哨兵实现故障发现和故障转移的原理。虽然细节上有很大不同,但集群的实现与哨兵思路类似:通过定时任务发送PING消息检测其他节点状态;节点下线分为主观下线和客观下线;客观下线后选取从节点进行故障转移。

与哨兵一样,集群只实现了主节点的故障转移;从节点故障时只会被下线,不会进行故障转移。因此,使用集群时,应谨慎使用读写分离技术,因为从节点故障会导致读服务不可用,可用性变差。

这里不再详细介绍故障转移的细节,只对重要事项进行说明:

节点数量:在故障转移阶段,需要由主节点投票选出哪个从节点成为新的主节点;从节点选举胜出需要的票数为N/2+1;其中N为主节点数量(包括故障主节点),但故障主节点实际上不能投票。因此为了能够在故障发生时顺利选出从节点,集群中至少需要3个主节点(且部署在不同的物理机上)。

故障转移时间:从主节点故障发生到完成转移,所需要的时间主要消耗在主观下线识别、主观下线传播、选举延迟等几个环节;具体时间与参数cluster-node-timeout有关,一般来说:

故障转移时间(毫秒) ≤ 1.5 * cluster-node-timeout + 1000

cluster-node-timeout的默认值为15000ms(15s),因此故障转移时间会在20s量级。

3. 集群的限制及应对方法

由于集群中的数据分布在不同节点中,导致一些功能受限,包括:

(1)key批量操作受限:例如mget、mset操作,只有当操作的key都位于一个槽时,才能进行。针对该问题,一种思路是在客户端记录槽与key的信息,每次针对特定槽执行mget/mset;另外一种思路是使用Hash Tag,将在下一小节介绍。

(2)keys/flushall等操作:keys/flushall等操作可以在任一节点执行,但是结果只针对当前节点,例如keys操作只返回当前节点的所有键。针对该问题,可以在客户端使用cluster nodes获取所有节点信息,并对其中的所有主节点执行keys/flushall等操作。

(3)事务/Lua脚本:集群支持事务及Lua脚本,但前提条件是所涉及的key必须在同一个节点。Hash Tag可以解决该问题。

(4)数据库:单机Redis节点可以支持16个数据库,集群模式下只支持一个,即db0。

(5)复制结构:只支持一层复制结构,不支持嵌套。

4. Hash Tag

Hash Tag原理是:当一个key包含 {} 的时候,不对整个key做hash,而仅对 {} 包括的字符串做hash

Hash Tag可以让不同的key拥有相同的hash值,从而分配在同一个槽里;这样针对不同key的批量操作(mget/mset等),以及事务、Lua脚本等都可以支持。不过Hash Tag可能会带来数据分配不均的问题,这时需要:(1)调整不同节点中槽的数量,使数据分布尽量均匀;(2)避免对热点数据使用Hash Tag,导致请求分布不均。

下面是使用Hash Tag的一个例子;通过对product加Hash Tag,可以将所有产品信息放到同一个槽中,便于操作。

5. 参数优化

cluster_node_timeout

cluster_node_timeout参数在前面已经初步介绍;它的默认值是15s,影响包括:

(1)影响PING消息接收节点的选择:值越大对延迟容忍度越高,选择的接收节点越少,可以降低带宽,但会降低收敛速度;应根据带宽情况和应用要求进行调整。

(2)影响故障转移的判定和时间:值越大,越不容易误判,但完成转移消耗时间越长;应根据网络状况和应用要求进行调整。

cluster-require-full-coverage

前面提到,只有当16384个槽全部分配完毕时,集群才能上线。这样做是为了保证集群的完整性,但同时也带来了新的问题:当主节点发生故障而故障转移尚未完成,原主节点中的槽不在任何节点中,此时会集群处于下线状态,无法响应客户端的请求。

cluster-require-full-coverage参数可以改变这一设定:如果设置为no,则当槽没有完全分配时,集群仍可以上线。参数默认值为yes,如果应用对可用性要求较高,可以修改为no,但需要自己保证槽全部分配。

6. redis-trib.rb

redis-trib.rb提供了众多实用工具:创建集群、增减节点、槽迁移、检查完整性、数据重新平衡等;通过help命令可以查看详细信息。在实践中如果能使用redis-trib.rb工具则尽量使用,不但方便快捷,还可以大大降低出错概率。

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值