Redis Cluster 实践

2017-08-28 14:23:22来源:oschina作者:京东技术人点击

分享
一:关于redis cluster1: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 supportup 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是通过集群中超过半数的master节点检测失效时才生效.


(3)客户端与redis节点直连,不需要中间proxy层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可


(4)redis-cluster把所有的物理节点映射到[0-16383]slot上,cluster 负责维护node<->slot<->key

2) redis-cluster选举:容错


(1)领着选举过程是集群中所有master参与,如果半数以上master节点与故障节点通信超过(cluster-node-timeout),认为该节点故障,自动触发故障转移操作.


(2):什么时候整个集群不可用(cluster_state:fail)?


a:如果集群任意master挂掉,且当前master没有slave.集群进入fail状态,也可以理解成集群的slot映射[0-16383]不完成时进入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
makeinstall

(2)安装ruby:version(1.9.2)

Java代码
收藏代码

#ruby1.9.2
cd/path/ruby
./configure-prefix=/usr/local/ruby
make
makeinstall
sudocpruby/usr/local/bin

(3)安装rubygem:version(1.8.16)

Java代码
收藏代码

#rubygems-1.8.16.tgz
cd/path/gem
sudorubysetup.rb
sudocpbin/gem/usr/local/bin

(4)安装gem-redis:version(3.0.0)

Java代码
收藏代码

geminstallredis--version3.0.0
#由于源的原因,可能下载失败,就手动下载下来安装
#download地址:http://rubygems.org/gems/redis/versions/3.0.0
geminstall-l/data/soft/redis-3.0.0.gem

(5)安装redis-cluster

Java代码
收藏代码

cd/path/redis
make
sudocp/opt/redis/src/redis-server/usr/local/bin
sudocp/opt/redis/src/redis-cli/usr/local/bin
sudocp/opt/redis/src/redis-trib.rb/usr/local/bin
2:配置redis cluster1)redis配置文件结构:

使用包含(include)把通用配置和特殊配置分离,方便维护.

2)redis通用配置.

Java代码
收藏代码

#GENERAL
daemonizeno
tcp-backlog511
timeout0
tcp-keepalive0
loglevelnotice
databases16
dir/opt/redis/data
slave-serve-stale-datayes
#slave只读
slave-read-onlyyes
#notusedefault
repl-disable-tcp-nodelayyes
slave-priority100
#打开aof持久化
appendonlyyes
#每秒一次aof写
appendfsynceverysec
#关闭在aofrewrite的时候对新的写操作进行fsync
no-appendfsync-on-rewriteyes
auto-aof-rewrite-min-size64mb
lua-time-limit5000
#打开redis集群
cluster-enabledyes
#节点互连超时的阀值
cluster-node-timeout15000
cluster-migration-barrier1
slowlog-log-slower-than10000
slowlog-max-len128
notify-keyspace-events""
hash-max-ziplist-entries512
hash-max-ziplist-value64
list-max-ziplist-entries512
list-max-ziplist-value64
set-max-intset-entries512
zset-max-ziplist-entries128
zset-max-ziplist-value64
activerehashingyes
client-output-buffer-limitnormal000
client-output-buffer-limitslave256mb64mb60
client-output-buffer-limitpubsub32mb8mb60
hz10
aof-rewrite-incremental-fsyncyes

3)redis特殊配置.

Java代码
收藏代码

#包含通用配置
include/opt/redis/redis-common.conf
#监听tcp端口
port6379
#最大可用内存
maxmemory100m
#内存耗尽时采用的淘汰策略:
#volatile-lru->removethekeywithanexpiresetusinganLRUalgorithm
#allkeys-lru->removeanykeyaccordinglytotheLRUalgorithm
#volatile-random->removearandomkeywithanexpireset
#allkeys-random->removearandomkey,anykey
#volatile-ttl->removethekeywiththenearestexpiretime(minorTTL)
#noeviction->don'texpireatall,justreturnanerroronwriteoperations
maxmemory-policyallkeys-lru
#aof存储文件
appendfilename"appendonly-6379.aof"
#不开启rdb存储,只用于添加slave过程
dbfilenamedump-6379.rdb
#cluster配置文件(启动自动生成)
cluster-config-filenodes-6379.conf
#部署在同一机器的redis实例,把auto-aof-rewrite搓开,因为cluster环境下内存占用基本一致.
#防止同意机器下瞬间fork所有redis进程做aofrewrite,占用大量内存
auto-aof-rewrite-percentage80-100

3:cluster 操作

cluster集群相关命令,更多redis相关命令见文档:http://redis.readthedocs.org/en/latest/




Java代码
收藏代码

