JavaGuide知識點整理——Redis面試題總結(上)

Redis基礎

簡單來說redis就是一個使用C語言開發的數據庫。不過與傳統數據庫不同的是Redis的數據是存在內存中的,也就是說它是內存數據庫,所以讀寫速度非常快,因爲Redis被廣泛用於緩存方向。
另外Redis除了可以做緩存外,還經常被用來做分佈式鎖,甚至消息隊列。
Redis提供了多種數據類型來支持不同的業務場景。Redis還支持事務,持久化,lua腳本,多種集羣方案。

分佈式緩存常見的技術選型方案有哪些?

分佈式緩存的話,使用較多的是memcached和Redis。現在最主流的是Redis。
Memcached是分佈式緩存剛興起那會比較常用的。後來隨着Redis發展,Redis使用率更高了。
分佈式緩存主要解決的是單機緩存的容量受服務器限制並且無法保持通訊信息的問題。因爲本地緩存只有在當前服務裏有效。比如部署兩個相同的服務器,他們之間的緩存是無法共同的。

Redis和Memcached的區別和共同點

共同點:

  1. 都是基於內存的數據庫,一般都被當做緩存使用
  2. 都有過期策略
  3. 兩者的性能都非常高

區別:

  1. Redis支持更豐富的數據類型。Memcached只支持K/V對。而Redis支持K/V對,list,set,zset,hash等數據結構的存儲。
  2. Redis支持數據的持久化,可以將內存中的數據保存在磁盤中,重啓的時候可以再次加載進行使用.Memcached把數據都存在內存中了。
  3. Redis有災難恢復機制。這也是因爲Redis可以持久化。
  4. Redis在服務器內存使用完後,可以將不用的數據放到磁盤上。但是Memcached在服務器內存使用完後會直接報異常
  5. Memcached沒有原生的集羣模式,需要依靠客戶端來實現集羣中分片寫入數據。Redis目前是原生支持cluster模式的。
  6. Memcached是多線程,非阻塞IO複用的網絡模型,而Redis使用的是單線程的多了IO複用模型(Redis6.0引入了多線程IO)
  7. Redis支持發佈訂閱模型,lua腳本,事務等功能,而Memcached不支持。
  8. Memcached過期數據的刪除策略只用了惰性刪除。而Redis同時使用了惰性刪除與定期刪除。

緩存數據的處理流程是怎樣的?

簡單來說如下:

  1. 用戶請求的數據在緩存中有,就直接返回
  2. 緩存中不存在就去查看數據庫是否存在
  3. 數據庫中存在就更新緩存中的數據
  4. 數據庫中不存在直接返回空

爲什麼要用Redis(爲什麼要用緩存)?

主要從高性能和高併發兩點考慮的。
高性能:
數據庫查詢是磁盤IO,效率沒有從內存中查詢快。如果用戶訪問的數據屬於高頻且不會經常改變,放在緩存中可以提升訪問速度。
高併發:
MySQL的OPS在1w左右,而Redis緩存之後很容易達到10W+。由此可見直接操作緩存能夠承受的請求數量遠遠大於直接訪問數據庫的。

Redis除了做緩存還能做什麼?

  • 分佈式鎖:通過Redis來做分佈式鎖是比較常見的方式。通常情況下我們基於Redisson來實現分佈式鎖(這個鎖採用看門狗概念防止宕機死鎖)。
  • 限流:一般通過Redis+Lua腳本的方式來實現限流。相關閱讀:《我司用了 6 年的 Redis 分佈式限流器,可以說是非常厲害了!》
  • 消息隊列:Redis自帶的list數據結構可以作爲一個簡單的隊列使用。Redis中增加的Stream類型的數據結構更加適合做消息隊列。它比較類似於kafka,有主題和消費組的概念,支持消息喫計劃以及ACK機制。
  • 複雜業務場景:比如sorted set實現排行榜,實現地理位置距離轉換等。

Redis如何實現消息對列?

Redis5.0新增了一個數據結構Stream可以用來做消息對列,其支持:

  • 發佈/訂閱模式
  • 按照消費組進行消費
  • 消息持久化(RDB和AOF)

不過和專業的消息隊列比較,還是有欠缺的地方。比如消息丟失和堆積問題不好解決。還是建議選擇市面上成熟的消息隊列,比如RocketMQ,Kafka等。

Redis數據結構

Redis常用的數據結構有哪些?

  • 5種基礎數據結構:String,List,Set,ZSet,Hash
  • 3種特殊數據結構:HyperLogLogs(基數統計).BitMap(位存儲),Geospatial(地理位置)

