中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

如何進行Redis深度分析

發布時間:2021-10-18 10:31:51 來源:億速云 閱讀:165 作者:柒染 欄目:大數據

今天就跟大家聊聊有關如何進行Redis深度分析,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結了以下內容,希望大家根據這篇文章可以有所收獲。

0、基礎:萬丈高樓平地起

1) 當字符串長度小于1M時,擴容都是加倍現有的空間,如果超過1M,擴容時一次只會多擴1M的空間。需要注意的是字符串最大長度為512M。

2) 如果value值是一個整數,還可以對它進行自增操作。自增是有范圍的,它的范圍是signed long的最大最小值,超過了這個值,Redis會
報錯,最大值 9223372036854775807
    
3) Redis的列表相當于Java語言里面的LinkedList,注意它是鏈表而不是數組。Redis的列表結構常用來做異步隊列使用。將需要延后處理
的任務結構體序列化成字符串塞進Redis的列表,另一個線程從這個列表中輪詢數據進行處理。
   右邊進左邊出--> 隊列
   右邊進右邊出--> 棧
   
   如果再深入一點,你會發現 Redis 底層存儲的還不是一個簡單的linkedlist,而是稱之為快速鏈表quicklist的一個結構。首先在列表
元素較少的情況下會使用一塊連續的內存存儲,這個結構是ziplist,也即是壓縮列表。它將所有的元素緊挨著一起存儲,分配的是一塊連
續的內存。當數據量比較多的時候才會改成quicklist。因為普通的鏈表需要的附加指針空間太大,會比較浪費空間,而且會加重內存的碎
片化。比如這個列表里存的只是 int 類型的數據,結構上還需要兩個額外的指針prev和next。所以Redis將鏈表和ziplist結合起來組成了
quicklist。也就是將多個ziplist使用雙向指針串起來使用。這樣既滿足了快速的插入刪除性能,又不會出現太大的空間冗余。
   
4) 壓縮列表ziplist是一種為節約內存而開發的順序型數據結構,它被用作列表鍵和哈希鍵的底層實現之一。

5) Hash,Redis的字典的值只能是字符串,而且Redis采用漸進式Rehash策略

6) set,Redis的集合相當于Java語言里面的HashSet,內部的鍵值對是無序的唯一的,其內部實現相當于一個特殊的字典,字典中所有的
value都是一個值NULL。set結構可以用來存儲活動中獎的用戶ID,因為有去重功能,可以保證同一個用戶不會中獎兩次。
   
7) zset,它類似于Java的SortedSet和HashMap的結合體,一方面它是一個set,保證了內部value的唯一性,另一方面它可以給每個value
賦予一個score,代表這個value的排序權重。它的內部實現用的是一種叫著跳躍列表的數據結構。zset還可以用來存儲學生的成績,value
值是學生的ID,score是他的考試成績。我們可以對成績按分數進行排序就可以得到他的名次。zset可以用來存粉絲列表,value值是粉絲的
用戶ID,score是關注時間。我們可以對粉絲列表按關注時間進行排序。
//https://yq.aliyun.com/articles/666398
typedef struct zset {

    // 字典,鍵為成員,值為分值
    // 用于支持 O(1) 復雜度的按成員取分值操作
    dict *dict;

    // 跳躍表,按分值排序成員
    // 用于支持平均復雜度為 O(log N) 的按分值定位成員操作
    // 以及范圍操作
    zskiplist *zsl;
} zset;

1、千帆競發:Redis分布式鎖

1) 使用setnx命令實現分布式鎖
   
超時問題?
   Redis 的分布式鎖不能解決超時問題,如果在加鎖和釋放鎖之間的邏輯執行的太長,以至于超出了鎖的超時限制,就會出現問題。因為這
時候鎖過期了,第二個線程重新持有了這把鎖,但是緊接著第一個線程執行完了業務邏輯,就把鎖給釋放了,第三個線程就會在第二個線程
邏輯執行完之間拿到了鎖。
   
