Redis知識點總結

Redis基礎篇

Redis數據結構

  • String
    一、概念:string是redis最基本的類型,你能夠理解成與Memcached如出一轍的類型,一個key對應一個value。
    string類型是二進制安全的。意思是redis的string能夠包含任何數據。好比jpg圖片或者序列化的對象 。
    string類型是Redis最基本的數據類型,一個鍵最大能存儲512MB。html

    二、實例:java

    redis 127.0.0.1:6379> SET name "runoob"
    OK
    redis 127.0.0.1:6379> GET name
    "runoob"
  • Hash
    一、概念:hash 是一個鍵值(key=>value)對集合。hash 是一個 string 類型的 field 和 value 的映射表,hash 特別適合用於存儲對象。
    二、實例:node

    redis> HMSET myhash field1 "Hello" field2 "World"
    "OK"
    redis> HGET myhash field1
    "Hello"
    redis> HGET myhash field2
    "World"
  • List
    一、概念:list是簡單的字符串列表,按照插入順序排序。你能夠添加一個元素到列表的頭部或者尾部。
    二、實例:redis

    redis 127.0.0.1:6379> lpush runoob redis
    (integer) 1
    redis 127.0.0.1:6379> lpush runoob mongodb
    (integer) 2
    redis 127.0.0.1:6379> lpush runoob rabitmq
    (integer) 3
    redis 127.0.0.1:6379> lrange runoob 0 10
    1) "rabitmq"
    2) "mongodb"
    3) "redis"
  • Set
    一、概念:Redis的Set是string類型的無序集合。集合是經過哈希表實現的,因此添加,刪除,查找的複雜度都是O(1)。
    二、實例:算法

    redis 127.0.0.1:6379> sadd runoob redis
    (integer) 1
    redis 127.0.0.1:6379> sadd runoob mongodb
    (integer) 1
    redis 127.0.0.1:6379> sadd runoob rabitmq
    (integer) 1
    redis 127.0.0.1:6379> sadd runoob rabitmq
    (integer) 0
    redis 127.0.0.1:6379> smembers runoob
    
    1) "redis"
    2) "rabitmq"
    3) "mongodb"
  • zset
    一、zset 和 set 同樣也是string類型元素的集合,且不容許重複的成員。不一樣的是每一個元素都會關聯一個double類型的分數。redis正是經過分數來爲集合中的成員進行從小到大的排序。zset的成員是惟一的,但分數(score)卻能夠重複。
    二、實例:mongodb

    redis 127.0.0.1:6379> zadd runoob 0 redis
    (integer) 1
    redis 127.0.0.1:6379> zadd runoob 0 mongodb
    (integer) 1
    redis 127.0.0.1:6379> zadd runoob 0 rabitmq
    (integer) 1
    redis 127.0.0.1:6379> zadd runoob 0 rabitmq
    (integer) 0
    redis 127.0.0.1:6379> > ZRANGEBYSCORE runoob 0 1000
    1) "mongodb"
    2) "rabitmq"
    3) "redis"
    ``

Redis基礎命令

用於Key的命令數據庫

  • SET : 爲Key設置值,實例:SET runoobkey redis
  • EXISTS : 查詢Key是否存在,存在返回1,不然返回0。實例:EXISTS runoob-new-key
  • PPTL : 以毫秒爲單位返回 key 的剩餘過時時間。實例:PTTL KEY_NAME
  • TTL : 秒爲單位返回 key 的剩餘過時時間。 實例: TTL KEY_NAME

用於字符串的命令json

  • SET : 設置指定key的值。實例:SET KEY_NAME value
  • GET : 獲取指定key的值。實例:GET KEY_NAME
  • INCR : 把key中存儲的數字值增1。實例:INCR KEY_NAME
  • DECR : 把key中存儲的數字值減1。實例:DECR KEY_NAME

用於Hash表的命令後端

  • HGET : 獲取hash表中的指定字段的值。實例:HGET KEY_NAME FIELD_NAME
  • HGETALL : 獲取hash表中全部字段 對應的值。實例:HGETALL KEY_NAME
  • HKEYS : 獲取hash表中全部字段。實例:HKEYS KEY_NAME

用於List集合的命令瀏覽器

  • LRANGE : 獲取集合指定範圍內的元素。實例:LRANGE key start stop 獲取下標從start到stop的元素