簡單說一下每種類型都有哪些操作:

  • String:
    SET key value 設置指定 key 的值
    SETNX key value 只有在 key 不存在時設置 key 的值
    GET key 獲取指定 key 的值
    MSET key1 value1 key2 value2 … 設置一個或多個指定 key 的值
    MGET key1 key2 ... 獲取一個或多個指定 key 的值
    STRLEN key 返回 key 所儲存的字符串值的長度
    INCR key 將 key 中儲存的數字值增一
    DECR key 將 key 中儲存的數字值減一
    EXISTS key 判斷指定 key 是否存在
    DEL key(通用) 刪除指定的 key
    EXPIRE key seconds(通用) 給指定 key 設置過期時間

  • List:
    RPUSH key value1 value2 ... 在指定列表的尾部(右邊)添加一個或多個元素
    LPUSH key value1 value2 ... 在指定列表的頭部(左邊)添加一個或多個元素
    LSET key index value 將指定列表索引 index 位置的值設置爲 value
    LPOP key 移除並獲取指定列表的第一個元素(最左邊)
    RPOP key 移除並獲取指定列表的最後一個元素(最右邊)
    LLEN key 獲取列表元素數量
    LRANGE key start end 獲取列表 start 和 end 之間 的元素

  • Hash:
    HSET key field value 設置指定哈希表中指定字段的值
    HSETNX key field value 只有指定字段不存在時設置指定字段的值
    HMSET key field1 value1 field2 value2 ... 同時將一個或多個 field-value (域-值)對設置到指定哈希表中
    HGET key field 獲取指定哈希表中指定字段的值
    HMGET key field1 field2 ... 獲取指定哈希表中一個或者多個指定字段的值
    HGETALL key 獲取指定哈希表中所有的鍵值對
    HEXISTS key field 查看指定哈希表中指定的字段是否存在
    HDEL key field1 field2 ... 刪除一個或多個哈希表字段
    HLEN key 獲取指定哈希表中字段的數量

  • Set:
    SADD key member1 member2 ... 向指定集合添加一個或多個元素
    SMEMBERS key 獲取指定集合中的所有元素
    SCARD key 獲取指定集合的元素數量
    SISMEMBER key member 判斷指定元素是否在指定集合中
    SINTER key1 key2 ... 獲取給定所有集合的交集
    SINTERSTORE destination key1 key2 ... 將給定所有集合的交集存儲在 destination 中
    SUNION key1 key2 ... 獲取給定所有集合的並集
    SUNIONSTORE destination key1 key2 ... 將給定所有集合的並集存儲在 destination 中
    SDIFF key1 key2 ... 獲取給定所有集合的差集
    SDIFFSTORE destination key1 key2 ... 將給定所有集合的差集存儲在 destination 中
    SPOP key count 隨機移除並獲取指定集合中一個或多個元素
    SRANDMEMBER key count 隨機獲取指定集合中指定數量的元素

  • ZSet:
    ZADD key score1 member1 score2 member2 ... 向指定有序集合添加一個或多個元素
    ZCARD KEY 獲取指定有序集合的元素數量
    ZSCORE key member 獲取指定有序集合中指定元素的 score 值
    ZINTERSTORE destination numkeys key1 key2 ... 將給定所有有序集合的交集存儲在 destination 中,對相同元素對應的 score 值進行 SUM 聚合操作,numkeys 爲集合數量
    ZUNIONSTORE destination numkeys key1 key2 ... 求並集,其它和 ZINTERSTORE 類似
    ZDIFF destination numkeys key1 key2 ... 求差集,其它和 ZINTERSTORE 類似
    ZRANGE key start end 獲取指定有序集合 start 和 end 之間的元素(score 從低到高)
    ZREVRANGE key start end 獲取指定有序集合 start 和 end 之間的元素(score 從高到底)
    ZREVRANK key member 獲取指定有序集合中指定元素的排名(score 從大到小排序)

三種特殊類型用法:

  • Bitmap:
    SETBIT key offset value 設置指定 offset 位置的值
    GETBIT key offset 獲取指定 offset 位置的值
    BITCOUNT key start end 獲取 start 和 end 之前值爲 1 的元素個數
    BITOP operation destkey key1 key2 ... 對一個或多個 Bitmap 進行運算,可用運算符有 AND, OR, XOR 以及 NOT

  • HyperLogLog:
    PFADD key element1 element2 ... 添加一個或多個元素到 HyperLogLog 中
    PFCOUNT key1 key2 獲取一個或者多個 HyperLogLog 的唯一計數。
    PFMERGE destkey sourcekey1 sourcekey2 ... 將多個 HyperLogLog 合併到 destkey 中,destkey 會結合多個源,算出對應的唯一計數。

  • Geospatial:
    GEOADD key longitude1 latitude1 member1 ... 添加一個或多個元素對應的經緯度信息到 GEO 中
    GEOPOS key member1 member2 ... 返回給定元素的經緯度信息
    GEODIST key member1 member2 M/KM/FT/MI 返回兩個給定元素之間的距離
    GEORADIUS key longitude latitude radius distance 獲取指定位置附近 distance 範圍內的其他元素,支持 ASC(由近到遠)、DESC(由遠到近)、Count(數量) 等參數
    GEORADIUSBYMEMBER key member radius distance 類似於 GEORADIUS 命令,只是參照的中心點是 GEO 中的元素