解決辦法:
1.1) Redis分布式鎖不要用于較長時間任務。如果真的偶爾出現了,數據出現的小波錯亂可能需要人工介入解決。
1.2) 有一個更加安全的方案是為set指令的value參數設置為一個隨機數,釋放鎖時先匹配隨機數是否一致,然后再刪除key。但是匹配value
和刪除key不是一個原子操作,這就需要使用Lua腳本來處理了,因為Lua腳本可以保證連續多個指令的原子性執行。

2) 單機Redis實現分布式鎖的缺陷
   比如在Sentinel集群中,主節點掛掉時,從節點會取而代之,客戶端上卻并沒有明顯感知。原先第一個客戶端在主節點中申請成功了一把
鎖,但是這把鎖還沒有來得及同步到從節點,主節點突然掛掉了。然后從節點變成了主節點,這個新的節點內部沒有這個鎖,所以當另一個客
戶端過來請求加鎖時,立即就批準了。這樣就會導致系統中同樣一把鎖被兩個客戶端同時持有,不安全性由此產生。
不過這種不安全也僅僅是在主從發生failover的情況下才會產生,而且持續時間極短,業務系統多數情況下可以容忍。
   
解決辦法:RedLock
   加鎖時,它會向過半節點發送 set(key, value, nx=True, ex=xxx) 指令,只要過半節點set成功,那就認為加鎖成功。釋放鎖時,需要
向所有節點發送del指令。不過Redlock算法還需要考慮出錯重試、時鐘漂移等很多細節問題,同時因為Redlock需要向多個節點進行讀寫,意
味著相比單實例 Redis 性能會下降一些。
如果你很在乎高可用性,希望掛了一臺redis完全不受影響,那就應該考慮 redlock。不過代價也是有的,需要更多的 redis 實例,性能
也下降了,代碼上還需要引入額外的library,運維上也需要特殊對待,這些都是需要考慮的成本,使用前請再三斟酌。
   
3) 鎖沖突處理
   我們講了分布式鎖的問題,但是沒有提到客戶端在處理請求時加鎖沒加成功怎么辦。
   
一般有3種策略來處理加鎖失敗:
3.1) 直接拋出異常,通知用戶稍后重試
     這種方式比較適合由用戶直接發起的請求,用戶看到錯誤對話框后,會先閱讀對話框的內容,再點擊重試,這樣就可以起到人工延時
的效果。如果考慮到用戶體驗,可以由前端的代碼替代用戶自己來進行延時重試控制。它本質上是對當前請求的放棄,由用戶決定是否重新
發起新的請求。
     
3.2) sleep一會再重試,不推薦

3.3) 將請求轉移至延時隊列,過一會再試
     這種方式比較適合異步消息處理,將當前沖突的請求扔到另一個隊列延后處理以避開沖突。

2、緩兵之計:延時隊列

1)異步消息隊列   
   Redis的消息隊列不是專業的消息隊列,它沒有非常多的高級特性,沒有ack保證,如果對消息的可靠性有著極致的追求,那么它就不適合使用。
   Redis的list(列表)數據結構常用來作為異步消息隊列使用,使用rpush/lpush操作入隊列,使用lpop和rpop來出隊列。
   
隊列空了怎么辦?
   可是如果隊列空了,客戶端就會陷入pop的死循環,不停地pop,沒有數據,接著再pop,又沒有數據。這就是浪費生命的空輪詢。空輪詢不但拉
高了客戶端的CPU,redis的QPS也會被拉高,如果這樣空輪詢的客戶端有幾十來個,Redis的慢查詢可能會顯著增多。
   
解決辦法:
1.1) 使用sleep來解決這個問題,讓線程睡一會
2.1) 使用blpop/brpop,阻塞讀在隊列沒有數據的時候,會立即進入休眠狀態,一旦數據到來,則立刻醒過來。消息的延遲幾乎為零。

