[TOC]java
Redis的Java API经过Jedis来进行操做,所以首先须要Jedis的第三方库,由于使用的是Maven工程,因此先给出Jedis的依赖:node
<dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> </dependency>
Redis能提供的命令,Jedis也都提供了,并且使用起来很是相似,因此下面只是给出了部分操做的代码。web
package com.uplooking.bigdata; import org.junit.After; import org.junit.Before; import org.junit.Test; import redis.clients.jedis.Jedis; import java.util.List; import java.util.Map; import java.util.Set; /** * Redis操做之java API * jedis 是咱们操做Redis的java api的入口 * 一个Jedis对象,就表明了一个Redis的链接 * CRUD */ public class RedisTest { private Jedis jedis; private String host; private int port; @Before public void setUp() { host = "uplooking01"; port = 6379; jedis = new Jedis(host, port); } @Test public void testCRUD() { //后去全部的key的集合 Set<String> keys = jedis.keys("*"); // jedis.select(index); 指定要执行操做的数据库,默认操做的是0号数据 System.out.println(keys); //string System.out.println("**************String**************"); //删除redis中的key nam1 Long del = jedis.del("nam1"); System.out.println(del == 1L ? "删除成功~" : "删除失败~"); List<String> mget = jedis.mget("name", "age"); System.out.println(mget); //hash System.out.println("**************Hash**************"); Map<String, String> person = jedis.hgetAll("person"); //keyset //entryset for (Map.Entry<String, String> me : person.entrySet()) { String field = me.getKey(); String value = me.getValue(); System.out.println(field + "---" + value); } //list System.out.println("**************List**************"); List<String> seasons = jedis.lrange("season", 0, -1); for (String season : seasons) { System.out.println(season); } //set System.out.println("**************Set**************"); Set<String> nosql = jedis.smembers("nosql"); for (String db : nosql) { System.out.println(db); } //zset System.out.println("**************Zset**************"); Set<String> website = jedis.zrange("website", 0, -1); for (String ws : website) { System.out.println(ws); } } @After public void cleanUp() { jedis.close(); } }
前面的代码是每次都创建一个Jedis的链接,这样比较消耗资源,可使用JedisPool来解决这个问题,同时为了提升后面的开发效率,能够基于JedisPool来开发一个工具类。redis
package com.uplooking.bigdata.common.util.redis; import com.uplooking.bigdata.constants.redis.JedisConstants; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; import java.io.IOException; import java.util.Properties; /** * Redis Java API 操做的工具类 * 主要为咱们提供 Java操做Redis的对象Jedis 模仿相似的数据库链接池 * * JedisPool */ public class JedisUtil { private JedisUtil() {} private static JedisPool jedisPool; static { Properties prop = new Properties(); try { prop.load(JedisUtil.class.getClassLoader().getResourceAsStream("redis/redis.properties")); JedisPoolConfig poolConfig = new JedisPoolConfig(); //jedis链接池中最大的链接个数 poolConfig.setMaxTotal(Integer.valueOf(prop.getProperty(JedisConstants.JEDIS_MAX_TOTAL))); //jedis链接池中最大的空闲链接个数 poolConfig.setMaxIdle(Integer.valueOf(prop.getProperty(JedisConstants.JEDIS_MAX_IDLE))); //jedis链接池中最小的空闲链接个数 poolConfig.setMinIdle(Integer.valueOf(prop.getProperty(JedisConstants.JEDIS_MIN_IDLE))); //jedis链接池最大的等待链接时间 ms值 poolConfig.setMaxWaitMillis(Long.valueOf(prop.getProperty(JedisConstants.JEDIS_MAX_WAIT_MILLIS))); //表示jedis的服务器主机名 String host = prop.getProperty(JedisConstants.JEDIS_HOST); String JEDIS_PORT = "jedis.port"; int port = Integer.valueOf(prop.getProperty(JedisConstants.JEDIS_PORT)); //表示jedis的服务密码 String password = prop.getProperty(JedisConstants.JEDIS_PASSWORD); jedisPool = new JedisPool(poolConfig, host, port, 10000); } catch (IOException e) { e.printStackTrace(); } } /** * 提供了Jedis的对象 * @return */ public static Jedis getJedis() { return jedisPool.getResource(); } /** * 资源释放 * @param jedis */ public static void returnJedis(Jedis jedis) { jedis.close(); } }
package com.uplooking.bigdata.constants.redis; /** * 专门用于存放Jedis的常量类 */ public interface JedisConstants { //表示jedis的服务器主机名 String JEDIS_HOST = "jedis.host"; //表示jedis的服务的端口 String JEDIS_PORT = "jedis.port"; //表示jedis的服务密码 String JEDIS_PASSWORD = "jedis.password"; //jedis链接池中最大的链接个数 String JEDIS_MAX_TOTAL = "jedis.max.total"; //jedis链接池中最大的空闲链接个数 String JEDIS_MAX_IDLE = "jedis.max.idle"; //jedis链接池中最小的空闲链接个数 String JEDIS_MIN_IDLE = "jedis.min.idle"; //jedis链接池最大的等待链接时间 ms值 String JEDIS_MAX_WAIT_MILLIS = "jedis.max.wait.millis"; }
########################################## ### ### redis的配置文件 ### ########################################## ###表示jedis的服务器主机名 jedis.host=uplooking01 ###表示jedis的服务的端口 jedis.port=6379 ###表示jedis的服务密码 jedis.password=uplooking ###jedis链接池中最大的链接个数 jedis.max.total=60 ###jedis链接池中最大的空闲链接个数 jedis.max.idle=30 ###jedis链接池中最小的空闲链接个数 jedis.min.idle=5 ###jedis链接池最大的等待链接时间 ms值 jedis.max.wait.millis=30000
后面就能够很是方便地使用这个工具类来进行Redis的操做:sql
// 得到Jedis链接对象 Jedis jedis = JedisUtil.getJedis(); // 释放Jedis对象资源 JedisUtil.returnJedis(jedis);
Redis集群是一个分布式Redis存储架构,能够在多个节点之间进行数据共享,解决Redis高可用、可扩展等问题。 Redis集群提供了如下两个好处 1.将数据自动切分(split)到多个节点 2.当集群中的某一个节点故障时,redis还可继续处理客户端的请求 一个Redis集群包含16384个哈希槽(hash slot),数据库中的每一个数据都属于这16384个哈希槽中的一个。 集群使用公式CRC16(key)%16384来计算key属于哪个槽。集群中的每个节点负责处理一部分哈希槽。 集群中的主从复制 集群中的每一个节点都有1个到N个复制品,其中一个为主节点,其他为从节点,若是主节点下线了, 集群就会把这个主节点的一个从节点设置为新的主节点,继续工做。这个集群就不会由于一个主节点的下线而没法正常工做。 若是某一个主节点和它全部的从节点都下线的话,redis集群就中止工做了。 Redis集群不保证数据的强一致性,在特定的状况下,redis集群会丢失已经执行过的命令。 使用异步复制(asynchronous replication)是Redis集群可能会丢失写命令的其中一个缘由, 有时候因为网络缘由,若是网络断开时间太长,redis集群就会启用新的主节点,以前发给主节点的数据聚会丢失。
上面的理论知识,在完成下面Reids主从复制环境和分布式环境的搭建后,相信会有很是直观的理解。数据库
这里使用三台设备,环境说明以下:vim
uplooking01 master uplooking02 slave uplooking03 slave 即uplooking01为主节点,02和03为从节点,主节点主要负责写,从节点主要负责读,不能写。 另外在两台从服务器上还会配置使用密码,测试一下使用密码时的链接方式(注意主服务器没有设置密码)。 下面的配置会对这些需求有所体现。
uplooking01 redis.conf配置以下:api
bind uplooking01 daemonize yes(后台运行) logfile /opt/redis-3.2.0/logs/redis.log(日志文件,目录必须存在)
uplooking02 redis.conf配置以下:ruby
bind uplooking02 daemonize yes(后台运行) logfile /opt/redis-3.2.0/logs/redis.log(日志文件,目录必须存在) slave-read-only yes requirepass uplooking slaveof uplooking01 6379
uplooking03 redis.conf配置以下:bash
bind uplooking03 daemonize yes(后台运行) logfile /opt/redis-3.2.0/logs/redis.log(日志文件,目录必须存在) slave-read-only yes requirepass uplooking slaveof uplooking01 6379
上面的配置完成后,在两台从服务器上分别启动redis即完成了主从复制集群的配置,须要注意以下问题:
1.认证的问题 若是须要链接uplooking02或者uplooking03,那么须要加上密码,不然没法完成认证。 有两种方式: 能够在链接时就指定密码:redis-cli -h uplooking03 -a uplooking 也能够先链接,到终端后再认证:auth uplooking 2.数据读写的问题 读:三台服务器上都能完成数据的读 写:只能在主节点上完成数据的写入 3.Java API的使用问题 在前面使用的代码中,若是链接的是从服务器,则还须要配置密码 以咱们开发的JedisPool工具类为例,在建立JedisPool时须要指定密码: jedisPool = new JedisPool(poolConfig, host, port, 10000, password);
集群说明:
1.前面搭建的只是主从复制的集群,这意味着,数据在三台机器上都是同样的,其目的只是为了读写分离,提升读的效率 同时也能够起到冗余的做用,主节点一旦出现故障,从节点能够替换,但显然,这只是集群,而不是分布式。 2.可是可能会出现一个问题,就是当数据量过大时,全部的数据都保存在同一个节点上 (虽然两台作了备份,但由于保存的数据都是同样的,因此看作一个节点), 单台服务器的数据存储压力会很大,所以,能够考虑使用分布式的环境来保存,这就是Redis的分布式集群。 分布式:数据分红几份保存在不一样的设备上 集群:对于相同的数据,都会有至少一个副本进行保存。 这能够类比hadoop中的hdfs或者是kafka中的partition(topic能够设置partition数量和副本因子) 3.在Redis中,搭建分布式集群环境至少须要6个节点,所以出于设备的考虑,这里会在同一台设备上操做 也就是说,这里搭建的是伪分布式环境,3个为主节点,另外3个分别为其从节点,用来保存其副本数据。 根据前面的理论知识,在分布式环境中,key值会进行以下的计算: CRC16(16) % 16384 来计算key值属于哪个槽,而对于咱们的环境,每一个主节点的槽位数量大概是16384 / 3 = 5461
[uplooking@uplooking01 ~]$ mkdir -p app/redis-cluster [uplooking@uplooking01 ~]$ tar -zxvf soft/redis-3.2.0.tar.gz -C app/redis-cluster/
[uplooking@uplooking01 ~]$ cd app/redis-cluster/redis-3.2.0/ [uplooking@uplooking01 redis-3.2.0]$ pwd /home/uplooking/app/redis-cluster/redis-3.2.0 [uplooking@uplooking01 redis-3.2.0]$ make [uplooking@uplooking01 redis-3.2.0]$ make install PREFIX=/home/uplooking/app/redis-cluster/redis-3.2.0
[uplooking@uplooking01 redis-cluster]$ mv redis-3.2.0/ 7000 [uplooking@uplooking01 redis-cluster]$ cp -r 7000 7001 [uplooking@uplooking01 redis-cluster]$ cp -r 7000 7002 [uplooking@uplooking01 redis-cluster]$ cp -r 7000 7003 [uplooking@uplooking01 redis-cluster]$ cp -r 7000 7004 [uplooking@uplooking01 redis-cluster]$ cp -r 7000 7005
以7000为例:
daemonize yes //配置redis后台运行 bind uplooking01 //绑定主机uplooking01 logfile "/home/uplooking/app/redis-cluster/7000/redis-7000.log" //注意目录要存在 pidfile /var/run/redis-7000.pid //pidfile文件对应7000,7002,7003 port 7000 //端口 cluster-enabled yes //开启集群 把注释#去掉 cluster-config-file nodes-7000.conf //集群的配置 配置文件首次启动自动生成 cluster-node-timeout 15000 //请求超时 设置15秒够了 appendonly yes //aof日志开启 有须要就开启,它会每次写操做都记录一条日志
在其它的节点上,只须要修改成7001,7002…便可。
技巧:配置完成7000后,能够直接复制到其它节点,cp redis.conf ../7001,而后再充分利用vim中的1,$s///g将7000替换为其它数字,如7001等。
先建立一个批量启动的脚本:
[uplooking@uplooking01 redis-cluster]$ cat start-all.sh #!/bin/bash cd 7000 bin/redis-server ./redis.conf cd .. cd 7001 bin/redis-server ./redis.conf cd .. cd 7002 bin/redis-server ./redis.conf cd .. cd 7003 bin/redis-server ./redis.conf cd .. cd 7004 bin/redis-server ./redis.conf cd .. cd 7005 bin/redis-server ./redis.conf cd ..
而后再执行脚本启动。
[uplooking@uplooking01 redis-cluster]$ ps -ef | grep redis 500 1460 1 0 01:17 ? 00:00:01 bin/redis-server uplooking01:7000 [cluster] 500 1464 1 0 01:17 ? 00:00:01 bin/redis-server uplooking01:7001 [cluster] 500 1468 1 0 01:17 ? 00:00:01 bin/redis-server uplooking01:7002 [cluster] 500 1472 1 0 01:17 ? 00:00:01 bin/redis-server uplooking01:7003 [cluster] 500 1474 1 0 01:17 ? 00:00:01 bin/redis-server uplooking01:7004 [cluster] 500 1480 1 0 01:17 ? 00:00:01 bin/redis-server uplooking01:7005 [cluster] 500 3233 1018 0 01:53 pts/0 00:00:00 grep redis
如今就是要使用前面准备好的redis节点,将其串联起来搭建集群。官方提供了一个工具:redis-trib.rb($REDIS_HOME/src 使用ruby编写的一个程序,因此须要安装ruby):
$ sudo yum -y install ruby ruby-devel rubygems rpm-build
再用gem这个命令安装redis接口(gem是ruby的一个工具包):
gem install redis [ -v 3.2.0] #[]中为可选项制定具体的软件版本 # 在我安装时,提示ruby版本须要>=2.2.2,可是上面接上redis接口的版本后就没有问题了。
接下来运行一下redis-trib.rb:
[uplooking@uplooking01 7000]$ src/redis-trib.rb create --replicas 1 192.168.56.101:7000 192.168.56.101:7001 192.168.56.101:7002 192.168.56.101:7003 192.168.56.101:7004 192.168.56.101:7005 >>> Creating cluster >>> Performing hash slots allocation on 6 nodes... Using 3 masters: 192.168.56.101:7000 192.168.56.101:7001 192.168.56.101:7002 Adding replica 192.168.56.101:7003 to 192.168.56.101:7000 Adding replica 192.168.56.101:7004 to 192.168.56.101:7001 Adding replica 192.168.56.101:7005 to 192.168.56.101:7002 M: 497bce5118057198afb0511cc7b88479bb0c3938 192.168.56.101:7000 slots:0-5460 (5461 slots) master M: f0568474acad5c707f25843add2d68455d2cbbb2 192.168.56.101:7001 slots:5461-10922 (5462 slots) master M: ebe86ea74af5612e6393c8e5c5b3363928a4b7b2 192.168.56.101:7002 slots:10923-16383 (5461 slots) master S: c99c55ab3fcea2d65ca3be5b4786390a6e463ea2 192.168.56.101:7003 replicates 497bce5118057198afb0511cc7b88479bb0c3938 S: 0a847801493a45d32487d701cd0fe37790d4b2f9 192.168.56.101:7004 replicates f0568474acad5c707f25843add2d68455d2cbbb2 S: 7f9e4bec579fda23a574a62d362a04463140bbc2 192.168.56.101:7005 replicates ebe86ea74af5612e6393c8e5c5b3363928a4b7b2 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.56.101:7000) M: 497bce5118057198afb0511cc7b88479bb0c3938 192.168.56.101:7000 slots:0-5460 (5461 slots) master M: f0568474acad5c707f25843add2d68455d2cbbb2 192.168.56.101:7001 slots:5461-10922 (5462 slots) master M: ebe86ea74af5612e6393c8e5c5b3363928a4b7b2 192.168.56.101:7002 slots:10923-16383 (5461 slots) master M: c99c55ab3fcea2d65ca3be5b4786390a6e463ea2 192.168.56.101:7003 slots: (0 slots) master replicates 497bce5118057198afb0511cc7b88479bb0c3938 M: 0a847801493a45d32487d701cd0fe37790d4b2f9 192.168.56.101:7004 slots: (0 slots) master replicates f0568474acad5c707f25843add2d68455d2cbbb2 M: 7f9e4bec579fda23a574a62d362a04463140bbc2 192.168.56.101:7005 slots: (0 slots) master replicates ebe86ea74af5612e6393c8e5c5b3363928a4b7b2 [OK] All nodes agree about slots configuration. >>> Check for open slots... >>> Check slots coverage... [OK] All 16384 slots covered.
仔细查看其提示,会对Redis分布式集群有一个更加清晰的理解。另外须要注意的是,因为redis-trib.rb 对域名或主机名支持很差,故在建立集群的时候要使用ip:port的方式。
[uplooking@uplooking01 7000]$ bin/redis-cli -h uplooking01 -p 7000 -c uplooking01:7000> set name xpleaf -> Redirected to slot [5798] located at 192.168.56.101:7001 OK 192.168.56.101:7001> get name "xpleaf" 192.168.56.101:7001> [uplooking@uplooking01 7000]$ bin/redis-cli -h uplooking01 -p 7004 -c uplooking01:7004> get name -> Redirected to slot [5798] located at 192.168.56.101:7001 "xpleaf" 192.168.56.101:7001> [uplooking@uplooking01 7000]$ bin/redis-cli -h uplooking01 -p 7004 -c uplooking01:7004> set name yyh -> Redirected to slot [5798] located at 192.168.56.101:7001 OK 192.168.56.101:7001> get name "yyh" [uplooking@uplooking01 7000]$ bin/redis-cli -h uplooking01 -p 7002 -c uplooking01:7002> keys * (empty list or set) uplooking01:7002> get name -> Redirected to slot [5798] located at 192.168.56.101:7001 "yyh"
上面的测试能够充分说明下面几个问题:
1.分布式 数据是分布式存储的,根据key的不一样会保存到不一样的主节点上。 2.数据备份 从节点是做为备份节点的,跟前面的主从复制集群同样,只是用来读数据,当须要写或修改数据时,须要切换到主节点上。
前面的代码只适合操做单机版本的Redis,若是使用的是分布式的Redis集群,那么就须要修改一下代码,这里,咱们直接开发一个工具类JedisClusterUtil
,以下:
package com.uplooking.bigdata.common.util.redis; import redis.clients.jedis.*; import java.io.IOException; import java.util.HashSet; import java.util.Properties; import java.util.Set; /** * Redis Java API 操做的工具类 * 专门负责redis的cluster模式 */ public class JedisClusterUtil { private JedisClusterUtil() {} private static JedisCluster jedisCluster; static { Set<HostAndPort> nodes = new HashSet<HostAndPort>(); nodes.add(new HostAndPort("uplooking01", 7000)); nodes.add(new HostAndPort("uplooking01", 7001)); nodes.add(new HostAndPort("uplooking01", 7002)); nodes.add(new HostAndPort("uplooking01", 7003)); nodes.add(new HostAndPort("uplooking01", 7004)); nodes.add(new HostAndPort("uplooking01", 7005)); jedisCluster = new JedisCluster(nodes);//获得的是redis的集群模式 } /** * 提供了Jedis的对象 * @return */ public static JedisCluster getJedis() { return jedisCluster; } /** * 资源释放 * @param jedis */ public static void returnJedis(JedisCluster jedis) { try { jedis.close(); } catch (IOException e) { e.printStackTrace(); } } }
在使用时须要注意的是,JedisCluster在使用mget等API操做时,是不容许同时在多个节点上获取数据的,例如:List<String> mget = jedis.mget("name", "age");,若是name和age分别在不一样的节点上,则会报异常,因此不建议使用此种方式来获取数据。