Redis進階篇

Redis進階指令篇

  • Scan

一、SCAN命令是增量的循環,每次調用只會返回一小部分的元素。因此不會有KEYS命令的坑。 SCAN命令返回的是一個遊標,從0開始遍歷,到0結束遍歷。scan也有以下一些特設:
(1)查詢複雜度爲O(n),經過遊標分步進行,不會阻塞線程
(2)提供limit參數,控制每次返回結果的最大條數。這裏值得注意的是,limit只是一個提示,返回的結果可多可少
(3)同keys同樣,它也提供模式匹配功能
(4)返回的結果可能會重複,須要客戶端去重
(5)遍歷過程當中,若是有數據修改,改動後的數據不必定能遍歷到
(6)單次返回結果是空的並不意味着遍歷結束,而是看返回的遊標值是否爲0

二、使用實例:
(1)先插入10個key
clipboard.png
(2)而後使用Scan進行掃描:
clipboard.png
這時候返回key三、key5,返回的遊標爲744
(3)使用744遊標繼續查詢:
clipboard.png
返回key一、key4,返回遊標爲268。同理,繼續查詢能夠查詢出匹配key*的全部key,直到遊標爲0爲止,結果以下圖:
clipboard.png

HyperLogLog

一、概念:
HyperLogLog這種數據結構用於解決去重統計問題,它提供了不精確的去重計數方案,標準偏差在0.81%。
二、使用方法:
(1)pfadd:增長計數,pfadd KEY_NAME id
(2)pfcount:獲取計數,pfcount KEY_NAME
(3)pfmerge:用於將多個pf計數值累加在一塊兒造成一個新的pf值
三、原理:較爲複雜,後續補充

Redis管道

一、概念:指的是客戶端容許將多個請求依次發給服務器,過程當中而不須要等待請求的回覆,在最後再一併讀取結果便可。主要做用在於提升吞吐量
clipboard.png
上圖中能夠看出,全部的請求合併爲一次IO,除了時延能夠下降以外,還能大幅度提高系統吞吐量。
二、實例:

package com.redis;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.Pipeline;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 管道處理實例
 * @author huangy on 2018/8/5
 */
public class Piple {

    private static Logger logger = LoggerFactory.getLogger(Piple.class);

    private static JedisPool jedisPool;

    // 總共有多少個併發任務
    private static final int taskCount = 50;
    // 管道大小
    private static final int batchSize = 10;
    // 每一個任務處理的命令數
    private static final int cmdCount = 1000;
    // redis服務地址
    private static final String host = "127.0.0.1";
    // 端口
    private static final int port = 6379;
    // 是否使用管道
    private static final boolean usePipeline = true;
    // 保存執行結果
    private static List<Object> results = new ArrayList<>(cmdCount);

    private static void init() {
        JedisPoolConfig poolConfig = new JedisPoolConfig();
        poolConfig.setMaxIdle(100);
        poolConfig.setTestOnBorrow(false);
        poolConfig.setTestOnReturn(false);

        jedisPool = new JedisPool(poolConfig, host, port);
    }

    public static void main(String[] args) throws InterruptedException {
        // 初始化redis
        init();

        long beginTime = System.currentTimeMillis();

        // 使用多線程執行
        ExecutorService executor = Executors.newCachedThreadPool();

        // 使用CountDownLatch保證全部任務都執行完
        CountDownLatch latch = new CountDownLatch(taskCount);
        for (int i = 0; i < taskCount; i++) {
            executor.submit(new DemoTask(i, latch));
        }

        latch.await();
        executor.shutdownNow();

        long endTime = System.currentTimeMillis();

        // 耗時
        logger.info("execution cost time(s)={}", (endTime - beginTime) / 1000.0);

        // 全部結果
        logger.info("results, size={}, all={}", results.size(), results);
    }

    private static Jedis get() {
        return jedisPool.getResource();
    }

    private static class DemoTask implements Runnable {

        private int id;
        private CountDownLatch latch;

        private DemoTask(int id, CountDownLatch latch) {
            this.id = id;
            this.latch = latch;
        }

        public void run() {
            logger.info("Task[{}] start.", id);
            try {
                if (usePipeline) {
                    runWithPipeline();
                } else {
                    runWithNonPipeline();
                }
            } finally {
                latch.countDown();
            }

            logger.info("Task[{}] end.", id);
        }