空閑連接自動斷開怎么辦?
解決辦法:
   如果線程一直阻塞在哪里,Redis的客戶端連接就成了閑置連接,閑置過久,服務器一般會主動斷開連接,減少閑置資源占用。這個時候
blpop/brpop會拋出異常來。所以編寫客戶端消費者的時候要小心,注意捕獲異常,還要重試。
   
2) 延時隊列
   延時隊列可以通過Redis的zset(有序列表)來實現。我們將消息序列化成一個字符串作為zset的value,這個消息的處理時間作為score,
然后用多個線程輪詢zset獲取到期的任務進行處理,多個線程是為了保障可用性,萬一掛了一個線程還有其它線程可以繼續處理。因為有
多個線程,所以需要考慮并發爭搶任務,確保任務不能被多次執行。

如何進行Redis深度分析

//Redis實現延遲隊列
public class RedisDelayingQueue<T> {

    static class TaskItem<T> {
        public String id;
        public T msg;
    }

    private Type TaskType = new TypeReference<TaskItem<T>>(){}.getType();

    private Jedis jedis;

    private String queueKey;

    public RedisDelayingQueue(Jedis jedis, String queueKey){
        this.jedis = jedis;
        this.queueKey = queueKey;
    }

    public void delay(T msg){
        TaskItem taskItem = new TaskItem();
        taskItem.id = UUID.randomUUID().toString();
        taskItem.msg = msg;
        String s = JSON.toJSONString(taskItem);
        jedis.zadd(queueKey, System.currentTimeMillis() + 5000, s);
    }

    public void loop(){
        while (!Thread.interrupted()){
            //只取一條數據
            Set values = jedis.zrangeByScore(queueKey,0,System.currentTimeMillis(),0,1);
            
            if(values.isEmpty()){
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    break;
                }
                continue;
            }

            String s = (String) values.iterator().next();
            //zrem用于移除有序集中一個或多個成員
            if(jedis.zrem(queueKey, s) > 0){
                TaskItem task = JSON.parseObject(s, TaskType);
                this.handleMsg(task.msg);
            }
        }
    }

    private void handleMsg(Object msg) {
        System.out.println(msg);
    }

    public static void main(String[] args) {
        Jedis jedis = new Jedis();
        RedisDelayingQueue queue = new RedisDelayingQueue(jedis, "test-queue");

        Thread producer = new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 10; i++){
                    queue.delay("lwh" + i);
                }
            }
        };

        Thread consumer = new Thread(){
            @Override
            public void run() {
                queue.loop();
            }
        };

        producer.start();
        consumer.start();

        try {
            producer.join();
            Thread.sleep(6000);
            consumer.interrupt();
            consumer.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

3、節衣縮食: 位圖

1) 位圖 
   位圖不是特殊的數據結構,它的內容其實就是普通的字符串,也就是 byte 數組。我們可以使用普通的 get/set 直接獲取和設置整個位
圖的內容,也可以使用位圖操作 getbit/setbit等將 byte 數組看成「位數組」來處理。
   redis位圖可以實現零存整取、零存零取等。「零存」就是使用 setbit 對位值進行逐個設置,「整存」就是使用字符串一次性填充所有
位數組,覆蓋掉舊值。
   命令形如
   1) setbit s 1 1
   2) getbit s
   3) get s
   4) set s h
   
2) 統計和查找
   Redis提供了位圖統計指令bitcount和位圖查找指令bitpos,bitcount用來統計指定位置范圍內1的個數,bitpos用來查找指定范圍內出現
的第一個0或1。比如我們可以通過bitcount統計用戶一共簽到了多少天,通過bitpos指令查找用戶從哪一天開始第一次簽到。如果指定了范圍
參數[start,end],就可以統計在某個時間范圍內用戶簽到了多少天,用戶自某天以后的哪天開始簽到。
   
3) bitfield

4、四兩撥千斤: HyperLogLog

1) HyperLogLog使用
   統計PV:每個網頁一個獨立的Redis計數器
   統計UV?
