redis 快速入门-集群-客户端(Jedis)测试

工具及软件

 1. linux : ubuntu 12.04

 2. redis-3.2.8 目前最新稳定版本

3. windows & redis-x64-3.2.00 


Redis的优点

以下是Redis的一些优点。

  • 异常快 - Redis非常快,每秒可执行大约110000次的设置(SET)操作,每秒大约可执行81000次的读取/获取(GET)操作。
  • 支持丰富的数据类型 - Redis支持开发人员常用的大多数数据类型,例如列表,集合,排序集和散列等等。这使得Redis很容易被用来解决各种问题,因为我们知道哪些问题可以更好使用地哪些数据类型来处理解决。
  • 操作具有原子性 - 所有Redis操作都是原子操作,这确保如果两个客户端并发访问,Redis服务器能接收更新的值。
  • 多实用工具 - Redis是一个多实用工具,可用于多种用例,如:缓存,消息队列(Redis本地支持发布/订阅),应用程序中的任何短期数据,例如,web应用程序中的会话,网页命中计数等。

Redis与其他键值存储系统

  • Redis是键值数据库系统的不同进化路线,它的值可以包含更复杂的数据类型,可在这些数据类型上定义原子操作。

  • Redis是一个内存数据库,但在磁盘数据库上是持久的,因此它代表了一个不同的权衡,在这种情况下,在不能大于存储器(内存)的数据集的限制下实现非常高的写和读速度。

  • 内存数据库的另一个优点是,它与磁盘上的相同数据结构相比,复杂数据结构在内存中存储表示更容易操作。 因此,Redis可以做很少的内部复杂性。


1. Redis环境安装配置

     

在Ubuntu上安装Redis

       linux redis 下载地址:点我下载

然后执行命令

   

root@finder01:/finder# cd redis/

root@finder01:/finder/redis# make


启动默认配置

root@finder01:/finder/redis# ./src/redis-server   windows==> redis-server.exe

也可以通过启动参数告诉redis使用指定配置文件使用下面命令启动

./redis-server redis.conf  在我所用的 3.2.8版本中 默认不会加载 redis.conf文件, 需要手动指定 



看到 以下 命令 表示启动成功

111411:M 20 Mar 11:29:33.357 * The server is now ready to accept connections on port 6379

检查redis是否正常工作

   

root@finder01:/finder/redis# src/redis-cli  或者 redis-cli.exe -h 127.0.0.1 -p 6379 
127.0.0.1:6379> set foo bar
OK
127.0.0.1:6379> get foo
"bar"
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> 



redis-server启用 密码

修改  redis.conf中   requirepass "mypass"

客户端

redis-cli -h 127.0.0.1 -p 6379 -a "mypass" 

或者使用  auth "mypass"



在上面的提示中,127.0.0.1是计算机的IP地址,6379是运行Redis服务器的端口.这表明Redis已成功在您的计算机上安装了

配置

使用  *  号获取所有配置项:

127.0.0.1:6379> CONFIG GET *
  1) "dbfilename"
  2) "dump.rdb"
  3) "requirepass"
  4) ""
  5) "masterauth"
  6) ""
  7) "unixsocket"
  8) ""
  9) "logfile"
 10) ""
 11) "pidfile"
 12) ""
 13) "slave-announce-ip"
 14) ""
 15) "maxmemory"
 16) "0"
 17) "maxmemory-samples"
 18) "5"
 19) "timeout"
 20) "0"
 21) "auto-aof-rewrite-percentage"
 22) "100"
 23) "auto-aof-rewrite-min-size"
 24) "67108864"
 25) "hash-max-ziplist-entries"
 26) "512"
 27) "hash-max-ziplist-value"
 28) "64"
 29) "list-max-ziplist-size"
 30) "-2"
 31) "list-compress-depth"
 32) "0"
 33) "set-max-intset-entries"
 34) "512"
 35) "zset-max-ziplist-entries"
 36) "128"
 37) "zset-max-ziplist-value"
 38) "64"
 39) "hll-sparse-max-bytes"
 40) "3000"
 41) "lua-time-limit"
 42) "5000"
 43) "slowlog-log-slower-than"
 44) "10000"
 45) "latency-monitor-threshold"
 46) "0"
 47) "slowlog-max-len"
 48) "128"
 49) "port"
 50) "6379"
 51) "tcp-backlog"
 52) "511"
 53) "databases"
 54) "16"
 55) "repl-ping-slave-period"
 56) "10"
 57) "repl-timeout"
 58) "60"
 59) "repl-backlog-size"
 60) "1048576"
 61) "repl-backlog-ttl"
 62) "3600"
 63) "maxclients"
 64) "10000"
 65) "watchdog-period"
 66) "0"
 67) "slave-priority"
 68) "100"
 69) "slave-announce-port"
 70) "0"
 71) "min-slaves-to-write"
 72) "0"
 73) "min-slaves-max-lag"
 74) "10"
 75) "hz"
 76) "10"
 77) "cluster-node-timeout"
 78) "15000"
 79) "cluster-migration-barrier"
 80) "1"
 81) "cluster-slave-validity-factor"
 82) "10"
 83) "repl-diskless-sync-delay"
 84) "5"
 85) "tcp-keepalive"
 86) "300"
 87) "cluster-require-full-coverage"
 88) "yes"
 89) "no-appendfsync-on-rewrite"
 90) "no"
 91) "slave-serve-stale-data"
 92) "yes"
 93) "slave-read-only"
 94) "yes"
 95) "stop-writes-on-bgsave-error"
 96) "yes"
 97) "daemonize"
 98) "no"
 99) "rdbcompression"
100) "yes"
101) "rdbchecksum"
102) "yes"
103) "activerehashing"
104) "yes"
105) "protected-mode"
106) "yes"
107) "repl-disable-tcp-nodelay"
108) "no"
109) "repl-diskless-sync"
110) "no"
111) "aof-rewrite-incremental-fsync"
112) "yes"
113) "aof-load-truncated"
114) "yes"
115) "maxmemory-policy"
116) "noeviction"
117) "loglevel"
118) "notice"
119) "supervised"
120) "no"
121) "appendfsync"
122) "everysec"
123) "syslog-facility"
124) "local0"
125) "appendonly"
126) "no"
127) "dir"
128) "/finder/redis"
129) "save"
130) "3600 1 300 100 60 10000"
131) "client-output-buffer-limit"
132) "normal 0 0 0 slave 268435456 67108864 60 pubsub 33554432 8388608 60"
133) "unixsocketperm"
134) "0"
135) "slaveof"
136) ""
137) "notify-keyspace-events"
138) ""
139) "bind"
140) ""
获取指定配置项
127.0.0.1:6379> CONFIG GET loglevel
1) "loglevel"
2) "notice"

编辑配置


你可以通过修改 redis.conf 文件或使用 CONFIG set 命令来修改配置。

参数说明

redis.conf 配置项说明如下:

1. Redis默认不是以守护进程的方式运行,可以通过该配置项修改,使用yes启用守护进程

    daemonize no

2. 当Redis以守护进程方式运行时,Redis默认会把pid写入/var/run/redis.pid文件,可以通过pidfile指定

    pidfile /var/run/redis.pid

3. 指定Redis监听端口,默认端口为6379,作者在自己的一篇博文中解释了为什么选用6379作为默认端口,因为6379在手机按键上MERZ对应的号码,而MERZ取自意大利歌女Alessia Merz的名字

    port 6379

4. 绑定的主机地址

    bind 127.0.0.1

5.当 客户端闲置多长时间后关闭连接,如果指定为0,表示关闭该功能

    timeout 300

6. 指定日志记录级别,Redis总共支持四个级别:debug、verbose、notice、warning,默认为verbose

    loglevel verbose

7. 日志记录方式,默认为标准输出,如果配置Redis为守护进程方式运行,而这里又配置为日志记录方式为标准输出,则日志将会发送给/dev/null

    logfile stdout

8. 设置数据库的数量,默认数据库为0,可以使用SELECT <dbid>命令在连接上指定数据库id

    databases 16

9. 指定在多长时间内,有多少次更新操作,就将数据同步到数据文件,可以多个条件配合

    save <seconds> <changes>

    Redis默认配置文件中提供了三个条件:

    save 900 1

    save 300 10

    save 60 10000

    分别表示900秒(15分钟)内有1个更改,300秒(5分钟)内有10个更改以及60秒内有10000个更改。

 

10. 指定存储至本地数据库时是否压缩数据,默认为yes,Redis采用LZF压缩,如果为了节省CPU时间,可以关闭该选项,但会导致数据库文件变的巨大

    rdbcompression yes

11. 指定本地数据库文件名,默认值为dump.rdb

    dbfilename dump.rdb

