Redis知識彙總

1、什麼是Redis?redis

    Redis 是一個高性能的開源的、C語言寫的Nosql(非關係型數據庫),數據保存在內存中。Redis 是以key-value形式存儲sql


2、Redis的優點?
typescript

    1.數據保存在內存,存取速度快,併發能力強
    2.它支持存儲的value類型相對更多,包括string(字符串)、list(鏈表)、set(集合)、 zset(sorted set --有序集合)和hash(哈希類型)。
    3.redis的出現,很大程度補償了memcached這類key/value存儲的不足,在部分場合能夠對關係數據庫(如MySQL)起到很好的補充做用。
    4.它提供了Java,C/C++,C#,PHP,JavaScript等客戶端,使用很方便。
    5.Redis支持集羣(主從同步、負載均衡)。數據能夠主服務器向任意數量從的從服務器上同步,從服務器能夠是關聯其餘從服務器的主服務器。
    6.支持持久化,能夠將數據保存在硬盤的文件中
    7.支持訂閱/發佈(subscribe/publish)功能 
數據庫


3、redis基本數據類型後端

    String(字符串):是redis最基本的類型.緩存

        示例:set name value 取值:get name 注:(鍵最大能存儲512MB)服務器

    Hash(哈希):是一個string類型的field和value的映射表.微信

        示例:HMSET name key value 取值:HGET name key併發

    List(列表):是簡單的字符串列表,按照插入順序排序。app

        你能夠添加一個元素到列表的頭部(左邊)或者尾部(右邊)

        示例:LPUSH name value

    Set(集合):是string類型的無序集合

        示例:SADD name value

    zset(有序集合):

        示例:ZADD name 下標 value


4、爲何使用redis,何時使用?

  把常常查詢的數據,不多修改的數據存放到緩存中,減小訪問數據庫,下降數據庫壓力而且緩存通常都是內存,訪問速度比較快。

  1.  不須要實時更新可是又極其消耗數據庫的數據。

  2. 須要實時更新,可是更新頻率不高的數據。

  3. 在某個時刻訪問量極大並且更新也很頻繁的數據。

  4. 典型場景:用戶消息,評論,訂單列表、地區數據、商品分類、數據字典 菜單等常常查詢或者不多修改的數據

5、Redis持久化

    Redis 提供了兩種不一樣級別的持久化方式:RDB和AOF,能夠經過修改redis.conf來進行配置

    1、RDB(快照)持久化:保存某個時間點的全量數據快照

    觸發機制:

    A.手動觸發分別對應save和bgsave命令

        ·save命令:阻塞當前Redis服務器,直到RDB過程完成爲止,對於內存 比較大的實例會形成長時間阻塞,線上環境不建議使用

        ·bgsave命令:Redis進程執行fork操做建立子進程,RDB持久化過程由子 進程負責,完成後自動結束。阻塞只發生在fork階段,通常時間很短


    B.自動觸發:使用save相關配置,如「save m n」。表示m秒內數據集存在n次修改 時,自動觸發bgsave。


優勢

(1)RDB文件緊湊,全量備份,很是適合用於進行備份和災難恢復。

(2)生成RDB文件的時候,redis主進程會fork()一個子進程來處理全部保存工做,主進程不須要進行任何磁盤IO操做。

(3)RDB 在恢復大數據集時的速度比 AOF 的恢復速度要快。

缺點

    RDB快照是一次全量備份,存儲的是內存數據的二進制序列化形式,存儲上很是緊湊。Redis版本演進過程當中有多個格式 的RDB版本,存在老版本Redis服務沒法兼容新版RDB格式的問題


    2、AOF(Append -Only - File)持久化:日誌追加模式。默認是關閉的。

    以獨立日誌的方式記錄每次寫命令, 重啓時再從新執行AOF文件中的命令達到恢復數據的目的。AOF的主要做用是解決了數據持久化的實時性

    開啓AOF功能須要設置配置:appendonly yes,默認不開啓。AOF文件名 經過appendfilename配置設置,默認文件名是appendonly.aof。保存路徑同 RDB持久化方式一致,經過dir配置指定。

    AOF的工做流程操做:命令寫入 (append)、文件同步(sync)、文件重寫(rewrite)、重啓加載 (load)

    優勢

(1)AOF能夠更好的保護數據不丟失,通常AOF會每隔1秒,經過一個後臺線程執行一次fsync操做,最多丟失1秒鐘的數據。

(2)AOF日誌文件沒有任何磁盤尋址的開銷,寫入性能很是高,文件不容易破損。

(3)AOF日誌文件即便過大的時候,出現後臺重寫操做,也不會影響客戶端的讀寫。

(4)AOF日誌文件的命令經過很是可讀的方式進行記錄,這個特性很是適合作災難性的誤刪除的緊急恢復。好比某人不當心用flushall命令清空了全部數據,只要這個時候後臺rewrite尚未發生,那麼就能夠當即拷貝AOF文件,將最後一條flushall命令給刪了,而後再將該AOF文件放回去,就能夠經過恢復機制,自動恢復全部數據

    缺點

(1)對於同一份數據來講,AOF日誌文件一般比RDB數據快照文件更大

(2)AOF開啓後,支持的寫QPS會比RDB支持的寫QPS低,由於AOF通常會配置成每秒fsync一第二天志文件,固然,每秒一次fsync,性能也仍是很高的