解決辦法:
1.1) set去重,頁面訪問量大的情況下,耗費太多存儲空間
1.2) 使用HyperLogLog,不精確去重,標準誤差0.81%
    
   HyperLogLog提供了兩個指令pfadd和pfcount,根據字面意義很好理解,一個是增加計數,一個是獲取計數。pfadd用法和set集合的sadd
是一樣的,來一個用戶ID,就將用戶ID塞進去就是。pfcount和scard用法是一樣的,直接獲取計數值。
   pfadd test-log user1
   pfadd test-log user2
   
   pfcount test-log
   
   HyperLogLog 除了上面的pfadd和pfcount之外,還提供了第三個指令pfmerge,用于將多個pf計數值累加在一起形成一個新的pf值。比如在
網站中我們有兩個內容差不多的頁面,運營說需要這兩個頁面的數據進行合并。其中頁面的UV訪問量也需要合并,那這個時候pfmerge就可以派
上用場了。

5、層巒疊嶂:布隆過濾器

1) 布隆過濾器
   講個使用場景,比如我們在使用新聞客戶端看新聞時,它會給我們不停地推薦新的內容,它每次推薦時要去重,去掉那些已經看過的內容。
問題來了,新聞客戶端推薦系統如何實現推送去重的?
   當布隆過濾器說某個值存在時,這個值可能不存在;當它說不存在時,那就肯定不存在。
   
基本指令:布隆過濾器有二個基本指令,bf.add添加元素,bf.exists查詢元素是否存在,它的用法和set集合的sadd和sismember差不多。
注意bf.add只能一次添加一個元素,如果想要一次添加多個,就需要用到bf.madd指令。同樣如果需要一次查詢多個元素是否存在,就需要用到
bf.mexists指令。
   
   bf.add test-filter user1
   bf.add test-filter user2
   
   bf.exists test-filter user1
   
   Redis其實還提供了自定義參數的布隆過濾器,需要我們在add之前使用bf.reserve指令顯式創建。如果對應的 key已經存在,bf.reserve
會報錯。bf.reserve有三個參數,分別是key,error_rate和initial_size。錯誤率越低,需要的空間越大。initial_size參數表示預計放入
的元素數量,當實際數量超出這個數值時,誤判率會上升。

2) 布隆過濾器的原理
   每個布隆過濾器對應到Redis的數據結構里面就是一個大型的位數組和幾個不一樣的無偏hash函數。所謂無偏就是能夠把元素的hash值算得
比較均勻。向布隆過濾器中添加key時,會使用多個hash函數對 key進行hash算得一個整數索引值然后對位數組長度進行取模運算得到一個位
置,每個hash函數都會算得一個不同的位置。再把位數組的這幾個位置都置為1就完成了add操作。

3) 布隆過濾器的其他應用
   在爬蟲系統中,我們需要對URL進行去重,已經爬過的網頁就可以不用爬了。但是URL太多了,幾千萬幾個億,如果用一個集合裝下這些URL
地址那是非常浪費空間的。這時候就可以考慮使用布隆過濾器。它可以大幅降低去重存儲消耗,只不過也會使得爬蟲系統錯過少量的頁面。布
隆過濾器在NoSQL數據庫領域使用非常廣泛,我們平時用到的HBase、Cassandra還有LevelDB、RocksDB內部都有布隆過濾器結構,布隆過濾器
可以顯著降低數據庫的IO請求數量。當用戶來查詢某個row時,可以先通過內存中的布隆過濾器過濾掉大量不存在的row請求,然后再去磁盤進
行查詢。
   郵箱系統的垃圾郵件過濾功能也普遍用到了布隆過濾器,因為用了這個過濾器,所以平時也會遇到某些正常的郵件被放進了垃圾郵件目錄中,
這個就是誤判所致,概率很低。

6、斷尾求生:簡單限流

   除了控制流量,限流還有一個應用目的是用于控制用戶行為,避免垃圾請求。比如在UGC社區,用戶的發帖、回復、點贊等行為都要嚴格受
