Redis使用文档一

1 Redis概述

1.1前言

Redis是一个开源、支持网络、基于内存亦可持久化的日志型、键值对存储数据库。使用ANSI C编写,并提供多种语言的API。其开发由VMware主持,是最流行的键值对存储数据库之一。

Redis的一大特点就是速度异常快,官方公布的性能测试结果显示,每秒钟可以达到10万次的操作。

1.2安装和验证

Redis的官网上,我们可以方便地下载Redis的各种版本,其官网下载地址为:http://redis.io/download

我们下载了redis的稳定版redis-2.8.9.tar.gz

我们依次执行以下命令:

     $ tar xzf redis-2.8.9.tar.gz

     $ cd redis-2.8.9

     $ make

 在执行完以上命令后,会在同级目录下生成src目录。再执行命令:

      $ src/redis-server

 就启动好了Redis

 

  快速验证服务是否启动成功可以执行以下命令:

        $ src/redis-cli

        redis> set foo bar

        OK

        redis> get foo

        "bar"

2 Redis数据结构 

Redis以键值的形式存储我们的数据。

Redis key值是二进制安全的,这意味着可以用任何二进制序列作为key值,例如字符串、或者一个JPEG的文件。特殊地,空字符串是一个有效的key值。

另外,对于我们的系统应用,如果多个系统公用一个redis实例,为了避免键值冲突,一个解决的办法,就是在key中包含系统名称。例如有两个用户账号信息的key,分别为:system1.account_info.123456system2.account_info.123456。当然,后边我们会提到,通过database来区分key的空间,也是一个不错的方案。 

redis提供了五种数据类型:String(字符串)、list(双向链表)、set(集合)、zset(有序集合)和hash(哈希类型)。

String(字符串):最常见的值,例如“aaa”“{no:'1234',name:'张三'}”等等。redis支持对其包括setgetincrappendstrlen等操作。

list(双向链表):数组,多用于1key对应多个value的场景。redis支持对其进行LpopLsetRpush等操作。

set(集合):也是一个key对应多个valude的场景。但对其的操作,主要是和集合相关的,例如saddsmovesdiffsunion等。

zset(有序集合):存储元素和set相近,但是内部以一个score来排序我们放进去的数据。redis对其支持的操作有ZADDZRANGEZREVRANGE等操作。

hash(哈希):值又是一个key-value键值对的集合。如果整个redis相当于一个java里的HashMap的话,类型为hashredis存储值又是一个HashMap。对其的操作包括HGETHDELHSETHKEYS等。

如何使用Redis

现阶段,我们可以通过两种方式来使用redis:命令方式、客户端方式。

3.1 Redis命令

参考下列网址的说明 https://redis.readthedocs.org/en/latest/

3.2 Java 客户端

 JedisRedis首推的java客户端开发包。该项目在git的源代码在:https://github.com/xetorthio/jedis

 Jedis主要功能是对redis的所有命令操作进行封装,供java开发人员调用。Jedis处理我们的每个命令调用过程如下:

     a.   根据提供的ipportpassword连接redis服务器,并持有连接;

     b.   接收各个命令及其参数;

     c.   对参数按utf8格式编码成byte[]

     d.   byte[]组装成符合redis协议格式的顺序,并添加redis格式要求的一些分隔符;例如:将byte[]形式的参数1{ 0x01, 0x02, 0x03, 0x04 }和参数2{ 0x05, 0x06, 0x07, 0x08 }之间用“\r”“\n”分开。(更多地关于redis协议的内容,请关注4.3.1Redis协议)

     e.   通过发送TCP请求(socket),将组装后的redis协议内容发送到redis服务器执行。

     f.    接收redis返回的符合redis协议的命令执行结果,通过utf8格式将byte[]转成str,解析出响应字符串,作为命令的执行结果返回给用户;

     g.   如果需要,关闭连接。

 

 Jedis中,Jedis.class是提供给开发人员使用的API类。Jedis.class继承自BinaryJedis.class。前者接收明文的参数,例如“aa”,后者接收byte[]形式的参数。如果我们调用Jedis.class中的Api,根据上述过程,我们可以看到,明码的参数会被转成byte[]形式的参数,最终调用BinaryJedis.class中的api完成我们的命令执行。

 面向redis管理的操作封装类包括:ClientConnectionProtocol 

 鉴于Jedis的工程代码比较简单,而且有很多的范例和测试代码,在此只是简单地说明一下工程的包结构以及查找案例的方法。

 redis.clients.jedis.tests.benchmark

 正如包名一样,此包下面的代码为客户端的示例代码。如果刚接触jedisredis,则可以直接修改其中的ip和端口等,体验一下redis

 redis.clients.jedis.tests.commands

 该包下面是对jedis的所有commond的单元测试案例。其测试的代码比较简洁,如果大家对jedis的某个命令使用不太明确,在此处搜索其使用方法,应该是一个不错的选择。

 更多关于jedis的细节,我们可以直接看Jedis的源码。

