Java-分布式框架-redis-2

一、Redis持久化

RDB快照(snapshot)

在默認(rèn)情況下, Redis 將內(nèi)存數(shù)據(jù)庫(kù)快照保存在名字為 dump.rdb 的二進(jìn)制文件中。你可以對(duì) Redis 進(jìn)行設(shè)置, 讓它在“ N 秒內(nèi)數(shù)據(jù)集至少有 M 個(gè)改動(dòng)”這一條件被滿足時(shí), 自動(dòng)保存一次數(shù)據(jù)集。比如說(shuō), 以下設(shè)置會(huì)讓 Redis 在滿足“ 60 秒內(nèi)有至少有 1000 個(gè)鍵被改動(dòng)”這一條件時(shí), 自動(dòng)保存一次數(shù)據(jù)集:
// save 60 1000
關(guān)閉RDB只需要將所有的save保存策略注釋掉即可。

還可以手動(dòng)執(zhí)行命令生成RDB快照,進(jìn)入redis客戶端執(zhí)行命令save或bgsave可以生成dump.rdb文件,每次命令執(zhí)行都會(huì)將所有redis內(nèi)存快照到一個(gè)新的rdb文件里,并覆蓋原有rdb快照文件。save是同步命令,bgsave是異步命令,bgsave會(huì)從redis主進(jìn)程fork(fork()是linux函數(shù))出一個(gè)子進(jìn)程專門用來(lái)生成rdb快照文件。

image.png

注意:如果設(shè)置鏡像保存策略為save 60 1000,若時(shí)間55秒,操作次數(shù)999次的時(shí)候redis宕機(jī)了,那么這999次操作會(huì)丟失。

AOF(append-only file)

快照功能并不是非常耐久(durable): 如果 Redis 因?yàn)槟承┰蚨斐晒收贤C(jī), 那么服務(wù)器將丟失最近寫入、且仍未保存到快照中的那些數(shù)據(jù)。從 1.1 版本開始, Redis 增加了一種完全耐久的持久化方式: AOF 持久化,將修改的每一條指令記錄進(jìn)文件appendonly.aof中你可以通過(guò)修改配置文件來(lái)打開 AOF 功能:

  • appendonly yes

從現(xiàn)在開始, 每當(dāng) Redis 執(zhí)行一個(gè)改變數(shù)據(jù)集的命令時(shí)(比如 SET), 這個(gè)命令就會(huì)被追加到 AOF 文件的末尾。這樣的話, 當(dāng) Redis 重新啟動(dòng)時(shí), 程序就可以通過(guò)重新執(zhí)行 AOF 文件中的命令來(lái)達(dá)到重建數(shù)據(jù)集的目的。你可以配置 Redis 多久才將數(shù)據(jù) fsync 到磁盤一次。有三個(gè)選項(xiàng):

  • appendfsync always:每次有新命令追加到 AOF 文件時(shí)就執(zhí)行一次 fsync ,非常慢,也非常安全。
  • appendfsync everysec:每秒 fsync 一次,足夠快(和使用 RDB 持久化差不多),并且在故障時(shí)只會(huì)丟失 1 秒鐘的數(shù)據(jù)。
  • appendfsync no:從不 fsync ,將數(shù)據(jù)交給操作系統(tǒng)來(lái)處理。更快,也更不安全的選擇。推薦(并且也是默認(rèn))的措施為每秒 fsync 一次, 這種 fsync 策略可以兼顧速度和安全性。
AOF重寫

AOF文件里可能有太多沒用指令,所以AOF會(huì)定期根據(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

如下兩個(gè)配置可以控制AOF自動(dòng)重寫頻率

  • auto-aof-rewrite-min-size 64mb

aof文件至少要達(dá)到64M才會(huì)自動(dòng)重寫,文件太小恢復(fù)速度本來(lái)就很快,重寫的意義不大

  • auto-aof-rewrite-percentage 100

aof文件自上一次重寫后文件大小增長(zhǎng)了100%則再次觸發(fā)重寫當(dāng)然AOF還可以手動(dòng)重寫,進(jìn)入redis客戶端執(zhí)行命令bgrewriteaof重寫AOF注意,AOF重寫redis會(huì)fork出一個(gè)子進(jìn)程去做,不會(huì)對(duì)redis正常命令處理有太多影響

命令 RDB AOF
啟動(dòng)優(yōu)先級(jí)
體積
恢復(fù)速度
數(shù)據(jù)安全性 容易丟數(shù) 據(jù)根據(jù)策略決定

注意1:redis啟動(dòng)時(shí)如果既有rdb文件又有aof文件則優(yōu)先選擇aof文件恢復(fù)數(shù)據(jù),因?yàn)閍of一般來(lái)說(shuō)數(shù)據(jù)更全一點(diǎn)。

注意2:redis數(shù)據(jù)庫(kù)的定位并不是作為持久層數(shù)據(jù)庫(kù),而是一個(gè)內(nèi)存數(shù)據(jù)庫(kù),是用來(lái)抗并發(fā)的。

Redis 4.0 混合持久化

重啟 Redis 時(shí),我們很少使用 RDB來(lái)恢復(fù)內(nèi)存狀態(tài),因?yàn)闀?huì)丟失大量數(shù)據(jù)。我們通常使用 AOF 日志重放,但是重放 AOF 日志性能相對(duì) RDB來(lái)說(shuō)要慢很多,這樣在 Redis 實(shí)例很大的情況下,啟動(dòng)需要花費(fèi)很長(zhǎng)的時(shí)間。 Redis 4.0 為了解決這個(gè)問(wèn)題,帶來(lái)了一個(gè)新的持久化選項(xiàng)——混合持久化。通過(guò)如下配置可以開啟混合持久化:

  • aof-use-rdb-preamble yes

