redis常用面試題總結
redis常用面試題總結
一、雪崩問題
在海量資料時,現在電商系統已經對快取的依賴性非常高。有一種情況。當海量的請求過來時,快取宕機,海量的請求繼續湧向資料庫,資料庫伺服器宕機。將資料庫伺服器重啟,重啟後,剛起來,海量的請求又來了資料庫伺服器都無法啟動。這種情況稱為雪崩。
解決辦法:必須使用分散式快取。叢集,可以通過多臺的伺服器分擔快取。這時如果一臺伺服器宕機,這時少量的請求湧向資料庫,這時資料庫可以承擔。不會宕機。如果訪問壓力還非常巨大,可以繼續增加伺服器。然後分佈的備份內容。形成快取的主從。前面的方案還會有少量的快取的資料丟失,但高可用後資料就不會丟失。
二、redis能否替代mysql
不能,redis NO-SQL no SQL,none SQL。它沒有複雜結構,不支援強關係型資料。
例如:關係型資料:部門和使用者。一個部門下有多個使用者,一個使用者從屬一個部門。非結構化資料:html/xml/json、視訊、圖片。
根據應用場景分類儲存:結構化的依然使用mysql傳統結構化資料庫。對非結構化但是很大的儲存到mongodb(視訊、word/excel/pdf等檔案)。對非結構的但是需要快速查詢的memCache或者Redis中(json)。對海量的非結構化的資料,還想對其進行類似關係型資料的分析hbase(列)。對需要分詞檢索的使用solr或者elasticSearch。
三、redis實現訊息佇列
redis能做訊息佇列得益於他list物件blpop brpop介面以及Pub/Sub(釋出/訂閱)的某些介面。他們都是阻塞版的,所以可以用來做訊息佇列。
四、redis持久化
redis直接將資料儲存到記憶體中,可通過兩種方式持久化:定時快照和基於語句的追加。定時快照的方法是指每隔一段時間將整個資料庫的資料寫到磁碟上,很明顯,每次均是寫全部資料,代價非常高;而基於語句的追加方法值追蹤變化的資料,這類似於MySQL的binlog方法,但追加log可能過大,同時所有操作均要重新執行一遍,回寫速度慢。
兩種實現方式分別為RDB和AOF,具體的實現方式參考這篇文章:https://www.cnblogs.com/xingzc/p/5988080.html
五、redis舉例應用
1、快取(資料查詢、短連線、新聞內容、商品內容等等)。(最多使用)
2、分散式叢集架構中的session分離。
3、聊天室的線上好友列表
4、任務佇列。(秒殺、搶購、12306等等)(先進先出)
5、應用排行榜。(可以給每個元素設定一個打分,這樣就可以排序)
6、網站訪問統計。
7、資料過期處理(可以精確到毫秒)。
8、分散式鎖
(1) 取最新N個數據的操作:
比如典型的取你網站的最新文章,通過下面方式,我們可以將最新的5000條評論的ID放在Redis的List集合中,並將超出集合部分從資料庫獲取
1)使用LPUSH latest.comments命令,向list集合中插入資料
2)插入完成後再用LTRIM latest.comments 0 5000命令使其永遠只儲存最近5000個ID。
(2) 聊天室好友線上列表
假設A存放你的好友,B存放所有線上的人,它們的交集就是你的線上好友 — sinter setA setB
(3)spring 定時任務
@schedule註解可以實現簡單的定時任務,但當有多臺服務同時部署時,這時需要一個分散式鎖來控制只能有一臺伺服器能執行定時任務。
jedis.setnx可以做到原子性操作,設定值成功返回1,失敗返回0,同時訪問只有一臺伺服器能設定成功,即拿到了這個鎖,接著執行定時任務,設定成功後對這個key加一個過期時間,最後在finally裡將這個key刪掉,避免影響下次的定時任務觸發。
(4)一次寫入大的資料量
pipeline適用於批處理。當有大量的操作需要一次性執行的時候,可以用管道。
Jedis jedis = new Jedis(String, int);
Pipeline p = jedis.pipelined();
p.set(key,value);//每個操作都發送請求給redis-server
p.get(key,value);
p.sync();//這段程式碼獲取所有的response
管道通過一次性寫入請求,然後一次性讀取響應。也就是說jedis是:request response,request response,…;pipeline則是:request request… response response的方式。這樣無需每次請求都等待server端的響應。
(5)分散式鎖實現
1. 通過jedis.setnx(key,value)實現
2. 通過事務(multi)實現
public boolean lock_2(long timeout) {
long nano = System.nanoTime();
timeout *= ONE_MILLI_NANOS;
try {
while ((System.nanoTime() - nano) < timeout) {
Transaction t = jedis.multi();
// 開啟事務,當server端收到multi指令
// 會將該client的命令放入一個佇列,然後依次執行,知道收到exec指令
t.getSet(key, LOCKED);
t.expire(key, EXPIRE);
String ret = (String) t.exec().get(0);
if (ret == null || ret.equals("UNLOCK")) {
return true;
}
// 短暫休眠,nano避免出現活鎖
Thread.sleep(3, r.nextInt(500));
}
} catch (Exception e) {
}
return false;
}
3. 通過事務+監聽實現
public boolean lock_3(long timeout) {
long nano = System.nanoTime();
timeout *= ONE_MILLI_NANOS;
try {
while ((System.nanoTime() - nano) < timeout) {
jedis.watch(key);
// 開啟watch之後,如果key的值被修改,則事務失敗,exec方法返回null
String value = jedis.get(key);
if (value == null || value.equals("UNLOCK")) {
Transaction t = jedis.multi();
t.setex(key, EXPIRE, LOCKED);
if (t.exec() != null) {
return true;
}
}
jedis.unwatch();
// 短暫休眠,nano避免出現活鎖
Thread.sleep(3, r.nextInt(500));
}
} catch (Exception e) {
}
return false;
}
六、mySQL裡有2000w資料,redis中只存20w的資料,如何保證redis中的資料都是熱點資料
redis 記憶體資料集大小上升到一定大小的時候,就會施行資料淘汰策略(回收策略)。redis 提供 6種資料淘汰策略:
volatile-lru:從已設定過期時間的資料集(server.db[i].expires)中挑選最近最少使用的資料淘汰。
volatile-ttl:從已設定過期時間的資料集(server.db[i].expires)中挑選將要過期的資料淘汰。
volatile-random:從已設定過期時間的資料集(server.db[i].expires)中任意選擇資料淘汰。
allkeys-lru:從資料集(server.db[i].dict)中挑選最近最少使用的資料淘汰。
allkeys-random:從資料集(server.db[i].dict)中任意選擇資料淘汰。
no-enviction(驅逐):禁止驅逐資料。
相關快取淘汰演算法–LRU演算法:https://blog.csdn.net/wangxilong1991/article/details/70172302
七、限制1小時內每使用者Id最多隻能登入5次
用列表實現:列表中每個元素代表登陸時間,只要最後的第5次登陸時間和現在時間差不超過1小時就禁止登陸。
八、redis事物的瞭解CAS(check-and-set 操作實現樂觀鎖 )?
Redis作為NoSQL資料庫也同樣提供了事務機制。在Redis中,MULTI/EXEC/DISCARD/WATCH這四個命令是我們實現事務的基石
事務的實現特徵:
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伺服器了。
九、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是否被重新設定成功。
十、redis配置檔案
常用到的配置項如下:
port 服務埠
bind 繫結ip其他ip不能訪問(多個ip空格隔開)
databases 資料庫數量,預設16個
daemonize設定為守護程序(linux平臺)
maxmemory最大的記憶體大小(1MB、1GB、1m、1g)
maxmemory-policy達到記憶體限制後的處理策略(見第6點)
預設RDB持久化機制配置
save 900 1 #900秒內如果超過1個key被修改,則發起快照儲存
save 300 10 #300秒內容如超過10個key被修改,則發起快照儲存
appendonly yes //啟用aof持久化方式
#appendfsync always //每次收到寫命令就立即強制寫入磁碟,最慢的,但是保證完全的持久化,不推薦使用
appendfsync everysec //每秒鐘強制寫入磁碟一次,在效能和持久化方面做了很好的折中,推薦(預設)
#appendfsync no //完全依賴os,效能最好,持久化沒保證