12. 指定本地数据库存放目录

    dir ./

13. 设置当本机为slav服务时,设置master服务的IP地址及端口,在Redis启动时,它会自动从master进行数据同步

    slaveof <masterip> <masterport>

14. 当master服务设置了密码保护时,slav服务连接master的密码

    masterauth <master-password>

15. 设置Redis连接密码,如果配置了连接密码,客户端在连接Redis时需要通过AUTH <password>命令提供密码,默认关闭

    requirepass foobared

16. 设置同一时间最大客户端连接数,默认无限制,Redis可以同时打开的客户端连接数为Redis进程可以打开的最大文件描述符数,如果设置 maxclients 0,表示不作限制。当客户端连接数到达限制时,Redis会关闭新的连接并向客户端返回max number of clients reached错误信息

    maxclients 128

17. 指定Redis最大内存限制,Redis在启动时会把数据加载到内存中,达到最大内存后,Redis会先尝试清除已到期或即将到期的Key,当此方法处理 后,仍然到达最大内存设置,将无法再进行写入操作,但仍然可以进行读取操作。Redis新的vm机制,会把Key存放内存,Value会存放在swap区

    maxmemory <bytes>

18. 指定是否在每次更新操作后进行日志记录,Redis在默认情况下是异步的把数据写入磁盘,如果不开启,可能会在断电时导致一段时间内的数据丢失。因为 redis本身同步数据文件是按上面save条件来同步的,所以有的数据会在一段时间内只存在于内存中。默认为no

    appendonly no

19. 指定更新日志文件名,默认为appendonly.aof

     appendfilename appendonly.aof

20. 指定更新日志条件,共有3个可选值: 
    no:表示等操作系统进行数据缓存同步到磁盘(快) 
    always:表示每次更新操作后手动调用fsync()将数据写到磁盘(慢,安全) 
    everysec:表示每秒同步一次(折衷,默认值)

    appendfsync everysec

 

21. 指定是否启用虚拟内存机制,默认值为no,简单的介绍一下,VM机制将数据分页存放,由Redis将访问量较少的页即冷数据swap到磁盘上,访问多的页面由磁盘自动换出到内存中(在后面的文章我会仔细分析Redis的VM机制)

     vm-enabled no

22. 虚拟内存文件路径,默认值为/tmp/redis.swap,不可多个Redis实例共享

     vm-swap-file /tmp/redis.swap

23. 将所有大于vm-max-memory的数据存入虚拟内存,无论vm-max-memory设置多小,所有索引数据都是内存存储的(Redis的索引数据 就是keys),也就是说,当vm-max-memory设置为0的时候,其实是所有value都存在于磁盘。默认值为0

     vm-max-memory 0

24. Redis swap文件分成了很多的page,一个对象可以保存在多个page上面,但一个page上不能被多个对象共享,vm-page-size是要根据存储的 数据大小来设定的,作者建议如果存储很多小对象,page大小最好设置为32或者64bytes;如果存储很大大对象,则可以使用更大的page,如果不 确定,就使用默认值

     vm-page-size 32

25. 设置swap文件中的page数量,由于页表(一种表示页面空闲或使用的bitmap)是在放在内存中的,,在磁盘上每8个pages将消耗1byte的内存。

     vm-pages 134217728

26. 设置访问swap文件的线程数,最好不要超过机器的核数,如果设置为0,那么所有对swap文件的操作都是串行的,可能会造成比较长时间的延迟。默认值为4

     vm-max-threads 4

27. 设置在向客户端应答时,是否把较小的包合并为一个包发送,默认为开启

    glueoutputbuf yes

28. 指定在超过一定的数量或者最大的元素超过某一临界值时,采用一种特殊的哈希算法

    hash-max-zipmap-entries 64

    hash-max-zipmap-value 512

29. 指定是否激活重置哈希,默认为开启(后面在介绍Redis的哈希算法时具体介绍)

    activerehashing yes

30. 指定包含其它的配置文件,可以在同一主机上多个Redis实例之间使用同一份配置文件,而同时各个实例又拥有自己的特定配置文件

    include /path/to/local.conf



几个工具

redis-benchmark:性能测试工具,测试Redis在你的系统及配置下的读写性能 
redis-check-aof:用于修复出问题的AOF文件 
redis-check-dump:用于修复出问题的dump.rdb文件 
redis-cli:Redis命令行操作工具 
redis-sentinel:Redis集群的管理工具 
redis-server:Redis服务器启动程序 


查看redis进程

root@finder01:/finder/redis/src# ps -aux|grep redis
Warning: bad ps syntax, perhaps a bogus '-'? See http://procps.sf.net/faq.html
root     112397  0.0  0.1  18656  1988 pts/3    T    13:58   0:00 nano redis.conf            
root     112823  0.0  0.2  40320  2452 pts/3    Sl+  14:55   0:00 ./src/redis-server *:6379
root     112896  0.0  0.0  13576   932 pts/4    S+   15:01   0:00 grep --color=auto redis
root@finder01:/finder/redis/src# src/redis-cli -h localhost -p 6379

redis-server 守护进程运行配置

在 sentinal.conf文件中添加如下配置

daemonize yes
logfile "/finder/redis/log/sentinel_log.log"


运行方式

root@finder01:/finder/redis# ./src/redis-server redis.conf 
或者

关闭 redis服务器

root@finder01:/finder/redis# ./src/redis-cli -p 6379 shutdown
或者 

pkill redis-server 


启用 远程访问redis-server

启用 远程访问redis-server

默认  情况 只能本机访问 , 一般我们需要配置在内网中给另外一台服务器操作。

首先: 修改 protected-mode no   

 其次: 我们需要 修改 bind参数 ,修改成内网IP 

# ~~~ WARNING ~~~ If the computer running Redis is directly exposed to the 
# internet, binding to all the interfaces is dangerous and will expose the
# instance to everybody on the internet. So by default we uncomment the
# following bind directive, that will force Redis to listen only into       
# the IPv4 lookback interface address (this means Redis will be able to
# accept connections only from clients running into the same computer it
# is running).
#
# IF YOU ARE SURE YOU WANT YOUR INSTANCE TO LISTEN TO ALL THE INTERFACES
# JUST COMMENT THE FOLLOWING LINE.
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#bind 127.0.0.1
bind 192.168.1.201

客户端可以这样连接

root@finder02:/finder/redis# ./src/redis-cli -h 192.168.1.201 -p 6379 -a password
192.168.1.201:6379> get a
"1"
192.168.1.201:6379> 


Redis数据类型


Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)。




在Ubuntu上安装Redis桌面管理



要在Ubuntu上安装Redis桌面管理器,可从  此链接下载 下载该软件包,安装即可。

这里我们下载 windows版本的 ,因为其他版本需要收费

打开下载的软件包并安装。

Redis桌面管理器将提供用于管理Redis的键和数据的UI。



JAVA使用 jedis 连接 redis-server

	@Test
	public void testJedis(){
		JedisShardInfo shardInfo = new JedisShardInfo("192.168.1.201", 6379);
		shardInfo.setPassword("password");
		Jedis client=new Jedis(shardInfo);
		
		System.out.println(client.ping());
		String ret=client.set("a", "100");
		System.out.println(ret);
		
		ret=client.get("a");
		
		
		ret=client.set("b".getBytes(),"你好a".getBytes());

		System.out.println("====b:"+client.get("b"));
		
		client.lpush("list", "A");
		client.lpush("list", "C");
		client.lpush("list", "D");
		client.lpush("list", "B");
		
		List<String> list = client.lrange("list", 0 ,5);
		for(int i = 0; i<list.size(); i++) { 
	         System.out.println("Stored string in redis:: "+list.get(i)); 
	      } 
		
		
		
		Map<String,String> p1=new HashMap<String,String>();
		p1.put("name","d好好");
		p1.put("age","10");
		
		client.hmset("p1", p1);
		
		List<String> p1_member=client.hmget("p1", "name","age");
		
		
		for(int i = 0; i<p1_member.size(); i++) { 
	         System.out.println("===p1:: "+p1_member.get(i)); 
	      } 
		
		
		
		
	}


执行结果

PONG
OK
====b:你好a
Stored string in redis:: B
Stored string in redis:: D
Stored string in redis:: C
Stored string in redis:: A
Stored string in redis:: B
Stored string in redis:: D
===p1:: d好好
===p1:: 10


其他基础教程请参考 :  点我查看 



Redis伪分布式集群搭建


redis-cluster架构图




(1)所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽.

(2)节点的fail是通过集群中超过半数的节点检测失效时才生效.

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

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

2) redis-cluster选举:容错



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 发送写命令的最大时间是有限制的, 这一时间限制称为节点超时时间(node timeout), 是 Redis 集群的一个重要的配置选项.

