天天看點

redis實戰、應用場景、操作方法、面試題

1. 主要問題

1.1 什麼是redis?

答:redis 是一個遠端記憶體的非關系型資料庫。

1.2 redis能夠存儲什麼資料?存儲量如何?

答:string 、list、hash、set、zset;

redis官網(https://redis.io/topics/data-types),一個String類型的value最大可以存儲512M;

redis實戰、應用場景、操作方法、面試題

list、hash、set、zset元素個數最多為2^32-1個,也就是4294967295個。

注意: jedis 的源碼,支援String 類型的value 存儲1G。

redis實戰、應用場景、操作方法、面試題

1.3 redis的主要優點?redis為什麼快?

答:速度快;因為他是記憶體資料庫,直接在記憶體中處理請求。

1.4 redis的缺點?如何解決?

答:1)記憶體存儲,不在硬碟中;

解決辦法:

采用兩種方式進行持久化(同步資料SNAPSHOT、同步指令AOF);

2)redis伺服器單片處理資料量有限;

解決辦法:

采用伺服器叢集、資料分片、資料主從複制,可以解決,些技術此處隻做該要介紹,具體實作方式參考《redis-in-action》一書;

1.5 redis 能不能用作一般網絡系統的主資料庫?存在有那些問題?如何解決?

答:可以,需要考慮資料持久化、伺服器叢集、資料分片等問題,具體操作見下;

2. 應用場景和實作

2.1 分頁查詢

/**
     * 代碼清單1-8
     * 1。 擷取分頁文章的ids;
     * 2。 擷取分頁文章的所有資訊;
     *
     * @param conn redis 連結
     * @param page 查詢文章的頁碼
     * @param order 文章分組資料zset 的key
     * @return 分頁文章的查詢結果
     */
    public List<Map<String, String>> getArticles(Jedis conn, int page, String order) {

        int start = (page - 1) * ARTICLES_PER_PAGE;
        int end = start + ARTICLES_PER_PAGE - 1;

        // 擷取分頁文章的ID;
        Set<String> ids = conn.zrevrange(order, start, end);
        List<Map<String, String>> articles = new ArrayList<Map<String, String>>();
        for (String id : ids) {
            // 根據文章ID,擷取文章的所有資訊;
            Map<String, String> articleData = conn.hgetAll(id);
            articleData.put("id", id);
            articles.add(articleData);
        }

        return articles;
    }

    /**
     * 代碼清單1-10
     * 1。 将文章加入分組的zset中;
     * 2。 擷取zset中的分頁資料;
     *
     * @param conn redis 連結
     * @param group 指定分組名稱
     * @param page 頁碼數
     * @param order 文章zset
     * @return 分組、分頁後的所有文章
     */
    public List<Map<String, String>> getGroupArticles(Jedis conn, String group, int page, String order) {

        // 建構文章分組的鍵名
        String key = order + group;
        if (!conn.exists(key)) {
            ZParams params = new ZParams().aggregate(ZParams.Aggregate.MAX);

            // 将group:group、order中的文章,取最大值,加入key中
            conn.zinterstore(key, params, "group:" + group, order);
            conn.expire(key, 60);

        }
        return getArticles(conn, page, key);
    }
           

2.2 分布式鎖

參考連結:https://blog.csdn.net/leinminna/article/details/101155733

2.3 資料持久化

redis将資料持久化到硬碟有兩種方法:

方法一: 快照(snapshotting),将某一時刻的所有資料都寫入硬碟(dump.rdb快照檔案);
save 60 1000                   // 60s内發生1000次寫入觸發bgsave,建立快照,
stop-writes-on-bgsave-error no // 發生錯誤是否停止寫入
rdbcompression yes             // 是否壓縮
dbfilename dump.rdb            // 快照檔案名稱
           

快照的其他指令:

SAVE:建立快照。接到save指令的redis伺服器在快照建立完成之前不會相應任何其他指令。

BGSAVE:建立快照。redis伺服器會建立一個子程序,在快照建立完成之前父程序繼續處理其他指令。

SHUTDOWN:關閉伺服器。redis會馬上執行SAVE阻塞其他指令,在SAVE完成後關閉伺服器。

SYNC:一個redis伺服器向另一個redis伺服器發送SNYC,如果主伺服器沒有執行BGSAVE,那麼,主伺服器就會執行BGSAVE指令。

方法二: 隻追加檔案(append-only-file,AOF),将被執行的指令複制到硬碟裡;
appendonly yes   				// 是否開啟AOF
appendfsync-no-rewrite everysec // 每秒執行一次同步,将寫指令同步到硬碟
auto-aof-rewrite-percentage 100 // 
auto-aof-rewrite-min-size 64mb  // 當AOF檔案大于64MB,并且體積至少是上次重寫後體積的2倍,Redis 将觸發BGREWRITEAOF
           