控,一般要嚴格限定某行為在規定時間內允許的次數,超過了次數那就是非法行為。對非法行為,業務必須規定適當的懲處策略。

如何進行Redis深度分析

//這個限流需求中存在一個滑動時間窗口,想想zset數據結構的score值,是不是可以通過score來圈出這個時間窗口來。而且我們只需要
//保留這個時間窗口,窗口之外的數據都可以砍掉。那這個zset的value填什么比較合適呢?它只需要保證唯一性即可,用uuid會比較浪費
//空間,那就改用毫秒時間戳吧。

//但這種方案也有缺點,因為它要記錄時間窗口內所有的行為記錄,如果這個量很大,比如限定60s內操作不得超過100w次這樣的參數,它
//是不適合做這樣的限流的,因為會消耗大量的存儲空間。
public class SimpleRateLimiter {

    private final Jedis jedis;

    public SimpleRateLimiter(Jedis jedis){
        this.jedis = jedis;
    }

    public boolean isActionAllowed(String userId, String actionKey, int period, int maxCount) throws IOException {
        String key = String.format("hist:%s:%s", userId, actionKey);
        long nowTs = System.currentTimeMillis();

        Pipeline pipeline = jedis.pipelined();
        //開啟一個事務
        pipeline.multi();
        //value和score都用毫秒時間戳
        pipeline.zadd(key, nowTs, "" + nowTs);
        //移除時間窗口之外的行為記錄,剩下的都是時間窗口內的
        pipeline.zremrangeByScore(key, 0, nowTs - period * 1000);
        //獲得[nowTs - period * 1000, nowTs]的key的數量
        Response<Long> count = pipeline.zcard(key);
        //每次設置都更新key的過期時間
        pipeline.expire(key, period);

        //在事務中執行上述命令
        pipeline.exec();
        pipeline.close();

        return count.get() <= maxCount;
    }

    public static void main(String[] args) throws IOException, InterruptedException {
        Jedis jedis=new Jedis("localhost",6379);
        SimpleRateLimiter limiter=new SimpleRateLimiter(jedis);
        for (int i = 0; i < 20; i++) {
            //每個用戶在1秒內最多能做五次動作
            System.out.println(limiter.isActionAllowed("lwh","reply",1,5));
        }
    }
}

7、一毛不拔:漏斗限流

   Redis4.0提供了一個限流Redis模塊,它叫redis-cell。該模塊也使用了漏斗算法,并提供了原子的限流指令。有了這個模塊,限流問題就
非常簡單了。
   
   cl.throttle lwh:reply 15 30 60 1
       
   上面這個指令的意思是允許「用戶lwh回復行為」的頻率為每 60s 最多 30 次(漏水速率),漏斗的初始容量為 15,也就是說一開始可以連
續回復 15 個帖子,然后才開始受漏水速率的影響。

8、近水樓臺:GeoHash

   Redis在3.2版本以后增加了地理位置GEO模塊,意味著我們可以使用Redis來實現摩拜單車「附近的 Mobike」、美團和餓了么「附近的
餐館」這樣的功能了。
   業界比較通用的地理位置距離排序算法是GeoHash算法,Redis也使用GeoHash算法。GeoHash算法將二維的經緯度數據映射到一維的整數,
這樣所有的元素都將在掛載到一條線上,距離靠近的二維坐標映射到一維后的點之間距離也會很接近。當我們想要計算「附近的人時」,首先
將目標位置映射到這條線上,然后在這個一維的線上獲取附近的點就行了。
   在Redis里面,經緯度使用52位的整數進行編碼,放進了zset里面,zset的value是元素的key,score是GeoHash的52位整數值。zset的
