在Java Spring 專案中,資料與遠端資料庫的頻繁互動對伺服器的記憶體消耗比較大,而 Redis 的特性可以有效解決這樣的問題。
Redis 的幾個特性:
下面,我將和大家一起學習分享 Redis 的相關知識。
Redis(Remote Dictionary Server ),即遠端字典服務,是一個開源的使用ANSI C語言編寫、支援網路、可基於記憶體亦可持久化的紀錄檔型、Key-Value 資料庫,並提供多種語言的API。
Redis會週期性地把更新的資料寫入磁碟或者把修改操作寫入追加的記錄檔案,並且在此基礎上實現了 master-slave (主從)同步。
Redis適用的幾個場景:
[root@localhost bin]# redis-cli -p 6379 #進入使用者端
127.0.0.1:6379>
127.0.0.1:6379> select 3 #選擇資料庫
OK
127.0.0.1:6379[3]> dbsize #檢視資料庫大小
(integer) 0
Redis 在6.0版本以後開始支援多執行緒,Redis 是基於記憶體操作,CPU 效能不是 Redis 的瓶頸,Redis 的效能取決機器的記憶體和網路頻寬是否良好。
Redis 預設是關閉多執行緒的,即預設只使用單執行緒。
為什麼 Redis 是單執行緒還這麼快?
原因:Redis 所有的資料都是存放在記憶體中的,單執行緒的操作沒有上下文的切換,從而達到很高的效率。
GitHub 地址:Release 5.0.10 · redis/redis · GitHub
解壓縮後:雙擊 redis-server.exe 即可在 Windows 上開啟服務。
在 Redis 的安裝目錄切換到 cmd,執行:redis-server.exe redis.windows.conf;
在保持上一個視窗開啟的情況下,再次在 Redis 目錄下開啟另一個 cmd 視窗,執行:redis-cli.exe -h 127.0.0.1 -p 6379;
當輸入 ping 時,返回 PONG 代表服務建立成功。
注:雖然在 Windows 上使用很方便,但官方推薦在 Linux 上使用 Redis 。
官網下載最新:Download | Redis redis-7.0.2.tar.gz
放入 Linux 資料夾中,比如可以放入 home/dingding 目錄下後解壓:
tar -zxvf redis-5.0.8.tar.gz
可以進入解壓後的檔案後:
看到組態檔:redis.conf,上述操作只是解壓操作,真正安裝往下看:
安裝 Redis :
1.還是在上述解壓目錄中,首先安裝 gcc-c++:
yum install gcc-c++
2.make 設定好所有的環境:
make
3.檢查 make 後是否成功安裝所需環境:
make install
安裝好後的預設路徑為:/usr/local/bin,進入後可以看到 redis-cli、redis-server 等內容。
4.將之前 redis 解壓目錄資料夾(home/dingding)中的 redis.conf 檔案複製到 redis 的安裝目錄(/usr/local/bin)下:
cp /opt/redis-7.0.2/redis.conf bin
注:或者可以在 bin目錄中新建一個 config 資料夾:mkdir config,上述命令則變為:
cp /opt/redis-7.0.2/redis.conf config
5.Redis 不是預設後臺啟動的,當前 /bin 目錄下應該有了複製過來的 redis.conf檔案,此時需要修改設定資訊:
vim redis.conf
將 daemonize no 改為 daemonize yes 即可保證 Redis 在後臺時可以執行:
遠端連線 Redis 服務:將 bind 127.0.0.1註釋掉,同時*protected-mode yes* 改成 protected-mode no
6.在 bin 目錄下,啟動 Redis 服務:需要用指定檔案下的 redis.conf 檔案來啟動:
redis-server bin/redis.conf
或者:
redis-server config/redis.conf
7.在 bin 目錄下,進入 redis client 終端,連線預設的埠號:
redis-cli -p 6379
8.在 任意 目錄下,確定 Redis 服務是否開啟:
ps -ef|grep redis
9.在 任意 目錄下,關閉 Redis 服務:
shutdown
exit
Redis-benchmark 是 Redis 中自帶的效能測試外掛,可能通過一系列的命令來測試 Redis 的效能。
這些命令都在 Redis 的 Linux 安裝目錄下進行。
redis-benchmark -h localhost -p 6379 -c 100 -n 100000
測試結果解讀:
100000次請求在4.84秒內完成,共100個並行使用者端,平均每秒完成27000+次請求,每次寫入3個位元組,以上都是在1臺伺服器上完成。
127.0.0.1:6379> flushall //清空資料庫
OK
127.0.0.1:6379> set name zzz //設定key為name,value為zzz
OK
127.0.0.1:6379> set age 3 //設定key為age,值為3
OK
127.0.0.1:6379> keys * //檢視所有的 key
1) "name"
2) "age"
127.0.0.1:6379> exists name //檢查是否存在 名為 name 的 key
(integer) 1 // 1 表示存在
127.0.0.1:6379> exists name1
(integer) 0
127.0.0.1:6379> move name 1 //移除 name
(integer) 1 // 1 表示移除成功
(1.16s)
127.0.0.1:6379> keys * //檢視所有的 key
1) "age"
127.0.0.1:6379> expire name 10 //設定 name 的過期時間為 10 秒鐘,單點登入可以使用 Redis 的過期功能
(integer) 1
127.0.0.1:6379> ttl name //檢視過期時間,已經過期2秒鐘
(integer) -2
127.0.0.1:6379> get name //此時 name 已不存在
(nil)
127.0.0.1:6379> type name //檢視 key 中所儲存的 value 的資料型別
string // string 型別
127.0.0.1:6379> type age //檢視 key 中所儲存的 value 的資料型別
string // string 型別
Redis 所有命令可以在:Commands | Redis 中去檢視。
由於在 Linux 環境中是使用終端命令來對 Redis 進行一些操作的,所以下面通過對一些 String 型別的命令的操作來進行講解。
127.0.0.1:6379> set key1 v1 //設定 key-value
OK
127.0.0.1:6379> get key1 //通過 get-key 來獲取 value
"v1"
127.0.0.1:6379> append key1 "hello" //追加字串,如果 key 不存在,就相當於重新 set key
(integer) 7
127.0.0.1:6379> get key1
"v1hello"
127.0.0.1:6379> strlen key1 //獲取字串長度
(integer) 7
127.0.0.1:6379> append key1 "+apluemxa" //追加字串
(integer) 16
127.0.0.1:6379> get key1
"v1hello+apluemxa"
下面再對 Redis 中一些現有的自增、自減、設定步長操作進行講解:
127.0.0.1:6379> set views 0 //設定 key 為0
OK
127.0.0.1:6379> get views
"0"
127.0.0.1:6379> incr views //自增
(integer) 1
127.0.0.1:6379> incr views
(integer) 5
127.0.0.1:6379> get views
"5"
127.0.0.1:6379> incrby views 20 //設定步長,並指定增量
(integer) 23
127.0.0.1:6379> get views
"23"
127.0.0.1:6379> DECRBY views 8 //設定步長,並指定減量
(integer) 15
下面是對字串範圍 range 做一個講解:
127.0.0.1:6379> set key1 "helle,world!" //設定 key 以及 value
OK
127.0.0.1:6379> get key1 //通過 key 獲取 value
"helle,world!"
127.0.0.1:6379> GETRANGE key1 0 3 //擷取字串 [0,3](陣列下標)
"hell"
127.0.0.1:6379> GETRANGE key1 0 -1 //表示擷取所有的字串,與 get key 作用一致
"helle,world!"
127.0.0.1:6379> GETRANGE key1 2 6 //擷取字串 [2,6](陣列下標)
"lle,w"
127.0.0.1:6379> SETRANGE key1 2 xx //替換字串為 xx,2為陣列下標
(integer) 12
127.0.0.1:6379> get key1 //檢視被替換後的字串
"hexxe,world!"
下面再對設定過期時間、判斷是否存在(分散式鎖中使用)進行講解:
127.0.0.1:6379> setex key3 30 "hello" //設定 key 的 value 為30,過期時間為30秒
OK
127.0.0.1:6379> ttl key3
(integer) 20
127.0.0.1:6379> setnx mykey "redis" //如果 mykey 不存在則建立
(integer) 1
(0.55s)
127.0.0.1:6379> keys *
1) "mykey"
2) "key1"
127.0.0.1:6379> setnx mykey "MySQL" //如果 mykey 已存在,則提示建立失敗,返回0
(integer) 0
下面再對 Redis 的批次操作命令進行講解:
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3 //同時建立多個 key-value
OK
127.0.0.1:6379> keys *
1) "k3"
2) "k2"
3) "k1"
127.0.0.1:6379> mget k1 k2 k3 //同時獲取多個 value
1) "v1"
2) "v2"
3) "v3"
127.0.0.1:6379> msetnx k1 v1 k4 v4 // msetnx 是一個原子性操作,要麼一起成功,要麼一起失敗
(integer) 0
127.0.0.1:6379> get k4 // 因為上述的 k1 v1 已經存在,所以上述命令執行失敗,k4 沒有建立成功
(nil)
最後再介紹幾個 Redis 的高階實戰用法:
set user:1{name:zhuzqc,age:123} //設定一個 user:1 物件,其 value 使用一個 json 字串來儲存
127.0.0.1:6379> mset user:1:name zhuzqc user:1:age 123 //同時為 user 物件設定 key為 name 和 age,並設定value
OK
127.0.0.1:6379> mget user:1:name user:1:age //通過 user 物件的 key,獲取對應的 value
1) "zhuzqc"
2) "123"
127.0.0.1:6379> getset db redis //先 get 後 set,若沒有值則返回 nil
(nil)
127.0.0.1:6379> get db //上述命令中已 set 了值
"redis"
127.0.0.1:6379> getset db mysql //同理先 get 到 db 的 value
"redis"
127.0.0.1:6379> get db //再重新 set db 的 value
"mysql"
本節講解的是 Redis 中關於 String 型別資料型別的基本命令以及進階用法,主要包括:
如:某網站的瀏覽量統計
uid:1234:views:0 incr views //使用者id、瀏覽量、瀏覽量自增
如:過期時間、是否存在(分散式鎖)
如:同時建立多個 key-value、先設定 key 再設定 value
前提:Redis 單條命令保證原子性,但是 Redis 的事務不保證原子性。
比如在關係型資料庫 MySQL 中,事務是具有原子性的,所有的命令都是一起成功或者失敗。
基本操作過程如下:
127.0.0.1:6379> multi #redis開啟事務
OK
127.0.0.1:6379(TX)> FLUSHDB #命令開始入隊
QUEUED
127.0.0.1:6379(TX)> keys *
QUEUED
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> get k2
QUEUED
127.0.0.1:6379(TX)> exec #執行事務,上述命令開始按順序執行並給出展示執行結果
1) OK
2) (empty array)
3) OK
4) OK
5) "v2"
編譯型異常(程式碼有問題或者命令有錯):事務中所有的命令都不會被執行;
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED
127.0.0.1:6379(TX)> getset k3 v33 #正確的 getset 語句
QUEUED
127.0.0.1:6379(TX)> getset k3 #錯誤的語句:編譯時(未執行)發生錯誤
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379(TX)> set k4 v4
QUEUED
127.0.0.1:6379(TX)> exec #事務中所有的命令都不會執行
(error) EXECABORT Transaction discarded because of previous errors.
執行時異常:如果事務佇列中存在語法性錯誤,那麼執行事務時,其它命令可以正常執行,錯誤的命令會丟擲異常。
127.0.0.1:6379> set k1 "v1"
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> INCR k1 #對字串的 value 使用自增,語法不報錯
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED
127.0.0.1:6379(TX)> get k3
QUEUED
127.0.0.1:6379(TX)> exec #執行事務後,只有錯的命令未被執行,其餘命令都被執行了
1) (error) ERR value is not an integer or out of range
2) OK
3) OK
4) "v3"
悲觀鎖
樂觀鎖
Redis 監視(watch)測試
127.0.0.1:6379> set money 1000
OK
127.0.0.1:6379> set cost 0
OK
127.0.0.1:6379> watch money #監視 money 物件
OK
127.0.0.1:6379> multi #開啟事務,資料正常變動
OK
127.0.0.1:6379(TX)> DECRBY money 28
QUEUED
127.0.0.1:6379(TX)> INCRBY cost 28
QUEUED
127.0.0.1:6379(TX)> EXEC #執行後得到正常的資料
1) (integer) 972
2) (integer) 28
127.0.0.1:6379> watch money #使用 watch 進行樂觀鎖操作
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> DECRBY money 100 #另一個執行緒操作
QUEUED
127.0.0.1:6379(TX)> INCRBY money 100
QUEUED
127.0.0.1:6379(TX)> exec #執行失敗
(nil)
######################################### 解決辦法 ##########################################
127.0.0.1:6379> unwatch #先解鎖
OK
127.0.0.1:6379> watch money #再次監視,獲取最新的值
OK
127.0.0.1:6379> multi #啟用事務
OK
127.0.0.1:6379(TX)> DECRBY money 10
QUEUED
127.0.0.1:6379(TX)> INCRBY cost 10
QUEUED
127.0.0.1:6379(TX)> exec #執行成功
1) (integer) 480
2) (integer) 48
什麼是 Jedis?
Jedis 是官方推薦的 Java 連線開發工具,本質上是一個 Java 操作 Redis 的中介軟體。
如果要使用 Java 操作 Redis ,那麼應該要先熟悉 Jedis 的一些操作。
<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>4.2.3</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.83</version>
</dependency>
/**
* @author Created by zhuzqc on 2022/6/20 23:04
*/
public class TestPing {
public static void main(String[] args) {
//1、new 一個 Jedis 物件
Jedis jedis = new Jedis("127.0.0.1",6379);
//2、所有 Jedis 命令都在 jedis 物件中去操作
System.out.println(jedis.ping());
}
}
關於 String
/**
* @author Created by zhuzqc on 2022/6/20 23:15
*/
public class TestAPI {
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1",6379);
//新增 key-value
jedis.set("name","zhuzqc");
jedis.set("age","123");
//使用 append 新增字串
jedis.append("name","-alumna");
//批次增加、獲取 key-value
jedis.mset("k1","v1","k2","v2");
jedis.mget("k1","k2");
//分散式鎖的是否存在、設定過期時間
jedis.setnx("k1","v1");
jedis.setex("k2",10,"v2");
//先 get 後 set
jedis.getSet("k2","vv22");
//擷取 k2 的字串
System.out.println(jedis.getrange("name", 2, 5));
}
}
常用方法
public Long hlen(String key) {
this.checkIsInMultiOrPipeline();
this.client.hlen(key);
return this.client.getIntegerReply();
}
public Set<String> hkeys(String key) {
this.checkIsInMultiOrPipeline();
this.client.hkeys(key);
return (Set)BuilderFactory.STRING_SET.build(this.client.getBinaryMultiBulkReply());
}
public List<String> hvals(String key) {
this.checkIsInMultiOrPipeline();
this.client.hvals(key);
List<String> lresult = this.client.getMultiBulkReply();
return lresult;
}
public Map<String, String> hgetAll(String key) {
this.checkIsInMultiOrPipeline();
this.client.hgetAll(key);
return (Map)BuilderFactory.STRING_MAP.build(this.client.getBinaryMultiBulkReply());
}
/**
* @author Created by zhuzqc on 2022/6/20 23:32
*/
public class TestTX {
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1",6379);
//自定義 JSON 資料
JSONObject jsonObject = new JSONObject();
jsonObject.put("name","Jackson");
jsonObject.put("age","123");
//強轉為 String
String result = jsonObject.toString();
try {
//開啟事務
Transaction multi = jedis.multi();
multi.set("user1",result);
//程式碼丟擲異常則事務都不執行
int i = 1/0;
multi.set("user2",result);
//執行事務
multi.exec();
} catch (Exception e) {
e.printStackTrace();
}finally {
//執行時報錯,不影響其它事務執行
System.out.println(jedis.get("user3"));
System.out.println(jedis.get("user2"));
System.out.println(jedis.get("user1"));
jedis.close();
}
}
}
在 Spring Boot 中運算元據的框架(元件)一般有:Spring-data、JPA、JDBC、Redis等。
說明:在Spring 2.x 版本後,原來使用的 Jedis 被替換成為了 lettuce。
原因:
原始碼解讀:
@Bean
//自己可定義一個 redisTemplate 來替換預設的
@ConditionalOnMissingBean(
name = {"redisTemplate"}
)
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
//兩個泛型都是Object,使用時需要強轉為<String,Object>
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
匯入依賴:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
組態檔:
# Redis 設定
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.database=0
簡單測試:
/**
* 獲取某個專案中會使用到的 accessToken
* @return accessToken
*/
public String getAccessToken() throws Exception {
String accessToken = null;
Jedis jedis = null;
try {
// 如果 redis 物件中已經存在 accessToken,則直接返回
jedis =jedisPool.getResource();
accessToken = jedis.get("accessToken:" + appKey);
if (StringUtils.isNotBlank(accessToken)){
return accessToken;
}
// 獲取 accessToken 所必須的引數
Client client = createClient();
GetAccessTokenRequest getAccessTokenRequest = new GetAccessTokenRequest()
.setAppKey(appKey)
.setAppSecret(appSecret);
GetAccessTokenResponse res = client.getAccessToken(getAccessTokenRequest);
//獲取到 accessToken 後將其以 K-V 的形式存入 redis 物件中,並設定過期時間為30分鐘
accessToken = res.getBody().getAccessToken();
jedis.set("accessToken:" + appKey, accessToken);
jedis.expire("accessToken:" + appKey, 1800);
// 異常捕獲
} catch (TeaException err) {
if (!com.aliyun.teautil.Common.empty(err.code) &&
!com.aliyun.teautil.Common.empty(err.message)) {
log.error("code:{}, message:{}", err.code, err.message);
}
} catch (Exception _err) {
TeaException err = new TeaException(_err.getMessage(), _err);
if (!com.aliyun.teautil.Common.empty(err.code) &&
!com.aliyun.teautil.Common.empty(err.message)) {
log.error("code:{}, message:{}", err.code, err.message);
}
}
//釋放 redisPool 資源池中的 redis 物件資源,保證 redis 物件數量夠用
finally {
if (jedis != null) {
jedis.close();
}
}
return accessToken;
}
自定義 Redis 設定類:
/**
* @author Created by zhuzqc on 2022/6/21 22:27
*/
@Configuration
public class RedisConfig {
/**
* 自定義 redisTemplate
* */
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate();
//設定具體的序列化方式,如 JSON 的序列化
Jackson2JsonRedisSerializer<Object> objectJackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
objectJackson2JsonRedisSerializer.setObjectMapper(om);
//設定 String 的序列化
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
//設定 key 的時候使用 String 序列化
template.setKeySerializer(stringRedisSerializer);
template.setHashKeySerializer(stringRedisSerializer);
//設定 value 的時候使用 JSON 序列化
template.setValueSerializer(objectJackson2JsonRedisSerializer);
template.setHashValueSerializer(objectJackson2JsonRedisSerializer);
template.setConnectionFactory(redisConnectionFactory);
template.afterPropertiesSet();
return template;
}
}
使用 Redis 工具類:
@Test
public void Test_1(){
redisUtil.set("nnaammee","zzzzzz");
System.out.println(redisUtil.get("nnaammee"));
}
Redis 是基於記憶體的資料庫,如果不將記憶體中的資料庫狀態儲存到磁碟,那麼一旦伺服器程序退出,伺服器中的資料庫狀態也會消失,所以 Redis 提供了資料持久化的能力 。
RDB 是 Redis 預設的持久化方案。在指定的時間間隔內,執行指定次數的寫操作,則會將記憶體中的資料寫入到磁碟中(觸發SnapShot 快照)。即在指定目錄下生成一個dump.rdb檔案。Redis 重啟會通過載入dump.rdb檔案恢復資料。
組態檔(原理)
# save ""
# save 指定時間間隔 <執行指定次數>
save 900 1
save 300 10
save 60 10000
分析:save <指定時間間隔> <執行指定次數更新操作>
滿足條件就將記憶體中的資料同步到硬碟中:
官方出廠設定預設是 900秒內有1個更改、00秒內有10個更改、60秒內有10000個更改,則將記憶體中的資料快照寫入磁碟。
# 指定本地資料庫檔名,一般採用預設的 dump.rdb
dbfilename dump.rdb
# 預設開啟資料壓縮
rdbcompression yes
觸發機制
RDB 資料恢復
將上述dump.rdb 檔案拷貝到redis的安裝目錄的bin目錄下,重啟redis服務即可恢復資料。
RDB 優缺點
優點:
缺點:
Redis 預設不開啟 AOF。
AOF 的出現是為了彌補 RDB 的不足(資料的不一致性),所以它採用紀錄檔的形式來記錄每個寫操作,並追加到檔案中。
Redis 重啟的會根據紀錄檔檔案的內容將寫指令從前到後執行一次以完成資料的恢復工作。
組態檔(原理)
開啟 redis.conf 檔案,找到 APPEND ONLY MODE 對應內容:
# redis 預設關閉,開啟需要手動把 no 改為 yes
appendonly yes
# 指定本地資料庫檔名,預設值為 appendonly.aof
appendfilename "appendonly.aof"
# 指定更新紀錄檔條件
# appendfsync always # 同步持久化,每次發生資料變化會立刻寫入到磁碟中。(效能較差,但資料完整性比較好)
appendfsync everysec # 預設每秒非同步記錄一次
# appendfsync no # 不同步
# 設定重寫觸發機制
auto-aof-rewrite-percentage 100 #當AOF檔案大小是上次rewrite後大小的一倍
auto-aof-rewrite-min-size 64mb #且檔案大於64M時觸發。(一般都設定為2G,64M太小)
AOF 資料恢復
正常情況下,將 appendonly.aof 檔案拷貝到 redis 的安裝目錄的 bin 目錄下,重啟redis服務即可。
注:如果因為某些原因導致 appendonly.aof 檔案格式異常,從而導致資料還原失敗,可以通過命令 redis-check-aof --fix appendonly.aof 進行修復。
AOF 重寫機制
當 AOF 檔案的大小超過所設定的閾值時,Redis 就會對 AOF 檔案的內容壓縮。
Redis 會 fork 出一條新程序,讀取記憶體中的資料,並重新寫到一個臨時檔案中。並沒有讀取舊檔案,最後替換舊的 AOF 檔案。
主從複製:是指將一臺 Redis 伺服器端的資料,複製到其它 Redis 伺服器的過程。
讀寫分離:一般專案中的業務資料 80% 的情況下都是在進行讀的操作,多個從節點進行讀操作,可以減輕 Redis 伺服器壓力。
前者稱為主節點(Master/laeder),後者稱為從節點(slave/follower),最低數量標準為「一主二從」。
在這個過程中資料的複製是單向的,資料只能從主節點複製到從節點。
其中 Master 節點以寫為主,Slave 節點以讀為主。
預設情況下每臺 Redis 都是主節點,一個主節點可以有多個從節點(也可以無從節點),但每個從節點只能有唯一的主節點。
叢集的設定只設定從庫、不用設定主庫。
檢查 Redis 伺服器資訊:
127.0.0.1:6379> info replication
# Replication
role:master # 當前角色預設是 Master
connected_slaves:0 # 當前從機為 0 個
master_failover_state:no-failover
master_replid:e87ac50f5b4f345c1a2a8f3a7374b25ef109219f
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
修改 conf 檔案中以下幾處:
設定原則:從機認定一個主機即可。
如:一主(79)二從(80、81):
127.0.0.1:6380> SLAVEOF 127.0.0.1 6379 #認79為主機
OK
127.0.0.1:6380> info replication
# Replication
role:slave #當前角色為從機
master_host:127.0.0.1
master_port:6379
127.0.0.1:6381> SLAVEOF 127.0.0.1 6379 #認79為主機
OK
127.0.0.1:6381>
127.0.0.1:6381> info replication
# Replication
role:slave #當前角色為從機
master_host:127.0.0.1
master_port:6379
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:2 # 該主機有兩個從機
slave0:ip=127.0.0.1,port=6380,state=online,offset=350,lag=1
slave1:ip=127.0.0.1,port=6381,state=online,offset=350,lag=1
注意:在 Redis 伺服器的組態檔中對主、從進行設定才能真正生效,上述命令設定是臨時的。
# 主節點可以讀也可以寫
127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> get k1
"v1"
# 從節點可以獲取到主節點的資料
127.0.0.1:6380> keys *
1) "k1"
127.0.0.1:6380> get k1
"v1"
# 從節點只允許讀,不允許寫
127.0.0.1:6380> set k2 v2
(error) READONLY You can't write against a read only replica.
# 主節點宕機後,從節點仍然可以獲取到主節點資料
127.0.0.1:6379> shutdown
127.0.0.1:6380> get key2
"shutdown"
Slave 啟動連線成功到 Master 後會傳送一個 sync 的同步命令,Master 接收到命令後將會傳送整個資料檔案到 Slave,並完成一次完全同步。