        /**
         * 不使用管道處理redis命令
         */
        private void runWithNonPipeline() {
            // 總共處理cmdCount個
            for (int i = 0; i < cmdCount; i++) {
                Jedis jedis = get();
                try {
                    jedis.set(key(i), UUID.randomUUID().toString());
                } finally {
                    if (jedis != null) {
                        jedisPool.returnResource(jedis);
                    }
                }
                if (i % batchSize == 0) {
                    logger.info("Task[{}] process -- {}", id, i);
                }
            }
        }

        /**
         * 使用管道處理redis命令
         * 總共處理cmdCount個redis命令,每次處理batchSize個
         */
        private void runWithPipeline() {

            for (int i = 0; i < cmdCount;) {
                Jedis jedis = get();

                try {
                    Pipeline pipeline = jedis.pipelined();
                    int j;
                    for (j = 0; j < batchSize; j++) {
                        if (i + j < cmdCount) {
                            pipeline.set(key(i + j), UUID.randomUUID().toString());
                        } else {
                            break;
                        }
                    }
//                    pipeline.sync();

                    // 查看結果,使用syncAndReturnAll
                    List<Object> tem = pipeline.syncAndReturnAll();
                    logger.info("Task thread id={} pipeline, cmd end={}, result size={}", id, i + j, tem.size());

                    results.addAll(tem);

                    i += j;

                } finally {
                    if (jedis != null) {
                        jedisPool.returnResource(jedis);
                    }
                }

            }
        }

        private String key(int i) {
            return i + "";
        }
    }
}

上述實例使用50個線程進行處理,每一個線程分別處理了1000個redis命令(set),在不使用管道的狀況下,耗時大約5秒;使用管道的狀況下,耗時大約2秒。

三、缺點:
(1)pipeline機制能夠優化吞吐量,但沒法提供原子性/事務保障,而這個能夠經過Redis-Multi等命令實現。
(2)部分讀寫操做存在相關依賴,沒法使用pipeline實現

異步隊列

一、概念:redis實現異步隊列,通常使用list結構做爲隊列,rpush生產消息,lpop消費消息。當lpop沒有消息的時候,要適當sleep一會再重試。
二、sleep優化:
(1)緣由:客戶端是經過隊列的pop操做來獲取消息,而後進行處理,處理完了再接着得到消息。但是若是隊列空了,客戶端就會不停的pop,這樣的操做不能獲取數據,而且拉高了客戶端的CPU,redis的QPS也會拉高,下降redis的查詢效率。
(2)使用sleep解決這個問題,讓線程睡一會,好比說1s。可讓客戶端的CPU降下來。
三、blpop/brpop 優化:
(1)緣由:sleep的方法能夠優化,可是睡眠會致使消息延遲增大。
(2)使用blpop(阻塞讀):阻塞讀在隊列沒有數據的時候,會當即進入休眠狀態,一旦數據到來,則馬上醒過來。
(3)PS:blpop指隊列左邊出數據。brpop指隊列右邊出數據
(4)注意點:當線程一直sleep,服務器通常會斷開鏈接,這時候blpop會拋出異常,因此在編寫客戶端消費者的時候要當心,注意捕獲異常,還要重試。
四、延時隊列:
(1)延時隊列能夠經過redis的zset(有序列表)來實現。
(2)簡單的代碼實例:

/**
 * 延時隊列案例
 * @author huangy on 2018/8/26
 */
@Component
public class QueueDemo {

    private static final Logger LOGGER = LoggerFactory.getLogger(QueueDemo.class);

    @Resource
    private Jedis jedis;

    private static final String QUEUE_KEY = "queueKey";

    /**
     * 將任務扔到延時隊列中
     * 使用時間做爲score
     * @param msg 任務(通常使用json格式的字符串)
     */
    public void delay(String msg) {
        jedis.zadd(QUEUE_KEY, System.currentTimeMillis() + 5000, msg);
    }

