close
給大家整理一下面試題,並附上答案。
Mysql索引在什麼情況下會失效
MySql的存儲引擎InnoDB與MyISAM的區別
Mysql在項目中的優化場景,慢查詢解決等
Mysql有什麼索引,索引模型是什麼
B-樹與B+樹的區別?為什麼不用紅黑樹
Mysql主從同步怎麼做
樂觀鎖與悲觀鎖的區別?
聊聊binlog日誌
redis 持久化有哪幾種方式,怎麼選?
redis 主從同步是怎樣的過程?
redis 的 zset 怎麼實現的?
Redis 過期策略和內存淘汰策略
Hashmap實現原理
select 和 epoll的區別
http與https的區別,https的原理,如何加密的?
Raft算法原理
消息中間件如何做到高可用
消息隊列怎麼保證不丟消息的
聊聊Redis的哨兵機制
算法題:無重複字符的最長子串
1. Mysql索引在什麼情況下會失效
查詢條件包含or,可能導致索引失效
如何字段類型是字符串,where時一定用引號括起來,否則索引失效
like通配符可能導致索引失效。
聯合索引,查詢時的條件列不是聯合索引中的第一個列,索引失效。
在索引列上使用mysql的內置函數,索引失效。
對索引列運算(如,+、-、*、/),索引失效。
索引字段上使用(!= 或者 < >,not in)時,可能會導致索引失效。
索引字段上使用is null, is not null,可能導致索引失效。
左連接查詢或者右連接查詢查詢關聯的字段編碼格式不一樣,可能導致索引失效。
mysql估計使用全表掃描要比使用索引快,則不使用索引。
2. MySql的存儲引擎InnoDB與MyISAM的區別
InnoDB支持事務,MyISAM不支持事務
InnoDB支持外鍵,MyISAM不支持外鍵
InnoDB 支持 MVCC(多版本並發控制),MyISAM 不支持
select count(*) from table時,MyISAM更快,因為它有一個變量保存了整個表的總行數,可以直接讀取,InnoDB就需要全表掃描。
Innodb不支持全文索引,而MyISAM支持全文索引(5.7以後的InnoDB也支持全文索引)
InnoDB支持表、行級鎖,而MyISAM支持表級鎖。
InnoDB表必須有主鍵,而MyISAM可以沒有主鍵
Innodb表需要更多的內存和存儲,而MyISAM可被壓縮,存儲空間較小。
Innodb按主鍵大小有序插入,MyISAM記錄插入順序是,按記錄插入順序保存。
InnoDB 存儲引擎提供了具有提交、回滾、崩潰恢復能力的事務安全,與 MyISAM 比 InnoDB 寫的效率差一些,並且會占用更多的磁盤空間以保留數據和索引
3. mysql在項目中的優化場景,慢查詢解決等

我們面對慢查詢,首先想到的就是加索引。還有就是慢查詢的排查解決手段:

打開慢查詢日誌slow_query_log,確認SQL語句是否占用過多資源,用explain查詢執行計劃、對group by、order by、join等語句優化,如果數據量實在太大,是否考慮分庫分表等等。

4. Mysql有什麼索引,索引模型是什麼

數據結構維度來講的話,一般使用都是B+樹索引,大家想詳細理解的話,可以看我之前這篇文章哈:MySQL索引底層:B+樹詳解

5. B-樹與B+樹的區別?為什麼不用紅黑樹

B-樹與B+樹的區別:

B-樹內部節點是保存數據的;而B+樹內部節點是不保存數據的,只作索引作用,它的葉子節點才保存數據。
B+樹相鄰的葉子節點之間是通過鍊表指針連起來的,B-樹卻不是。
查找過程中,B-樹在找到具體的數值以後就結束,而B+樹則需要通過索引找到葉子結點中的數據才結束
B-樹中任何一個關鍵字出現且只出現在一個結點中,而B+樹可以出現多次。

為什麼索引結構默認使用B+樹,而不是B-Tree,Hash哈希,二叉樹,紅黑樹?

Hash哈希,只適合等值查詢,不適合範圍查詢。
一般二叉樹,可能會特殊化為一個鍊表,相當於全表掃描。
紅黑樹,是一種特化的平衡二叉樹,MySQL 數據量很大的時候,索引的體積也會很大,內存放不下的而從磁盤讀取,樹的層次太高的話,讀取磁盤的次數就多了。
B-Tree,葉子節點和非葉子節點都保存數據,相同的數據量,B+樹更矮壯,也是就說,相同的數據量,B+樹數據結構,查詢磁盤的次數會更少。
6. Mysql主從同步怎麼做