3.3 Redis通信

接下来,我们看看如果哪天我们觉得jedis不好用了,我们想自己写一套客户端,我们应该怎么来和redis通信交互。

首先,redis和外部通信,只支持tcp协议,端口默认为6379

其次,如果想要redis能解析你发给它的命令和参数,我们的命令和参数必须符合redis协议。另外,redis回复给我们的响应信息,也是按照redis协议来组装。接下来,我们详细看看redis协议是怎么回事。

3.3.1 Redis协议

在这个协议中,所有发送至Redis 服务器的参数都是二进制安全(binary safe)的。

我们先看一个实际的例子:

我们想发送一个set(“mykey”, “myvalue”)的命令给redis,那么这条命令最终会被转换成符合redis协议的形式(真正传给redis的,是byte[]型数据,这里只是为了便于说明和看清问题,因而用没有转换),其内容如下:

"*3\r\n$3\r\nSET\r\n$5\r\nmykey\r\n$7\r\nmyvalue\r\n"

我们可以对照协议的说明来解读下上述命令。

协议的格式为:

*<number of arguments> CR LF

$<number of bytes of argument 1> CR LF

<argument data> CR LF

...

$<number of bytes of argument N> CR LF

<argument data> CR LF

我们结合协议的格式解读一下上边实际的例子:

 a.   CR表示\rLF表示\n;每个Redis命令或者客户端和服务器之间传输的数据都以\r\n (CRLF)结束;

 b.   第一个以*号开始的数字表示参数的个数;其中,我们的命令名称set是第一个参数;

 c.   各个参数以$开始来表示;

 d.   第一个$后紧接着的内容为第一个参数的长度;

 e.   第一个$后的CRLF后的内容(”set”)为第一个参数的内容;

 f.    以此内推,第二个参数的长度为5,值为mykey

 g.   第三个参数的长度为7,值为myvalue

 h.   不要忘了,最后一个参数后还要紧跟一个CRLF作为该数据结束的标志

另外,对于回复,redis也有一些规定,我们可以参照附属资料《redis大全》通信协议.回复、状态回复等章节的相关说明。

4 Redis事务

Redis通过MULTIDISCARDEXECWATCH四个命令来实现事务功能。

4.1Hello world

一个简单地使用事务的例子如下所示:

redis> MULTI

OK

redis> SET book-name "Mastering C++ in 21 days"

QUEUED

redis> GET book-name

QUEUED

redis> SADD tag "C++" "Programming" "Mastering Series"

QUEUED

redis> SMEMBERS tag

QUEUED

redis> EXEC

1) OK

2) "Mastering C++ in 21 days"

3) (integer) 3

4) 1) "Mastering Series"

   2) "C++"

   3) "Programming"

4.2 开始事务MULTI

MULTI命令用于开始一个事务。该命令的效果,即是将服务端上记录的客户端状态改为事务状态。

该命令返回“OK”

4.3 添加要执行的命令

Redis服务端上的客户端状态修改为事务状态后,一个明显的区别就是:所有发送给服务端执行的命令(除了用于事务处理的四个命令,MULTIDISCARDEXECWATCH),不是马上执行以及返回执行结果;取而代之的是,这些命令会被redis放到一个命令队列中,并返回客户端“QUEUED” 

 Redis上的事务命令队列是一个数组,每个数组项包含三个属性:

 1. 要执行的命令(cmd);

 2. 命令的参数(argv);

 3. 参数的个数(argc)。