集群搭建

要让集群正常运作至少需要三个主节点,不过在刚开始试用集群功能时, 强烈建议使用六个节点: 其中三个为主节点, 而其余三个则是各个主节点的从节点。

我们单独创建一个目录来做集群测试

root@finder01:/finder# mkdir redis-cluster
root@finder01:/finder# cp redis redis-cluster/
root@finder01:/finder# cp -r redis redis-cluster/
-bash: cd: redis-c: No such file or directory
root@finder01:/finder# cd redis-cluster/
root@finder01:/finder/redis-cluster# ls
redis
root@finder01:/finder/redis-cluster# mkdir 7001 7002 7003 7004 7005 7006
root@finder01:/finder/redis-cluster# ls
7001  7002  7003  7004  7005  7006  redis
root@finder01:/finder/redis-cluster# touch 7001/redis.conf
root@finder01:/finder/redis-cluster# nano 7001/redis.conf

redis.conf 内容如下

port 7001
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
appendonly yes
protected-mode no   =>支持远程计算机访问
daemonize yes      ==> 守护进程启动



把redis.conf拷贝到其他5个目录下面

root@finder01:/finder/redis-cluster# cp 7001/redis.conf 7002/
root@finder01:/finder/redis-cluster# cp 7001/redis.conf 7003/
root@finder01:/finder/redis-cluster# cp 7001/redis.conf 7004/
root@finder01:/finder/redis-cluster# cp 7001/redis.conf 7005/
root@finder01:/finder/redis-cluster# cp 7001/redis.conf 7006/
修改对一个的 port参数


拷贝  redis 包到每个 对应文件下

root@finder01:/finder/redis-cluster# cp -r redis 7001/redis
root@finder01:/finder/redis-cluster# ls 7001
redis  redis.conf
root@finder01:/finder/redis-cluster# ls
7001  7002  7003  7004  7005  7006  appendonly.aof  nodes.conf  redis
root@finder01:/finder/redis-cluster# cp -r redis 7002/redis
root@finder01:/finder/redis-cluster# cp -r redis 7003/redis
root@finder01:/finder/redis-cluster# cp -r redis 7004/redis
root@finder01:/finder/redis-cluster# cp -r redis 7005/redis
root@finder01:/finder/redis-cluster# cp -r redis 7006/redis

分别启动 redis-server

root@finder01:/finder/redis-cluster# cd 7001
root@finder01:/finder/redis-cluster/7001# ls
redis  redis.conf
root@finder01:/finder/redis-cluster/7001# ps -aux|grep redis-server
Warning: bad ps syntax, perhaps a bogus '-'? See http://procps.sf.net/faq.html
root       9191  0.0  0.2  40320  2520 ?        Ssl  11:44   0:00 redis/src/redis-server *:7001 [cluster]
root       9196  0.0  0.0  13576   932 pts/2    S+   11:44   0:00 grep --color=auto redis-server

我们在启动的时候,最好手动指定日志输出

如: 

# redis/src/redis-server ./redis.conf > ./7001/redis-7001.log 2>&1 &



ps -aux|grep redis-server 此命令查看 启动进程信息, 可以看到 是 redis/src/redis-server 来启动的 绑定到7001端口 启动成功

然后 依次启动其他 5个  redis server

启动成功后,可以查看启动后的进程状态

root@finder01:/finder/redis-cluster/7006# ps -aux|grep redis-server
Warning: bad ps syntax, perhaps a bogus '-'? See http://procps.sf.net/faq.html
root       9191  0.0  0.2  40320  2520 ?        Ssl  11:44   0:00 redis/src/redis-server *:7001 [cluster]
root       9212  0.0  0.2  40320  2516 ?        Ssl  11:46   0:00 redis/src/redis-server *:7002 [cluster]
root       9219  0.0  0.2  40320  2528 ?        Ssl  11:47   0:00 redis/src/redis-server *:7003 [cluster]
root       9224  0.0  0.2  40320  2528 ?        Ssl  11:47   0:00 redis/src/redis-server *:7004 [cluster]
root       9228  0.0  0.2  40320  2520 ?        Ssl  11:47   0:00 redis/src/redis-server *:7005 [cluster]
root       9235  0.0  0.2  40320  2516 ?        Ssl  11:47   0:00 redis/src/redis-server *:7006 [cluster]
root       9240  0.0  0.0  13580   936 pts/2    S+   11:47   0:00 grep --color=auto redis-server

也可以查看启动后,各自目录下的文件变化

root@finder01:/finder/redis-cluster/7001# ls
appendonly.aof  nodes.conf  redis  redis.conf
root@finder01:/finder/redis-cluster/7001# cat nodes.conf 
20b15b763a555ad7c6bdbde8aae2609c23f331ae :0 myself,master - 0 0 0 connected
vars currentEpoch 0 lastVoteEpoch 0
root@finder01:/finder/redis-cluster/7001# cat appendonly.aof

搭建集群:

  现在我们启动了 6个 独立的 redis 服务, 各自之间没有任何关系,那我们怎么让他们之前变成一个集群服务器呢

    通过使用 Redis 集群命令行工具 redis-trib , 是基于redis提供的集群命令封装成简单、便捷、实用的操作工具,编写节点配置文件的工作可以非常容易地完成: redis-trib 位于 Redis 源码的 src 文件夹中, 它是一个 Ruby 程序, 这个程序通过向实例发送特殊命令来完成创建新集群, 检查集群, 或者对集群进行重新分片(reshared)等工作。

 所以 我们首先得先安装ruby环境。

      1. 下载安装 zlib-1.2.6.tar.gz    

    

# tar -zxvf zlib-1.2.6.tar.gz
# ./configure
# make
# make install

   2. 下载安装  ruby-1.9.2.tar.gz

# tar -zxvf ruby-1.9.2.tar.gz 
# cd /path/ruby  
#./configure -prefix=/usr/local/ruby  
#make  
#make install  
#cp ruby /usr/local/bin

   3.   下载安装 rubygems-2.4.2.tgz

#cd /path/gem  
# ruby setup.rb  
#cp bin/gem /usr/local/bin

复制所用命令

root@finder01:/finder/redis-cluster/7001/redis/src# cp redis-server /usr/local/bin
root@finder01:/finder/redis-cluster/7001/redis/src# cp redis-cli /usr/local/bin
root@finder01:/finder/redis-cluster/7001/redis/src# cp redis-trib.rb /usr/local/bin

创建一个新集群 

 以下 在创建集群的时候 有错误   

/custom_require.rb:36:in `require': no such file to load -- redis (LoadError)

提示不能加载redis,是因为缺少redis的接口 

我们需要下载 redis-gem

gem install redis --version 3.2.8  如果执行失败 可能远程仓库中不能下载,那么我们需要手动下载

这里提供  redis-3.2.1.gem 供下载

下载后 在 redis-.3.2.1.gem 目录再次执行 

gem install redis --version 3.2.8

如下面所示 执行成功


root@finder01:/finder/redis-cluster# redis-trib.rb create --replicas 1 192.168.1.201:7001 192.168.1.201:7002 192.168.1.201:7003 192.168.1.201:7004 192.168.1.201:7005 192.168.1.201:7006
/usr/local/ruby/lib/ruby/site_ruby/1.9.1/rubygems/custom_require.rb:36:in `require': no such file to load -- redis (LoadError)
        from /usr/local/ruby/lib/ruby/site_ruby/1.9.1/rubygems/custom_require.rb:36:in `require'
        from /usr/local/bin/redis-trib.rb:25:in `<main>'
root@finder01:/finder/redis-cluster# gem install redis --version 3.2.8
Successfully installed redis-3.2.1
1 gem installed
Installing ri documentation for redis-3.2.1...
Installing RDoc documentation for redis-3.2.1...
root@finder01:/finder/redis-cluster# redis-trib.rb create --replicas 1 192.168.1.201:7001 192.168.1.201:7002 192.168.1.201:7003 192.168.1.201:7004 192.168.1.201:7005 192.168.1.201:7006
>>> Creating cluster
>>> Performing hash slots allocation on 6 nodes...
Using 3 masters:
192.168.1.201:7001
192.168.1.201:7002
192.168.1.201:7003
Adding replica 192.168.1.201:7004 to 192.168.1.201:7001
Adding replica 192.168.1.201:7005 to 192.168.1.201:7002
Adding replica 192.168.1.201:7006 to 192.168.1.201:7003
M: 20b15b763a555ad7c6bdbde8aae2609c23f331ae 192.168.1.201:7001
   slots:0-5460 (5461 slots) master