大家要熟悉MySQL主從複製原理哈:

詳細的主從複製過程如圖:

上圖主從複製過程分了五個步驟進行:

主庫的更新SQL(update、insert、delete)被寫到binlog
從庫發起連接,連接到主庫。
此時主庫創建一個binlog dump thread,把binlog的內容發送到從庫。
從庫啟動之後,創建一個I/O線程,讀取主庫傳過來的binlog內容並寫入到relay log
從庫還會創建一個SQL線程,從relay log裡面讀取內容,從ExecMasterLog_Pos位置開始執行讀取到的更新事件,將更新內容寫入到slave的db

主從同步這塊呢,還涉及到如何保證主從一致的、數據庫主從延遲的原因與解決方案、數據庫的高可用方案。

7. 樂觀鎖與悲觀鎖的區別?

悲觀鎖:

悲觀鎖她專一且缺乏安全感了,她的心只屬於當前事務,每時每刻都擔心着它心愛的數據可能被別的事務修改,所以一個事務擁有(獲得)悲觀鎖後,其他任何事務都不能對數據進行修改啦,只能等待鎖被釋放才可以執行。

select ...for update就是悲觀鎖一種實現。

樂觀鎖:

樂觀鎖的「樂觀情緒」體現在,它認為數據的變動不會太頻繁。因此,它允許多個事務同時對數據進行變動。實現方式:樂觀鎖一般會使用版本號機制或CAS算法實現。

之前用樂觀鎖解決過實戰的並發問題,大家有興趣可以加我微信,一起聊聊哈。

8. 聊聊binlog日誌

binlog是歸檔日誌,屬於MySQL Server層的日誌。可以實現主從複製和數據恢復兩個作用。當需要恢復數據時,可以取出某個時間範圍內的binlog進行重放恢復即可。

binlog 日誌有三種格式,分別是statement,row和mixed。

如果是statement格式,binlog記錄的是SQL的原文,他可能會導致主庫不一致(主庫和從庫選的索引不一樣時)。我們來分析一下。假設主庫執行刪除這個SQL(其中a和create_time都有索引)如下:

deletefromtwherea>'666'andcreate_time<'2022-03-01'limit1;

我們知道,數據選擇了a索引和選擇create_time索引,最後limit 1出來的數據一般是不一樣的。所以就會存在這種情況:在binlog = statement格式時,主庫在執行這條SQL時,使用的是索引a,而從庫在執行這條SQL時,使用了索引create_time。最後主從數據不一致了。

如何解決這個問題呢?

可以把binlog格式修改為row。row格式的binlog日誌,記錄的不是SQL原文,而是兩個event:Table_map 和 Delete_rows。Table_map event說明要操作的表,Delete_rows event用於定義要刪除的行為,記錄刪除的具體行數。row格式的binlog記錄的就是要刪除的主鍵ID信息,因此不會出現主從不一致的問題。

但是如果SQL刪除10萬行數據,使用row格式就會很占空間的,10萬條數據都在binlog裡面,寫binlog的時候也很耗IO。但是statement格式的binlog可能會導致數據不一致,因此設計MySQL的大叔想了一個折中的方案,mixed格式的binlog。所謂的mixed格式其實就是row和statement格式混合使用,當MySQL判斷可能數據不一致時,就用row格式,否則使用就用statement格式。

9. Redis 持久化有哪幾種方式,怎麼選?

既然它是基於內存的,如果Redis服務器掛了,數據就會丟失。為了避免數據丟失了,Redis提供了兩種持久化方式,RDB和AOF。

9.1 AOF 持久化

AOF(append only file) 持久化,採用日誌的形式來記錄每個寫操作,追加到AOF文件的末尾。Redis默認情況是不開啟AOF的。重啟時再重新執行AOF文件中的命令來恢復數據。它主要解決數據持久化的實時性問題。

AOF是執行完命令後才記錄日誌的。為什麼不先記錄日誌再執行命令呢?這是因為Redis在向AOF記錄日誌時,不會先對這些命令進行語法檢查,如果先記錄日誌再執行命令,日誌中可能記錄了錯誤的命令,Redis使用日誌回複數據時,可能會出錯。

正是因為執行完命令後才記錄日誌,所以不會阻塞當前的寫操作。但是會存在兩個風險:

更執行完命令還沒記錄日誌時,宕機了會導致數據丟失
AOF不會阻塞當前命令,但是可能會阻塞下一個操作。

這兩個風險最好的解決方案是折中妙用AOF機制的三種寫回策略 appendfsync:

always,同步寫回,每個子命令執行完,都立即將日誌寫回磁盤。
everysec,每個命令執行完,只是先把日誌寫到AOF內存緩衝區,每隔一秒同步到磁盤。
no:只是先把日誌寫到AOF內存緩衝區,有操作系統去決定何時寫入磁盤。

always同步寫回,可以基本保證數據不丟失,no策略則性能高但是數據可能會丟失,一般可以考慮折中選擇everysec。

如果接受的命令越來越多,AOF文件也會越來越大,文件過大還是會帶來性能問題。日誌文件過大怎麼辦呢?AOF重寫機制!就是隨着時間推移,AOF文件會有一些冗餘的命令如:無效命令、過期數據的命令等等,AOF重寫機制就是把它們合併為一個命令(類似批處理命令),從而達到精簡壓縮空間的目的。

AOF重寫會阻塞嘛?AOF日誌是由主線程會寫的,而重寫則不一樣,重寫過程是由後台子進程bgrewriteaof完成。

AOF的優點:數據的一致性和完整性更高,秒級數據丟失。
缺點:相同的數據集,AOF文件體積大於RDB文件。數據恢復也比較慢。
9.2 RDB

因為AOF持久化方式,如果操作日誌非常多的話,Redis恢復就很慢。有沒有在宕機快速恢復的方法呢,有的,RDB!

RDB,就是把內存數據以快照的形式保存到磁盤上。和AOF相比,它記錄的是某一時刻的數據,,並不是操作。

什麼是快照?可以這樣理解,給當前時刻的數據,拍一張照片,然後保存下來。

RDB持久化,是指在指定的時間間隔內,執行指定次數的寫操作,將內存中的數據集快照寫入磁盤中,它是Redis默認的持久化方式。執行完操作後,在指定目錄下會生成一個dump.rdb文件,Redis 重啟的時候,通過加載dump.rdb文件來恢復數據。RDB觸發機制主要有以下幾種:

RDB通過bgsave命令的執行全量快照,可以避免阻塞主線程。basave命令會fork一個子進程,然後該子進程會負責創建RDB文件,而服務器進程會繼續處理命令請求

快照時,數據能修改嘛? Redis接住操作系統的寫時複製技術(copy-on-write,COW),在執行快照的同時,正常處理寫操作。

雖然bgsave執行不會阻塞主線程,但是頻繁執行全量快照也會帶來性能開銷。比如bgsave子進程需要通過fork操作從主線程創建出來,創建後不會阻塞主線程,但是創建過程是會阻塞主線程的。可以做增量快照。

RDB的優點:與AOF相比,恢復大數據集的時候會更快,它適合大規模的數據恢復場景,如備份,全量複製等
缺點:沒辦法做到實時持久化/秒級持久化。

Redis4.0開始支持RDB和AOF的混合持久化,就是內存快照以一定頻率執行,兩次快照之間,再使用AOF記錄這期間的所有命令操作。

9.3 如何選擇RDB和AOF
如果數據不能丟失,RDB和AOF混用
如果只作為緩存使用,可以承受幾分鐘的數據丟失的話,可以只使用RDB。
如果只使用AOF,優先使用everysec的寫回策略。
10. Redis 主從同步是怎樣的過程?

Redis主從同步包括三個階段。

第一階段:主從庫間建立連接、協商同步。

從庫向主庫發送psync 命令,告訴它要進行數據同步。
主庫收到 psync 命令後,響應FULLRESYNC命令(它表示第一次複製採用的是全量複製),並帶上主庫runID和主庫目前的複製進度offset。

第二階段:主庫把數據同步到從庫,從庫收到數據後,完成本地加載。

主庫執行bgsave命令,生成RDB文件,接着將文件發給從庫。從庫接收到RDB 文件後,會先清空當前數據庫,然後加載 RDB 文件。
主庫把數據同步到從庫的過程中,新來的寫操作,會記錄到replication buffer。

第三階段,主庫把新寫的命令,發送到從庫。

主庫完成RDB發送後,會把replication buffer中的修改操作發給從庫,從庫再重新執行這些操作。這樣主從庫就實現同步啦。
11. 聊聊Redis的zset,它是怎麼實現的?

zset是Redis常用數據類型之一,它的成員是有序排列的,一般用於排行榜類型的業務場景,比如 QQ 音樂排行榜、禮物排行榜等等。

它的簡單格式舉例:zadd key score member [score member ...],zrank key member
它的底層內部編碼:ziplist(壓縮列表)、skiplist(跳躍表)

