Redis筆記整理(二):Java API使用與Redis分布式集群環境搭建
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; @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(); } }
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(); //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(); } }
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分別在不同的節點上,則會報異常,所以不建議使用此種方式來獲取數據。
Redis筆記整理(二):Java API使用與Redis分布式集群環境搭建