阿里巴巴一面 :十道经典面试题解析
1. 用到分布式事務嘛?為什么用這種方案,有其他方案嘛?
什么是分布式事務
談到事務,我們就會想到數據庫事務,很容易就想到原子性、一致性、持久性、隔離性。
分布式事務跟數據庫事務有點不一樣,它是指事務的參與者、支持事務的服務器、資源服務器以及事務管理器分別位于不同的分布式系統的不同節點之上。簡單來說,分布式事務指的就是分布式系統中的事務,它的存在就是為了保證不同數據庫節點的數據一致性。
分布式事務基礎
分布式事務需要需要知道CAP理論和BASE理論。
CAP理論
- 一致性(C:Consistency):一致性是指數據在多個副本之間能否保持一致的特性。例如一個數據在某個分區節點更新之后,在其他分區節點讀出來的數據也是更新之后的數據。
- 可用性(A:Availability):可用性是指系統提供的服務必須一直處于可用的狀態,對于用戶的每一個操作請求總是能夠在有限的時間內返回結果。這里的重點是"有限時間內"和"返回結果"。
- 分區容錯性(P:Partition tolerance):分布式系統在遇到任何網絡分區故障的時候,仍然需要能夠保證對外提供滿足一致性和可用性的服務。
一個分布式系統中,CAP理論它只能同時滿足(一致性、可用性、分區容錯性)中的兩點。
BASE 理論
BASE 理論, 是對CAP中AP的一個擴展,對于我們的業務系統,我們考慮犧牲一致性來換取系統的可用性和分區容錯性。BASE是Basically Available(基本可用),Soft state(軟狀態),和 Eventually consistent(最終一致性)三個短語的縮寫。
- 基本可用是指,通過支持局部故障而不是系統全局故障來實現的;
- Soft State表示狀態可以有一段時間不同步;
- 最終一致,最終數據是一致的就可以了,而不是實時保持強一致。
分布式事務的幾種解決方案
- 2PC(二階段提交)方案,事務的提交分為兩個階段:準備階段和提交執行方案。
- TCC(即Try、Confirm、Cancel),它采用了補償機制,核心思想是:針對每個操作,都要注冊一個與其對應的確認和補償(撤銷)操作。
- 本地消息表,它的核心思想就是將分布式事務拆分成本地事務進行處理。
- 最大努力通知,實現最大努力通知,可以采用MQ的ack機制。
- Saga事務,它的核心思想是將長事務拆分為多個本地短事務,由Saga事務協調器協調,如果正常結束那就正常完成,如果某個步驟失敗,則根據相反順序一次調用補償操作。
業界目前使用本地消息表這種方案是比較多的,它的核心思想就是將分布式事務拆分成本地事務進行處理。可以看一下基本的實現流程圖吧:
對于消息發送方:
- 首先需要有一個消息表,記錄著消息狀態相關信息。
- 業務數據和消息表在同一個數據庫,即要保證它倆在同一個本地事務。
- 在本地事務中處理完業務數據和寫消息表操作后,通過寫消息到MQ消息隊列。
- 消息會發到消息消費方,如果發送失敗,即進行重試。
消息消費方:
- 處理消息隊列中的消息,完成自己的業務邏輯。
- 此時如果本地事務處理成功,則表明已經處理成功了。
- 如果本地事務處理失敗,那么就會重試執行。
- 如果是業務上面的失敗,給消息生產方發送一個業務補償消息,通知進行回滾等操作。
生產方和消費方定時掃描本地消息表,把還沒處理完成的消息或者失敗的消息再發送一遍。如果有靠譜的自動對賬補賬邏輯,這種方案還是非常實用的。
2.JDK6、7、8分別提供了哪些新特性
JDK 6 新特性
- Desktop類(它允許一個Java應用程序啟動本地的另一個應用程序去處理URI或文件請求)
- 使用JAXB2來實現對象與XML之間的映射
- 輕量級 Http Server API
- 插入式注解處理API(lombok框架基于這個特性實現)
- STAX(是JDK6中一種處理XML文檔的API)
JDK 7的新特性
- switch 支持String字符串類型
- try-with-resources,資源自動關閉
- 整數類型如(byte,short,int,long)能夠用二進制來表示
- 數字常量支持下劃線
- 泛型實例化類型自動推斷,即”<>”
- 一個catch中捕獲多個異常類型,用(|)分隔開
- 增強的文件系統
- Fork/join 框架
JDK8 的新特性
- lambada表達式
- 函數式接口
- 方法引用
- 默認方法
- Stream API
- Optional
- Date Time API(如LocalDate)
- 重復注解
- Base64
- JVM的新特性(如元空間Metaspace代替持久代)
3. https原理,工作流程
- HTTPS = HTTP + SSL/TLS,即用SSL/TLS對數據進行加密和解密,Http進行傳輸。
- SSL,即Secure Sockets Layer(安全套接層協議),是網絡通信提供安全及數據完整性的一種安全協議。
- TLS,即Transport Layer Security(安全傳輸層協議),它是SSL3.0的后續版本。
4. 講講java jmm volatile的實現原理
volatile關鍵字是Java虛擬機提供的的最輕量級的同步機制,它作為一個修飾符,用來修飾變量。它保證變量對所有線程可見性,禁止指令重排,但是不保證原子性。
volatile是如何保證可見性的呢?我們先來看下java內存模型(jmm)
- Java虛擬機規范試圖定義一種Java內存模型,來屏蔽掉各種硬件和操作系統的內存訪問差異,以實現讓Java程序在各種平臺上都能達到一致的內存訪問效果。
- 為了更好的執行性能,java內存模型并沒有限制執行引擎使用處理器的特定寄存器或緩存來和主內存打交道,也沒有限制編譯器進行調整代碼順序優化。所以Java內存模型會存在緩存一致性問題和指令重排序問題的。
- Java內存模型規定所有的變量都是存在主內存當中,每個線程都有自己的工作內存。這里的變量包括實例變量和靜態變量,但是不包括局部變量,因為局部變量是線程私有的。
- 線程的工作內存保存了被該線程使用的變量的主內存副本,線程對變量的所有操作都必須在工作內存中進行,而不能直接操作操作主內存。并且每個線程不能訪問其他線程的工作內存。
volatile變量,保證新值能立即同步回主內存,以及每次使用前立即從主內存刷新,所以我們說volatile保證了多線程操作變量的可見性。
指令重排是指在程序執行過程中,為了提高性能, 編譯器和CPU可能會對指令進行重新排序。volatile是如何禁止指令重排的?在Java語言中,有一個先行發生原則(happens-before)
- 程序次序規則:在一個線程內,按照控制流順序,書寫在前面的操作先行發生于書寫在后面的操作。
- 管程鎖定規則:一個unLock操作先行發生于后面對同一個鎖額lock操作
- volatile變量規則:對一個變量的寫操作先行發生于后面對這個變量的讀操作
- 線程啟動規則:Thread對象的start()方法先行發生于此線程的每個一個動作
- 線程終止規則:線程中所有的操作都先行發生于線程的終止檢測,我們可以通過Thread.join()方法結束、Thread.isAlive()的返回值手段檢測到線程已經終止執行
- 線程中斷規則:對線程interrupt()方法的調用先行發生于被中斷線程的代碼檢測到中斷事件的發生
- 對象終結規則:一個對象的初始化完成先行發生于他的finalize()方法的開始
- 傳遞性:如果操作A先行發生于操作B,而操作B又先行發生于操作C,則可以得出操作A先行發生于操作C
實際上volatile保證可見性和禁止指令重排都跟內存屏障有關。我們來看一段volatile使用的demo代碼
public class Singleton { private volatile static Singleton instance; private Singleton (){} public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } } 復制代碼編譯后,對比有volatile關鍵字和沒有volatile關鍵字時所生成的匯編代碼,發現有volatile關鍵字修飾時,會多出一個lock addl $0x0,(%esp),即多出一個lock前綴指令,lock指令相當于一個「內存屏障」
lock指令相當于一個內存屏障,它保證以下這幾點:
- 1.重排序時不能把后面的指令重排序到內存屏障之前的位置
- 2.將本處理器的緩存寫入內存
- 3.如果是寫入動作,會導致其他處理器中對應的緩存無效。
第2點和第3點就是保證volatile保證可見性的體現嘛,第1點就是禁止指令重排列的體現。內存屏障又是什么呢?
內存屏障四大分類:(Load 代表讀取指令,Store代表寫入指令)
| LoadLoad屏障 | Load1; LoadLoad; Load2 | 在Load2要讀取的數據被訪問前,保證Load1要讀取的數據被讀取完畢。 |
| StoreStore屏障 | Store1; StoreStore; Store2 | 在Store2寫入執行前,保證Store1的寫入操作對其它處理器可見 |
| LoadStore屏障 | Load1; LoadStore; Store2 | 在Store2被寫入前,保證Load1要讀取的數據被讀取完畢。 |
| StoreLoad屏障 | Store1; StoreLoad; Load2 | 在Load2讀取操作執行前,保證Store1的寫入對所有處理器可見。 |
為了實現volatile的內存語義,Java內存模型采取以下的保守策略
- 在每個volatile寫操作的前面插入一個StoreStore屏障。
- 在每個volatile寫操作的后面插入一個StoreLoad屏障。
- 在每個volatile讀操作的后面插入一個LoadLoad屏障。
- 在每個volatile讀操作的后面插入一個LoadStore屏障。
有些小伙伴,可能對這個還是有點疑惑,內存屏障這玩意太抽象了。我們照著代碼看下吧:
內存屏障保證前面的指令先執行,所以這就保證了禁止了指令重排啦,同時內存屏障保證緩存寫入內存和其他處理器緩存失效,這也就保證了可見性,哈哈~
5. 講一講7層網絡模型,tcp的為什么要三次握手
計算機網路體系結構有三層:OSI七層模型、TCP/IP四層模型、五層體系結構,如圖:
七層模型,亦稱OSI(Open System Interconnection),國際標準化組織(International Organization for Standardization)制定的一個用于計算機或通信系統間互聯的標準體系。
- 應用層:網絡服務與最終用戶的一個接口,常見的協議有:HTTP FTP SMTP SNMP DNS.
- 表示層:數據的表示、安全、壓縮。,確保一個系統的應用層所發送的信息可以被另一個系統的應用層讀取。
- 會話層:建立、管理、終止會話,對應主機進程,指本地主機與遠程主機正在進行的會話.
- 傳輸層:定義傳輸數據的協議端口號,以及流控和差錯校驗,協議有TCP UDP.
- 網絡層:進行邏輯地址尋址,實現不同網絡之間的路徑選擇,協議有ICMP IGMP IP等.
- 數據鏈路層:在物理層提供比特流服務的基礎上,建立相鄰結點之間的數據鏈路。
- 物理層:建立、維護、斷開物理連接。
6.說說線程池的工作原理
面試官如果要我們講下線程池工作原理的話,大家講下以下這個流程圖就可以啦:
為了形象描述線程池執行,加深大家的理解,我打個比喻:
- 核心線程比作公司正式員工
- 非核心線程比作外包員工
- 阻塞隊列比作需求池
- 提交任務比作提需求
- 當產品提個需求,正式員工(核心線程)先接需求(執行任務)
- 如果正式員工都有需求在做,即核心線程數已滿),產品就把需求先放需求池(阻塞隊列)。
- 如果需求池(阻塞隊列)也滿了,但是這時候產品繼續提需求,怎么辦呢?那就請外包(非核心線程)來做。
- 如果所有員工(最大線程數也滿了)都有需求在做了,那就執行拒絕策略。
- 如果外包員工把需求做完了,它經過一段(keepAliveTime)空閑時間,就離開公司了。
7.你們數據庫的高可用是怎么實現的?
高可用,即High Availability,是分布式系統架構設計中必須考慮的因素之一,它通常是指,通過設計減少系統不能提供服務的時間。單機部署談不上高可用,因為單點故障問題。高可用都是多個節點的,我們在考慮MySQL數據庫的高可用的架構時,需要考慮這幾個方面:
- 如果數據庫節點宕機,需要盡快回復,保證業務不受宕機影響。
- 從數據庫節點的數據,盡可能跟主節點數據實時保持一致,至少保證最終一致性。
- 數據庫節點切換時,數據不能缺失。
7.1 主從或主主半同步復制
用雙節點數據庫,搭建單向或者雙向的半同步復制。架構如下:
通常會和proxy、keepalived等第三方軟件同時使用,即可以用來監控數據庫的健康,又可以執行一系列管理命令。如果主庫發生故障,切換到備庫后仍然可以繼續使用數據庫。
這種方案優點是架構、部署比較簡單,主機宕機直接切換即可。缺點是完全依賴于半同步復制,半同步復制退化為異步復制,無法保證數據一致性;另外,還需要額外考慮haproxy、keepalived的高可用機制。
7.2 半同步復制優化
半同步復制機制是可靠的,可以保證數據一致性的。但是如果網絡發生波動,半同步復制發生超時會切換為異步復制,異復制是無法保證數據的一致性的。因此,可以在半同復制的基礎上優化一下,盡可能保證半同復制。如雙通道復制方案
- 優點:這種方案架構、部署也比較簡單,主機宕機也是直接切換即可。比方案1的半同步復制,更能保證數據的一致性。
- 缺點:需要修改內核源碼或者使用mysql通信協議,沒有從根本上解決數據一致性問題。
7.3 高可用架構優化
保證高可用,可以把主從雙節點數據庫擴展為數據庫集群。Zookeeper可以作為集群管理,它使用分布式算法保證集群數據的一致性,可以較好的避免網絡分區現象的產生。
- 優點:保證了整個系統的高可用性,擴展性也較好,可以擴展為大規模集群。
- 缺點:數據一致性仍然依賴于原生的mysql半同步復制;引入Zookeeper使系統邏輯更復雜。
7.4 共享存儲
共享存儲實現了數據庫服務器和存儲設備的解耦,不同數據庫之間的數據同步不再依賴于MySQL的原生復制功能,而是通過磁盤數據同步的手段,來保證數據的一致性。
DRBD磁盤復制
DRBD是一個用軟件實現的、無共享的、服務器之間鏡像塊設備內容的存儲復制解決方案。主要用于對服務器之間的磁盤、分區、邏輯卷等進行數據鏡像,當用戶將數據寫入本地磁盤時,還會將數據發送到網絡中另一臺主機的磁盤上,這樣的本地主機(主節點)與遠程主機(備節點)的數據就可以保證實時同步。常用架構如下:
當本地主機出現問題,遠程主機上還保留著一份相同的數據,即可以繼續使用,保證了數據的安全。
- 優點:部署簡單,價格合適,保證數據的強一致性
- 缺點:對IO性能影響較大,從庫不提供讀操作
7.5 分布式協議
分布式協議可以很好解決數據一致性問題。常見的部署方案就是MySQL cluster,它是官方集群的部署方案,通過使用NDB存儲引擎實時備份冗余數據,實現數據庫的高可用性和數據一致性。如下:
- 優點:不依賴于第三方軟件,可以實現數據的強一致性;
- 缺點:配置較復雜;需要使用NDB儲存引擎;至少三節點;
8. 讀寫分離的場景下,怎么保證從數據庫讀到最新的數據?
數據庫讀寫分離,主要解決高并發時,提高系統的吞吐量。來看下讀寫分離數據庫模型:
- 寫請求是直接寫主庫,然后同步數據到從庫
- 讀請求一般直接讀從庫,除飛強制讀主庫
在高并發場景或者網絡不佳的場景,如果存在較大的主從同步數據延遲,這時候讀請求去讀從庫,就會讀到舊數據。這時候最簡單暴力的方法,就是強制讀主庫。實際上可以使用緩存標記法。
- A發起寫請求,更新主庫數據,并在緩存中設置一個標記,表示數據已更新,標記格式為:userId+業務Id。
- 設置此標記,設置過期時間(估值為主庫和從庫同步延遲的時間)
- B發起讀請求,先判斷此請求,在緩存中有沒有更新標記。
- 如果存在標記,走主庫;如果沒有,請求走從庫。
這個方案,解決了數據不一致問題,但是每次請求都要先跟緩存打交道,會影響系統吞吐。
9. 如何保證MySQL數據不丟?
MySQL這種關系型數據庫,是日志先行策略(Write-Ahead Logging),只要binlog和redo log日志能保證持久化到磁盤,我們就能確保MySQL異常重啟后,數據不丟失。
binlog日志
binlog,又稱為二進制日志,它會記錄數據庫執行更改的所有操作,但是不包括查詢select等操作。一般用于恢復、復制等功能。它的格式有三種:statement、mixed和row。
- statement:每一條會修改數據的sql都會記錄到binlog中,不建議使用。
- row:基于行的變更情況記錄,會記錄行更改前后的內容,推薦使用。
- mixed:混合statement和row兩個模式,不建議使用。
binlog 的寫入機制是怎樣的呢?
事務執行過程中,先把日志寫到 binlog cache,事務提交的時候,再把binlog cache寫到binlog文件中 。
系統為每個客戶端線程分配一個binlog cache,其大小值控制參數是binlog_cache_size。如果binlog cache的值超過閥值,就會臨時持久化到磁盤。當事務提交的時候,再將 binlog cache中完整的事務持久化到磁盤中,并且清空binlog cache。
binlog寫文件分write和fsync兩個過程:
- write:指把日志寫到文件系統的page cache,并沒有把數據持久化到磁盤,因此速度較快。
- fsync,實際的寫盤操作,即把數據持久化到磁盤。
write和fsync的寫入時機,是由變量sync_binlog控制的:
如果IO出現性能瓶頸,可以將sync_binlog設置成一個較大的值。比如設置為(100~1000)。但是,會存在數據丟失的風險,當主機異常重啟時,會丟失N個最近提交的事務binlog。
redo log日志
redo log,又稱為重做日志文件,只記錄事務對數據頁做了哪些修改,它記錄的是數據修改之后的值。redo 有三種狀態
- 物理上是在MySQL進程內存中,存在redo log buffer中,
- 物理上在文件系統的page cache里,寫到磁盤 (write),但是還沒有持久化(fsync)。
- 存在hard disk,已經持久化到磁盤。
日志寫到redo log buffer是很快的;wirte到page cache也很快,但是持久化到磁盤的速度就慢多了。
為了控制redo log的寫入策略,Innodb根據innodb_flush_log_at_trx_commit參數不同的取值采用不同的策略,它有三種不同的取值:
- 設置為0時,表示每次事務提交時都只是把redo log留在redo log buffer 中 ;
- 設置為1時,表示每次事務提交時都將 redo log 直接持久化到磁盤;
- 設置為2時,表示每次事務提交時都只是把redo log 寫到page cache。
三種模式下,0的性能最好,但是不安全,MySQL進程一旦崩潰會導致丟失一秒的數據。1的安全性最高,但是對性能影響最大,2的話主要由操作系統自行控制刷磁盤的時間,如果僅僅是MySQL宕機,對數據不會產生影響,如果是主機異常宕機了,同樣會丟失數據。
10. 高并發下如何設計秒殺系統?
設計一個秒殺系統,需要考慮這些問題:
如何解決這些問題呢?
- 頁面靜態化
- 按鈕至灰控制
- 服務單一職責
- 秒殺鏈接加鹽
- 限流
- 分布式鎖
- MQ異步處理
- 限流&降級&熔斷
頁面靜態化
秒殺活動的頁面,大多數內容都是固定不變的,如商品名稱,商品圖片等等,可以對活動頁面做靜態化處理,減少訪問服務端的請求。秒殺用戶會分布在全國各地,有的在上海,有的在深圳,地域相差很遠,網速也各不相同。為了讓用戶最快訪問到活動頁面,可以使用CDN(Content Delivery Network,內容分發網絡)。CDN可以讓用戶就近獲取所需內容。
按鈕至灰控制
秒殺活動開始前,按鈕一般需要置灰的。只有時間到了,才能變得可以點擊。這是防止,秒殺用戶在時間快到的前幾秒,瘋狂請求服務器,然后秒殺時間點還沒到,服務器就自己掛了。
服務單一職責
我們都知道微服務設計思想,也就是把各個功能模塊拆分,功能那個類似的放一起,再用分布式的部署方式。
如用戶登錄相關的,就設計個用戶服務,訂單相關的就搞個訂單服務,再到禮物相關的就搞個禮物服務等等。那么,秒殺相關的業務邏輯也可以放到一起,搞個秒殺服務,單獨給它搞個秒殺數據庫。
服務單一職責有個好處:如果秒殺沒抗住高并發的壓力,秒殺庫崩了,服務掛了,也不會影響到系統的其他服務。
秒殺鏈接加鹽
鏈接如果明文暴露的話,會有人獲取到請求Url,提前秒殺了。因此,需要給秒殺鏈接加鹽。可以把URL動態化,如通過MD5加密算法加密隨機的字符串去做url。
限流
一般有兩種方式限流:nginx限流和redis限流。
- 為了防止某個用戶請求過于頻繁,我們可以對同一用戶限流;
- 為了防止黃牛模擬幾個用戶請求,我們可以對某個IP進行限流;
- 為了防止有人使用代理,每次請求都更換IP請求,我們可以對接口進行限流。
- 為了防止瞬時過大的流量壓垮系統,還可以使用阿里的Sentinel、Hystrix組件進行限流。
分布式鎖
可以使用redis分布式鎖解決超賣問題。
使用Redis的SET EX PX NX + 校驗唯一隨機值,再刪除釋放鎖。
if(jedis.set(key_resource_id, uni_request_id, "NX", "EX", 100s) == 1){ //加鎖try {do something //業務處理}catch(){}finally {//判斷是不是當前線程加的鎖,是才釋放if (uni_request_id.equals(jedis.get(key_resource_id))) {jedis.del(lockKey); //釋放鎖}} } 復制代碼在這里,判斷是不是當前線程加的鎖和釋放鎖不是一個原子操作。如果調用jedis.del()釋放鎖的時候,可能這把鎖已經不屬于當前客戶端,會解除他人加的鎖。
為了更嚴謹,一般也是用lua腳本代替。lua腳本如下:
if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) elsereturn 0 end; 復制代碼MQ異步處理
如果瞬間流量特別大,可以使用消息隊列削峰,異步處理。用戶請求過來的時候,先放到消息隊列,再拿出來消費。
限流&降級&熔斷
- 限流,就是限制請求,防止過大的請求壓垮服務器;
- 降級,就是秒殺服務有問題了,就降級處理,不要影響別的服務;
- 熔斷,服務有問題就熔斷,一般熔斷降級是一起出現。
更多大廠面試資料及答案可關注下方公公眾號【不脫發有志青年】即可免費獲取
?
總結
以上是生活随笔為你收集整理的阿里巴巴一面 :十道经典面试题解析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: .Net Core 为 x86 和 x6
- 下一篇: 历年阿里面试题汇总