當 zset 滿足以下條件時使用壓縮列表:

當成員的數量小於128 個;
每個 member (成員)的字符串長度都小於 64 個字節。

壓縮列表做簡單介紹,它由以下五部分組成

zlbytes 是一個無符號整數,表示當前ziplist占用的總字節數;
zltail 指的是壓縮列表尾部元素相對於壓縮列表起始元素的偏移量。
zllen 指 ziplist 中 entry 的數量。當 zllen 比2^16 - 2大時,需要完全遍歷 entry 列表來獲取 entry 的總數目。
entry 用來存放具體的數據項(score和member),長度不定,可以是字節數組或整數,entry 會根據成員的數量自動擴容。-zlend 是一個單字節的特殊值,等於 255,起到標識 ziplist 內存結束點的作用。

skiplist(跳躍表)在鍊表的基礎上,增加了多級索引,通過索引位置的幾個跳轉,實現數據的快速定位,其插入、刪除、查找的時間複雜度均為 O(logN)。

12. Redis 過期策略和內存淘汰策略 12.1 Redis的過期策略

我們在set key的時候,可以給它設置一個過期時間,比如expire key 60。指定這key60s後過期,60s後,redis是如何處理的嘛?我們先來介紹幾種過期策略哈:

一般有定時過期、惰性過期、定期過期三種。

定時過期

每個設置過期時間的key都需要創建一個定時器,到過期時間就會立即對key進行清除。該策略可以立即清除過期的數據,對內存很友好;但是會占用大量的CPU資源去處理過期的數據,從而影響緩存的響應時間和吞吐量。

惰性過期

只有當訪問一個key時,才會判斷該key是否已過期,過期則清除。該策略可以最大化地節省CPU資源,卻對內存非常不友好。極端情況可能出現大量的過期key沒有再次被訪問,從而不會被清除,占用大量內存。

定期過期

每隔一定的時間,會掃描一定數量的數據庫的expires字典中一定數量的key,並清除其中已過期的key。該策略是前兩者的一個折中方案。通過調整定時掃描的時間間隔和每次掃描的限定耗時,可以在不同情況下使得CPU和內存資源達到最優的平衡效果。

expires字典會保存所有設置了過期時間的key的過期時間數據,其中,key是指向鍵空間中的某個鍵的指針,value是該鍵的毫秒精度的UNIX時間戳表示的過期時間。鍵空間是指該Redis集群中保存的所有鍵。

Redis中同時使用了惰性過期和定期過期兩種過期策略。

假設Redis當前存放30萬個key,並且都設置了過期時間,如果你每隔100ms就去檢查這全部的key,CPU負載會特別高,最後可能會掛掉。
因此,redis採取的是定期過期,每隔100ms就隨機抽取一定數量的key來檢查和刪除的。
但是呢,最後可能會有很多已經過期的key沒被刪除。這時候,redis採用惰性刪除。在你獲取某個key的時候,redis會檢查一下,這個key如果設置了過期時間並且已經過期了,此時就會刪除。

但是呀,如果定期刪除漏掉了很多過期的key,然後也沒走惰性刪除。就會有很多過期key積在內存內存,直接會導致內存爆的。或者有些時候,業務量大起來了,redis的key被大量使用,內存直接不夠了,運維小哥哥也忘記加大內存了。難道redis直接這樣掛掉?不會的!Redis用8種內存淘汰策略保護自己~

12.2 Redis 內存淘汰策略
volatile-lru:當內存不足以容納新寫入數據時,從設置了過期時間的key中使用LRU(最近最少使用)算法進行淘汰;
allkeys-lru:當內存不足以容納新寫入數據時,從所有key中使用LRU(最近最少使用)算法進行淘汰。
volatile-lfu:4.0版本新增,當內存不足以容納新寫入數據時,在過期的key中,使用LFU(最少訪問算法)進行刪除key。
allkeys-lfu:4.0版本新增,當內存不足以容納新寫入數據時,從所有key中使用LFU算法進行淘汰;
volatile-random:當內存不足以容納新寫入數據時,從設置了過期時間的key中,隨機淘汰數據;。
allkeys-random:當內存不足以容納新寫入數據時,從所有key中隨機淘汰數據。
volatile-ttl:當內存不足以容納新寫入數據時,在設置了過期時間的key中,根據過期時間進行淘汰,越早過期的優先被淘汰;
noeviction:默認策略,當內存不足以容納新寫入數據時,新寫入操作會報錯。
13. Hashmap 是怎樣實現的?為什麼要用紅黑樹,而不用平衡二叉樹?為什麼在1.8中鍊表大於8時會轉紅黑樹?HashMap是線性安全的嘛?如何保證安全? 13.1 Hashmap 是怎樣實現的?
JDK1.7 Hashmap的底層數據結構是數組+鍊表
JDK1.8 Hashmap的底層數據結構是數組+鍊表+紅黑樹

