1. 程式人生 > 實用技巧 >redis知識彙總

redis知識彙總

一、是什麼?

  redis是高效能key value非關係型資料庫,C語言開發,基於記憶體的,速度極快,用作分散式快取和分散式鎖,每秒處理10萬次讀寫。支援事務持久化lua指令碼叢集。

   5種資料型別:String,list,set,sorted set,hash(全部通過redisObject物件進行儲存)

  redis客戶端與伺服器

  • String,字串操作,整數和浮點數自增或自減操作,用作鍵值對快取。也可以存圖片或序列化物件
  • List,列表型資料,粉絲列表,文章列表類的,最新訊息排行,可以當做訊息佇列用。雙向連結串列,實現反向查詢和遍歷,帶來了額外的記憶體開銷
  • Set,交併差操作。無順序不重複。判斷元素是否在集合彙總。
  • Zset,排序不重複。排行榜,帶權重的訊息佇列。內部使用HashMap和跳躍表(SkipList)來保證資料的儲存與有序,HashMap放成員到Score的對映,跳躍表放所有成員,排序依據是HashMap中的Score,使用跳躍表的資料結構可以獲得較高的查詢效率,並且實現上簡單。Redis快取實現排序功能 海量積分資料實時排名(資料結構)
    返回使用者7日內的文章,且文章要求不重複 ,zadd userId score title 這裡的score為當前時間的時間戳;ZREMRANGEBYSCORE key 0 score 這裡的score設為當前時間前7天對應的時間的時間戳;(具體時間戳可以用java Calander類計算得到)這裡可以啟動一個定時任務去定時呼叫這個命令即可
  • Hash,操作鍵值對,特適合儲存物件。儲存讀取修改使用者屬性

二、特性

  支援豐富的資料型別,速度快(讀11萬次每秒,寫8萬次每秒),可持久化(AOF,RDB)

   單執行緒,Redis使用佇列技術,將併發訪問變為序列訪問,消除了傳統資料庫序列控制的開銷,消除了併發競爭

  支援事務和主從複製

  資料庫容量受記憶體限制,不能用作海量資料的高效能讀寫。

  不具備自動容錯和恢復功能,主機從機的宕機都會導致前端部分請求失敗,需要等待機器重啟或主動切換前端IP

  主機宕機,切換IP後還會導致資料不一致,降低系統可用性

  1、Redis為什麼這麼快?

    完全基於記憶體,絕大部分請求是存粹記憶體操作,速度非常快。資料結構簡單。單執行緒避免了不必要的上下文切換和競爭條件,不用考慮鎖的問題以及加鎖釋放鎖,不存在死鎖導致效能開銷。使用IO多路複用模型,是非阻塞IO。Redis直接構建了VM機制,一般系統呼叫函式,會浪費效能。

