一话浇、Redis持久化
RDB快照(snapshot)
在默認(rèn)情況下, Redis 將內(nèi)存數(shù)據(jù)庫快照保存在名字為 dump.rdb 的二進(jìn)制文件中筷转。你可以對 Redis 進(jìn)行設(shè)置蔚万, 讓它在“ N 秒內(nèi)數(shù)據(jù)集至少有 M 個改動”這一條件被滿足時, 自動保存一次數(shù)據(jù)集徽曲。比如說零截, 以下設(shè)置會讓 Redis 在滿足“ 60 秒內(nèi)有至少有 1000 個鍵被改動”這一條件時, 自動保存一次數(shù)據(jù)集:
// save 60 1000
關(guān)閉RDB只需要將所有的save保存策略注釋掉即可秃臣。
還可以手動執(zhí)行命令生成RDB快照涧衙,進(jìn)入redis客戶端執(zhí)行命令save或bgsave可以生成dump.rdb文件,每次命令執(zhí)行都會將所有redis內(nèi)存快照到一個新的rdb文件里奥此,并覆蓋原有rdb快照文件弧哎。save是同步命令,bgsave是異步命令稚虎,bgsave會從redis主進(jìn)程fork(fork()是linux函數(shù))出一個子進(jìn)程專門用來生成rdb快照文件撤嫩。
注意:如果設(shè)置鏡像保存策略為save 60 1000,若時間55秒蠢终,操作次數(shù)999次的時候redis宕機(jī)了序攘,那么這999次操作會丟失鸭限。
AOF(append-only file)
快照功能并不是非常耐久(durable): 如果 Redis 因?yàn)槟承┰蚨斐晒收贤C(jī), 那么服務(wù)器將丟失最近寫入两踏、且仍未保存到快照中的那些數(shù)據(jù)败京。從 1.1 版本開始, Redis 增加了一種完全耐久的持久化方式: AOF 持久化梦染,將修改的每一條指令記錄進(jìn)文件appendonly.aof中你可以通過修改配置文件來打開 AOF 功能:
- appendonly yes
從現(xiàn)在開始赡麦, 每當(dāng) Redis 執(zhí)行一個改變數(shù)據(jù)集的命令時(比如 SET), 這個命令就會被追加到 AOF 文件的末尾帕识。這樣的話泛粹, 當(dāng) Redis 重新啟動時, 程序就可以通過重新執(zhí)行 AOF 文件中的命令來達(dá)到重建數(shù)據(jù)集的目的肮疗。你可以配置 Redis 多久才將數(shù)據(jù) fsync 到磁盤一次晶姊。有三個選項(xiàng):
- appendfsync always:每次有新命令追加到 AOF 文件時就執(zhí)行一次 fsync ,非常慢伪货,也非常安全们衙。
- appendfsync everysec:每秒 fsync 一次,足夠快(和使用 RDB 持久化差不多)碱呼,并且在故障時只會丟失 1 秒鐘的數(shù)據(jù)蒙挑。
- appendfsync no:從不 fsync ,將數(shù)據(jù)交給操作系統(tǒng)來處理愚臀。更快忆蚀,也更不安全的選擇。推薦(并且也是默認(rèn))的措施為每秒 fsync 一次姑裂, 這種 fsync 策略可以兼顧速度和安全性馋袜。
AOF重寫
AOF文件里可能有太多沒用指令,所以AOF會定期根據(jù)內(nèi)存的最新數(shù)據(jù)生成aof文件例如舶斧,執(zhí)行了如下幾條命令:
127.0.0.1:6379> incr readcount
(integer) 1
127.0.0.1:6379> incr readcount
(integer) 2
127.0.0.1:6379> incr readcount
(integer) 3
127.0.0.1:6379> incr readcount
(integer) 4
127.0.0.1:6379> incr readcount
(integer) 5
重寫后AOF文件里變成一條命令欣鳖,而不是5條
*3
$3
SET
$9
readcount
$1
5
如下兩個配置可以控制AOF自動重寫頻率
- auto-aof-rewrite-min-size 64mb
aof文件至少要達(dá)到64M才會自動重寫,文件太小恢復(fù)速度本來就很快捧毛,重寫的意義不大
- auto-aof-rewrite-percentage 100
aof文件自上一次重寫后文件大小增長了100%則再次觸發(fā)重寫當(dāng)然AOF還可以手動重寫观堂,進(jìn)入redis客戶端執(zhí)行命令bgrewriteaof重寫AOF注意让网,AOF重寫redis會fork出一個子進(jìn)程去做呀忧,不會對redis正常命令處理有太多影響
命令 | RDB | AOF |
---|---|---|
啟動優(yōu)先級 | 低 | 高 |
體積 | 小 | 大 |
恢復(fù)速度 | 快 | 慢 |
數(shù)據(jù)安全性 | 容易丟數(shù) | 據(jù)根據(jù)策略決定 |
注意1:redis啟動時如果既有rdb文件又有aof文件則優(yōu)先選擇aof文件恢復(fù)數(shù)據(jù),因?yàn)閍of一般來說數(shù)據(jù)更全一點(diǎn)溃睹。
注意2:redis數(shù)據(jù)庫的定位并不是作為持久層數(shù)據(jù)庫而账,而是一個內(nèi)存數(shù)據(jù)庫,是用來抗并發(fā)的因篇。
Redis 4.0 混合持久化
重啟 Redis 時泞辐,我們很少使用 RDB來恢復(fù)內(nèi)存狀態(tài)笔横,因?yàn)闀G失大量數(shù)據(jù)。我們通常使用 AOF 日志重放咐吼,但是重放 AOF 日志性能相對 RDB來說要慢很多吹缔,這樣在 Redis 實(shí)例很大的情況下,啟動需要花費(fèi)很長的時間锯茄。 Redis 4.0 為了解決這個問題厢塘,帶來了一個新的持久化選項(xiàng)——混合持久化。通過如下配置可以開啟混合持久化:
- aof-use-rdb-preamble yes
如果開啟了混合持久化肌幽,AOF在重寫時晚碾,不再是單純將內(nèi)存數(shù)據(jù)轉(zhuǎn)換為RESP命令寫入AOF文件,而是將重寫這一刻之前的內(nèi)存做RDB快照處理喂急,并且將RDB快照內(nèi)容和增量的AOF修改內(nèi)存數(shù)據(jù)的命令存在一起格嘁,都寫入新的AOF文件溃斋,新的文件一開始不叫appendonly.aof臣疑,等到重寫完新的AOF文件才會進(jìn)行改名夏块,原子的覆蓋原有的AOF文件侵状,完成新舊兩個AOF文件的替換蚯嫌。于是在 Redis 重啟的時候缨硝,可以先加載 RDB 的內(nèi)容助隧,然后再重放增量 AOF 日志就可以完全替代之前的 AOF 全量文件重放恳蹲,因此重啟效率大幅得到提升步氏。
混合持久化AOF文件結(jié)構(gòu)
Redis主從架構(gòu)
Redis主從工作原理
- slave連接master成功响禽,發(fā)送psync同步命令給master。
- master接收到同步命令荚醒,執(zhí)行bgsave后臺生成最新rdb快照數(shù)據(jù)文件芋类,注意這期間master還會接收到新的寫命令,master會把這些指令緩存起來界阁。
- master發(fā)送rdb數(shù)據(jù)文件給slave侯繁。
- master發(fā)送期間緩存起來的寫指令。
- slave清除原來的rdb數(shù)據(jù)文件泡躯。
- 合成接收到的rdb快照數(shù)據(jù)文件與新的寫指令贮竟,并加載到內(nèi)存中。
- master通過socket長連接持續(xù)把寫指令發(fā)送給從節(jié)點(diǎn)较剃,保證主從命令數(shù)據(jù)一致性咕别。
主從復(fù)制(全量復(fù)制)流程圖:
注意:repl_back_buffer容器中專門存儲最近的寫命令,但容量有限,默認(rèn)為1MB.
主從之間的連接可能會因?yàn)榫W(wǎng)絡(luò)不穩(wěn)定等原因會造成連接暫時中斷與重連,不可能每次重連發(fā)現(xiàn)主從數(shù)據(jù)不一致就采用全量復(fù)制,redis也給我們提供了部分復(fù)制的功能.
- slave連接中斷.
- 期間repl_back_buffer容器存儲最近的寫命令.
- slave重連master成功.
- slave發(fā)送psync(offerset)同步指令給master.
- master收到同步指令,判斷offerset是否在repl_back_buffer容器的寫指令的范圍內(nèi),若在,同步offerset后面的指令;若不在,全量同步.
- master通過socket長連接持續(xù)把寫指令發(fā)送給從節(jié)點(diǎn),保證主從命令數(shù)據(jù)一致性写穴。
主從復(fù)制(部分復(fù)制)流程圖:
redis主從架構(gòu)搭建惰拱,配置從節(jié)點(diǎn)步驟
1. 復(fù)制一份redis.conf文件
2. 將相關(guān)配置修改為如下值:
port 6380
pidfile /var/run/redis_6380.pid
logfile "6380.log"
dir /usr/local/redis‐5.0.3/data/6380
3. 配置主從復(fù)制
replicaof 192.168.0.60 6379 # 從本機(jī)6379的redis實(shí)例復(fù)制數(shù)據(jù)
replica‐read‐only yes
4. 啟動從節(jié)點(diǎn)
redis‐server redis.conf
5. 連接從節(jié)點(diǎn)
redis‐cli ‐p 6380
6. 測試在6379實(shí)例上寫數(shù)據(jù),6380實(shí)例是否能及時同步新修改數(shù)據(jù)
7. 可以自己再配置一個6381的從節(jié)點(diǎn)
單機(jī)使用
- 添加依賴
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
public class JedisSingleTest {
public static void main(String[] args) throws IOException {
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxTotal(20);
jedisPoolConfig.setMaxIdle(10);
jedisPoolConfig.setMinIdle(5);
// timeout啊送,這里既是連接超時又是讀寫超時偿短,從Jedis 2.8開始有區(qū)分connectionTimeout和soTimeout的構(gòu)造函數(shù)
JedisPool jedisPool = new JedisPool(jedisPoolConfig, "192.168.0.60", 6379, 3000, null);
Jedis jedis = null;
try {
//從redis連接池里拿出一個連接執(zhí)行命令
jedis = jedisPool.getResource();
//******* jedis普通操作示例 ********
System.out.println(jedis.set("name", "value"));
System.out.println(jedis.get("name"));
} catch (Exception e) {
e.printStackTrace();
} finally {
//注意這里不是關(guān)閉連接欣孤,在JedisPool模式下,Jedis會被歸還給資源池昔逗。
if (jedis != null)
jedis.close();
}
}
}
Redis哨兵高可用架構(gòu)
sentinel哨兵是特殊的redis服務(wù)降传,不提供讀寫服務(wù),主要用來監(jiān)控redis實(shí)例節(jié)點(diǎn)勾怒。哨兵架構(gòu)下client端第一次從哨兵找出redis的主節(jié)點(diǎn)搬瑰,后續(xù)就直接訪問redis的主節(jié)點(diǎn),不會每次都通過sentinel代理訪問redis的主節(jié)點(diǎn)控硼,當(dāng)redis的主節(jié)點(diǎn)發(fā)生變化泽论,哨兵會第一時間感知到,并且將新的redis主節(jié)點(diǎn)通知給client端(這里面redis的client端一般都實(shí)現(xiàn)了訂閱功能卡乾,訂閱sentinel發(fā)布的節(jié)點(diǎn)變動消息)
redis哨兵架構(gòu)搭建步驟:
1翼悴、復(fù)制一份sentinel.conf文件
cp sentinel.conf sentinel‐26379.conf
2、將相關(guān)配置修改為如下值:
port 26379
daemonize yes
pidfile "/var/run/redis‐sentinel‐26379.pid"
logfile "26379.log"
dir "/usr/local/redis‐5.0.3/data"
// sentinel monitor <master‐name> <ip> <redis‐port> <quorum>
// quorum是一個數(shù)字幔妨,指明當(dāng)有多少個sentinel認(rèn)為一個master失效時(值一般為:sentinel總數(shù)/2 +1)鹦赎,master才算真正失效
sentinel monitor mymaster 192.168.0.60 6379 2
3、啟動sentinel哨兵實(shí)例
src/redis‐sentinel sentinel‐26379.conf
4误堡、查看sentinel的info信息
src/redis‐cli ‐p 26379
127.0.0.1:26379>info
可以看到Sentinel的info里已經(jīng)識別出了redis的主從
5古话、可以自己再配置兩個sentinel,端口26380和26381锁施,注意上述配置文件里的對應(yīng)數(shù)字都要修改
哨兵使用
public class JedisSentinelTest {
public static void main(String[] args) throws IOException {
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(20);
config.setMaxIdle(10);
config.setMinIdle(5);
String masterName = "mymaster";
Set<String> sentinels = new HashSet<String>();
sentinels.add(new HostAndPort("192.168.0.60",26379).toString());
sentinels.add(new HostAndPort("192.168.0.60",26380).toString());
sentinels.add(new HostAndPort("192.168.0.60",26381).toString());
//JedisSentinelPool其實(shí)本質(zhì)跟JedisPool類似陪踩,都是與redis主節(jié)點(diǎn)建立的連接池
//JedisSentinelPool并不是說與sentinel建立的連接池,而是通過sentinel發(fā)現(xiàn)redis主節(jié)點(diǎn)并與其建立連接
JedisSentinelPool jedisSentinelPool = new JedisSentinelPool(masterName, sentinels, config, 3000, null);
Jedis jedis = null;
try {
jedis = jedisSentinelPool.getResource();
System.out.println(jedis.set("sentinel666", "666"));
System.out.println(jedis.get("sentinel666"));
} catch (Exception e) {
e.printStackTrace();
} finally {
//注意這里不是關(guān)閉連接悉抵,在JedisPool模式下肩狂,Jedis會被歸還給資源池。
if (jedis != null)
jedis.close();
}
}
}
SpringBoot下哨兵使用
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
//線程池
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
spring:
redis:
database: 0
timeout: 3000
lettuce:
pool:
max-idle: 50
min-idle: 10
max-active: 100
max-wait: 1000
sentinel: #哨兵模式
master: mymaster #主服務(wù)器所在集群名稱
nodes: 192.168.0.60:26379,192.168.0.60:26380,192.168.0.60:26381
@Autowired
private StringRedisTemplate stringRedisTemplate;
@RequestMapping("/test_sentinel")
public void testSentinel() throws InterruptedException {
int i = 1;
while (true){
try {
stringRedisTemplate.opsForValue().set("name"+i, i+""); //jedis.set(key,value);
System.out.println("設(shè)置key:"+ "name" + i);
i++;
Thread.sleep(1000);
}catch (Exception e){
logger.error("錯誤:", e);
}
}
}