數據元素通過映射關係,即散列函數,映射到桶數組對應索引的位置,插入該位置時,如果發生衝突,從衝突的位置拉一個鍊表,把衝突元素放到鍊表。如果鍊表長度>8且數組大小>=64,鍊表轉為紅黑樹 如果紅黑樹節點個數<6 ,轉為鍊表。

13.2 為什麼要用紅黑樹,為什麼不用二叉樹?為什麼不用平衡二叉樹?

為什麼不用二叉樹?

紅黑樹是一種平衡的二叉樹,其插入、刪除、查找的最壞時間複雜度都為 O(logn),避免了二叉樹最壞情況下的O(n)時間複雜度。

為什麼不用平衡二叉樹?

平衡二叉樹是比紅黑樹更嚴格的平衡樹,為了保持保持平衡,需要旋轉的次數更多,也就是說平衡二叉樹保持平衡的效率更低,所以平衡二叉樹插入和刪除的效率比紅黑樹要低。

13.3 為什麼在1.8中鍊表大於8時會轉紅黑樹?

紅黑樹的平均查找長度是log(n),如果長度為8,平均查找長度為log(8)=3,鍊表的平均查找長度為n/2,當長度為8時,平均查找長度為8/2=4,這才有轉換成樹的必要;鍊表長度如果是小於等於6,6/2=3,而log(6)=2.6,雖然速度也很快的,但是轉化為樹結構和生成樹的時間並不會太短。

13.4 HashMap是線性安全的嘛?如何保證安全?

HashMap不是線程安全的,多線程下擴容死循環。可以使用HashTable、Collections.synchronizedMap、以及 ConcurrentHashMap 可以實現線程安全。

HashTable 是在每個方法加上 synchronized 關鍵字,粒度比較大;
Collections.synchronizedMap 是使用 Collections 集合工具的內部類,通過傳入 Map 封裝出一個 SynchronizedMap 對象,內部定義了一個對象鎖,方法內通過對象鎖實現;
ConcurrentHashMap 在jdk1.7中使用分段鎖,在jdk1.8中使用CAS+synchronized。
14. select 和 epoll的區別 14.1 IO多路復用之select

應用進程通過調用select函數,可以同時監控多個fd,在select函數監控的fd中,只要有任何一個數據狀態準備就緒了,select函數就會返回可讀狀態,這時應用進程再發起recvfrom請求去讀取數據。

非阻塞IO模型(NIO)中,需要N(N>=1)次輪詢系統調用,然而藉助select的IO多路復用模型,只需要發起一次詢問就夠了,大大優化了性能。

但是呢,select有幾個缺點:

監聽的IO最大連接數有限,在Linux系統上一般為1024。
select函數返回後,是通過遍歷fdset,找到就緒的描述符fd。(僅知道有I/O事件發生,卻不知是哪幾個流,所以遍歷所有流)
因為存在連接數限制,所以後來又提出了poll。與select相比,poll解決了連接數限制問題。但是呢,select和poll一樣,還是需要通過遍歷文件描述符來獲取已經就緒的socket。如果同時連接的大量客戶端,在一時刻可能只有極少處於就緒狀態,伴隨着監視的描述符數量的增長,效率也會線性下降。
14.2 IO多路復用之epoll

為了解決select/poll存在的問題,多路復用模型epoll誕生,它採用事件驅動來實現,流程圖如下:

epoll先通過epoll_ctl()來註冊一個fd(文件描述符),一旦基於某個fd就緒時,內核會採用回調機制,迅速激活這個fd,當進程調用epoll_wait()時便得到通知。這裡去掉了遍歷文件描述符的坑爹操作,而是採用監聽事件回調的機制。這就是epoll的亮點。

一下select、poll、epoll的區別

selectpollepoll底層數據結構數組鍊表紅黑樹和雙鍊表獲取就緒的fd遍歷遍歷事件回調事件複雜度O(n)O(n)O(1)最大連接數1024無限制無限制fd數據拷貝每次調用select,需要將fd數據從用戶空間拷貝到內核空間每次調用poll,需要將fd數據從用戶空間拷貝到內核空間使用內存映射(mmap),不需要從用戶空間頻繁拷貝fd數據到內核空間