M: 4a015c25e9e4a0ba706e8059e238b156e28d972b 192.168.1.201:7002
   slots:5461-10922 (5462 slots) master
M: 88fee9f17376aec13e32193f8daad0677d866669 192.168.1.201:7003
   slots:10923-16383 (5461 slots) master
S: cef3300aa56c1521bd44bb26b1507c73112acd35 192.168.1.201:7004
   replicates 20b15b763a555ad7c6bdbde8aae2609c23f331ae
S: 8d8b43d2108e4af3f8a0b9a06ae0e40a0553be32 192.168.1.201:7005
   replicates 4a015c25e9e4a0ba706e8059e238b156e28d972b
S: da12ede8bda2cc968467f0461e9ae4e64cd2162d 192.168.1.201:7006
   replicates 88fee9f17376aec13e32193f8daad0677d866669
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
Waiting for the cluster to join...
>>> Performing Cluster Check (using node 192.168.1.201:7001)
M: 20b15b763a555ad7c6bdbde8aae2609c23f331ae 192.168.1.201:7001
   slots:0-5460 (5461 slots) master
   1 additional replica(s)
S: cef3300aa56c1521bd44bb26b1507c73112acd35 192.168.1.201:7004
   slots: (0 slots) slave
   replicates 20b15b763a555ad7c6bdbde8aae2609c23f331ae
M: 4a015c25e9e4a0ba706e8059e238b156e28d972b 192.168.1.201:7002
   slots:5461-10922 (5462 slots) master
   1 additional replica(s)
S: da12ede8bda2cc968467f0461e9ae4e64cd2162d 192.168.1.201:7006
   slots: (0 slots) slave
   replicates 88fee9f17376aec13e32193f8daad0677d866669
M: 88fee9f17376aec13e32193f8daad0677d866669 192.168.1.201:7003
   slots:10923-16383 (5461 slots) master
   1 additional replica(s)
S: 8d8b43d2108e4af3f8a0b9a06ae0e40a0553be32 192.168.1.201:7005
   slots: (0 slots) slave
   replicates 4a015c25e9e4a0ba706e8059e238b156e28d972b
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
root@finder01:/finder/redis-cluster# 

我们看到 了 最后  [OK] All 16384 slots covered.  这表示集群中的 16384 个槽都有至少一个主节点在处理, 集群运作正常  ;SUCCESSFULLY
搭建期间遇到 需要安装很多关于ruby的工具, 只要按照以上步骤按照, 就可以 成功。
至此 ,集群搭建成功

集群测试
     测试 Redis 集群比较简单的办法就是使用 redis-rb-cluster 或者 redis-cli , 接下来我们将使用 redis-cli 为例来进行演示:

root@finder02:/finder/redis# ./src/redis-cli -c -h 192.168.1.201 -p 7001
192.168.1.201:7001> get a
-> Redirected to slot [15495] located at 192.168.1.201:7003
(nil)
192.168.1.201:7003> set a 1
OK
192.168.1.201:7003> get a
"1"
192.168.1.201:7003> get b
-> Redirected to slot [3300] located at 192.168.1.201:7001
(nil)
192.168.1.201:7001> set b hello
OK
192.168.1.201:7001> get b
"hello"
192.168.1.201:7001> 
192.168.1.201:7001> lpush list 1
-> Redirected to slot [12291] located at 192.168.1.201:7003
(integer) 1
192.168.1.201:7003> lpush list 2
(integer) 2
192.168.1.201:7003> lpush list 3
(integer) 3

测试成功。  注意 需要 加上参数 -c  

可视化工具查看 存储的分区


我们可以查看集群中的状态信息
如下

192.168.1.201:7003> cluster info
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6
cluster_size:3
cluster_current_epoch:6
cluster_my_epoch:3
cluster_stats_messages_sent:5193
cluster_stats_messages_received:5193
192.168.1.201:7003> cluster nodes
da12ede8bda2cc968467f0461e9ae4e64cd2162d 192.168.1.201:7006 slave 88fee9f17376aec13e32193f8daad0677d866669 0 1490174984810 6 connected
8d8b43d2108e4af3f8a0b9a06ae0e40a0553be32 192.168.1.201:7005 slave 4a015c25e9e4a0ba706e8059e238b156e28d972b 0 1490174983801 5 connected
20b15b763a555ad7c6bdbde8aae2609c23f331ae 192.168.1.201:7001 master - 0 1490174985314 1 connected 0-5460
4a015c25e9e4a0ba706e8059e238b156e28d972b 192.168.1.201:7002 master - 0 1490174984307 2 connected 5461-10922
cef3300aa56c1521bd44bb26b1507c73112acd35 192.168.1.201:7004 slave 20b15b763a555ad7c6bdbde8aae2609c23f331ae 0 1490174985818 4 connected
88fee9f17376aec13e32193f8daad0677d866669 192.168.1.201:7003 myself,master - 0 0 3 connected 10923-16383
192.168.1.201:7003>

查看集群状态

root@finder01:/finder/redis-cluster/7001# redis-trib.rb check 192.168.1.201:7001
>>> Performing Cluster Check (using node 192.168.1.201:7001)
M: 20b15b763a555ad7c6bdbde8aae2609c23f331ae 192.168.1.201:7001
   slots:0-5460 (5461 slots) master
   1 additional replica(s)
S: cef3300aa56c1521bd44bb26b1507c73112acd35 192.168.1.201:7004
   slots: (0 slots) slave
   replicates 20b15b763a555ad7c6bdbde8aae2609c23f331ae
M: 4a015c25e9e4a0ba706e8059e238b156e28d972b 192.168.1.201:7002
   slots:5461-10922 (5462 slots) master
   1 additional replica(s)
S: da12ede8bda2cc968467f0461e9ae4e64cd2162d 192.168.1.201:7006
   slots: (0 slots) slave
   replicates 88fee9f17376aec13e32193f8daad0677d866669
M: 88fee9f17376aec13e32193f8daad0677d866669 192.168.1.201:7003
   slots:10923-16383 (5461 slots) master
   1 additional replica(s)
S: 8d8b43d2108e4af3f8a0b9a06ae0e40a0553be32 192.168.1.201:7005
   slots: (0 slots) slave
   replicates 4a015c25e9e4a0ba706e8059e238b156e28d972b
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.

3主3从


 failover 测试
      首先我们把7001端口的服务器给关闭了,然后我们看看 7004端的从服务器能不能变成 主服务器

    

  

root@finder01:/finder/redis-cluster/7001# ps -aux|grep redis-server
Warning: bad ps syntax, perhaps a bogus '-'? See http://procps.sf.net/faq.html
root       9191  0.0  0.3  42368  3160 ?        Ssl  Mar22   1:05 redis/src/redis-server *:7001 [cluster]
root       9212  0.0  0.2  42368  2884 ?        Ssl  Mar22   1:04 redis/src/redis-server *:7002 [cluster]
root       9219  0.0  0.3  42368  3096 ?        Ssl  Mar22   1:06 redis/src/redis-server *:7003 [cluster]
root       9224  0.0  0.2  40320  2636 ?        Ssl  Mar22   1:02 redis/src/redis-server *:7004 [cluster]
root       9228  0.0  0.2  40320  2696 ?        Ssl  Mar22   1:03 redis/src/redis-server *:7005 [cluster]
root       9235  0.0  0.2  40320  2548 ?        Ssl  Mar22   1:02 redis/src/redis-server *:7006 [cluster]
root      26070  0.0  0.0  13580   932 pts/2    S+   11:55   0:00 grep --color=auto redis-server
root@finder01:/finder/redis-cluster/7001# kill -9 9191
root@finder01:/finder/redis-cluster/7001# ps -aux|grep redis-server
Warning: bad ps syntax, perhaps a bogus '-'? See http://procps.sf.net/faq.html
root       9212  0.0  0.2  42368  2884 ?        Ssl  Mar22   1:04 redis/src/redis-server *:7002 [cluster]
root       9219  0.0  0.3  42368  3096 ?        Ssl  Mar22   1:06 redis/src/redis-server *:7003 [cluster]
root       9224  0.0  0.2  40320  2636 ?        Ssl  Mar22   1:02 redis/src/redis-server *:7004 [cluster]
root       9228  0.0  0.2  40320  2696 ?        Ssl  Mar22   1:03 redis/src/redis-server *:7005 [cluster]
root       9235  0.0  0.2  40320  2548 ?        Ssl  Mar22   1:02 redis/src/redis-server *:7006 [cluster]
root      26073  0.0  0.0  13580   936 pts/2    S+   11:55   0:00 grep --color=auto redis-server
root@finder01:/finder/redis-cluster/7001# redis-trib.rb check 192.168.1.201:7001
[ERR] Sorry, can't connect to node 192.168.1.201:7001
root@finder01:/finder/redis-cluster/7001# redis-trib.rb check 192.168.1.201:7002
>>> Performing Cluster Check (using node 192.168.1.201:7002)
M: 4a015c25e9e4a0ba706e8059e238b156e28d972b 192.168.1.201:7002
   slots:5461-10922 (5462 slots) master
   1 additional replica(s)