    /**
     * 從延時隊列中獲取任務,利用時間範圍做爲score的範圍
     * zrangeByScore :返回有序集 key 中,全部 score 值介於 min 和 max 之間(包括等於 min 或 max )的成員
     */
    public void loop() {
        while (!Thread.interrupted()) {
            Set<String> values = jedis.zrangeByScore(QUEUE_KEY,
                    0, 577742424, 0, 1);
            if (values.isEmpty()) {
                try {
                    Thread.sleep(500);
                } catch (Exception e) {
                    LOGGER.error("loop sleep fail", e);
                    break;
                }

                continue;
            }

            String s = values.iterator().next();
            // 利用zrem判斷是否拿到任務
            if (jedis.zrem(QUEUE_KEY, s) > 0) {
                // 拿到這個任務了,進行處理
                LOGGER.info("loop msg, msg={}", s);
            }
        }
    }
}

Redis集羣篇

一致性哈希

  • 普通的HASH算法的缺點:
    在使用Redis集羣的時候,若是直接使用HASH算法 hash(key) % length,當緩存服務器變化時,length字段變化,致使全部緩存的數據須要從新進行HASH運算,才能使用。而這段時間若是訪問量上升了,容易引發服務器雪崩。所以,引入了一致性哈希
  • 一致性哈希:
    經過對2^32取模的方式,保證了在增長/刪除緩存服務器的狀況下,其餘緩存服務器的緩存仍然可用,從而不引發雪崩問題。

Redis Cluster(Redis官方集羣方案)

  • Redis Cluster是一種服務器Sharding技術,3.0版本開始正式提供。Redis Cluster中,Sharding採用slot(槽)的概念,一共分紅16384個槽,這有點兒相似前面講的pre sharding思路。對於每一個進入Redis的鍵值對,根據key進行散列,分配到這16384個slot中的某一箇中。使用的hash算法也比較簡單,就是CRC16後16384取模。Redis集羣中的每一個node(節點)負責分攤這16384個slot中的一部分,也就是說,每一個slot都對應一個node負責處理。當動態添加或減小node節點時,須要將16384個槽作個再分配,槽中的鍵值也要遷移。固然,這一過程,在目前實現中,還處於半自動狀態,須要人工介入。Redis集羣,要保證16384個槽對應的node都正常工做,若是某個node發生故障,那它負責的slots也就失效,整個集羣將不能工做。爲了增長集羣的可訪問性,官方推薦的方案是將node配置成主從結構,即一個master主節點,掛n個slave從節點。這時,若是主節點失效,Redis Cluster會根據選舉算法從slave節點中選擇一個上升爲主節點,整個集羣繼續對外提供服務。這很是相似前篇文章提到的Redis Sharding場景下服務器節點經過Sentinel監控架構成主從結構,只是Redis Cluster自己提供了故障轉移容錯的能力。
  • Redis Cluster的新節點識別能力、故障判斷及故障轉移能力是經過集羣中的每一個node都在和其它nodes進行通訊,這被稱爲集羣總線(cluster bus)。它們使用特殊的端口號,即對外服務端口號加10000。例如若是某個node的端口號是6379,那麼它與其它nodes通訊的端口號是16379。nodes之間的通訊採用特殊的二進制協議。

對客戶端來講,整個cluster被看作是一個總體,客戶端能夠鏈接任意一個node進行操做,就像操做單一Redis實例同樣,當客戶端操做的key沒有分配到該node上時,就像操做單一Redis實例同樣,當客戶端操做的key沒有分配到該node上時,Redis會返回轉向指令,指向正確的node,這有點兒像瀏覽器頁面的302 redirect跳轉。
Redis Cluster是Redis 3.0之後才正式推出,時間較晚,目前能證實在大規模生產環境下成功的案例還不是不少,須要時間檢驗。

Redis Sharding集羣(客戶端分片)

  • Redis 3正式推出了官方集羣技術,解決了多Redis實例協同服務問題。Redis Cluster能夠說是服務端Sharding分片技術的體現,即將鍵值按照必定算法合理分配到各個實例分片上,同時各個實例節點協調溝通,共同對外承擔一致服務。
  • 多Redis實例服務,比單Redis實例要複雜的多,這涉及到定位、協同、容錯、擴容等技術難題。這裏,咱們介紹一種輕量級的客戶端Redis Sharding技術。
  • Redis Sharding能夠說是Redis Cluster出來以前,業界廣泛使用的多Redis實例集羣方法。其主要思想是採用哈希算法將Redis數據的key進行散列,經過hash函數,特定的key會映射到特定的Redis節點上。這樣,客戶端就知道該向哪一個Redis節點操做數據。Sharding架構如圖:

clipboard.png

  • 慶幸的是,java redis客戶端驅動jedis,已支持Redis Sharding功能,即ShardedJedis以及結合緩存池的ShardedJedisPool。
  • Jedis的Redis Sharding實現具備以下特色:
    一、採用一致性哈希算法(consistent hashing),將key和節點name同時hashing,而後進行映射匹配,採用的算法是MURMUR_HASH。採用一致性哈希而不是採用簡單相似哈希求模映射的主要緣由是當增長或減小節點時,不會產生因爲從新匹配形成的rehashing。一致性哈希隻影響相鄰節點key分配,影響量小。
    2.爲了不一致性哈希隻影響相鄰節點形成節點分配壓力,ShardedJedis會對每一個Redis節點根據名字(沒有,Jedis會賦予缺省名字)會虛擬化出160個虛擬節點進行散列。根據權重weight,也可虛擬化出160倍數的虛擬節點。用虛擬節點作映射匹配,能夠在增長或減小Redis節點時,key在各Redis節點移動再分配更均勻,而不是隻有相鄰節點受影響。
    3.ShardedJedis支持keyTagPattern模式,即抽取key的一部分keyTag作sharding,這樣經過合理命名key,能夠將一組相關聯的key放入同一個Redis節點,這在避免跨節點訪問相關數據時很重要。
  • 擴容問題:
    一、Redis Sharding採用客戶端Sharding方式,服務端Redis仍是一個個相對獨立的Redis實例節點,沒有作任何變更。同時,咱們也不須要增長額外的中間處理組件,這是一種很是輕量、靈活的Redis多實例集羣方法。Redis Sharding採用客戶端Sharding方式,服務端Redis仍是一個個相對獨立的Redis實例節點,沒有作任何變更。同時,咱們也不須要增長額外的中間處理組件,這是一種很是輕量、靈活的Redis多實例集羣方法。固然,Redis Sharding這種輕量靈活方式必然在集羣其它能力方面作出妥協。好比擴容,當想要增長Redis節點時,儘管採用一致性哈希,畢竟仍是會有key匹配不到而丟失,這時須要鍵值遷移。
    二、做爲輕量級客戶端sharding,處理Redis鍵值遷移是不現實的,這就要求應用層面容許Redis中數據丟失或從後端數據庫從新加載數據。但有些時候,擊穿緩存層,直接訪問數據庫層,會對系統訪問形成很大壓力。有沒有其它手段改善這種狀況?
    三、Redis做者給出了一個比較討巧的辦法–presharding,即預先根據系統規模儘可能部署好多個Redis實例,這些實例佔用系統資源很小,一臺物理機可部署多個,讓他們都參與sharding,當須要擴容時,選中一個實例做爲主節點,新加入的Redis節點做爲從節點進行數據複製。數據同步後,修改sharding配置,讓指向原實例的Shard指向新機器上擴容後的Redis節點,同時調整新Redis節點爲主節點,原實例可再也不使用。這樣,咱們的架構模式變成一個Redis節點切片包含一個主Redis和一個備Redis。在主Redis宕機時,備Redis接管過來,上升爲主Redis,繼續提供服務。主備共同組成一個Redis節點,經過自動故障轉移,保證了節點的高可用性。則Sharding架構演變成:

clipboard.png

  • Redis Sentinel提供了主備模式下Redis監控、故障轉移功能達到系統的高可用性。
  • 高訪問量下,即便採用Sharding分片,一個單獨節點仍是承擔了很大的訪問壓力,這時咱們還須要進一步分解。一般狀況下,應用訪問Redis讀操做量和寫操做量差別很大,讀經常是寫的數倍,這時咱們能夠將讀寫分離,並且讀提供更多的實例數。能夠利用主從模式實現讀寫分離,主負責寫,從負責只讀,同時一主掛多個從。在Sentinel監控下,還能夠保障節點故障的自動監測。

Redis主從同步原理

和MySQL主從複製的緣由同樣,Redis雖然讀取寫入的速度都特別快,可是也會產生讀壓力特別大的狀況。爲了分擔讀壓力,Redis支持主從複製,Redis的主從結構能夠採用一主多從或者級聯結構,下圖爲級聯結構。
clipboard.png

  • Redis主從結構

 Redis主從複製能夠根據是不是全量分爲全量同步和增量同步。