4.4 执行事务EXEC

 如果客户端正处于事务状态,当客户端再发送EXEC命令到服务端时,Redis服务端的执行过程如下:

 1. 服务端会从客户端所保存事务命令队列中,以FIFO的顺序取出命令执行,并且把每条命令执行的结果保存到一个FIFO的回复队列中;

 2.当事务命令队列中的所有命令都被执行完了以后,EXEC命令会将回复队列中的结果全部返回给客户端。

4.5 取消事务

 DISCARD 命令用于取消一个事务,它清空客户端的整个事务队列,然后将客户端从事务状态调整回非事务状态,最后返回字符串OK给客户端,说明事务已被取消。

 之前已经提到过,在客户端输入了EXEC命令开启一个事务后,和事务相关的MULTIEXECDISCARDWATCH四个命令不会被放到客户端的事务命令队列中。

 那么如果在客户端已经开启一个事务的情况下,客户端输入上述四个命令,redis会怎么处理呢?我们下边来看看。

MULTI

 Redis 的事务是不可嵌套的, 当客户端已经处于事务状态, 而客户端又再向服务器发送 MULTI 时, 服务器只是简单地向客户端发送一个错误, 然后继续等待其他命令的入队。 MULTI 命令的发送不会造成整个事务失败, 也不会修改事务队列中已有的数据。

WATCH

只能在客户端进入事务状态之前执行, 在事务状态下发送 WATCH 命令会引发一个错误, 但它不会造成整个事务失败, 也不会修改事务队列中已有的数据(和前面处理 MULTI 的情况一样)。

当然,最后,EXEC会执行事务队列中的全部命令;DISCARD会清空事务队列中的命令,并修改客户端状态。

4.6WATCH的事务

WATCH命令用于在事务开始之前监视任意数量的键:当调用EXEC命令执行事务时,如果任意一个被监视的键已经被其他客户端修改了,那么整个事务不再执行,直接返回失败。

以下示例展示了一个执行失败的事务例子:

redis> WATCH name

OK

redis> MULTI

OK

redis> SET name peter

QUEUED

redis> EXEC

(nil)

        注意最后EXEC命令返回的不再是SET命令执行的结果。

4.7 WATCH命令的实现和触发

在每个代表数据库的redis.h/redisDb 结构类型中,都保存了一个 watched_keys 字典,字典的键是这个数据库被监视的键,而字典的值则是一个链表,链表中保存了所有监视这个键的客户端。

比如说,以下字典就展示了一个 watched_keys 字典的例子:




 
 
<!--[if !supportLineBreakNewLine]-->
<!--[endif]-->

其中,键 key1 正在被 client2  client5  client1 三个客户端监视, 其他一些键也分别被其他别的客户端监视着。

WATCH 命令的作用, 就是将当前客户端和要监视的键在 watched_keys 中进行关联。

举个例子,如果当前客户端为client10086,那么当客户端执行WATCH key1 key2时,前面展示的watched_keys 将被修改成这个样子:




 
  

通过watched_keys字典,如果程序想检查某个键是否被监视,那么它只要检查字典中是否存在这个键即可;如果程序要获取监视某个键的所有客户端,那么只要取出键的值(一个链表),然后对链表进行遍历即可。

 

在任何对数据库键空间(keyspace)进行修改的命令成功执行之后(比如FLUSHDBSETDELLPUSHSADDZREM,诸如此类),multi.c/touchWatchedKey函数都会被调用——它检查数据库的watched_keys字典,看是否有客户端在监视已经被命令修改的键,如果有的话,程序将所有监视这个/这些被修改键的客户端的REDIS_DIRTY_CAS选项打开。

当客户端发送EXEC命令、触发事务执行时,服务器会对客户端的状态进行检查:

 如果客户端的REDIS_DIRTY_CAS选项已经被打开,那么说明被客户端监视的键至少有一个已经被修改了,事务的安全性已经被破坏。服务器会放弃执行这个事务,直接向客户端返回空回复,表示事务执行失败。

 如果REDIS_DIRTY_CAS选项没有被打开,那么说明所有监视键都安全,服务器正式执行事务。

4.8 事务状态和非事务状态下执行命令

无论在事务状态下, 还是在非事务状态下, Redis 命令都由同一个函数执行, 所以它们共享很多服务器的一般设置, 比如 AOF 的配置、RDB 的配置,以及内存限制,等等。

