1.什么是 Redis?簡述它的優缺點?
Redis的全稱是:Remote Dictionary.Server
,本質上是一個Key-Value
類型的內存數據庫,很像memcached
,整個數據庫統統加載在內存當中進行操作,定期通過異步操作把數據庫數據flush
到硬盤上進行保存。因為是純內存操作,Redis
的性能非常出色,每秒可以處理超過 10 萬次讀寫操作,是已知性能最快的Key-Value DB。
Redis 的出色之處不僅僅是性能,Redis
最大的魅力是支持保存多種數據結構,此外單個 value
的最大限制是 1GB
,不像 memcached 只能保存 1MB 的數據,因此 Redis 可以用來實現很多有用的功能。
比方說用他的 List
來做 FIFO
雙向鏈表,實現一個輕量級的高性 能消息隊列服務,用他的 Set
可以做高性能的 tag
系統等等。
另外 Redis
也可以對存入的 Key-Value
設置 expire
時間,因此也可以被當作一個功能加強版的memcached 來用。 Redis 的主要缺點是數據庫容量受到物理內存的限制,不能用作海量數據的高性能讀寫,因此 Redis 適合的場景主要局限在較小數據量的高性能操作和運算上。
2.Redis 支持的 Java 客戶端都有哪些?官方推薦用哪個?
Redisson、Jedis、lettuce 等等,官方推薦使用 Redisson
。
3.Redis 與 Memcached 相比有哪些優勢?
memcached
所有的值均是簡單的字符串,redis
作為其替代者,支持更為豐富的數據類型。redis
的速度比memcached
快很多。redis
可以持久化其數據。
4.Redis 支持哪幾種數據類型?並簡單介紹一下?
String(字符串)、Hash(哈希)、List(列表)、Set(集合)、Zset(sorted set:有序集合)
String(字符串)
String是redis最基本的類型,你可以理解成與Memcached一模一樣的類型,一個key對應一個value。
String類型是二進制安全的。意思是redis的string可以包含任何數據。比如jpg圖片或者序列化的對象 。
String類型是Redis最基本的數據類型,一個鍵最大能存儲512MB。
一個鍵能存512M,但是取出來的時候就要注意了。如果存的value過大,卻用String接收的話,就會拋異常了。我們就出現過線上問題~
Hash(哈希)
Redis hash是一個鍵值對集合。
Redis hash是一個string類型的field和value的映射表,hash特別適合用於存儲對象。每個 hash 可以存儲 2^32 - 1鍵值對(40多億)。
List(列表)
Redis 列表是簡單的字符串列表,按照插入順序排序。你可以添加一個元素導列表的頭部(左邊)或者尾部(右邊)。
列表最多可存儲 2^32 - 1元素 (4294967295, 每個列表可存儲40多億)。
Set(集合)
Redis的Set是string類型的無序集合。
集合是通過哈希表實現的,所以添加,刪除,查找的復雜度都是O(1)。
sadd 命令:添加一個string元素到,key對應的set集合中,成功返回1,如果元素以及在集合中返回0,key對應的set不存在返回錯誤。
Zset
Redis
zset
和set
一樣也是string
類型元素的集合,且不允許重復的成員。不同的是每個元素都會關聯一個double
類型的分數。redis
正是通過分數來為集合中的成員進行從小到大的排序。
zset
的成員是唯一的,但分數(score
)卻可以重復。
zadd
命令:添加元素到集合,元素在集合中存在則更新對應score
.
5.怎么理解 Redis 事務?相關事務命令有哪些?並介紹一下
事務是一個單獨的隔離操作:事務中的所有命令都會序列化、按順序地執行,事務在執行的過程中,不會被其他客戶端發送來的命令請求所打斷。事務是一個原子操作:事務中的命令要么全部被執行,要么全部都不執行。
命令:MULTI
、EXEC
、DISCARD
、WATCH
、UNWATCH
.
- DISCARD
取消事務,放棄執行事務塊內的所有命令。 - EXEC
執行所有事務塊內的命令。 - MULTI
標記一個事務塊的開始。 - UNWATCH
取消 WATCH 命令對所有 key 的監視。 - WATCH key [key ...]
監視一個(或多個) key ,如果在事務執行之前這個(或這些) key 被其他命令所改動,那么事務將被打斷。
在傳統的關系式數據庫中,常常用 ACID
性質來檢驗事務功能的可靠性和安全性。在 Redis
中,事務總是具有原子性(Atomicity)、一致性(Consistency)和隔離性(Isolation),並且當 Redis 運行在某種特定的持久化模式下時,事務也具有持久性(Durability)。
6.為什么要用 redis 而不用 map/guava 做緩存?
緩存分為本地緩存和分布式緩存。以 Java
為例,使用自帶的map
或者 guava
實現的是本地緩存,最主要的特點是輕量以及快速,生命周期隨着 jvm
的銷毀而結束,並且在多實例的情況下,每個實例都需要各自保存一份緩存,緩存不具有一致性。
使用 redis
或memcached
之類的稱為分布式緩存,在多實例的情況下,各實例共用一份緩存數據,緩存具有一致性。缺點是需要保持 redis
或 memcached
服務的高可用,整個程序架構上較為復雜。
7. redis 設置過期時間
Redis中有個設置時間過期的功能,即對存儲在 redis 數據庫中的值可以設置一個過期時間。作為一個緩存數據庫,這是非常實用的。如我們一般項目中的 token 或者一些登錄信息,尤其是短信驗證碼都是有時間限制的,按照傳統的數據庫處理方式,一般都是自己判斷過期,這樣無疑會嚴重影響項目性能。
我們 set key 的時候,都可以給一個 expire time,就是過期時間,通過過期時間我們可以指定這個 key 可以存活的時間。
如果假設你設置了一批 key 只能存活1個小時,那么接下來1小時后,redis是怎么對這批key進行刪除的?
定期刪除+惰性刪除。
通過名字大概就能猜出這兩個刪除方式的意思了。
定期刪除:redis默認是每隔 100ms 就隨機抽取一些設置了過期時間的key,檢查其是否過期,如果過期就刪除。注意這里是隨機抽取的。為什么要隨機呢?你想一想假如 redis 存了幾十萬個 key ,每隔100ms就遍歷所有的設置過期時間的 key 的話,就會給 CPU 帶來很大的負載!
惰性刪除 :定期刪除可能會導致很多過期 key 到了時間並沒有被刪除掉。所以就有了惰性刪除。假如你的過期 key,靠定期刪除沒有被刪除掉,還停留在內存里,除非你的系統去查一下那個 key,才會被redis給刪除掉。這就是所謂的惰性刪除,也是夠懶的哈!
但是僅僅通過設置過期時間還是有問題的。我們想一下:如果定期刪除漏掉了很多過期 key,然后你也沒及時去查,也就沒走惰性刪除,此時會怎么樣?如果大量過期key堆積在內存里,導致redis內存塊耗盡了。怎么解決這個問題呢?
8. 說一說Redis 內存淘汰機制(MySQL里有2000w數據,Redis中只存20w的數據,如何保證Redis中的數據都是熱點數據?)
redis
配置文件 redis.conf
中有相關注釋,大家可以自行查閱或者通過這個網址查看:
http://download.redis.io/redis-stable/redis.conf
redis
提供 6種數據淘汰策略:
volatile-lru:從已設置過期時間的數據集(server.db[i].expires)中挑選最近最少使用的數據淘汰
volatile-ttl:從已設置過期時間的數據集(server.db[i].expires)中挑選將要過期的數據淘汰
volatile-random:從已設置過期時間的數據集(server.db[i].expires)中任意選擇數據淘汰
allkeys-lru:當內存不足以容納新寫入數據時,在鍵空間中,移除最近最少使用的key(這個是最常用的).
allkeys-random:從數據集(server.db[i].dict)中任意選擇數據淘汰
no-eviction:禁止驅逐數據,也就是說當內存不足以容納新寫入數據時,新寫入操作會報錯。這個應該沒人使用吧!
9. 如何解決 Redis 的並發競爭 Key 問題?
所謂 Redis
的並發競爭 Key
的問題也就是多個系統同時對一個 key
進行操作,但是最后執行的順序和我們期望的順序不同,這樣也就導致了結果的不同!
推薦一種方案:分布式鎖(zookeeper
和 redis
都可以實現分布式鎖)。(如果不存在 Redis
的並發競爭 Key
問題,不要使用分布式鎖,這樣會影響性能)
基於zookeeper
臨時有序節點可以實現的分布式鎖。
大致思想為:每個客戶端對某個方法加鎖時,在zookeeper
上的與該方法對應的指定節點的目錄下,生成一個唯一的瞬時有序節點。 判斷是否獲取鎖的方式很簡單,只需要判斷有序節點中序號最小的一個。 當釋放鎖的時候,只需將這個瞬時節點刪除即可。同時,其可以避免服務宕機導致的鎖無法釋放,而產生的死鎖問題。完成業務流程后,刪除對應的子節點釋放鎖。
在實踐中,當然是從以可靠性為主。所以首推Zookeeper
。
10.Redis 為什么是單線程的?
官方FAQ表示,因為Redis是基於內存的操作,CPU不是Redis的瓶頸,Redis的瓶頸最有可能是機器內存的大小或者網絡帶寬。既然單線程容易實現,而且CPU不會成為瓶頸,那就順理成章地采用單線程的方案了(畢竟采用多線程會有很多麻煩!)Redis利用隊列技術將並發訪問變為串行訪問
1)絕大部分請求是純粹的內存操作(非常快速)
2)采用單線程,避免了不必要的上下文切換和競爭條件
3)非阻塞IO優點:
1.速度快,因為數據存在內存中,類似於HashMap,HashMap的優勢就是查找和操作的時間復雜度都是O(1)
2. 支持豐富數據類型,支持string,list,set,sorted set,hash
3.支持事務,操作都是原子性,所謂的原子性就是對數據的更改要么全部執行,要么全部不執行
4. 豐富的特性:可用於緩存,消息,按key設置過期時間,過期后將會自動刪除如何解決redis的並發競爭key問題
同時有多個子系統去set一個key。這個時候要注意什么呢? 不推薦使用redis的事務機制。因為我們的生產環境,基本都是redis集群環境,做了數據分片操作。你一個事務中有涉及到多個key操作的時候,這多個key不一定都存儲在同一個redis-server上。因此,redis的事務機制,十分雞肋。
(1)如果對這個key操作,不要求順序: 准備一個分布式鎖,大家去搶鎖,搶到鎖就做set操作即可
(2)如果對這個key操作,要求順序: 分布式鎖+時間戳。 假設這會系統B先搶到鎖,將key1設置為{valueB 3:05}。接下來系統A搶到鎖,發現自己的valueA的時間戳早於緩存中的時間戳,那就不做set操作了。以此類推。
(3) 利用隊列,將set方法變成串行訪問也可以redis遇到高並發,如果保證讀寫key的一致性
對redis的操作都是具有原子性的,是線程安全的操作,你不用考慮並發問題,redis內部已經幫你處理好並發的問題了。
11.說一說Redis 持久化機制?
Redis
是一個支持持久化的內存數據庫,通過持久化機制把內存中的數據同步到硬盤文件來保證數據持久化。當Redis
重啟后通過把硬盤文件重新加載到內存,就能達到恢復數據的目的。
實現:單獨創建fork()
一個子進程,將當前父進程的數據庫數據復制到子進程的內存中,然后由子進程寫入到臨時文件中,持久化的過程結束了,再用這個臨時文件替換上次的快照文件,然后子進程退出,內存釋放。
- RDB是
Redis
默認的持久化方式。按照一定的時間周期策略把內存的數據以快照的形式保存到硬盤的二進制文件。即Snapshot快照存儲,對應產生的數據文件為dump.rdb,通過配置文件中的save參數來定義快照的周期。( 快照可以是其所表示的數據的一個副本,也可以是數據的一個復制品。) - AOF:
Redis
會將每一個收到的寫命令都通過Write
函數追加到文件最后,類似於MySQL
的binlog
。當Redis
重啟是會通過重新執行文件中保存的寫命令來在內存中重建整個數據庫的內容。
當兩種方式同時開啟時,數據恢復Redis
會優先選擇AOF
恢復。
12.熱點數據和冷數據是什么?
熱點數據,緩存才有價值
對於熱點數據,比如我們的某IM產品,生日祝福模塊,當天的壽星列表,緩存以后可能讀取數十萬次。再舉個例子,某導航產品,我們將導航信息,緩存以后可能讀取數百萬次。
對於冷數據而言,大部分數據可能還沒有再次訪問到就已經被擠出內存,不僅占用內存,而且價值不大。頻繁修改的數據,看情況考慮使用緩存
對於上面兩個例子,壽星列表、導航信息都存在一個特點,就是信息修改頻率不高,讀取通常非常高的場景。
數據更新前至少讀取兩次,緩存才有意義。這個是最基本的策略,如果緩存還沒有起作用就失效了,那就沒有太大價值了。
那存不存在,修改頻率很高,但是又不得不考慮緩存的場景呢?有!比如,這個讀取接口對數據庫的壓力很大,但是又是熱點數據,這個時候就需要考慮通過緩存手段,減少數據庫的壓力,比如我們的某助手產品的,點贊數,收藏數,分享數等是非常典型的熱點數據,但是又不斷變化,此時就需要將數據同步保存到Redis緩存,減少數據庫壓力。
13. 簡單說一說緩存雪崩以及解決辦法?
緩存雪崩我們可以簡單的理解為:由於原有緩存失效,新緩存未到期間
(例如:我們設置緩存時采用了相同的過期時間,在同一時刻出現大面積的緩存過期),所有原本應該訪問緩存的請求都去查詢數據庫了,而對數據庫CPU和內存造成巨大壓力,嚴重的會造成數據庫宕機。從而形成一系列連鎖反應,造成整個系統崩潰。
解決辦法:
大多數系統設計者考慮用加鎖( 最多的解決方案)或者隊列的方式保證來保證不會有大量的線程對數據庫一次性進行讀寫,從而避免失效時大量的並發請求落到底層存儲系統上。還有一個簡單方案就時講緩存失效時間分散開。
14. 簡單說一說緩存穿透以及解決辦法?
緩存穿透是指用戶查詢數據,在數據庫沒有,自然在緩存中也不會有。這樣就導致用戶查詢的時候,在緩存中找不到,每次都要去數據庫再查詢一遍,然后返回空(相當於進行了兩次無用的查詢)。這樣請求就繞過緩存直接查數據庫,這也是經常提的緩存命中率問題。
解決辦法:
最常見的則是采用布隆過濾器,將所有可能存在的數據哈希到一個足夠大的bitmap中,一個一定不存在的數據會被這個bitmap攔截掉,從而避免了對底層存儲系統的查詢壓力。
另外也有一個更為簡單粗暴的方法,如果一個查詢返回的數據為空(不管是數據不存在,還是系統故障),我們仍然把這個空結果進行緩存,但它的過期時間會很短,最長不超過五分鍾。通過這個直接設置的默認值存放到緩存,這樣第二次到緩沖中獲取就有值了,而不會繼續訪問數據庫,這種辦法最簡單粗暴。
5TB的硬盤上放滿了數據,請寫一個算法將這些數據進行排重。如果這些數據是一些32bit大小的數據該如何解決?如果是64bit的呢?
對於空間的利用到達了一種極致,那就是Bitmap和布隆過濾器(Bloom Filter)。
Bitmap: 典型的就是哈希表
缺點是,Bitmap對於每個元素只能記錄1bit信息,如果還想完成額外的功能,恐怕只能靠犧牲更多的空間、時間來完成了。
布隆過濾器(推薦)
就是引入了k(k>1)k(k>1)個相互獨立的哈希函數,保證在給定的空間、誤判率下,完成元素判重的過程。
它的優點是空間效率和查詢時間都遠遠超過一般的算法,缺點是有一定的誤識別率和刪除困難。
Bloom-Filter算法的核心思想就是利用多個不同的Hash函數來解決“沖突”。
Hash存在一個沖突(碰撞)的問題,用同一個Hash得到的兩個URL的值有可能相同。為了減少沖突,我們可以多引入幾個Hash,如果通過其中的一個Hash值我們得出某元素不在集合中,那么該元素肯定不在集合中。只有在所有的Hash函數告訴我們該元素在集合中時,才能確定該元素存在於集合中。這便是Bloom-Filter的基本思想。
Bloom-Filter一般用於在大數據量的集合中判定某元素是否存在。
15. 簡單說一說緩存預熱?
除了緩存服務器自帶的緩存失效策略之外(Redis默認的有6中策略可供選擇),我們還可以根據具體的業務需求進行自定義的緩存淘汰,常見的策略有兩種:
(1)定時去清理過期的緩存;
(2)當有用戶請求過來時,再判斷這個請求所用到的緩存是否過期,過期的話就去底層系統得到新數據並更新緩存。
兩者各有優劣,第一種的缺點是維護大量緩存的key是比較麻煩的,第二種的缺點就是每次用戶請求過來都要判斷緩存失效,邏輯相對比較復雜!具體用哪種方案,大家可以根據自己的應用場景來權衡。
16. 簡單說一說緩存降級?
當訪問量劇增、服務出現問題(如響應時間慢或不響應)或非核心服務影響到核心流程的性能時,仍然需要保證服務還是可用的,即使是有損服務。系統可以根據一些關鍵數據進行自動降級,也可以配置開關實現人工降級。
降級的最終目的是保證核心服務可用,即使是有損的。而且有些服務是無法降級的(如加入購物車、結算)。
以參考日志級別設置預案:
(1)一般:比如有些服務偶爾因為網絡抖動或者服務正在上線而超時,可以自動降級;
(2)警告:有些服務在一段時間內成功率有波動(如在95~100%之間),可以自動降級或人工降級,並發送告警;
(3)錯誤:比如可用率低於90%,或者數據庫連接池被打爆了,或者訪問量突然猛增到系統能承受的最大閥值,此時可以根據情況自動降級或者人工降級;
(4)嚴重錯誤:比如因為特殊原因數據錯誤了,此時需要緊急人工降級。
服務降級的目的,是為了防止Redis服務故障,導致數據庫跟着一起發生雪崩問題。因此,對於不重要的緩存數據,可以采取服務降級策略,例如一個比較常見的做法就是,Redis出現問題,不去數據庫查詢,而是直接返回默認值給用戶。
17.Redis 常見性能問題和解決方案?
(1) Master 最好不要做任何持久化工作,如 RDB 內存快照和 AOF 日志文件
(2) 如果數據比較重要,某個 Slave 開啟 AOF 備份數據,策略設置為每秒同步一次
(3) 為了主從復制的速度和連接的穩定性, Master 和 Slave 最好在同一個局域網內
(4) 盡量避免在壓力很大的主庫上增加從庫
(5) 主從復制不要用圖狀結構,用單向鏈表結構更為穩定,即: Master <- Slave1 <- Slave2 <- Slave3…
18.Redis如何實現分布式鎖?
1.根據lockKey區進行setnx(set not exist,如果key值為空,則正常設置,返回1,否則不會進行設置並返回0)操作,如果設置成功,表示已經獲得鎖,否則並沒有獲取鎖。
2.如果沒有獲得鎖,去Redis上拿到該key對應的值,在該key上我們存儲一個時間戳(用毫秒表示,t1),為了避免死鎖以及其他客戶端占用該鎖超過一定時間(5秒),使用該客戶端當前時間戳,與存儲的時間戳作比較。
3.如果沒有超過該key的使用時限,返回false,表示其他人正在占用該key,不能強制使用;如果已經超過時限,那我們就可以進行解鎖,使用我們的時間戳來代替該字段的值。
4.但是如果在setnx失敗后,get該值卻無法拿到該字段時,說明操作之前該鎖已經被釋放,這個時候,最好的辦法就是重新執行一遍setnx方法來獲取其值以獲得該鎖。
詳細內容可以查看:Redis與Zookeeper實現分布式鎖的區別(https://www.cnblogs.com/mengchunchen/p/9647756.html)
19.如何保證緩存與數據庫雙寫時的數據一致性?
你只要用緩存,就可能會涉及到緩存與數據庫雙存儲雙寫,你只要是雙寫,就一定會有數據一致性的問題,那么你如何解決一致性問題?
一般來說,就是如果你的系統不是嚴格要求緩存+數據庫必須一致性的話,緩存可以稍微的跟數據庫偶爾有不一致的情況,最好不要做這個方案,讀請求和寫請求串行化,串到一個內存隊列里去,這樣就可以保證一定不會出現不一致的情況
串行化之后,就會導致系統的吞吐量會大幅度的降低,用比正常情況下多幾倍的機器去支撐線上的一個請求。
還有一種方式就是可能會暫時產生不一致的情況,但是發生的幾率特別小,就是先更新數據庫,然后再刪除緩存。
這種情況不存在並發問題么?
不是的。假設這會有兩個請求,一個請求A做查詢操作,一個請求B做更新操作,那么會有如下情形產生
(1)緩存剛好失效
(2)請求A查詢數據庫,得一個舊值
(3)請求B將新值寫入數據庫
(4)請求B刪除緩存
(5)請求A將查到的舊值寫入緩存
ok,如果發生上述情況,確實是會發生臟數據。
然而,發生這種情況的概率又有多少呢?
發生上述情況有一個先天性條件,就是步驟(3)的寫數據庫操作比步驟(2)的讀數據庫操作耗時更短,才有可能使得步驟(4)先於步驟(5)。可是,大家想想,數據庫的讀操作的速度遠快於寫操作的(不然做讀寫分離干嘛,做讀寫分離的意義就是因為讀操作比較快,耗資源少),因此步驟(3)耗時比步驟(2)更短,這一情形很難出現。
如何解決上述並發問題?
首先,給緩存設有效時間是一種方案。其次,采用異步延時刪除策略,保證讀請求完成以后,再進行刪除操作。
20.是否使用過Redis集群,集群的原理是什么?
Redis Sentinal
着眼於高可用,在master
宕機時會自動將slave
提升為master
,繼續提供服務。
Redis Cluster
着眼於擴展性,在單個redis
內存不足時,使用Cluster
進行分片存儲。
Slot:插槽,可以存儲兩個數值的一個變量這個變量的取值范圍是:0-16383。
Cluster:集群管理者,使集群對外暴漏的是一個整體。
redis cluster:采用虛擬分區的方式,將整個集群看成一個整體,然后分成16384個槽位。
然后再將16484個槽位分別分配給集群的各個節點,然后各個節點各自負責一部分槽位。
原理:
節點1負責 0-5000
之間的槽位,節點2負責5001-10000
之間的槽位,節點3負責10001-16383
之間的槽位。
k-v鍵值對數據只會和槽位相關,與物理機器無關。通過crc16算法計算出 k對應的整數值(有點類似hash),然后對算出的整數值%16384取模,計算出k-v對應在哪個槽位上,然后再根據槽位與機器節點的映射關系,存儲到相應的節點上去。取的時候,也是相應的過 程所以整個集群協同一致對外,給client看到的視圖就是完整的數據集。
21. redis事物的了解CAS(check-and-set 操作實現樂觀鎖 )?
和眾多其它數據庫一樣,Redis作為NoSQL數據庫也同樣提供了事務機制。在Redis中,MULTI/EXEC/DISCARD/WATCH這四個命令是我們實現事務的基石。相信對有關系型數據庫開發經驗的開發者而言這一概念並不陌生,即便如此,我們還是會簡要的列出Redis中事務的實現特征:
1). 在事務中的所有命令都將會被串行化的順序執行,事務執行期間,Redis不會再為其它客戶端的請求提供任何服務,從而保證了事物中的所有命令被原子的執行。
2). 和關系型數據庫中的事務相比,在Redis事務中如果有某一條命令執行失敗,其后的命令仍然會被繼續執行。
3). 我們可以通過MULTI命令開啟一個事務,有關系型數據庫開發經驗的人可以將其理解為"BEGIN TRANSACTION"語句。在該語句之后執行的命令都將被視為事務之內的操作,最后我們可以通過執行EXEC/DISCARD命令來提交/回滾該事務內的所有操作。這兩個Redis命令可被視為等同於關系型數據庫中的COMMIT/ROLLBACK語句。
4). 在事務開啟之前,如果客戶端與服務器之間出現通訊故障並導致網絡斷開,其后所有待執行的語句都將不會被服務器執行。然而如果網絡中斷事件是發生在客戶端執行EXEC命令之后,那么該事務中的所有命令都會被服務器執行。
5). 當使用Append-Only模式時,Redis會通過調用系統函數write將該事務內的所有寫操作在本次調用中全部寫入磁盤。然而如果在寫入的過程中出現系統崩潰,如電源故障導致的宕機,那么此時也許只有部分數據被寫入到磁盤,而另外一部分數據卻已經丟失。
Redis服務器會在重新啟動時執行一系列必要的一致性檢測,一旦發現類似問題,就會立即退出並給出相應的錯誤提示。此時,我們就要充分利用Redis工具包中提供的redis-check-aof工具,該工具可以幫助我們定位到數據不一致的錯誤,並將已經寫入的部分數據進行回滾。修復之后我們就可以再次重新啟動Redis服務器了。
22.WATCH命令和基於CAS的樂觀鎖?
在Redis的事務中,WATCH命令可用於提供CAS(check-and-set)功能。假設我們通過WATCH命令在事務執行之前監控了多個Keys,倘若在WATCH之后有任何Key的值發生了變化,EXEC命令執行的事務都將被放棄,同時返回Null multi-bulk應答以通知調用者事務
執行失敗。例如,我們再次假設Redis中並未提供incr命令來完成鍵值的原子性遞增,如果要實現該功能,我們只能自行編寫相應的代碼。其偽碼如下:
val = GET mykey
val = val + 1
SET mykey $val
以上代碼只有在單連接的情況下才可以保證執行結果是正確的,因為如果在同一時刻有多個客戶端在同時執行該段代碼,那么就會出現多線程程序中經常出現的一種錯誤場景--競態爭用(race condition)。比如,客戶端A和B都在同一時刻讀取了mykey的原有值,假設該值為10,此后兩個客戶端又均將該值加一后set回Redis服務器,這樣就會導致mykey的結果為11,而不是我們認為的12。為了解決類似的問題,我們需要借助WATCH命令的幫助,見如下代碼:
WATCH mykey
val = GET mykey
val = val + 1
MULTI
SET mykey $val
EXEC
和此前代碼不同的是,新代碼在獲取mykey的值之前先通過WATCH命令監控了該鍵,此后又將set命令包圍在事務中,這樣就可以有效的保證每個連接在執行EXEC之前,如果當前連接獲取的mykey的值被其它連接的客戶端修改,那么當前連接的EXEC命令將執行失敗。這樣調用者在判斷返回值后就可以獲悉val是否被重新設置成功。
23.redis 最適合的場景有哪些?
Redis最適合所有數據in-momory
的場景,雖然Redis也提供持久化功能,但實際更多的是一個disk-backed
的功能,跟傳統意義上的持久化有比較大的差別,那么可能大家就會有疑問,似乎Redis更像一個加強版的Memcached
,那么何時使用Memcached
,何時使用Redis呢?
如果簡單地比較Redis與Memcached的區別,大多數都會得到以下觀點:
- Redis不僅僅支持簡單的k/v類型的數據,同時還提供
list
,set
,zset
,hash
等數據結構的存儲。 - Redis支持數據的備份,即
master-slave
模式的數據備份。 - Redis支持數據的持久化,可以將內存中的數據保持在磁盤中,重啟的時候可以再次加載進行使用。
1.會話緩存(Session Cache)
最常用的一種使用Redis的情景是會話緩存(session cache)。用Redis緩存會話比其他存儲(如Memcached)的優勢在於:Redis提供持久化。當維護一個不是嚴格要求一致性的緩存時,如果用戶的購物車信息全部丟失,大部分人都會不高興的,現在,他們還會這樣嗎?
幸運的是,隨着 Redis 這些年的改進,很容易找到怎么恰當的使用Redis來緩存會話的文檔。甚至廣為人知的商業平台Magento也提供Redis的插件。
2.全頁緩存(FPC)
除基本的會話token之外,Redis還提供很簡便的FPC
平台。回到一致性問題,即使重啟了Redis實例,因為有磁盤的持久化,用戶也不會看到頁面加載速度的下降,這是一個極大改進,類似PHP本地FPC。
再次以Magento為例,Magento提供一個插件來使用Redis作為全頁緩存后端。
此外,對WordPress的用戶來說,Pantheon有一個非常好的插件 wp-redis,這個插件能幫助你以最快速度加載你曾瀏覽過的頁面。
3. 隊列
Reids在內存存儲引擎領域的一大優點是提供 list 和 set 操作,這使得Redis能作為一個很好的消息隊列平台來使用。Redis作為隊列使用的操作,就類似於本地程序語言(如Python)對 list 的 push/pop 操作。
如果你快速的在Google中搜索“Redis queues”,你馬上就能找到大量的開源項目,這些項目的目的就是利用Redis創建非常好的后端工具,以滿足各種隊列需求。例如,Celery有一個后台就是使用Redis作為broker,你可以從這里去查看。
4.排行榜/計數器
Redis在內存中對數字進行遞增或遞減的操作實現的非常好。集合(Set)和有序集合(Sorted Set)也使得我們在執行這些操作的時候變的非常簡單,Redis只是正好提供了這兩種數據結構。
所以,我們要從排序集合中獲取到排名最靠前的10個用戶–我們稱之為“user_scores”,我們只需要像下面一樣執行即可:
當然,這是假定你是根據你用戶的分數做遞增的排序。如果你想返回用戶及用戶的分數,你需要這樣執行:
ZRANGE user_scores 0 10 WITHSCORES
Agora Games就是一個很好的例子,用Ruby實現的,它的排行榜就是使用Redis來存儲數據的,你可以在這里看到。
5.發布/訂閱
最后(但肯定不是最不重要的)是Redis的發布/訂閱功能。發布/訂閱的使用場景確實非常多。我已看見人們在社交網絡連接中使用,還可作為基於發布/訂閱的腳本觸發器,甚至用Redis的發布/訂閱功能來建立聊天系統!(不,這是真的,你可以去核實)。
Redis提供的所有特性中,我感覺這個是喜歡的人最少的一個,雖然它為用戶提供如果此多功能。
24. 說說Redis哈希槽的概念?
Redis集群沒有使用一致性hash,而是引入了哈希槽的概念,Redis集群有16384個哈希槽,每個key通過CRC16校驗后對16384取模來決定放置哪個槽,集群的每個節點負責一部分hash槽。
25. Redis集群方案什么情況下會導致整個集群不可用?
有A,B,C三個節點的集群,在沒有復制模型的情況下,如果節點B失敗了,那么整個集群就會以為缺少5501-11000
這個范圍的槽而不可用。
關於Redis集群架構更加詳細的內容,可以看看大佬的文章:
那些年用過的Redis集群架構(含面試解析):(https://www.cnblogs.com/rjzheng/p/10360619.html)
推薦
文末
歡迎關注個人微信公眾號:Coder編程
歡迎關注Coder編程公眾號,主要分享數據結構與算法、Java相關知識體系、框架知識及原理、Spring全家桶、微服務項目實戰、DevOps實踐之路、每日一篇互聯網大廠面試或筆試題以及PMP項目管理知識等。更多精彩內容正在路上~
文章收錄至
Github: https://github.com/CoderMerlin/coder-programming
Gitee: https://gitee.com/573059382/coder-programming
歡迎關注並star~