score雖然是浮點數,但是對于52位的整數值,它可以無損存儲。在使用Redis進行Geo查詢時,我們要時刻想到它的內部結構實際上只是一
個zset(skiplist)。通過zset的score 排序就可以得到坐標附近的其它元素 (實際情況要復雜一些,不過這樣理解足夠了),通過將score
還原成坐標值就可以得到元素的原始坐標。
   
   1) 添加,geoadd指令攜帶集合名稱以及多個經緯度名稱三元組
       geoadd company 116.48105 39.996794 juejin
       geoadd company 116.514203 39.905409 ireader
   
   2) 距離,geodist指令可以用來計算兩個元素之間的距離
       geodist company juejin ireader km
       
   3) 獲取元素位置,geopos指令可以獲取集合中任意元素的經緯度坐標
	   geopos company juejin
	   
   我們觀察到獲取的經緯度坐標和geoadd進去的坐標有輕微的誤差,原因是geohash對二維坐標進行的一維映射是有損的,通過映射再還原
回來的值會出現較小的差別。對于「附近的人」這種功能來說,這點誤差根本不是事。
   
   4) 附近的公司,georadiusbymember指令是最為關鍵的指令,它可以用來查詢指定元素附近的其它元素
       //范圍20公里以內最多3個元素按距離正排,它不會排除自身
   	   georadiusbymember company ireader 20 km count 3 asc
   	   
   	   //三個可選參數 withcoord withdist withhash 用來攜帶附加參數
   	   //withdist可以顯示距離
   	   //withcoord顯示坐標
   	   georadiusbymember company ireader 20 km withcoord withdist withhash count 3 asc

   5) Redis還提供了根據坐標值來查詢附近的元素,這個指令更加有用,它可以根據用戶的定位來計算「附近的車」,「附近的餐館」等。
它的參數和georadiusbymember基本一致,除了將目標元素改成經緯度坐標值。
	   georadius company 116.514202 39.905409 20 km withdist count 3 asc   
	
注意事項:
   在一個地圖應用中,車的數據、餐館的數據、人的數據可能會有百萬千萬條,如果使用Redis的Geo數據結構,它們將全部放在一個zset
集合中。在Redis的集群環境中,集合可能會從一個節點遷移到另一個節點,如果單個key的數據過大,會對集群的遷移工作造成較大的影響,
在集群環境中單個key對應的數據量不宜超過1M,否則會導致集群遷移出現
卡頓現象,影響線上服務的正常運行。

   所以,這里建議Geo的數據使用單獨的Redis實例部署,不使用集群環境。如果數據量過億甚至更大,就需要對Geo數據進行拆分,按國家
拆分、按省拆分,按市拆分,在人口特大城市甚至可以按區拆分。這樣就可以顯著降低單個zset集合的大小。

9、大海撈針:Scan

   在平時線上Redis維護工作中,有時候需要從Redis實例成千上萬的key中找出特定前綴的key列表來手動處理數據,可能是修改它的值,也
可能是刪除key。這里就有一個問題,如何從海量的key中找出滿足特定前綴的key列表來?
   Redis提供了一個簡單暴力的指令keys用來列出所有滿足特定正則字符串規則的key。缺點:沒有offset、limit參數,事件復雜度O(n),key
過多時會導致卡頓
   
   Redis為了解決這個問題,它在2.8版本中加入了大海撈針的指令——scan。scan相比keys具備有以下特點:
1)、復雜度雖然也是 O(n),但是它是通過游標分步進行的,不會阻塞線程
2)、提供limit參數,可以控制每次返回結果的最大條數,limit只是一個hint,返回的結果可多可少
3)、同keys一樣,它也提供模式匹配功能
4)、服務器不需要為游標保存狀態,游標的唯一狀態就是scan返回給客戶端的游標整數
5)、返回的結果可能會有重復,需要客戶端去重復,這點非常重要
6)、遍歷的過程中如果有數據修改,改動后的數據能不能遍歷到是不確定的
7)、單次返回的結果是空的并不意味著遍歷結束,而要看返回的游標值是否為零

   scan提供了三個參數,第一個是cursor整數值,第二個是key的正則模式,第三個是遍歷的limit hint。第一次遍歷時,cursor值為0,然