三、使用場景

  • 網站排行榜
  • 計數器
    • 點贊數,關注數,粉絲數,已讀數,未讀數,帖子數,收藏數,閱讀數,介面一分鐘限制多少請求
    • 特點:實時性要求高,寫的頻率高
    • @SpringBootTest
      public class Redis3ApplicationTests {
          @Test
          public void increase() throws InterruptedException {
              long currentTimeMillis = System.currentTimeMillis();
              Thread thread = new Thread(new Runnable() {
                  Jedis jedis = RedisUtil.getJedis();
                  @Override
                  public void run() {
                      for (int i = 0; i < 10000; i++) {
                          Long num = jedis.incr("num");
                      }
                  }
              });
              thread.start();
              thread.join();
              System.out.println("耗時"+(System.currentTimeMillis() - currentTimeMillis));
          }
      }
      
      計數器
      計數器
      package com.yhq.redis3.util;
      
      import redis.clients.jedis.Jedis;
      import redis.clients.jedis.JedisPool;
      import redis.clients.jedis.JedisPoolConfig;
      
      public  class RedisUtil {
          //Redis伺服器IP
          private static String ADDR = "127.0.0.1";
      
          //Redis的埠號
          private static int PORT = 6379;
      
          //訪問密碼
          private static String AUTH = "root123456";
      
          //可用連線例項的最大數目,預設值為8;
          //如果賦值為-1,則表示不限制;如果pool已經分配了maxActive個jedis例項,則此時pool的狀態為exhausted(耗盡)。
          private static int MAX_TOTAL = 8;
      
          //最小空閒連線數, 預設0
          private static int MIN_IDLE=0;
      
          //控制一個pool最多有多少個狀態為idle(空閒的)的jedis例項,預設值也是8。
          //最大空閒連線數, 預設8個
          private static int MAX_IDLE = 8;
      
          //獲取連線時的最大等待毫秒數(如果設定為阻塞時BlockWhenExhausted),如果超時就拋異常, 小於零:阻塞不確定的時間,  預設-1
          //等待可用連線的最大時間,單位毫秒,預設值為-1,表示永不超時。如果超過等待時間,則直接丟擲JedisConnectionException;
          private static int MAX_WAIT = -1;
      
          private static int TIMEOUT = 10000;
      
          //連線耗盡時是否阻塞, false報異常,ture阻塞直到超時, 預設true
          private static boolean BLOCK_WHEN_EXHAUSTED = false;
      
          //設定的逐出策略類名, 預設DefaultEvictionPolicy(當連線超過最大空閒時間,或連線數超過最大空閒連線數)
          private static String EVICTION_POLICY_CLASSNAME="org.apache.commons.pool2.impl.DefaultEvictionPolicy";
      
          //是否啟用pool的jmx管理功能, 預設true
          private static boolean JMX_ENABLED=true;
      
          //MBean ObjectName = new ObjectName("org.apache.commons.pool2:type=GenericObjectPool,name=" + "pool" + i); 預設為"pool", JMX不熟,具體不知道是幹啥的...預設就好.
          private static String JMX_NAME_PREFIX="pool";
      
          //是否啟用後進先出, 預設true
          private static boolean LIFO=true;
      
          //逐出連線的最小空閒時間 預設1800000毫秒(30分鐘)
          private static long MIN_EVICTABLE_IDLE_TIME_MILLIS=1800000L;
      
          //物件空閒多久後逐出, 當空閒時間>該值 且 空閒連線>最大空閒數 時直接逐出,不再根據MinEvictableIdleTimeMillis判斷  (預設逐出策略)
          private static long SOFT_MIN_EVICTABLE_IDLE_TIME_MILLIS=1800000L;
      
          //每次逐出檢查時 逐出的最大數目 如果為負數就是 : 1/abs(n), 預設3
          private static int NUM_TESTS_PER_EVICYION_RUN=3;
      
          //在borrow一個jedis例項時,是否提前進行validate操作;如果為true,則得到的jedis例項均是可用的;
          //在獲取連線的時候檢查有效性, 預設false
          private static boolean TEST_ON_BORROW = false;
      
          //在空閒時檢查有效性, 預設false
          private static boolean TEST_WHILEIDLE=false;
      
          //逐出掃描的時間間隔(毫秒) 如果為負數,則不執行逐出執行緒, 預設-1
          private static long TIME_BERWEEN_EVICTION_RUNS_MILLIS=-1;
      
          private static JedisPool jedisPool = null;
      
          /**
           * 初始化Redis連線池
           */
          static {
              try {
                  JedisPoolConfig config = new JedisPoolConfig();
                  config.setBlockWhenExhausted(BLOCK_WHEN_EXHAUSTED);
                  config.setEvictionPolicyClassName(EVICTION_POLICY_CLASSNAME);
                  config.setJmxEnabled(JMX_ENABLED);
                  config.setJmxNamePrefix(JMX_NAME_PREFIX);
                  config.setLifo(LIFO);
                  config.setMaxIdle(MAX_IDLE);
                  config.setMaxTotal(MAX_TOTAL);
                  config.setMaxWaitMillis(MAX_WAIT);
                  config.setMinEvictableIdleTimeMillis(MIN_EVICTABLE_IDLE_TIME_MILLIS);
                  config.setMinIdle(MIN_IDLE);
                  config.setNumTestsPerEvictionRun(NUM_TESTS_PER_EVICYION_RUN);
                  config.setSoftMinEvictableIdleTimeMillis(SOFT_MIN_EVICTABLE_IDLE_TIME_MILLIS);
                  config.setTestOnBorrow(TEST_ON_BORROW);
                  config.setTestWhileIdle(TEST_WHILEIDLE);
                  config.setTimeBetweenEvictionRunsMillis(TIME_BERWEEN_EVICTION_RUNS_MILLIS);
      
                  jedisPool = new JedisPool(config, ADDR, PORT, TIMEOUT, AUTH);
              } catch (Exception e) {
                  e.printStackTrace();
              }
          }
      
          /**
           * 獲取Jedis例項
           * @return
           */
          public synchronized static Jedis getJedis() {
              try {
                  if (jedisPool != null) {
                      Jedis resource = jedisPool.getResource();
                      return resource;
                  } else {
                      return null;
                  }
              } catch (Exception e) {
                  e.printStackTrace();
                  return null;
              }
          }
      
          /**
           * 釋放jedis資源
           * @param jedis
           */
          public static void close(final Jedis jedis) {
              if (jedis != null) {
                  jedis.close();
              }
          }
      }
      
      jedispool 和 計數器
      jedispool 和 計數器
  • 時間軸
  • 佇列實現
    • rpush生產訊息,lpop消費訊息。當lpop沒有訊息的時候,要適當sleep一會再重試
  • 分散式鎖實現
    • Redlock演算法
  • session統一快取
  • 網站訪問限流
  • 手寫redis
  • 單點登入
  • 釋出訂閱