如果開啟了混合持久化,AOF在重寫時(shí),不再是單純將內(nèi)存數(shù)據(jù)轉(zhuǎn)換為RESP命令寫入AOF文件,而是將重寫這一刻之前的內(nèi)存做RDB快照處理,并且將RDB快照內(nèi)容和增量的AOF修改內(nèi)存數(shù)據(jù)的命令存在一起,都寫入新的AOF文件,新的文件一開始不叫appendonly.aof,等到重寫完新的AOF文件才會(huì)進(jìn)行改名,原子的覆蓋原有的AOF文件,完成新舊兩個(gè)AOF文件的替換。于是在 Redis 重啟的時(shí)候,可以先加載 RDB 的內(nèi)容,然后再重放增量 AOF 日志就可以完全替代之前的 AOF 全量文件重放,因此重啟效率大幅得到提升。

混合持久化AOF文件結(jié)構(gòu)


image.png

Redis主從架構(gòu)

Redis主從工作原理
  1. slave連接master成功,發(fā)送psync同步命令給master。
  2. master接收到同步命令,執(zhí)行bgsave后臺(tái)生成最新rdb快照數(shù)據(jù)文件,注意這期間master還會(huì)接收到新的寫命令,master會(huì)把這些指令緩存起來(lái)。
  3. master發(fā)送rdb數(shù)據(jù)文件給slave。
  4. master發(fā)送期間緩存起來(lái)的寫指令。
  5. slave清除原來(lái)的rdb數(shù)據(jù)文件。
  6. 合成接收到的rdb快照數(shù)據(jù)文件與新的寫指令,并加載到內(nèi)存中。
  7. master通過(guò)socket長(zhǎng)連接持續(xù)把寫指令發(fā)送給從節(jié)點(diǎn),保證主從命令數(shù)據(jù)一致性。

主從復(fù)制(全量復(fù)制)流程圖:

image.png

注意:repl_back_buffer容器中專門存儲(chǔ)最近的寫命令,但容量有限,默認(rèn)為1MB.

主從之間的連接可能會(huì)因?yàn)榫W(wǎng)絡(luò)不穩(wěn)定等原因會(huì)造成連接暫時(shí)中斷與重連,不可能每次重連發(fā)現(xiàn)主從數(shù)據(jù)不一致就采用全量復(fù)制,redis也給我們提供了部分復(fù)制的功能.

  1. slave連接中斷.
  2. 期間repl_back_buffer容器存儲(chǔ)最近的寫命令.
  3. slave重連master成功.
  4. slave發(fā)送psync(offerset)同步指令給master.
  5. master收到同步指令,判斷offerset是否在repl_back_buffer容器的寫指令的范圍內(nèi),若在,同步offerset后面的指令;若不在,全量同步.
  6. master通過(guò)socket長(zhǎng)連接持續(xù)把寫指令發(fā)送給從節(jié)點(diǎn),保證主從命令數(shù)據(jù)一致性。

主從復(fù)制(部分復(fù)制)流程圖:

image.png

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. 啟動(dòng)從節(jié)點(diǎn)
redis‐server redis.conf
5. 連接從節(jié)點(diǎn)
redis‐cli ‐p 6380
6. 測(cè)試在6379實(shí)例上寫數(shù)據(jù),6380實(shí)例是否能及時(shí)同步新修改數(shù)據(jù)
7. 可以自己再配置一個(gè)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,這里既是連接超時(shí)又是讀寫超時(shí),從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連接池里拿出一個(gè)連接執(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會(huì)被歸還給資源池。
            if (jedis != null)
                jedis.close();
        }
    }
}

Redis哨兵高可用架構(gòu)

sentinel哨兵是特殊的redis服務(wù),不提供讀寫服務(wù),主要用來(lái)監(jiān)控redis實(shí)例節(jié)點(diǎn)。哨兵架構(gòu)下client端第一次從哨兵找出redis的主節(jié)點(diǎn),后續(xù)就直接訪問(wèn)redis的主節(jié)點(diǎn),不會(huì)每次都通過(guò)sentinel代理訪問(wèn)redis的主節(jié)點(diǎn),當(dāng)redis的主節(jié)點(diǎn)發(fā)生變化,哨兵會(huì)第一時(shí)間感知到,并且將新的redis主節(jié)點(diǎn)通知給client端(這里面redis的client端一般都實(shí)現(xiàn)了訂閱功能,訂閱sentinel發(fā)布的節(jié)點(diǎn)變動(dòng)消息)


image.png
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是一個(gè)數(shù)字,指明當(dāng)有多少個(gè)sentinel認(rèn)為一個(gè)master失效時(shí)(值一般為:sentinel總數(shù)/2 +1),master才算真正失效
sentinel monitor mymaster 192.168.0.60 6379 2
3、啟動(dòng)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)識(shí)別出了redis的主從
5、可以自己再配置兩個(gè)sentinel,端口26380和26381,注意上述配置文件里的對(duì)應(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并不是說(shuō)與sentinel建立的連接池,而是通過(guò)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會(huì)被歸還給資源池。
            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("錯(cuò)誤:", e);
        }
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容