后將返回結果中第一個整數值作為下一次遍歷的cursor。一直遍歷到返回的cursor值為0時結束。
    scan 0 match key99* count 1000     --> 返回cursor13796作為下次遍歷的cursor
    scan 13976 match key99* count 1000

    scan 指令返回的游標就是第一維數組的位置索引,我們將這個位置索引稱為槽 (slot)。如果不考慮字典的擴容縮容,直接按數組下標挨
個遍歷就行了。limit參數就表示需要遍歷的槽位數,之所以返回的結果可能多可能少,是因為不是所有的槽位上都會掛接鏈表,有些槽位可能
是空的,還有些槽位上掛接的鏈表上的元素可能會有多個。每一次遍歷都會將limit數量的槽位上掛接的所有鏈表元素進行模式匹配過濾后,一
次性返回給客戶端。
    
    scan 的遍歷順序非常特別。它不是從第一維數組的第0位一直遍歷到末尾,而是采用了高位進位加法來遍歷。之所以使用這樣特殊的方式
進行遍歷,是考慮到字典的擴容和縮容時避免槽位的遍歷重復和遺漏。
    
    在平時的業務開發中,要盡量避免大key的產生。有時候會因為業務人員使用不當,在Redis實例中會形成很大的對象,比如一個很大的
hash,一個很大的zset這都是經常出現的。這樣的對象對Redis的集群數據遷移帶來了很大的問題,因為在集群環境下,如果某個key太大,
會數據導致遷移卡頓。另外在內存分配上,如果一個key太大,那么當它需要擴容時,會一次性申請更大的一塊內存,這也會導致卡頓。如果
這個大key被刪除,內存會一次性回收,卡頓現象會再一次產生。
    不過Redis官方已經在redis-cli指令中提供了大key掃描功能。第二條指令每隔 100 條 scan 指令就會休眠 0.1s,ops 就不會劇烈抬升,
但是掃描的時間會變長。
    redis-cli -h 127.0.0.1 -p 7001 –-bigkeys
    redis-cli -h 127.0.0.1 -p 7001 –-bigkeys -i 0.1

如何進行Redis深度分析

如何進行Redis深度分析

10、鞭辟入里:線程IO模型

1) 定時任務
   服務器處理要響應IO事件外,還要處理其它事情。比如定時任務就是非常重要的一件事。如果線程阻塞在select系統調用上,定時任務將無
法得到準時調度。那Redis是如何解決這個問題的呢?
   Redis的定時任務會記錄在一個稱為最小堆的數據結構中。這個堆中,最快要執行的任務排在堆的最上方。在每個循環周期,Redis都會將最
小堆里面已經到點的任務立即進行處理。處理完畢后,將最快要執行的任務還需要的時間記錄下來,這個時間就是select系統調用的timeout參
數。因為Redis知道未來timeout時間內,沒有其它定時任務需要處理,所以可以安心睡眠timeout的時間。
   Nginx 和 Node 的事件處理原理和 Redis 也是類似的

11、交頭接耳:通信協議

   Redis的作者認為數據庫系統的瓶頸一般不在于網絡流量,而是數據庫自身內部邏輯處理上。所以即使Redis使用了浪費流量的文本協議,
依然可以取得極高的訪問性能。
   RESP(Redis Serialization Protocol). RESP是Redis序列化協議的簡寫。它是一種直觀的文本協議,優勢在于實現異常簡單,解析性能
極好。

12、未雨綢繆:持久化

   當父進程對其中一個頁面的數據進行修改時,會將被共享的頁面復制一份分離出來,然后對這個復制的頁面進行修改。這時子進程相應的
頁面是沒有變化的,還是進程產生時那一瞬間的數據。子進程因為數據沒有變化,它能看到的內存里的數據在進程產生的一瞬間就凝固了,
再也不會改變,這也是為什么 Redis 的持久化叫「快照」的原因。接下來子進程就可以非常安心的遍歷數據了進行序列化寫磁盤了。
   
   Redis4.0混合持久化
   重啟Redis時,我們很少使用rdb來恢復內存狀態,因為會丟失大量數據。我們通常使用AOF日志重放,但是重放AOF日志性能相對rdb來說要