四、Redis執行緒模型

  Redis執行緒模型

  Redis基於Reactor 模式開發了自己的網路事件處理器: 這個處理器被稱為檔案事件處理器(file event handler)。檔案事件處理器包括4個組成部分:套接字、I/O多路複用程式、檔案事件分派器以及事件處理器。

  • 為了對連線伺服器的各個客戶端進行應答, 伺服器要為監聽套接字關聯連線應答處理器。
  • 為了接收客戶端傳來的命令請求, 伺服器要為客戶端套接字關聯命令請求處理器。
  • 為了向客戶端返回命令的執行結果, 伺服器要為客戶端套接字關聯命令回覆處理器。
  • 當主伺服器和從伺服器進行復制操作時, 主從伺服器都需要關聯特別為複製功能編寫的複製處理器。
  • 等等。

  時間事件應用例項:持續執行的redis伺服器需要定期對自身的資源和狀態進行檢查和調整,從而確保伺服器可以長期穩定的進行,這些定期操作主要工作包括:

  • 更新伺服器的各種統計資訊,比如時間、記憶體佔用、資料庫佔用等。
  • 清理資料庫中的過期鍵值對
  • 關閉和清理連線失效的客戶端
  • 嘗試進行AOF和RDB持久化操作
  • 如果伺服器是主伺服器,那麼對從伺服器進行定期同步
  • 如果處於叢集模式,對叢集進行定期同步和連線測試

五、快取一致性問題

六、Redis持久化

  持久化方案:Rdb和Aof。分為手動觸發和自動觸發兩種。預設開啟RDB,要實現AOF,需要在配置檔案中配置 appendonly yes。

  RDB方案,檔案緊湊,體積小,網路傳輸快,適合全量複製,恢復速度比AOF快很多,對效能的影響相對較小。做不到實時持久化,容易造成資料大量丟失。切RDB檔案格式相容性差。

  AOF方案,主流,檔案大,恢復速度慢,對效能影響大,但支援秒級持久化,相容性好。

  選擇策略:資料丟失沒關係,則可不進行持久化,實時性要去不高,選擇RDB,如果是秒級,則選AOF,但大多數會配置主從環境。  

  master最好不做持久化工作,如RDB記憶體快照和AOF檔案。

  redis伺服器程序就是一個事件迴圈,這個迴圈中的檔案時間負責接收客戶端的命令請求以及向客戶端傳送命令回覆,而時間事件則負責執行像serverCron函式這樣需要定時執行的函式。

  AOF是通過儲存被執行的寫命令來記錄資料庫狀態,所以AOF檔案中的內容會越來越多,檔案的體積也越來越大,對redis伺服器、宿主機、AOF還原都有影響,因此,redis提供AOF檔案重寫功能,通過重寫,redis伺服器可以建立一個新的AOF檔案來替代現有的AOF檔案,新AOF不會有冗餘命令。AOF重寫並不需要對現有AOF檔案進行任何讀取、分析、寫入操作,這個功能是通過伺服器當前的資料庫狀態來實現的。因為redis是單執行緒處理命令,如果由伺服器直接呼叫重寫命令,那麼重寫執行期間將無法處理客戶端發來的請求,故放到子程序裡執行,這樣做既可以達到父程序繼續處理客戶端命令的請求,而且紫禁城帶有伺服器程序的資料副本,使用子程序而不是執行緒,可以避免使用鎖的情況下,保證資料的安全性。但如何保證一致性呢,redis伺服器執行完一個寫命令之後,會將這個寫命令傳送給AOF緩衝區和AOF重寫緩衝區,AOF緩衝區會被定期寫入到AOF檔案,現有AOF檔案的處理工作正常進行。當子程序完成重寫工作後,會向父程序傳送一個訊號,父程序在接收到該訊號之後,會呼叫一個訊號處理函式(將AOF重寫緩衝區的內容寫入到新AOF檔案中,對新的AOF改名,覆蓋現有AOF檔案,該函式會造成阻塞)