集群
CLUSTERINFO打印集群的信息
CLUSTERNODES列出集群当前已知的所有节点(node),以及这些节点的相关信息。
节点
CLUSTERMEET将ip和port所指定的节点添加到集群当中,让它成为集群的一份子。
CLUSTERFORGET从集群中移除node_id指定的节点。
CLUSTERREPLICATE将当前节点设置为node_id指定的节点的从节点。
CLUSTERSAVECONFIG将节点的配置文件保存到硬盘里面。
槽(slot)
CLUSTERADDSLOTS[slot...]将一个或多个槽(slot)指派(assign)给当前节点。
CLUSTERDELSLOTS[slot...]移除一个或多个槽对当前节点的指派。
CLUSTERFLUSHSLOTS移除指派给当前节点的所有槽,让当前节点变成一个没有指派任何槽的节点。
CLUSTERSETSLOTNODE将槽slot指派给node_id指定的节点,如果槽已经指派给另一个节点,那么先让另一个节点删除该槽>,然后再进行指派。
CLUSTERSETSLOTMIGRATING将本节点的槽slot迁移到node_id指定的节点中。
CLUSTERSETSLOTIMPORTING从node_id指定的节点中导入槽slot到本节点。
CLUSTERSETSLOTSTABLE取消对槽slot的导入(import)或者迁移(migrate)。

CLUSTERKEYSLOT计算键key应该被放置在哪个槽上。
CLUSTERCOUNTKEYSINSLOT返回槽slot目前包含的键值对数量。
CLUSTERGETKEYSINSLOT返回count个slot槽中的键。

4:redis cluster 运维操作1)初始化并构建集群

(1)启动集群相关节点(必须是空节点,beta3后可以是有数据的节点),指定配置文件和输出日志




Java代码
收藏代码

redis-server/opt/redis/conf/redis-6380.conf>/opt/redis/logs/redis-6380.log2>&1&
redis-server/opt/redis/conf/redis-6381.conf>/opt/redis/logs/redis-6381.log2>&1&
redis-server/opt/redis/conf/redis-6382.conf>/opt/redis/logs/redis-6382.log2>&1&
redis-server/opt/redis/conf/redis-7380.conf>/opt/redis/logs/redis-7380.log2>&1&
redis-server/opt/redis/conf/redis-7381.conf>/opt/redis/logs/redis-7381.log2>&1&
redis-server/opt/redis/conf/redis-7382.conf>/opt/redis/logs/redis-7382.log2>&1&

(2):使用自带的ruby工具(redis-trib.rb)构建集群




Java代码
收藏代码

#redis-trib.rb的create子命令构建
#--replicas则指定了为RedisCluster中的每个Master节点配备几个Slave节点
#节点角色由顺序决定,先master之后是slave(为方便辨认,slave的端口比master大1000)
redis-trib.rbcreate--replicas110.10.34.14:638010.10.34.14:638110.10.34.14:638210.10.34.14:738010.10.34.14:738110.10.34.14:7382

(3):检查集群状态



Java代码
收藏代码

#redis-trib.rb的check子命令构建
#ip:port可以是集群的任意节点
redis-trib.rbcheck10.10.34.14:6380

最后输出如下信息,没有任何警告或错误,表示集群启动成功并处于ok状态

Java代码
收藏代码

[OK]Allnodesagreeaboutslotsconfiguration.
>>>Checkforopenslots...
>>>Checkslotscoverage...
[OK]All16384slotscovered.

2):添加新master节点

(1)添加一个master节点:创建一个空节点(empty node),然后将某些slot移动到这个空节点上,这个过程目前需要人工干预


a):根据端口生成配置文件(ps:establish_config.sh是我自己写的输出配置脚本)




Java代码
收藏代码

shestablish_config.sh6386>conf/redis-6386.conf


b):启动节点




Java代码
收藏代码

redis-server/opt/redis/conf/redis-6386.conf>/opt/redis/logs/redis-6386.log2>&1&

c):加入空节点到集群add-node 将一个节点添加到集群里面,第一个是新节点ip:port, 第二个是任意一个已存在节点ip:port




Java代码
收藏代码

redis-trib.rbadd-node10.10.34.14:638610.10.34.14:6381

node:新节点没有包含任何数据, 因为它没有包含任何slot。新加入的加点是一个主节点, 当集群需要将某个从节点升级为新的主节点时, 这个新节点不会被选中,同时新的主节点因为没有包含任何slot,不参加选举和failover。



d):为新节点分配slot




Java代码
收藏代码

redis-trib.rbreshard10.10.34.14:6386
#根据提示选择要迁移的slot数量(ps:这里选择500)
Howmanyslotsdoyouwanttomove(from1to16384)?500
#选择要接受这些slot的node-id
WhatisthereceivingnodeID?f51e26b5d5ff74f85341f06f28f125b7254e61bf
#选择slot来源:
#all表示从所有的master重新分配,
#或者数据要提取slot的master节点id,最后用done结束
PleaseenterallthesourcenodeIDs.
Type'all'touseallthenodesassourcenodesforthehashslots.
Type'done'onceyouenteredallthesourcenodesIDs.
Sourcenode#1:all
#打印被移动的slot后,输入yes开始移动slot以及对应的数据.
#Doyouwanttoproceedwiththeproposedreshardplan(yes/no)?yes
#结束