M: cef3300aa56c1521bd44bb26b1507c73112acd35 192.168.1.201:7004
   slots:0-5460 (5461 slots) master
   0 additional replica(s)
S: 8d8b43d2108e4af3f8a0b9a06ae0e40a0553be32 192.168.1.201:7005
   slots: (0 slots) slave
   replicates 4a015c25e9e4a0ba706e8059e238b156e28d972b
S: da12ede8bda2cc968467f0461e9ae4e64cd2162d 192.168.1.201:7006
   slots: (0 slots) slave
   replicates 88fee9f17376aec13e32193f8daad0677d866669
M: 88fee9f17376aec13e32193f8daad0677d866669 192.168.1.201:7003
   slots:10923-16383 (5461 slots) master
   1 additional replica(s)
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
变成 3主 2从了,  7004接任 7001的工作

现在我们可以使用 redis desktop manage工具去连接7004 。 之前 7004为从服务器状态时 是不可以连接上的





      

192.168.1.201:7003> get b
-> Redirected to slot [3300] located at 192.168.1.201:7004
"hello"
192.168.1.201:7004> cluster info
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6
cluster_size:3
cluster_current_epoch:7
cluster_my_epoch:7
cluster_stats_messages_sent:298687
cluster_stats_messages_received:296749

可以看到  get b 数据的时候  槽 跳转到 7004服务器上了。 RDM也可以连接上 7004。


新增从节点

root@finder01:/finder/redis-cluster/7001# ./redis/src/redis-server ./redis.conf > ./log/redis-7001.log 2>&1 &
[1] 26196
root@finder01:/finder/redis-cluster/7001# ps -aux|grep redis-server
Warning: bad ps syntax, perhaps a bogus '-'? See http://procps.sf.net/faq.html
root       9212  0.0  0.2  42368  2884 ?        Ssl  Mar22   1:05 redis/src/redis-server *:7002 [cluster]
root       9219  0.0  0.3  42368  3096 ?        Ssl  Mar22   1:09 redis/src/redis-server *:7003 [cluster]
root       9224  0.0  0.2  42368  2768 ?        Ssl  Mar22   1:04 redis/src/redis-server *:7004 [cluster]
root       9228  0.0  0.2  40320  2696 ?        Ssl  Mar22   1:05 redis/src/redis-server *:7005 [cluster]
root       9235  0.0  0.2  40320  2676 ?        Ssl  Mar22   1:04 redis/src/redis-server *:7006 [cluster]
root      26197  0.2  0.2  44416  2848 ?        Ssl  12:22   0:00 ./redis/src/redis-server *:7001 [cluster]
root      26203  0.0  0.0  13580   936 pts/2    S+   12:22   0:00 grep --color=auto redis-server
[1]+  Done                    ./redis/src/redis-server ./redis.conf > ./log/redis-7001.log 2>&1
root@finder01:/finder/redis-cluster/7001# redis-trib.rb add-node --slave --master-id cef3300aa56c1521bd44bb26b1507c73112acd35 192.168.1.201:7001 192.168.1.201:7002
>>> Adding node 192.168.1.201:7001 to cluster 192.168.1.201:7002
>>> Performing Cluster Check (using node 192.168.1.201:7002)
M: 4a015c25e9e4a0ba706e8059e238b156e28d972b 192.168.1.201:7002
   slots:5461-10922 (5462 slots) master
   1 additional replica(s)
M: cef3300aa56c1521bd44bb26b1507c73112acd35 192.168.1.201:7004
   slots:0-5460 (5461 slots) master
   1 additional replica(s)
S: 20b15b763a555ad7c6bdbde8aae2609c23f331ae 192.168.1.201:7001
   slots: (0 slots) slave
   replicates cef3300aa56c1521bd44bb26b1507c73112acd35
S: 8d8b43d2108e4af3f8a0b9a06ae0e40a0553be32 192.168.1.201:7005
   slots: (0 slots) slave
   replicates 4a015c25e9e4a0ba706e8059e238b156e28d972b
S: da12ede8bda2cc968467f0461e9ae4e64cd2162d 192.168.1.201:7006
   slots: (0 slots) slave
   replicates 88fee9f17376aec13e32193f8daad0677d866669
M: 88fee9f17376aec13e32193f8daad0677d866669 192.168.1.201:7003
   slots:10923-16383 (5461 slots) master
   1 additional replica(s)
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
[ERR] Node 192.168.1.201:7001 is not empty. Either the node already knows other nodes (check with CLUSTER NODES) or contains some key in database 0

这样 我们就把 7001 设置成 7004 的从节点了。


添加主节点

    # redis-trib.rb add-node 192.168.1.201:7007 192.168.1.201:7001


为主节点重新分配槽

     

# redis-trib.rb reshard 192.168.1.201:7007 //下面是主要过程  
  
How many slots do you want to move (from 1 to 16384)? 1000 //设置slot数1000  
What is the receiving node ID? 03ccad2ba5dd1e062464bc7590400441fafb63f2 //新节点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:all //表示全部节点重新洗牌  
Do you want to proceed with the proposed reshard plan (yes/no)? yes //确认重新分 

1,删除从节点
    
# redis-trib.rb del-node 192.168.1.201:7007 '9c240333476469e8e2c8e80b089c48f389827265'  
2,删除主节点
如果主节点有从节点,将从节点转移到其他主节点
如果主节点有slot,去掉分配的slot,然后在删除主节点
    

# redis-trib.rb reshard 192.168.1.201:7007 //取消分配的slot,下面是主要过程  
  
How many slots do you want to move (from 1 to 16384)? 1000 //被删除master的所有slot数量  
What is the receiving node ID? 5d8ef5a7fbd72ac586bef04fa6de8a88c0671052 //接收6378节点slot的master  
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:03ccad2ba5dd1e062464bc7590400441fafb63f2 //被删除master的node-id  
Source node #2:done   
  
Do you want to proceed with the proposed reshard plan (yes/no)? yes //取消slot后,reshard 

新增master节点后,也进行了这一步操作,当时是分配,现在去掉。反着的。

  