七、Redis與Spring結合,序列化

  RedisTemplate(序列化java物件)和StringRedisTemplate主要區別是使用的序列化類,RedisTemplate試用JdkSerializationRedisSerializer序列化物件,StringRedisTemplate使用的是StringRedisSerializer序列化String。

  JacksonJsonRedisSerializer(序列化object物件為json字串)

  jdkSerializationRedisSerializer 序列化後長度最小,jackson2JsonRedisSerializer效率最高。推薦key使用stringRedisSerializer,value用Jackson2JsonRedisSerializer,如果空間敏感,用JdkSerializationRedisSerializer

genericJackson2JsonRedisSerializer序列化時間:64   序列化後的大小2786
genericJackson2JsonRedisSerializer反序列化時間:48
jackson2JsonRedisSerializer序列化時間:2   序列化後的大小1711
jackson2JsonRedisSerializer反序列化時間:2
jdkSerializationRedisSerializer序列化時間:22   序列化後的大小415
jdkSerializationRedisSerializer反序列化時間:3
stringRedisSerializer序列化時間:0   序列化後的大小415
stringRedisSerializer反序列化時間:0
@Bean(name = "redisTemplate")
    public RedisTemplate<String, Object> getRedisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
        redisTemplate.setConnectionFactory(factory);
        redisTemplate.setKeySerializer(new StringRedisSerializer()); // key的序列化型別

        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();

        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);

        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer); // value的序列化型別
        return redisTemplate;
    }

RedisTemplate
RedisTemplate

八、Redis與事務

  不支援事務回滾,不支援事務原子性,事務中任意命令執行失敗,其餘命令扔回執行

  事務:原子性,一致性,隔離性,永續性 。 redis事務只支援一致性和隔離性

  redis事務的隔離性,redis是單程序程式,不會對事務進行終端,事務可以直接執行完事務佇列的全部命令。因此,redis事務總帶有隔離性

  redis事務就是一次性、順序性、排他性的執行一個佇列中的一系列命令。

  相關命令:

  • watch命令 CAS監聽多個鍵,一旦鍵被改,之後的事務不會執行,監控一直持續到exec命令
  • multi命令 開啟事務,執行之後,客戶端傳送的命令被放到佇列中,直到exec命令
  • exec命令 執行事務塊的命令,按順序返回命令的返回值,當操作被打斷,返回nil
  • discard命令 清空事務佇列,放棄事務
  • unwatch 命令 取消對key的監控

  事務傳播機制(預設required):

  1. required, A調B,無事務再建立,有事務,則使用相同事務,B若回滾,則整個事務回滾
  2. required_new, 和1比,無論事務是否存在,永遠開啟新事務,B回滾,不會導致A回滾
  3. nested, 和required_new 類似
  4. supports, 方法呼叫時,有事務則用事務,無事務就不用事務
  5. not_supported , 強制方法不在事務中執行,若有事務,在方法呼叫到結束階段先掛起事務
  6. never 強制不能有事務,有事務,則丟擲異常
  7. mandatory 強制必須有事務,如果沒有事務就丟擲異常

九、Redis與Lua指令碼

十、Pipeline管道

  一次操作可能需要執行多個命令,一個一個去執行命令會浪費很多網路消耗時間,並不是原子性執行。

十一、Redis與釋出訂閱

十二、Reids複製

  主從複製。複製是高可用Redis的基礎,哨兵和叢集都是在複製基礎上實現高可用,複製主要實現多機備份,以及對讀操作的負載均衡和簡單的故障恢復。缺陷:故障恢復無法自動化,寫操作無法負載均衡,儲存能力受到單機的限制。

  操作:配置檔案新增:masterauth root123456。 向從伺服器傳送命令:slaveof 127.0.0.1 6379

  redis複製實現方式

十三、Redis叢集(重點)

  redis通訊協議,RESP是redis客戶端和服務端之間的通訊協議

redis叢集與一致性hash

十四、哨兵機制(重點)

  在複製的基礎上,哨兵實現了自動化的故障恢復。

   哨兵也是分散式的,哨兵叢集,互相協調工作,故障轉移時,判斷master是否宕機,需要分散式選舉,大部分哨兵同意才行。哨兵+主從架構,只是保證redis的高可用。

  哨兵的功能:

  • 叢集監控:負責監控redis master和slave程序是否正常工作
  • 訊息通知:如果redis例項有故障,哨兵負責傳送訊息作為報警通知給管理員
  • 故障轉移:master掛掉了,會自動移到slave
  • 配置中心:如果故障轉移發生了,通知client客戶端新的master地址