大家可以看我這篇文章哈:看一遍就理解:IO模型詳解

15. http與https的區別,https的原理,如何加密的?

http與https的區別

思路: 這道題實際上考察的知識點是HTTP與HTTPS的區別,這個知識點非常重要,可以從安全性、數據是否加密、默認端口等這幾個方面去回答哈。其實,當你理解HTTPS的整個流程,就可以很好回答這個問題啦。

HTTP,即超文本傳輸協議,是一個基於TCP/IP通信協議來傳遞明文數據的協議。HTTP會存在這幾個問題:

請求信息是明文傳輸,容易被竊聽截取。
沒有驗證對方身份,存在被冒充的風險
數據的完整性未校驗,容易被中間人篡改

為了解決Http存在的問題,Https出現啦。

HTTPS= HTTP+SSL/TLS,可以理解Https是身披SSL(Secure Socket Layer,安全套接層)的HTTP。

HTTP + HTTPS的區別

https的原理,如何加密的

客戶端發起Https請求,連接到服務器的443端口。
服務器必須要有一套數字證書(證書內容有公鑰、證書頒發機構、失效日期等)。
服務器將自己的數字證書發送給客戶端(公鑰在證書裡面,私鑰由服務器持有)。
客戶端收到數字證書之後,會驗證證書的合法性。如果證書驗證通過,就會生成一個隨機的對稱密鑰,用證書的公鑰加密。
客戶端將公鑰加密後的密鑰發送到服務器。
服務器接收到客戶端發來的密文密鑰之後,用自己之前保留的私鑰對其進行非對稱解密,解密之後就得到客戶端的密鑰,然後用客戶端密鑰對返回數據進行對稱加密,醬紫傳輸的數據都是密文啦。
服務器將加密後的密文返回到客戶端。
客戶端收到後,用自己的密鑰對其進行對稱解密,得到服務器返回的數據。
16. Raft算法原理

Raft 算法是分布式系統開發首選的共識算法,它通過「一切以領導者為準」的方式,實現一系列值的共識和各節點日誌的一致。Raft 算法一共涉及三種角色(Follower、Candidate、Leader)和兩個過程(Leader選舉和日誌複製)。

16.1 Raft 角色

跟隨者(Follower):,默默地接收和處理來自Leader的消息,當等待Leader心跳信息超時的時候,就主動站出來,推薦自己當候選人(Candidate)。

候選人(Candidate):向其他節點發送投票請求,通知其他節點來投票,如果贏得了大多數(N/2+1)選票,就晉升領導(Leader)。

領導者(Leader):負責處理客戶端請求,進行日誌複製等操作,每一輪選舉的目標就是選出一個領導者;領導者會不斷地發送心跳信息,通知其他節點「我是領導者,我還活着,你們不要發起新的選舉,不用找個新領導者來替代我。」

16.2 領導選舉過程

1.在初始時,集群中所有的節點都是Follower狀態,都被設定一個隨機選舉超時時間(一般150ms-300ms):

2. 如果Follower在規定的超時時間,都沒有收到來自Leader的心跳,它就發起選舉:將自己的狀態切為 Candidate,增加自己的任期編號,然後向集群中的其它Follower節點發送請求,詢問其是否選舉自己成為Leader:

其他節點收到候選人A的請求投票消息後,如果在編號為1的這屆任期內還沒有進行過投票,那麼它將把選票投給節點A,並增加自己的任期編號:
當收到來自集群中過半節點的接受投票後,A節點即成為本屆任期內 Leader,他將周期性地發送心跳消息,通知其他節點我是Leader,阻止Follower發起新的選舉:
16.2 日誌複製

當有了leader,系統可以對外工作期啦。客戶端的一切請求來發送到leader,leader來調度這些並發請求的順序,並且保證leader與followers狀態的一致性。Leader接收到來自客戶端寫請求後,處理寫請求的過程其實就是一個日誌複製的過程。

日誌項長什麼樣呢?如下圖:

請求完整過程:

當系統leader收到一個來自客戶端的寫請求,就會添加一個log entry(日誌項)到本地日誌。
Leader通過日誌複製(AppendEntries)RPC 消息,將日誌項並行複製到集群其它Follower節點。
如果Leader接收到大多數的「複製成功」響應後,它將日誌項應用到自己的狀態機,並返回成功給客戶端。相反沒有收到大多數的「複製成功」響應,那麼就返回錯誤給客戶端;
當Follower接收到心跳信息,或者新的AppendEntries消息後,如果發現Leader已經提交了某條日誌項,而自己還沒應用,那麼Follower就會將這條日誌項應用到本地的狀態機中。

