1雷滚、redis 的底層數(shù)據(jù)結(jié)構(gòu)有哪些
(1)字符串
(2)哈希
(3)鏈表
(4)set集合
(5)zset有序集合
2需曾、redis 中的 SDS 和 C 語言中的字符串有什么區(qū)別,優(yōu)點是什么
SDS(Simple Dynamic String揭措,簡單動態(tài)字符串)是 Redis 底層所使用的字符串表示胯舷。
SDS比C語言的字符串多了一個SDSHDR表頭,存放了free(空閑空間)绊含、len(已用空間)墓律、buf(緩沖區(qū))
什荣。
優(yōu)點:
(1)獲取字符串長度更快。C語言獲取字符串需要遍歷整個字符串,時間復(fù)雜度為O(N)黑毅,SDS的表頭len
已經(jīng)存放了已使用空間,獲取字符串長度只需要O(1)券盅。
(2)杜絕緩沖區(qū)溢出鞭执。C語言字符串本身不記錄字符串長度和已使用空間,容易造成緩沖區(qū)溢出伴找。SDS表頭free
成員存放了空閑空間盈蛮,拼接字符串前會先通過free
判斷剩余空間是否足夠,如果不夠就會進行擴容技矮。
(3)減少內(nèi)存分配次數(shù)抖誉。C語言對字符串進行增長或縮短操作殊轴,都要重新分配內(nèi)存。SDS使用空間預(yù)分配和惰性空間釋放策略袒炉,減少了內(nèi)存分配的次數(shù)旁理。
(4)二進制安全。C語言字符串遇0則止我磁,會對文件進行截斷孽文。SDS判斷字符串是否結(jié)尾的依據(jù)是表頭的len成員,不會改變二進制文件夺艰。
SDS:https://redisbook.readthedocs.io/en/latest/internal-datastruct/sds.html
3芋哭、redis 中的字典是如何實現(xiàn)的,如何解決沖突和擴容
根據(jù)鍵值對的鍵計算哈希值和索引值劲适,然后根據(jù)索引值楷掉,將包含鍵值對的哈希節(jié)點存放在哈希數(shù)組的指定索引上。
解決沖突:redis采用鏈地址法解決沖突霞势,每個哈希節(jié)點有一個next指針烹植,多個哈希節(jié)點通過next指針構(gòu)成單向鏈表,總是將最新的節(jié)點添加到表頭愕贡。
擴容:redis的擴容通過rehash(重新散列)實現(xiàn)草雕,為字典ht[1]分配空間,ht[1]的大小為第一個大于等于ht[0].used * 2 的 2^n固以。
4墩虹、redis 的跳表的使用場景是什么,可以實現(xiàn)一下嗎
使用場景:有序列表憨琳。比如實時統(tǒng)計分?jǐn)?shù)排行榜诫钓。
java實現(xiàn)
// 跳表中存儲的是正整數(shù),并且存儲的數(shù)據(jù)是不重復(fù)的
public class SkipList {
private static final int MAX_LEVEL = 16; // 結(jié)點的個數(shù)
private int levelCount = 1; // 索引的層級數(shù)
private Node head = new Node(); // 頭結(jié)點
private Random random = new Random();
// 查找操作
public Node find(int value){
Node p = head;
for(int i = levelCount - 1; i >= 0; --i){
while(p.next[i] != null && p.next[i].data < value){
p = p.next[i];
}
}
if(p.next[0] != null && p.next[0].data == value){
return p.next[0]; // 找到篙螟,則返回原始鏈表中的結(jié)點
}else{
return null;
}
}
// 插入操作
public void insert(int value){
int level = randomLevel();
Node newNode = new Node();
newNode.data = value;
newNode.maxLevel = level; // 通過隨機函數(shù)改變索引層的結(jié)點布置
Node update[] = new Node[level];
for(int i = 0; i < level; ++i){
update[i] = head;
}
Node p = head;
for(int i = level - 1; i >= 0; --i){
while(p.next[i] != null && p.next[i].data < value){
p = p.next[i];
}
update[i] = p;
}
for(int i = 0; i < level; ++i){
newNode.next[i] = update[i].next[i];
update[i].next[i] = newNode;
}
if(levelCount < level){
levelCount = level;
}
}
// 刪除操作
public void delete(int value){
Node[] update = new Node[levelCount];
Node p = head;
for(int i = levelCount - 1; i >= 0; --i){
while(p.next[i] != null && p.next[i].data < value){
p = p.next[i];
}
update[i] = p;
}
if(p.next[0] != null && p.next[0].data == value){
for(int i = levelCount - 1; i >= 0; --i){
if(update[i].next[i] != null && update[i].next[i].data == value){
update[i].next[i] = update[i].next[i].next[i];
}
}
}
}
// 隨機函數(shù)
private int randomLevel(){
int level = 1;
for(int i = 1; i < MAX_LEVEL; ++i){
if(random.nextInt() % 2 == 1){
level++;
}
}
return level;
}
// Node內(nèi)部類
public class Node{
private int data = -1;
private Node next[] = new Node[MAX_LEVEL];
private int maxLevel = 0;
// 重寫toString方法
@Override
public String toString(){
StringBuilder builder = new StringBuilder();
builder.append("{data:");
builder.append(data);
builder.append("; leves: ");
builder.append(maxLevel);
builder.append(" }");
return builder.toString();
}
}
// 顯示跳表中的結(jié)點
public void display(){
Node p = head;
while(p.next[0] != null){
System.out.println(p.next[0] + " ");
p = p.next[0];
}
System.out.println();
}
}
數(shù)據(jù)結(jié)構(gòu)與算法——跳表:http://www.reibang.com/p/fef9817cc943
5菌湃、redis 緩存穿透,緩存擊穿遍略,緩存雪崩惧所,熱點數(shù)據(jù)集中失效 (常問)
(1)緩存穿透:在緩存層和數(shù)據(jù)庫層,都沒有找到數(shù)據(jù)绪杏。解決方案1:把空數(shù)據(jù)存儲起來下愈,并設(shè)置過期時間。解決方案2:使用布隆過濾器蕾久。
(2)緩存擊穿:緩存中的數(shù)據(jù)在某個時刻大批量過期势似,導(dǎo)致大部分的請求都會直接落在數(shù)據(jù)庫上。解決方案1:針對不同類型的數(shù)據(jù),設(shè)置不同的過期時間履因。解決方案2:使用分布式鎖辖佣。
(3)緩存雪崩:緩存在某一時刻集中失效,或者緩存系統(tǒng)出現(xiàn)故障搓逾,所有的并發(fā)流量會直接到達數(shù)據(jù)庫。解決方案1:保證redis的高可用杯拐,使用集群部署霞篡。解決方案2:降流限級。
(4)熱點數(shù)據(jù)集中失效:類似于緩存擊穿端逼。熱點數(shù)據(jù)同時失效朗兵,造成都去查數(shù)據(jù)庫。解決方案:利用集群部署顶滩,保證redis的高可用余掖。
6、redis 的淘汰策略礁鲁,來寫一下 LRU 吧
(1)noeviction:當(dāng)內(nèi)存使用超過配置的時候會返回錯誤盐欺,不會驅(qū)逐任何鍵
(2)allkeys-lru:加入鍵的時候,如果過限仅醇,首先通過LRU算法驅(qū)逐最久沒有使用的鍵
(3)volatile-lru:加入鍵的時候冗美,如果過限,首先從設(shè)置了過期時間的鍵集合中驅(qū)逐最久沒有使用的鍵
(4)allkeys-random:加入鍵的時候析二,如果過限粉洼,從所有key隨機刪除
(5)volatile-random:加入鍵的時候,如果過限叶摄,從過期鍵的集合中隨機驅(qū)逐
(6)volatile-ttl:從配置了過期時間的鍵中驅(qū)逐馬上就要過期的鍵
(7)volatile-lfu:從所有配置了過期時間的鍵中驅(qū)逐使用頻率最少的鍵
(8)allkeys-lfu:從所有鍵中驅(qū)逐使用頻率最少的鍵
實現(xiàn)
public class LRU<K,V> {
private static final float hashLoadFactory = 0.75f;
private LinkedHashMap<K,V> map;
private int cacheSize;
public LRU(int cacheSize) {
this.cacheSize = cacheSize;
int capacity = (int)Math.ceil(cacheSize / hashLoadFactory) + 1;
map = new LinkedHashMap<K,V>(capacity, hashLoadFactory, true){
private static final long serialVersionUID = 1;
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > LRU.this.cacheSize;
}
};
}
public synchronized V get(K key) {
return map.get(key);
}
public synchronized void put(K key, V value) {
map.put(key, value);
}
public synchronized void clear() {
map.clear();
}
public synchronized int usedSize() {
return map.size();
}
public void print() {
for (Map.Entry<K, V> entry : map.entrySet()) {
System.out.print(entry.getValue() + "--");
}
System.out.println();
}
}
7属韧、redis 的持久化方式,RDB 和 AOF 分別的使用場景
持久化方式
(1)RDB:將某一時刻的內(nèi)存快照蛤吓,以二進制的方式寫入磁盤
(2)AOF:將redis的操作日志以追加的方式寫入文件
使用場景
(1)如果追求備份的速度宵喂,可以忽略部分?jǐn)?shù)據(jù)丟失,推薦使用RDB
(2)如果追求數(shù)據(jù)的完整性柱衔,可以接受犧牲備份的速度樊破,推薦使用AOF
8、redis 如何處理事務(wù)
redis使用MULTI唆铐、EXEC哲戚、DISCARD、WATCH
四個事務(wù)命令
使用MULTI
開啟事務(wù)艾岂,客戶端可以向服務(wù)器發(fā)送任意多個命令顺少,這些命令不會立馬執(zhí)行,而是被放到一個隊列中,當(dāng)調(diào)用EXEC
命令時脆炎,所有隊列中的命令才會被執(zhí)行梅猿。如果在執(zhí)行事務(wù)的過程發(fā)生異常,而沒有執(zhí)行EXEC
秒裕,那么事務(wù)中的所有命令都不會被執(zhí)行袱蚓。至于在執(zhí)行EXEC
命令之后發(fā)生了錯誤,及時事務(wù)中某個命令發(fā)生了錯誤几蜻,其他事務(wù)也會正常執(zhí)行喇潘,沒有回滾操作
通過調(diào)用DISCARD
,客戶端可以清空事務(wù)隊列梭稚,并放棄執(zhí)行事務(wù)
WATCH
命令可以為redis提供CAS
行為
9颖低、redis 為什么那么快?
(1)采用多路復(fù)用IO阻塞機制
(2)數(shù)據(jù)結(jié)構(gòu)簡單弧烤,操作節(jié)省時間
(3)運行在內(nèi)存中
10忱屑、redis 是單線程為什么還那么快?
(1)采用了非阻塞IO多路復(fù)用機制
(2)單線程操作暇昂,避免了頻繁的上下文切換
(3)運行在內(nèi)存中
11莺戒、redis 的操作為什么是原子性的,如何保證原子性
因為redis是單線程的
對于redis來說话浇,執(zhí)行get
脏毯、set
以及eval
等api,都是一個一個的任務(wù)幔崖,這些任務(wù)都會由redis的線程去負(fù)責(zé)執(zhí)行食店,任務(wù)要么執(zhí)行成功,要么執(zhí)行失敗
12赏寇、redis 集群用過哪些方案吉嫩,分別怎么做。講一下一致性哈希
集群方案
(1)主從模式嗅定。只需要在配置文件加上slaveof 192.169.x.x 6379
自娩,指明master的ip和端口號
(2)哨兵模式。給幾臺哨兵服務(wù)添加不同的端口渠退,配置主服務(wù)器的ip和端口忙迁,并且加上權(quán)值,使用Redis命令啟動哨兵碎乃,./redis-sentinel sentinel-26380.conf
(3)cluster集群模式姊扔。配置集群地址,開啟cluster梅誓,cluster-enable yes
恰梢,集群配置文件:cluster-config-file nodes-7000.conf
佛南,這個可以不修改,集群超時時間:cluster-node-timeout 15000
嵌言,槽是否全覆蓋:cluster-require-full-coverage no
嗅回,默認(rèn)是yes,后臺運行:daemonize yes
摧茴,輸出日志:logfile "./redis.log"
绵载,監(jiān)聽端口:port 7000
,拷貝同樣的配置文件苛白,修改端口
13尘分、redis 什么情況下會出現(xiàn)性能問題,有什么處理辦法丸氛?
(1)master寫內(nèi)存快照,save命令調(diào)度rdbSave函數(shù)著摔,會阻塞主線程的工作缓窜,當(dāng)快照比較大時對性能影響是非常大的,會間斷性暫停服務(wù)谍咆,所以Master最好不要寫內(nèi)存快照禾锤。
(2)Master AOF持久化,如果不重寫AOF文件摹察,這個持久化方式對性能的影響是最小的恩掷,但是AOF文件會不斷增大,AOF文件過大會影響Master重啟的恢復(fù)速度供嚎。Master最好不要做任何持久化工作黄娘,包括內(nèi)存快照和AOF日志文件,特別是不要啟用內(nèi)存快照做持久化,如果數(shù)據(jù)比較關(guān)鍵克滴,某個Slave開啟AOF備份數(shù)據(jù)逼争,策略為每秒同步一次。
(3)Master調(diào)用BGREWRITEAOF重寫AOF文件劝赔,AOF在重寫的時候會占大量的CPU和內(nèi)存資源誓焦,導(dǎo)致服務(wù)load過高,出現(xiàn)短暫服務(wù)暫妥琶保現(xiàn)象杂伟。
(4)Redis主從復(fù)制的性能問題,為了主從復(fù)制的速度和連接的穩(wěn)定性仍翰,Slave和Master最好在同一個局域網(wǎng)內(nèi)
14赫粥、有沒有使用過 redis 的分布式鎖,有什么優(yōu)缺點
優(yōu)點:
(1)redis性能很好
(2)redis對命令的支持較好歉备,實現(xiàn)起來比較方便
缺點:
(1)redis分布式鎖傅是,需要自己不斷嘗試去獲取鎖,比較消耗性能
(2)redis獲取鎖的那個客戶端掛了,只能等待超時時間后才能釋放鎖
15喧笔、說一下 redis 的內(nèi)存模型
(1)數(shù)據(jù)
(2)redis主進程本身運行需要占用的內(nèi)存帽驯。如代碼、常量池等
(3)緩沖內(nèi)存书闸。
(4)內(nèi)存碎片尼变。redis在分配、回收物理內(nèi)存過程中產(chǎn)生
16浆劲、說一下 redis 和 memcache 的區(qū)別
(1)memcache僅支持簡單的key-value數(shù)據(jù)結(jié)構(gòu)嫌术,redis支持字符串、哈希牌借、鏈表度气、set集合,有序set集合
(2)redis不是所有的數(shù)據(jù)都存儲在內(nèi)存膨报,會將一些很久沒用的value交換到磁盤
(3)redis采用的單核磷籍,memcache可以采用多核,在存儲小數(shù)據(jù)的時候现柠,redis性能更好院领,而在大數(shù)據(jù)的時候,memcache性能更好
(4)redis集群可以采用一主多從够吩,一主一從比然。memcache集群只能采用一主多從
(5)redis支持?jǐn)?shù)據(jù)恢復(fù)。memcache不支持
17周循、你用 redis 做過什么强法?(這里盡量不要講只做過緩存,可以說一下隊列湾笛,排行榜/計數(shù)器拟烫,發(fā)布/訂閱)
(1)緩存
(2)商品銷量排行榜
(3)消息隊列
18、你用過哪些非關(guān)系型數(shù)據(jù)庫迄本,都有什么特點硕淑,使用場景分別是什么(體現(xiàn)你技術(shù)廣- 度的時刻到了,盡可能多說嘉赎,但是不會的不要說置媳,防止被問死)
(1)redis:可以通過key來添加、查詢或者刪除數(shù)據(jù)公条,鑒于使用主鍵訪問拇囊,所以會獲得不錯的性能及擴展性。適用于儲存用戶信息靶橱,比如會話寥袭、配置文件路捧、參數(shù)、購物車等等传黄。
(2)MongoDB:將數(shù)據(jù)以文檔的形式儲存杰扫。適用于日志、分析
(3)HBase:將數(shù)據(jù)儲存在列族中膘掰,一個列族存儲經(jīng)常被一起查詢的相關(guān)數(shù)據(jù)章姓。適用于日志、博客平臺
19识埋、Mongodb 相對于 Mysql 有哪些優(yōu)勢凡伊,底層索引使用的數(shù)據(jù)結(jié)構(gòu)是什么,為什么要使用這個
(1)Mongod的訪問速度優(yōu)于MySQL
(2)Mongodb獲取數(shù)據(jù)的方式比MySQL快捷
(3)Mongodb的表的擴展窒舟、刪除系忙、維護簡單,不依賴于SQL
(4)Mongodb對開發(fā)更友好惠豺,拿到的數(shù)據(jù)都是json格式
(5)Mongodb會將系統(tǒng)內(nèi)存作為緩存
(6)Mongodb自帶分布式文件系統(tǒng)笨觅,可以很方便部署在服務(wù)器集群上
Mongodb索引數(shù)據(jù)結(jié)構(gòu)為B-Tree
為什么要使用這個數(shù)據(jù)結(jié)構(gòu),參考文章:https://blog.csdn.net/u012939368/article/details/76696673
20耕腾、Mongodb 中的分片是什么意思
MongoDB分片集群將數(shù)據(jù)分布在一個或多個分片上。每個分片部署成一個MongoDB副本集杀糯,該副本集保存了集群整體數(shù)據(jù)的一部分扫俺。因為每個分片都是一個副本集,所以他們擁有自己的復(fù)制機制固翰,能夠自動進行故障轉(zhuǎn)移狼纬。你可以直接連接單個分片,就像連接單獨的副本集一樣骂际。但是疗琉,如果連接的副本集是分片集群的一部分,那么只能看到部分?jǐn)?shù)據(jù)歉铝。
參考文章:https://blog.csdn.net/wanght89/article/details/77842336