慢很多,這樣在Redis實例很大的情況下,啟動需要花費很長的時間。Redis4.0為了解決這個問題,帶來了一個新的持久化選項——混合持久化。
將rdb文件的內容和增量的AOF日志文件存在一起。這里的AOF日志不再是全量的日志,而是自持久化開始到持久化結束的這段時間發生的增量
AOF日志,通常這部分AOF日志很小。
   于是在Redis重啟的時候,可以先加載rdb的內容,然后再重放增量AOF日志就可以完全替代之前的AOF全量文件重放,重啟效率因此大幅得
到提升。

13、開源節流:小對象壓縮

1) 小對象壓縮
   如果Redis內部管理的集合數據結構很小,它會使用緊湊存儲形式壓縮存儲。如果它存儲的是hash結構,那么key和 value會作為兩個entry
相鄰存在一起。如果它存儲的是zset,那么value和score會作為兩個entry相鄰存在一起。
   存儲界限 當集合對象的元素不斷增加,或者某個value值過大,這種小對象存儲也會被升級為標準結構。
   
2) 內存回收機制
   Redis并不總是可以將空閑內存立即歸還給操作系統。
   如果當前Redis內存有10G,當你刪除了1GB的key后,再去觀察內存,你會發現內存變化不會太大。原因是操作系統回收內存是以頁為單位,
如果這個頁上只要有一個key還在使用,那么它就不能被回收。Redis雖然刪除了1GB的key,但是這些key分散到了很多頁面中,每個頁面都還有
其它key存在,這就導致了內存不會立即被回收。
   不過,如果你執行flushdb,然后再觀察內存會發現內存確實被回收了。原因是所有的key都干掉了,大部分之前使用的頁面都完全干凈了,
會立即被操作系統回收。
   Redis雖然無法保證立即回收已經刪除的key的內存,但是它會重用那些尚未回收的空閑內存。這就好比電影院里雖然人走了,但是座位還在,
下一波觀眾來了,直接坐就行。而操作系統回收內存就好比把座位都給搬走了。這個比喻是不是很6?

14、有備無患:主從同步

1) CAP理論
   C:Consistent,一致性
   A:Availbility,可用性
   P:Partition tolerance,分區容忍性
   
   分布式系統的節點往往都是分布在不同的機器上進行網絡隔離開的,這意味著必然會有網絡斷開的風險,這個網絡斷開的場景的專業詞匯
叫著「網絡分區」。
   在網絡分區發生時,兩個分布式節點之間無法進行通信,我們對一個節點進行的修改操作將無法同步到另外一個節點,所以數據的「一致
性」將無法滿足,因為兩個分布式節點的數據不再保持一致。除非我們犧牲「可用性」,也就是暫停分布式節點服務,在網絡分區發生時,不
再提供修改數據的功能,直到網絡狀況完全恢復正常再繼續對外提供服務。
   一句話概括CAP原理就是——網絡分區發生時,一致性和可用性兩難全。

看完上述內容,你們對如何進行Redis深度分析有進一步的了解嗎?如果還想了解更多知識或者相關內容,請關注億速云行業資訊頻道,感謝大家的支持。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

凤山县| 新泰市| 隆尧县| 嘉祥县| 五台县| 长阳| 神农架林区| 安泽县| 景东| 绵阳市| 桐乡市| 当雄县| 南京市| 丹江口市| 沙雅县| 闵行区| 石屏县| 台安县| 凤凰县| 湄潭县| 福海县| 武安市| 运城市| 青冈县| 应城市| 浙江省| 奉新县| 商都县| 河西区| 赫章县| 三门峡市| 遂平县| 漠河县| 杭锦后旗| 杂多县| 金堂县| 汶上县| 建水县| 阿鲁科尔沁旗| 赤壁市| 沙田区|