1、缓存在系统中用来作什么?node
1. 少许数据存储,高速读写访问。经过数据所有in-momery 的方式来保证高速访问,同时提供数据落地的功能,实际这正是Redis最主要的适用场景。面试
2. 海量数据存储,分布式系统支持,数据一致性保证,方便的集群节点添加/删除。Redis3.0之后开始支持集群,实现了半自动化的数据分片,不过须要smart-client的支持。redis
2、从不一样的角度来详细介绍redis数据库
网络模型:Redis使用单线程的IO复用模型,本身封装了一个简单的AeEvent事件处理框架,主要实现了epoll、kqueue和select,对于单纯只有IO操做来讲,单线程能够将速度优点发挥到最大,可是Redis也提供了一些简单的计算功能,好比排序、聚合等,对于这些操做,单线程模型实际会严重影响总体吞吐量,CPU计算过程当中,整个IO调度都是被阻塞住的。编程
内存管理:Redis使用现场申请内存的方式来存储数据,而且不多使用free-list等方式来优化内存分配,会在必定程度上存在内存碎片,Redis跟据存储命令参数,会把带过时时间的数据单独存放在一块儿,并把它们称为临时数据,非临时数据是永远不会被剔除的,即使物理内存不够,致使swap也不会剔除任何非临时数据(但会尝试剔除部分临时数据),这点上Redis更适合做为存储而不是cache。缓存
数据一致性问题:在一致性问题上,我的感受redis没有memcached实现的好,Memcached提供了cas命令,能够保证多个并发访问操做同一份数据的一致性问题。 Redis没有提供cas 命令,并不能保证这点,不过Redis提供了事务的功能,能够保证一串命令的原子性,中间不会被任何操做打断。服务器
支持的KEY类型:Redis除key/value以外,还支持list,set,sorted set,hash等众多数据结构,提供了KEYS进行枚举操做,但不能在线上使用,若是须要枚举线上数据,Redis提供了工具能够直接扫描其dump文件,枚举出全部数据,Redis还同时提供了持久化和复制等功能。网络
客户端支持:redis官方提供了丰富的客户端支持,包括了绝大多数编程语言的客户端,好比我这次测试就选择了官方推荐了Java客户端Jedis.里面提供了丰富的接口、方法使得开发人员无需关系内部的数据分片、读取数据的路由等,只需简单的调用便可,很是方便。数据结构
数据复制:从2.8开始,Slave会周期性(每秒一次)发起一个Ack确认复制流(replication stream)被处理进度, Redis复制工做原理详细过程以下:多线程
1. 若是设置了一个Slave,不管是第一次链接仍是重连到Master,它都会发出一个SYNC命令;
2. 当Master收到SYNC命令以后,会作两件事:
a) Master执行BGSAVE:后台写数据到磁盘(rdb快照);
b) Master同时将新收到的写入和修改数据集的命令存入缓冲区(非查询类);
3. 当Master在后台把数据保存到快照文件完成以后,Master会把这个快照文件传送给Slave,而Slave则把内存清空后,加载该文件到内存中;
4. 而Master也会把此前收集到缓冲区中的命令,经过Reids命令协议形式转发给Slave,Slave执行这些命令,实现和Master的同步;
5. Master/Slave此后会不断经过异步方式进行命令的同步,达到最终数据的同步一致;
6. 须要注意的是Master和Slave之间一旦发生重连都会引起全量同步操做。但在2.8以后,也多是部分同步操做。
2.8开始,当Master和Slave之间的链接断开以后,他们之间能够采用持续复制处理方式代替采用全量同步。
Master端为复制流维护一个内存缓冲区(in-memory backlog),记录最近发送的复制流命令;同时,Master和Slave之间都维护一个复制偏移量(replication offset)和当前Master服务器ID(Masterrun id)。
当网络断开,Slave尝试重连时:
a. 若是MasterID相同(即还是断网前的Master服务器),而且从断开时到当前时刻的历史命令依然在Master的内存缓冲区中存在,则Master会将缺失的这段时间的全部命令发送给Slave执行,而后复制工做就能够继续执行了;
b. 不然,依然须要全量复制操做。
读写分离:redis支持读写分离,并且使用简单,只需在配置文件中把redis读服务器和写服务器进行配置,多个服务器使用逗号分开以下:
水平动态扩展:历时三年之久,终于等来了期待已由的Redis 3.0。新版本主要是实现了Cluster的功能,增删集群节点后会自动的进行数据迁移。实现 Redis 集群在线重配置的核心就是将槽从一个节点移动到另外一个节点的能力。由于一个哈希槽实际上就是一些键的集合, 因此 Redis 集群在重哈希(rehash)时真正要作的,就是将一些键从一个节点移动到另外一个节点。
数据淘汰策略:redis 内存数据集大小上升到必定大小的时候,就会施行数据淘汰策略。redis 提供 6种数据淘汰策略:
3、集群(即分布式)
下面详细介绍一下redis的集群功能,从3.0之后的版本开始支持集群功能,也就是正真意义上实现了分布式。
Redis 集群是一个分布式(distributed)、容错(fault-tolerant)的 Redis 实现, 集群可使用的功能是普通单机 Redis 所能使用的功能的一个子集(subset)。
Redis 集群中不存在中心(central)节点或者代理(proxy)节点, 集群的其中一个主要设计目标是达到线性可扩展性(linear scalability)。
Redis 集群为了保证一致性(consistency)而牺牲了一部分容错性: 系统会在保证对网络断线(netsplit)和节点失效(node failure)具备有限(limited)抵抗力的前提下,尽量地保持数据的一致性。
集群特性:
(1)全部的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽。
(2)节点的fail是经过集群中超过半数的节点检测失效时才生效。
(3)客户端与redis节点直连,不须要中间proxy层.客户端不须要链接集群全部节点,链接集群中任何一个可用节点便可。
(4)redis-cluster把全部的物理节点映射到[0-16383]slot上,cluster 负责维护node<->slot<->value
Redis 集群实现的功能子集:
Redis集群实现了单机 Redis 中, 全部处理单个数据库键的命令。针对多个数据库键的复杂计算操做, 好比集合的并集操做、合集操做没有被实现,那些理论上须要使用多个节点的多个数据库键才能完成的命令也没有被实现。在未来, 用户也许能够经过 MIGRATE COPY 命令,在集群的计算节点(computation node)中执行针对多个数据库键的只读操做, 但集群自己不会去实现那些须要将多个数据库键在多个节点中移来移去的复杂多键命令。
Redis 集群不像单机Redis 那样支持多数据库功能, 集群只使用默认的 0 号数据库, 而且不能使用 SELECT 命令。
Redis 集群协议中的客户端和服务器:
Redis 集群中的节点有如下责任:
1. 持有键值对数据。
2. 记录集群的状态,包括键到正确节点的映射(mappingkeys to right nodes)。
3. 自动发现其余节点,识别工做不正常的节点,并在有须要时,在从节点中选举出新的主节点。
为了执行以上列出的任务, 集群中的每一个节点都与其余节点创建起了“集群链接(cluster bus)”, 该链接是一个 TCP 链接, 使用二进制协议进行通信。
节点之间使用Gossip 协议 来进行如下工做:
1. 传播(propagate)关于集群的信息,以此来发现新的节点。
2. 向其余节点发送 PING 数据包,以此来检查目标节点是否正常运做。
3. 在特定事件发生时,发送集群信息。
4. 除此以外, 集群链接还用于在集群中发布或订阅信息。
由于集群节点不能代理(proxy)命令请求, 因此客户端应该在节点返回 -MOVED 或者 -ASK 转向(redirection)错误时,自行将命令请求转发至其余节点。由于客户端能够自由地向集群中的任何一个节点发送命令请求, 并能够在有须要时, 根据转向错误所提供的信息, 将命令转发至正确的节点,因此在理论上来讲, 客户端是无须保存集群状态信息的。不过, 若是客户端能够将键和节点之间的映射信息保存起来, 能够有效地减小可能出现的转向次数, 籍此提高命令执行的效率。
键分布模型
Redis 集群的键空间被分割为 16384 个槽(slot), 集群的最大节点数量也是 16384 个。
推荐的最大节点数量为 1000 个左右。每一个主节点都负责处理 16384 个哈希槽的其中一部分。
当咱们说一个集群处于“稳定”(stable)状态时, 指的是集群没有在执行重配(reconfiguration)操做,每一个哈希槽都只由一个节点进行处理。重配置指的是将某个/某些槽从一个节点移动到另外一个节点。一个主节点能够有任意多个从节点,这些从节点用于在主节点发生网络断线或者节点失效时, 对主节点进行替换。
集群节点属性:
每一个节点在集群中都有一个独一无二的 ID , 该 ID 是一个十六进制表示的 160 位随机数, 在节点第一次启动时由 /dev/urandom 生成。
节点会将它的 ID 保存到配置文件, 只要这个配置文件不被删除,节点就会一直沿用这个 ID 。节点 ID 用于标识集群中的每一个节点。一个节点能够改变它的 IP 和端口号, 而不改变节点 ID 。集群能够自动识别出 IP/端口号的变化, 并将这一信息经过 Gossip 协议广播给其余节点知道。
如下是每一个节点都有的关联信息, 而且节点会将这些信息发送给其余节点:
1. 节点所使用的 IP 地址和 TCP 端口号。
2. 节点的标志(flags)。
3. 节点负责处理的哈希槽。
4. 节点最近一次使用集群链接发送 PING 数据包(packet)的时间。
5. 节点最近一次在回复中接收到 PONG 数据包的时间。
6. 集群将该节点标记为下线的时间。
7. 该节点的从节点数量。
8. 若是该节点是从节点的话,那么它会记录主节点的节点 ID 。若是这是一个主节点的话,那么主节点 ID 这一栏的值为 0000000 。
以上信息的其中一部分能够经过向集群中的任意节点(主节点或者从节点均可以)发送 CLUSTER NODES 命令来得到。
节点握手:
节点老是应答(accept)来自集群链接端口的链接请求,并对接收到的 PING 数据包进行回复, 即便这个 PING 数据包来自不可信的节点。然而,除了 PING 以外, 节点会拒绝其余全部并不是来自集群节点的数据包。要让一个节点认可另外一个节点同属于一个集群,只有如下两种方法:
1. 一个节点能够经过向另外一个节点发送 MEET 信息,来强制让接收信息的节点认可发送信息的节点为集群中的一份子。 一个节点仅在管理员显式地向它发送CLUSTER MEET ipport 命令时, 才会向另外一个节点发送 MEET 信息。
2. 若是一个可信节点向另外一个节点传播第三者节点的信息, 那么接收信息的那个节点也会将第三者节点识别为集群中的一份子。也便是说, 若是 A 认识 B , B 认识 C , 而且 B 向 A 传播关于 C 的信息, 那么 A 也会将 C 识别为集群中的一份子, 并尝试链接 C 。
这意味着若是咱们将一个/一些新节点添加到一个集群中, 那么这个/这些新节点最终会和集群中已有的其余全部节点链接起来。
这说明只要管理员使用 CLUSTER MEET 命令显式地指定了可信关系,集群就能够自动发现其余节点。这种节点识别机制经过防止不一样的 Redis 集群由于 IP 地址变动或者其余网络事件的发生而产生意料以外的联合(mix), 从而使得集群更具健壮性。当节点的网络链接断开时,它会主动链接其余已知的节点。
MOVED 转向:
一个 Redis 客户端能够向集群中的任意节点(包括从节点)发送命令请求。节点会对命令请求进行分析, 若是该命令是集群能够执行的命令, 那么节点会查找这个命令所要处理的键所在的槽。若是要查找的哈希槽正好就由接收到命令的节点负责处理,那么节点就直接执行这个命令。另外一方面, 若是所查找的槽不是由该节点处理的话, 节点将查看自身内部所保存的哈希槽到节点 ID 的映射记录,并向客户端回复一个 MOVED 错误。
即便客户端在从新发送 GET 命令以前, 等待了很是久的时间,以致于集群又再次更改了配置, 使得节点 127.0.0.1:6381 已经再也不处理槽 3999 , 那么当客户端向节点 127.0.0.1:6381 发送 GET 命令的时候, 节点将再次向客户端返回 MOVED 错误, 指示如今负责处理槽 3999 的节点。
虽然咱们用 ID 来标识集群中的节点, 可是为了让客户端的转向操做尽量地简单,,节点在 MOVED 错误中直接返回目标节点的 IP 和端口号,而不是目标节点的 ID 。但一个客户端应该记录(memorize)下“槽 3999 由节点 127.0.0.1:6381 负责处理“这一信息, 这样当再次有命令须要对槽 3999 执行时, 客户端就能够加快寻找正确节点的速度。
注意, 当集群处于稳定状态时, 全部客户端最终都会保存有一个哈希槽至节点的映射记录(map of hash slots to nodes), 使得集群很是高效: 客户端能够直接向正确的节点发送命令请求, 无须转向、代理或者其余任何可能发生单点故障(single point failure)的实体(entiy)。
除了 MOVED转向错误以外, 一个客户端还应该能够处理稍后介绍的 ASK 转向错误。
集群在线重配置:
Redis 集群支持在集群运行的过程当中添加或者移除节点。实际上, 节点的添加操做和节点的删除操做能够抽象成同一个操做,那就是, 将哈希槽从一个节点移动到另外一个节点:添加一个新节点到集群, 等于将其余已存在节点的槽移动到一个空白的新节点里面。从集群中移除一个节点, 等于将被移除节点的全部槽移动到集群的其余节点上面去。
所以, 实现Redis 集群在线重配置的核心就是将槽从一个节点移动到另外一个节点的能力。 由于一个哈希槽实际上就是一些键的集合, 因此 Redis 集群在重哈希(rehash)时真正要作的, 就是将一些键从一个节点移动到另外一个节点。
要理解Redis 集群如何将槽从一个节点移动到另外一个节点, 咱们须要对 CLUSTER 命令的各个子命令进行介绍,这些命理负责管理集群节点的槽转换表(slots translation table)。
如下是CLUSTER 命令可用的子命令:
最开头的两条命令ADDSLOTS 和 DELSLOTS 分别用于向节点指派(assign)或者移除节点,当槽被指派或者移除以后, 节点会将这一信息经过 Gossip 协议传播到整个集群。 ADDSLOTS 命令一般在新建立集群时, 做为一种快速地将各个槽指派给各个节点的手段来使用。
CLUSTERSETSLOT slot NODE node 子命令能够将指定的槽 slot 指派给节点node 。
至于CLUSTER SETSLOT slot MIGRATING node 命令和 CLUSTER SETSLOTslot IMPORTING node 命令, 前者用于将给定节点 node 中的槽 slot 迁移出节点, 然后者用于将给定槽 slot导入到节点 node :
当一个槽被设置为MIGRATING 状态时, 原来持有这个槽的节点仍然会继续接受关于这个槽的命令请求, 但只有命令所处理的键仍然存在于节点时, 节点才会处理这个命令请求。
若是命令所使用的键不存在与该节点, 那么节点将向客户端返回一个 -ASK 转向(redirection)错误, 告知客户端, 要将命令请求发送到槽的迁移目标节点。
当一个槽被设置为IMPORTING 状态时, 节点仅在接收到 ASKING 命令以后, 才会接受关于这个槽的命令请求。
若是客户端没有向节点发送 ASKING 命令, 那么节点会使用 -MOVED 转向错误将命令请求转向至真正负责处理这个槽的节点。
上面关于MIGRATING 和 IMPORTING 的说明有些难懂, 让咱们用一个实际的实例来讲明一下。
假设如今, 咱们有 A 和 B 两个节点, 而且咱们想将槽8 从节点 A 移动到节点 B , 因而咱们:
向节点 B 发送命令 CLUSTER SETSLOT 8 IMPORTING A
向节点 A 发送命令 CLUSTER SETSLOT 8 MIGRATING B
每当客户端向其余节点发送关于哈希槽 8 的命令请求时, 这些节点都会向客户端返回指向节点 A 的转向信息:
若是命令要处理的键已经存在于槽 8 里面, 那么这个命令将由节点 A 处理。
若是命令要处理的键未存在于槽 8 里面(好比说,要向槽添加一个新的键), 那么这个命令由节点 B 处理。
这种机制将使得节点 A 再也不建立关于槽 8 的任何新键。
与此同时, 一个特殊的客户端 redis-trib 以及 Redis 集群配置程序(configuration utility)会将节点 A 中槽 8 里面的键移动到节点 B 。
键的移动操做由如下两个命令执行:
CLUSTERGETKEYSINSLOT slot count
上面的命令会让节点返回 count 个 slot 槽中的键, 对于命令所返回的每一个键, redis-trib 都会向节点 A 发送一条 MIGRATE 命令, 该命令会将所指定的键原子地(atomic)从节点 A 移动到节点 B (在移动键期间,两个节点都会处于阻塞状态,以避免出现竞争条件)。
如下为MIGRATE 命令的运做原理:
MIGRATEtarget_host target_port key target_database id timeout
执行MIGRATE 命令的节点会链接到 target 节点, 并将序列化后的 key 数据发送给 target , 一旦 target 返回 OK , 节点就将本身的 key 从数据库中删除。
从一个外部客户端的视角来看, 在某个时间点上, 键 key 要么存在于节点 A , 要么存在于节点 B , 但不会同时存在于节点 A 和节点 B 。
由于 Redis集群只使用 0 号数据库, 因此当 MIGRATE 命令被用于执行集群操做时, target_database 的值老是 0 。
target_database参数的存在是为了让 MIGRATE 命令成为一个通用命令, 从而能够做用于集群之外的其余功能。
咱们对MIGRATE 命令作了优化, 使得它即便在传输包含多个元素的列表键这样的复杂数据时, 也能够保持高效。
不过, 尽管MIGRATE 很是高效, 对一个键很是多、而且键的数据量很是大的集群来讲, 集群重配置仍是会占用大量的时间, 可能会致使集群没办法适应那些对于响应时间有严格要求的应用程序。
ASK 转向:
在以前介绍 MOVED 转向的时候, 咱们说除了 MOVED 转向以外, 还有另外一种 ASK 转向。当节点须要让一个客户端长期地(permanently)将针对某个槽的命令请求发送至另外一个节点时,节点向客户端返回 MOVED 转向。另外一方面, 当节点须要让客户端仅仅在下一个命令请求中转向至另外一个节点时, 节点向客户端返回 ASK 转向。
好比说, 在咱们上一节列举的槽 8 的例子中, 由于槽 8 所包含的各个键分散在节点 A 和节点 B 中, 因此当客户端在节点 A 中没找到某个键时, 它应该转向到节点 B 中去寻找, 可是这种转向应该仅仅影响一次命令查询,而不是让客户端每次都直接去查找节点 B : 在节点 A 所持有的属于槽 8 的键没有所有被迁移到节点 B 以前, 客户端应该先访问节点 A , 而后再访问节点 B 。由于这种转向只针对 16384 个槽中的其中一个槽, 因此转向对集群形成的性能损耗属于可接受的范围。
由于上述缘由, 若是咱们要在查找节点 A 以后, 继续查找节点 B , 那么客户端在向节点 B 发送命令请求以前, 应该先发送一个 ASKING 命令, 不然这个针对带有IMPORTING 状态的槽的命令请求将被节点 B 拒绝执行。接收到客户端 ASKING 命令的节点将为客户端设置一个一次性的标志(flag), 使得客户端能够执行一次针对 IMPORTING 状态的槽的命令请求。从客户端的角度来看, ASK 转向的完整语义(semantics)以下:
1. 若是客户端接收到 ASK 转向, 那么将命令请求的发送对象调整为转向所指定的节点。
2. 先发送一个 ASKING 命令,而后再发送真正的命令请求。
3. 没必要更新客户端所记录的槽 8 至节点的映射: 槽 8 应该仍然映射到节点 A , 而不是节点 B 。
一旦节点 A 针对槽 8 的迁移工做完成, 节点 A 在再次收到针对槽 8 的命令请求时, 就会向客户端返回 MOVED 转向, 将关于槽 8 的命令请求长期地转向到节点 B 。
注意, 即便客户端出现 Bug , 过早地将槽 8 映射到了节点 B 上面, 但只要这个客户端不发送 ASKING 命令, 客户端发送命令请求的时候就会赶上 MOVED 错误, 并将它转向回节点 A 。
容错:
节点失效检测,如下是节点失效检查的实现方法:
1. 当一个节点向另外一个节点发送 PING 命令, 可是目标节点未能在给定的时限内返回 PING 命令的回复时, 那么发送命令的节点会将目标节点标记为 PFAIL(possible failure,可能已失效)。等待 PING 命令回复的时限称为“节点超时时限(node timeout)”, 是一个节点选项(node-wise setting)。
2. 每次当节点对其余节点发送 PING 命令的时候,它都会随机地广播三个它所知道的节点的信息, 这些信息里面的其中一项就是说明节点是否已经被标记为 PFAIL或者 FAIL 。
当节点接收到其余节点发来的信息时, 它会记下那些被其余节点标记为失效的节点。这称为失效报告(failure report)。
3. 若是节点已经将某个节点标记为 PFAIL , 而且根据节点所收到的失效报告显式,集群中的大部分其余主节点也认为那个节点进入了失效状态, 那么节点会将那个失效节点的状态标记为 FAIL 。
4. 一旦某个节点被标记为 FAIL , 关于这个节点已失效的信息就会被广播到整个集群,全部接收到这条信息的节点都会将失效节点标记为 FAIL 。
简单来讲, 一个节点要将另外一个节点标记为失效, 必须先询问其余节点的意见, 而且获得大部分主节点的赞成才行。由于过时的失效报告会被移除,因此主节点要将某个节点标记为 FAIL 的话, 必须以最近接收到的失效报告做为根据。
从节点选举:一旦某个主节点进入 FAIL 状态, 若是这个主节点有一个或多个从节点存在,那么其中一个从节点会被升级为新的主节点, 而其余从节点则会开始对这个新的主节点进行复制。
新的主节点由已下线主节点属下的全部从节点中自行选举产生,如下是选举的条件:
1. 这个节点是已下线主节点的从节点。
2. 已下线主节点负责处理的槽数量非空。
3. 从节点的数据被认为是可靠的, 也便是, 主从节点之间的复制链接(replication link)的断线时长不能超过节点超时时限(nodetimeout)乘以REDIS_CLUSTER_SLAVE_VALIDITY_MULT 常量得出的积。
若是一个从节点知足了以上的全部条件, 那么这个从节点将向集群中的其余主节点发送受权请求, 询问它们,是否容许本身(从节点)升级为新的主节点。
若是发送受权请求的从节点知足如下属性, 那么主节点将向从节点返FAILOVER_AUTH_GRANTED 受权, 赞成从节点的升级要求:
1. 发送受权请求的是一个从节点, 而且它所属的主节点处于 FAIL状态。
2. 在已下线主节点的全部从节点中, 这个从节点的节点 ID 在排序中是最小的。
3. 这个从节点处于正常的运行状态: 它没有被标记为 FAIL 状态,也没有被标记为 PFAIL 状态。
一旦某个从节点在给定的时限内获得大部分主节点的受权,它就会开始执行如下故障转移操做:
1. 经过 PONG 数据包(packet)告知其余节点, 这个节点如今是主节点了。
2. 经过 PONG 数据包告知其余节点, 这个节点是一个已升级的从节点(promoted slave)。
3. 接管(claiming)全部由已下线主节点负责处理的哈希槽。
4. 显式地向全部节点广播一个 PONG 数据包, 加速其余节点识别这个节点的进度,而不是等待定时的 PING / PONG 数据包。
全部其余节点都会根据新的主节点对配置进行相应的更新:
在集群的生命周期中, 若是一个带有 PROMOTED 标识的主节点由于某些缘由转变成了从节点,那么该节点将丢失它所带有的 PROMOTED 标识。