不过事务中的命令和普通命令在执行上还是有一点区别的,其中最重要的两点是:

     1.非事务状态下的命令以单个命令为单位执行,前一个命令和后一个命令的客户端不一定是同一个;而事务状态则是以一个事务为单位,执行事务队列中的所有命令:除非当前事务执行完毕,否则服务器不会中断事务,也不会执行其他客户端的其他命令。

      2.在非事务状态下,执行命令所得的结果会立即被返回给客户端;而事务则是将所有命令的结果集合到回复队列,再作为 EXEC 命令的结果返回给客户端。

4.9 更多的Redis事务说明

官方说明

根据Redis官方文档,redis事务有以下两个重要的保证:

 1. 所有的事务命令队列中的命令会被顺序地执行;并且,在一个客户端的事务正在执行时,其他的客户端的请求都将不会执行。

 2. 不管事务命令队列中是否有命令,Redis都是具有原子性的。也就是说,EXEC命令都将执行队列中的所有命令。基于此,发生以下错误时,redis的处理如下:

     a.如果在MULTI命令执行之前,客户端的连接在断开了,则什么也不会执行;

      b. 如果EXEC命令已经调用,而客户端的连接断开了,则所有的命令都将执行;

       c.  如果事务命令队列中已经存在了一些待执行的命令,此时发生一些停机、断电等操作,则redis会按情况处理:

           内存模式:如果Redis没有采取任何持久化机制,那么重启之后的数据库总是空白的,所以数据总是一致的。

            RDB模式:在执行事务时,Redis不会中断事务去执行保存RDB的工作,只有在事务执行之后,保存RDB的工作才有可能开始。所以当RDB模式下的Redis服务器进程在事务中途被杀死时,事务内执行的命令,不管成功了多少,都不会被保存到RDB文件里。恢复数据库需要使用现有的RDB文件,而这个RDB文件的数据保存的是最近一次的数据库快照(snapshot),所以它的数据可能不是最新的,但只要RDB文件本身没有因为其他问题而出错,那么还原后的数据库就是一致的。

             AOF模式:因为保存AOF文件的工作在后台线程进行,所以即使是在事务执行的中途,保存AOF文件的工作也可以继续进行,因此,根据事务语句是否被写入并保存到AOF文件,有以下两种情况发生:

如果事务语句未写入到AOF文件,或AOF未被SYNC调用保存到磁盘,那么当进程被杀死之后,Redis可以根据最近一次成功保存到磁盘的AOF文件来还原数据库,只要AOF文件本身没有因为其他问题而出错,那么还原后的数据库总是一致的,但其中的数据不一定是最新的。

如果事务的部分语句被写入到AOF文件,并且AOF文件被成功保存,那么不完整的事务执行信息就会遗留在AOF文件里,当重启Redis时,程序会检测到AOF文件并不完整,Redis会退出,并报告错误。需要使用redis-check-aof工具将部分成功的事务命令移除之后,才能再次启动服务器。还原之后的数据总是一致的,而且数据也是最新的(直到事务执行之前为止)。 

处理错误

在操作事务时,我们经常会发生以下两种错误:

1.在执行了MULTI命令,再往事务命令队列中添加命令时,可能会出现一些错误。例如添加的命令的参数个数不对错误,甚至内存溢出等系统级错误;

2.在执行EXEC命令时,出现的一些错误。例如我们使用对Sting类型值的命令,但实际上相应key上存储的值是List

对于第一种错误,客户端会明显地收到非“QUEUED”回复;此时,客户端最应该撤销该事务;另外,在redis2.6.5以后,如果客户端不做任何处理,服务端也记住了这种错误,并且在执行EXEC命令时,返回错误信息,并且不会执行事务命令队列中的命令。

对于第二种错误,剩下的其他命令会继续执行;并且在EXEC命令的返回值中,我们可以看到响应的错误信息。 

持久性(Durability)补充

因为事务不过是用队列包裹起了一组Redis命令,并没有提供任何额外的持久性功能,所以事务的持久性由Redis所使用的持久化模式决定:

在单纯的内存模式下,事务肯定是不持久的。

RDB模式下,服务器可能在事务执行之后、RDB文件更新之前的这段时间失败,所以RDB模式下的Redis事务也是不持久的。

AOF总是SYNC”模式下,事务的每条命令在执行成功之后,都会立即调用fsyncfdatasync将事务数据写入到AOF文件。但是,这种保存是由后台线程进行的,主线程不会阻塞直到保存成功,所以从命令执行成功到数据保存到硬盘之间,还是有一段非常小的间隔,所以这种模式下的事务也是不持久的。

