Redis分布式部署,一致性hash;分布式与缓存队列
http://blog.iyunv.com/yfkiss/article/details/39996129Redis 3.0.0 RC1版本10.9号发布,Release Note
这个版本支持Redis Cluster,相信很多同学期待已久,不过这个版本只是RC版本,要应用到生产环境,还得等等
Redis Cluster设计要点:
架构:无中心
Redis Cluster采用无中心结构,每个节点都保存数据和整个集群的状态
每个节点都和其他所有节点连接,这些连接保持活跃
使用gossip协议传播信息以及发现新节点
node不作为client请求的代理,client根据node返回的错误信息重定向请求
数据分布:预分桶
预分好16384个桶,根据 CRC16(key) mod 16384的值,决定将一个key放到哪个桶中
每个Redis物理结点负责一部分桶的管理,当发生Redis节点的增减时,调整桶的分布即可
例如,假设Redis Cluster三个节点A/B/C,则
Node A 包含桶的编号可以为: 0 到 5500.
Node B 包含桶的编号可以为: 5500 到 11000.
Node C包含桶的编号可以为: 11001 到 16384.
当发生Redis节点的增减时,调整桶的分布即可。
预分桶的方案介于“硬Hash”和“一致性Hash”之间,牺牲了一定的灵活性,但相比“一致性Hash“,数据的管理成本大大降低
可用性:Master-Slave
为了保证服务的可用性,Redis Cluster采取的方案是的Master-Slave
每个Redis Node可以有一个或者多个Slave。当Master挂掉时,选举一个Slave形成新的Master
一个RedisNode包含一定量的桶,当这些桶对应的Master和Slave都挂掉时,这部分桶对应的数据不可用
写
Redis Cluster使用异步复制
一个完整的写操作步骤:
1.client写数据到master
2.master告诉client "ok"
3.master传播更新到slave
存在数据丢失的风险:
1. 上述写步骤1)和2)成功后,master crash,而此时数据还没有传播到slave
2. 由于分区导致同时存在两个master,client向旧的master写入了数据。
当然,由于Redis Cluster存在超时及故障恢复机制,第2个风险基本上不可能发生
数据迁移
Redis Cluster支持在线增/减节点。
基于桶的数据分布方式大大降低了迁移成本,只需将数据桶从一个Redis Node迁移到另一个Redis Node即可完成迁移。
当桶从一个Node A向另一个Node B迁移时,Node A和Node B都会有这个桶,Node A上桶的状态设置为MIGRATING,Node B上桶的状态被设置为IMPORTING
当客户端请求时:
所有在Node A上的请求都将由A来处理,所有不在A上的key都由Node B来处理。同时,Node A上将不会创建新的key
多key操作
当系统从单节点向多节点扩展时,多key的操作总是一个非常难解决的问题,Redis Cluster方案如下:
1. 不支持多key操作
2. 如果一定要使用多key操作,请确保所有的key都在一个node上,具体方法是使用“hash tag”方案
hash tag方案是一种数据分布的例外情况
Reference:
Redis cluster tutorial
Redis cluster Specification
http://zhoushouby.blog.iyunv.com/9150272/1560346
redis 3.0 cluster 集群 学习之路篇
2014-10-02 11:35:15
标签:集群 redis 3.0
版权声明:原创作品,谢绝转载!否则将追究法律责任。
周氏一族,整理技术文档,给下一代留点教程......
目前,项目用的redis主备,感觉超不爽,很多局限性,特别是在 redis master 宕机之后,维护非常麻烦,寻思着弄一个redis集群,可不,总算到了今年10.1,redis发布了cluster版本。开启摸索之路...
很多人,一看到官网有最新的cluster版本,满怀热血,第一件事,就是搭建cluster环境,其实,鄙人却不,还是要从基层走起,先来了解一下官方信息,整理成下面的,大部分都是根据网络上整理出来的,又存在相同,纯属借鉴,往见谅!!!
redis主备面临的问题需求
1、主备模式,过于单一,特别是主服务器宕机的情况下,数据没法写入。
2、业务发展,数据庞大,主备必须同步相同数据,对存储需求较大,有点多余
3、虽然说有sentinel,但是效率还是非常低
Redis集群是一个可以在多个 Redis 节点之间进行数据共享的设施。 这一点毫无疑问的!
Redis集群通过分区来提供一定程度的可用性:即使集群中有一部分节点失效或者无法进行通讯, 集群也可以继续处理命令请求。
Redis 集群提供了以下两个好处:
(1)将数据自动切分(split)到多个节点的能力。
面临大数据时代,集群,就是得要能实现一台宕机,不影响整个集群运作,换句话说,数据得每台机器都必须拥有对方的data,但是这样由于业务的慢慢扩展,数据会原来越大,很明显对存储比较吃力。redis在这一点上面,做得比较完美,那就是"切片"。通过内部 “私有算法技术”,把data分布式存储到集群机器,当要读取数据时,再通过 “私有内部算法” 映射到对应机器读取。
(2)当集群中的一部分节点失效或者无法进行通讯时, 仍然可以继续处理命令请求的能力。
因为在3.0版本当中,redis采用master n-1 salver的模式,也就是说,一台master主,就若干台备,甚至没有备也行,但是本人强烈建议要1:1的模式,条件允许的情况下,建议1:n的模式
2、集群的数据分片功能:
集群要实现的目的是要将不同的 key 分散放置到不同的 redis 节点,这里我们需要一个规则或者算法,通常的做法是获取 key 的哈希值,然后根据节点数来求模,但这种做法有其明显的弊端,当我们需要增加或减少一个节点时,会造成大量的 key 无法命中,这种比例是相当高的,所以就有人提出了一致性哈希的概念。
一致性哈希有四个重要特征:
均衡性:也有人把它定义为平衡性,是指哈希的结果能够尽可能分布到所有的节点中去,这样可以有效的利用每个节点上的资源。
单调性:对于单调性有很多翻译让我非常的不解,而我想要的是当节点数量变化时哈希的结果应尽可能的保护已分配的内容不会被重新分派到新的节点。
分散性和负载:这两个其实是差不多的意思,就是要求一致性哈希算法对 key 哈希应尽可能的避免重复。
但是:
Redis 集群没有使用一致性hash, 而是引入了哈希槽的概念。
Redis 集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽.集群的每个节点负责一部分hash槽。这种结构很容易添加或者删除节点,并且无论是添加删除或者修改某一个节点,都不会造成集群不可用的状态。
使用哈希槽的好处就在于可以方便的添加或移除节点。
当需要增加节点时,只需要把其他节点的某些哈希槽挪到新节点就可以了;
当需要移除节点时,只需要把移除节点上的哈希槽挪到其他节点就行了;
在这一点上,我们以后新增或移除节点的时候不用先停掉所有的 redis 服务,very good的
3、Redis集群的主从架构:
为了使在部分节点失败或者大部分节点无法通信的情况下集群仍然可用,所以集群使用了主从复制模型,每个节点都会有N-1个复制品。
例如有A,B,C三个节点的集群,在没有复制模型的情况下,如果节点B失败了,那么整个集群就会以为缺少B节点所承担的哈希槽这个范围的槽而不可用。
然而如果在集群创建的时候(或者过一段时间)我们为每个节点添加一个从节点A1,B1,C1,那么整个集群便有三个master节点和三个slave节点组成,这样在节点B失败后,集群便会选举B1为新的主节点继续服务,整个集群便不会因为槽找不到而不可用了。当然如果B和B1都down了,那集群还是不可用的,不过这种情况微乎其妙,基本不用考虑,出发你交换机挂了吧,或者机房断电。
4、redis架构图
架构细节:
(1)所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽.
(2)节点的fail是通过集群中超过半数的节点检测失效时才生效.
(3)客户端与redis节点直连,不需要中间proxy层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可
(4)redis-cluster把所有的物理节点映射到slot上,cluster 负责维护nodeslotvalue
5、redis-cluster选举:容错
(1)领着选举过程是集群中所有master参与,如果半数以上master节点与master节点通信超过(cluster-node-timeout),认为当前master节点挂掉.
(2):什么时候整个集群不可用(cluster_state:fail),当集群不可用时,所有对集群的操作做都不可用,收到((error) CLUSTERDOWN The cluster is down)错误
a:如果集群任意master挂掉,且当前master没有slave.集群进入fail状态,也可以理解成进群的slot映射不完成时进入fail状态.
b:如果进群超过半数以上master挂掉,无论是否有slave集群进入fail状态
本文出自 “周氏一族” 博客,谢绝转载!
http://hot66hot.iteye.com/blog/2050676
最近研究redis-cluster,正好搭建了一个环境,遇到了很多坑,系统的总结下,等到redis3 release出来后,换掉memCache 集群. 转载请注明出处哈:http://hot66hot.iteye.com/admin/blogs/2050676
一:关于redis cluster
1:redis cluster的现状
reids-cluster计划在redis3.0中推出,可以看作者antirez的声明:http://antirez.com/news/49 (ps:跳票了好久,今年貌似加快速度了),目前的最新版本见:https://raw.githubusercontent.com/antirez/redis/3.0/00-RELEASENOTES
作者的目标:Redis Cluster will support up to ~1000 nodes. 赞...
目前redis支持的cluster特性(已测试):
1):节点自动发现
2):slave->master 选举,集群容错
3):Hot resharding:在线分片
4):集群管理:cluster xxx
5):基于配置(nodes-port.conf)的集群管理
6):ASK 转向/MOVED 转向机制.
2:redis cluster 架构
1)redis-cluster架构图
架构细节:
(1)所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽.
(2)节点的fail是通过集群中超过半数的节点检测失效时才生效.
(3)客户端与redis节点直连,不需要中间proxy层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可
(4)redis-cluster把所有的物理节点映射到slot上,cluster 负责维护nodeslotvalue
2) redis-cluster选举:容错
(1)领着选举过程是集群中所有master参与,如果半数以上master节点与master节点通信超过(cluster-node-timeout),认为当前master节点挂掉.
(2):什么时候整个集群不可用(cluster_state:fail)?
a:如果集群任意master挂掉,且当前master没有slave.集群进入fail状态,也可以理解成集群的slot映射不完成时进入fail状态. ps : redis-3.0.0.rc1加入cluster-require-full-coverage参数,默认关闭,打开集群兼容部分失败.
b:如果集群超过半数以上master挂掉,无论是否有slave集群进入fail状态.
ps:当集群不可用时,所有对集群的操作做都不可用,收到((error) CLUSTERDOWN The cluster is down)错误
二:redis cluster的使用
1:安装redis cluster
1):安装redis-cluster依赖:redis-cluster的依赖库在使用时有兼容问题,在reshard时会遇到各种错误,请按指定版本安装.
(1)确保系统安装zlib,否则gem install会报(no such file to load -- zlib)
Java代码
[*]#download:zlib-1.2.6.tar
[*]./configure
[*]make
[*]make install
(2)安装ruby:version(1.9.2)
Java代码
[*]# ruby1.9.2
[*]cd /path/ruby
[*]./configure -prefix=/usr/local/ruby
[*]make
[*]make install
[*]sudo cp ruby /usr/local/bin
(3)安装rubygem:version(1.8.16)
Java代码
[*]# rubygems-1.8.16.tgz
[*]cd /path/gem
[*]sudo ruby setup.rb
[*]sudo cp bin/gem /usr/local/bin
(4)安装gem-redis:version(3.0.0)
Java代码
[*]gem install redis --version 3.0.0
[*]#由于源的原因,可能下载失败,就手动下载下来安装
[*]#download地址:http://rubygems.org/gems/redis/versions/3.0.0
[*]gem install -l /data/soft/redis-3.0.0.gem
(5)安装redis-cluster
Java代码
[*]cd /path/redis
[*]make
[*]sudo cp /opt/redis/src/redis-server /usr/local/bin
[*]sudo cp /opt/redis/src/redis-cli /usr/local/bin
[*]sudo cp /opt/redis/src/redis-trib.rb /usr/local/bin
2:配置redis cluster
1)redis配置文件结构:
使用包含(include)把通用配置和特殊配置分离,方便维护.
2)redis通用配置.
Java代码
[*]#GENERAL
[*]daemonize no
[*]tcp-backlog 511
[*]timeout 0
[*]tcp-keepalive 0
[*]loglevel notice
[*]databases 16
[*]dir /opt/redis/data
[*]slave-serve-stale-data yes
[*]#slave只读
[*]slave-read-only yes
[*]#not use default
[*]repl-disable-tcp-nodelay yes
[*]slave-priority 100
[*]#打开aof持久化
[*]appendonly yes
[*]#每秒一次aof写
[*]appendfsync everysec
[*]#关闭在aof rewrite的时候对新的写操作进行fsync
[*]no-appendfsync-on-rewrite yes
[*]auto-aof-rewrite-min-size 64mb
[*]lua-time-limit 5000
[*]#打开redis集群
[*]cluster-enabled yes
[*]#节点互连超时的阀值
[*]cluster-node-timeout 15000
[*]cluster-migration-barrier 1
[*]slowlog-log-slower-than 10000
[*]slowlog-max-len 128
[*]notify-keyspace-events ""
[*]hash-max-ziplist-entries 512
[*]hash-max-ziplist-value 64
[*]list-max-ziplist-entries 512
[*]list-max-ziplist-value 64
[*]set-max-intset-entries 512
[*]zset-max-ziplist-entries 128
[*]zset-max-ziplist-value 64
[*]activerehashing yes
[*]client-output-buffer-limit normal 0 0 0
[*]client-output-buffer-limit slave 256mb 64mb 60
[*]client-output-buffer-limit pubsub 32mb 8mb 60
[*]hz 10
[*]aof-rewrite-incremental-fsync yes
3)redis特殊配置.
Java代码
[*]#包含通用配置
[*]include /opt/redis/redis-common.conf
[*]#监听tcp端口
[*]port 6379
[*]#最大可用内存
[*]maxmemory 100m
[*]#内存耗尽时采用的淘汰策略:
[*]# 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
[*]maxmemory-policy allkeys-lru
[*]#aof存储文件
[*]appendfilename "appendonly-6379.aof"
[*]#不开启rdb存储,只用于添加slave过程
[*]dbfilename dump-6379.rdb
[*]#cluster配置文件(启动自动生成)
[*]cluster-config-file nodes-6379.conf
[*]#部署在同一机器的redis实例,把auto-aof-rewrite搓开,因为cluster环境下内存占用基本一致.
[*]#防止同意机器下瞬间fork所有redis进程做aof rewrite,占用大量内存(ps:cluster必须开启aof)
[*]auto-aof-rewrite-percentage 80-100
3:cluster 操作
cluster集群相关命令,更多redis相关命令见文档:http://redis.readthedocs.org/en/latest/
Java代码
[*]集群
[*]CLUSTER INFO 打印集群的信息
[*]CLUSTER NODES 列出集群当前已知的所有节点(node),以及这些节点的相关信息。
[*]节点
[*]CLUSTER MEET 将 ip 和 port 所指定的节点添加到集群当中,让它成为集群的一份子。
[*]CLUSTER FORGET从集群中移除 node_id 指定的节点。
[*]CLUSTER REPLICATE将当前节点设置为 node_id 指定的节点的从节点。
[*]CLUSTER SAVECONFIG 将节点的配置文件保存到硬盘里面。
[*]槽(slot)
[*]CLUSTER ADDSLOTS 将一个或多个槽(slot)指派(assign)给当前节点。
[*]CLUSTER DELSLOTS 移除一个或多个槽对当前节点的指派。
[*]CLUSTER FLUSHSLOTS 移除指派给当前节点的所有槽,让当前节点变成一个没有指派任何槽的节点。
[*]CLUSTER SETSLOTNODE将槽 slot 指派给 node_id 指定的节点,如果槽已经指派给另一个节点,那么先让另一个节点删除该槽>,然后再进行指派。
[*]CLUSTER SETSLOTMIGRATING将本节点的槽 slot 迁移到 node_id 指定的节点中。
[*]CLUSTER SETSLOTIMPORTING从 node_id 指定的节点中导入槽 slot 到本节点。
[*]CLUSTER SETSLOTSTABLE 取消对槽 slot 的导入(import)或者迁移(migrate)。
[*]键
[*]CLUSTER KEYSLOT计算键 key 应该被放置在哪个槽上。
[*]CLUSTER COUNTKEYSINSLOT返回槽 slot 目前包含的键值对数量。
[*]CLUSTER GETKEYSINSLOT 返回 count 个 slot 槽中的键。
4:redis cluster 运维操作
1)初始化并构建集群
(1)启动集群相关节点(必须是空节点,beta3后可以是有数据的节点),指定配置文件和输出日志
Java代码
[*]redis-server /opt/redis/conf/redis-6380.conf > /opt/redis/logs/redis-6380.log 2>&1 &
[*]redis-server /opt/redis/conf/redis-6381.conf > /opt/redis/logs/redis-6381.log 2>&1 &
[*]redis-server /opt/redis/conf/redis-6382.conf > /opt/redis/logs/redis-6382.log 2>&1 &
[*]redis-server /opt/redis/conf/redis-7380.conf > /opt/redis/logs/redis-7380.log 2>&1 &
[*]redis-server /opt/redis/conf/redis-7381.conf > /opt/redis/logs/redis-7381.log 2>&1 &
[*]redis-server /opt/redis/conf/redis-7382.conf > /opt/redis/logs/redis-7382.log 2>&1 &
(2):使用自带的ruby工具(redis-trib.rb)构建集群
Java代码
[*]#redis-trib.rb的create子命令构建
[*]#--replicas 则指定了为Redis Cluster中的每个Master节点配备几个Slave节点
[*]#节点角色由顺序决定,先master之后是slave(为方便辨认,slave的端口比master大1000)
[*]redis-trib.rb create --replicas 1 10.10.34.14:6380 10.10.34.14:6381 10.10.34.14:6382 10.10.34.14:7380 10.10.34.14:7381 10.10.34.14:7382
(3):检查集群状态
Java代码
[*]#redis-trib.rb的check子命令构建
[*]#ip:port可以是集群的任意节点
[*]redis-trib.rb check 10.10.34.14:6380
最后输出如下信息,没有任何警告或错误,表示集群启动成功并处于ok状态
Java代码
[*] All nodes agree about slots configuration.
[*]>>> Check for open slots...
[*]>>> Check slots coverage...
[*] All 16384 slots covered.
2):添加新master节点
(1)添加一个master节点:创建一个空节点(empty node),然后将某些slot移动到这个空节点上,这个过程目前需要人工干预
a):根据端口生成配置文件(ps:establish_config.sh是我自己写的输出配置脚本)
Java代码
[*]sh establish_config.sh 6386 > conf/redis-6386.conf
b):启动节点
Java代码
[*]redis-server /opt/redis/conf/redis-6386.conf > /opt/redis/logs/redis-6386.log 2>&1 &
c):加入空节点到集群
add-node将一个节点添加到集群里面, 第一个是新节点ip:port, 第二个是任意一个已存在节点ip:port
Java代码
[*]redis-trib.rb add-node 10.10.34.14:6386 10.10.34.14:6381
node:新节点没有包含任何数据, 因为它没有包含任何slot。新加入的加点是一个主节点, 当集群需要将某个从节点升级为新的主节点时, 这个新节点不会被选中
d):为新节点分配slot
Java代码
[*]redis-trib.rb reshard 10.10.34.14:6386
[*]#根据提示选择要迁移的slot数量(ps:这里选择500)
[*]How many slots do you want to move (from 1 to 16384)? 500
[*]#选择要接受这些slot的node-id
[*]What is the receiving node ID? f51e26b5d5ff74f85341f06f28f125b7254e61bf
[*]#选择slot来源:
[*]#all表示从所有的master重新分配,
[*]#或者数据要提取slot的master节点id,最后用done结束
[*]Please enter all the source node IDs.
[*]Type 'all' to use all the nodes as source nodes for the hash slots.
[*]Type 'done' once you entered all the source nodes IDs.
[*]Source node #1:all
[*]#打印被移动的slot后,输入yes开始移动slot以及对应的数据.
[*]#Do you want to proceed with the proposed reshard plan (yes/no)? yes
[*]#结束
3):添加新的slave节点
a):前三步操作同添加master一样
b)第四步:redis-cli连接上新节点shell,输入命令:cluster replicate 对应master的node-id
Java代码
[*]cluster replicate 2b9ebcbd627ff0fd7a7bbcc5332fb09e72788835
note:在线添加slave 时,需要dump整个master进程,并传递到slave,再由 slave加载rdb文件到内存,rdb传输过程中Master可能无法提供服务,整个过程消耗大量io,小心操作.
例如本次添加slave操作产生的rdb文件
Java代码
[*]-rw-r--r-- 1 root root34946 Apr 17 18:23 dump-6386.rdb
[*]-rw-r--r-- 1 root root34946 Apr 17 18:23 dump-7386.rdb
4):在线reshard 数据:
对于负载/数据不均匀的情况,可以在线reshard slot来解决,方法与添加新master的reshard一样,只是需要reshard的master节点是老节点.
5):删除一个slave节点
Java代码
[*]#redis-trib del-node ip:port ''
[*]redis-trib.rb del-node 10.10.34.14:7386 'c7ee2fca17cb79fe3c9822ced1d4f6c5e169e378'
6):删除一个master节点
a):删除master节点之前首先要使用reshard移除master的全部slot,然后再删除当前节点(目前只能把被删除
master的slot迁移到一个节点上)
Java代码
[*]#把10.10.34.14:6386当前master迁移到10.10.34.14:6380上
[*]redis-trib.rb reshard 10.10.34.14:6380
[*]#根据提示选择要迁移的slot数量(ps:这里选择500)
[*]How many slots do you want to move (from 1 to 16384)? 500(被删除master的所有slot数量)
[*]#选择要接受这些slot的node-id(10.10.34.14:6380)
[*]What is the receiving node ID? c4a31c852f81686f6ed8bcd6d1b13accdc947fd2 (ps:10.10.34.14:6380的node-id)
[*]Please enter all the source node IDs.
[*]Type 'all' to use all the nodes as source nodes for the hash slots.
[*]Type 'done' once you entered all the source nodes IDs.
[*]Source node #1:f51e26b5d5ff74f85341f06f28f125b7254e61bf(被删除master的node-id)
[*]Source node #2:done
[*]#打印被移动的slot后,输入yes开始移动slot以及对应的数据.
[*]#Do you want to proceed with the proposed reshard plan (yes/no)? yes
b):删除空master节点
Java代码
[*]redis-trib.rb del-node 10.10.34.14:6386 'f51e26b5d5ff74f85341f06f28f125b7254e61bf'
三:redis cluster 客户端(Jedis)
1:客户端基本操作使用
Java代码
[*] private static BinaryJedisCluster jc;
[*]static {
[*] //只给集群里一个实例就可以
[*] Set jedisClusterNodes = new HashSet();
[*] jedisClusterNodes.add(new HostAndPort("10.10.34.14", 6380));
[*] jedisClusterNodes.add(new HostAndPort("10.10.34.14", 6381));
[*] jedisClusterNodes.add(new HostAndPort("10.10.34.14", 6382));
[*] jedisClusterNodes.add(new HostAndPort("10.10.34.14", 6383));
[*] jedisClusterNodes.add(new HostAndPort("10.10.34.14", 6384));
[*] jedisClusterNodes.add(new HostAndPort("10.10.34.14", 7380));
[*] jedisClusterNodes.add(new HostAndPort("10.10.34.14", 7381));
[*] jedisClusterNodes.add(new HostAndPort("10.10.34.14", 7382));
[*] jedisClusterNodes.add(new HostAndPort("10.10.34.14", 7383));
[*] jedisClusterNodes.add(new HostAndPort("10.10.34.14", 7384));
[*] jc = new BinaryJedisCluster(jedisClusterNodes);
[*] }
[*]@Test
[*] public void testBenchRedisSet() throws Exception {
[*] final Stopwatch stopwatch = new Stopwatch();
[*] List list = buildBlogVideos();
[*] for (int i = 0; i < 1000; i++) {
[*] String key = "key:" + i;
[*] stopwatch.start();
[*] byte[] bytes1 = protostuffSerializer.serialize(list);
[*] jc.setex(key, 60 * 60, bytes1);
[*] stopwatch.stop();
[*] }
[*] System.out.println("time=" + stopwatch.toString());
[*] }
2:jedis客户端的坑.
1)cluster环境下redis的slave不接受任何读写操作,
2)client端不支持keys批量操作,不支持select dbNum操作,只有一个db:select 0
3)JedisCluster 的info()等单机函数无法调用,返回(No way to dispatch this command to Redis Cluster)错误,.
4)JedisCluster 没有针对byte[]的API,需要自己扩展(附件是我加的基于byte[]的BinaryJedisClusterapi)
参考文档:
http://redis.io/topics/cluster-spec
http://redis.io/topics/cluster-tutorial
http://blog.sina.com.cn/s/blog_48c95a190101dhe9.html
Redis Cluster即Redis的分布式版本,将是Redis继支持Lua脚本之后的又一重磅功能,官方声明将会在今年第三季度发布Redis Cluster的beta版并在年底发布第一个稳定版本。当前,虽然Redis的稳定版本里还没有集成分布式功能,但实际上在开发版中Redis Cluster的开发已经取得了长足的进展,我们已经可以搭建Redis集群并使用其部分功能了。今天,本博主基于最新的开发版代码尝试着搭建了一个三节点的Redis集群,这里不妨把过程简单总结一下,希望能对各位看客们有所帮助!(注:本篇博文中的搭建方法是彻彻底底的野路子,Redis官方已正式发布Redis Cluster搭建方法,链接如下:http://redis.io/topics/cluster-tutorial。)
首先,下载最新的Redis开发版源码包。这个再简单不过了,大家既可以去Redis在github上的主页下载,也可以直接运行“git clone git://github.com/antirez/redis.git”克隆整个Redis代码库,当然前提是你已经安装了git。
其次,安装Redis。因为本博主要搭建一个三节点的Redis集群,所以在这三个节点上都要安装好Redis,我们这里姑且将这三个节点命名为Redis Cluster Node1/Node2/Node3吧,安装目录为/usr/local/redis/。
再次,修改Redis配置文件。因为即使是在支持分布式功能的开发版中,Redis配置文件也是默认不打开Redis Cluster的,所以在启动Redis服务器之前,我们需要修改Redis配置文件,打开Redis Cluster。为了简单起见,我们这里只修改与Redis Cluster相关的配置,其他无关的配置均采用默认值,具体修改内容如下:
################################ REDIS CLUSTER###############################
#
# Normal Redis instances can't be part of a Redis Cluster; only nodes that are
# started as cluster nodes can. In order to start a Redis instance as a
# cluster node enable the cluster support uncommenting the following:
#
cluster-enabled yes
# Every cluster node has a cluster configuration file. This file is not
# intended to be edited by hand. It is created and updated by Redis nodes.
# Every Redis Cluster node requires a different cluster configuration file.
# Make sure that instances running in the same system does not have
# overlapping cluster configuration file names.
#
cluster-config-file nodes-6379.conf
# Cluster node timeout is the amount of seconds a node must be unreachable
# for it to be considered in failure state.
# Most other internal time limits are multiplicators of the node timeout.
#
cluster-node-timeout 15
# In order to setup your cluster make sure to read the documentation
# available at http://redis.io web site.
再次,启动三个节点上的Redis服务器。此时,三个Redis服务器节点均会以Redis Cluster的方式开始运行,但并没有自动构建集群,因为三者还处于“我不认识你,你不属于我”的状态,它们每个都是孤零零的Redis节点,或者是只包含了一个节点的集群。我们可以通过Redis客户端连接到服务器查看它们的状态,图一给出了状态查询方法和查询结果,其中cluster nodes命令用于查看当前Redis节点所属的Redis集群中的所有节点,而cluster info则用于查看当前Redis节点所属的Redis集群的整体状态。由图中我们可以看到,Redis集群中仅包含一个Redis节点,也就是当前节点,整个集群的状态是fail。
图一 未形成集群时Redis节点状态
再次,搭建Redis集群。这里所谓的搭建集群,说白了就是让之前启动的三个Redis节点彼此连通,意识到彼此的存在,那么如何做到这一点呢?答案就是cluster meet命令。该命令的作用就是将当前节点主动介绍给另外一个节点认识,图二给出了cluster meet命令的执行方法和执行效果,由图中可知我们使用cluster meet命令分别将Redis Cluster Node1介绍给了Redis Cluster Node2(节点IP地址为192.168.32.3,运行端口为6379)和Redis Cluster Node3(节点IP地址为192.168.32.4,运行端口为6379),之后我们再次查看集群节点和集群状态就可以知道,三个节点已经成功合并到了同一个集群中。
图二 搭建Redis集群
再次,为集群中的Redis节点分配hash slot。通过上面的操作,我们已经将三个各自为政的Redis节点规划到一个相同的集群中,那么我们现在是否就已经完成了集群搭建的所有工作了呢?非也!通过图二中对集群状态的查看我们可以知道,当前集群的状态还是fail,此时的Redis集群是不工作的,无法处理任何Redis命令。那么集群的状态为什么还是fail呢?本博主通过查看官方文档说明找到了原因所在,现摘录原文如下:
The FAIL state for the cluster happens in two cases.
1) If at least one hash slot is not served as the node serving it currently is in FAIL state.
2) If we are not able to reach the majority of masters (that is, if the majorify of masters are simply in PFAIL state, it is enough for the node to enter FAIL mode).
很明显,导致我们的集群处于fail状态的原因不是第二个条,也就是说至少有一个hash slot没有被服务!稍微考虑一下,可不是!何止有一个hash slot没有被服务,压根儿就没有Redis节点为任何hash slot服务!众所周知,Redis Cluster通过hash slot将数据根据主键来分区,所以一条key-value数据会根据算法自动映射到一个hash slot,但是一个hash slot存储在哪个Redis节点上并不是自动映射的,是需要集群管理者自行分配的。那么我们需要为多少个hash slot分配Redis节点呢?根据源码可知是16384个,即我们要将16384个hash slot分配到集群内的三个节点上。Redis中用于分配hash slot的命令有很多,其中包括cluster addslots、cluster delslots和cluster setslot。鉴于我们现在是集群的初始化阶段,所以我们可以选择cluster addslots来分配hash slot,该命令的语法为cluster addslots slot1 ... 。
图三 Redis Cluster Node1上nodes-6379.conf的内容
但是另一个问题又出现了,对于16384个hash slot来说,我们总不能用cluster addslots一个个去分配吧?幸运的是,我们还有另外一种方法来分配hash slot,那就是通过集群配置文件,也就是我们在第三步中配置的cluster-config-file参数,来完成hash slot的分段配置。此时,我们在执行Redis服务器启动的目录下找到名字为nodes-6379.conf的配置文件。图三给出了Redis Cluster Node1上nodes-6379.conf的内容,由图可知该文件中的内容与我们执行cluster nodes命令得到的结果一样,就是对集群中所有Redis节点的描述,而我们需要修改的就是为每个节点添加上hash slot的分配信息。针对Redis Cluster Node1上nodes-6379.conf的内容,修改内容如下:cda76a0a094d2ce624e33bed7f3c75689a4128fd :0 myself,master - 0 0 connected 0-5000(注意是在自身节点的描述,也就是包含了myself那一行的后面追加hash slot的范围)。类似的,Redis Cluster Node2上nodes-6379.conf文件中追加5001-10000,Redis Cluster Node3上nodes-6379.conf文件中追加10001-16383。经过这样的配置后,Redis Cluster Node1负责存储0至5000之间的所有hash slots,Redis Cluster Node2负责存储5001至10000之间的所有hash slots,Redis Cluster Node3负责存储10001至16383的所有hash slots。
保存以上修改,通过Redis客户端将三个Redis服务器重启,再次执行命令cluster nodes和cluster info,我们就可以看到集群的状态已经为ok了(如图四所示),这说明之前对于集群配置文件的修改确实起到了作用。如果经过以上步骤之后集群状态依然是fail的话,可以重新修改一下三个节点的nodes-6379.conf,将其中所有除myself那一行以外的所有配置行都删除,然后再保存重启服务器即可。当然,这里需要说明的一点是,本博主并没有看到任何关于Redis Cluster集群配置文件的官方文档,以上配置方法尽管有效但并不一定是官方推荐的做法!
图四 修改集群配置文件后重启的集群状态
最后,执行Redis命令。在集群状态显示为ok之后,我们就可以像在Redis单机版上一样执行Redis命令了。图五显示了Redis客户端连接到Redis Cluster Node1上执行“set foo bar”命令的执行结果,由图可知主键foo所属的hash slot存储在Redis Cluster Node3上。我们再连接到Redis Cluster Node3,执行相同的命令,正确执行!
图五 执行Redis命令
参考链接:http://redis.io/topics/cluster-spec
http://blog.sina.com.cn/s/blog_48c95a190101ig6a.html
去年本博主发表了一篇关于Redis Cluster搭建的博文,因为当时没有找到任何官方的说明文档,所以只能根据Redis主页上的Redis cluster Specification一步步地摸索出了一种搭建方法,可以说是彻彻底底的野路子。后来自己怕误人子弟,还很自觉地在原文中对这一点作了说明。后来某位网友发来一篇博文的链接:http://no-fucking-idea.com/blog/2012/04/16/setting-up-redis-cluster/,其中就是介绍如何使用redis源码包中的redis-trib.rb工具来实现Redis Cluster的搭建,不仅实现方法异常简单而且发表时间也早了一年有余,由此可知自己的那篇博文确实是闭门造车了。最近Redis官网正式推出了支持Redis Cluster的3.0 Beta版,我在官网上也找到了关于Redis Cluster搭建的tutorial,今天按照上面的步骤尝试了一把,果真是极好的!于是乎赶紧发一篇博文,希望已经被俺前一篇博文毒害的弟兄们早日悬崖勒马,回归正道!
首先,下载支持Redis Cluster的源码包,最方便的当然就是直接下载3.0 Beta版,其链接地址为:https://github.com/antirez/redis/archive/3.0.0-beta1.tar.gz。解压安装后,对Redis进行配置,主要就是将cluster功能打开,与Cluster相关的具体配置如下所示。
cluster-enabled yes
cluster-config-file /home/wqd/work/conf/nodes-6379.conf
cluster-node-timeout 5000
此外,这次通过运行在同一台机器上的三个不同Redis实例来搭建Redis Cluster,所以需要准备三个配置文件,每个配置文件中设置不同的端口。这里姑且将这三个配置文件分别命名为redis-6379.conf、redis-6380.conf和redis-6381.conf,其中的配置也是一样,像pid文件、日志文件、rdb文件、aof文件和集群配置文件等都是通过端口号来区分。在完成对配置文件的修改后,分别启动三个Redis实例,结果如图一所示。
图一 启动三个Redis实例
其次,通过redis-trib.rb工具构建Redis Cluster。通过以上操作我们已经有了三个独立运行的Redis实例,彼此之间各自为政,接下来就是redis-trib.rb工具发挥作用的时候了。在执行该工具之前,一些准备工作是必不可少的,其一就是安装ruby和rubygem,为了方便起见这里推荐安装ruby 1.9.2及之后的版本,这些版本已经包含了rubygem,无需单独安装,安装方法这里就不赘述了。在完成ruby和rubygem的安装之后,我们还需要为ruby安装redis库,安装命令为:gem install redis。在做完了以上这些准备工作后,我们就可以执行redis-trib.rb这个工具了,图二给出了该工具在没有任何参数下的执行结果,其中我们可以了解该工具支持的所有功能,这里就不一一介绍了,官方的tutorial中有详细的介绍。
图二 redis-trib.rb工具使用方法
再次,基于三个实例构建Redis Cluster。上面我们已经看到了redis-trib.rb支持的所有子命令了,而其中用于构建Redis Cluster的子命令就是create。create子命令的参数有两种,host1:port1 ... hostN:portN指定了用于构建Redis Cluster的所有redis实例,而--replicas 则指定了为Redis Cluster中的每个Master节点配备几个Slave节点。关于后一个参数这里需要简单说明一下,那就是Redis Cluster中的节点分为两种:Master节点和Slave节点,一个Master节点可以拥有若干个Slave节点,Master节点上的数据通过异步方式与Slave节点实现数据同步,当Master节点因为某种原因退出集群后,Redis Cluster会自动从该Master节点的Slave节点中选择出一个作为新的Master节点。因此,redis-trib.rb工具的create子命令提供了--args参数来指定集群中的Master节点拥有几个Slave节点,譬如使用6个redis实例构建集群且--args参数值为1,那么整个集群就包含三个Master节点和三个Slave节点,每个Master节点都有一个Slave节点。这里我们只有三个redis实例,所以选择不为Master节点配备Slave节点,创建集群的方法和结果如图三所示,从中可知Redis集群已经构建成功,其中监听6379的实例负责存储0-5460哈希槽,监听6380的实例负责存储5461-10921哈希槽,监听6381的实例负责存储10922-16383哈希槽。
图三 通过redis-trib.rb创建Redis Cluster
如果忘记了Redis Cluster的搭建环境,如集群中有几个节点,这些节点分别负责存储哪些哈希槽,我们可以通过redis-trib.rb工具的check子命令来查看集群信息,该子命令只需要提供集群中任意redis实例的IP地址和端口号即可,图四给出了check子命令的执行方法和结果。
图四 通过redis-trib.rb查看Redis Cluster
最后,通过redis客户端实现对Redis Cluster的读写。当前,redis客户端同样实现了对集群的支持,但使用方法略有不同,即在启动的时候需要添加一个-c参数。图五给出了redis客户端的使用方法和执行结果,从中可以看到当读写Key-Value数据所属的哈希槽存储在别的节点上时,redis客户端会将数据自动重定向到目标节点上,而不是像之前那样(即不带-c参数)返回错误并给出目标节点了事。
图五 通过redis客户端实现对Redis Cluster读写
以上我们简单介绍了通过redis-trib.rb工具构建Redis Cluster的方法与步骤,关于redis-trib.rb工具实际上还有很多实用的功能没有介绍,像reshard、add-node、del-node等等,感兴趣地话推荐大家还是去阅读官方的tutorial,其中有详细的说明和示例。
参考链接:http://no-fucking-idea.com/blog/2012/04/16/setting-up-redis-cluster/
http://redis.io/topics/cluster-tutorial
http://blog.nosqlfan.com/html/3302.html
Redis集群功能说明
作者:nosqlfan on 星期一, 十月 24, 2011 · 2条评论 【阅读:14,306 次】
虽然目前可以通过在客户端做hash的方法来构建Redis集群,但Redis原生的集群支持还是颇受期待。本文是对Redis集群功能官方描述文档的一个翻译,译者是@PPS萝卜同学,也感谢他的投稿分享。
介绍
这篇文档主要是为了说明正在进展中的Redis集群功能。文档主要分为两个部分,前一部分主要介绍我在非稳定分支已完成的代码,后一部分主要介绍还有哪些功能待实现。
本文档所有的说明都有可能在将来由于设计原因而进行更改,而未实现的计划比已实现的功能更有可能会被更改。
本文档包含了所有client library所需要的细节,但是client library的作者们需要提前意识到真正实现的细节在将来很有可能会有变化。
什么是Redis集群?
Redis集群是一个实现分布式并且允许单点故障的Redis高级版本。
Redis集群没有最重要或者说中心节点,这个版本最主要的一个目标是设计一个线性可伸缩(可随意增删节点?)的功能。
Redis集群为了数据的一致性可能牺牲部分允许单点故障的功能,所以当网络故障和节点发生故障时这个系统会尽力去保证数据的一致性和有效性。(这里我们认为节点故障是网络故障的一种特殊情况)
为了解决单点故障的问题,我们同时需要masters 和 slaves。 即使主节点(master)和从节点(slave)在功能上是一致的,甚至说他们部署在同一台服务器上,从节点也仅用以替代故障的主节点。 实际上应该说 如果对从节点没有read-after-write(写并立即读取数据 以免在数据同步过程中无法获取数据)的需求,那么从节点仅接受只读操作。
已实现子集
Redis集群会把所有的单一key存储在非分布式版本的Redis中。对于复合操作比如求并集求交集之类则未实现。
在将来,有可能会增加一种为“Computation Node”的新类型节点。这种节点主要用来处理在集群中multi-key的只读操作,但是对于multi-key的只读操作不会以集群传输到Computation Node节点再进行计算的方式实现。
Redis集群版本将不再像独立版本一样支持多数据库,在集群版本中只有database 0,并且SELECT命令是不可用的。
客户端与服务端在Redis集群版中的约定
在Redis集群版本中,节点有责任/义务保存数据和自身状态,这其中包括把数据(key)映射到正确的节点。所有节点都应该自动探测集群中的其他节点,并且在发现故障节点之后把故障节点的从节点更改为主节点(原文这里有“如果有需要” 可能是指需要设置或者说存在从节点)。
集群节点使用TCP bus和二进制协议进行互联并对任务进行分派。各节点使用gossip 协议发送ping packets给集群其他节点以确定其他节点是否正常工作。cluster bus也可以用来在节点间执行PUB/SUB命令。
当发现集群节点无应答的时候则会使用redirections errors -MOVED and -ASK命令并且会重定向至可用节点。理论上客户端可随意向集群中任意节点发送请求并获得重定向,也就是说客户端实际上并不用关心集群的状态。然而,客户端也可以缓存数据对应的节点这样可以免去服务端进行重定向的工作,这在一定程度上可以提高效率。
Keys分配模式
一个集群可以包含最多4096个节点(但是我们建议最多设置几百个节点)。
所有的主节点会控制4096个key空间的百分比。当集群稳定之后,也就是说不会再更改集群配置(更改配置指的增删节点),那么一个节点将只为一个hash slot服务。(但是服务节点(主节点)可以拥有多个从节点用来防止单点故障)
用来计算key属于哪个hash slot的算法如下:
HASH_SLOT = CRC16(key) mod 4096
Name: XMODEM (also known as ZMODEM or CRC-16/ACORN)
Width: 16 bit
Poly: 1021 (That is actually x^16 + x^12 + x^5 + 1)
Initialization: 0000
Reflect Input byte: False
Reflect Output CRC: False
Xor constant to output CRC: 0000
Output for "123456789": 31C3
这里我们会取CRC16后的12个字节。在我们的测试中,对于4096个slots, CRC16算法最合适。
集群节点特性
在集群中每个节点都拥有唯一的名字。节点名为16进制的160 bit随机数,当节点获取到名字后将被立即启用。节点名将被永久保存到节点设置文件中,除非系统管理员手动删除节点配置文件。
节点名是集群中每个节点的身份证明。在不更改节点ID的情况下是允许修改节点IP和地址的。cluster bus会自动通过gossip协议获取更改后的节点设置。
每个节点可获知其他节点的信息包括:
[*]IP 端口
[*]状态
[*]管理的hash slots
[*]cluster bus最后发送PING的时间
[*]最后接收到PONG的时间
[*]从节点数量
[*]节点ID
无论是主节点还是从节点都可以通过CLUSTER NODES命令来获取以上信息
示例如下:
$ redis-cli cluster nodes
d1861060fe6a534d42d8a19aeb36600e18785e04 :0 myself - 0 1318428930 connected 0-1364
3886e65cc906bfd9b1f7e7bde468726a052d1dae 127.0.0.1:6380 master - 1318428930 1318428931 connected 1365-2729
d289c575dcbc4bdd2931585fd4339089e461a27d 127.0.0.1:6381 master - 1318428931 1318428931 connected 2730-4095
节点交互
所有节点总是允许接受来自cluster bus的连接请求,并且即使请求PING的节点是不可信的也会进行应答。然而,所有来自非集群节点的packets都会被忽略。
只有以下两种情况节点才会把其他节点认为是集群的一部分:
如果一个节点使用 MEET message 介绍自己。MEET message 命令是强制其他节点把自己当成是集群的一部分。只有系统管理员使用 CLUSTER MEET ip port 命令节点才会发送MEET message给其他节点。
另外一种方式就是通过集群节点间的推荐机制。例如 如果A节点知道B节点属于集群,而B知道C节点属于集群,那么B将会发送gossip信息告知A:C是属于集群的。当A获得gossip信息之后就会尝试去连接C。
这意味着,当我们以任意连接方式为集群加入一个节点,集群中所有节点都会自动对新节点建立信任连接。也就是说,集群具备自动识别所有节点的功能,但是这仅发生在当系统管理强制为新节点与集群中任意节点建立信任连接的前提下。
这个机制使得集群系统更加健壮。
当一个节点故障时,其余节点会尝试连接其他所有已知的节点已确定其他节点的健壮性。
被移动数据的重定向
Redis客户端被允许向集群中的任意节点发送命令,其中包括从节点。被访问的节点将会分析命令中所需要的数据(这里仅指请求单个key),并自己通过判断hash slot确定数据存储于哪个节点。
如果被请求节点拥有hash slot数据(这里指请求数据未被迁移过 或者 hash slot在数据迁移后被重新计算过),则会直接返回结果,否则将会返回一个 MOVED 错误。
MOVED 错误如下:
GET x
-MOVED 3999 127.0.0.1:6381
这个错误包括请求的数据所处的 hash slot(3999) 和 数据目前所属的IP:端口。客户端需要通过访问返回的IP:端口获取数据。即使在客户端请求并等待数据返回的过程中,集群配置已被更改(也就是说请求的key在配置文件中所属的节点ID已被重定向至新的IP:端口),目标节点依然会返回一个MOVED错误。
所以虽然在集群中的节点使用节点ID来确定身份,但是map依然是靠hash slot和Redis节点的IP:端口来进行配对。
客户端虽然不被要求但是应该尝试去记住hash slot 3999现在已被转移至127.0.0.1:6381。这样的话,当一个新的命令需要从hash slot 3999获取数据时就可以有更高的几率从hash slot获取到正确的目标节点。
假设集群已经足够的稳定(不增删节点),那么所有的客户端将会拥有一份hash slots对应节点的数据,这可以使整个集群更高效,因为这样每个命令都会直接定向到正确的节点,不需要通过节点寻找节点或者重新计算hash slot对应的节点。
集群不下线更新配置
Redis集群支持线上增删节点。实际上对于系统来说,增加和删除节点在本质上是一样的,因为他们都是把hash slot从一个节点迁移至另外一个节点而已。
增加节点:集群中加入一个空节点并且把hash slot从已存在的节点们移至新节点。
删除节点:集群删除一个已存在节点并且把hash slot分散到已存在的其他节点中。
所以实现这个功能的核心就是迁移slots。实际上从某种观点上来说,hash slot只不过是一堆key的合集,所以Redis集群要做的事情只是在重分片的时候把一堆key从一个实例移动到另外一个实例。
为了清楚的了解这是如何实现的,我们需要先了解一下CLUSTER用来控制slots传输的底层命令。
这些底层命令包括:
CLUSTER ADDSLOTS slot1 ...
CLUSTER DELSLOTS slot1 ...
CLUSTER SETSLOT slot NODE node
CLUSTER SETSLOT slot MIGRATING node
CLUSTER SETSLOT slot IMPORTING node
前两个命令 ADDSLOTS 和 DELSLOTS 是用来在Redis节点上增加/删除slots。当hash slots被赋值之后他们会通过gossip协议在整个集群进行广播(例如:大喊一声 兄弟们 我现在住在X节点 有需要我的以后请到X节点来找我)。当slots被添加,ADDSLOTS 命令是用来通知集群其余所有节点最高效的方法。
SETSLOT 命令是用来给把slot注册给一个特殊的node ID(也就是说ADDSLOTS 和 DELSLOTS 对slots进行操作是不指定节点的 而SETSLOT 是会指定节点的)。另外 SETSLOT 还包含两个特殊的状态 MIGRATING 和 IMPORTING:
当一个slot是以 MIGRATING 状态进行设置,那么目标节点将在确认key存在的前提下接受这个hash slot的所有请求,否则查询会被使用 -ASK 重定向至源节点。
当一个slot是以 IMPORTING 状态进行设置,那么目标节点只接受被设置过ASKING命令的所有请求,否则查询将会通过 -MOVED错误重定向至真正的hash slot所有者。
(MIGRATING 和 IMPORTING 我自己也没太看懂 所以这里不敢保证翻译的没有问题)
当你第一次看到以上内容的时候或许会感到困惑,不过没关系,现在我们来把思路理清楚。假设我们有2个Redis节点,一个叫A,另一个叫B。现在我们希望把hash slot8 从A移动到B,那么我们执行的命令应该如下:
We send B: CLUSTER SETSLOT 8 IMPORTING A
We send A: CLUSTER SETSLOT 8 MIGRATING B
所有来自客户端对hash slot8的查询每次都会被导向至节点A,实际过程如下:
所有对A节点存在的数据查询会由A节点来处理
所有对A节点不存在的数据查询会由B节点来处理
我们会发现我们将会无法在A节点创建任何新的数据,因为会被导向B节点。为了解决这个问题,我们设计了一个叫redis-trib的特殊客户端来保证把A节点所有存在的key迁移至B节点。
我们用以下命令来处理:
CLUSTER GETKEYSINSLOT slot count
上面的命令将会返回hash slot中 count keys。对每一个key,redis-trib都会给A节点发送一个 MIGRATE 命令,这个命令会以一种原子的方式把key从A迁移到B(两个节点在迁移过程中都会被锁定)。
以下展示 MIGRATE 如何工作:
MIGRATE target_host target_port key target_database id timeout
MIGRATE 命令会先连接目标节点,并把目标key序列化后进行传输,当源节点收到OK返回值后会删除源节点上的key删除。所以从这个观点上来看,一个key只能存在A或者B而不会同时存在与A和B。
ASK 重定向
在之前的章节我们说了一下ASK重定向,为什么我们不能只是简单的使用 MOVED 重定向?因为如果使用MOVED命令则有可能会为一个key轮询集群中所有的节点,而ASK命令只询问下一个节点。
ASK是必要的因为在对于hash slot8的下一次查询命令依然是发送给A节点,我们希望客户端先尝试在A节点找数据然后在获取不到的情况下再向B节点请求数据。
然后我们真正的需求是客户端在向A节点请求数据失败后仅尝试向B节点请求数据而不再轮询。节点B将只接受带ASKING命令的IMPORTING 数据查询。
简单说,ASKING 命令给IMPORTING slot添加了一个只轮询一次的标记。
Clients implementations hints
TODO Pipelining: use MULTI/EXEC for pipelining.
TODO Persistent connections to nodes.
TODO hash slot guessing algorithm.
单点故障
节点故障侦测
故障侦测使用以下方法实现:
[*]如果一个节点没有在给定时间内回复PING请求,则该一个节点会被其他节点设置 PFAIL 标志(possible failure 有可能故障)
[*]如果一个节点被设置 PFAIL 标志,那么对目标节点设置 PFAIL 标志的节点会在节点之间互相进行广播通知并通知其他节点发送PING请求
[*]如果有一个节点被设置 PFAIL 标志,并且其他节点也认同其为 PFAIL 状态,那么该节点会被设置为 FAIL 状态(故障)
[*]一旦一个节点被设置 FAIL 标志,那么对故障节点设置 FAIL 标志的节点会通知其余所有节点
所以实际只有大多数节点认同的情况下,一个节点才会被设置为故障状态
(还在努力实现)一旦一个节点被设置为故障,那么其他任何节点收到来自故障节点的PING或者连接请求则会返回“MARK AS FAIL”从而强制故障节点把自己设置为故障
集群状态侦测(目前仅实现了一部分):每当集群配置文件发生变更,所有集群节点都会重新扫描节点列表(这可以是由更改一个hash slot 或者只是一个节点故障造成的)
每个被扫描的节点会返回以下状态中的一个:
[*]FAIL:节点故障
[*]OK:节点正常
这意味着Redis集群被设计为有能力拒绝对故障节点的查询。然而这里有一个特例,就是一个节点从被设置为 PFAIL 到被设置为 FAIL 是有时间差的,如果仅仅是被设置为 PFAIL 还是有可能对该节点尝试连接
另外,Redis集群将不再支持MULTI/EXEC批量方法
从节点推举制度(未实现)
每个主节点可以拥有0个或者多个从节点。当主节点发生故障的时候,从节点有责任/义务推举自己成为主节点。假设我们有A1,A2,A3三个节点,A1是主节点并且A2和A3为A1的从节点
如果A1发生故障并且长时间未回复PING请求,那么其他节点将会将A1标记为故障节点。当这种情况发生的时候,第一个从节点将会尝试推举自己为主节点。
定义第一个从节点非常简单。取所有从节点中节点ID最小的那个。如果第一个从节点也被标记为故障,那么就由第二个从节点推举自己,以此类推。
实际流程是:集群配置被变更(节点故障导致的),故障节点所有的从节点检测自己是否是第一个从节点。从节点在升级为主节点后会通知其他节点更改配置
保护模式(未实现)
如果部分节点由于网络原因被隔离(比如断网),则这些节点会停止判断其他节点是否正常,而会开始从节点推举或者其他操作去更改集群配置。为了防止这种情况发生,节点间一旦发现大部分节点在一段时间内被标记为 PFAIL 或者 FAIL 状态则会立即让集群启动保护模式以阻止集群采取任何行动(更改配置)。
一旦集群状态恢复正常则保护模式会被取消
主节点多数原则(未完成)
对于发生网络故障的情况,2个或者更多的分片有能力处理所有的hash slots。而这会影响集群数据的一致性,所以网络故障应该导致0或者只有1个分区可用。
为了强制此规则生效,所有符合主节点多数原则的节点应该强制不处理任何命令。
Publish/Subscribe(已实现,但是会重定义)
在一个Redis集群中,所有节点都被允许订阅其他节点或者对其他节点进行广播。集群系统会保证所有的广播通知给所有节点。
目前的实现仅仅是简单的一一进行广播,但是在某种程度上广播应该使用bloom filters或者其他算法进行优化。
另外,关于Redis Cluster的一些信息,你还可以参考本站较早的文章:《听 antirez 讲 Redis Cluster》(视频+PPT)
http://quietmadman.blog.iyunv.com/3269500/1553232
这里使用的 redis 版本是 redis-3.0.0-beta8(2.9.57)来安装和配置的。
整个安装和配置过程都是参考的 redis 官方的 cluster 手册: http://redis.io/topics/cluster-tutorial
其他的一些 API 和 redis cluster 相关的开源项目有:
https://github.com/xetorthio/jedis
https://github.com/antirez/redis-rb-cluster
https://github.com/Grokzen/redis-py-cluster
在安装配置之前对 redis cluster 有了一点初步的了解:
+ 每一个 redis cluster 节点都至少需要两个 tcp 连接,一个是用于为 client 服务的监听端口(如: 6379),另一个则用于为 cluster 节点通信提供的通道(cluster bus,如:6379+10000 = 16379);
+ cluster 节点之间传输的协议为 binary protocol,主要用于 故障检测(failure detection),配置更新(configuration update),故障转移授权(failover authorization)等等;
+ Redis Cluster 并没有采用一致性 hash 算法来对 data 进行 sharding,而是采用了简单的 hash slot 机制来实现 -- 计算给定 key 的 hash slot 槽位 -- CRC16(key) % 16384 ;
+ Redis Cluster 最多能够支持 16384 个节点;
+ 为了把所有 slot 占满,当节点少的时候,需要每一个节点分配一定范围的 slots,如:
- Node A contains hash slots from 0 to 5500
- Node B contains hash slots from 5501 to 11000
- Node C contains hash slots from 11001 to 16384
当有新的 node 进来,那需要从 A,B,C 中移出一些 slots 分配给 D。
当有节点要退出(如:A),那就需要将 A 上的 slots 再分配给 B 和 C。
+ Redis Cluster 支持 master-slave 模型来实现高可用性,一个节点退出,可以再次选举出该节点的某一个 slave 节点作为新的 master 节点来服务;
下面开始安装配置:
1,创建本地测试目录:
1
$ mkdir -p /root/test/redis-cluster
2,本次测试以在同台主机上测试集群,端口从 7000~7005 的 6 个节点
1
2
$ cd /root/test/redis-cluster
$ mkdir 7000 7001 7002 7003 7004 7005
3,在每个节点目录下分别创建 redis.conf 配置文件,这里的配置文件内容都如下(端口除外):
1
2
3
4
5
port 7000
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
appendonly yes
创建好后,结构如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# tree
.
├── 7000
│ └── redis.conf
├── 7001
│ └── redis.conf
├── 7002
│ └── redis.conf
├── 7003
│ └── redis.conf
├── 7004
│ └── redis.conf
└── 7005
└── redis.conf
6 directories, 6 files
4,启动这 6 个节点,如下示例:
1
2
$ cd 7000/
$ redis-server redis.conf
运行后在每个节点的目录下可以看到新增了 nodes.conf 配置,如 7000 节点的配置如下:
1
2
123ed65d59ff22370f2f09546f410d31207789f6 :0 myself,master - 0 0 0 connected
vars currentEpoch 0 lastVoteEpoch 0
其中 123ed65d59ff22370f2f09546f410d31207789f6 为每个节点给自己分配的 NodeID,该 ID 在之后的 cluster 环境中唯一的标识该节点实例,每一个节点也都是通过这个 ID 来表示其他的节点的。
5,查看网络监听,可以看到同时监听 700* + 10000
1
2
3
4
5
6
7
8
9
10
11
12
tcp 0 0 :::17003 :::* LISTEN 10614/redis-server
tcp 0 0 :::17004 :::* LISTEN 10655/redis-server
tcp 0 0 :::17005 :::* LISTEN 10696/redis-server
tcp 0 0 :::7000 :::* LISTEN 10483/redis-server
tcp 0 0 :::7001 :::* LISTEN 10549/redis-server
tcp 0 0 :::7002 :::* LISTEN 10573/redis-server
tcp 0 0 :::7003 :::* LISTEN 10614/redis-server
tcp 0 0 :::7004 :::* LISTEN 10655/redis-server
tcp 0 0 :::7005 :::* LISTEN 10696/redis-server
tcp 0 0 :::17000 :::* LISTEN 10483/redis-server
tcp 0 0 :::17001 :::* LISTEN 10549/redis-server
tcp 0 0 :::17002 :::* LISTEN 10573/redis-server
6,下面开始搭建集群,这里使用源码中自带的 ruby 脚本 redis-trib.rb 来创建集群,因此首先确保系统已经安装了最新版本的 ruby 环境
redis-trib.rb 除了创建集群,还可以 check,reshard 一个存在的 cluster。
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
$ ./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
>>> Creating cluster
Connecting to node 127.0.0.1:7000: OK
Connecting to node 127.0.0.1:7001: OK
... ...
>>> Performing hash slots allocation on 6 nodes...
Using 3 masters:
127.0.0.1:7000
127.0.0.1:7001
127.0.0.1:7002
Adding replica 127.0.0.1:7003 to 127.0.0.1:7000
Adding replica 127.0.0.1:7004 to 127.0.0.1:7001
Adding replica 127.0.0.1:7005 to 127.0.0.1:7002
// M 开头的为 master 节点
// S 开头的为 slave 节点,同时 replicates 后面的 NodeID 为 master 节点ID
M: 123ed65d59ff22370f2f09546f410d31207789f6 127.0.0.1:7000
slots:0-5460 (5461 slots) master
M: 82578e8ec9747e46cbb4b8cc2484c71b9b2c91f4 127.0.0.1:7001
slots:5461-10922 (5462 slots) master
M: f5bdda1518cd3826100a30f5953ed82a5861ed48 127.0.0.1:7002
slots:10923-16383 (5461 slots) master
S: 35e0f6fdadbf81a00a1d6d1843698613e653867b 127.0.0.1:7003
replicates 123ed65d59ff22370f2f09546f410d31207789f6
S: 61dfb1055760d5dcf6519e35435d60dc5b207940 127.0.0.1:7004
replicates 82578e8ec9747e46cbb4b8cc2484c71b9b2c91f4
S: bfc910f924d772fe03d9fe6a19aabd73d5730d26 127.0.0.1:7005
replicates f5bdda1518cd3826100a30f5953ed82a5861ed48
// 回复 yes 表示接受 redis-trib 的 master-slave 的配置
Can I set the above configuration? (type 'yes' to accept): yes
>>> Nodes configuration updated
>>> Assign a different config epoch to each node
>>> Sending CLUSTER MEET messages to join the cluster
// 等待 cluster 的 node 加入
Waiting for the cluster to join...
// 加入后开始给各个 node 分配 slots 槽位
// 如:127.0.0.1:7000 的 master 节点分配了 0-5460 的槽位
>>> Performing Cluster Check (using node 127.0.0.1:7000)
M: 123ed65d59ff22370f2f09546f410d31207789f6 127.0.0.1:7000
slots:0-5460 (5461 slots) master
M: 82578e8ec9747e46cbb4b8cc2484c71b9b2c91f4 127.0.0.1:7001
slots:5461-10922 (5462 slots) master
M: f5bdda1518cd3826100a30f5953ed82a5861ed48 127.0.0.1:7002
slots:10923-16383 (5461 slots) master
M: 35e0f6fdadbf81a00a1d6d1843698613e653867b 127.0.0.1:7003
slots: (0 slots) master
replicates 123ed65d59ff22370f2f09546f410d31207789f6
M: 61dfb1055760d5dcf6519e35435d60dc5b207940 127.0.0.1:7004
slots: (0 slots) master
replicates 82578e8ec9747e46cbb4b8cc2484c71b9b2c91f4
M: bfc910f924d772fe03d9fe6a19aabd73d5730d26 127.0.0.1:7005
slots: (0 slots) master
replicates f5bdda1518cd3826100a30f5953ed82a5861ed48
All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
All 16384 slots covered.
#
下面为使用 cluster nodes 和 slots 命令查看节点以及 slot 的信息:
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
27
28
29
30
31
32
$ redis-cli -c -p 7000
// 查看节点分布信息
127.0.0.1:7000> cluster nodes
35e0f6fdadbf81a00a1d6d1843698613e653867b 127.0.0.1:7003 slave 123ed65d59ff22370f2f09546f410d31207789f6 0 1410835785216 4 connected
61dfb1055760d5dcf6519e35435d60dc5b207940 127.0.0.1:7004 slave 82578e8ec9747e46cbb4b8cc2484c71b9b2c91f4 0 1410835784715 5 connected
82578e8ec9747e46cbb4b8cc2484c71b9b2c91f4 127.0.0.1:7001 master - 0 1410835786217 2 connected 5461-10922
123ed65d59ff22370f2f09546f410d31207789f6 127.0.0.1:7000 myself,master - 0 0 1 connected 0-5460
bfc910f924d772fe03d9fe6a19aabd73d5730d26 127.0.0.1:7005 slave f5bdda1518cd3826100a30f5953ed82a5861ed48 0 1410835786717 6 connected
f5bdda1518cd3826100a30f5953ed82a5861ed48 127.0.0.1:7002 master - 0 1410835785715 3 connected 10923-16383
127.0.0.1:7000>
// 查看 slots 分布信息
127.0.0.1:7000> cluster slots
1) 1) (integer) 5461 // slots 的其实槽位索引
2) (integer) 10922 // slots 的结束槽位索引
// 即
3) 1) "127.0.0.1"
2) (integer) 7001 // master
4) 1) "127.0.0.1"
2) (integer) 7004 // 对应的 slave 节点
2) 1) (integer) 0
2) (integer) 5460
3) 1) "127.0.0.1"
2) (integer) 7000
4) 1) "127.0.0.1"
2) (integer) 7003
3) 1) (integer) 10923
2) (integer) 16383
3) 1) "127.0.0.1"
2) (integer) 7002
4) 1) "127.0.0.1"
2) (integer) 7005
127.0.0.1:7000>
本文出自 “安静的疯子” 博客,请务必保留此出处http://quietmadman.blog.iyunv.com/3269500/1553232
http://blog.iyunv.com/freebird_lb/article/details/7778999
Redis-2.4.15目前没有提供集群的功能,Redis作者在博客中说将在3.0中实现集群机制。目前Redis实现集群的方法主要是采用一致性哈稀分片(Shard),将不同的key分配到不同的redis server上,达到横向扩展的目的。下面来介绍一种比较常用的分布式场景:
在读写操作比较均匀且实时性要求较高,可以用下图的分布式模式:
在读操作远远多于写操作时,可以用下图的分布式模式:
对于一致性哈稀分片的算法,Jedis-2.0.0已经提供了,下面是使用示例代码(以ShardedJedisPool为例):
package com.jd.redis.client;
import java.util.ArrayList;
import java.util.List;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.JedisShardInfo;
import redis.clients.jedis.ShardedJedis;
import redis.clients.jedis.ShardedJedisPool;
import redis.clients.util.Hashing;
import redis.clients.util.Sharded;
publicclass RedisShardPoolTest {
static ShardedJedisPoolpool;
static{
JedisPoolConfig config =new JedisPoolConfig();//Jedis池配置
config.setMaxActive(500);//最大活动的对象个数
config.setMaxIdle(1000 * 60);//对象最大空闲时间
config.setMaxWait(1000 * 10);//获取对象时最大等待时间
config.setTestOnBorrow(true);
String hostA = "10.10.224.44";
int portA = 6379;
String hostB = "10.10.224.48";
int portB = 6379;
List jdsInfoList =new ArrayList(2);
JedisShardInfo infoA = new JedisShardInfo(hostA, portA);
infoA.setPassword("redis.360buy");
JedisShardInfo infoB = new JedisShardInfo(hostB, portB);
infoB.setPassword("redis.360buy");
jdsInfoList.add(infoA);
jdsInfoList.add(infoB);
pool =new ShardedJedisPool(config, jdsInfoList, Hashing.MURMUR_HASH,
Sharded.DEFAULT_KEY_TAG_PATTERN);
}
/**
* @param args
*/
publicstaticvoid main(String[] args) {
for(int i=0; i
页:
[1]