一、全量同步
 Redis全量複製通常發生在Slave初始化階段,這時Slave須要將Master上的全部數據都複製一份。具體步驟以下:
  1)從服務器鏈接主服務器,發送SYNC命令;
  2)主服務器接收到SYNC命名後,開始執行BGSAVE命令生成RDB文件並使用緩衝區記錄此後執行的全部寫命令;
  3)主服務器BGSAVE執行完後,向全部從服務器發送快照文件,並在發送期間繼續記錄被執行的寫命令;
  4)從服務器收到快照文件後丟棄全部舊數據,載入收到的快照;
  5)主服務器快照發送完畢後開始向從服務器發送緩衝區中的寫命令;
  6)從服務器完成對快照的載入,開始接收命令請求,並執行來自主服務器緩衝區的寫命令;
clipboard.png

Redis全量同步過程
  完成上面幾個步驟後就完成了從服務器數據初始化的全部操做,從服務器此時能夠接收來自用戶的讀請求。

二、增量同步
 Redis增量複製是指Slave初始化後開始正常工做時主服務器發生的寫操做同步到從服務器的過程。
增量複製的過程主要是主服務器每執行一個寫命令就會向從服務器發送相同的寫命令,從服務器接收並執行收到的寫命令。

三、Redis主從同步策略
 主從剛剛鏈接的時候,進行全量同步;全同步結束後,進行增量同步。固然,若是有須要,slave 在任什麼時候候均可以發起全量同步。redis 策略是,不管如何,首先會嘗試進行增量同步,如不成功,要求從機進行全量同步。

四、其餘:Redis 2.8之後提供了PSYNC優化了斷線重連的效率
http://blog.csdn.net/sk199048...

Redis緩存篇

緩存雪崩

案例情景
若是緩存集中在一段時間內失效,發生大量的緩存穿透,全部的查詢都落在數據庫上,形成了緩存雪崩。而且若是大量的key過時時間設置的過於集中,到過時的那個時間點,redis可能會出現短暫的卡頓現象

解決方法
一、使用互斥鎖(這裏使用redis的setnx實現分佈式鎖):
只有獲取鎖,才能去訪問數據庫,加載數據,保證同一時刻只有一個請求訪問數據庫,避免數據庫崩潰。但這樣子可能形成請求延時比較久的問題。

二、讓緩存過時時間不那麼集中:
好比咱們能夠在原有的失效時間基礎上增長一個隨機值,好比1-5分鐘隨機,這樣每個緩存的過時時間的重複率就會下降,就很難引起集體失效的事件

三、作二級緩存
A1爲原始緩存,A2爲拷貝緩存,A1失效時,能夠訪問A2。A1緩存失效時間設置爲短時間,A2設置爲長期。這個在業務追求數據一致性要求不高的狀況下,可使用。

四、緩存永遠不過時
這裏的「永遠不過時」包含兩層意思:
(1) 從緩存上看,確實沒有設置過時時間,這就保證了,不會出現熱點key過時問題,也就是「物理」不過時。
(2) 從功能上看,若是不過時,那不就成靜態的了嗎?因此咱們把過時時間存在key對應的value裏,若是發現要過時了,經過一個後臺的異步線程進行緩存的構建,也就是「邏輯」過時.
從實戰看,這種方法對於性能很是友好,惟一不足的就是構建緩存時候,其他線程(非構建緩存的線程)可能訪問的是老數據,可是對於通常的互聯網功能來講這個仍是能夠忍受。

clipboard.png

緩存穿透

案例情景
緩存穿透是指查詢一個必定不存在的數據,因爲緩存是不命中時須要從數據庫查詢,查不到數據則不寫入緩存,這將致使這個不存在的數據每次請求都要到數據庫去查詢,形成緩存穿透

解決方法
使用Bloom filter(布隆過濾器)。當用戶查詢某個row時,能夠先經過內存中的布隆過濾器過濾掉大量不存在的row請求,而後再到數據庫進行查詢。

參考:
http://www.zsythink.net/archi...
http://blog.csdn.net/upxiaofe...
http://www.runoob.com/redis/r...
https://blog.csdn.net/sk19904...
https://www.cnblogs.com/littl...
https://blog.csdn.net/fei3342...

相關文章
相關標籤/搜索