(3)之前AOF發生過bug,就是經過AOF記錄的日誌,進行數據恢復的時候,沒有恢復如出一轍的數據出來。

    

    3、RDB-AOF 混合持久化方式BGSAVE 全量持久化,AOF作增量持久化 

            

RDB與AOF區別

根據本身須要選擇合適的持久化


6、Redis常見的問題及解決方案

    1.緩存穿透

        key對應的數據在數據源並不存在,每次針對此key的請求從緩存獲取不到,請求都會到數據源,從而可能壓垮數據源。好比一個不存在的用戶id獲取用戶信息,不論緩存仍是數據庫都沒有,若黑客利用此漏洞進行攻擊就可能壓垮數據庫,致使系統崩潰。

    解決方案:

    (1)採用布隆過濾器,將全部可能存在的數據哈希到一個足夠大的bitmap中,一個必定不存在的數據會被這個bitmap攔截掉,從而避免了對底層存儲系統的查詢壓力。

    (2)另外也有一個更爲簡單粗暴的方法,若是一個查詢返回的數據爲空(不論是數據不存在,仍是系統故障),咱們仍然把這個空結果進行緩存,但它的過時時間會很短,最長不超過五分鐘。

public object GetRedis() { int cacheTime = 10; String cacheKey = "key";
String cacheValue = CacheHelper.Get(cacheKey); if (cacheValue != null) { return cacheValue; }
cacheValue = CacheHelper.Get(cacheKey); if (cacheValue != null) { return cacheValue; } else { //數據庫查詢不到,爲空 cacheValue = GetProductListFromDB(); if (cacheValue == null) { //若是發現爲空,設置個默認值,也緩存起來 cacheValue = string.Empty; } CacheHelper.Add(cacheKey, cacheValue, cacheTime); return cacheValue; }}


    2.緩存擊穿

        key對應的數據存在,但在redis中過時,此時如有大量併發請求過來,這些請求發現緩存過時通常都會從後端DB加載數據並回設到緩存,這個時候大併發的請求可能會瞬間把後端DB壓垮。

    解決方案:

使用互斥鎖(mutex key)

    比較經常使用的作法,是使用mutex。在緩存失效的時候(判斷拿出來的值爲空),不是當即去load db,而是先使用緩存工具的某些帶成功操做返回值的操做去set一個mutex key,當操做返回成功時,再進行load db的操做並回設緩存;不然,就重試整個get緩存的方法。

SETNX,是「SET if Not eXists」的縮寫,也就是隻有不存在的時候才設置,能夠利用它來實現鎖的效果。

public String get(key) {  String value = redis.get(key);  if (value == null) { //表明緩存值過時  //設置3min的超時,防止del操做失敗的時候,下次緩存過時一直不能load db  if (redis.setnx(key_mutex, 1, 3 * 60) == 1) { //表明設置成功  value = db.get(key);  redis.set(key, value, expire_secs);  redis.del(key_mutex);  } else { //這個時候表明同時候的其餘線程已經load db並回設到緩存了,這時候重試獲取緩存值便可  sleep(50);  get(key); //重試  }  } else {  return value;  }  }


    3.緩存雪崩

       當緩存服務器重啓或者大量緩存集中在某一個時間段失效,這樣在失效的時候,也會給後端系統(好比DB)帶來很大壓力。

        解決方案:

        時間岔開,確保你們的key不會落在同一個expire點上。

          使用鎖或隊列、設置過時標誌更新緩存、爲key設置不一樣的緩存失效時間,還有一種被稱爲「二級緩存」的解決方法。

           考慮用加鎖或者隊列的方式保證來保證不會有大量的線程對數據庫一次性進行讀寫,從而避免失效時大量的併發請求落到底層存儲系統上。還有一個簡單方案就時講緩存失效時間分散開,好比咱們能夠在原有的失效時間基礎上增長一個隨機值,好比1-5分鐘隨機,這樣每個緩存的過時時間的重複率就會下降,就很難引起集體失效的事件。

加鎖排隊//僞代碼public object GetRedis() { int cacheTime = 30; String cacheKey = "key"; String lockKey = cacheKey;
String cacheValue = CacheHelper.get(cacheKey); if (cacheValue != null) { return cacheValue; } else { synchronized(lockKey) { cacheValue = CacheHelper.get(cacheKey); if (cacheValue != null) { return cacheValue; } else { //這裏通常是sql查詢數據 cacheValue = GetProductListFromDB(); CacheHelper.Add(cacheKey, cacheValue, cacheTime); } } return cacheValue; }}

加鎖排隊的解決方式分佈式環境的併發問題,有可能還要解決分佈式鎖的問題;線程還會被阻塞,用戶體驗不好!


隨機值僞代碼://僞代碼public object GetRedis() { int cacheTime = 30; String cacheKey = "key"; //緩存標記 String cacheSign = cacheKey + "_sign";
String sign = CacheHelper.Get(cacheSign); //獲取緩存值 String cacheValue = CacheHelper.Get(cacheKey); if (sign != null) { return cacheValue; //未過時,直接返回 } else { CacheHelper.Add(cacheSign, "1", cacheTime); ThreadPool.QueueUserWorkItem((arg) -> { //這裏通常是 sql查詢數據 cacheValue = GetProductListFromDB(); //日期設緩存時間的2倍,用於髒讀 CacheHelper.Add(cacheKey, cacheValue, cacheTime * 2); }); return cacheValue; }}


本文分享自微信公衆號 - JAVA技術摘要(lei_culture)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索