更多集群命令
CLUSTER INFO 打印集群的信息  
CLUSTER NODES 列出集群当前已知的所有节点(node),以及这些节点的相关信息。  
节点  
CLUSTER MEET <ip> <port> 将 ip 和 port 所指定的节点添加到集群当中,让它成为集群的一份子。  
CLUSTER FORGET <node_id> 从集群中移除 node_id 指定的节点。  
CLUSTER REPLICATE <node_id> 将当前节点设置为 node_id 指定的节点的从节点。  
CLUSTER SAVECONFIG 将节点的配置文件保存到硬盘里面。  
槽(slot)  
CLUSTER ADDSLOTS <slot> [slot ...] 将一个或多个槽(slot)指派(assign)给当前节点。  
CLUSTER DELSLOTS <slot> [slot ...] 移除一个或多个槽对当前节点的指派。  
CLUSTER FLUSHSLOTS 移除指派给当前节点的所有槽,让当前节点变成一个没有指派任何槽的节点。  
CLUSTER SETSLOT <slot> NODE <node_id> 将槽 slot 指派给 node_id 指定的节点,如果槽已经指派给另一个节点,那么先让另一个节点删除该槽>,然后再进行指派。  
CLUSTER SETSLOT <slot> MIGRATING <node_id> 将本节点的槽 slot 迁移到 node_id 指定的节点中。  
CLUSTER SETSLOT <slot> IMPORTING <node_id> 从 node_id 指定的节点中导入槽 slot 到本节点。  
CLUSTER SETSLOT <slot> STABLE 取消对槽 slot 的导入(import)或者迁移(migrate)。  
键  
CLUSTER KEYSLOT <key> 计算键 key 应该被放置在哪个槽上。  
CLUSTER COUNTKEYSINSLOT <slot> 返回槽 slot 目前包含的键值对数量。  
CLUSTER GETKEYSINSLOT <slot> <count> 返回 count 个 slot 槽中的键。







 redis cluser客户端(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,需要自己扩展

下面试来自网上的客户端测试代码 我就直接贴了, 测试成功的哦。

  

package redis;

import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import redis.clients.jedis.Client;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisCluster;

/**
 * Created by liubenlong on 2016/12/20.
 */
public class RedisClusterTest {

    JedisCluster jedisCluster = null;

    static String prefix = "luffi:lbl";
    static String KEY_SPLIT = ":"; //用于隔开缓存前缀与缓存键值

    String nameKey = prefix + KEY_SPLIT + "name";
    
    private static String config_addresses="192.168.1.201:7001,192.168.1.201:7002,192.168.1.201:7003";
    
    private static String config_password=null;//当没有密码的时候 设置 null 而不是""
    /**
     * 因为是测试,这里没有写单例
     */
    @Before
    public void before(){
        String[] serverArray = config_addresses.split(",");
        Set<HostAndPort> nodes = new HashSet<HostAndPort>();

        for (String ipPort : serverArray) {
            String[] ipPortPair = ipPort.split(":");
            nodes.add(new HostAndPort(ipPortPair[0].trim(), Integer.valueOf(ipPortPair[1].trim())));
        }

        //注意:这里超时时间不要太短,他会有超时重试机制。而且其他像httpclient、dubbo等RPC框架也要注意这点
        jedisCluster = new JedisCluster(nodes, 1000, 1000, 1, config_password, new GenericObjectPoolConfig());

//        大多数测试都是使用【nameKey】测试的,所以在启动之前先把这个key删掉
        jedisCluster.del(nameKey);
    }

    /**
     * 发布
     */
    @Test
    public void publish(){
        System.out.println(jedisCluster.publish("channel1", "ss"));
    }
    /**
     * 订阅
     */
    @Test
    public void psubscribe(){
//        jedisCluster.psubscribe(new JedisPubSubListener(), "channel1");//带通配符
        jedisCluster.subscribe(new JedisPubSubListener(), "channel1");
    }

    /**
     * 简单字符串读写
     */
    @Test
    public void setStringData(){
        System.out.println(jedisCluster.set(nameKey, "张三"));
        System.out.println(jedisCluster.get(nameKey));
    }

    /**
     * setnx : 如果key存在,返回0,如果不存在,则设置成功。
     * setnx的意思是set if not exist.
     */
    @Test
    public void setnxTest(){
        System.out.println(jedisCluster.setnx(nameKey, "张三"));//key不存在,返回值为1
        System.out.println(jedisCluster.get(nameKey));

        System.out.println(jedisCluster.setnx(nameKey, "张三"));//已经存在,返回值为0
        System.out.println(jedisCluster.get(nameKey));
    }

    /**
     * 简单字符串读写,带过期时间
     */
    @Test
    public void setexTest() throws InterruptedException {
        System.out.println(jedisCluster.setex(nameKey, 3, "张三"));//时间单位是秒
        for(int i = 0 ; i < 5 ; i ++){
            System.out.println(jedisCluster.get(nameKey));//过期以后redis集群自动删除
            Thread.sleep(1000);
        }
    }


    /**
     * 操作子字符串
     */
    @Test
    public void setrangeTest() throws InterruptedException {
        System.out.println(jedisCluster.set(nameKey, "[email protected]"));
        System.out.println(jedisCluster.get(nameKey));//结果:[email protected]

        //从offset=8开始替换字符串value的值
        System.out.println(jedisCluster.setrange(nameKey, 8, "abc"));//结果:85202688abcq.com
        System.out.println(jedisCluster.get(nameKey));

        System.out.println(jedisCluster.setrange(nameKey, 8, "abcdefghhhhhh"));//结果:85202688abcdefghhhhhh
        System.out.println(jedisCluster.get(nameKey));

        //查询子串,返回startOffset到endOffset的字符
        System.out.println(jedisCluster.getrange(nameKey, 2, 5));//结果:2026
    }


    /**
     * 批量操作key
     * keySlot算法中,如果key包含{},就会使用第一个{}内部的字符串作为hash key,这样就可以保证拥有同样{}内部字符串的key就会拥有相同slot。
     * 参考  http://brandnewuser.iteye.com/blog/2314280
     * redis.clients.util.JedisClusterCRC16#getSlot(java.lang.String)
     *
     * 注意:这样的话,本来可以hash到不同的slot中的数据都放到了同一个slot中,所以使用的时候要注意数据不要太多导致一个slot数据量过大,数据分布不均匀!
     *
     * MSET 是一个原子性(atomic)操作,所有给定 key 都会在同一时间内被设置,某些给定 key 被更新而另一些给定 key 没有改变的情况,不可能发生。
     */
    @Test
    public void msetTest() throws InterruptedException {
        /**
         * jedisCluster.mset("sf","d","aadf","as");
         * 直接这样写,会报错:redis.clients.jedis.exceptions.JedisClusterException: No way to dispatch this command to Redis Cluster because keys have different slots.
         * 这是因为key不在同一个slot中
         */
    	
        String result = jedisCluster.mset("{" + prefix + KEY_SPLIT + "}" + "name", "张三", "{" + prefix + KEY_SPLIT + "}" + "age", "23", "{" + prefix + KEY_SPLIT + "}" + "address", "adfsa", "{" + prefix + KEY_SPLIT + "}" + "score", "100");
        System.out.println(result);

        String name = jedisCluster.get("{" + prefix + KEY_SPLIT + "}" + "name");
        System.out.println(name);

        Long del = jedisCluster.del("{" + prefix + KEY_SPLIT + "}" + "age");
        System.out.println(del);

        List<String> values = jedisCluster.mget("{" + prefix + KEY_SPLIT + "}" + "name", "{" + prefix + KEY_SPLIT + "}" + "age", "{" + prefix + KEY_SPLIT + "}" + "address");
        System.out.println(values);
    }


    /**
     *  MSETNX 命令:它只会在所有给定 key 都不存在的情况下进行设置操作。
     *  http://doc.redisfans.com/string/mset.html
     */
    @Test
    public void msetnxTest() throws InterruptedException {
        Long msetnx = jedisCluster.msetnx(
                "{" + prefix + KEY_SPLIT + "}" + "name", "张三",
                "{" + prefix + KEY_SPLIT + "}" + "age", "23",
                "{" + prefix + KEY_SPLIT + "}" + "address", "adfsa",
                "{" + prefix + KEY_SPLIT + "}" + "score", "100");
        System.out.println(msetnx);

        System.out.println(jedisCluster.mget(
                "{" + prefix + KEY_SPLIT + "}" + "name",
                "{" + prefix + KEY_SPLIT + "}" + "age",
                "{" + prefix + KEY_SPLIT + "}" + "address",
                "{" + prefix + KEY_SPLIT + "}" + "score"));//[张三, 23, adfsa, 100]

        //name这个key已经存在,由于mset是原子的,该条指令将全部失败
        msetnx = jedisCluster.msetnx(
                "{" + prefix + KEY_SPLIT + "}" + "phone", "110",
                "{" + prefix + KEY_SPLIT + "}" + "name", "张三",
                "{" + prefix + KEY_SPLIT + "}" + "abc", "asdfasfdsa");
        System.out.println(msetnx);


        System.out.println(jedisCluster.mget(
                "{" + prefix + KEY_SPLIT + "}" + "name",
                "{" + prefix + KEY_SPLIT + "}" + "age",
                "{" + prefix + KEY_SPLIT + "}" + "address",
                "{" + prefix + KEY_SPLIT + "}" + "score",
                "{" + prefix + KEY_SPLIT + "}" + "phone",
                "{" + prefix + KEY_SPLIT + "}" + "abc"));//[张三, 23, adfsa, 100, null, null]
    }


    /**
     *  getset:设置key值,并返回旧值
     */
    @Test
    public void getsetTest() throws InterruptedException {
        System.out.println(jedisCluster.set(nameKey, "zhangsan"));
        System.out.println(jedisCluster.get(nameKey));
        System.out.println(jedisCluster.getSet(nameKey, "lisi"));
        System.out.println(jedisCluster.get(nameKey));
    }

    /**
     *  append: 追加. 其返回值是追加后数据的长度
     */
    @Test
    public void appendTest() throws InterruptedException {
        System.out.println(jedisCluster.append(nameKey, "aa"));
        System.out.println(jedisCluster.get(nameKey));
        System.out.println(jedisCluster.append(nameKey, "lisi"));
        System.out.println(jedisCluster.get(nameKey));
    }


    /**
     *  incr:
     *  将 key 中储存的数字值增一。

     如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 INCR 操作。

     如果值包含错误的类型,或字符串类型的值不能表示为数字,那么返回一个错误。

     本操作的值限制在 64 位(bit)有符号数字表示之内。

     这是一个针对字符串的操作,因为 Redis 没有专用的整数类型,所以 key 内储存的字符串被解释为十进制 64 位有符号整数来执行 INCR 操作。

     返回值:     执行 INCR 命令之后 key 的值。

     这里有问题,最终数据结果大于10000    后续在研究 TODO
     这是因为设置的超时时间太小了,他去重试了,所以最终结果大于10000
     */
    @Test
    public void incrTest() throws InterruptedException {
        /**
         * 测试线程安全
         */
        jedisCluster.del("incrNum");
        final AtomicInteger atomicInteger = new AtomicInteger(0);
        final CountDownLatch countDownLatch = new CountDownLatch(10);
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        for(int i = 0 ; i < 10 ; i ++){
            executorService.submit(new Runnable() {
                @Override
                public void run() {
                    //每个线程增加1000次,每次加1
                    for(int j = 0 ; j < 1000 ; j ++){
                        atomicInteger.incrementAndGet();
                        jedisCluster.incr("incrNum");
                    }

                    countDownLatch.countDown();
                }
            });
        }

        countDownLatch.await();
        System.out.println(jedisCluster.get("incrNum"));
        System.out.println(atomicInteger);
    }



    /**
     *
     * @throws InterruptedException
     */
    @Test
    public void hashTest() throws InterruptedException {
        String hashKey = "hashKey";
        jedisCluster.del(hashKey);
        
       /* jedisCluster.hset(hashKey.getBytes(), "int".getBytes(), new byte[]{Integer.valueOf(1).byteValue()});
        System.out.println(jedisCluster.get("int"));*/
        
        
        
        System.out.println(jedisCluster.hset(hashKey, "program_zhangsan", "111"));
        System.out.println(jedisCluster.hexists(hashKey, "program_zhangsan"));
        System.out.println(jedisCluster.hset(hashKey, "program_zhangsan", "222"));

        System.out.println(jedisCluster.hset(hashKey, "program_wangwu", "333"));
        System.out.println(jedisCluster.hset(hashKey, "program_lisi", "444"));

        System.out.println("hkeys:" + jedisCluster.hkeys(hashKey));

        System.out.println(jedisCluster.hgetAll(hashKey));
        System.out.println(jedisCluster.hincrBy(hashKey, "program_zhangsan", 2));
        System.out.println(jedisCluster.hmget(hashKey, "program_zhangsan", "program_lisi"));

        jedisCluster.hdel(hashKey, "program_wangwu");
        System.out.println(jedisCluster.hgetAll(hashKey));


        System.out.println("hsetnx:" + jedisCluster.hsetnx(hashKey, "program_lisi", "666"));

        System.out.println("hvals:" + jedisCluster.hvals(hashKey));

        System.out.println("expire:" + jedisCluster.expire(hashKey, 3));

        for(int i = 0 ; i < 5 ; i ++){
            System.out.println(jedisCluster.hgetAll(hashKey));
            Thread.sleep(1000);
        }

    }
    /**
     * 模拟先进先出队列
     * 生产者 消费者
     */
    @Test
    public void queue() throws InterruptedException {
        String key = prefix + KEY_SPLIT + "queue";
        jedisCluster.del(key);

        System.out.println(jedisCluster.lpush(key, "1", "2", "3"));
        System.out.println(jedisCluster.lpush(key, "4"));
        System.out.println(jedisCluster.lpush(key, "5"));
        System.out.println("lrange:" + jedisCluster.lrange(key, 0, -1));

        System.out.println("lindex[2]:" + jedisCluster.lindex(key, 2));
        //在“3”的前面插入“100”
        System.out.println("linsert:" + jedisCluster.linsert(key, Client.LIST_POSITION.BEFORE, "3", "100"));
        System.out.println("lrange:" + jedisCluster.lrange(key, 0, -1));

        //写进去的顺序是12345,读取出来的也是12345
        for(int i = 0 ; i< 6 ; i ++){
            System.out.println(jedisCluster.rpop(key));
        }

//        如果想达到生产者消费者那种模式需要使用阻塞式队列才可。这个另外写多个客户端测试。
    }



    /**
     * Set集合
     */
    @Test
    public void setTest() throws InterruptedException {
        String keyA = "{" + prefix + KEY_SPLIT + "set}a";
        String keyB = "{" + prefix + KEY_SPLIT + "set}b";
        jedisCluster.del(keyA);
        jedisCluster.del(keyB);

        System.out.println(jedisCluster.sadd(keyA, "a", "b", "c"));//给集合添加数据
        System.out.println(jedisCluster.sadd(keyA, "a"));//给集合添加数据.集合是不可以重复的
        System.out.println(jedisCluster.sadd(keyA, "d"));//给集合添加数据
        System.out.println(jedisCluster.smembers(keyA));//返回集合所有数据
        System.out.println(jedisCluster.scard(keyA));//返回集合长度
        System.out.println("c是否在集合A中:" + jedisCluster.sismember(keyA, "c"));//判断 member 元素是否集合 key 的成员。
        /*
        从 Redis 2.6 版本开始, SRANDMEMBER 命令接受可选的 count 参数:
如果 count 为正数,且小于集合基数,那么命令返回一个包含 count 个元素的数组,数组中的元素各不相同。如果 count 大于等于集合基数,那么返回整个集合。
如果 count 为负数,那么命令返回一个数组,数组中的元素可能会重复出现多次,而数组的长度为 count 的绝对值。
         */
        System.out.println(jedisCluster.srandmember(keyA));//返回集合中的一个随机元素。
        System.out.println(jedisCluster.spop(keyA)); //移除并返回集合中的一个随机元素。
        System.out.println(jedisCluster.smembers(keyA));//返回集合所有数据
        System.out.println("---------");

        /*
        SMOVE 是原子性操作。
如果 source 集合不存在或不包含指定的 member 元素,则 SMOVE 命令不执行任何操作,仅返回 0 。否则, member 元素从 source 集合中被移除,并添加到 destination 集合中去。
当 destination 集合已经包含 member 元素时, SMOVE 命令只是简单地将 source 集合中的 member 元素删除。
当 source 或 destination 不是集合类型时,返回一个错误。
注:不可以在redis-cluster中使用SMOVE:redis.clients.jedis.exceptions.JedisClusterException: No way to dispatch this command to Redis Cluster because keys have different slots.
解决办法可以参考上面的mset命令,使用“{}”来讲可以设置的同一个slot中
         */
        System.out.println(jedisCluster.smove(keyA, keyB, "a"));//返回集合所有数据
        System.out.println("keyA: "+jedisCluster.smembers(keyA));//返回集合所有数据
        System.out.println("keyB: "+jedisCluster.smembers(keyB));//返回集合所有数据

        System.out.println(jedisCluster.sadd(keyB, "a", "f", "c"));//给集合添加数据
        System.out.println(jedisCluster.sdiff(keyA, keyB));//差集 keyA-keyB
        System.out.println(jedisCluster.sinter(keyA, keyB));//交集
        System.out.println(jedisCluster.sunion(keyA, keyB));//并集
    }


    /**
     * sortedSet集合
     */
    @Test
    public void sortedSetTest() throws InterruptedException {
        String keyA = "{"+prefix + KEY_SPLIT + "sortedSet}a";
        String keyB = "{"+prefix + KEY_SPLIT + "sortedSet}b";
        String keyC = "{"+prefix + KEY_SPLIT + "sortedSet}c";

        jedisCluster.del(keyA);
        jedisCluster.del(keyB);

        System.out.println(jedisCluster.zadd(keyA, 10, "aa"));
        Map<String, Double> map = new HashMap<String,Double>();
        map.put("b", 8.0);
        map.put("c", 4.0);
        map.put("d", 6.0);
        System.out.println(jedisCluster.zadd(keyA, map));
        System.out.println(jedisCluster.zcard(keyA));//返回有序集 key 的数量。
        System.out.println(jedisCluster.zcount(keyA, 3, 8));//返回有序集 key 中score某个范围的数量。
        System.out.println("zrange: "+jedisCluster.zrange(keyA, 0, -1));//返回有序集 key 中,指定区间内的成员。按score从小到大
        System.out.println("zrevrange: "+jedisCluster.zrevrange(keyA, 0, -1));//返回有序集 key 中,指定区间内的成员。按score从大到小
        System.out.println("zrangeWithScores: "+jedisCluster.zrangeWithScores(keyA, 0, -1));//返回有序集 key 中,指定区间内的成员。按score从小到大.包含分值

        System.out.println("zscore: "+jedisCluster.zscore(keyA, "aa"));

        /*
        返回有序集 key 中,所有 score 值介于 min 和 max 之间(包括等于 min 或 max )的成员。有序集成员按 score 值递增(从小到大)次序排列。
        具有相同 score 值的成员按字典序(lexicographical order)来排列(该属性是有序集提供的,不需要额外的计算)。
         */
        System.out.println("zrangeByScore: "+jedisCluster.zrangeByScore(keyA, 3, 8));
        System.out.println("zrank: "+jedisCluster.zrank(keyA, "c"));//返回有序集 key 中成员 member 的排名。其中有序集成员按 score 值递增(从小到大)顺序排列。
        System.out.println("zrevrank: "+jedisCluster.zrevrank(keyA, "c"));//返回有序集 key 中成员 member 的排名。其中有序集成员按 score 值递增(从大到小)顺序排列。

        System.out.println("zrem: "+jedisCluster.zrem(keyA, "c", "a"));//移除有序集 key 中的一个或多个成员,不存在的成员将被忽略。
        System.out.println("zrange: "+jedisCluster.zrange(keyA, 0, -1));



        System.out.println("zremrangeByRank: "+jedisCluster.zremrangeByRank(keyA, 1, 2));//按下标删除
        System.out.println("zrange: "+jedisCluster.zrange(keyA, 0, -1));
        System.out.println("zremrangeByScore: "+jedisCluster.zremrangeByScore(keyA, 1, 3));//按评分删除
        System.out.println("zrange: "+jedisCluster.zrange(keyA, 0, -1));

        /*
        接下来这几个操作,需要使用"{}"使得key落到同一个slot中才可以
         */
        System.out.println("-------");
        System.out.println(jedisCluster.zadd(keyB, map));
        System.out.println("zrange: "+jedisCluster.zrange(keyB, 0, -1));
        /*
        ZUNIONSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX]
计算给定的一个或多个有序集的并集,其中给定 key 的数量必须以 numkeys 参数指定,并将该并集(结果集)储存到 destination 。
默认情况下,结果集中某个成员的 score 值是所有给定集下该成员 score 值之 和 。
WEIGHTS
使用 WEIGHTS 选项,你可以为 每个 给定有序集 分别 指定一个乘法因子(multiplication factor),每个给定有序集的所有成员的 score 值在传递给聚合函数(aggregation function)之前都要先乘以该有序集的因子。
如果没有指定 WEIGHTS 选项,乘法因子默认设置为 1 。
AGGREGATE
使用 AGGREGATE 选项,你可以指定并集的结果集的聚合方式。
默认使用的参数 SUM ,可以将所有集合中某个成员的 score 值之 和 作为结果集中该成员的 score 值;使用参数 MIN ,可以将所有集合中某个成员的 最小 score 值作为结果集中该成员的 score 值;而参数 MAX 则是将所有集合中某个成员的 最大 score 值作为结果集中该成员的 score 值。
         */
        System.out.println("zunionstore: "+jedisCluster.zunionstore(keyC, keyA, keyB));//合并keyA和keyB并保存到keyC中
        System.out.println("zrange: "+jedisCluster.zrange(keyC, 0, -1));
        System.out.println("zinterstore: "+jedisCluster.zinterstore(keyC, keyA, keyB));//交集
        System.out.println("zrange: "+jedisCluster.zrange(keyC, 0, -1));
    }

    /**
     * 列表 排序
     */
    @Test
    public void sort() throws InterruptedException {
        String key = prefix + KEY_SPLIT + "queue";
        jedisCluster.del(key);

        System.out.println(jedisCluster.lpush(key, "1", "5", "3", "20", "6"));
        System.out.println(jedisCluster.lrange(key, 0, -1));

        System.out.println(jedisCluster.sort(key));
        System.out.println(jedisCluster.lrange(key, 0, -1));// 原来的数据并没有发生排序
    }



    /**
     * lua脚本
     */
    @Test
    public void script() throws InterruptedException {

        /*
        * 其中 "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 是被求值的 Lua 脚本,数字 2 指定了键名参数的数量,
        * key1 和 key2 是键名参数,分别使用 KEYS[1] 和 KEYS[2] 访问,而最后的 first 和 second 则是附加参数,可以通过 ARGV[1] 和 ARGV[2] 访问它们。
        *
        * 注意,这里一些操作不适用于redis-cluster,主要还是因为不同的key被分配到了不同的slot中
        */
        Object eval = jedisCluster.eval("return {KEYS[1],ARGV[1],ARGV[2]}", 1, "lua", "key1", "dd");
        System.out.println(eval);

        //脚本里使用的所有键都应该由 KEYS 数组来传递:
        //因为:所有的 Redis 命令,在执行之前都会被分析,籍此来确定命令会对哪些键进行操作。因此,对于 EVAL 命令来说,必须使用正确的形式来传递键,才能确保分析工作正确地执行
        System.out.println(jedisCluster.eval("return redis.call('set', KEYS[1], ARGV[1])", 1, "luaTest", "cv"));
        System.out.println(jedisCluster.get("luaTest"));

        //注意这里需要指定KEY,因为这里lua脚本也是和slot挂钩的
        String scriptLoad = jedisCluster.scriptLoad("return redis.call('get', KEYS[1])", "luaTest");//加载脚本
        System.out.println(scriptLoad);//返回的SHA1校验和,后续可以直接使用这个进行操作。
        System.out.println(jedisCluster.scriptExists(scriptLoad, "luaTest"));//检查是否存在

        System.out.println(jedisCluster.evalsha(scriptLoad, 1, "luaTest"));//执行lua脚本

        System.out.println(jedisCluster.scriptFlush("luaTest".getBytes()));//删除KEY as  上的所有lua脚本
        System.out.println(jedisCluster.scriptExists(scriptLoad, "luaTest"));
        System.out.println(jedisCluster.evalsha(scriptLoad, 1, "luaTest"));//脚本已经删除,返回错误:NOSCRIPT No matching script. Please use EVAL.
    }


    /**
     * redis中的lua脚本做了很多限制,防止随机性的发生。比如lua脚本中返回的总是有序的集合。
     * 详情见 http://doc.redisfans.com/script/eval.html - 纯函数脚本
     */
    @Test
    public void scriptFuc() throws InterruptedException {
        String key = "luaTest";
        System.out.println(jedisCluster.del(key));
        System.out.println(jedisCluster.sadd(key, "10","3","7","40","6"));
        System.out.println(jedisCluster.smembers(key));//这里怎么返回的值是有序的?  [3, 6, 7, 10, 40]
        System.out.println(jedisCluster.eval("return redis.call('smembers', KEYS[1])", 1, key));//根据字母序排序  [10, 3, 40, 6, 7]
    }


    /**
     * 使用lua脚本记录日志
     * @throws InterruptedException
     */
    @Test
    public void redisLog() throws InterruptedException {
        //这里后面必须要有key???
        System.out.println(jedisCluster.eval("redis.log(redis.LOG_WARNING, 'Something is wrong with this script.')", 1, "afd"));

    }



    /**
     * 模拟先进先出 定长 队列
     * 参考 http://www.cnblogs.com/stephen-liu74/archive/2012/02/14/2351859.html
     *      https://www.v2ex.com/t/65663
     */
    @Test
    public void queue1() throws InterruptedException {

        String key = prefix + KEY_SPLIT + "queue";
        Long del = jedisCluster.del(key);
        System.out.println(del);
        //我们定义队列长度是5
        int length = 5;

        System.out.println(jedisCluster.lpush(key, "1", "2", "3", "4", "5", "6", "7"));

        List<String> list = jedisCluster.lrange(key, 0, -1);//打印所有数据
        System.out.println(list);

        Long llen = jedisCluster.llen(key);
        System.out.println("目前队列长度:" + llen);

        /**
         * 该命令将仅保留指定范围内的元素
         * 每次lpush以后,就用ltrim进行截取,已达到定长队列(或定长list)的目的
         *
         * 注意:
         *      这里根据实际业务需求,超长了不一定截取丢掉,也可以进行分流限流报警处理或者其他阻塞操作
         */
        System.out.println(jedisCluster.ltrim(key, 0, length - 1));
        System.out.println(jedisCluster.lrange(key, 0, -1));
    }

    /**
     * 分布式互斥锁
     * 一般是通过 set nx ex 实现的
     * 但是这样不完美,具体参考  http://ifeve.com/redis-lock/
     */
    @Test
    public void lock() throws  InterruptedException{
        String key = prefix + KEY_SPLIT + "lock";
        /**
         * 复合命令:    SET KEY VALUE [EX seconds] [PX milliseconds] [NX|XX]
         */
        System.out.println(jedisCluster.set(key, "本机ID", "nx", "ex", 3));
        for (int i = 0 ; i < 6 ; i ++) {
            System.out.println(jedisCluster.get(key));
            Thread.sleep(1000);
        }
    }


    @After
    public void after(){
        try {
            if(jedisCluster != null) jedisCluster.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}



发布了211 篇原创文章 · 获赞 29 · 访问量 36万+

猜你喜欢

转载自blog.csdn.net/wuhualong1314/article/details/64123665