String的應用場景有哪些?

  • 常規數據(比如session,token,序列化後的對象)的緩存。
  • 計數。比如用戶單位時間的請求數,頁面單位時間的訪問數。
  • 分佈式鎖

String還是hash存儲對象數據更好呢?

  • String存儲的是序列化後的對象數據,存放的是整個對象。HashS是對對象的每個字段單獨存儲,可以獲取部分字段的信息。也可以修改或者添加部分字段,節省網絡流量。如果對象中某些字段需要經常變動或者經常單純查詢某個屬性,Hash比較合適。
  • String存儲相對來說更節省內存,緩存相同數量的對象數據,String消耗的內存是Hash的一般,並且存儲具有多重嵌套對象的時候也很方便。如果系統對性能和資源消耗非常敏感的話,String就非常合適。

打個比方,購物車信息建議用Hash存儲。用戶id爲key,商品id爲field,商品數量爲value。因爲購物車中的商品會頻繁改動。絕大多數情況我們建議用String存儲對象數據。

使用Redis實現一個排行榜

Redis中zset經常被用在各種排行榜的場景,比如直播間禮物排行榜,朋友圈的微信步數排行榜,王者中的段位排行榜等。因爲這個數據自帶一些命令:zrange(從小到大排序),zrevrange(從大到小排序),zrevrank(按照自定元素排名)

使用Set實現抽獎需要用到什麼命令?

  • SPOP key count:隨機移除並獲取指定集合中一個或者多個元素。適合不允許重複中獎的場景。
  • SRANFMEMBER key count:隨機獲取指定集合中指定數量的元素,適合允許重複中獎的場景

使用Bitmap統計活躍用戶做法

使用日期作爲key,用戶id爲offset,,如果當日活躍過就設置爲1.統計的時候用BITOP獲取一段時間內的活躍度或者BICOUNT獲得某日總活躍度:


Redis線程模型

Redis單線程模型

Redis是基於Reactor模式來設計開發了自己的一套高效的事件處理模型(Netty的線程模型也是基於Reactor模式)。這套事件處理模型對應的是Redis中的文件事件處理器。由於文件事件處理器是單線程方式運行的,所以我們一般都說Redis是單線程模型。
既然是單線程,那麼怎麼監聽大量的客戶端連接呢?
Redis通過IO多路複用程序來監聽來自客戶端的大量連接(或者說是監聽多個socket)。它會將感興趣的時間以及類型註冊到內核中並監聽每個事件是否發生。
這樣的好處非常明顯:IO多路複用技術的使用讓Redis不需要額外創建多餘的線程來監聽客戶端的大量連接,降低了資源的消耗。
另外Redis服務器是一個事件驅動程序,服務器需要處理兩類事件:

  1. 文件事件
  2. 時間事件

時間事件不需要多花時間瞭解,我們接觸最多的還是文件事件(客戶端進行讀取寫入等到左,涉及一系列網絡通信)。

文件事件處理器主要包括四個部分:

  • 多個socket
  • IO多路複用程序
  • 文件事件分派器(將socket關聯到相應的事件處理器)
  • 事件處理器(連接應答處理器,命令請求處理器,命令回覆處理器)


Redis6.0之前爲什麼都是單線程模式?

雖說Redis是單線程模型,但是4.0之後就加入了對多線程的支持。不過4.0增加的多線程主要是針對一些大的鍵值對的刪除操作命令,使這些命令異步處理。大體來說6.0之前還是單線程的。爲什麼要這樣呢?主要原因有三個:

  1. 單線程編程容易並且更容易維護
  2. Redis的性能瓶頸不在CPU上,主要在內存和網絡
  3. 多線程會存在死鎖,線程上下文切換等問題,甚至會影響性能

Redis6.0爲何引入了多線程?

Redis6.0引入多線程主要是爲了提高網絡IO讀寫性能。因爲這個也算是性能瓶頸之一(內存和網絡)。
雖然6.0引入了多線程,但是隻是在網絡數據的讀寫這類耗時操作上使用了。執行命令仍然是單線程順序執行。
並且6.0的多線程默認是禁用的,如需要開啓要修改配置文件:

io-threads-do-reads yes

開啓多線程後還需要配置線程數,否則也是不生效的,同樣是修改配置文件:

io-threads 4 #官網建議4核的機器建議設置爲2或3個線程,8核的建議設置爲6個線程

Redis內存管理

Redis給緩存數據設置過期時間用處

因爲內存是有效的,如果緩存中的所有數據都一致保存的話,耗費性能且沒必要。Redis子弟了給緩存數據設置國企時間的功能,除了字符串類型有自己獨有設置過期時間的命令setex之外,其他方法都需要依靠expire命令來設置過期時間。另外presist命令可以移除一個鍵的過期時間。

過期時間除了緩解內存的消耗,還可以針對業務需求有實際的效果。比如驗證碼的有效期是五分鐘,如果是傳統數據庫我們還要自己判斷過期時間,更麻煩且性能還差。

Redis是如何判斷數據是否過期的?

Redis通過一個叫做過期字典來保存數據的過期時間。過期字典的鍵指向Redis數據庫中的key,值是log類型的證書,保存的key的過期時間。

過期的數據刪除策略

有兩個策略:

  • 惰性刪除:使用的時候做檢查,對CPU友好,但是可能造成大量的過期key沒有刪除,對內存不友好
  • 定期刪除:每隔一段時間抽取key執行刪除過期key操作。並且通過限制刪除操作執行的時長和頻率來減少對CPU時間的影響。

Redis採用的是定期刪除+惰性刪除

Redis內存淘汰機制

  1. volatile-lru:從設置過期時間的數據集中挑選最近最少使用的數據淘汰
  2. volatile-ttl:從設置過期時間的數據集中挑選將要過期的數據淘汰
  3. volatile-random:從設置過期時間的數據集中隨機淘汰
  4. allkeys-lru:移除最近最少使用的key
  5. allkeys-random:隨機淘汰
  6. no-eviction:不淘汰,內存滿了直接報錯

4.0版本後增加兩個策略:
7.volatile-lfu:從設置過期時間的數據集中挑選最不經常用的淘汰

  1. allkeys-lfu:挑選最不經常用的key淘汰

Redis持久化機制

持久化其實是保證掛掉重啓後可以進行數據恢復的保證。Redis相比於Memcached最大的優點也是支持持久化。Redis有兩種持久化方式:RDB快照,AOF文件追加。

RDB持久化

創建快照獲取存儲在內存裏的數據的某個時間點上的副本。Redis是默認開啓這種持久化方式的,我們可以設置快照的頻率。

save 900 1           #在900秒(15分鐘)之後,如果至少有1個key發生變化,Redis就會自動觸發BGSAVE命令創建快照。

save 300 10          #在300秒(5分鐘)之後,如果至少有10個key發生變化,Redis就會自動觸發BGSAVE命令創建快照。

save 60 10000        #在60秒(1分鐘)之後,如果至少有10000個key發生變化,Redis就會自動觸發BGSAVE命令創建快照。

AOF持久化

AOF其實就是redis的每一條命令都追加到文件中。恢復的時候從頭執行一次就行。但是Redis默認是不開啓AOF的。可以通過appendonly參數開啓:

appendonly yes

這個原理類似MySQL的日誌記錄。每一個命令存到緩存文件中,然後根據配置同步到硬盤的文件中。Redis有三種不同的AOP持久化方式:

appendfsync always    #每次有數據修改發生時都會寫入AOF文件,這樣會嚴重降低Redis的速度
appendfsync everysec  #每秒鐘同步一次,顯式地將多個寫命令同步到硬盤
appendfsync no        #讓操作系統決定何時進行同步

AOF重寫

其實這個功能不會對原有文件有任何的讀取,分析或者寫入的操作。而是根據讀取數據庫中的鍵值對來實現的。
比如對key1 這個鍵 設置值1,然後改成2,然後改成lsj,然後改成ll。正常的aof會有四條命令。但是其實有效的只有key1 = ll這一個。所以重寫後會清理很多無用的東西。
重寫本質就是讀取當前kv.然後讀取過程中創建線程記錄讀取時候的命令,在讀取完後追加讀取過程中的命令作爲一個新的AOF文件替換舊的,這就是AOF重寫。

Redis4.0對持久化機制優化

Redis4.0開始支持AOF和RDB混合持久化。
AOF重寫的時候直接把RDB的內容寫到AOF文件開頭,既快速又避免丟失過多數據。缺點就是AOF文件不再是AOF格式,可讀性差。

本篇筆記就記到這裏,如果稍微幫到你了記得點個喜歡點個關注。也祝大家工作順順利利,中秋快樂喲~!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章