十五、Spring與哨兵結合

十六、快取擊穿、快取雪崩

  setnx,可以不存在時,設定成功,返回1,否則返回0。etnx來爭搶鎖,搶到之後,再用expire給鎖加一個過期時間防止鎖忘記了釋放

  快取擊穿是指快取中沒有但資料庫中有的資料(一般是快取時間到期),這時由於併發使用者特別多,同時讀快取沒讀到資料,又同時去資料庫去取資料,引起資料庫壓力瞬間增大,造成過大壓力

   快取擊穿方案,快取失效時,不loaddb,setnx返回1則loaddb並設定快取,否則重試get快取

  快取雪崩是指快取中資料大批量到過期時間,而查詢資料量巨大,引起資料庫壓力過大甚至down機。和快取擊穿不同的是, 快取擊穿指併發查同一條資料,快取雪崩是不同資料都過期了,很多資料都查不到從而查資料庫。

十七、布隆過濾器

  類似於hashset,快速判斷元素是否在集合中

十八、海量資料

  利用SCAN系列命令(SCAN、SSCAN、HSCAN、ZSCAN)完成資料迭代

十九、資料恢復與轉移

二十、Redis6種淘汰策略

  • 不刪除策略
  • LRU(less recently used)最少使用的key
  • LRU volatile 只針對過期key的部分
  • random
  • random volatile
  • volatile-ttl : 只限於設定了expire的部分

刪除策略

  • 定時刪除
    • 保證過期鍵儘快被刪除,記憶體友好。佔用cpu,影響服務的響應時間和吞吐量
  • 惰性刪除
    • 獲取鍵時進行過期鍵檢查並是否刪除,不浪費cpu,但是記憶體也不會釋放,造成記憶體洩漏
  • 定期刪除
    • 限制刪除的執行時長和頻率,減少CPU的影響

二十一、Redis記憶體劃分

二十二、Redis應用實戰

二十三、慢查詢分析,效能測試

二十四、Redis 與Spring boot Cache

  Redis之Spring boot整合redis和Spring cache

二十五、redis 連線池

二十六、bitmap

二十七、Lettuce

Redis高階客戶端Lettuce詳解

二十八、Redis常見配置

  • appendonly no:是否開啟AOF
  • appendfilename "appendonly.aof":AOF檔名
  • dir ./:RDB檔案和AOF檔案所在目錄
  • appendfsync everysec:fsync持久化策略
  • no-appendfsync-on-rewrite no:AOF重寫期間是否禁止fsync;如果開啟該選項,可以減輕檔案重寫時CPU和硬碟的負載(尤其是硬碟),但是可能會丟失AOF重寫期間的資料;需要在負載和安全性之間進行平衡
  • auto-aof-rewrite-percentage 100:檔案重寫觸發條件之一
  • auto-aof-rewrite-min-size 64mb:檔案重寫觸發提交之一
  • aof-load-truncated yes:如果AOF檔案結尾損壞,Redis啟動時是否仍載入AOF檔案

Redis常用管理命令

# dbsize 返回當前資料庫 key 的數量。
# info 返回當前 redis 伺服器狀態和一些統計資訊。
# monitor 實時監聽並返回redis伺服器接收到的所有請求資訊。
# shutdown 把資料同步儲存到磁碟上,並關閉redis服務。
# config get parameter 獲取一個 redis 配置引數資訊。(個別引數可能無法獲取)
# config set parameter value 設定一個 redis 配置引數資訊。(個別引數可能無法獲取)
# config resetstat 重置 info 命令的統計資訊。(重置包括:keyspace 命中數、
# keyspace 錯誤數、 處理命令數,接收連線數、過期 key 數)
# debug object key 獲取一個 key 的除錯資訊。
# debug segfault 製造一次伺服器當機。
# flushdb 刪除當前資料庫中所有 key,此方法不會失敗。小心慎用
# flushall 刪除全部資料庫中所有 key,此方法不會失敗。小心慎用

Redis工具命令

#redis-server:Redis 伺服器的 daemon 啟動程式
#redis-cli:Redis 命令列操作工具。當然,你也可以用 telnet 根據其純文字協議來操作
#redis-benchmark:Redis 效能測試工具,測試 Redis 在你的系統及你的配置下的讀寫效能
$redis-benchmark -n 100000 –c 50
#模擬同時由 50 個客戶端傳送 100000 個 SETs/GETs 查詢
#redis-check-aof:更新日誌檢查
#redis-check-dump:本地資料庫檢查