其他AOF模式也和总是SYNC”模式类似,所以它们都是不持久的。

5  订阅与发布

 Redis通过PUBLISHSUBSCRIBE等命令实现了订阅与发布模式,这个功能提供两种信息机制,分别是订阅/发布到频道和订阅/发布到模式。

 频道,指具体要发布或订阅的消息的标示,例如news .sports

 模式,指可以匹配多个频道的表达式,例如news.*匹配频道news.sportsnews.education等。

 Redis目前只支持在线订阅,不支持durable式订阅。

5.1Hello world

先看一个简单订阅者的例子:

redis> psubscribe news.* tweet.*

Reading messages... (press Ctrl-C to quit)

1) "psubscribe" # 返回值的类型:显示订阅成功

2) "news.*" # 订阅的模式

3) (integer) 1 # 目前已订阅的模式的数量

 

1) "psubscribe"

2) "tweet.*"

3) (integer) 2

 

1) "pmessage" # 返回值的类型:信息

2) "news.*" # 信息匹配的模式

3) "news.it" # 信息本身的目标频道

4) "Google buy Motorola" # 信息的内容

 

当然,在用户A订阅后,还要等用户B发送消息。我们再看一个发布者的例子:

redis> publish msg "good morning"

(integer) 1

发布消息后,将返回订阅者的数量。

5.2Redis的命令

 Redis的订阅发布功能很简单,我们这里简单地罗列一下各个命令的用法和说明,方便大家查阅。

PSUBSCRIBE pattern [pattern ...]

 订阅一个或多个模式或频道。多个频道或模式之间用空格隔开。

 

PUBLISH channel message

 将信息message发送到指定的一个频道channel

 

PUBSUB <subcommand> [argument [argument ...]]

 PUBSUB 是一个查看订阅与发布系统状态的内省命令,它由数个不同格式的子命令组成。

 

PUBSUB CHANNELS [pattern]

 列出当前的活跃频道。活跃频道指的是那些至少有一个订阅者的频道,订阅模式的客户端不计算在内。

 pattern 参数是可选的:

 如果不给出pattern 参数,那么列出订阅与发布系统中的所有活跃频道。

 如果给出pattern 参数,那么只列出和给定模式pattern 相匹配的那些活跃频道。

 

PUBSUB NUMSUB [channel-1 ... channel-N]

 返回给定频道的订阅者数量,订阅模式的客户端不计算在内。

 

PUBSUB NUMPAT

 返回订阅模式的数量。注意,这个命令返回的不是订阅模式的客户端的数量,而是客户端订阅的所有模式的数量总和。

 

PUNSUBSCRIBE [pattern [pattern ...]]

 指示客户端退订所有给定模式。如果没有模式被指定,也即是,一个无参数的PUNSUBSCRIBE调用被执行,那么客户端使用PSUBSCRIBE

 命令订阅的所有模式都会被退订。在这种情况下,命令会返回一个信息,告知客户端所有被退订的模式。

 

SUBSCRIBE channel [channel ...]

 订阅给定的一个或多个频道的信息。

 

UNSUBSCRIBE [channel [channel ...]]

 指示客户端退订给定的频道。如果没有频道被指定,那么客户端使用SUBSCRIBE命令订阅的所有频道都会被退订。在这种情况下,命令会返回一个信息,告知客户端所有被退订的频道。

5.3订阅与发布使用补充说明

在实际使用redis-cli测试订阅与发布过程中,发现有些问题:

作为订阅者者(subscribe)的窗口,在订阅了模式或频道后,面临一个尴尬的问题:不能退出(或者说,没找到)订阅收听模式。如下图所示:

 

也就是说,这个时候,我想取消订阅(unsubscribe)时,没有了输入命令的地方。

 jedisredisjava客户端)中,找到了有关订阅与发布的测试代码redis.clients.jedis.tests.commands.PublishSubscribeCommandsTest。其逻辑还是比较简单,终于松了一口气,看到了怎么实际玩这个功能。 