Raft算法,Leader是通過強制Follower直接複製自己的日誌項,來處理不一致日誌,從而最終實現了集群各節點日誌的一致。

大家有興趣可以看這篇文章哈:分布式一致性:Raft算法原理[1](https://www.tpvlog.com/article/66)

17. 消息中間件如何做到高可用

消息中間件如何保證高可用呢?單機是沒有高可用可言的,高可用都是對集群來說的,一起看下kafka的高可用吧。

Kafka 的基礎集群架構,由多個broker組成,每個broker都是一個節點。當你創建一個topic時,它可以劃分為多個partition,而每個partition放一部分數據,分別存在於不同的 broker 上。也就是說,一個 topic 的數據,是分散放在多個機器上的,每個機器就放一部分數據。

有些夥伴可能有疑問,每個partition放一部分數據,如果對應的broker掛了,那這部分數據是不是就丟失了?那還談什麼高可用呢?

Kafka 0.8 之後,提供了複製品副本機制來保證高可用,即每個 partition 的數據都會同步到其它機器上,形成多個副本。然後所有的副本會選舉一個 leader 出來,讓leader去跟生產和消費者打交道,其他副本都是follower。寫數據時,leader 負責把數據同步給所有的follower,讀消息時, 直接讀 leader 上的數據即可。如何保證高可用的?就是假設某個 broker 宕機,這個broker上的partition 在其他機器上都有副本的。如果掛的是leader的broker呢?其他follower會重新選一個leader出來。

18. 消息隊列怎麼保證不丟消息的

一個消息從生產者產生,到被消費者消費,主要經過這3個過程:

因此如何保證MQ不丟失消息,可以從這三個階段闡述:

生產者保證不丟消息
存儲端不丟消息
消費者不丟消息
18.1 生產者保證不丟消息

生產端如何保證不丟消息呢?確保生產的消息能到達存儲端。

如果是RocketMQ消息中間件,Producer生產者提供了三種發送消息的方式,分別是:

同步發送
異步發送
單向發送

生產者要想發消息時保證消息不丟失,可以:

採用同步方式發送,send消息方法返回成功狀態,就表示消息正常到達了存儲端Broker。
如果send消息異常或者返回非成功狀態,可以重試。
可以使用事務消息,RocketMQ的事務消息機制就是為了保證零丟失來設計的
18.2 存儲端不丟消息

如何保證存儲端的消息不丟失呢?確保消息持久化到磁盤。大家很容易想到就是刷盤機制。

刷盤機制分同步刷盤和異步刷盤:

生產者消息發過來時,只有持久化到磁盤,RocketMQ的存儲端Broker才返回一個成功的ACK響應,這就是同步刷盤。它保證消息不丟失,但是影響了性能。
異步刷盤的話,只要消息寫入PageCache緩存,就返回一個成功的ACK響應。這樣提高了MQ的性能,但是如果這時候機器斷電了,就會丟失消息。

Broker一般是集群部署的,有master主節點和slave從節點。消息到Broker存儲端,只有主節點和從節點都寫入成功,才反饋成功的ack給生產者。這就是同步複製,它保證了消息不丟失,但是降低了系統的吞吐量。與之對應的就是異步複製,只要消息寫入主節點成功,就返回成功的ack,它速度快,但是會有性能問題。

18.3 消費階段不丟消息

消費者執行完業務邏輯,再反饋會Broker說消費成功,這樣才可以保證消費階段不丟消息。

19. Redis如何保證高可用?聊聊Redis的哨兵機制

主從模式中,一旦主節點由於故障不能提供服務,需要人工將從節點晉升為主節點,同時還要通知應用方更新主節點地址。顯然,多數業務場景都不能接受這種故障處理方式。Redis從2.8開始正式提供了Redis Sentinel(哨兵)架構來解決這個問題。

哨兵模式,由一個或多個Sentinel實例組成的Sentinel系統,它可以監視所有的Redis主節點和從節點,並在被監視的主節點進入下線狀態時,自動將下線主服務器屬下的某個從節點升級為新的主節點。但是呢,一個哨兵進程對Redis節點進行監控,就可能會出現問題(單點問題),因此,可以使用多個哨兵來進行監控Redis節點,並且各個哨兵之間還會進行監控。

簡單來說,哨兵模式就三個作用:

發送命令,等待Redis服務器(包括主服務器和從服務器)返回監控其運行狀態;
哨兵監測到主節點宕機,會自動將從節點切換成主節點,然後通過發布訂閱模式通知其他的從節點,修改配置文件,讓它們切換主機;
哨兵之間還會相互監控,從而達到高可用。

故障切換的過程是怎樣的呢

假設主服務器宕機,哨兵1先檢測到這個結果,系統並不會馬上進行 failover 過程,僅僅是哨兵1主觀的認為主服務器不可用,這個現象成為主觀下線。當後面的哨兵也檢測到主服務器不可用,並且數量達到一定值時,那麼哨兵之間就會進行一次投票,投票的結果由一個哨兵發起,進行 failover 操作。切換成功後,就會通過發布訂閱模式,讓各個哨兵把自己監控的從服務器實現切換主機,這個過程稱為客觀下線。這樣對於客戶端而言,一切都是透明的。

哨兵的工作模式如下:

每個Sentinel以每秒鐘一次的頻率向它所知的Master,Slave以及其他Sentinel實例發送一個 PING命令。
如果一個實例(instance)距離最後一次有效回復 PING 命令的時間超過 down-after-milliseconds 選項所指定的值, 則這個實例會被 Sentinel標記為主觀下線。
如果一個Master被標記為主觀下線,則正在監視這個Master的所有 Sentinel 要以每秒一次的頻率確認Master的確進入了主觀下線狀態。
當有足夠數量的 Sentinel(大於等於配置文件指定的值)在指定的時間範圍內確認Master的確進入了主觀下線狀態, 則Master會被標記為客觀下線。
在一般情況下, 每個 Sentinel 會以每10秒一次的頻率向它已知的所有Master,Slave發送 INFO 命令。
當Master被 Sentinel 標記為客觀下線時,Sentinel 向下線的 Master 的所有 Slave 發送 INFO 命令的頻率會從 10 秒一次改為每秒一次
若沒有足夠數量的 Sentinel同意Master已經下線, Master的客觀下線狀態就會被移除;若Master 重新向 Sentinel 的 PING 命令返回有效回復, Master 的主觀下線狀態就會被移除。
20. 無重複字符的最長子串

給定一個字符串 s ,請你找出其中不含有重複字符的 最長子串 的長度。

示例 1:

輸入:s="abcabcbb"輸出:3解釋:因為無重複字符的最長子串是"abc",所以其長度為 3。

示例 2:

輸入:s="bbbbb"輸出:1解釋:因為無重複字符的最長子串是"b",所以其長度為 1。

這道題可以使用滑動窗口來實現。滑動窗口就是維護一個窗口,不斷滑動,然後更新答案。

滑動窗口的大致邏輯框架,偽代碼如下:

intleft=0,right=0;while(right<s.size()){//增大窗口window.add(s[right]);right++;while(windowneedsshrink){//縮小窗口window.remove(s[left]);left++;}}

解法流程如下:

首先呢,就是獲取原字符串的長度。
接着維護一個窗口(數組、哈希、隊列)
窗口一步一步向右擴展
窗口在向右擴展滑動過程,需要判斷左邊是否需要縮減
最後比較更新答案

完整代碼如下:

intlengthOfLongestSubstring(Strings){//獲取原字符串的長度intlen=s.length();//維護一個哈希集合的窗口Set<Character>windows=newHashSet<>();intleft=0,right=0;intres=0;while(right<len){charc=s.charAt(right);//窗口右移right++;//判斷是否左邊窗口需要縮減,如果已經包含,那就需要縮減while(windows.contains(c)){windows.remove(s.charAt(left));left++;}windows.add(c);//比較更新答案res=Math.max(res,windows.size());}returnres;}參考與感謝
分布式理論之分布式一致性:Raft算法原理[2]
一文搞懂Raft算法[3]
參考資料
[1]

分布式一致性:Raft算法原理: https://www.tpvlog.com/article/66

[2]

分布式理論之分布式一致性:Raft算法原理: https://www.tpvlog.com/article/66

[3]

一文搞懂Raft算法: https://www.cnblogs.com/xybaby/p/10124083.html


- EOF -

推薦閱讀點擊標題可跳轉

Java不支持協程?那是你不知道Quasar!

JDK動態代理為什麼必須要基於接口?

List 如何根據對象的屬性去重?Java 8 輕鬆搞定!

看完本文有收穫?請轉發分享給更多人

關注「ImportNew」,提升Java技能

點讚和在看就是最大的支持❤️

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 鑽石舞台 的頭像
    鑽石舞台

    鑽石舞台

    鑽石舞台 發表在 痞客邦 留言(0) 人氣()