1.你項目中用到了spring cloud,請問你用了哪些spring cloud組件?
答:服務發現——Netflix Eureka,客服端負載均衡——Netflix Ribbon,
斷路器——Netflix Hystrix,服務網關——Netflix Zuul,分佈式配置——Spring Cloud Config
2.談一談斷路器Hystrix的原理
答:Hystrix 是一個幫助解決分佈式系統交互時超時處理和容錯的類庫, 它同樣擁有保護系統的能力。Netflix的衆多開源項目之一。
a. 隔離:
Hystrix隔離方式採用線程/信號的方式,通過隔離限制依賴的併發量和阻塞擴散
1)線程隔離
Hystrix在用戶請求和服務之間加入了線程池。
Hystrix爲每個依賴調用分配一個小的線程池,如果線程池已滿調用將被立即拒絕,默認不採用排隊.加速失敗判定時間。線程數是可以被設定的。
原理:用戶的請求將不再直接訪問服務,而是通過線程池中的空閒線程來訪問服務,如果線程池已滿,則會進行降級處理,用戶的請求不會被阻塞,至少可以看到一個執行結果(例如返回友好的提示信息),而不是無休止的等待或者看到系統崩潰。
隔離前:
隔離後:
2)信號隔離:
信號隔離也可以用於限制併發訪問,防止阻塞擴散, 與線程隔離最大不同在於執行依賴代碼的線程依然是請求線程(該線程需要通過信號申請, 如果客戶端是可信的且可以快速返回,可以使用信號隔離替換線程隔離,降低開銷。信號量的大小可以動態調整, 線程池大小不可以。
b. 熔斷:
如果某個目標服務調用慢或者有大量超時,此時,熔斷該服務的調用,對於後續調用請求,不在繼續調用目標服務,直接返回,快速釋放資源。如果目標服務情況好轉則恢復調用。
熔斷器:Circuit Breaker
熔斷器是位於線程池之前的組件。用戶請求某一服務之後,Hystrix會先經過熔斷器,此時如果熔斷器的狀態是打開(跳起),則說明已經熔斷,這時將直接進行降級處理,不會繼續將請求發到線程池。熔斷器相當於在線程池之前的一層屏障。每個熔斷器默認維護10個bucket ,每秒創建一個bucket ,每個blucket記錄成功,失敗,超時,拒絕的次數。當有新的bucket被創建時,最舊的bucket會被拋棄。
熔斷器的狀態機:
- Closed:熔斷器關閉狀態,調用失敗次數積累,到了閾值(或一定比例)則啓動熔斷機制;
- Open:熔斷器打開狀態,此時對下游的調用都內部直接返回錯誤,不走網絡,但設計了一個時鐘選項,默認的時鐘達到了一定時間(這個時間一般設置成平均故障處理時間,也就是MTTR),到了這個時間,進入半熔斷狀態;
- Half-Open:半熔斷狀態,允許定量的服務請求,如果調用都成功(或一定比例)則認爲恢復了,關閉熔斷器,否則認爲還沒好,又回到熔斷器打開狀態;
3.說一說hashmap的擴容機制,hashmap不是線程安全的,一寫多讀會怎麼樣?
答:hashmap的擴容需要滿足兩個條件:當前數據存儲的數量(即size())大小必須大於等於閾值;當前加入的數據是否發生了hash衝突。
因爲上面這兩個條件,所以存在下面這些情況
(1)、就是hashmap在存值的時候(默認大小爲16,負載因子0.75,閾值12),可能達到最後存滿16個值的時候,再存入第17個值纔會發生擴容現象,因爲前16個值,每個值在底層數組中分別佔據一個位置,並沒有發生hash碰撞。
(2)、當然也有可能存儲更多值(超多16個值,最多可以存26個值)都還沒有擴容。原理:前11個值全部hash碰撞,存到數組的同一個位置(這時元素個數小於閾值12,不會擴容),後面所有存入的15個值全部分散到數組剩下的15個位置(這時元素個數大於等於閾值,但是每次存入的元素並沒有發生hash碰撞,所以不會擴容),前面11+15=26,所以在存入第27個值的時候才同時滿足上面兩個條件,這時候纔會發生擴容現象。
(3)讀寫不一致也就會產生線程安全問題,所以hashmap一寫多讀也不是線程安全的。
4.你項目中用到了redis,說一說redis有哪些結構?
答:字符串,哈希,列表,集合,有序集合
5.redis中的存儲的字符串value值有啥限制
答:最大不能超過512M。
6.談一談redis的list結構
答:列表對象的編碼可以是ziplist和linkedlist之一。
(1) ziplist編碼
ziplist編碼的哈希隨想底層實現是壓縮列表,每個壓縮裏列表節點保存了一個列表元素。
(2)linkedlist編碼
linkedlist編碼底層採用雙端鏈表實現,每個雙端鏈表節點都保存了一個字符串對象,在每個字符串對象內保存了一個列表元素。
列表對象編碼轉換:
-
- 列表對象使用ziplist編碼需要滿足兩個條件:一是所有字符串長度都小於64字節,二是元素數量小於512,不滿足任意一個都會使用linkedlist編碼。
- 兩個條件的數字可以在Redis的配置文件中修改,list-max-ziplist-value選項和list-max-ziplist-entries選項。
- 圖中StringObject就是上一節講到的字符串對象,字符串對象是唯一個在五大對象中作爲嵌套對象使用的
7.你項目經歷還寫了用到了k8s,談一談你對k8s的認識?
答:Kubernetes,它可以幫助用戶省去應用容器化過程的許多手動部署和擴展操作。也就是說,您可以將運行 Linux 容器的多組主機聚集在一起,由 Kubernetes 幫助您輕鬆高效地管理這些集羣。而且,這些集羣可跨公共雲、私有云或混合雲部署主機。因此,對於要求快速擴展的雲原生應用而言(例如藉助 Apache Kafka 進行的實時數據流處理)Kubernetes 是理想的託管平臺。。真正的生產型應用會涉及多個容器。這些容器必須跨多個服務器主機進行部署。Kubernetes 可以提供所需的編排和管理功能,以便您針對這些工作負載大規模部署容器。藉助 Kubernetes 編排功能,您可以構建跨多個容器的應用服務、跨集羣調度、擴展這些容器,並長期持續管理這些容器的健康狀況。然後巴拉巴拉說了一些七雜八雜的。
8.談一談K8S中的node和pod?
除了Master,Kubernetes集羣中的其他機器被稱爲Node節點,在較早的版本中也被稱爲Minion。與Master一樣,Node節點可以是一臺物理主機,也可以是一臺虛擬機。Node節點纔是Kubernetes集羣中的工作負載節點,每個Node都會被Master分配一些工作負載(Docker容器),當某個Node宕機時,其上的工作負載會被Master自動轉移到其他節點上去。
每個Node節點上都運行着以下一組關鍵進程:
kubelet:負責Pod對應的容器的創建、啓停等任務,同時與Master節點密切協作,實現集羣管理的基本功能。
kube-proxy:實現Kubernetes Service的通信與負載均衡機制的重要組件。
Docker Engine (docker):Docker引擎,負責本機的容器創建和管理工作。
Node節點可以在運行期間動態增加到Kubernetes集羣中,前提是這個節點上已經正確安裝、配置和啓動了上述關鍵進程,在默認情況下kubelet會向Master註冊自己,這也是Kubernetes推薦的Node管理方式。一旦Node被納入集羣管理範圍,kubelet進程就會定時向Master節點彙報自身的情報,例如操作系統、Docker版本、機器的CPU和內存情況,以及當前有哪些Pod在運行等,這樣Master可以獲知每個Node的資源使用情況,並實現高效均衡等資源調度策略。而某個Node超過指定時間不上報信息時,會被Master判斷爲“失聯”,Node的狀態被標記爲不可用(Not Ready),隨後Master會觸發“工作負載大轉移”的自動流程。
Pod是Kubernetes的最重要也最基本的概念,如下圖所示是Pod的組成示意圖,我們看到每個Pod都有一個特殊的被成爲“根容器”的Pause容器。Pause容器對應的鏡像屬於Kubernetes平臺的一部分,除了Pause容器,每個Pod還包含一個或多個緊密相關的用戶業務容器。
9.說一說K8S的常用命令?
下表包括所有kubectl操作的簡短描述和一般語法:
Operation |
Syntax |
Description |
|
annotate |
`kubectl annotate (-f FILENAME |
TYPE NAME |
|
api-versions |
kubectl api-versions [flags] |
列出可用的API版本。 |
|
apply |
kubectl apply -f FILENAME [flags] |
對文件或標準輸入流更改資源應用配置。 |
|
attach |
kubectl attach POD -c CONTAINER [-i] [-t] [flags] |
attach 到正在運行的容器來查看輸出流或與容器(stdin)進行交互。 |
|
autoscale |
`kubectl autoscale (-f FILENAME |
TYPE NAME |
|
cluster-info |
kubectl cluster-info [flags] |
顯示有關集羣中master節點和服務的端點信息。 |
|
config |
kubectl config SUBCOMMAND [flags] |
修改kubeconfig文件。有關詳細信息,請參閱各個子命令。 |
|
create |
kubectl create -f FILENAME [flags] |
從文件或stdin創建一個或多個資源。 |
|
delete |
`kubectl delete (-f FILENAME |
TYPE [NAME |
|
describe |
`kubectl describe (-f FILENAME |
TYPE [NAME_PREFIX |
|
edit |
`kubectl edit (-f FILENAME |
TYPE NAME |
|
exec |
kubectl exec POD [-c CONTAINER] [-i] [-t] [flags] [-- COMMAND [args...]] |
對pod中的容器執行命令 |
|
explain |
kubectl explain [--include-extended-apis=true] [--recursive=false] [flags] |
獲取各種資源的文檔。例如 pods, nodes, services 等. |
|
expose |
`kubectl expose (-f FILENAME |
TYPE NAME |
|
get |
`kubectl get (-f FILENAME |
TYPE [NAME |
|
label |
`kubectl label (-f FILENAME |
TYPE NAME |
|
logs |
kubectl logs POD [-c CONTAINER] [--follow] [flags] |
在pod的容器中打印日誌。 |
|
patch |
`kubectl patch (-f FILENAME |
TYPE NAME |
|
port-forward |
kubectl port-forward POD [LOCAL_PORT:]REMOTE_PORT [...[LOCAL_PORT_N:]REMOTE_PORT_N] [flags] |
將一個或多個本地端口轉發到pod。 |
|
proxy |
kubectl proxy [--port=PORT] [--www=static-dir] [--www-prefix=prefix] [--api-prefix=prefix] [flags] |
運行一個代理到Kubernetes API服務器。 |
|
replace |
kubectl replace -f FILENAME |
從文件或stdin替換資源。 |
|
rolling-update |
`kubectl rolling-update OLD_CONTROLLER_NAME ([NEW_CONTROLLER_NAME] –image=NEW_CONTAINER_IMAGE |
-f NEW_CONTROLLER_SPEC) [flags]` |
|
run |
kubectl run NAME --image=image [--env="key=value"] [--port=port] [--replicas=replicas] [--dry-run=bool] [--overrides=inline-json] [flags] |
在集羣上運行指定的鏡像。 |
|
scale |
`kubectl scale (-f FILENAME |
TYPE NAME |
|
stop |
kubectl stop |
已棄用: 相應的, 請查看 kubectl delete. |
|
version |
kubectl version [--client] [flags] |
顯示在客戶端和服務器上運行的Kubernetes版本。 |
10.你簡歷項目中用到了kafka,談一談你們爲什麼用kafka,而不用其他消息隊列?
答:
特性 MQ |
ActiveMQ |
RabbitMQ |
RocketMQ |
Kafka |
生產者消費者模式 |
支持 |
支持 |
支持 |
支持 |
發佈訂閱模式 |
支持 |
支持 |
支持 |
支持 |
請求迴應模式 |
支持 |
支持 |
不支持 |
不支持 |
Api完備性 |
高 |
高 |
高 |
高 |
多語言支持 |
支持 |
支持 |
java |
支持 |
單機吞吐量 |
萬級 |
萬級 |
十萬級 |
十萬級 |
消息延遲 |
無 |
微秒級 |
毫秒級 |
毫秒級 |
可用性 |
高(主從) |
高(主從) |
非常高(分佈式) |
非常高(分佈式) |
消息丟失 |
低 |
低 |
理論上不會丟失 |
理論上不會丟失 |
文檔的完備性 |
高 |
高 |
教高 |
高 |
提供快速入門 |
有 |
有 |
有 |
有 |
社區活躍度 |
高 |
高 |
中 |
高 |
商業支持 |
無 |
無 |
商業雲 |
商業雲 |
總體來說:
ActiveMQ 歷史悠久的開源項目,已經在很多產品中得到應用,實現了JMS1.1規範,可以和spring-jms輕鬆融合,實現了多種協議,不夠輕巧(源代碼比RocketMQ多),支持持久化到數據庫,對隊列數較多的情況支持不好。
RabbitMQ 它比Kafka成熟,支持AMQP事務處理,在可靠性上,RabbitMQ超過Kafka,在性能方面超過ActiveMQ。
RocketMQ RocketMQ是阿里開源的消息中間件,目前在Apache孵化,使用純Java開發,具有高吞吐量、高可用性、適合大規模分佈式系統應用的特點。RocketMQ思路起源於Kafka,但並不是簡單的複製,它對消息的可靠傳輸及事務性做了優化,目前在阿里集團被廣泛應用於交易、充值、流計算、消息推送、日誌流式處理、binglog分發等場景,支撐了阿里多次雙十一活動。 因爲是阿里內部從實踐到產品的產物,因此裏面很多接口、API並不是很普遍適用。其可靠性毋庸置疑,而且與Kafka一脈相承(甚至更優),性能強勁,支持海量堆積。
Kafka Kafka設計的初衷就是處理日誌的,不支持AMQP事務處理,可以看做是一個日誌系統,針對性很強,所以它並沒有具備一個成熟MQ應該具備的特性。Kafka的性能(吞吐量、tps)比RabbitMQ要強,如果用來做大數據量的快速處理是比RabbitMQ有優勢的。
11.kafka爲什麼比較快?
Kafka的消息是保存或緩存在磁盤上的,一般認爲在磁盤上讀寫數據是會降低性能的,因爲尋址會比較消耗時間,但是實際上,Kafka的特性之一就是高吞吐率。
即使是普通的服務器,Kafka也可以輕鬆支持每秒百萬級的寫入請求,超過了大部分的消息中間件,這種特性也使得Kafka在日誌處理等海量數據場景廣泛應用。
寫入數據,Kafka會把收到的消息都寫入到硬盤中,它絕對不會丟失數據。爲了優化寫入速度Kafka採用了兩個技術, 順序寫入 和 MMFile 。
順序寫入,磁盤讀寫的快慢取決於你怎麼使用它,也就是順序讀寫或者隨機讀寫。在順序讀寫的情況下,某些優化場景磁盤的讀寫速度可以和內存持平(注:此處有疑問, 不推敲細節,參考 http://searene.me/2017/07/09/Why-is-Kafka-so-fast/
)。
因爲硬盤是機械結構,每次讀寫都會尋址->寫入,其中尋址是一個“機械動作”,它是最耗時的。所以硬盤最討厭隨機I/O,最喜歡順序I/O。爲了提高讀寫硬盤的速度,Kafka就是使用順序I/O。
而且Linux對於磁盤的讀寫優化也比較多,包括read-ahead和write-behind,磁盤緩存等。如果在內存做這些操作的時候,一個是JAVA對象的內存開銷很大,另一個是隨着堆內存數據的增多,JAVA的GC時間會變得很長,使用磁盤操作有以下幾個好處:
磁盤順序讀寫速度超過內存隨機讀寫
JVM的GC效率低,內存佔用大。使用磁盤可以避免這一問題
系統冷啓動後,磁盤緩存依然可用
上圖就展示了Kafka是如何寫入數據的, 每一個Partition其實都是一個文件 ,收到消息後Kafka會把數據插入到文件末尾(虛框部分)。
這種方法有一個缺陷—— 沒有辦法刪除數據 ,所以Kafka是不會刪除數據的,它會把所有的數據都保留下來,每個消費者(Consumer)對每個Topic都有一個offset用來表示 讀取到了第幾條數據 。
兩個消費者,Consumer1有兩個offset分別對應Partition0、Partition1(假設每一個Topic一個Partition);Consumer2有一個offset對應Partition2。這個offset是由客戶端SDK負責保存的,Kafka的Broker完全無視這個東西的存在;一般情況下SDK會把它保存到zookeeper裏面。(所以需要給Consumer提供zookeeper的地址)。
如果不刪除硬盤肯定會被撐滿,所以Kakfa提供了兩種策略來刪除數據。一是基於時間,二是基於partition文件大小。具體配置可以參看它的配置文檔。
Memory Mapped Files
即便是順序寫入硬盤,硬盤的訪問速度還是不可能追上內存。所以Kafka的數據並 不是實時的寫入硬盤 ,它充分利用了現代操作系統 分頁存儲 來利用內存提高I/O效率。
Memory Mapped Files(後面簡稱mmap)也被翻譯成 內存映射文件 ,在64位操作系統中一般可以表示20G的數據文件,它的工作原理是直接利用操作系統的Page來實現文件到物理內存的直接映射。完成映射之後你對物理內存的操作會被同步到硬盤上(操作系統在適當的時候)。
通過mmap,進程像讀寫硬盤一樣讀寫內存(當然是虛擬機內存),也不必關心內存的大小有虛擬內存爲我們兜底。
使用這種方式可以獲取很大的I/O提升, 省去了用戶空間到內核空間 複製的開銷(調用文件的read會把數據先放到內核空間的內存中,然後再複製到用戶空間的內存中。)也有一個很明顯的缺陷——不可靠, 寫到mmap中的數據並沒有被真正的寫到硬盤,操作系統會在程序主動調用flush的時候才把數據真正的寫到硬盤。 Kafka提供了一個參數——producer.type來控制是不是主動flush,如果Kafka寫入到mmap之後就立即flush然後再返回Producer叫 同步 (sync);寫入mmap之後立即返回Producer不調用flush叫 異步 (async)。
讀取數據,Kafka在讀取磁盤時做了哪些優化?
基於sendfile實現Zero Copy
傳統模式下,當需要對一個文件進行傳輸的時候,其具體流程細節如下:
調用read函數,文件數據被copy到內核緩衝區
read函數返回,文件數據從內核緩衝區copy到用戶緩衝區
write函數調用,將文件數據從用戶緩衝區copy到內核與socket相關的緩衝區。
數據從socket緩衝區copy到相關協議引擎。
以上細節是傳統read/write方式進行網絡文件傳輸的方式,我們可以看到,在這個過程當中,文件數據實際上是經過了四次copy操作:
硬盤—>內核buf—>用戶buf—>socket相關緩衝區—>協議引擎
而sendfile系統調用則提供了一種減少以上多次copy,提升文件傳輸性能的方法。
在內核版本2.1中,引入了sendfile系統調用,以簡化網絡上和兩個本地文件之間的數據傳輸。 sendfile的引入不僅減少了數據複製,還減少了上下文切換。
sendfile(socket, file, len);
運行流程如下:
sendfile系統調用,文件數據被copy至內核緩衝區
再從內核緩衝區copy至內核中socket相關的緩衝區
最後再socket相關的緩衝區copy到協議引擎
相較傳統read/write方式,2.1版本內核引進的sendfile已經減少了內核緩衝區到user緩衝區,再由user緩衝區到socket相關緩衝區的文件copy,而在內核版本2.4之後,文件描述符結果被改變,sendfile實現了更簡單的方式,再次減少了一次copy操作。
在apache,nginx,lighttpd等web服務器當中,都有一項sendfile相關的配置,使用sendfile可以大幅提升文件傳輸性能。
Kafka把所有的消息都存放在一個一個的文件中,當消費者需要數據的時候Kafka直接把文件發送給消費者,配合mmap作爲文件讀寫方式,直接把它傳給sendfile。
批量壓縮
在很多情況下,系統的瓶頸不是CPU或磁盤,而是網絡IO,對於需要在廣域網上的數據中心之間發送消息的數據流水線尤其如此。進行數據壓縮會消耗少量的CPU資源,不過對於kafka而言,網絡IO更應該需要考慮。
如果每個消息都壓縮,但是壓縮率相對很低,所以Kafka使用了批量壓縮,即將多個消息一起壓縮而不是單個消息壓縮
Kafka允許使用遞歸的消息集合,批量的消息可以通過壓縮的形式傳輸並且在日誌中也可以保持壓縮格式,直到被消費者解壓縮
Kafka支持多種壓縮協議,包括Gzip和Snappy壓縮協議
Kafka速度的祕訣在於,它把所有的消息都變成一個批量的文件,並且進行合理的批量壓縮,減少網絡IO損耗,通過mmap提高I/O速度,寫入數據的時候由於單個Partion是末尾添加所以速度最優;讀取數據的時候配合sendfile直接暴力輸出。
12.你項目中用到了多線程,如果線上發生死鎖問題,你會怎麼排查?
答:在分佈式部署的情況下需要根據鏈路追蹤找到對應服務器死鎖的進程,找到機器後根據jps -l 查找正在運行的java程序的pid,然後再根據jastack -pid 就能查到死鎖的位置
總結:
這是今晚進行一場電話面試實際問到的問題,問的問題基本平時都遇到過,但感覺回答得不夠深入,所以面試了大概40分鐘就結束了。面而知不足,現整理下來記錄(答案已做整理)