Redis是基於內存的NoSql數據庫,同時以其卓越的讀寫性能聞名業內,並且我在這篇博客Redis持久化機制原理分析與解惑-爲什麼Redis進行RDB持久化數據時,新起一個進程而不是在原進程中起一個線程中講過Redis的兩種數據持久化方式,但是如果Redis讀寫壓力較大的情況下,將所有的數據都存在一個實例中,這將會大大降低Redis的性能,但是我們可以使用Redis提供的主從複製功能來實現數據冗餘和讀寫分離。
Redis在2.8版本之前,對於從機器每次重連到主都會發送SYNC命令進行全量複製,在2.8版本之後從向主發送PSYNC支持斷點續傳的複製方式,我們接下來依次看看這兩種不同的主從複製方式。
(一)我們先看2.6版本全量主從複製流程:
(1) 從節點起動起來之後主動向主節點發送SYNC命令要求同步數據
(2) 主節點收到SYNC命令之後,fork出一個子進程非阻塞主實例的執行RBD持久化機制,執行完RDB之後將rdb文件發給從節點,
在執行RDB期間主節點將寫命令寫入緩存,當主節點接到多個從發送SYNC命令之後只執行一次RDB
(3) 從節點收到rdb文件之後載入內存,這個過程從節點可以使用舊數據響應客戶端請求,也可以返回一個錯誤信息
(4) 主節點將緩存中的寫命令以Redis命令協議的形式發給從節點
(二)下面先使用版本redis-2.6.16.tar.gz構建一個主從架構
(1)解壓 tar zxvf redis-2.6.16.tar.gz
(2)進入到解壓後的文件夾redis-2.6.16 執行make命令
(3)複製redis.conf配置文件 爲redis_6379.conf、redis_6380.conf、redis_6381.conf
依次修改內容如下:
#主節點配置文件redis_6379.conf
port 6379
#配置log文件
logfile master_6379.log
#主節點關閉持久化機制
#save 900 1
#save 300 10
#save 60 10000
#內存設置爲100M,在實際使用中 這裏內存實際數值設置要大於實際所需,原因是要爲主寫命令緩存區預留出空間
maxmemory 104857600
#從節點配置文件redis_6380.conf
port 6380
#配置log文件
logfile slave_6380.log
maxmemory 104857600
#設置將該節點設置爲 6379端口實例的從節點
slaveof 127.0.0.1 6379
#從節點配置文件redis_6381.conf
port 6381
#配置log文件
logfile slave_6381.log
maxmemory 104857600
#設置將該節點設置爲 6379端口實例的從節點
slaveof 127.0.0.1 6379
(4)進入到src目錄下依次執行如下命令:
nohup ./redis-server ../redis_6379.conf &
nohup ./redis-server ../redis_6380.conf &
nohup ./redis-server ../redis_6381.conf &
(5)我們通過log來看一下主從複製過程
這份是主節點的log
這份是其中一個從節點的log
從主從的log中也可以清晰的看到主從複製的流程,其中從節點我們配置文件中設置了slave-serve-stale-data yes,這樣從節點就會非阻塞的方式加載rdb文件,並且使用舊數據響應客戶端讀請求。
(三)登錄客戶端查看主從信息
使用如下命令依次登錄主從節點
./redis-cli -h 127.0.0.1 -p 6379
./redis-cli -h 127.0.0.1 -p 6381
使用info命令看到主從的信息:
主節點主從複製信息如下圖,可以看到主節點中有兩個從節點,並且從節點都在線
從節點主從複製信息,記錄了主節點的相關信息
(四)使用telnet 查看Redis主節點以Redis命令協議形式發送緩存寫命令
這裏提供一個連接Redis服務端的Jedis工具類給大家
/**
* 連接redis服務的工具類
* @author yujie.wang3
* @since 09/08/2017
*/
public final class RedisUtil {
//Redis服務器IP
private static String ADDR = "10.4.36.87";
//Redis的端口號
private static int PORT = 6379;
//可用連接實例的最大數目,默認值爲8;
//如果賦值爲-1,則表示不限制;如果pool已經分配了maxActive個jedis實例,則此時pool的狀態爲exhausted(耗盡)。
private static int MAX_ACTIVE = 100;
//控制一個pool最多有多少個狀態爲idle(空閒的)的jedis實例,默認值也是8。
private static int MAX_IDLE = 20;
//等待可用連接的最大時間,單位毫秒,默認值爲-1,表示永不超時。如果超過等待時間,則直接拋出JedisConnectionException;
private static int MAX_WAIT = 10000;
private static int TIMEOUT = 10000;
//在borrow一個jedis實例時,是否提前進行validate操作;如果爲true,則得到的jedis實例均是可用的;
private static boolean TEST_ON_BORROW = true;
private static JedisPool jedisPool = null;
/**
* 初始化Redis連接池
*/
static {
try {
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxActive(MAX_ACTIVE);
config.setMaxIdle(MAX_IDLE);
config.setMaxWait(MAX_WAIT);
config.setTestOnBorrow(TEST_ON_BORROW);
jedisPool = new JedisPool(config, ADDR, PORT);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 獲取Jedis實例
* @return
*/
public synchronized static Jedis getJedis() {
try {
if (jedisPool != null) {
Jedis resource = jedisPool.getResource();
return resource;
} else {
return null;
}
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 釋放jedis資源
* @param jedis
*/
public static void returnResource(final Jedis jedis) {
if (jedis != null) {
jedisPool.returnResource(jedis);
}
}
}
我們使用如下代碼寫入和讀取key
/**
* 測試Redis主節點以Redis命令協議的形式向從節點發送寫命令
* @author yujie.wang
* @since 09/08/2017
*/
public class RedisTest {
public static void main(String[] args) {
String key0 = "yujie_";
String value = "";
for(int i = 0; i <= 10000; i++){
String key = key0 + String.valueOf(i);
value = String.valueOf(i);
//寫入key
addKeys(key, value);
System.out.println("add key"+ key + " value: "+ value);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//讀取key
System.out.println("get_Key: " + key + " : "+ getKey(key));
}
}
public static String getKey(String key) {
Jedis client = RedisUtil.getJedis();
String value = client.get(key);
RedisUtil.returnResource(client);
return value;
}
public static void addKeys(String key ,String value){
Jedis client = RedisUtil.getJedis();
client.set(key, value);
RedisUtil.returnResource(client);
}
}
啓動程序之後我們使用telnet 127.0.0.1 6379 訪問主節點,併發送sync命令:
從這個過程可以看到 主節點首先傳過來一個包含內存中數據的rdb文件,之後會以redis命令協議的形式發送寫命令,並且每寫一個命令就會發送一個命令。基於此我們可以實現redis的讀寫分離機制。