6 Redis的内存结构(database

 Redis数据库是真正存储数据的地方。当然,数据库本身也是存储在内存中的。

 Databased的数据结构伪代码如下:

typedef struct redisDb {

    // 保存着数据库以整数表示的号码

    int id; 

    // 保存着数据库中的所有键值对数据

    // 这个属性也被称为键空间(key space

    dict *dict; 

    // 保存着键的过期信息

    dict *expires; 

    // 实现列表阻塞原语,如 BLPOP

    // 在列表类型一章有详细的讨论

    dict *blocking_keys;

    dict *ready_keys; 

    // 用于实现 WATCH 命令

    // 在事务章节有详细的讨论

    dict *watched_keys;

} redisDb;

一个数据库,在内存中的数据结构如下图所示:

 


 
 

Database的内容要点包括:

 1.数据库主要由 dict  expires 两个字典构成,其中 dict 保存键值对,而 expires 则保存键的过期时间。

 2.数据库的键总是一个字符串对象,而值可以是任意一种 Redis 数据类型,包括字符串、哈希、集合、列表和有序集。

 3.expires 的某个键和 dict 的某个键共同指向同一个字符串对象,而 expires 键的值则是该键以毫秒计算的 UNIX 过期时间戳。

 4.Redis 使用惰性删除和定期删除两种策略来删除过期的键。

   a.更新后的 RDB 文件和重写后的 AOF 文件都不会保留已经过期的键。

   b.当一个过期键被删除之后,程序会追加一条新的 DEL 命令到现有 AOF 文件末尾。

   c.当主节点删除一个过期键之后,它会显式地发送一条 DEL 命令到所有附属节点。

d.附属节点即使发现过期键,也不会自作主张地删除它,而是等待主节点发来 DEL 命令,这样可以保证主节点和附属节点的数据总是一致的。

数据库的 dict 字典和 expires 字典的扩展策略和普通字典一样。它们的收缩策略是:当节点的填充百分比不足 10% 时,将可用节点数量减少至大于等于当前已用节点数量。

集群简介

Redis集群可以实现在多个redis节点之间进行数据共享。

对于多个键的redis命令,不支持在集群环境里运行。(稍后说明为什么多个键的不行)

Redis集群带来的好处:

将数据自动切分到了多个节点上。

     当集群中的一部分节点无法进行通讯时,仍然可以继续处理命令请求。

7.1 集群部署和验证

Redis集群部署大致分为以下步骤:

1.为每个redis节点修改生成redis.conf配置文件:

port 7000                     //节点端口

cluster-enabled yes            //集群开关

cluster-config-file nodes.conf    //指定节点id的存储文件

cluster-node-timeout 5000      //集群中节点超时时间

appendonly yes               //aof文件读写模式

其中,

nodes.conf会在创建集群的过程中自动生成,并存储集群环境中各个nodeid;该idredis实例的唯一标示,在实例的整个生命周期内有效;

cluster-node-timeout会在后边一节提到其功能,主要用于节点间发现自己或别人有没有已经不在了。

 2. 通过命令redis-server ./redis.conf启动所有的节点实例;

 3. 通过一个rubyredis-trib工具脚本创建集群。

      a. 服务器上需要ruby的运行环境;

      b. 工具的使用命令为:./redis-trib.rb create --replicas 1 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005。命令解释为:将最后的6个服务器上的redis实例,创建成每个master1个从节点的集群环境。

      c. redis-trib会尽量地分配主从节点在不同的ip机器上。 

 4. 正常情况下,此时会得到一个redis-trib工具为我们分配好的集群环境方案,如果我们认同,则输入“yes”

 5. 最后,出现如下日志信息时,表示集群环境搭建成功:

>>> Check for open slots...

>>> Check slots coverage...

[OK] All 16384 slots covered.

 

集群环境的验证:

使用redis自带的客户端工具redis-cli,依次输入以下命令:

$ redis-cli -c -p 7000

redis 127.0.0.1:7000> set foo bar

-> Redirected to slot [12182] located at 127.0.0.1:7002

OK

redis 127.0.0.1:7002> set hello world

-> Redirected to slot [866] located at 127.0.0.1:7000

OK

redis 127.0.0.1:7000> get foo

-> Redirected to 

redis-cli -c -p 7000命令表示:使用集群环境,且连接7000端口的redis实例。

通过执行每个命令的提示信息,我们可以看到集群环境已经生效了。

7.2Redis集群内部原理

数据共享

新的redis版本,采用数据分片(sharding)而非一致性哈希来实现:一个Redis集群包含16384个哈希槽(hash slot),数据库中的每个键都属于这16384个哈希槽的其中一个,集群使用公式CRC16(key)%16384来计算键key属于哪个槽,其中CRC16(key)语句用于计算键keyCRC16校验和。

集群中的每个节点负责处理一部分哈希槽。举个例子,一个集群可以有三个哈希槽,其中:

     a.节点A负责处理0号至5500号哈希槽。

     b.节点B负责处理5501号至11000号哈希槽。

     c.节点C负责处理11001号至16384号哈希槽。

     这种将哈希槽分布到不同节点的做法使得用户可以很容易地向集群中添加或者删除节点。比如说:

     d.如果用户将新节点D添加到集群中,那么集群只需要将节点ABC中的某些槽移动到节点D就可以了。

     e.与此类似,如果用户要从集群中移除节点A,那么集群只需要将节点A中的所有哈希槽移动到节点B和节点C,然后再移除空白(不包含任何哈希槽)的节点A就可以了。

因为将一个哈希槽从一个节点移动到另一个节点不会造成节点阻塞,所以无论是添加新节点还是移除已存在节点,又或者改变某个节点包含的哈希槽数量,都不会造成集群下线。 

主从复制

为了使得集群在一部分节点下线或者无法与集群的大多数节点进行通讯的情况下,仍然可以正常运作,Redis集群对节点使用了主从复制功能:集群中的每个节点都有1个至N个复制品(replica),其中一个复制品为主节点(master),而其余的N-1个复制品为从节点(slave)。

     在之前列举的节点ABC的例子中,如果节点B下线了,那么集群将无法正常运行,因为集群找不到节点来处理5501号至11000号的哈希槽。

     另一方面,假如在创建集群的时候(或者至少在节点B下线之前),我们为主节点B添加了从节点B1,那么当主节点B下线的时候,集群就会将B1设置为新的主节点,并让它代替下线的主节点B,继续处理5501号至11000号的哈希槽,这样集群就不会因为主节点B的下线而无法正常运作了。

不过如果节点BB1都下线的话,Redis集群还是会停止运作。 

一致性保证

Redis集群不保证数据的强一致性(strong consistency):在特定条件下,Redis集群可能会丢失已经被执行过的写命令。

使用异步复制(asynchronous replication)是Redis集群可能会丢失写命令的其中一个原因。考虑以下这个写命令的例子:

     a.客户端向主节点B发送一条写命令。

     b.主节点B执行写命令,并向客户端返回命令回复。

     c.主节点B将刚刚执行的写命令复制给它的从节点B1B2B3

     Note:如果真的有必要的话,Redis集群可能会在将来提供同步地(synchronou)执行写命令的方法。

 

     Redis集群另外一种可能会丢失命令的情况是:集群出现网络分裂(network partition),并且一个客户端与至少包括一个主节点在内的少数(minority)实例被孤立。

     举个例子,假设集群包含ABCA1B1C1六个节点,其中ABC为主节点,而A1B1C1分别为三个主节点的从节点,另外还有一个客户端Z1

     假设集群中发生网络分裂,那么集群可能会分裂为两方,大多数(majority)的一方包含节点ACA1B1C1,而少数(minority)的一方则包含节点B和客户端Z1

     在网络分裂期间,主节点B仍然会接受Z1发送的写命令:

     a.如果网络分裂出现的时间很短,那么集群会继续正常运行;

     b.但是,如果网络分裂出现的时间足够长,使得大多数一方将从节点B1设置为新的主节点,并使用B1来代替原来的主节点B,那么Z1发送给主节点B的写命令将丢失。

     注意,在网络分裂出现期间,客户端Z1可以向主节点B发送写命令的最大时间是有限制的,这一时间限制称为节点超时时间(nodetimeout),是Redis集群的一个重要的配置选项:

     c.对于大多数一方来说,如果一个主节点未能在节点超时时间所设定的时限内重新联系上集群,那么集群会将这个主节点视为下线,并使用从节点来代替这个主节点继续工作。 

     d.对于少数一方,如果一个主节点未能在节点超时时间所设定的时限内重新联系上集群,那么它将停止处理写命令,并向客户端报告错误。

 

 

猜你喜欢

转载自coderbase64.iteye.com/blog/2065746