您好,登錄后才能下訂單哦!
今天就跟大家聊聊有關如何進行Redis深度分析,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結了以下內容,希望大家根據這篇文章可以有所收獲。
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) 使用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) 將請求轉移至延時隊列,過一會再試 這種方式比較適合異步消息處理,將當前沖突的請求扔到另一個隊列延后處理以避開沖突。
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實現延遲隊列 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(); } } }
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
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就可以派 上用場了。
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請求,然后再去磁盤進 行查詢。 郵箱系統的垃圾郵件過濾功能也普遍用到了布隆過濾器,因為用了這個過濾器,所以平時也會遇到某些正常的郵件被放進了垃圾郵件目錄中, 這個就是誤判所致,概率很低。
除了控制流量,限流還有一個應用目的是用于控制用戶行為,避免垃圾請求。比如在UGC社區,用戶的發帖、回復、點贊等行為都要嚴格受 控,一般要嚴格限定某行為在規定時間內允許的次數,超過了次數那就是非法行為。對非法行為,業務必須規定適當的懲處策略。
//這個限流需求中存在一個滑動時間窗口,想想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)); } } }
Redis4.0提供了一個限流Redis模塊,它叫redis-cell。該模塊也使用了漏斗算法,并提供了原子的限流指令。有了這個模塊,限流問題就 非常簡單了。 cl.throttle lwh:reply 15 30 60 1 上面這個指令的意思是允許「用戶lwh回復行為」的頻率為每 60s 最多 30 次(漏水速率),漏斗的初始容量為 15,也就是說一開始可以連 續回復 15 個帖子,然后才開始受漏水速率的影響。
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集合的大小。
在平時線上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
1) 定時任務 服務器處理要響應IO事件外,還要處理其它事情。比如定時任務就是非常重要的一件事。如果線程阻塞在select系統調用上,定時任務將無 法得到準時調度。那Redis是如何解決這個問題的呢? Redis的定時任務會記錄在一個稱為最小堆的數據結構中。這個堆中,最快要執行的任務排在堆的最上方。在每個循環周期,Redis都會將最 小堆里面已經到點的任務立即進行處理。處理完畢后,將最快要執行的任務還需要的時間記錄下來,這個時間就是select系統調用的timeout參 數。因為Redis知道未來timeout時間內,沒有其它定時任務需要處理,所以可以安心睡眠timeout的時間。 Nginx 和 Node 的事件處理原理和 Redis 也是類似的
Redis的作者認為數據庫系統的瓶頸一般不在于網絡流量,而是數據庫自身內部邏輯處理上。所以即使Redis使用了浪費流量的文本協議, 依然可以取得極高的訪問性能。 RESP(Redis Serialization Protocol). RESP是Redis序列化協議的簡寫。它是一種直觀的文本協議,優勢在于實現異常簡單,解析性能 極好。
當父進程對其中一個頁面的數據進行修改時,會將被共享的頁面復制一份分離出來,然后對這個復制的頁面進行修改。這時子進程相應的 頁面是沒有變化的,還是進程產生時那一瞬間的數據。子進程因為數據沒有變化,它能看到的內存里的數據在進程產生的一瞬間就凝固了, 再也不會改變,這也是為什么 Redis 的持久化叫「快照」的原因。接下來子進程就可以非常安心的遍歷數據了進行序列化寫磁盤了。 Redis4.0混合持久化 重啟Redis時,我們很少使用rdb來恢復內存狀態,因為會丟失大量數據。我們通常使用AOF日志重放,但是重放AOF日志性能相對rdb來說要 慢很多,這樣在Redis實例很大的情況下,啟動需要花費很長的時間。Redis4.0為了解決這個問題,帶來了一個新的持久化選項——混合持久化。 將rdb文件的內容和增量的AOF日志文件存在一起。這里的AOF日志不再是全量的日志,而是自持久化開始到持久化結束的這段時間發生的增量 AOF日志,通常這部分AOF日志很小。 于是在Redis重啟的時候,可以先加載rdb的內容,然后再重放增量AOF日志就可以完全替代之前的AOF全量文件重放,重啟效率因此大幅得 到提升。
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?
1) CAP理論 C:Consistent,一致性 A:Availbility,可用性 P:Partition tolerance,分區容忍性 分布式系統的節點往往都是分布在不同的機器上進行網絡隔離開的,這意味著必然會有網絡斷開的風險,這個網絡斷開的場景的專業詞匯 叫著「網絡分區」。 在網絡分區發生時,兩個分布式節點之間無法進行通信,我們對一個節點進行的修改操作將無法同步到另外一個節點,所以數據的「一致 性」將無法滿足,因為兩個分布式節點的數據不再保持一致。除非我們犧牲「可用性」,也就是暫停分布式節點服務,在網絡分區發生時,不 再提供修改數據的功能,直到網絡狀況完全恢復正常再繼續對外提供服務。 一句話概括CAP原理就是——網絡分區發生時,一致性和可用性兩難全。
看完上述內容,你們對如何進行Redis深度分析有進一步的了解嗎?如果還想了解更多知識或者相關內容,請關注億速云行業資訊頻道,感謝大家的支持。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。