Redis笔记整理(二):Java API使用与Redis分布式集群环境搭建
Redis Java API 使用(一):单机版本 Redis API 使用
Redis 的 Java API 通过 Jedis 来进行操作,因此首先需要 Jedis 的第三方库,因为使用的是 Maven 工程,所以先给出 Jedis 的依赖:
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
基本代码示例
Redis 能提供的命令,Jedis 也都提供了,而且使用起来非常类似,所以下面只是给出了部分操作的代码。
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;
public void setUp() {
host = "uplooking01";
port = 6379;
jedis = new Jedis(host, port);
}
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);
}
}
public void cleanUp() {
jedis.close();
}
}
JedisPool 工具类开发
前面的代码是每次都建立一个 Jedis 的连接,这样比较消耗资源,可以使用 JedisPool 来解决这个问题,同时为了提高后面的开发效率,可以基于 JedisPool 来开发一个工具类。
JedisUtil.java
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();<span class="hljs-comment">//jedis连接池中最大的连接个数</span> poolConfig.setMaxTotal(Integer.valueOf(prop.getProperty(JedisConstants.JEDIS_MAX_TOTAL))); <span class="hljs-comment">//jedis连接池中最大的空闲连接个数</span> poolConfig.setMaxIdle(Integer.valueOf(prop.getProperty(JedisConstants.JEDIS_MAX_IDLE))); <span class="hljs-comment">//jedis连接池中最小的空闲连接个数</span> poolConfig.setMinIdle(Integer.valueOf(prop.getProperty(JedisConstants.JEDIS_MIN_IDLE))); <span class="hljs-comment">//jedis连接池最大的等待连接时间 ms值</span> poolConfig.setMaxWaitMillis(Long.valueOf(prop.getProperty(JedisConstants.JEDIS_MAX_WAIT_MILLIS))); <span class="hljs-comment">//表示jedis的服务器主机名</span> <span class="hljs-type">String</span> <span class="hljs-variable">host</span> <span class="hljs-operator">=</span> prop.getProperty(JedisConstants.JEDIS_HOST); <span class="hljs-type">String</span> <span class="hljs-variable">JEDIS_PORT</span> <span class="hljs-operator">=</span> <span class="hljs-string">"jedis.port"</span>; <span class="hljs-type">int</span> <span class="hljs-variable">port</span> <span class="hljs-operator">=</span> Integer.valueOf(prop.getProperty(JedisConstants.JEDIS_PORT)); <span class="hljs-comment">//表示jedis的服务密码</span> <span class="hljs-type">String</span> <span class="hljs-variable">password</span> <span class="hljs-operator">=</span> prop.getProperty(JedisConstants.JEDIS_PASSWORD); jedisPool = <span class="hljs-keyword">new</span> <span class="hljs-title class_">JedisPool</span>(poolConfig, host, port, <span class="hljs-number">10000</span>); } <span class="hljs-keyword">catch</span> (IOException e) { e.printStackTrace(); }
}
/**
- 提供了 Jedis 的对象
- @return
*/
public static Jedis getJedis() {
return jedisPool.getResource();
}
/**
- 资源释放
@param jedis
*/
public static void returnJedis(Jedis jedis) {
jedis.close();
}
}
JedisConstants.java
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.properties
##########################################
###
### 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 的操作:
// 获得 Jedis 连接对象
Jedis jedis = JedisUtil.getJedis();
// 释放 Jedis 对象资源
JedisUtil.returnJedis(jedis);
Redis 分布式集群环境搭建
Redis 集群理论知识
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 主从复制环境和分布式环境的搭建后,相信会有非常直观的理解。
Redis 主从复制集群安装
这里使用三台设备,环境说明如下:
uplooking01 master uplooking02 slave uplooking03 slave
即uplooking01为主节点,02和03为从节点,主节点主要负责写,从节点主要负责读,不能写。
另外在两台从服务器上还会配置使用密码,测试一下使用密码时的连接方式(注意主服务器没有设置密码)。
下面的配置会对这些需求有所体现。
uplooking01 redis.conf 配置如下:
bind uplooking01
daemonize yes(后台运行)
logfile /opt/redis-3.2.0/logs/redis.log(日志文件,目录必须存在)
uplooking02 redis.conf 配置如下:
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 配置如下:
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);
Redis 分布式集群安装部署
集群说明:
1.前面搭建的只是主从复制的集群,这意味着,数据在三台机器上都是一样的,其目的只是为了读写分离,提高读的效率
同时也可以起到冗余的作用,主节点一旦出现故障,从节点可以替换,但显然,这只是集群,而不是分布式。
2.但是可能会出现一个问题,就是当数据量过大时,所有的数据都保存在同一个节点上
(虽然两台做了备份,但因为保存的数据都是一样的,所以看做一个节点),
单台服务器的数据存储压力会很大,因此,可以考虑使用分布式的环境来保存,这就是 Redis 的分布式集群。
分布式:数据分成几份保存在不同的设备上
集群:对于相同的数据,都会有至少一个副本进行保存。
这可以类比 hadoop 中的 hdfs 或者是 kafka 中的 partition(topic 可以设置 partition 数量和副本因子)
3.在Redis中,搭建分布式集群环境至少需要6个节点,因此出于设备的考虑,这里会在同一台设备上操作
也就是说,这里搭建的是伪分布式环境,3 个为主节点,另外 3 个分别为其从节点,用来保存其副本数据。
根据前面的理论知识,在分布式环境中,key 值会进行如下的计算:
CRC16(16) % 16384
来计算 key 值属于哪一个槽,而对于我们的环境,每个主节点的槽位数量大概是 16384 / 3 = 5461
1. 解压安装包
[uplooking@uplooking01 ~]$ mkdir -p app/redis-cluster
[uplooking@uplooking01 ~]$ tar -zxvf soft/redis-3.2.0.tar.gz -C app/redis-cluster/
2.编译安装
[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
3. 创建 Redis 节点
[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
4. 修改各个节点的配置
以 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 等。
5. 启动各个节点
先创建一个批量启动的脚本:
[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 ..
然后再执行脚本启动。
6. 查看服务
[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
7. 创建集群(核心)
现在就是要使用前面准备好的 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 的方式。
8. 测试
[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 Java API 使用(二):Cluster API 使用
前面的代码只适合操作单机版本的 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 分别在不同的节点上,则会报异常,所以不建议使用此种方式来获取数据。