3):添加新的slave节点

a):前三步操作同添加master一样


b)第四步:redis-cli连接上新节点shell,输入命令:cluster replicate 对应master的node-id




Java代码
收藏代码

clusterreplicate2b9ebcbd627ff0fd7a7bbcc5332fb09e72788835


注意:在线添加slave 时,需要bgsave整个master数据,并传递到slave,再由 slave加载rdb文件到内存,rdb生成和传输的过程中消耗Master大量内存和网络IO,以此不建议单实例内存过大,线上小心操作。


例如本次添加slave操作产生的rdb文件




Java代码
收藏代码

-rw-r--r--1rootroot34946Apr1718:23dump-6386.rdb
-rw-r--r--1rootroot34946Apr1718:23dump-7386.rdb

4):在线reshard 数据:

对于负载/数据不均匀的情况,可以在线reshard slot来解决,方法与添加新master的reshard一样,只是需要reshard的master节点是已存在的老节点.

5):删除一个slave节点

Java代码
收藏代码

#redis-tribdel-nodeip:port''
redis-trib.rbdel-node10.10.34.14:7386'c7ee2fca17cb79fe3c9822ced1d4f6c5e169e378'

6):删除一个master节点

a):删除master节点之前首先要使用reshard移除master的全部slot,然后再删除当前节点


(redis-trib.rb一次只能把下线节点的slot迁移到一个节点上,如果需要均衡的迁移到其它节点需要执行多次reshard命令)




Java代码
收藏代码

#把10.10.34.14:6386节点slot和数据迁移到10.10.34.14:6380上
redis-trib.rbreshard10.10.34.14:6380
#根据提示选择要迁移的slot数量(ps:这里选择500)
Howmanyslotsdoyouwanttomove(from1to16384)?500(被删除master的所有slot数量)
#选择要接受这些slot的node-id(10.10.34.14:6380)
WhatisthereceivingnodeID?c4a31c852f81686f6ed8bcd6d1b13accdc947fd2(ps:10.10.34.14:6380的node-id)
PleaseenterallthesourcenodeIDs.
Type'all'touseallthenodesassourcenodesforthehashslots.
Type'done'onceyouenteredallthesourcenodesIDs.
Sourcenode#1:f51e26b5d5ff74f85341f06f28f125b7254e61bf(被删除master的node-id)
Sourcenode#2:done
#打印被移动的slot后,输入yes开始移动slot以及对应的数据.
#Doyouwanttoproceedwiththeproposedreshardplan(yes/no)?yes


b):删除空master节点




Java代码
收藏代码

redis-trib.rbdel-node10.10.34.14:6386'f51e26b5d5ff74f85341f06f28f125b7254e61bf'


三:redis cluster 客户端(Jedis)1:客户端基本操作使用



Java代码
收藏代码

privatestaticBinaryJedisClusterjc;
static{
//只给集群里一个实例就可以
SetjedisClusterNodes=newHashSet();
jedisClusterNodes.add(newHostAndPort("10.10.34.14",6380));
jedisClusterNodes.add(newHostAndPort("10.10.34.14",6381));
jedisClusterNodes.add(newHostAndPort("10.10.34.14",6382));
jedisClusterNodes.add(newHostAndPort("10.10.34.14",6383));
jedisClusterNodes.add(newHostAndPort("10.10.34.14",6384));
jedisClusterNodes.add(newHostAndPort("10.10.34.14",7380));
jedisClusterNodes.add(newHostAndPort("10.10.34.14",7381));
jedisClusterNodes.add(newHostAndPort("10.10.34.14",7382));
jedisClusterNodes.add(newHostAndPort("10.10.34.14",7383));
jedisClusterNodes.add(newHostAndPort("10.10.34.14",7384));
jc=newBinaryJedisCluster(jedisClusterNodes);
}
@Test
publicvoidtestBenchRedisSet()throwsException{
finalStopwatchstopwatch=newStopwatch();
Listlist=buildBlogVideos();
for(inti=0;i<1000;i++){
Stringkey="key:"+i;
stopwatch.start();
byte[]bytes1=protostuffSerializer.serialize(list);
jc.setex(key,60*60,bytes1);
stopwatch.stop();
}
System.out.println("time="+stopwatch.toString());
}

2:redis-cluster客户端的一些坑.

1)cluster环境下slave默认不接受任何读写操作,在slave执行readonly命令后,可执行读操作


2)client端不支持多key操作(mget,mset等),但当keys集合对应的slot相同时支持mget操作见:hash_tag


3)不支持多数据库,只有一个db,select 0。


4)JedisCluster没有针对byte[]的API,需要自己扩展(附件是我加的基于byte[]的BinaryJedisCluster api)


目前"Jedis-3.0.0-SNAPSHOT"已支持BinaryJedisCluster和基于hash_tag的mget操作.

最新文章

123

最新摄影

微信扫一扫

第七城市微信公众平台