兩個方法可以單獨使用,也可以同時使用;

BGREWRITEAOF:優化AOF檔案。移出備援指令,縮小檔案體積,類似于BGSAVE名命,子程序執行。

2.4 資料分片

分片: 就是基于某些簡單的規則,将資料劃分為更小的部分,然後根據資料所屬的部分來決定将資料發送到那個位置上;

/**
     * 一個簡單的分片規則,通過分片數量和的鍵計算新的分片的ID,進而,将資料存儲到不同的hset中
     *
     * @param base          原來存儲的hash的基礎鍵
     * @param key           該條資料的key
     * @param totalElements 存儲資料的總量
     * @param shardSize     設計的分片數量
     * @return 生成的分片編号:新hash的鍵,用于存儲該條資料的分片的鍵
     */
    public String shardKey(String base, String key, long totalElements, int shardSize) {
        long shardId = 0;
        if (isDigit(key)) {
            shardId = Integer.parseInt(key, 10) / shardSize;
        } else {
            CRC32 crc = new CRC32();
            crc.update(key.getBytes());
            long shards = 2 * totalElements / shardSize;
            shardId = Math.abs(((int) crc.getValue()) % shards);
        }
        return base + ':' + shardId;
    }

    /**
     * 儲存該條資料到分片中
     *
     * @param conn          jedis連結
     * @param base          原來資料存儲的鍵
     * @param key           該條資料的key
     * @param value         該條資料的值
     * @param totalElements 存儲資料的總量
     * @param shardSize     設計的分片數量
     * @return 儲存是否成功
     */
    public Long shardHset(
            Jedis conn, String base, String key, String value, long totalElements, int shardSize) {
        String shard = shardKey(base, key, totalElements, shardSize);
        return conn.hset(shard, key, value);
    }

    /**
     * 從分片中擷取該條資料
     *
     * @param conn          jedis連結
     * @param base          原來資料存儲的鍵
     * @param key           該條資料的key
     * @param totalElements 存儲資料的總量
     * @param shardSize     設計的分片數量
     * @return 擷取的資料
     */
    public String shardHget(
            Jedis conn, String base, String key, int totalElements, int shardSize) {
        String shard = shardKey(base, key, totalElements, shardSize);
        return conn.hget(shard, key);
    }
           

2.5 叢集

單台redis 伺服器的讀寫性能和單片容量有限,然後就有了叢集;

擴充讀性能: 将主伺服器的資料複制到重伺服器,進而可以同時通路多台從伺服器;

擴充寫性能: 不建議将資料寫入不同伺服器,後續資料彙總會出現諸多問題;

主從鍊: 使用樹狀結構,在減少資料傳輸次數,同時能降低主伺服器的傳輸負載;

處理系統故障: 詳細資訊參考《redis-in-action》page73-76;

2.6 降低記憶體占用

redis 可以存儲string 、list、set、hash、zset。

但是list、set、hash、zset他們的結構被稱為體積較大的結構,使用會産生許多備援空間,這四種資料結構可以被分割成為多個體積較小的結構。

list: 它的資料結構實際上是一個雙向連結清單結構,每個節點包含了三個指針占用的三個空間、一個字元串長度占用空間、一個剩餘長度占用空間、字元串本身和一個額外位元組占消耗用空間。

壓縮效率:以’'ten"占用的空間為例,采用list,該節點将消耗21位元組的記憶體,采用壓縮清單,隻會消耗2個位元組記憶體,很令人心動。

但,萬物是公平的,當使用壓縮清單這種資料結構時,當存儲的元素的大小過大和數量長時,就會對redis的性能産生影響。其他三種結構類似。

開啟壓縮清單的配置檔案:

// 《redis-in-action》介紹:list壓縮清單長度限制在500-2000,單個元素體積小于128位元組,list壓縮清單的性能會處于合理的範圍之内。
list-max-ziplist-entries 1024 // list壓縮清單,最大元素數量;
list-max-ziplist-value 64    // list壓縮清單, 每個節點最大體積64位元組

hash-max-ziplist-entries 512 // hash壓縮清單,最大元素數量
hash-max-ziplist-value 64    // hash壓縮清單, 每個節點最大體積64位元組

zset-max-ziplist-entries 128 // zset壓縮清單,最大元素數量
zset-max-ziplist-value 64    // zset壓縮清單, 每個節點最大體積64位元組

set-max-intset-entries  512  // set限制使用十進制整數,
           

當壓縮清單中的元素超過了這個配置,那麼,壓縮清單将将會失效,資料結構轉化為普通的資料結構,即使之後資料再次滿足配置。

推薦書籍:《redis-in-action》

繼續閱讀