redis集群学习笔记.docx
- 文档编号:18076761
- 上传时间:2023-08-13
- 格式:DOCX
- 页数:19
- 大小:26.79KB
redis集群学习笔记.docx
《redis集群学习笔记.docx》由会员分享,可在线阅读,更多相关《redis集群学习笔记.docx(19页珍藏版)》请在冰点文库上搜索。
redis集群学习笔记
Redis集群
Redis集群介绍
Redis集群是一个提供在多个Redis间节点间共享数据的程序集.
Redis集群并不支持处理多个keys的命令,因为这需要在不同的节点间移动数据,从而达不到像Redis那样的性能,在高负载的情况下可能会导致不可预料的错误.
Redis集群通过分区来提供一定程度的可用性,在实际环境中当某个节点宕机或者不可达的情况下继续处理命令.
Redis集群的优势:
∙自动分割数据到不同的节点上.
∙整个集群的部分节点失败或者不可达的情况下能够继续处理命令.
Redis集群的数据分片
Redis集群没有使用一致性hash,而是引入了哈希槽的概念.
Redis集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽.集群的每个节点负责一部分hash槽,举个例子,比如当前集群有3个节点,那么:
∙节点A包含0到5500号哈希槽.
∙节点B包含5501到11000号哈希槽.
∙节点C包含11001到16384号哈希槽.
这种结构很容易添加或者删除节点.比如如果我想新添加个节点D,我需要从节点A,B,C中得部分槽到D上.如果我像移除节点A,需要将A中得槽移到B和C节点上,然后将没有任何槽的A节点从集群中移除即可.
由于从一个节点将哈希槽移动到另一个节点并不会停止服务,所以无论添加删除或者改变某个节点的哈希槽的数量都不会造成集群不可用的状态.
Redis集群的主从复制模型
为了使在部分节点失败或者大部分节点无法通信的情况下集群仍然可用,所以集群使用了主从复制模型,每个节点都会有N-1个复制品.
在我们例子中具有A,B,C三个节点的集群,在没有复制模型的情况下,如果节点B失败了,那么整个集群就会以为缺少5501-11000这个范围的槽而不可用.
然而如果在集群创建的时候(或者过一段时间)我们为每个节点添加一个从节点A1,B1,C1,那么整个集群便有三个master节点和三个slave节点组成,这样在节点B失败后,集群便会选举B1为新的主节点继续服务,整个集群便不会因为槽找不到而不可用了
不过当B和B1都失败后,集群人爱是不可用的.
Redis一致性保证
Redis并不能保证数据的强一致性.这意味这在实际中集群在特定的条件下可能会丢失写操作.
第一个原因是因为集群是用了异步复制.写操作过程:
∙客户端向主节点B写入一条命令.
∙主节点B向客户端回复命令状态.
∙主节点将写操作复制给他得从节点B1,B2和B3.
主节点对命令的复制工作发生在返回命令回复之后,因为如果每次处理命令请求都需要等待复制操作完成的话,那么主节点处理命令请求的速度将极大地降低——我们必须在性能和一致性之间做出权衡。
注意:
Redis集群可能会在将来提供同步写的方法。
Redis集群另外一种可能会丢失命令的情况是集群出现了网络分区,并且一个客户端与至少包括一个主节点在内的少数实例被孤立。
.
举个例子假设集群包含A、B、C、A1、B1、C1六个节点,其中A、B、C为主节点,A1、B1、C1为A,B,C的从节点,还有一个客户端Z1
假设集群中发生网络分区,那么集群可能会分为两方,大部分的一方包含节点A、C、A1、B1和C1,小部分的一方则包含节点B和客户端Z1.
Z1仍然能够向主节点B中写入,如果网络分区发生时间较短,那么集群将会继续正常运作,如果分区的时间足够让大部分的一方将B1选举为新的master,那么Z1写入B中得数据便丢失了.
注意,在网络分裂出现期间,客户端Z1可以向主节点B发送写命令的最大时间是有限制的,这一时间限制称为节点超时时间(nodetimeout),是Redis集群的一个重要的配置选项:
搭建并使用Redis集群
搭建集群的第一件事情我们需要一些运行在 集群模式的Redis实例.这意味这集群并不是由一些普通的Redis实例组成的,集群模式需要通过配置启用,开启集群模式后的Redis实例便可以使用集群特有的命令和特性了.
下面是一个最少选项的集群的配置文件:
port7000
cluster-enabledyes
cluster-config-filenodes.conf
cluster-node-timeout5000
appendonlyyes
文件中的cluster-enabled选项用于开实例的集群模式,而cluster-conf-file选项则设定了保存节点配置文件的路径,默认值为nodes.conf.节点配置文件无须人为修改,它由Redis集群在启动时创建,并在有需要时自动进行更新。
要让集群正常运作至少需要三个主节点,不过在刚开始试用集群功能时,强烈建议使用六个节点:
其中三个为主节点,而其余三个则是各个主节点的从节点。
首先,让我们进入一个新目录,并创建六个以端口号为名字的子目录,稍后我们在将每个目录中运行一个Redis实例:
命令如下:
mkdircluster-test
cdcluster-test
mkdir700070017002700370047005
在文件夹7000至7005中,各创建一个redis.conf文件,文件的内容可以使用上面的示例配置文件,但记得将配置中的端口号从7000改为与文件夹名字相同的号码。
从RedisGithub页面的unstable分支中取出最新的Redis源码,编译出可执行文件redis-server,并将文件复制到cluster-test文件夹,然后使用类似以下命令,在每个标签页中打开一个实例:
cd7000
../redis-server./redis.conf
实例打印的日志显示,因为nodes.conf文件不存在,所以每个节点都为它自身指定了一个新的ID:
[82462]26Nov11:
56:
55.329*Noclusterconfigurationfound,I'm97a3a64667477371c4479320d683e4c8db5858b1
实例会一直使用同一个ID,从而在集群中保持一个独一无二(unique)的名字。
搭建集群
现在我们已经有了六个正在运行中的Redis实例,接下来我们需要使用这些实例来创建集群,并为每个节点编写配置文件。
通过使用Redis集群命令行工具redis-trib,编写节点配置文件的工作可以非常容易地完成:
redis-trib 位于Redis源码的src文件夹中,它是一个Ruby程序,这个程序通过向实例发送特殊命令来完成创建新集群,检查集群,或者对集群进行重新分片(reshared)等工作。
./redis-trib.rbcreate--replicas1127.0.0.1:
7000127.0.0.1:
7001\
127.0.0.1:
7002127.0.0.1:
7003127.0.0.1:
7004127.0.0.1:
7005
这个命令在这里用于创建一个新的集群,选项--replicas1 表示我们希望为集群中的每个主节点创建一个从节点。
之后跟着的其他参数则是这个集群实例的地址列表,3个master3个slave
redis-trib会打印出一份预想中的配置给你看,如果你觉得没问题的话,就可以输入yes,redis-trib就会将这份配置应用到集群当中,让各个节点开始互相通讯,最后可以得到如下信息:
[OK]All16384slotscovered
这表示集群中的16384个槽都有至少一个主节点在处理,集群运作正常。
使用集群
Redis集群现阶段的一个问题是客户端实现很少。
以下是一些我知道的实现:
∙redis-rb-cluster 是我(@antirez)编写的Ruby实现,用于作为其他实现的参考。
该实现是对redis-rb的一个简单包装,高效地实现了与集群进行通讯所需的最少语义(semantic).
∙redis-py-cluster redis-py-cluster看上去是redis-rb-cluster的一个Python版本,这个项目有一段时间没有更新了(最后一次提交是在六个月之前),不过可以将这个项目用作学习集群的起点。
∙流行的Predis 曾经对早期的Redis集群有过一定的支持,但我不确定它对集群的支持是否完整,也不清楚它是否和最新版本的Redis集群兼容(因为新版的Redis集群将槽的数量从4k改为16k了).
∙使用最多的时java客户端, Jedis 最近添加了对集群的支持,详细请查看项目README中JedisCluster部分.
∙Redisunstable分支中的 redis-cli 程序实现了非常基本的集群支持,可以使用命令 redis-cli-c 来启动。
测试Redis集群比较简单的办法就是使用 redis-rb-cluster 或者 redis-cli ,接下来我们将使用 redis-cli 为例来进行演示:
$redis-cli-c-p7000
redis127.0.0.1:
7000>setfoobar
->Redirectedtoslot[12182]locatedat127.0.0.1:
7002
OK
redis127.0.0.1:
7002>sethelloworld
->Redirectedtoslot[866]locatedat127.0.0.1:
7000
OK
redis127.0.0.1:
7000>getfoo
->Redirectedtoslot[12182]locatedat127.0.0.1:
7002
"bar"
redis127.0.0.1:
7000>gethello
->Redirectedtoslot[866]locatedat127.0.0.1:
7000
"world"
redis-cli对集群的支持是非常基本的,所以它总是依靠Redis集群节点来将它转向(redirect)至正确的节点。
一个真正的(serious)集群客户端应该做得比这更好:
它应该用缓存记录起哈希槽与节点地址之间的映射(map),从而直接将命令发送到正确的节点上面。
这种映射只会在集群的配置出现某些修改时变化,比如说,在一次故障转移(failover)之后,或者系统管理员通过添加节点或移除节点来修改了集群的布局(layout)之后,诸如此类。
使用redis-rb-cluster写一个例子
在展示如何使用集群进行故障转移、重新分片等操作之前,我们需要创建一个示例应用,了解一些与Redis集群客户端进行交互的基本方法。
在运行示例应用的过程中,我们会尝试让节点进入失效状态,又或者开始一次重新分片,以此来观察Redis集群在真实世界运行时的表现,并且为了让这个示例尽可能地有用,我们会让这个应用向集群进行写操作。
本节将通过两个示例应用来展示redis-rb-cluster的基本用法,以下是本节的第一个示例应用,它是一个名为example.rb的文件,包含在redis-rb-cluster项目里面
1require'./cluster'
2
3startup_nodes=[
4{:
host=>"127.0.0.1",:
port=>7000},
5{:
host=>"127.0.0.1",:
port=>7001}
6]
7rc=RedisCluster.new(startup_nodes,32,:
timeout=>0.1)
8
9last=false
10
11whilenotlast
12begin
13last=rc.get("__last__")
14last=0if!
last
15rescue=>e
16puts"error#{e.to_s}"
17sleep1
18end
19end
20
21((last.to_i+1)..1000000000).each{|x|
22begin
23rc.set("foo#{x}",x)
24putsrc.get("foo#{x}")
25rc.set("__last__",x)
26rescue=>e
27puts"error#{e.to_s}"
28end
29sleep0.1
30}
这个应用所做的工作非常简单:
它不断地以foo
∙SETfoo00
∙SETfoo11
∙SETfoo22
∙Andsoforth...
代码中的每个集群操作都使用一个begin和rescue代码块(block)包裹着,因为我们希望在代码出错时,将错误打印到终端上面,而不希望应用因为异常(exception)而退出。
代码的第七行是代码中第一个有趣的地方,它创建了一个Redis集群对象,其中创建对象所使用的参数及其意义如下:
第一个参数是记录了启动节点的startup_nodes列表,列表中包含了两个集群节点的地址。
第二个参数指定了对于集群中的各个不同的节点,Redis集群对象可以获得的最大连接数,第三个参数timeout指定了一个命令在执行多久之后,才会被看作是执行失败。
启动列表中并不需要包含所有集群节点的地址,但这些地址中至少要有一个是有效的:
一旦redis-rb-cluster成功连接上集群中的某个节点时,集群节点列表就会被自动更新,任何真正的的集群客户端都应该这样做。
现在,程序创建的Redis集群对象实例被保存到rc变量里面,我们可以将这个对象当作普通Redis对象实例来使用。
在十一至十九行,我们先尝试阅读计数器中的值,如果计数器不存在的话,我们才将计数器初始化为0:
通过将计数值保存到Redis的计数器里面,我们可以在示例重启之后,仍然继续之前的执行过程,而不必每次重启之后都从foo0开始重新设置键值对。
为了让程序在集群下线的情况下,仍然不断地尝试读取计数器的值,我们将读取操作包含在了一个while循环里面,一般的应用程序并不需要如此小心。
二十一至三十行是程序的主循环,这个循环负责设置键值对,并在设置出错时打印错误信息。
程序在主循环的末尾添加了一个sleep调用,让写操作的执行速度变慢,帮助执行示例的人更容易看清程序的输出。
执行example.rb程序将产生以下输出:
ruby./example.rb
1
2
3
4
5
6
7
8
9
^C(Istoppedtheprogramhere)
这个程序并不是十分有趣,稍后我们就会看到一个更有趣的集群应用示例,不过在此之前,让我们先使用这个示例来演示集群的重新分片操作。
集群重新分片
现在,让我们来试试对集群进行重新分片操作。
在执行重新分片的过程中,请让你的example.rb程序处于运行状态,这样你就会看到,重新分片并不会对正在运行的集群程序产生任何影响,你也可以考虑将example.rb中的sleep调用删掉,从而让重新分片操作在近乎真实的写负载下执行
重新分片操作基本上就是将某些节点上的哈希槽移动到另外一些节点上面,和创建集群一样,重新分片也可以使用redis-trib程序来执行
执行以下命令可以开始一次重新分片操作:
./redis-trib.rbreshard127.0.0.1:
7000
你只需要指定集群中其中一个节点的地址,redis-trib就会自动找到集群中的其他节点。
目前redis-trib只能在管理员的协助下完成重新分片的工作,要让redis-trib自动将哈希槽从一个节点移动到另一个节点,目前来说还做不到
你像移动多少个槽(从1到16384)?
我们尝试从将100个槽重新分片,如果example.rb程序一直运行着的话,现在1000个槽里面应该有不少键了。
除了移动的哈希槽数量之外,redis-trib还需要知道重新分片的目标,也即是,负责接收这1000个哈希槽的节点。
$redis-cli-p7000clusternodes|grepmyself
97a3a64667477371c4479320d683e4c8db5858b1:
0myself,master-000connected0-5460
我得目标节点是97a3a64667477371c4479320d683e4c8db5858b1.
现在需要指定从哪写节点来移动keys到目标借调我输入的是all ,这样就会从其他每个master上取一些哈希槽。
最后确认后你将会看到每个redis-trib移动的槽的信息,每个key的移动的信息也会打印出来
在重新分片的过程中,你得例子程序是不会受到影响的,你可以停止或者重新启动多次。
在重新分片结束后你可以通过如下命令检查集群状态:
一个更有趣的程序
我们在前面使用的示例程序example.rb并不是十分有趣,因为它只是不断地对集群进行写入,但并不检查写入结果是否正确。
比如说,集群可能会错误地将example.rb发送的所有SET命令都改成了SETfoo42,但因为example.rb并不检查写入后的值,所以它不会意识到集群实际上写入的值是错误的因为这个原因,redis-rb-cluster项目包含了一个名为consistency-test.rb的示例应用,这个应用比起example.rb有趣得多:
它创建了多个计数器(默认为1000个),并通过发送INCR命令来增加这些计数器的值。
在增加计数器值的同时,consistency-test.rb还执行以下操作:
∙每次使用INCR命令更新一个计数器时,应用会记录下计数器执行INCR命令之后应该有的值。
举个例子,如果计数器的起始值为0,而这次是程序第50次向它发送INCR命令,那么计数器的值应该是50。
∙在每次发送INCR命令之前,程序会随机从集群中读取一个计数器的值,并将它与自己记录的值进行对比,看两个值是否相同。
换句话说,这个程序是一个一致性检查器(consistencychecker):
如果集群在执行INCR命令的过程中,丢失了某条INCR命令,又或者多执行了某条客户端没有确认到的INCR命令,那么检查器将察觉到这一点——在前一种情况中,consistency-test.rb记录的计数器值将比集群记录的计数器值要大;而在后一种情况中,consistency-test.rb记录的计数器值将比集群记录的计数器值要小。
运行consistency-test程序将产生类似以下的输出:
$rubyconsistency-test.rb
925R(0err)|925W(0err)|
5030R(0err)|5030W(0err)|
9261R(0err)|9261W(0err)|
13517R(0err)|13517W(0err)|
17780R(0err)|17780W(0err)|
22025R(0err)|22025W(0err)|
25818R(0err)|25818W(0err)|
结果展示了执行的读和 写,和错误(由于系统不可用而没有接受的查询发生的错误)的数量.
如果程序察觉了不一致的情况出现,它将在输出行的末尾显式不一致的详细情况。
比如说,如果我们在consistency-test.rb运行的过程中,手动修改某个计数器的值:
$redis127.0.0.1:
7000>setkey_2170
OK
(intheothertabIsee...)
94774R(0err)|94774W(0err)|
98821R(0err)|98821W(0err)|
102886R(0err)|102886W(0err)|114lost|
107046R(0err)|107046W(0err)|114lost|
在我们修改计数器值的时候,计数器的正确值是114(执行了114次INCR命令),因为我们将计数器的值设成了0,所以consistency-test.rb会向我们报告说丢失了114个INCR命令。
这个程序作为测试程序很有意思,所以我们用这个程序来测试故障恢复.
测试故障转移
在执行本节操作的过程中,请一直运行consistency-test程序。
要触发一次故障转移,最简单的办法就是令集群中的某个主节点进入下线状态。
首先用以下命令列出集群中的所有主节点:
$redis-cli-p7000clusternodes|grepmaster
3e3a6cb0d9a9a87168e266b0a0b24026c0aae3f0127.0.0.1:
7001master-0138********820connected5960-10921
2938205e12de373867bf38f1ca29d31d0ddb3e46127.0.0.1:
7002master-0138********820connected11423-16383
97a3a64667477371c4479320d683e4c8db5858b1:
0myself,master-000connected0-595910922-11422
通过命令输出得知端口号为7000、7001和7002的节点都是主节点,然后我们可以通过向端口号为7002的主节点发送DEBUGSEGFAULT命令,让这个主节点崩溃:
$redis-cli-p7002debugsegfault
Error:
Serverclosedtheconnection
现在,切换到运行着consistency-test的标签页,可以看到,consistency-test在7002下线之后的一段时间里将产生大量的错误警告信息:
18849R(0err)|18849W(0err)|
23151R(0err)|23151W(0err)|
27302R(0err)|27302W(0err)|
...manyerrorwarningshere...
29659R(578err)|29660W(577err)|
33749R(578err)|33750W(577err)|
37918R(578err)|37919W(577err)|
42077R(578err)|42078W(577err)|
从consistency-test的这段输出可以看到,集群在执行故障转移期间,总共丢失了578个读命令和577个写命令,但是并没
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- redis 集群 学习 笔记