爆肝一周,不眠不休!就為 點贊+好評+收藏 三連
收藏黨可以通過百度網盤下載全部檔案:
鏈接:https://pan.baidu.com/s/1nwlBO2tYXDDl7OjGhs4e4Q
提取碼:1111
目錄
爆肝一周,不眠不休!就為 點贊+好評+收藏 三連
Java JDK原始碼合輯
HashMap篇
ConcurrentHashMap篇
常用主流框架面試合輯
Spring框架篇
SpringMVC原理篇
MyBatis框架篇
Netty篇
微服務面試合輯
Spring Boot篇
Dubbo篇
Spring Cloud篇
并發編程面試篇合輯
并發編程(上)
并發編程(下)
分布式中間件面試合輯
分布式呼叫RPC篇
分布式限流Zookeeper篇
分布式負載均衡Nginx篇
分布式訊息通訊RabbitMQ篇
分布式訊息通訊Kafka篇
分布式訊息通訊ActiveMQ篇
分布式資料庫Reids篇
分布式資料庫MongoDB篇
分布式資料庫Memcached
性能調優合集
JVM性能優化面試篇
Tomcat調優面試篇
MySQL調優面試篇
word檔案下載地址:鏈接:https://pan.baidu.com/s/1BaUi8KUjvjJ6RPN7CmMSyw
提取碼:1111爆肝一周,不眠不休!就為 點贊+好評+收藏 三連
HashMap篇
1、并發修改例外
原因
迭代器中修改數 和hashmap中的 modCount 不相等
目的
暴露例外,快速失敗
出現場景
- 多執行緒,一個迭代器迭代,一個執行緒增刪操作
- 迭代器迭代的時候,使用hashmap本身的remove方法
解決方案
- 多個執行緒,操作使用ConcurrentHashMap,或者hashTable
- 在迭代的時候,使用迭代器中的remove方法,保存修改數和modCount一致
2、HashMap底層資料結構
1.7:陣列+鏈表
1.8:陣列+鏈表+紅黑樹(其中紅黑樹,也是用了雙向鏈表,主要是為了鏈表操作方便,在擴容,鏈表轉紅黑樹,紅黑樹轉鏈表的程序中都要操作鏈表,)
3、hash 陣列的最大值
1 << 30
- 首先必須是2的倍數,方便計算對應的table下標,
- 為什么不是32位 高1位 為正負標識,不能占有,
- 為什么不是31位 他達不到2^31,因為Integer的最大值就是2^31-1,如果threadhold超過2^30,會把Integer的最大值賦給他,
4、hash 尋址演算法
hash值 和 陣列長度 -1 做與運算,
- 陣列長度為2的冪次方
- 長度-1,那么低位全部為1,做運算那么下標 肯定落到陣列長度范圍內,
5、1.7 HashMap的put方法的實作程序
- 判斷當前的陣列是否為空,如果為空則初始化該陣列
- 判斷key是否為null
- 遍歷tab[0],如果有key為null的entry,重新設定新值,回傳oldValue,
- 沒有找到則將key,value封裝成entry,存到陣列下標0的位置,回傳null,
- 根據key做hash運算得到hashcode
- 根據hashCode和陣列長度-1,邏輯與運算,算出hashcode基于當前陣列對應的陣列下標i
- 遍歷tab[i]位置的鏈表,當找到節點的key和傳入的key相同時,則重新設定為新值,回傳oldValue,
- 沒有找到這,說明是新的key,modCount++
- 再將hashcode,key,value,i 封裝成Entry物件,通過頭插法插入到改tab[i]位置
- 如果當前size是否大于等于閾值,并且當前桶位不為null 則進行擴容,
6、HashMap1.7 擴容機制
擴容條件:
當前容量大于等于閾值 并且 當前桶位不為null
擴容流程
- rehash,當容量大于等于我們設定的hash閾值,生成一個新的hash種子
- new 一個2倍長度的新陣列
- 回圈每個桶上的鏈表
- 重新計算hashcode,然后再和新的陣列長度-1做與運算,得到新陣列下標
- 判斷新陣列當tab[i]位置是否有元素
- 沒有元素則直接封裝成Entry物件賦值到當前tab[i]位置
- 有元素,則通過頭插法插入到鏈表中,再賦值到tab[i]位置
8、HashMap1.7 擴容產生回圈鏈表
場景
- 兩個(多)執行緒同時轉移同一個桶對應的鏈表
- 執行緒1依次將鏈表倒序方式轉移到新陣列中,
- 執行緒2此時轉移比如當前指標指向 1節點,下個指標指向2節點, 而鏈表中2的next節點指向的是1節點,
- 當插入的時候會1節點會指向2節點,2節點指向1節點,形成環形鏈表,
影響
- put時候,會造成死回圈,(需要回圈判斷鏈表中是否有相同的key)
- get時候,會造成死回圈,
9、1.8 hash運算的實作方式
將hashcode 高16 和 低16位 異或,算出hash值,
然后再和 陣列的長度 -1 比較,
要 高16和低16異或?
目的是當陣列的的長度為 2的 小于等于 16次方,也是就是2進制 小于等于 16位,,兩個key的hashcode運算出的低16位一樣,而高16位不一樣,如果高16低16位不做運算,那么他們做與運算等到的是通過樣的陣列下標,對每個hash值,在他的低16位中,讓高低16位進行了異或,讓他的低16位同時保持了高低16位的特征,盡量避免一些hash值后續出現沖突,大家可能會進入陣列的同一個位置, key 更加散列,
10、1.8 HashMap的put方法的實作程序
- 根據key生成hash 值
- 判斷當前hashMap物件的陣列是否為空,如果為空則初始化該陣列
- 根據邏輯與運算,算出hashcode基于當前陣列對應的陣列下標i
- 判斷陣列的第i個位置的元素(tab[i])是否為空
- 如果為空,則將key,value封裝成Node物件賦值給tab[i]
- 如果不為空
- 如果put方法傳入進來的key等于tab[i].key,那么證明存在相同的key
- 如果不等于tab[i].key,則:
- 如果tab[i]的型別是TreeNode,則表示陣列的第i位置上是一顆紅黑樹,那么將key和value插入到紅黑樹中,并且在插入之前會判斷在紅黑樹中是否存在相同的key
- 如果tab[i]的型別不是TreeNode,則表示陣列的第i位子上是一個鏈表,那么遍歷回圈找是否存在相同的key,并且在遍歷的程序中會對鏈表中的節點數進行計數,當遍歷到最后一個節點時,會將key,value封裝成Node插入到鏈表的尾部,同時判斷在插入新節點之前的鏈表節點個數是不是大于等于8,并且table長度大于等64,如果是,則將鏈表改為紅黑樹
- 如果上述步驟中發現存在相同的key,則根據onlyIfAbsent標記來判斷是否需要更新value值,然后回傳oldValue
- modCount++
- hashMap的元素個數size加1
- 如果size大于擴容的閾值,則進行擴容
11、1.8 HashMap 擴容機制
擴容條件:
- 當前容量大于等于閾值
- 或 在樹化之前,當前陣列的長度小于64,鏈表長度大于等于8 也會發生擴容,
擴容流程:
- new 2倍陣列長度的,新陣列
- 節點對應的hashcode和新陣列長度做與運算
- 結果為0,為低位鏈表,不為0為高位鏈表
- 低位插入新陣列老下標位置(i),高位插入新陣列的老陣列長度+老下標位置,(oldLength+i)
- 判斷是否進行樹化,
12、1.8 HashMap 樹化程序
樹化條件:
鏈表的長度大于等于8 且 陣列的長度大于等于64
樹化實作:
- 現將單向鏈表轉變為雙向鏈表
- 再將雙向鏈表,將頭結點作為root節點,然后依次將next節點插入到根節點,轉變紅黑樹,
- 再插入時候key比較
- 如果key實作了comparable介面,通過實作方式比較
- 否則比較key的hashCode
- 否則比較key的class.getName
- 否則比較key的System.identityHashCode比較
- 最后樹化后,取出root節點(TreeNode),放到entry位置
13、1.8 HashMap 的get實作程序
- 根據key生成hashcode
- 如果陣列為空,則直接回傳空
- 如果陣列不為空,則利用hashcode和陣列長度-1通過邏輯與操作算出key所對應的陣列下標i
- 如果陣列的第i個位置上沒有元素,則直接回傳空
- 如果陣列的第i個位置的元素的key等于get方法鎖傳進來的key,則回傳該元素,并獲取該元素的value,
- 如果不等于則判斷該元素還有沒有下個元素,如果沒有回傳空
- 如果有則判斷鈣元素的型別是鏈表節點還是紅黑樹節點
- 如果是鏈表則遍歷鏈表
- 如果是紅黑樹則遍歷紅黑樹
- 找到即回傳元素,沒找到則回傳空
14、1.8 HashMap 的Remove實作程序
- 找到對應的位置(和get方式類似)
- 鏈表節點直接洗掉
- 紅黑樹節點
- 先洗掉鏈表的對應的節點,實作方式將上個節點指向下下個節點
- 然后再維護紅黑樹上的節點,可能會發生退化成鏈表
- modCount--
- size--
15、1.8 HashMap 為什么使用紅黑樹,不使用AVL樹,二分查找樹,鏈表
- 因為AVL樹插入節點或者洗掉節點,整體的性能是不如紅黑樹的,AVL每個左右節點的高度是不能大于1的,所以維持這種結構比較消耗性能,
- 二分查找樹,他的左右節點不平衡,一開始就固定了root,那么極端的情況下會成為鏈表結構,
- 鏈表長度越長,那么他的插入和查詢效率都很低,
- 而紅黑樹他的整體查找,增刪節點的效率都是比較高的,
16、1.8 HashMap 什么時候將鏈表轉化成紅黑樹
- 當發現鏈表的元素個數大于8
- 并且當前的陣列長度大于等于64的時候,
因為當陣列比較小的時候,我們可以通過擴容的方式,將鏈表的長度變短,這樣就用樹化,
17、1.7 和 1.8 HashMap 不同點
- 結構:1.8使用了紅黑樹
- 插入法:1.7使用了頭插法(多執行緒情況會出現回圈鏈表,導致CPU飆升),1.8是用來尾插法(1.8中反正要去計算鏈表當前節點的個數,需要遍歷鏈表,所以直接使用了尾插法,)
- hash演算法復雜度:1.7 的hash演算法比1.8鐘的更復雜,hash演算法越復雜,生成hashcode則更散列,那么hashmap中的元素則更散列,更散列則hashmap的查詢性能更好,jdk7中沒有紅黑樹,所以只能優化hash演算法使元素更散列,1.8中重甲了紅黑樹,查詢性能得到了保障,所以可以簡化一下hash演算法,畢竟hash演算法越復雜越消耗CPU,
- 擴容的程序中:1.7可能會重新對key進行哈希(重新hash跟哈希種子有關系,),而1.8中沒有這部分邏輯
- 擴容的條件不一樣:1.7除了判斷是否大于等于閾值,同時還判斷了tab[i]是否為空,不為空才會進行擴容,1.8則沒有這部分邏輯,
- 擴容的轉移邏輯不一樣:jdk7是每次轉移一個元素,jdk8是先算出當前位置,高低位鏈表,再一次性轉移過去
- jdk8 多了一個api :putIfAbsent(key,value),
ConcurrentHashMap篇
1、JDK7 ConcurrentHashMap是怎么保證并發安全的?
主要利用了Unsafe操作+ReentrantLock+分段思想,
主要使用了Unsafe操作中的:
- compareAndSwapObject:通過cas的方式修改物件的屬性
- putOrderedObeject:并發安全的給陣列的某個位置賦值
- getObjectVolatile:并發安全的獲取陣列某個位置的元素
分段思想:
為了提高ConcurrentHashMap的并發量,分段數越高則支持的最大并發量越高,程式員可以通過concurrencyLevel引數來指定并發量,ConcurrentHashMap的內部類Segment就是用來表示某一個段的,
ReentrantLock:
每個Segement就是一個小型的 HashMap,當呼叫ConcurrentHashMap的put方法時,最侄訓呼叫到Segment的put方法,而Segment類繼承了ReentrantLock,所以Segment自帶可重入鎖,當呼叫到Segment的put方法時,會先利用可重入鎖加鎖,加鎖成功后再將待插入的key,value插入到小型HashMap中,插入完成后解鎖,
2、JDK7 ConcurrentHashMap的底層原理
ConcurrentHashMap底層是由兩層嵌套陣列來實作的
- ConcurrentHashMap物件中有一個屬性segments,型別為segment[];
- Segment物件中有一個屬性table,型別為hashEntry[];
當呼叫ConcurrentHashMap的put方法時,先根據key計算出對應的Segment[]陣列下標j,確定好當前key,value應該插入到哪個segment物件中,如果segments[j] 為空,則利用自旋鎖的方式在j位置生成一個Segment物件,
然后呼叫Segment物件的put方法,
Segment物件的put方法會先加鎖,然后也根據key計算出對應的HashEntry[]陣列下標i,然后將key,value封裝為Entry物件放入該位置,此程序和1,.7的put方法一樣,然后解鎖,
3、JDK7 ConcurrentHashMap的put實作程序
- 判斷key不能為null
- 通過hashcode和segment陣列長度-1,算出segment下標
- 判斷segement是否為空,如果為空,從segment[0]原型中獲取segment初始化的屬性,用來初始化segment物件,
- tryLock,
- 獲取鎖,走類似put的插入邏輯,
- 沒有獲取鎖,通過自旋的方式,找到head節點,
- 算出key對應的HashEntry陣列下標i,走類似put的插入邏輯
4、JDK7 ConcurrentHashMap的擴容
特點:
區域擴容,只擴容segment中的hashEntry陣列,并且在單執行緒下擴容,不會有并發問題,
條件:
當segment中hashEntry陣列容量大于等于閾值就會發生擴容,
流程:
- new 2倍陣列長度,得到新陣列
- 回圈hashEntry,處理每一個桶位鏈表,
- 回圈鏈表,計算出每個節點新的陣列的下標,這里會找到不間斷的區域鏈表都在同一個下標位置,將從不變化的開始位置,到鏈表的尾部,一次性到轉移到新的陣列下標上,
- 再回圈鏈表將其他的節點依次轉移到新的陣列中,
5、JDK7 ConcurrentHashMap的Size
- 第一層死回圈
- 為每個segment加鎖
- 第二層回圈累加每個segment的modCount 和 size,
- 然后比較上次回圈中的modCount總數和當前回圈的modCount總和,
- 相等則跳出死回圈,回傳size總和
6、JDK8 ConcurrentHashMap是怎么保證并發安全的
主要利用Unsafe操作+synchronized關鍵字
主要使用了Unsafe操作中的:
- compareAndSwapObject:通過cas的方式修改物件的屬性
- putOrderedObeject:并發安全的給陣列的某個位置賦值
- getObjectVolatile:并發安全的獲取陣列某個位置的元素
Synchronized主要負責在需要操作某個位置時進行加鎖(該位置不能為空),比如向某個位置的鏈表進行插入節點,向某個位置的紅黑樹插入節點
JDK中其實仍然有分段鎖的思想,只不過JDK7中段數是可以控制的,而JDK8中是陣列的每一個位置都有一把鎖,
7、JDK8 ConcurrentHashMap的put實作程序
- 首先根據key計算對應的陣列下標i,如果該位置沒有元素,則通過自旋的方式去向該位置賦值
- 如果該位置有元素,則通過synchronized將tab[i] 元素加鎖
- 加鎖成功之后,再判斷該元素的型別
- 如果是鏈表節點則進行添加節點到鏈表中
- 如果是紅黑樹則添加到紅黑樹中
- 添加成功后,走出了同步塊,判斷是否需要進行樹化
- addCount,這個方法的意思是ConcurrentHashMap的元素個數加1,但是這個操作也是需要并發安全的,并且元素個數加1成功后,會繼續判斷是否需要進行擴容,如果需要,則會進行擴容,所以這個方法很重要,
- 同時一個執行緒在put時如果發現當前ConcurrentHashMap正則進行擴容則會去幫助擴容
8、JDK8 ConcurrentHashMap的樹化
樹化條件:
當發現鏈表的元素個數大于等于8 (hashmap還會判斷陣列大小大于等于64)
樹化流程:
- 對當前tab[i]加鎖,鎖TreeBin物件
- 將鏈表轉變成雙向鏈表,目的是方便紅黑樹操作
- 將雙向鏈表插入到TreeBin中
9、JDK8 ConcurrentHashMap的TreeBin
相當于紅黑樹的殼子,他本身就是紅黑樹,他有屬性root表示根節點,無論樹結構怎么變,treebin都不會變,
10、JDK8 ConcurrentHashMap的addCount
- 判斷是否初始化了baseCount,沒有通過自旋的方式去初始化
- 通過亂數,計算出對應的countCells下標i
- countCells陣列不為空,判斷當前conuntCells[i] 是否有值,有值自旋方式 conuntCells[i] 值+1.
- 為空,回圈
- 先自旋方式 baseCount+1
- 不成功則初始化countCells陣列
- 找到對應的countCells陣列下標自旋方式 conuntCells[i] 值+1.
- 再不成功再自旋 baseCount+1
11、JDK8 ConcurrentHashMap的擴容
擴容條件
- 當一個執行緒自旋2次 為counterCells +1都失敗
- 或 元素個數大于等于了閾值
特點
- 當執行緒在put的時候,發現有正在擴容標記的時候,他會加入協助擴容
- 擴容到一定程度就不會擴容了
擴容流程
- new 2倍陣列長度,得到新陣列
- 首先為執行緒設定固定長度的步長,分配起始位置和結束位置,每個執行緒都會擴容自己那部分
- 每個執行緒先鎖住桶,依次將自己負責的桶轉移到新陣列中
- 節點對應的hashcode和新陣列長度做與運算
- 結果為0,為低位鏈表,不為0為高位鏈表
- 低位插入新陣列老下標位置(i),高位插入新陣列的老陣列長度+老下標位置,(oldLength+i)
- 這里會先找區域鏈表,該鏈表從頭到尾節點的下標都一致,對應新陣列的位置,直接轉移過去,再將其他的節點轉移過去,
- 判斷是否進行樹化,
12、JDK8 ConcurrentHashMap的size
累加countCells陣列每個元素值,再加上baseCount,
13、JDK8 ConcurrentHashMap的remove
減size,不減容量
14、JDK7和JDK8 ConcurrentHashMap的區別
- jdk8中沒有分段鎖,而是使用了synchronize的來進行控制
- jdk8中的擴容性能更高,支持多執行緒同時擴容,實際上jdk7中也支持多執行緒擴容,因為jdk7中的擴容是針對每個Segment的,所以也可能多執行緒擴容,但是性能沒有jdk8高,因為jdk8中對于任何一個執行緒都可以去幫助擴容
- jdk8的元素個數統計實作不一樣,jdk8是 counterCell陣列元素+baseCount,jdk7是通過回圈 遍歷每個segment物件加鎖統計累加的modCount和累加的size,和上次得出modCount的結果比較,
- 外加hashmap中的不同點
Java JDK原始碼 word檔案下載地址:鏈接:https://pan.baidu.com/s/1BaUi8KUjvjJ6RPN7CmMSyw
提取碼:1111爆肝一周,不眠不休!就為 點贊+好評+收藏 三連
常用主流框架面試合輯
Spring框架篇
1、什么是 Spring 框架?Spring 框架有哪些主要模塊?
Spring 框架是一個為 Java 應用程式的開發提供了綜合、廣泛的基礎性支持的 Java 平臺,Spring 幫助開發者解決了開發中基礎性的問題,使得開發人員可以專注于應用程式的開發,
Spring 框架本身亦是按照設計模式精心打造,這使得我們可以在開發環境中安心的集成 Spring 框架,不必擔心 Spring 是如何在后臺進行作業的,Spring 框架至今已集成了 20 多個模塊,這些模塊主要被分如下圖所示的核心容器、資料訪問/集成,、Web、AOP(面向切面編程)、工具、訊息和測 試模塊,
2、使用 Spring 框架能帶來哪些好處?
下面列舉了一些使用 Spring 框架帶來的主要好處:
? Dependency Injection(DI) 方法使得構造器和 JavaBean properties 檔案中的依賴關系一目了然,
? 與 EJB 容器相比較,IoC 容器更加趨向于輕量級,這樣一來 IoC 容器在有限的記憶體和 CPU 資源的情況下進行應用程式的開發和發布就變得十分有利,
? Spring 并沒有閉門造車,Spring 利用了已有的技術比如 ORM 框架、logging 框架、J2EE、Q uartz和JDK Timer,以及其他視圖技術,
? Spring 框架是按照模塊的形式來組織的,由包和類的編號就可以看出其所屬的模塊,開發者僅僅需要選用他們需要的模塊即可,
? 要測驗一項用 Spring 開發的應用程式十分簡單,因為測驗相關的環境代碼都已經囊括在框架中了,更加簡單的是,利用 JavaBean 形式的 POJO 類,可以很方便的利用依賴注入來寫入測驗資料,
? Spring 的 Web 框架亦是一個精心設計的 Web MVC 框架,為開發者們在web 框架的選擇上提供了一個除了主流框架比如 Struts、過度設計的、不流行 web 框架的以外的有力選項,
? Spring 提供了一個便捷的事務管理介面,適用于小型的本地事物處理(比如在單 DB 的環境下)和復雜的共同事物處理(比如利用 JTA 的復雜 DB 環境),
3、什么是控制反轉(IOC)?什么是依賴注入?
控制反轉是應用于軟體工程領域中的,在運行時被裝配器物件來系結耦合物件的一種編程技巧,物件之間耦合關系在編譯時通常是未知的,在傳統的編程方式中,業務邏輯的流程是由應用程式中的早已被設定好關聯關系的物件來決定的,在使用控制反轉的情況下,業務邏輯的流程是由物件關系圖來決定的,該物件關系圖由裝配器負責實體化,這種實作方式還可以將物件之間的關聯關系的定義抽象化,而系結的程序是通過“依賴注入”實作的,
控制反轉是一種以給予應用程式中目標組件更多控制為目的設計范式,并在我們的實際作業中起到了有效的作用,
依賴注入是在編譯階段尚未知所需的功能是來自哪個的類的情況下,將其他物件所依賴的功能物件實體化的模式,這就需要一種機制用來激活相應 的組件以提供特定的功能,所以依賴注入是控制反轉的基礎,否則如果在組件不受框架控制的情況下,框架又怎么知道要創建哪個組件?
在 Java 中依然注入有以下三種實作方式:
1. 構造器注入
2. Setter 方法注入
3. 介面注入
4、請解釋下 Spring 框架中的 IoC?
Spring 中的 org.springframework.beans 包和 org.springframework.context包構成了 Spring 框架 IoC 容器的基礎,BeanFactory 介面提供了一個先進的配置機制,使得任何型別的物件的配置成為可能,
ApplicationContex 介面對 BeanFactory(是一個子介面)進行了擴展,在 BeanFactory 的基礎上添加了其他功能,比如與 Spring 的AOP 更容易集成,也提供了處理 message resource 的機制(用于國際化)、事件傳播以及應用層的特別配置,比如針對 Web 應用的WebApplicationContext,
org.springframework.beans.factory.BeanFactory 是 Spring IoC 容器的具體實作,用來包裝和管理前面提到的各種 bean,BeanFactory 介面是 Spring IoC 容器的核心介面,
IOC:把物件的創建、初始化、銷毀交給 spring 來管理,而不是由開發者控制,實作控制反轉,
5、BeanFactory 和 ApplicationContext 有什么區別?
BeanFactory 可以理解為含有 bean 集合的工廠類,BeanFactory 包含了種bean 的定義,以便在接收到客戶端請求時將對應的 bean 實體化,BeanFactory 還能在實體化物件的時生成協作類之間的關系,此舉將 bean 自身與 bean 客戶端的 配置中解放出來,BeanFactory 還包含了 bean 生命周期的控制,呼叫客戶端的初始化方法 (initialization methods)和銷毀方法(destruction methods),
從表面上看,application context 如同 bean factory 一樣具有 bean 定義、bean 關聯關系的設定,根據請求分發 bean 的功能,但 applicationcontext 在此基礎上還提供了其他的功能,
1. 提供了支持國際化的文本訊息
2. 統一的資源檔案讀取方式
3. 已在監聽器中注冊的bean的事件
以下是三種較常見的 ApplicationContext 實作方式:
1、ClassPathXmlApplicationContext:從 classpath 的 XML 組態檔中讀取背景關系,并生成背景關系定義,應用程式背景關系從程式環境變數中
ApplicationContext context = new ClassPathXmlApplicationContex t(“bean.xml”);
2、FileSystemXmlApplicationContext :由檔案系統中的 XML 組態檔讀取背景關系,
ApplicationContext context = new FileSystemXmlApplicationConte xt(“bean.xml”);
3、XmlWebApplicationContext:由 Web 應用的 XML 檔案讀取背景關系,
4、AnnotationConfigApplicationContext(基于 Java 配置啟動容器)

6、Spring 有幾種配置方式?
將 Spring 配置到應用開發中有以下三種方式:
1. 基于XML的配置
2. 基于注解的配置
3. 基于Java的配置
7、如何用基于 XML 配置的方式配置 Spring?
在 Spring 框架中,依賴和服務需要在專門的組態檔來實作,我常用的XML 格式的組態檔,這 些組態檔的格式通常<beans>開頭,然后一系列的 bean 定義和專門的應用配置選項組成,
SpringXML 配置的主要目的時候是使所有的 Spring 組件都可以用 xml 檔案的形式來進行配置,這意味著不會出現其他的 Spring 配置型別(比如宣告的方式或基于 Java Class 的配置方式)
Spring 的 XML 配置方式是使用被 Spring 命名空間的所支持的一系列的XML 標簽來實作的,Spring 有以下主要的命名空間:context、beans、jdbc、tx、aop、mvc 和 aso,
如:

下面這個 web.xml 僅僅配置了 DispatcherServlet,這件最簡單的配置便能滿足應用程式配置運行時組件的需求


8、如何用基于 Java 配置的方式配置 Spring?
Spring 對 Java 配置的支持是由@Configuration 注解和@Bean 注解來實作的,由@Bean 注解的方法將會實體化、配置和初始化一個 新物件,這個物件將由 Spring 的 IoC 容器來管理,@Bean 宣告所起到的作用與 <bean>元素類似,被 @Configuration 所注解的類則表示這個類的主要目的是作為 bean 定義的資源,被@Configuration 宣告的類可以通過在同一個類的內部調 用@bean 方法來設定嵌入 bean 的依賴關系,
最簡單的@Configuration 宣告類請參考下面的代碼:

對于上面的@Beans 組態檔相同的 XML 組態檔如下:
<beans>
<bean id="myService" class="com.somnus.services.MyServiceI mpl"/>
</beans>
上述配置方式的實體化方式如下:利用 AnnotationConfigApplicationContext
public static void main(String[] args)
{ ApplicationContext ctx = new
AnnotationConfigApplicationContext(AppConfig.class);
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
要使用組件組建掃描,僅需用@Configuration 進行注解即可:
@Configuration
@ComponentScan(basePackages = "com.somnus")
public class AppConfig {
... }
在上面的例子中,com.acme 包首先會被掃到,然后再容器內查找被@Component 宣告的類,找到后將這些類按照 Sring bean 定義進行注冊,
如果你要在你的 web 應用開發中選用上述的配置的方式的話,需要用
AnnotationConfigWebApplicationContext 類來讀取組態檔,可以用來配置 Spring 的 Servlet 監聽器 ContextLoaderListener 或者 Spring MVC 的
<web-app>
<!-- Configure ContextLoaderListener to use AnnotationConfigWebApplicationContext
instead of the default XmlWebApplicationContext -->
<context-param>
<param-name>contextClass</param-name>
<param-value> org.springframework.web.context.support.AnnotationConfigWebApp licatio
nContext
</param-value>
</context-param>
<!-- Configuration locations must consist of one or more comma
- or space-delimited
fully-qualified @Configuration classes. Fully-qualifie
d
packages may also be
specified for component-scanning -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.howtodoinjava.AppConfig</param-value>
</context-param>
<!-- Bootstrap the root application context as usual using ContextLoaderListener -->
<web-app>
<!-- Configure ContextLoaderListener to use AnnotationConfigWebApplicationContext
instead of the default XmlWebApplicationContext -->
<context-param>
<param-name>contextClass</param-name>
<param-value> org.springframework.web.context.support.AnnotationConfigWebApp
licatio nContext
</param-value>
</context-param>
<!-- Configuration locations must consist of one or more comma
- or space-delimited
fully-qualified @Configuration classes. Fully-qualifie
d
packages may also be
specified for component-scanning -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.howtodoinjava.AppConfig</param-value>
</context-param>
<!-- Bootstrap the root application context as usual using ContextLoaderListener -->
9、怎樣用注解的方式配置 Spring?
Spring 在 2.5 版本以后開始支持用注解的方式來配置依賴注入,可以用注解的方式來替代 XML 方式的 bean 描述,可以將 bean 描述轉移到組件類的內部,只需要在相關類上、方法上或者欄位宣告上使用注解即可,注解注入將會被容器在 XML 注入之前被處理,所以后者會覆寫掉前者對于同一個屬性的處理結果,
注解裝配在 Spring 中是默認關閉的,所以需要在 Spring 檔案中配置一下才能使用基于注解的裝配模式,如果你想要在你的應用程式中使用關于注 解的方法的話,請參考如下的配置,
<beans>
<context:annotation-config/>
<!-- bean definitions go here -->
</beans>
在<context:annotation-config/>標簽配置完成以后,就可以用注解
的方式在 Spring 中向屬性、方法和構造方法中自動裝配變數,下面是幾種比較重要的注解型別:
1. @Required:該注解應用于設值方法,
2. @Autowired:該注解應用于有值設值方法、非設值方法、構造方法和變 量,
3. @Qualifier:該注解和@Autowired 注解搭配使用,用于消除特定 bean 自動裝配的歧義,
4. JSR-250 Annotations:Spring 支持基于 JSR-250 注解的以下注解,
@Resource、 @PostConstruct 和 @PreDestroy,
10、請解釋 Spring Bean 的生命周期?
Spring Bean 的生命周期簡單易懂,在一個 bean 實體被初始化時,需要執行一系列的初始化操作以達到可用的狀態,同樣的,當一個 bean 不在被呼叫時需要進行相關的析構操作,并從 bean 容 器中移除,
Spring bean factory 負責管理在 spring 容器中被創建的 bean 的生命周期,Bean 的生命周期由兩組回呼(call back)方法組成,
1. 初始化之后呼叫的回呼方法,
2. 銷毀之前呼叫的回呼方法,
Spring 框架提供了以下四種方式來管理 bean 的生命周期事件:
? InitializingBean 和 DisposableBean 回呼介面
? 針對特殊行為的其他 Aware 介面
? Bean組態檔中的Custom init()方法和destroy()方法
? @PostConstruct 和@PreDestroy 注解方式
使用 customInit()和 customDestroy()方法管理 bean 生命周期的代碼樣例如下:
<beans>
<bean id="demoBean" class="com.somnus.task.DemoBean"
init- method="customInit" destroy-method="customDestroy"></bean>
</beans>
11、Spring Bean 的作用域之間有什么區別?
Spring 容器中的 bean 可以分為 5 個范圍,所有范圍的名稱都是自說明的,但是為了避免混淆,還是讓我們來解釋一下:
1. singleton:這種 bean 范圍是默認的,這種范圍確保不管接受到多少個請求,每個容器中只有一個 bean 的實體,單例的模式由 bean factory 自身來維護,
2. prototype:原形范圍與單例范圍相反,為每一個 bean 請求提供一個實體,
3. request:在請求 bean 范圍內會每一個來自客戶端的網路請求創建一個實體,在請求完成以后,bean 會失效并被垃圾回收器回收,
4. Session:與請求范圍類似,確保每個 session 中有一個 bean 的實體,在
session 過期后,bean 會隨之失效,
5. global- session:global-session 和 Portlet 應用相關,當你的應用部署在Portlet 容器中作業時,它包含很多 portlet,如果你想要宣告讓所有的portlet 共用全域的存盤變數的話,那么這全域變數需要存盤在 global- session 中,
全域作用域與 Servlet 中的 session 作用域效果相同,
12、什么是 Spring inner beans?
在 Spring 框架中,無論何時 bean 被使用時,當僅被呼叫了一個屬性,一個明智的做法是將這個 bean 宣告為內部 bean,內部 bean 可以用 setter 注入“屬性”和構造方法注入“構造引數”的方式來 實作,
比如,在我們的應用程式中,一個 Customer 類參考了一個 Person 類, 我們的要做的是創建一個 Person 的實體,然后在 Customer 內部使用,
public class Customer{ private Person person;
//Setters and Getters
}
public class
Person{ private String name; private String address; private int age
內部 bean 的宣告方式如下:
<bean id="CustomerBean" class="com.somnus.common.Customer">
<property name="person">
<!-- This is inner bean -->
<bean class="com.howtodoinjava.common.Person">
<property name="name" value="lokesh" />
<property name="address" value="India" />
<property name="age" value="34" />
</bean>
</property>
</bean>
13、Spring 框架中的單例 Beans 是執行緒安全的么?
Spring 框架并沒有對單例 bean 進行任何多執行緒的封裝處理,關于單例bean 的執行緒安全和并發問題需要開發者自行去搞定,但實際上,大部分的 Spring bean 并沒有可變的狀態(比如 Serview 類和 DAO 類),所以在某種程度上說 Spring 的單例 bean 是執行緒安全的,如果你的 bean 有多種狀態的話(比如 View Model 物件),就需要自行保證執行緒安全,
最淺顯的解決辦法就是將多型 bean 的作用域由“singleton”變更為“prototype”,
14、請舉例說明如何在 Spring 中注入一個 Java Collection?
Spring 提供了以下四種集合類的配置元素:
- <list>該標簽用來裝配可重復的 list 值,
- <set>該標簽用來裝配沒有重復的 set 值,
- <map>該標簽可用來注入鍵和值可以為任何型別的鍵值對
- <props>該標簽支持注入鍵和值都是字串型別的鍵值對,
下面看一下具體的例子:
<beans>
<!-- Definition for javaCollection -->
<bean id="javaCollection" class="com.howtodoinjava.JavaCollect ion">
<!-- java.util.List -->
<property name="customList">
<list>
<value>INDIA</value>
<value>Pakistan</value>
<value>USA</value>
<value>UK</value>
</list>
</property>
<!-- java.util.Set -->
<property name="customSet"
<set>
<value>INDIA</value>
<value>Pakistan</value>
<value>USA</value>
<value>UK</value>
</set>
</property>
<!-- java.util.Map -->
<property name="customMap">
<map>
<entry key="1" value="INDIA"/>
<entry key="2" value="Pakistan"/>
<entry key="3" value="USA"/>
<entry key="4" value="UK"/>
</map>
</property>
<!-- java.util.Properties -->
<property name="customProperies">
<props>
<prop key="admin">admin@nospam.com</prop>
<prop key="support">support@nospam.com</prop>
</props>
</property>
</bean>
</beans>
15、如何向 Spring Bean 中注入一個 Java.util.Properties?
第一種方法是使用如下面代碼所示的<props>標簽:
<bean id="adminUser" class="com.somnus.common.Customer">
<!-- java.util.Properties -->
<property name="emails">
<props>
<prop key="admin">admin@nospam.com</prop>
<prop key="support">support@nospam.com</prop>
</props>
</property>
</bean>
也可用”util:”命名空間來從 properties 檔案中創建出一個 propertiesbean,然后利用 setter 方 法注入 bean 的參考,
16、請解釋 Spring Bean 的自動裝配?
在 Spring 框架中,在組態檔中設定 bean 的依賴關系是一個很好的機 制,Spring 容器還可以自 動裝配合作關系 bean 之間的關聯關系,這意味著 Spring 可以通過向 Bean Factory 中注入的方 式自動搞定 bean 之間的依賴關系,自動裝配可以設定在每個 bean 上,也可以設定在特定的 bean 上,下面的 XML 組態檔表明了如何根據名稱將一個 bean 設定為自動裝配:
<bean id="employeeDAO" class="com.howtodoinjava.EmployeeDAOImp l"
autowire="byName" />
除了 bean 組態檔中提供的自動裝配模式,還可以使用@Autowired 注解來自動裝配指定 的 bean,在使用@Autowired 注解之前需要在按照如下的配置方式在 Spring 組態檔進行配置才可以使用,
<context:annotation-config />
也可以通過在組態檔中配置 AutowiredAnnotationBeanPostProcessor 達到相同的效果,
<bean class
="org.springframework.beans.factory.annotation.AutowiredAnnota tionBea
nPostProcessor"/>
配置好以后就可以使用@Autowired 來標注了,
@Autowired
public EmployeeDAOImpl ( EmployeeManager manager ) { this.manager = manager;
}
17、請解釋自動裝配模式的區別?
在 Spring 框架中共有 5 種自動裝配,讓我們逐一分析,
1. no:這是 Spring 框架的默認設定,在該設定下自動裝配是關閉的,開發者需要自行在 bean 定義 中用標簽明確的設定依賴關系,
2. byName:該選項可以根據 bean 名稱設定依賴關系,當向一個 bean 中自動裝配一個屬性時,容器將根據 bean 的名稱自動在在組態檔中查詢一個匹配的 bean,如果找到的話,就裝配這個屬性,如果沒找到的話就報錯,
3. byType:該選項可以根據 bean 型別設定依賴關系,當向一個 bean 中自動裝配一個屬性時,容器將根據 bean 的型別自動在在組態檔中查詢一個匹配的 bean,如果找到的話,就裝配這個屬性,如果沒找到的話就報錯,
4. constructor:構造器的自動裝配和 byType 模式類似,但是僅僅適用于與有構造器相同引數的 bean,如果在容器中沒有找到與構造器引數型別一致的 bean,那么將會拋出例外,
5. autodetect:該模式自動探測使用構造器自動裝配或者 byType 自動裝配,首先,首先會嘗試找合適的帶引數的構造器,如果找到的話就是用構造器自動裝配,如果在 bean 內部沒有找到相應的構造器或者是無參構造器,容器就會自動選擇 byTpe 的自動裝配方式,
18、如何開啟基于注解的自動裝配?
要使用 @Autowired,需要注冊 AutowiredAnnotationBeanPostProcessor,可以
有以下兩種方式來實作:
引入組態檔中的<bean>下引入<context:annotation-config>
<beans>
<context:annotation-config />
</beans>
class="org.springframework.beans.factory.annotation.AutowiredA nnotati
onBeanPostProcessor"/>
在 bean 組態檔中直接引入 AutowiredAnnotationBeanPostProcessor
19、請舉例解釋@Required 注解?
在產品級別的應用中,IoC 容器可能宣告了數十萬了 bean,bean 與 bean
之間有著復雜的依賴關系,設值注解方法的短板之一就是驗證所有的屬性
是否被注解是一項十分困難的操作,可以通過在 中設定“dependency-check”來解決這個問題,
在應用程式的生命周期中,你可能不大愿意花時間在驗證所有 bean 的屬性是否按照背景關系件正確配置,或者你寧可驗證某個 bean 的特定屬性是否被正確的設定,即使是用“dependency- check”屬性也不能很好的解決這個問題,在這種情況下,你需要使用@Required 注解,
需要用如下的方式使用來標明 bean 的設值方法:
public class EmployeeFactoryBean extends AbstractFactoryBean<O bject>{
private String designation; public String getDesignation() {
return designation;
}
@Required
public void setDesignation(String designation)
{ this.designation = designation;
}
//more code here
}
RequiredAnnotationBeanPostProcessor 是 Spring 中的后置處理用來驗證被 @Required 注解的 bean 屬性是否被正確的設定了,在使用RequiredAnnotationBeanPostProcesso 來驗證 bean 屬性之前,首先要在IoC 容器中對其進行注冊: 但是如果沒有屬性被用 @Required 注解過的話,后置處理器會拋出一個BeanInitializationException 例外,
20、請舉例解釋@Autowired 注解?
@Autowired 注解對自動裝配何時何處被實作提供了更多細粒度的控制,@Autowired 注解可 以像@Required 注解、構造器一樣被用于在 bean 的設定方法上自動裝配 bean 的屬性,一個引數或者帶有任意名稱或帶有多個引數的方法,比如,可以在設值方法上使用@Autowired 注解來替代組態檔中的<property>元素當 Spring 容器在 setter 方法上找到@Autowired 注解時,會嘗試用 byType 自動裝配,當然我們也可以在構造方法上使用@Autowired 注解,帶有@Autowired 注解的構造方法意味著 在創建一個 bean 時將會被自動裝配,即便在組態檔中使用<property>元素,
public class TextEditor {
private SpellChecker spellChecker; @Autowired
public TextEditor(SpellChecker spellChecker)
{
System.out.println("Inside TextEditor constructor." );
this.spellChecker = spellChecker;
}
public void spellCheck(){ spellChecker.checkSpelling();
下面是沒有構造引數的配置方式:
<beans>
<context:annotation-config/>
<!-- Definition for textEditor bean without constructor-arg
-->
<bean id="textEditor" class="com.howtodoinjava.TextEditor"/
>
<!-- Definition for spellChecker bean -->
<bean id="spellChecker" class="com.howtodoinjava.SpellCheck er"/>
</beans>
21、請舉例說明@Qualifier 注解?
@Qualifier 注解意味著可以在被標注 bean 的欄位上可以自動裝配,Qualifier 注解可以用來取消 Spring 不能取消的 bean 應用,下面的示例將會在 Customer 的 person 屬性中自動裝配 person 的值
public class Customer{ @Autowired
private Person person
<bean id="customer" class="com.somnus.common.Customer" />
<bean id="personA" class="com.somnus.common.Person" >
<property name="name" value="lokesh" />
</bean>
<bean id="personB" class="com.somnus.common.Person" >
<property name="name" value="alex" />
</bean>
下面我們要在組態檔中來配置 Person 類,
Spring 會知道要自動裝配哪個 person bean 么?不會的,但是運行上面的示例時,會拋出下面的例外:
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionExceptio n:
No unique bean of type [com.howtodoinjava.common.Person] is de fined: expected single matching bean but found2: [personA, pe rsonB]
要解決上面的問題,需要使用 @Quanlifier 注解來告訴 Spring 容器要裝配哪個 bean:
public class Customer{ @Autowired @Qualifier("personA") private Person person;
}
22、構造方法注入和設值注入有什么區別? 請注意以下明顯的區別:
1. 在設值注入方法支持大部分的依賴注入,如果我們僅需要注入int、 string和long型的變數,我們不要用設值的方法注入,對于基本型別,如果我們沒有注入的話,可以為基本型別設定默認值,在構造方法 注入不支持大部分的依賴注入,因為在呼叫構造方法中必須傳入正確的構造引數,否 則的話為報錯,
2. 設值注入不會重寫構造方法的值,如果我們對同一個變數同時使用了構 造方法注入又使用了設定方法注入的話,那么構造方法將不能覆寫由設值方法注入的值,很明顯,因為構造方法在物件被創建時呼叫,
3. 在使用設值注入時有可能還不能保證某種依賴是否已經被注入,也就是說這時物件的依賴關系有可能是不完整的,而在另一種情況下,構造器注入則不允許生成依賴關系不完整的物件,
4. 在設值注入時如果物件A和物件B互相依賴,在創建物件A時Spring會拋出 sObjectCurrentlyInCreationException 例外,因為在 B 物件被創建之前A 對 象是不能被創建的,反之亦然,所以 Spring 用設值注入的方法解決了回圈依賴的問題,因物件的設值方法是在物件被創建之前被呼叫的,
23、Spring 框架中有哪些不同型別的事件?
Spring 的 ApplicationContext 提供了支持事件和代碼中監聽器的功能,我們可以創建 bean 用來監聽在 ApplicationContext 中發布的事件,ApplicationEvent 類和在 ApplicationContext 介面中處理的事件,如果一個bean 實作了 ApplicationListener 介面,當一個 ApplicationEvent 被發布以后,bean 會自動被通知,
public class AllApplicationEventListener implements Applicatio nListener < ApplicationEvent >{
@Override
public void onApplicationEvent(ApplicationEvent applicatio nEvent)
{
//process event
}
}
Spring 提供了以下 5 種標準的事件:
1. 背景關系更新事件(ContextRefreshedEvent):該事件會在ApplicationContext被初始化或者更新時發布,也可以在呼叫 ConfigurableApplicationContext介面中的 refresh()方法時被觸發,
2. 背景關系開始事件(ContextStartedEvent):當容器呼叫ConfigurableApplicationContext的 Start()方法開始/重新開始容器時觸發該事件,
3. 背景關系停止事件(ContextStoppedEvent):當容器呼叫ConfigurableApplicationContext的 Stop()方法停止容器時觸發該事件,
4. 背景關系關閉事件(ContextClosedEvent):當ApplicationContext被關閉時觸發該事件,容器被關閉時,其管理的所有單例 Bean 都被銷毀,
5. 請求處理事件(RequestHandledEvent):在Web應用中,當一個http請求(request)結束 觸發該事件,
public class CustomApplicationEvent extends ApplicationEvent{ public CustomApplicationEvent ( Object source, final String ms g ){
super(source);
System.out.println("Created a Custom event");
}
}
為了監聽這個事件,還需要創建一個監聽器:
public class CustomEventListener implements ApplicationListene r <
CustomApplicationEvent >{ @Override
public void onApplicationEvent(CustomApplicationEvent applicationEvent) {
//handle event
}
}
之后通過 applicationContext 介面的 publishEvent()方法來發布自定義事件,
CustomApplicationEvent customEvent = new
CustomApplicationEvent(applicationContext, "Test message");
applicationContext.publishEvent(customEvent);
24、FileSystemResource 和 ClassPathResource 有何區別?
在 FileSystemResource 中需要給出 spring-config.xml 檔案在你專案中的相對路徑或者絕對路徑,在 ClassPathResource 中 spring 會在 ClassPath 中自動搜尋組態檔,所以要把 ClassPathResource 檔案放在 ClassPath下,
如果將 spring-config.xml 保存在了 src 檔案央下的話,只需給出組態檔的名稱即可,因為 src 檔案央是默認,
簡而言之,ClassPathResource 在環境變數中讀取組態檔,
FileSystemResource 在組態檔中讀取組態檔,
25、Spring 框架中都用到了哪些設計模式?
Spring 框架中使用到了大量的設計模式,下面列舉了比較有代表性的:
- 代理模式—在AOP和remoting中被用的比較多,
- 單例模式—在spring組態檔中定義的bean默認為單例模式,
- 模板方法—用來解決代碼重復的問題,比 如.RestTemplate,JmsTemplate,JpaTempl ate,
- 前端控制器—Spring提供了DispatcherServlet來對請求進行分發,
- 視圖幫助(ViewHelper)—Spring提供了一系列的JSP標簽,高效宏來輔助 將分散的代碼整合在視圖里,
- 依賴注入—貫穿于BeanFactory/ApplicationContext介面的核心理念,
- 工廠模式—BeanFactory用來創建物件的實體
26、開發中主要使用 Spring 的什么技術 ?
1. IOC 容器管理各層的組件
2. 使用 AOP 配置宣告式事務
3. 整合其他框架.
27、簡述 AOP 和 IOC 概念 AOP:
Aspect Oriented Program, 面向(方面)切面的編程;Filter(過濾器) 也是一種AOP. AOP 是一種新的方法論, 是對傳統 OOP(Object-Oriented Programming, 面向物件編程) 的補充. AOP 的 主要編程物件是切面(aspect), 而切面模塊化橫切關注點.可以舉例通過事務說明.
IOC: Invert Of Control, 控制反轉. 也成為 DI(依賴注入)其思想是反轉資源獲取的方向. 傳統的資源查找方式要求組件向容器發起請求查找資源.作為回應, 容器適時的回傳資源. 而應用了 IOC 之后, 則是容器主動地將資源推送給它所管理的組件,組件所要做的僅是選擇一種合適的方式來接受資源. 這種行為也被稱為查找的被動形式
28、在 Spring 中如何配置 Bean ?
Bean 的配置方式:
- 通過全類名(反射)
- 通過工廠方法(靜態工廠方法 & 實體工廠方法)
- FactoryBean
29、IOC 容器對 Bean 的生命周期:
1. 通過構造器或工廠方法創建 Bean 實體
2. 為 Bean 的屬性設定值和對其他 Bean 的參考
3. 將 Bean 實體傳遞給 Bean 后置處理器的 postProcessBeforeInitialization方法
4. 呼叫 Bean 的初始化方法(init-method)
5. 將 Bean 實體傳遞給 Bean 后置處理器的 postProcessAfterInitialization方法
6. Bean 可以使用了
7. 當容器關閉時, 呼叫 Bean 的銷毀方法(destroy-method)
下載鏈接:https://pan.baidu.com/s/1nwlBO2tYXDDl7OjGhs4e4Q
提取碼:1111
SpringMVC原理篇
1、什么是 SpringMvc?
SpringMvc 是 spring 的一個模塊,基于 MVC 的一個框架,無需中間整合層來整合,
2、Spring MVC 的優點:
1. 它是基于組件技術的.全部的應用物件,無論控制器和視圖,還是業務物件 之類的都是 java 組件.并且和 Spring 提供的其他基礎結構緊密集成.
2. 不依賴于 Servlet API(目標雖是如此,但是在實作的時候確實是依賴于Servlet 的)
3. 可以任意使用各種視圖技術,而不僅僅局限于 JSP
4. 支持各種請求資源的映射策略
5. 它應是易于擴展的
3、SpringMVC 作業原理?
1. 客戶端發送請求到 DispatcherServlet
2. DispatcherServlet 查詢 handlerMapping 找到處理請求的 Controller
3. Controller 呼叫業務邏輯后,回傳 ModelAndView 4.DispatcherServlet 查詢 ModelAndView,找到指定視圖5.視圖將結果回傳到客戶端
4、SpringMVC 流程?
1.用戶發送請求至前端控制器 DispatcherServlet,
2.DispatcherServlet 收到請求呼叫 HandlerMapping 處理器映射器,
3. 處理器映射器找到具體的處理器(可以根據 xml 配置、注解進行查找),生成處理器物件及處理器攔截器(如果有則生成)一并回傳給 DispatcherServlet,
4. DispatcherServlet 呼叫 HandlerAdapter 處理器配接器,
5.HandlerAdapter 經過適配呼叫具體的處理器(Controller,也叫后端控制器),
6.Controller 執行完成回傳 ModelAndView,
7.HandlerAdapter 將 controller 執行結果 ModelAndView 回傳給DispatcherServlet,
8. DispatcherServlet 將 ModelAndView 傳給 ViewReslover 視圖決議器,
9. ViewReslover 決議后回傳具體 View,
10. DispatcherServlet 根據 View 進行渲染視圖(即將模型資料填充至視圖中),
11. DispatcherServlet 回應用戶,
5、SpringMVC的控制器是不是單例模式,如果是,有什么問題,怎么解決?
是單例模式,所以在多執行緒訪問的時候有執行緒安全問題,不要用同步,會影響 性能的,解決方案是在控制器里面不能寫欄位,
6、如果你也用過 struts2.簡單介紹下 springMVC 和 struts2 的區別有哪些?
1. springmvc 的入口是一個 servlet 即前端控制器,而 struts2 入口是一個
filter 過濾器,
2. springmvc 是基于方法開發(一個 url 對應一個方法),請求引數傳遞到方法的形參,可以 設計為單例或多例(建議單例),struts2 是基于類開發,傳遞引數是通過類的屬性,只能設 計為多例,
3. Struts 采用值堆疊存盤請求和回應的資料,通過 OGNL 存取資料, springmvc 通過引數決議器是將 request 請求內容決議,并給方法形參賦值,將資料和視圖封裝成 ModelAndView 物件,最后又將 ModelAndView 中的模型資料通過 reques 域傳輸到頁面,Jsp 視圖決議器默認使用 jstl,
7、SpringMVC中的控制器的注解一般用那個,有沒有別的注解可以替代?
一般用@Conntroller 注解,表示是表現層,不能用用別的注解代替,
8、 @RequestMapping 注解用在類上面有什么作用?
是一個用來處理請求地址映射的注解,可用于類或方法上,用于類上,表示類中的所有回應請求的方法都是以該地址作為父路徑,
9、怎么樣把某個請求映射到特定的方法上面?
答:直接在方法上面加上注解@RequestMapping,并且在這個注解里面寫上 要攔截的路徑
10、如果在攔截請求中,我想攔截 get 方式提交的方法,怎么配置?
可以在@RequestMapping 注解里面加上 method=RequestMethod.GET
11、怎么樣在方法里面得到 Request,或者 Session?
直接在方法的形參中宣告 request,SpringMvc 就自動把 request 物件傳入
12、我想在攔截的方法里面得到從前臺傳入的引數,怎么得到?
答:直接在形參里面宣告這個引數就可以,但必須名字和傳過來的引數一樣
13、如果前臺有很多個引數傳入,并且這些引數都是一個物件的,那么怎么樣快速得到這個物件?
直接在方法中宣告這個物件,SpringMVC 就自動會把屬性賦值到這個物件里面,
14、SpringMVC中函式的回傳值是什么?
答:回傳值可以有很多型別,有 String, ModelAndView,但一般用 String 比較好,
15、SpringMVC 怎么樣設定重定向和轉發的?
在回傳值前面加"forward:"就可以讓結果轉發,比如"forward:user.do? name=method4"
在回傳值前面加"redirect:"就可以讓回傳值重定向,比如redirect:http://www.baidu.com
16、SpringMVC用什么物件從后臺向前臺傳遞資料的?
答:通過 ModelMap 物件,可以在這個物件里面用 put 方法,把物件加到里面, 前臺就可以通過 el 運算式拿到,
17、SpringMVC中有個類把視圖和資料都合并的一起的,叫什么?
叫 ModelAndView,
18、怎么樣把 ModelMap 里面的資料放入 Session 里面?
可以在類上面加上@SessionAttributes 注解,里面包含的字串就是要放入session 里面的 key
19、SpringMVC 怎么和 AJAX 相互呼叫的?
通過 Jackson 框架就可以把 Java 里面的物件直接轉化成 Js 可以識別的Json 物件,
具體步驟如下 :
1. 加入 Jackson.jar
2. 在組態檔中配置 json 的映射
3. 在接受 Ajax 方法里面可以直接回傳 Object,List 等,但方法前面要加上@ResponseBody 注解
20、當一個方法向 AJAX 回傳特殊物件,譬如 Object,List 等,需要做什么處理?
要加上@ResponseBody 注解
21、SpringMVC里面攔截器是怎么寫的
有兩種寫法,一種是實作介面,另外一種是繼承配接器類,然后在 SpringMvc
<!-- 配置 SpringMvc 的攔截器 --> <mvc:interceptors>
<!-- 配置—個攔截器的 Bean 就可以了 默認是對所有請求都攔截 -->
<bean id="myInterceptor" class="com.et.action.MyHandlerInterce ptor"></bean>
<!-- 只針對部分請求攔截 -->
<mvc:interceptor>
<mvc:mapping path="/modelMap.do" />
<bean class="com.et.action.MyHandlerInterceptorAdapter" /> </m vc:interceptor>
</mvc:interceptors>
22、講下 SpringMVC 的執行流程
系統啟動的時候根據組態檔創建spring的容器, 首先是發送http請求到核心控制器 disPatherServlet,spring 容器通過映射器去尋找業務控制器,使用配接器找到相應的業務類,在進業務類時進行資料封裝,在封裝前可能 會涉及到型別轉換,執行完業務類后使用 ModelAndView 進行視圖轉發, 資料放在 model 中,用 map 傳遞資料進行頁面顯示,
MyBatis框架篇
1、什么是 MyBatis?
MyBatis 是一個可以自定義 SQL、存盤程序和高級映射的持久層框架,
2、講下 MyBatis 的快取
MyBatis 的快取分為一級快取和二級快取,一級快取放在 session 里面,默認就有,二級緩 存放在它的命名空間里,默認是不打開的,使用二級快取屬性類需要實作 Serializable 序列化介面(可用來保存物件的狀態),可在它的映射檔案中配置<cache/>
3、Mybatis 是如何進行分頁的?分頁插件的原理是什么?
1. Mybatis 使用 RowBounds 物件進行分頁,也可以直接撰寫 sql 先分頁, 也可以使用 Mybatis 的分頁插件,
2. 分頁插件的原理:實作 Mybatis 提供的介面,實作自定義插件,在插件的攔截方法內攔截待執行的 sql,然后重寫 sql,
select * from student
攔截 sql 后重寫為:
select t.* from (select * from student)t limit 0,10
4、簡述 Mybatis 的插件運行原理?以及如何撰寫一個插件?
1. Mybatis 僅可以撰寫針對 ParameterHandler、ResultSetHandler、StatementHandler、 Executor 這 4 種介面的插件,Mybatis 通過動態代 理,為需要攔截的介面生成代理物件以實 現介面方法攔截功能,每當執行這 4 種介面物件的方法時,就會進入攔截方法,具體就是InvocationHandler 的 invoke()方法,當然,只會攔截那些你指定需要攔截的方法,
2. 實作 Mybatis 的 Interceptor 介面并復寫 intercept()方法,然后在給插件撰寫注解,指定要攔截哪一個介面的哪些方法即可,記住,別忘了在配置 檔案中配置你撰寫的插件,
5、Mybatis 動態 sql 是做什么的?都有哪些動態 sql?能簡述一下動態 sql的執行原理嗎?
1. Mybatis 動態 sql 可以讓我們在 Xml 映射檔案內,以標簽的形式撰寫動態 sql,完成邏輯 判斷和動態拼接 sql 的功能,
2. Mybatis 提供了 9 種動態 sql 標簽:
trim|where|set|foreach|if|choose|when|otherwise|bind,
3. 其執行原理為,使用 OGNL 從 sql 引數物件中計算運算式的值,根據運算式的值動態拼接 sql,以此來完成動態 sql 的功能,
6、#{}和${}的區別是什么?
1.#{}是預編譯處理,${}是字串替換,
2. Mybatis 在處理#{}時,會將 sql 中的#{}替換為?號,呼叫PreparedStatement 的 set 方法來賦值;
3. Mybatis 在處理${}時,就是把${}替換成變數的值,
4.使用#{}可以有效的防止 SQL 注入,提高系統安全性,
7、為什么說 Mybatis 是半自動 ORM 映射工具?它與全自動的區別在哪里?
Hibernate 屬于全自動 ORM 映射工具,使用 Hibernate 查詢關聯物件或者關聯集合物件時,可以根據物件關系模型直接獲取,所以它是全自動的,而 Mybatis 在查詢關聯物件或關聯集合物件時,需要手動撰寫 sql 來完 成,所以,稱之為半自動 ORM 映射工具,
8、Mybatis 是否支持延遲加載?如果支持?它的實作原理是什么?
1.Mybatis 僅支持 association 關聯物件和 collection 關聯集合物件的延遲加載,association 指的就是一對一,collection 指的就是一對多查詢,在Mybatis 組態檔中,可以配置是否啟用延遲加載lazyLoadingEnabled=true|false
2.它的原理是,使用 CGLIB 創建目標物件的代理物件,當呼叫目標方法時,進入攔截器方法,比如呼叫a.getB().getName() ,攔截器 invoke()方法發現 a.getB()是 null 值,那么就會單獨發送事先保存好的查詢關聯 B物件的 sql,把 B 查詢上來,然后調a.setB(b) 于是 a 的物件 b方法的屬性性呼叫就有值了,接著完成a.getB().getName() ,這就是延遲加載的基本原理,
9、MyBatis 與 Hibernate 有哪些不同?
1. Mybatis 和 hibernate 不同,它不完全是一個 ORM 框架,因為 MyBatis 需要程式員自己撰寫 Sql 陳述句,不過 mybatis 可以通過 XML 或注解方式靈活配置要運行的 sql 陳述句,并將 java 物件和 sql 陳述句映射生成最終執行的sql,最后將 sql 執行的結果再映射生成 java 物件,
2. Mybatis 學習門檻低,簡單易學,程式員直接撰寫原生態 sql,可嚴格控制 sql 執行性能,靈活度高,非常適合對關系資料模型要求不高的軟體開發,例如互聯網軟體、企業運營類軟體等,因為這類軟體需求變化頻繁, 一但需求變化要求成果輸出迅速,但是靈活的前提是 mybatis 無法做到資料庫無關性,如果需要實作支持多種資料庫的軟體則需要自定 義多套 sql 映射檔案,作業量大,
3. Hibernate 物件/關系映射能力強,資料庫無關性好,對于關系模型要求高的軟體(例如需求固定的定制化軟體)如果用 hibernate 開發可以節省很多代碼,提高效率,但是 Hibernate 的缺點是學習門檻高,要精通門檻更高,而且怎么設計 O/R 映射,在性能和物件模型之間如何權衡,以及怎樣用好 Hibernate 需要具有很強的經驗和能力才行,總之,按照用戶的需求在有限的資源環境下只要能做出維護性、擴展性良好的軟體架構都是好架構,所以框架只有適合才是最好,
10、MyBatis 的好處是什么?
1. MyBatis 把 sql 陳述句從 Java 源程式中獨立出來,放在單獨的 XML 檔案中撰寫,給程式的維護帶來了很大便利,
2. MyBatis 封裝了底層 JDBC API 的呼叫細節,并能自動將結果集轉換成Java Bean 物件,大大簡化了 Java 資料庫編程的重復作業,
3. 因為 MyBatis 需要程式員自己去撰寫 sql 陳述句,程式員可以結合資料庫自身的特點靈活控制 sql 陳述句,因此能夠實作比 Hibernate 等全自動 orm 框架更高的查詢效率,能夠完成復雜查詢,
11、簡述 Mybatis 的 Xml 映射檔案和 Mybatis 內部資料結構之間的映射關系?
Mybatis 將所有 Xml 配置資訊都封裝到 All-In-One 重量級物件Configuration 內部,在 Xml 映射檔案中,<prameterMap>標簽會被決議為 ParameterMap 物件,其每個子元素會 被決議為 ParameterMapping物件,<resultMap>標簽會被決議為 ResultMap 物件,其每個子元素會被決議為 ResultMapping 物件,每一個<select>、<insert>、<update>、<delete> 標簽均會被決議為 MappedStatement 物件,標簽內的 sql 會被決議為 BoundSql 物件,
12、什么是 MyBatis 的介面系結,有什么好處?
介面映射就是在 MyBatis 中任意定義介面,然后把介面里面的方法和 SQL 陳述句系結,我們直接呼叫介面方法就可以,這樣比起原來了 Sql Session 提供的方法我們可以有更加靈活的選 擇和設定.
13、介面系結有幾種實作方式,分別是怎么實作的?
介面系結有兩種實作方式,一種是通過注解系結,就是在介面的方法上面加 上 @Select@Update 等注解里面包含 Sql 陳述句來系結,另外一種就是通過xml 里面寫 SQL 來系結,在這種情況下,要指定 xml 映射檔案里面的namespace 必須為介面的全路徑名.
14、什么情況下用注解系結,什么情況下用 xml 系結?
當 Sql 陳述句比較簡單時候,用注解系結;當 SQL 陳述句比較復雜時候,用 xml 系結,一般用 xml 系結的比較多
15、MyBatis 實作一對一有幾種方式?具體怎么操作的?
有聯合查詢和嵌套查詢,聯合查詢是幾個表聯合查詢,只查詢一次,通過在resultMap 里面配置 association 節點配置一對一的類就可以完成;嵌套查詢是先查一個表,根據這個表里面的結果的外鍵 id,去再另外一個表里面查詢資料,也是通過 association 配置,但另外一個表的查詢通過 select 屬性配置,
16、Mybatis 能執行一對一、一對多的關聯查詢嗎?都有哪些實作方式? 以及它們之間的區別?
能,Mybatis 不僅可以執行一對一、一對多的關聯查詢,還可以執行多對一,多對多的關聯查詢,多對一查詢,其實就是一對一查詢,只需要把selectOne()修改為 selectList()即可;多對多查詢,其實就是一對多查詢,只需要把 selectOne()修改為 selectList()即可,
關聯物件查詢,有兩種實作方式,一種是單獨發送一個 sql 去查詢關聯物件,賦給主物件,然后回傳主物件,另一種是使用嵌套查詢,嵌套查詢的 含義為使用 join 查詢,一部分列是 A 物件的屬性值,另外一部分列是關聯物件 B 的屬性值,好處是只發一個 sql 查詢,就可以把主物件和其關聯物件查出來,
17、MyBatis 里面的動態 Sql 是怎么設定的?用什么語法?
MyBatis 里面的動態 Sql 一般是通過 if 節點來實作,通過 OGNL 語法來實作,但是如果要寫的完整,必須配合 where,trim 節點,where 節點是判斷包含節點有內容就插入 where,否則不插入,trim 節點是用來判斷如果動態陳述句是以 and 或 or 開始,那么會自動把這個 and 或者 or 取掉,
18、Mybatis 是如何將 sql 執行結果封裝為目標物件并回傳的?都有哪些映射形式?
第一種是使用<resultMap>標簽,逐一定義列名和物件屬性名之間的映射關系,
第二種是使用 sql 列的別名功能,將列別名書寫為物件屬性名,比如T_NAME AS NAME,物件屬性名一般是 name,小寫,但是列名不區分大小寫,Mybatis 會忽略列名大小寫,只能找到與之對應物件屬性名,你甚至可以寫成T_NAME AS NAME,MyBatis一樣可以正常作業,有了列名與屬性名的映射關系后,Mybatis 通過反射創建物件,同時使用反射給物件的屬性逐一賦值并回傳,那些找不到映射關系的屬性,是無法完成賦值的,
19、Xml 映射檔案中?除了常見的 select|insert|updae|delete 標簽之外?還有哪些標簽?
還有很多其他的標簽,<resultMap>、<parameterMap>、<sql>、<include>、 <selectKey>,加上動態 sql 的 9 個標簽,
trim|where|set|foreach|if|choose|when|otherwise|bind等,其中<sql>為 sql 片段標簽,通過<include>標簽引入 sql 片段<selectKey>為不支持自增的主鍵生成策略標簽
20、當物體類中的屬性名和表中的欄位名不一樣?如果將查詢的結果封裝到指定 pojo?
1. 通過在查詢的 sql 陳述句中定義欄位名的別名,
2.通過<resultMap>來映射欄位名和物體類屬性名的一一對應的關系,
21、模糊查詢 like 陳述句該怎么寫
1.在 java 中拼接通配符,通過#{}賦值
2.在 Sql 陳述句中拼接通配符 (不安全會引起 Sql 注入)
22、通常一個 Xml 映射檔案?都會寫一個 Dao 介面與之對應, Dao 的作業原理?是否可以多載?
不能多載,因為通過 Dao 尋找 Xml 對應的 sql 的時候權限名+方法名的保存和尋找策略,介面作業原理為 jdk 動態代理原理,運行時會為 dao 生成proxy,代理物件會攔截介面方法,去執行對應的 sql 回傳資料,
23、Mybatis 映射檔案中?如果 A 標簽通過 include 參考了 B 標簽的內容?請問?B 標簽能否定義在 A 標簽的后面?還是說必須定義在 A 標簽的前面?
雖然 Mybatis 決議 Xml 映射檔案是按照順序決議的,但是,被參考的 B 標簽依然可以定義在任何地方,Mybatis 都可以正確識別,原理是,Mybatis 決議 A 標簽,發現 A 標簽參考了 B 標簽,但是 B 標簽尚未決議到,尚不存在,此時,Mybatis 會將 A 標簽標記為未解 析狀態,然后繼續決議余下的標簽,包含 B 標簽,待所有標簽決議完畢,Mybatis 會重新決議那些被標記為未決議的標簽,此時再決議 A 標簽時,B 標簽已經存在,A 標簽也就可以正常決議完成了,
24、Mybatis 的 Xml 映射檔案中?不同的 Xml 映射檔案?id 是否可以重復?
不同的 Xml 映射檔案,如果配置了 namespace,那么 id 可以重復;如果沒有配置 namespace,那么 id 不能重復;畢竟 namespace 不是必須的,只是最佳實踐而已,原因就是 namespace+id 是作為Map<String,MappedStatement>的 key 使用的,如果沒有 namespace,就剩下 id,那么,id 重復會導致資料互相覆寫,有了 namespace,自然 id 就可以重復,namespace不同,namespace+id 自然也就不同,
25、Mybatis 中如何執行批處理?
使用 BatchExecutor 完成批處理,
26、Mybatis 都有哪些 Executor 執行器?它們之間的區別是什么?
Mybatis 有三種基本的Excutor執行器,SimpleExecutor、ReuseExecutor、 BatchExecutor,
1、SimpleExecutor:每執行一次Updata或者select就開啟一個statement物件,用完立刻關閉 Statement 物件.
2、ReuseExecutor:執行 update 或 select,以 sql 作為 key 查找 Statement 物件,存在就使用,不存在就創建,用完后,不關閉 Statement 物件,而是放置于 Map
3、BatchExecutor:完成批處理,
27、Mybatis 中如何指定使用哪一種 Executor 執行器?
在 Mybatis 組態檔中,可以指定默認的 ExecutorType 執行器型別,也可以手動給 DefaultSqlSessionFactory 的創建 SqlSession 的方法傳遞ExecutorType 型別引數,
28、Mybatis 執行批量插入?能回傳資料庫主鍵串列嗎?
能,JDBC 都能,Mybatis 當然也能,
29、Mybatis 是否可以映射 Enum 列舉類?
Mybatis 可以映射列舉類,不單可以映射列舉類,Mybatis 可以映射任何物件到表的一列上,映射方式為自定義一個 TypeHandler,實作 TypeHandler的 setParameter()和 getResult()介面方法,
TypeHandler 有兩個作用,一是完成從 javaType 至 jdbcType 的轉換, 二是完成 jdbcType 至 javaType 的轉換,體現為 setParameter()和 getResult() 兩個方法,分別代表設定 sql 問號占位符引數和獲取列查詢結果,
30、如何獲取自動生成的(主)鍵值?
組態檔設定 usegeneratedkeys 為 true
31、在 mapper 中如何傳遞多個引數?
1. 直接在方法中傳遞引數,xml 檔案用#{0} #{1}來獲取
2. 使用 @param 注解:這樣可以直接在 xml 檔案中通過#{name}來獲取
32、resultType resultMap 的區別?
1. 類的名字和資料庫相同時,可以直接設定 resultType 引數為 Pojo 類
2. 若不同,需要設定 resultMap 將結果名字和 Pojo 名字進行轉換
33、使用 MyBatis 的 mapper 介面呼叫時有哪些要求?
型別相同
1.Mapper 介面方法名和 mapper.xml 中定義的每個 sql 的 id 相同
2.Mapper 介面方法的輸入引數型別和 mapper.xml 中定義的每個 sql的parameterType 的型別相同
3. Mapper 介面方法的輸出引數型別和 mapper.xml 中定義的每個 sql 的resultType 的型別相同
4. Mapper.xml 檔案中的 namespace 即是 mapper 介面的類路徑,
34、Mybatis 比 IBatis 比較大的幾個改進是什么?
1.有介面系結,包括注解系結 sql 和 xml 系結 Sql
2.動態 sql 由原來的節點配置變成 OGNL 運算式
3.在一對一,一對多的時候引進了association,在一對多的時候引入了collection 節點,不過都是在 resultMap 里面配置
35、IBatis 和 MyBatis 在核心處理類分別叫什么?
IBatis 里面的核心處理類叫 SqlMapClient,MyBatis 里面的核心處理類叫做SqlSession,
36、IBatis 和 MyBatis 在細節上的不同有哪些?
1. 在 sql 里面變數命名有原來的#變數# 變成了#{變數}
2. 原來的$變數$變成了${變數}
3. 原來在 sql 節點里面的 class 都換名字叫 type
4. 原來的queryForObject queryForList 變成了selectOne selectList5)原來的別名設定在映射檔案里面放在了核心組態檔里
Netty篇
1.BIO、NIO 和 AIO 的區別?
BIO:一個連接一個執行緒,客戶端有連接請求時服務器端就需要啟動一個執行緒進行處理,執行緒開銷大,
偽異步 IO:將請求連接放入執行緒池,一對多,但執行緒還是很寶貴的資源,
NIO:一個請求一個執行緒,但客戶端發送的連接請求都會注冊到多路復用器上,多路復用器輪詢到連接有 I/O 請求時才啟動一個執行緒進行處理,
AIO:一個有效請求一個執行緒,客戶端的 I/O 請求都是由 OS 先完成了再通知服務器應用去啟動執行緒進行處理,
- BIO 是面向流的,NIO 是面向緩沖區的;
- BIO 的各種流是阻塞的,而 NIO 是非阻塞的;
- BIO 的 Stream 是單向的,而 NIO 的 channel 是雙向的,
NIO 的特點:事件驅動模型、單執行緒處理多任務、非阻塞 I/O,I/O 讀寫不再阻塞,而是回傳 0、基于 block 的傳輸比基于流的傳輸更高效、更高級的 IO 函式 zero-copy、IO 多路復用大大提高了 Java 網路應用的可伸縮性和實用性,基于 Reactor 執行緒模型,
在 Reactor 模式中,事件分發器等待某個事件或者可應用或個操作的狀態發生,事件分發器 就把這個事件傳給事先注冊的事件處理函式或者回呼函式,由后者來做實際的讀寫操作,如在 Reactor 中實作讀:注冊讀就緒事件和相應的事件處理器、事件分發器等待事件、事件到 來,激活分發器,分發器呼叫事件對應的處理器、事件處理器完成實際的讀操作,處理讀到 的資料,注冊新的事件,然后返還控制權,
2.NIO 的組成
Buffer:與Channel進行互動,資料是從Channel讀入緩沖區,從緩沖區寫入Channel中的flip方法:反轉此緩沖區,將position給limit,然后將position置為0,其實就是切換讀寫模式
clear方法:清除此緩沖區,將position置為0,把capacity的值給limit,
rewind方法:重繞此緩沖區,將position置為0
DirectByteBuffer可減少一次系統空間到用戶空間的拷貝,但Buffer創建和銷毀的成本更高,不可控,通常會用記憶體池來提高性能,直接緩沖區主要分配給那些易受基礎系統的本機I/O操作影響的大型、持久的緩沖區,如果資料量比較小的中小應用情況下,可以考慮使用heapBuffer,由JVM進行管理,
Channel:表示IO源與目標打開的連接,是雙向的,但不能直接訪問資料,只能與Buffer進行互動,通過原始碼可知,FileChannel的read方法和write方法都導致資料復制了兩次!
Selector:可使一個單獨的執行緒管理多個Channel,open方法可創建Selector,register方法向多路復用器器注冊通道,可以監聽的事件型別:讀、寫、連接、accept,注冊事件后會產生一個SelectionKey:它表示SelectableChannel和Selector之間的注冊關系,wakeup方法:使尚未回傳的第一個選擇操作立即回傳,喚醒的原因是:注冊了新的channel或者事件;channel關閉,取消注冊;優先級更高的事件觸發(如定時器事件),希望及時處理,
Selector在Linux的實作類是EPollSelectorImpl,委托給EPollArrayWrapper實作,其中三個native方法是對epoll的封裝,而EPollSelectorImpl.implRegister方法,通過呼叫epoll_ctl向epoll實體中注冊事件,還將注冊的檔案描述符(fd)與SelectionKey的對應關系添加到fdToKey中,這個map維護了檔案描述符與SelectionKey的映射,
fdToKey有時會變得非常大,因為注冊到Selector上的Channel非常多(百萬連接);過期或失效的Channel沒有及時關閉,fdToKey總是串行讀取的,而讀取是在select方法中進行的,該方法是非執行緒安全的,
Pipe:兩個執行緒之間的單向資料連接,資料會被寫到sink通道,從source通道讀取NIO的服務端建立程序:Selector.open():打開一個Selector;ServerSocketChannel.open():創建服務端的Channel;bind():系結到某個埠上,并配置非阻塞模式;register():注冊Channel和關注的事件到Selector上;select()輪詢拿到已經就緒的事件.
3.Netty的特點?
一個高性能、異步事件驅動的NIO框架,它提供了對TCP、UDP和檔案傳輸的支持,使用更高效的socket底層,對epoll空輪詢引起的cpu占用飆升在內部進行了處理,避免了直接使用NIO的陷阱,簡化了NIO的處理方式, 采用多種decoder/encoder支持,對TCP粘包/分包進行自動化處理 可使用接受/處理執行緒池,提高連接效率,對重連、心跳檢測的簡單支持 可配置IO執行緒數、TCP引數,TCP接收和發送緩沖區使用直接記憶體代替堆記憶體,通過記憶體池的方式回圈利用ByteBuf 通過參考計數器及時申請釋放不再參考的物件,降低了GC頻率 使用單執行緒串行化的方式,高效的Reactor執行緒模型 大量使用了volitale、使用了CAS和原子類、執行緒安全類的使用、讀寫鎖的使用
4.Netty的執行緒模型?
Netty通過Reactor模型基于多路復用器接收并處理用戶請求,內部實作了兩個執行緒池,boss 執行緒池和work執行緒池,其中boss執行緒池的執行緒負責處理請求的accept事件,當接收到accept 事件的請求時,把對應的socket封裝到一個NioSocketChannel中,并交給work執行緒池,其中work執行緒池負責請求的read和write事件,由對應的Handler處理,
單執行緒模型:所有I/O操作都由一個執行緒完成,即多路復用、事件分發和處理都是在一個Reactor執行緒上完成的,既要接收客戶端的連接請求,向服務端發起連接,又要發送/讀取請求或應答/回應訊息,一個NIO執行緒同時處理成百上千的鏈路,性能上無法支撐,速度慢,若執行緒進入死回圈,整個程式不可用,對于高負載、大并發的應用場景不合適,
多執行緒模型:有一個NIO執行緒(Acceptor)只負責監聽服務端,接收客戶端的TCP連接請求;NIO執行緒池負責網路IO的操作,即訊息的讀取、解碼、編碼和發送;1個NIO執行緒可以同時處理N條鏈路,但是1個鏈路只對應1個NIO執行緒,這是為了防止發生并發操作問題,但在并發百萬客戶端連接或需要安全認證時,一個Acceptor執行緒可能會存在性能不足問題,
主從多執行緒模型:Acceptor執行緒用于系結監聽埠,接收客戶端連接,將SocketChannel從主執行緒池的Reactor執行緒的多路復用器上移除,重新注冊到Sub執行緒池的執行緒上,用于處理I/O的讀寫等操作,從而保證mainReactor只負責接入認證、握手等操作;
5.TCP粘包/拆包的原因及解決方法?
TCP是以流的方式來處理資料,一個完整的包可能會被TCP拆分成多個包進行發送,也可能把小的封裝成一個大的資料包發送,
TCP粘包/分包的原因:
應用程式寫入的位元組大小大于套接字發送緩沖區的大小,會發生拆包現象,而應用程式寫入資料小于套接字緩沖區大小,網卡將應用多次寫入的資料發送到網路上,這將會發生粘包現象;進行MSS大小的TCP分段,當TCP報文長度-TCP頭部長度>MSS的時候將發生拆包 以太網幀的payload(凈荷)大于MTU(1500位元組)進行ip分片,
解決方法:
訊息定長:FixedLengthFrameDecoder類包尾增加特殊字符分割:行分隔符類:LineBasedFrameDecoder或自定義分隔符類:
DelimiterBasedFrameDecoder 將訊息分為訊息頭和訊息體:LengthFieldBasedFrameDecoder類,分為有頭部的拆包與粘包、長度欄位在前且有頭部的拆包與粘包、多擴展頭部的拆包與粘包,
6.了解哪幾種序列化協議?
序列化(編碼)是將物件序列化為二進制形式(位元組陣列),主要用于網路傳輸、資料持久化等;而反序列化(解碼)則是將從網路、磁盤等讀取的位元組陣列還原成原始物件,主要用于網路傳輸物件的解碼,以便完成遠程呼叫,
影響序列化性能的關鍵因素:序列化后的碼流大小(網路帶寬的占用)、序列化的性能(CPU 資源占用);是否支持跨語言(異構系統的對接和開發語言切換),
Java默認提供的序列化:無法跨語言、序列化后的碼流太大、序列化的性能差
XML
優點:人機可讀性好,可指定元素或特性的名稱,缺點:序列化資料只包含資料本身以及類的結構,不包括型別標識和程式集資訊;只能序列化公共屬性和欄位;不能序列化方法;檔案龐大,檔案格式復雜,傳輸占帶寬,適用場景:當做組態檔存盤資料,實時資料轉換,
JSON
是一種輕量級的資料交換格式,優點:兼容性高、資料格式比較簡單,易于讀寫、序列化后資料較小,可擴展性好,兼容性好、與XML相比,其協議比較簡單,決議速度比較快,缺點:資料的描述性比XML差、不適合性能要求為ms級別的情況、額外空間開銷比較大,適用場景(可替代XML):跨防火墻訪問、可調式性要求高、基于Webbrowser的Ajax請求、傳輸資料量相對小,實時性要求相對低(例如秒級別)的服務,
Fastjson
采用一種“假定有序快速匹配”的演算法,優點:介面簡單易用、目前java語言中最快的json庫,
缺點:過于注重快,而偏離了“標準”及功能性、代碼質量不高,檔案不全,適用場景:協議互動、Web輸出、Android客戶端
Thrift
不僅是序列化協議,還是一個RPC框架,優點:序列化后的體積小,速度快、支持多種語言和豐富的資料型別、對于資料欄位的增刪具有較強的兼容性、支持二進制壓縮編碼,
缺點:使用者較少、跨防火墻訪問時,不安全、不具有可讀性,除錯代碼時相對困難、不能與其他傳輸層協議共同使用(例如HTTP)、無法支持向持久層直接讀寫資料,即不適合做資料持久化序列化協議,適用場景:分布式系統的RPC解決方案
Avro
Hadoop的一個子專案,解決了JSON的冗長和沒有IDL的問題,
優點:支持豐富的資料型別、簡單的動態語言結合功能、具有自我描述屬性、提高了資料決議速度、快速可壓縮的二進制資料形式、可以實作遠程程序呼叫RPC、支持跨編程語言實作,
缺點:對于習慣于靜態型別語言的用戶不直觀,適用場景:在Hadoop中做Hive、Pig和MapReduce的持久化資料格式,
Protobuf
將資料結構以.proto檔案進行描述,通過代碼生成工具可以生成對應資料結構的 POJO物件和Protobuf相關的方法和屬性,
優點:序列化后碼流小,性能高、結構化資料存盤格式(XMLJSON等)、通過標識欄位的順序,可以實作協議的前向兼容、結構化的檔案更容易管理和維護,
缺點:需要依賴于工具生成代碼、支持的語言相對較少,官方只支持Java、C++、python,適用場景:對性能要求高的RPC呼叫、具有良好的跨防火墻的訪問屬性、適合應用層物件的持久化
protostuff
基于protobuf協議,但不需要配置proto檔案,直接導包即可
Jbossmarshaling
可以直接序列化java類,無須實java.io.Serializable介面
Messagepack
一個高效的二進制序列化格式
Hessian
采用二進制協議的輕量級remotingonhttp工具
kryo
基于protobuf協議,只支持java語言,需要注冊(Registration),然后序列化(Output),反序列化(Input)
7.如何選擇序列化協議?
具體場景
- 對于公司間的系統呼叫,如果性能要求在100ms以上的服務,基于XML的SOAP協議是一個值得考慮的方案,
- 基于Webbrowser的Ajax,以及Mobileapp與服務端之間的通訊,JSON協議是首選,對于性能要求不太高,或者以動態型別語言為主,或者傳輸資料載荷很小的的運用場景,JSON 也是非常不錯的選擇,
- 對于除錯環境比較惡劣的場景,采用JSON或XML能夠極大的提高除錯效率,降低系統開發成本,
- 當對性能和簡潔性有極高要求的場景,Protobuf,Thrift,Avro之間具有一定的競爭關系,對于T級別的資料的持久化應用場景,Protobuf和Avro是首要選擇,如果持久化后的資料存盤在hadoop子專案里,Avro會是更好的選擇,
- 對于持久層非Hadoop專案,以靜態型別語言為主的應用場景,Protobuf會更符合靜態型別語言工程師的開發習慣,由于Avro的設計理念偏向于動態型別語言,對于動態語言為主的應用場景,Avro是更好的選擇,
- 如果需要提供一個完整的RPC解決方案,Thrift是一個好的選擇,
- 如果序列化之后需要支持不同的傳輸層協議,或者需要跨防火墻訪問的高性能場景,Protobuf可以優先考慮,protobuf的資料型別有多種:bool、double、float、int32、int64、string、bytes、enum、message,
protobuf的限定符:required:必須賦值,不能為空、optional:欄位可以賦值,也可以不賦值、
repeated:該欄位可以重復任意次數(包括0次)、列舉;只能用指定的常量集中的一個值作為其值;
protobuf的基本規則:每個訊息中必須至少留有一個required型別的欄位、包含0個或多個optional型別的欄位;repeated表示的欄位可以包含0個或多個資料;[1,15]之內的標識號在編碼的時候會占用一個位元組(常用),[16,2047]之內的標識號則占用2個位元組,標識號一定不能重復、使用訊息型別,也可以將訊息嵌套任意多層,可用嵌套訊息型別來代替組,
protobuf的訊息升級原則:不要更改任何已有的欄位的數值標識;不能移除已經存在的required欄位,optional和repeated型別的欄位可以被移除,但要保留標號不能被重用,新添加的欄位必須是optional或repeated,因為舊版本程式無法讀取或寫入新增的required限定符的欄位,編譯器為每一個訊息型別生成了一個.java檔案,以及一個特殊的Builder類(該類是用來創建訊息類介面的)
如:UserProto.User.Builder builder =UserProto.User.newBuilder();builder.build();
Netty中的使用:ProtobufVarint32FrameDecoder是用于處理半包訊息的解碼類;
ProtobufDecoder(UserProto.User.getDefaultInstance())這是創建的UserProto.java檔案中的解碼類;ProtobufVarint32LengthFieldPrepender對protobuf協議的訊息頭上加上一個長度為32 的整形欄位,用于標志這個訊息的長度的類;ProtobufEncoder是編碼類將StringBuilder轉換為ByteBuf型別:copiedBuffer()方法
8.Netty的零拷貝實作?
Netty的接收和發送ByteBuffer采用DIRECTBUFFERS,使用堆外直接記憶體進行Socket讀寫,不需要進行位元組緩沖區的二次拷貝,堆記憶體多了一次記憶體拷貝,JVM會將堆記憶體Buffer拷貝一份到直接記憶體中,然后才寫入Socket中ByteBuffer由ChannelConfig分配,由ChannelConfig創建ByteBufAllocator默認使用DirectBufferCompositeByteBuf類可以將多個ByteBuf合并為一個邏輯上的ByteBuf,避免了傳統通過記憶體拷貝的方式將幾個小Buffer合并成一個大的Buffer,
addComponents方法將header與body合并為一個邏輯上的ByteBuf,這兩個ByteBuf在CompositeByteBuf內部都是單獨存在的,CompositeByteBuf只是邏輯上是一個整體通過FileRegion包裝的FileChannel.tranferTo方法實作檔案傳輸,可以直接將檔案緩沖區的資料發送到目標Channel,避免了傳統通過回圈write方式導致的記憶體拷貝問題,
通過wrap方法,我們可以將byte[]陣列、ByteBuf、ByteBuffer等包裝成一個NettyByteBuf物件,進而避免了拷貝操作,
SelectorBUG:若Selector的輪詢結果為空,也沒有wakeup或新訊息處理,則發生空輪詢,
CPU使用率100%,
Netty的解決辦法:對Selector的select操作周期進行統計,每完成一次空的select操作進行一次計數,若在某個周期內連續發生N次空輪詢,則觸發了epoll死回圈bug,重建Selector,判斷是否是其他執行緒發起的重建請求,若不是則將原SocketChannel從舊的Selector上去除注冊,重新注冊到新的Selector上,并將原來的Selector關閉,
9.Netty的高性能表現在哪些方面?
心跳,對服務端:會定時清除閑置會話inactive(netty5),
對客戶端:用來檢測會話是否斷開,是否重來,檢測網路延遲,其中idleStateHandler類用來檢測會話狀態串行無鎖化設計,即訊息的處理盡可能在同一個執行緒內完成,期間不進行執行緒切換,這樣就避免了多執行緒競爭和同步鎖,表面上看,串行化設計似乎CPU利用率不高,并發程度不夠,
但是,通過調整NIO執行緒池的執行緒引數,可以同時啟動多個串行化的執行緒并行運行,這種區域無鎖化的串行執行緒設計相比一個佇列-多個作業執行緒模型性能更優,可靠性,鏈路有效性檢測:鏈路空閑檢測機制讀/寫空閑超時機制;記憶體保護機制:通過記憶體池重用ByteBuf;ByteBuf的解碼保護;優雅停機:不再接收新訊息、退出前的預處理操作、資源的釋放操作,
Netty安全性:支持的安全協議:SSLV2和V3,TLS,SSL單向認證、雙向認證和第三方CA認證,
高效并發編程的體現:volatile的大量、正確使用;CAS和原子類的廣泛使用;執行緒安全容器的使用;通過讀寫鎖提升并發性能,IO通信性能三原則:傳輸(AIO)、協議(Http)、執行緒(主從多執行緒)
流量整型的作用(變壓器):防止由于上下游網元性能不均衡導致下游網元被壓垮,業務流中斷;防止由于通信模塊接受訊息過快,后端業務執行緒處理不及時導致撐死問題,
TCP引數配置:SO_RCVBUF和SO_SNDBUF:通常建議值為128K或者256K;
SO_TCPNODELAY:NAGLE演算法通過將緩沖區內的小封包自動相連,組成較大的封包,阻止大量小封包的發送阻塞網路,從而提高網路應用效率,但是對于時延敏感的應用場景需要關閉該優化演算法;
10.NIOEventLoopGroup原始碼?
NioEventLoopGroup(其實是MultithreadEventExecutorGroup)內部維護一個型別為EventExecutorchildren[],默認大小是處理器核數*2,
這樣就構成了一個執行緒池,初始化EventExecutor時NioEventLoopGroup多載newChild方法,所以children元素的實際型別為NioEventLoop,
- 執行緒啟動時呼叫SingleThreadEventExecutor的構造方法,執行NioEventLoop類的run方法,首先會呼叫hasTasks()方法判斷當前taskQueue是否有元素,如果taskQueue中有元素,執行selectNow()方法,最終執行selector.selectNow(),該方法會立即回傳,如果taskQueue 沒有元素,執行select(oldWakenUp)方法
- select(oldWakenUp)方法解決了Nio中的bug,selectCnt用來記錄selector.select方法的執行次數和標識是否執行selector.selectNow(),若觸發了epoll的空輪詢bug,則會反復執行selector.select(timeoutMillis),變數selectCnt會逐漸變大,當selectCnt達到閾值(默認512),則執行rebuildSelector方法,進行selector重建,解決cpu占用100%的bug,
- rebuildSelector方法先通過openSelector方法創建一個新的selector,然后將oldselector的selectionKey執行cancel,最后將oldselector的channel重新注冊到新的selector中,rebuild 后,需要重新執行方法selectNow,檢查是否有已ready的selectionKey,
- 接下來呼叫processSelectedKeys方法(處理I/O任務),當selectedKeys!=null時,呼叫processSelectedKeysOptimized方法,迭代selectedKeys獲取就緒的IO事件的selectkey存放在陣列selectedKeys中,然后為每個事件都呼叫processSelectedKey來處理它,processSelectedKey中分別處理OP_READ;OP_WRITE;OP_CONNECT事件,
- 最后呼叫runAllTasks方法(非IO任務),該方法首先會呼叫fetchFromScheduledTaskQueue 方法,把scheduledTaskQueue中已經超過延遲執行時間的任務移到taskQueue中等待被執行,然后依次從taskQueue中取任務執行,每執行64個任務,進行耗時檢查,如果已執行時間超過預先設定的執行時間,則停止執行非IO任務,避免非IO任務太多,影響IO任務的執行,
每個NioEventLoop對應一個執行緒和一個Selector,NioServerSocketChannel會主動注冊到某一個NioEventLoop的Selector上,NioEventLoop負責事件輪詢,
Outbound事件都是請求事件,發起者是Channel,處理者是unsafe,通過Outbound事件進行通知,傳播方向是tail到head,Inbound事件發起者是unsafe,事件的處理者是Channel,是通知事件,傳播方向是從頭到尾,
記憶體管理機制,首先會預申請一大塊記憶體Arena,Arena由許多Chunk組成,而每個Chunk默認由2048個page組成,Chunk通過AVL樹的形式組織Page,每個葉子節點表示一個Page,而中間節點表示記憶體區域,節點自己記錄它在整個Arena中的偏移地址,當區域被分配出去后,中間節點上的標記位會被標記,這樣就表示這個中間節點以下的所有節點都已被分配了,大于8k的記憶體分配在poolChunkList中,而PoolSubpage用于分配小于8k的記憶體,它會把一個page分割成多段,進行記憶體分配,
ByteBuf的特點:支持自動擴容(4M),保證put方法不會拋出例外、通過內置的復合緩沖型別,實作零拷貝(zero-copy);不需要呼叫flip()來切換讀/寫模式,讀取和寫入索引分開;方法鏈;參考計數基于AtomicIntegerFieldUpdater用于記憶體回收;PooledByteBuf采用二叉樹來實作一個記憶體池,集中管理記憶體的分配和釋放,不用每次使用都新建一個緩沖區物件,UnpooledHeapByteBuf每次都會新建一個緩沖區物件,
并發編程面試合輯 word檔案下載地址:鏈接:https://pan.baidu.com/s/1nwlBO2tYXDDl7OjGhs4e4Q
提取碼:1111爆肝一周,不眠不休!就為 點贊+好評+收藏 三連
微服務面試合輯
Spring Boot篇
1、什么是 Spring Boot?
多年來,隨著新功能的增加,spring 變得越來越復雜,只需訪問https://spring.io/projects 頁面,我們就會看到可以在我們的應用程式中使用的所有 Spring 專案的不同功能,如果必須啟動一個新的 Spring 專案, 我們必須添加構建路徑或添加 Maven 依賴關系,配置應用程式服務器, 添加 spring 配置,因此,開始一個新的 spring 專案需要很多努力,因為我們現 在必須從頭開始做所有事情,
Spring Boot 是解決這個問題的方法,Spring Boot 已經建立在現有 spring 框架之上,使用spring 啟動,我們避免了之前我們必須做的所有樣板代碼和配置,因此,Spring Boot可以幫助我們以最少的作業量,更加健壯地使用現有的 Spring 功能,
2、Spring Boot 有哪些優點?
Spring Boot 的優點有:
減少開發,測驗時間和努力,
使用 JavaConfig 有助于避免使用 XML,避免大量的 Maven 匯入和各種版本沖突,提供意見發展方法,
通過提供默認值快速開始開發,
沒有單獨的 Web 服務器需要,這意味著你不再需要啟動 Tomcat,Glassfish 或其他任何東西,需要更少的配置 因為沒有 web.xml 檔案,只需添加用@ Configuration 注釋的類,然后添加用@Bean 注釋的方法,Spring 將自動加載物件并像以前一樣對其進行管理,您甚至可以將 @Autowired 添加到 bean 方法中, 以使 Spring 自動裝入需要的依賴關系中, 基于環境的配置使用這些屬性,您可以將您正在使用的環境傳遞到應用程式:- Dspring.profiles.active ={enviornment},在加載主應用程式屬性檔案后,Spring 將在(application{environment} .properties)中加載后續的應用程式屬性檔案,
3、什么是 JavaConfig?
Spring JavaConfig是Spring社區的產品,它提供了配置Spring IoC容器的純Java方法,因此它有助于避免使用 XML 配置,使用 JavaConfig 的優點在于:
面向物件的配置,由于配置被定義為 JavaConfig 中的類,因此用戶可以充分利用 Java 中的面向物件功能,一個配置類可以繼承另一個,重寫它的@Bean 方法等,
減少或消除 XML 配置,基于依賴注入原則的外化配置的好處已被證明,但是,許多開發人員不希望在 XML 和 Java 之間來回切換,JavaConfig 為開發人員提供了一種純 Java 方法來配 置與 XML 配置概念相似的 Spring 容器,從技術角度來講,只使用 JavaConfig 配置類來配置 容器是可行的,但實際上很多人認為將 JavaConfig 與 XML 混合匹配是理想的, 型別安全和重構友好,JavaConfig 提供了一種型別安全的方法來配置 Spring 容器,由于 Java 5.0 對泛型的支持,現在可以按型別而不是按名稱檢索bean,不需要任何強制轉換或 基于字串的查找,
4、如何重新加載 Spring Boot 上的更改?而無需重新啟動服務器?
這可以使用 DEV 工具來實作,通過這種依賴關系,您可以節省任何更改,嵌入式 tomcat 將重新啟動,Spring Boot 有一個開發工具(DevTools)模塊,它有助于提高開發人員的生 產力,Java 開發人員面臨的一個主要挑戰是將檔案更改自動部署到服務器并自動重啟服務 器,開發人員可以重新加載Spring Boot上的更改,而無需重新啟動服務器,這將消除每次手動部署更改的需要,Spring Boot 在發布它的第一個版本時沒有這個功能,這是開發人員最需要的功能,DevTools 模塊完全滿足開發人員的需求,該模塊將在生產環境中被禁用,它還提供 H2 資料庫控制臺以更好地測驗應用程式,
org.springframework.boot spring-boot-devtools true
5、Spring Boot 中的監視器是什么?
Spring boot actuator是spring啟動框架中的重要功能之一,Spring boot監視器可幫助您訪 問生產環境中正在運行的應用程式的當前狀態,有幾個指標必須在生產環境中進行檢查和 監控,即使一些外部應用程式可能正在使用這些服務來向相關人員觸發警報訊息,監視器 模塊公開了一組可直接作為 HTTP URL 訪問的 REST 端點來檢查狀態,
6、如何在 Spring Boot 中禁用 Actuator 端點安全性?
默認情況下,所有敏感的 HTTP 端點都是安全的,只有具有 ACTUATOR 角色的用戶才能訪問它們,安全性是使用標準的HttpServletRequest.isUserInRole 方法實施的,我們可以使用management.security.enabled = false來禁用安全性,只有在執行機構端點在防火墻后訪問時,才建議禁用安全性,
7、如何在自定義埠上運行 Spring Boot 應用程式?
為了在自定義埠上運行 Spring Boot 應用程式,您可以在application.properties 中指定埠,server.port = 8090
8、什么是 YAML?
YAML 是一種人類可讀的資料序列化語言,它通常用于組態檔,與屬性檔案相比,如果我們想要在組態檔中添加復雜的屬性,YAML 檔案就更加結構化,而且更少混淆,可以看出 YAML 具有分層配置資料,
9、如何實作 Spring Boot 應用程式的安全性?
為了實作Spring Boot的安全性,我們使用 spring-boot-starter-security依賴項,并且必須添加安全配置,它只需要很少的代碼,配置類將必須擴展WebSecurityConfigurerAdapter 并覆 蓋其方法,
10、如何集成 Spring Boot 和 ActiveMQ?
對于集成 Spring Boot 和 ActiveMQ,我們使用spring-boot-starter-activemq
依賴關系, 它只需要很少的配置,并且不需要樣板代碼,
11、如何使用 Spring Boot 實作分頁和排序?
使用 Spring Boot 實作分頁非常簡單,使用 Spring Data-JPA 可以實作將可分頁的 org.springframework.data.domain.Pageable傳遞給存盤庫方法,
12、什么是 Swagger?你用 Spring Boot 實作了它嗎?
Swagger 廣泛用于可視化 API,使用 Swagger UI 為前端開發人員提供在線沙箱,Swagger 是用于生成 RESTful Web 服務的可視化表示的工具,規范和完整框架實作,它使檔案能夠以與服務器相同的速度更新,當通過Swagger 正確定義時,消費者可以使用最少量的實作邏 輯來理解遠程服務并與其進行互動,因此,Swagger 消除了呼叫服務時的猜測,
13、什么是 Spring Profiles?
Spring Profiles 允許用戶根據組態檔(dev,test,prod 等)來注冊 bean,因此,當應用程式在開發中運行時,只有某些 bean 可以加載,而在PRODUCTION 中,某些其他 bean 可以加載,假設我們的要求是Swagger 檔案僅適用于 QA 環境,并且禁用所有其他檔案,這可以使用組態檔來完成,Spring Boot 使得使用組態檔非常簡單,
14、什么是 Spring Batch?
Spring Boot Batch提供可重用的函式,這些函式在處理大量記錄時非常重要,包括日志/跟 蹤,事務管理,作業處理統計資訊,作業重新啟動,跳過和資源管理,它還提供了更先進的技術服務和功能,通過優化和磁區技術,可以實作極高批量和高性能批處理作業,簡單以及復雜的大批量批處理作業可以高度可擴展的方式利用框架處理重要大量的資訊,
15、什么是 FreeMarker 模板?
FreeMarker 是一個基于 Java 的模板引擎,最初專注于使用 MVC 軟體架構進行動態網頁生成,使用 Freemarker 的主要優點是表示層和業務層的完全分離,程式員可以處理應用程式代碼,而設計人員可以處理 html 頁面設計,最后使用 freemarker 可以將這些結合起來,給出最終的輸出頁面,
16、如何使用 Spring Boot 實作例外處理?
Spring提供了一種使用ControllerAdvice處理例外的非常有用的方法,我們通過實作一個 ControlerAdvice 類,來處理控制器類拋出的所有例外,
17、您使用了哪些 starter maven 依賴項?
使用了下面的一些依賴項
spring-boot-starter-activemq spring-boot-starter-security
spring-boot-starter-web 這有助于增加更少的依賴關系,并減少版本的沖突,
18、什么是 CSRF 攻擊?
CSRF 代表跨站請求偽造,這是一種攻擊,迫使最終用戶在當前通過身份驗證的 Web 應用 程式上執行不需要的操作,CSRF 攻擊專門針對狀態改變請求,而不是資料竊取,因為攻擊者無法查看對偽造請求的回應,
19、什么是 WebSockets?
WebSocket 是一種計算機通信協議,通過單個 TCP 連接提供全雙工通信信道,
WebSocket 是雙向的 -使用 WebSocket 客戶端或服務器可以發起訊息發送,
WebSocket 是全雙工的 -客戶端和服務器通信是相互獨立的,
單個TCP連接 -初始連接使用HTTP,然后將此連接升級到基于套接字的連接,然后這個單一連接用于所有未來的通信Light -與 http 相比,WebSocket 訊息資料交換要輕得多,
20、什么是 AOP?
在軟體開發程序中,跨越應用程式多個點的功能稱為交叉問題,這些交叉問題與應用程式的主要業務邏輯不同,因此,將這些橫切關注與業務邏輯分開是面向方面編程(AOP)的 地方,
21、什么是 Apache Kafka?
Apache Kafka 是一個分布式發布 - 訂閱訊息系統,它是一個可擴展的,容錯的發布 - 訂閱 訊息系統,它使我們能夠構建分布式應用程式,這是一個Apache 頂級專案,Kafka 適合離 線和在線訊息消費,
22、我們如何監視所有 Spring Boot 微服務?
Spring Boot 提供監視器端點以監控各個微服務的度量,這些端點對于獲取有關應用程式的資訊(如它們是否已啟動)以及它們的組件(如資料庫等)是 否正常運行很有幫助,但是,使用監視器的一個主要缺點或困難是,我們必須單獨打開應用程式的知識點以了解其狀態或健康狀況,想象一下涉及50 個應用程式的微服務,管理員將不得不擊中所有 50 個應用程式的執行終端,
Dubbo篇
1、Dubbo 中 zookeeper 做注冊中心?如果注冊中心集群都掛掉?發布者和訂閱者之間還能通信么?
可以通信的,啟動 dubbo 時,消費者會從 zk 拉取注冊的生產者的地址介面等資料,快取在本地,每次呼叫時,按照本地存盤的地址進行呼叫; 注冊中心對等集群,任意一臺宕機后,將會切換到另一臺;注冊中心全部宕機后,服務的提供者和消費者仍能通過本地快取通訊,服務提供者無狀態, 任一臺宕機后,不影響使用;服務提供者全部宕機,服務消費者會無法使 用,并無限次重連等待服務者恢復; 掛掉是不要緊的,但前提是你沒有增加新的服務,如果你要呼叫新的服務,則是不能辦到的,

2、dubbo 服務負載均衡策略?
l Random LoadBalance
隨機,按權重設定隨機概率,在一個截面上碰撞的概率高,但呼叫量越大 分布越均勻,而且按概率使用權重后也比 較均勻,有利于動態調整提供者權重,(權重可以在 dubbo 管控臺配置)
l RoundRobin LoadBalance
輪循,按公約后的權重設定輪循比率,存在慢的提供者累積請求問題,比如:第二臺機器很慢,但沒掛,當請求調
到第二臺時就卡在那,久而久之,所有請求都卡在調到第二臺上,
l LeastActive LoadBalance
最少活躍呼叫數,相同活躍數的隨機,活躍數指呼叫前后計數差,使慢的提供者收到更少請求,因為越慢的提供者的
呼叫前后計數差會越大,
l ConsistentHash LoadBalance
一致性 Hash,相同引數的請求總是發到同一提供者,當某一臺提供者掛時,原本發往該提供者的請求,基于虛擬節點,平攤到其它提供者,不會引起劇烈變動,預設只對第一個引數 Hash,如果要修改,請配置
<dubbo:parameter key="hash.arguments" value="0,1" />
<dubbo:parameter key="hash.nodes" value="320" />
3、 Dubbo 在安全機制方面是如何解決的
Dubbo 通過 Token 令牌防止用戶繞過注冊中心直連,然后在注冊中心上管理授權,Dubbo 還提供服務黑白名單,來控制服務所允許的呼叫方,
4、dubbo 連接注冊中心和直連的區別
在開發及測驗環境下,經常需要繞過注冊中心,只測驗指定服務提供者, 這時候可能需要點對點直連,點對點直聯方式,將以服務介面為單位,忽 略注冊中心的提供者串列,l Failsafe Cluster失敗安全,出現例外時,直接忽略,通常用于寫入審計日志等操作,
[AppleScript] 純文本查看復制代碼 ?
服務注冊中心,動態的注冊和發現服務,使服務的位置透明,并通過在消費方獲取服務提供方地址串列,實作軟負載均衡和 Failover
注冊中心回傳服務提供者地址串列給消費者,如果有變更,注冊中心將基于長連接推 送變更資料給消費者,服務消費者,從提供者地址串列中,基于軟負載均衡演算法,選一臺提供者進行呼叫,如果呼叫失敗,再選另一臺呼叫,
注冊中心負責服務地址的注冊與查找,相當于目錄服務,服務提供者和消費者只在啟動時與注冊中心互動,注冊中心不轉發請求,服務消費者向注冊中心獲取服務提供者地址串列,并根據負載演算法直接呼叫提供者
注冊中心,服務提供者,服務消費者三者之間均為長連接,監控中心除外,注冊中心通過長連接感知服務提供者的存在,服務提供者宕機,注冊中心將立即推送事件通知消費者注冊中心和監控中心全部宕機,不影響已運行的提供者和消費者,消費者在本地快取了提供者串列
注冊中心和監控中心都是可選的,服務消費者可以直連服務提供者,
5. dubbo 服務集群配置(集群容錯模式)
在集群呼叫失敗時,Dubbo 提供了多種容錯方案,預設為 failover 重試,可以自行擴展集群容錯策略 l Failover Cluster(默認)
失敗自動切換,當出現失敗,重試其它服務器,(預設)通常用于讀操作, 但重試會帶來更長延遲,可通過 retries="2"來設定重試次數(不含第一次),
<dubbo:service retries="2" cluster="failover"/>
或:<dubbo:reference retries="2" cluster="failover"/>
cluster="failover"可以不用寫,因為默認就是 failover
l Failfast Cluster
快速失敗,只發起一次呼叫,失敗立即報錯,通常用于非冪等性的寫操作,比如新增記錄,
dubbo:service cluster="failfast" />
或:
<dubbo:reference cluster="failfast" />
cluster="failfast"和 把 cluster="failover"、retries="0"是—樣的效果,retries="0"就是不重試
l Failsafe Cluster
失敗安全,出現例外時,直接忽略,通常用于寫入審計日志等操作,
<dubbo:service cluster="failsafe" />
或:
<dubbo:reference cluster="failsafe" />
l Failback Cluster
失敗自動恢復,后臺記錄失敗請求,定時重發,通常用于訊息通知操作,
<dubbo:service cluster="failback" />
或:
<dubbo:reference cluster="failback" />
l Forking Cluster
并行呼叫多個服務器,只要一個成功即回傳,通常用于實時性要求較高的讀操作,但需要浪費更多服務資源,可通過 forks="2"來設定最大并行數
<dubbo:service cluster=“forking" forks="2"/>
或:
<dubbo:reference cluster=“forking" forks="2"/>
配置:
服務端服務級別
<dubbo:service interface="..." loadbalance="roundrobin" />
客戶端服務級別
<dubbo:reference interface="..." loadbalance="roundrobin"
服務端方法級別
<dubbo:service interface="..."> <dubbo:meth od name="..." loadbalance
客戶端方法級別
<dubbo:reference interf ace="..."> <dubbo:method name="..." loadbalance="
6. Dubbo 通信協議 Dubbo 協議為什么要消費者比提供者個數多
因 dubbo 協議采用單一長連接,假設網路為千兆網卡(1024Mbit=128MByte),根 據測驗經驗資料每條連接最多只能壓滿 7MByte(不同的環境可能不一樣,供參考),理論上 1 個服務提供者需要 20 個服務消費者才能壓滿網卡,
7. Dubbo 通信協議 Dubbo 協議為什么不能傳大包
因 dubbo 協議采用單一長連接, 如果每次請求的資料包大小為 500KByte,假設網路為千兆網卡(1024Mbit=128MByte)每條連接最大 7MByte(不同的環境可能不一樣,供參考),
單個服務提的 TPS(每秒處理事務數)最大為:128MByte / 500KByte = 262,
單個消費者呼叫單個服務提供者的TPS(每秒處理事務數)最大為:7MByte / 500KByte = 14,
如果能接受,可以考慮使用,否則網路將成為瓶頸,
8.dubbo 通信協議 dubbo 協議為什么采用異步單一長連接
因為服務的現狀大都是服務提供者少,通常只有幾臺機器,而服務的消費者多,可能 整個網站都在訪問該服務, 比如 Morgan 的提供者只有 6 臺提供者,卻有上百臺消費者,每天有 1.5 億次呼叫,如果采用常規的 hessian 服務,服務提供者很容易就被壓跨,通過單一連接,保證單一消費者不會 壓死提供者, 長連接,減少連接握手驗證等, 并使用異步 IO,復用執行緒池,防止 C10K 問題,
9. dubbo 通信協議 dubbo 協議適用范圍和適用場景適用范圍
傳入傳出引數資料包較小(建議小于 100K),消費者比提供者個數多,單一消費者無法壓滿提供者,盡量不要用 dubbo 協議傳輸大檔案或超大字串,
適用場景:常規遠程服務方法呼叫 dubbo 協議補充: 連接個數:單連接連接方式:長連接傳輸協議:TCP 傳輸方式:NIO 異步傳輸序列化:Hessian 二進制序列化
10. RMI 協議 RMI 協議采用的標準
JDK 標準的 java.rmi.*實作,采用阻塞式短連接和 JDK 標準序列 化方式,Java 標準的遠程呼叫協議,
連接個數:多連接連接方式:短連接 傳輸協議:TCP 傳輸方式:同步傳輸 序列化:Java 標準二進制序列化
適用范圍:傳入傳出引數資料包大小混合,消費者與提供者個數差不多,可傳檔案,適用場景:常規遠程服務方法呼叫,與原生RMI 服務互操作
11. 什么是Hessian 協議
Hessian 協議用于集成 Hessian 的服務,Hessian 底層采用 Http 通訊,采用 Servlet 暴露服務,Dubbo 預設內嵌 Jetty 作為服務器實作 基于 Hessian 的遠程呼叫協議,
連接個數:多連接 連接方式:短連接 傳輸協議:HTTP 傳輸方式:同步傳輸序列化:Hessian 二進制序列化
適用范圍:傳入傳出引數資料包較大,提供者比消費者個數多,提供者 壓力較大,可傳檔案,
適用場景:頁面傳輸,檔案傳輸,或與原生hessian 服務互操作
12. http 采用 Spring 的 HttpInvoker 實作
基于 http 表單的遠程呼叫協議,連接個數:多連接 連接方式:短連接 傳輸協議:HTTP 傳輸方式:同步傳輸序列化:表單序列化(JSON)
適用范圍:傳入傳出引數資料包大小混合,提供者比消費者個數多,可用瀏覽器查看,可用表單或 URL 傳入引數, 暫不支持傳檔案,
適用場景:需同時給應用程式和瀏覽器 JS 使用的服務,
13. Webservice 基于 CXF 的 frontend-simple 和 transports-http 實作
基于WebService 的遠程呼叫協議, 連接個數:多連接
連接方式:短連接 傳輸協議:HTTP
傳輸方式:同步傳輸 序列化:SOAP 文本序列化
適用場景:系統集成,跨語言呼叫,
14. 概述下Thrif
Thrift 是 Facebook 捐給 Apache 的一個 RPC 框架,當前 dubbo 支持的 thrift 協議是對 thrift 原生協議的擴展,在原生協議的基礎上添加了一些額外的頭信 息,比如 service name,magic number 等
Spring Cloud篇
1、畫出Spring cloud 基本架構圖

- Eureka:服務注冊中心
- Feign:服務呼叫
- Ribbon:負載均衡
- Zuul/Spring Cloud Gatway:網關
這么多的系統,電商系統包含了20個子系統,每個子系統有20個核心介面,一共電商系統有400個介面,這么多的介面,直接對外暴露,前后端分離的架構,難道你讓前端的同學必須記住你的20個系統的部署的機器,他們去做負載均衡,記住400個介面微服務那塊,網關
灰度發布、統一熔斷、統一降級、統一快取、統一限流、統一授權認證
Hystrix、鏈路追蹤、stream、很多組件,Hystrix這塊東西,其實是會放在高可用的環節去說的,并不是說一個普通系統剛開始就必須得用的,沒有用好的話,反而會出問題,Hystrix線路熔斷的框架,必須得設計對應的一整套的限流方案、熔斷方案、資源隔離、降級機制,配合降級機制來做
2、Spring Cloud 組件原理
eureka 原理圖

Eureka 快取的設計目的
優化并發 并發沖突,如果操作服務注冊表,讀時加鎖防止寫,寫時加鎖不能讀,效率降低,
Feign 原理
在配置類上,加上@EnableFeginClients,那么該注解是基于@Import注解,注冊有關Fegin的決議注冊類,這個類是實作 ImportBeanDefinitionRegistrar 這個介面,重寫registryBeanDefinition 方法,他會掃描所有加了@FeginClient 的介面,然后針對這個注解的介面生成動態代理,然后你針對fegin的動態代理去呼叫他方法的時候,此時會在底層生成http協議格式的請求,
Ribbo 原理
底層的話,使用HTTP通信的框架組件,HttpClient,先得使用Ribbon去本地的Eureka注冊表的快取里獲取出來對方機器的串列,然后進行負載均衡,選出一臺機器,接著針對那臺機器發送 Http請求過去即可
Zuul 原理
配置一下不同的請求路徑和服務的對應關系,你的請求到了網關,他直接查找到匹配的服務,然后就直接把請求轉發給服務的某臺機器,Ribbon從Eureka本地的快取串列里獲取一臺機器,負載均衡,把請求直接用HTTP通信擴建發送到指定機器上去,
3、Spring Cloud 和 Dubbo 的區別
Dubbo,RPC的性能比HTTP的性能更好,并發能力更強,經過深度優化的RPC服務框架,性能和并發能力是更好一些
很多中小型公司而言,其實稍微好一點的性能,Dubbo一次請求10ms,Spring Cloud耗費20ms,對很多中小型公司而言,性能、并發,并不是最主要的因素
Spring Cloud這套架構原理,走HTTP介面和HTTP請求,就足夠滿足性能和并發的需要了,沒必要使用高度優化的RPC服務框架
Dubbo之前的一個定位,就是一個單純的服務框架而已,不提供任何其他的功能,配合的網關還得選擇其他的一些技術
Spring Cloud,中小型公司用的特別多,老系統從Dubbo遷移到Spring Cloud,新系統都是用Spring Cloud來進行開發,全家桶,主打的是微服務架構里,組件齊全,功能齊全,網關直接提供了,分布式配置中心,授權認證,服務呼叫鏈路追蹤,Hystrix可以做服務的資源隔離、熔斷降級、服務請求QPS監控、契約測驗、訊息中間件封裝、ZK封裝
勝是勝在功能齊全,中小型公司開箱即用,直接滿足系統的開發需求
Spring Cloud原來支持的一些技術慢慢的未來會演變為,跟阿里技術體系進行融合,Spring Cloud Alibaba,阿里技識訓融入Spring Cloud里面去
4、你們的服務注冊中心進行過選型調研嗎?對比一下各種服務注冊中心!
Eureka、ZooKeeper
Dubbo作為服務框架的,一般注冊中心會選擇zk
Spring Cloud作為服務框架的,一般服務注冊中心會選擇Eureka
(1)服務注冊發現的原理
集群模式

Eureka,peer-to-peer,部署一個集群,但是集群里每個機器的地位是對等的,各個服務可以向任何一個Eureka實體服務注冊和服務發現,集群里任何一個Euerka實體接收到寫請求之后,會自動同步給其他所有的Eureka實體 
ZooKeeper,服務注冊和發現的原理,Leader + Follower兩種角色,只有Leader可以負責寫也就是服務注冊,他可以把資料同步給Follower,讀的時候leader/follower都可以讀
(2)一致性保障:CP or AP
CAP,C是一致性,A是可用性,P是磁區容錯性
CP,AP
ZooKeeper是有一個leader節點會接收資料, 然后同步寫其他節點,一旦leader掛了,要重新選舉leader,這個程序里為了保證C,就犧牲了A,不可用一段時間,但是一個leader選舉好了,那么就可以繼續寫資料了,保證一致性
Eureka是peer模式,可能還沒同步資料過去,結果自己就死了,此時還是可以繼續從別的機器上拉取注冊表,但是看到的就不是最新的資料了,但是保證了可用性,強一致,最終一致性
(3)服務注冊發現的時效性
zk,時效性更好,注冊或者是掛了,一般秒級就能感知到
eureka,默認配置非常糟糕,服務發現感知要到幾十秒,甚至分鐘級別,上線一個新的服務實體,到其他人可以發現他,極端情況下,可能要1分鐘的時間,ribbon去獲取每個服務上快取的eureka的注冊表進行負載均衡
服務故障,隔60秒才去檢查心跳,發現這個服務上一次心跳是在60秒之前,隔60秒去檢查心跳,超過90秒沒有心跳,才會認為他死了,2分鐘都過去
30秒,才會更新快取,30秒,其他服務才會來拉取最新的注冊表
三分鐘都過去了,如果你的服務實體掛掉了,此時別人感知到,可能要兩三分鐘的時間,一兩分鐘的時間,很漫長
(4)容量
zk,不適合大規模的服務實體,因為服務上下線的時候,需要瞬間推送資料通知到所有的其他服務實體,所以一旦服務規模太大,到了幾千個服務實體的時候,會導致網路帶寬被大量占用
eureka,也很難支撐大規模的服務實體,因為每個eureka實體都要接受所有的請求,實體多了壓力太大,扛不住,也很難到幾千服務實體
之前dubbo技術體系都是用zk當注冊中心,spring cloud技術體系都是用eureka當注冊中心這兩種是運用最廣泛的,但是現在很多中小型公司以spring cloud居多,所以后面基于eureka說一下服務注冊中心的生產優化
5、畫圖闡述一下你們的服務注冊中心部署架構,生產環境下怎么保證高可用?


6、你們系統遇到過服務發現過慢的問題嗎?怎么優化和解決的?
zk,一般來說還好,服務注冊和發現,都是很快的
eureka,必須優化引數
· 服務器到注冊中心心跳時間設定
· 注冊中心定時檢測心跳時間設定
· 心跳失效時間設定
· readWrite快取定更新到readOnly時間設定
· 客戶端定時拉取readWrite快取時間設定
服務發現的時效性變成秒級,幾秒鐘可以感知服務的上線和下線
7、說一下自己公司的服務注冊中心怎么技術選型的?生產環境中應該怎么優化?
l 可用性
l 時效性
l 資料一致性 CP AP
l 容量
通過集群保證可用性
8、你們對網關的技術選型是怎么考慮的?能對比一下各種網關技術的優劣嗎?
網關的核心功能
(1)動態路由:新開發某個服務,動態把請求路徑和服務的映射關系熱加載到網關里去;服務增級訓器,網關自動熱感知
(2)灰度發布
(3)授權認證
(4)性能監控:每個API介面的耗時、成功率、QPS
(5)系統日志
(6)資料快取
(7)限流熔斷
幾種技術選型
Kong、Zuul、Nginx+Lua(OpenResty)、自研網關
Kong:Nginx里面的一個基于lua寫的模塊,實作了網關的功能
Zuul:基于Java開發,核心網關功能都比較簡單,但是比如灰度發布、限流、動態路由之類的,很多都要自己做二次開發,高并發能力不強,部署到一些機器上去,還要基于Tomcat來部署,Spring Boot用Tomcat把網關系統跑起來;Java語言開發,可以直接把控原始碼,可以做二次開發封裝各種需要的功能
Nginx+Lua(OpenResty): 直接通過nginx來當做網關
自研網關:自己來寫類似Zuul的網關,基于Servlet、Netty來做網關,實作上述所有的功能
9、如果網關需要抗每秒10萬的高并發訪問,你應該怎么對網關進行生產優化?

Zuul網關部署的是什么配置的機器,部署32核64G,對網關路由轉發的請求,每秒抗個小幾萬請求是不成問題的,幾臺Zuul網關機器
每秒是1萬請求,8核16G的機器部署Zuul網關,5臺機器就夠了
10、生產級的網關,應該具備我剛才說的幾個特點和功能:
(1)動態路由:新開發某個服務,動態把請求路徑和服務的映射關系熱加載到網關里去;服務增級訓器,網關自動熱感知
(2)灰度發布:基于現成的開源插件來做
(3)授權認證
(4)限流熔斷
(5)性能監控:每個API介面的耗時、成功率、QPS
(6)系統日志
(7)資料快取
11、如果需要部署上萬服務實體,現有的服務注冊中心能否抗住?如何優化?
Eureka 和 ZK都是扛不住了,(可以主動說出注冊中心的缺點)
eureka:peer-to-peer,每臺機器都是高并發請求,有瓶頸
zookeeper:服務上下線,全量通知其他服務,網路帶寬被打滿,有瓶頸
1. 可以加一個資料庫層(或者是 redis快取層),每個服務定時通過資料庫(redis快取層)來更新服務注冊表,然后資料庫(redis快取層)定時拉取注冊中心來更新注冊表,
2. 可以自研,類似于 redis 集群 加主備架構,將壓力分散開,按需拉取區域的注冊表,比如說服務A在,注冊中心1,那么只用拉取注冊中心1的注冊表,而不用將注冊中心1,2,3,4等等其他注冊拉取過來,緩解壓力,

12、說說生產環境下,你們是怎么實作網關對服務的動態路由的?
l 通過資料庫+網關定時拉取資料庫 服務注冊中心配置,
l 首先開發注冊中心配置系統,通過頁面可以動態的將增加新老服務,寫入到資料庫,
l 同時也可以通過拉取eureka來最新的服務注冊中心配置,寫入到資料庫,
l 網關定時10秒拉取資料庫的最新配置,
這樣好處減少了eureka的壓力,同時當注冊中心服務宕機,也不影響當前網關的路由,
13、你們是如何基于網關實作灰度發布的?說說你們的灰度發布方案?
1. 準備一個資料庫和一個表(也可以用Apollo配置中心、Redis、ZooKeeper,其實都可以),放一個灰度發布啟用表
2. 寫一個zuul的filter,對每個請求,zuul都會呼叫這個filter
3. 當嘗試新版本發布,修改新服務的版本為 new
4. 通過頁面 修改配置中心,或者修改資料庫表,開灰度發布,
5. 開灰度發布 網關的 filter 就會隨機 百分之1的請求 帶上 new 版本,這樣請求就會跑到新服務
6. 當新服務使用一段時間沒有問題,再將old服務全部替換成 new服務 版本設定為 current,關倍訓度發布,
14、說說你們一個服務從開發到上線,服務注冊、網關路由、服務呼叫的流程?
spring cloud 原理圖,
注冊中心 eureka
網關 zuul
服務呼叫 fegin
負載均衡 ribbon
15、什么是 Spring Cloud?
Spring cloud 流應用程式啟動器是基于 Spring Boot 的 Spring 集成應用程式,提供與外部系統的集成,Spring cloud Task,一個生命周期短暫的微服務框架,用于快速構建執行有限資料處理的應用程式,
16、使用 Spring Cloud 有什么優勢?
使用 Spring Boot 開發分布式微服務時,我們面臨以下問題
- 與分布式系統相關的復雜性-這種開銷包括網路問題,延遲開銷,帶寬問題,安全問題,
- 服務發現-服務發現工具管理群集中的流程和服務如何查找和互相交談, 它涉及一個服務目錄,在該目錄中注冊服務,然后能夠查找并連接到該目錄中的服務,
- 冗余-分布式系統中的冗余問題,
- 負載平衡 --負載平衡改善跨多個計算資源的作業負荷,諸如計算機,計算機集群,網路鏈路,中央 處理單元,或磁盤驅動器的分布,
- 性能-問題 由于各種運營開銷導致的性能問題,
- 部署復雜性-Devops 技能的要求,
17、服務注冊和發現是什么意思?Spring Cloud 如何實作?
當我們開始一個專案時,我們通常在屬性檔案中進行所有的配置,隨著越來越多的服務開發和部署,添加和修改這些屬性變得更加復雜,有些服務可能會下降,而某些位置可能會發生變化,手動更改屬性可能會產生問 題,Eureka 服務注冊和發現可以在這種情況下提供幫助,由于所有服務都在 Eureka 服務器上注冊并通過呼叫 Eureka 服務器完成查找,因此無需處理服務地點的任何更改和處理,
18、負載平衡的意義什么?
在計算中,負載平衡可以改善跨計算機,計算機集群,網路鏈接,中央處理單元或磁盤驅動器等多種計算資源的作業負載分布,負載平衡旨在優化資源使用,最大化吞吐量,最小化回應時間并避免任何單一資源的過載,使用多個組件進行負載平衡而不是單個組件可能會通過冗余來提 高可靠性和可用性,負載平衡通常涉及專用軟體或硬體,例如多層交換機或域名系統服務器行程,
19、什么是 Hystrix?它如何實作容錯?
Hystrix 是一個延遲和容錯庫,旨在隔離遠程系統,服務和第三方庫的訪問點,當出現故障是不可避免的故障時,停止級聯故障并在復雜的分布式系統中實作彈性,
通常對于使用微服務架構開發的系統,涉及到許多微服務,這些微服務彼此協作,
思考一下微服務

假設如果上圖中的微服務 9 失敗了,那么使用傳統方法我們將傳播一個例外,但這仍然會導致整個系統崩潰,
隨著微服務數量的增加,這個問題變得更加復雜,微服務的數量可以高達1000.這是 hystrix 出現的地方, 我們將使用 Hystrix 在這種情況下的Fallback 方法功能,我們有兩個服務 employee-consumer 使用由employee-consumer 公開的服務,

20、什么是 Hystrix 斷路器?我們需要它嗎?
由于某些原因,employee-consumer 公開服務會引發例外,在這種情況下使用 Hystrix 我們定義了一個 回退方法,如果在公開服務中發生例外,則回退方法回傳一些默認值,

如果 firstPage method() 中的例外繼續發生,則 Hystrix 電路將中斷,并且員工使用者將一起跳過 firtsPage 方法,并直接呼叫回退方法,斷路器的目的是給第一頁方法或第一頁方法可能呼叫的其他方法 留出時間,并導致例外恢復,可能發生的情況是,在負載較小的情況下,導致例外的問題有 更好的恢復機會,

21、什么是 Netflix Feign?它的優點是什么?
Feign 是受到 Retrofit,JAXRS-2.0 和 WebSocket 啟發的 java 客戶端聯編程式,Feign 的第一個目標是將約束分母的復雜性統一到 http apis,而不考慮其穩定性,在 employee-consumer 的例子中,我們使用了 employee- producer 使用 REST 模板公開的 REST 服務,
但是我們必須撰寫大量代碼才能執行以下步驟
- 使用功能區進行負載平衡,
- 獲取服務實體,然后獲取基本 URL,
- 利用 REST 模板來使用服務,前面的代碼如下
@Controller
public class ConsumerControllerClient {
@Autowired
private LoadBalancerClient loadBalancer;
public void getEmployee() throws RestClientException, IOExc eption {
ServiceInstance serviceInstance=loadBalancer.choose("employ ee-producer");
System.out.println(serviceInstance.getUri());
String baseUrl=serviceInstance.getUri().toString();
baseUrl=baseUrl+"/employee";
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> response=null;
try{
response=restTemplate.exchange(baseUrl,
HttpMethod.GET, getHeaders(),String.class);
}catch (Exception ex)
{
System.out.println(ex);
}
System.out.println(response.getBody());
之前的代碼,有像 NullPointer 這樣的例外的機會,并不是最優的,我們將看到如何使用 Netflix Feign 使呼叫變得更加輕松和清潔,如果 Netflix Ribbon 依賴關系也在類路徑中,那么 Feign 默認也會負責負載平衡,
22、什么是 Spring Cloud Bus?我們需要它嗎?
考慮以下情況:我們有多個應用程式使用 Spring Cloud Config 讀取屬性, 而 Spring Cloud Config 從 GIT 讀取這些屬性,
下面的例子中多個員工生產者模塊從 Employee Config Module 獲取Eureka 注冊的財產,

如果假設 GIT 中的 Eureka 注冊屬性更改為指向另一臺 Eureka 服務器,會發生什么情況,在這種情況下,我們將不得不重新啟動服務以獲取更新的屬性,
還有另一種使用執行器斷點/重繪的方式,使我們將不得不為每個模塊單 獨呼叫這個 url,例如,如果 Employee Producer1 部署在埠 8080 上, 則呼叫 http:// localhost:8080 / refresh,同樣對于 Employee Producer2 http:// localhost:8081 / refresh 等等,這又很麻煩,這就是 Spring Cloud Bus 發揮作用的地方,

Spring Cloud Bus 提供了跨多個實體重繪配置的功能,因此,在上面的示例中,如果我們重繪 Employee Producer1,則會自動重繪所有其他必需的模塊,如果我們有多個微服務啟動并運行,這特別 有用,這是通過將所有微服務連接到單個訊息代理來實作的,無論何時重繪實體,此事件都會訂閱到偵聽 此代理的所有微服務,并且它們也會重繪,可以通過使用端點/ 總線/重繪來實作對任何單個實體的重繪,
微服務面試合輯 word檔案下載地址:鏈接:https://pan.baidu.com/s/1KdvrTt7bpxS2QMwlbhMErw
提取碼:1111爆肝一周,不眠不休!就為 點贊+好評+收藏 三連
并發編程面試篇合輯
并發編程(上)
1、Synchronized用過嗎?其原理是什么?
這是一道Java面試中幾乎百分百會問到的問題,因為沒有任何寫過并發程式的開發者會沒聽說或者沒接觸過Synchronized,Synchronized是由JVM 實作的一種實作互斥同步的一種方式,如果你查看被Synchronized修飾過的程式塊編譯后的位元組碼,會發現,被Synchronized修飾過的程式塊,在 編譯前后被編譯器生成了monitorenter和monitorexit兩個位元組碼指令,這兩 個指令是什么意思呢2在虛擬機執行到monitorenter指令時,首先要嘗試獲取物件的鎖:如果這個物件沒有鎖定,或者當前執行緒已經擁有了這個對 象的鎖,把鎖的計數器+ 1;當執行monitorexit指令時將鎖計數器-1;當計數器為0時,鎖就被釋放了,如果獲取物件失敗了,那當前執行緒就要阻塞 等待,直到物件鎖被另外一個執行緒釋放為止,Java中Synchronize通過在對 象頭設定標記,達到了獲取鎖和釋放鎖的目的,
2、你剛才提到獲取物件的鎖?這個 “鎖 ”到底是什么2如何確定物件的鎖2
“鎖 ”的本質其實是monitorenter和monitorexit位元組碼指令的一個Reference 型別的引數,即要鎖定和解鎖的物件,我們知道,使用Synchronized 可以修飾不同的物件,因此,對應的物件鎖可以這么確定,
1. 如果 Synchronized 明確指定了鎖物件,比如 Synchronized(變數名)、Synchronized(this) 等,說明加解鎖物件為該物件,
2. 如果沒有明確指定:
若 Synchronized 修飾的方法為非靜態方法,表示此方法對應的物件為 鎖物件;
若 Synchronized 修飾的方法為靜態方法,則表示此方法對應的類物件 為鎖物件,
注意,當一個物件被鎖住時,物件里面所有用 Synchronized 修飾的 方法都將產生堵塞,而物件里非 Synchronized 修飾的方法可正常被 呼叫,不受鎖影響,
3、什么是可重入性?為什么說 Synchronized 是可重入鎖?
可重入性是鎖的一個基本要求,是為了解決自己鎖死自己的情況, 比如下面的偽代碼,一個類中的同步方法呼叫另一個同步方法,假如Synchronized 不支持重入,進入 method2 方法時當前執行緒獲得鎖,method2 方法里面執行 method1 時當前執行緒又要去嘗試獲取鎖,這 時如果不支持重入,它就要等釋放,把自己阻塞,導致自己鎖死自己,
對 Synchronized 來說,可重入性是顯而易見的,剛才提到,在執行monitorenter 指令時,如果這個物件沒有鎖定,或者當前執行緒已經擁有了這個物件的鎖(而不是已擁有了鎖則不能繼續獲取),就把鎖的計 數器 +1, 其實本質上就通過這種方式實作了可重入性,
4、JVM 對 Java 的原生鎖做了哪些優化?
在 Java 6 之前,Monitor 的實作完全依賴底層作業系統的互斥鎖來 實作, 也就是我們剛才在問題二中所闡述的獲取/釋放鎖的邏輯,
由于 Java 層面的執行緒與作業系統的原生執行緒有映射關系,如果要將一個執行緒進行阻塞或喚起都需要作業系統的協助,這就需要從用戶態切換 到內核態來執行,這種切換代價十分昂貴,很耗處理器時間,現代 JDK 中做了大量的優化, 一種優化是使用自旋鎖,即在把執行緒進行阻塞操作之前先讓執行緒自旋等 待一段時間,可能在等待期間其他執行緒已經解鎖,這時就無需再讓執行緒 執行阻塞操作,避免了用戶態到內核態的切換,
現代 JDK 中還提供了三種不同的 Monitor 實作,也就是三種不同的鎖:
? 偏向鎖(Biased Locking)
? 輕量級鎖
? 重量級鎖
這三種鎖使得 JDK 得以優化 Synchronized 的運行,當 JVM 檢測 到不同的競爭狀況時,會自動切換到適合的鎖實作,這就是鎖的升級、 降級,
? 當沒有競爭出現時,默認會使用偏向鎖,
JVM 會利用 CAS 操作,在物件頭上的 Mark Word 部分設定執行緒 ID,以表示這個物件偏向于當前執行緒,所以并不涉及真正的互斥鎖,因 為在很多應用場景中,大部分物件生命周期中最多會被一個執行緒鎖定, 使用偏斜鎖可以降低無競爭開銷,
? 如果有另一執行緒試圖鎖定某個被偏斜過的物件,JVM 就撤銷偏斜鎖, 切換到輕量級鎖實作,
? 輕量級鎖依賴 CAS 操作 Mark Word 來試圖獲取鎖,如果重試成功, 就使用普通的輕量級鎖;否則,進一步升級為重量級鎖,
5、為什么說 Synchronized 是非公平鎖?
非公平主要表現在獲取鎖的行為上,并非是按照申請鎖的時間前后給等 待執行緒分配鎖的,每當鎖被釋放后,任何一個執行緒都有機會競爭到鎖, 這樣做的目的是為了提高執行性能,缺點是可能會產生執行緒饑餓現象,
6、什么是鎖消除和鎖粗化?
? 鎖消除:指虛擬機即時編譯器在運行時,對一些代碼上要求同步,但被 檢測到不可能存在共享資料競爭的鎖進行消除,主要根據逃逸分析,
程式員怎么會在明知道不存在資料競爭的情況下使用同步呢?很多不是 程式員自己加入的,
? 鎖粗化:原則上,同步塊的作用范圍要盡量小,但是如果一系列的連續 操作都對同一個物件反復加鎖和解鎖,甚至加鎖操作在回圈體內,頻繁 地進行互斥同步操作也會導致不必要的性能損耗,
鎖粗化就是增大鎖的作用域,
7、為什么說 Synchronized 是一個悲觀鎖?樂觀鎖的實作原理 又是什么?什么是 CAS?它有什么特性?
Synchronized 顯然是一個悲觀鎖,因為它的并發策略是悲觀的: 不管是否會產生競爭,任何的資料操作都必須要加鎖、用戶態核心態轉 換、維護鎖計數器和檢查是否有被阻塞的執行緒需要被喚醒等操作,隨著硬體指令集的發展,我們可以使用基于沖突檢測的樂觀并發策略,先進行操作,如果沒 有其他執行緒征用資料,那操作就成功了; 如果共享資料有征用,產生了沖突,那就再進行其他的補償措施,這種樂觀的并發策略的許多實作不需要執行緒掛起,所以被稱為非阻塞同步,樂觀鎖的核心演算法是CAS(Compareand Swap,比較并交換),它涉及到三個運算元:記憶體值、預期值、新值,當且僅當預期值和記憶體值相等時才將記憶體值修改為新值, 這樣處理的邏輯是,首先檢查某塊記憶體的值是否跟之前我讀取時的一 樣, 如不一樣則表示期間此記憶體值已經被別的執行緒更改過,舍棄本次操 作,否則說明期間沒有其他執行緒對此記憶體值操作,可以把新值設定給此 塊記憶體,
CAS 具有原子性,它的原子性由 CPU 硬體指令實作保證,即使用 JNI 呼叫 Native 方法呼叫由 C++ 撰寫的硬體級別指令,JDK 中提 供了 Unsafe 類執行這些操作,
8、樂觀鎖一定就是好的嗎?
樂觀鎖避免了悲觀鎖獨占物件的現象,同時也提高了并發性能,但它也有缺點:
1. 樂觀鎖只能保證一個共享變數的原子操作,如果多一個或幾個變數,樂觀鎖將變得力不從心,但互斥鎖能輕易解決,不管物件數量多少及物件顆粒度大小,
2. 長時間自旋可能導致開銷大,假如 CAS 長時間不成功而一直自旋,會給 CPU 帶來很大的開銷,
3. ABA 問題,CAS 的核心思想是通過比對記憶體值與預期值是否一樣而判斷記憶體值是否被改過,但這個判斷邏輯不嚴謹,假如記憶體值原來是 A, 后來被一條執行緒改為 B,最后又被改成了 A,則 CAS 認為此記憶體值并沒有發生改變,但實際上是有被其他執行緒改過的,這種情況對依賴程序值的情景 的運算結果影響很大,解決的思路是引入版本號,每次變數更新都把版本號加一,
9、跟 Synchronized 相比?可重入鎖 ReentrantLock 其實作 原理有什么不同?
其實,鎖的實作原理基本是為了達到一個目的: 讓所有的執行緒都能看到某種標記,
Synchronized 通過在物件頭中設定標記實作了這一目的,是一種 JVM 原生的鎖實作方式,而 ReentrantLock 以及所有的基于 Lock 介面的 實作類,都是通過用一個 volitile 修飾的 int 型變數,并保證每個線 程都能擁有對該 int 的可見性和原子修改,其本質是基于所謂的 AQS 框架,
10 、 那 么 請 談 談 AQS 框 架 是 怎 么 回 事 兒 ?
AQS(AbstractQueuedSynchronizer 類)是一個用來構建鎖和同步器的框架, 各種 Lock 包中的鎖(常用的有 ReentrantLock、 ReadWriteLock) ,以及其他如 Semaphore、CountDownLatch, 甚至是早期的 FutureTask 等,都是基于 AQS 來構建,
1. AQS 在內部定義了一個 volatile int state 變數,表示同步狀態:當執行緒呼叫 lock 方法時 ,如果 state=0,說明沒有任何執行緒占有共享資源 的鎖,可以獲得鎖并將 state=1;如果 state=1,則說明有執行緒目前正在 使用共享變數,其他執行緒必須加入同步佇列進行等待,
2. AQS 通過 Node 內部類構成的一個雙向鏈表結構的同步佇列,來完成執行緒獲取鎖的排隊作業,當有執行緒獲取鎖失敗后,就被添加到佇列末尾,
? Node 類是對要訪問同步代碼的執行緒的封裝,包含了執行緒本身及其狀態叫
waitStatus(有五種不同取值,分別表示是否被阻塞,是否等待喚醒, 是否已經被取消等),每個 Node 結點關聯其 prev 結點和 next 結 點,方便執行緒釋放鎖后快速喚醒下一個在等待的執行緒,是一個 FIFO 的程序,
? Node 類有兩個常量,SHARED 和 EXCLUSIVE,分別代表共享模式和獨占模式,所謂共享模式是一個鎖允許多條執行緒同時操作(信號量Semaphore 就是基于 AQS 的共享模式實作的),獨占模式是同一個時間段只能有一個執行緒對共享資源進行操作,多余的請求執行緒需要排隊等待 ( 如 ReentranLock) ,
3. AQS 通過內部類 ConditionObject 構建等待佇列(可有多個),當Condition 呼叫 wait() 方法后,執行緒將會加入等待佇列中,而當Condition 呼叫 signal() 方法后,執行緒將從等待佇列轉移動同步佇列中進行鎖競爭,
4. AQS 和 Condition 各自維護了不同的佇列,在使用 Lock 和 Condition
的時候,其實就是兩個佇列的互相移動,
11、請盡可能詳盡地對比下 Synchronized 和 ReentrantLock 的異同
ReentrantLock 是 Lock 的實作類,是一個互斥的同步鎖,從功能角度, ReentrantLock 比 Synchronized 的同步操作更精細(因為可以像普通物件一樣使用),甚至實作 Synchronized 沒有的高級功能,如:
? 等待可中斷:當持有鎖的執行緒長期不釋放鎖的時候,正在等待的執行緒可以選擇放棄等待,對處理執行時間非常長的同步塊很有用,
? 帶超時的獲取鎖嘗試:在指定的時間范圍內獲取鎖,如果時間到了仍然無法獲取則回傳,
? 可以判斷是否有執行緒在排隊等待獲取鎖,
? 可以回應中斷請求:與 Synchronized 不同,當獲取到鎖的執行緒被中斷時,能夠回應中斷,中斷例外將會被拋出,同時鎖會被釋放,
? 可以實作公平鎖,
從鎖釋放角度,Synchronized 在 JVM 層面上實作的,不但可以通過一些 監控工具監控 Synchronized 的鎖定,而且在代碼執行出現例外 時,JVM 會自動釋放鎖定;但是使用 Lock 則不行,Lock 是通過代碼實作的,要保證鎖定一定會被釋放,就必須將 unLock() 放到 finally{} 中 ,
從性能角度,Synchronized 早期實作比較低效,對比ReentrantLock,大多數場景性能都相差較大,
但是在 Java 6 中對其進行了非常多的改進,在競爭不激烈時, Synchronized 的性能要優于 ReetrantLock;在高競爭情況下, Synchronized 的性能會下降幾十倍,但是 ReetrantLock 的性能能維持常態,
12、ReentrantLock 是如何實作可重入性的?
ReentrantLock 內部自定義了同步器 Sync(Sync 既實作了 AQS, 又實作了 AOS,而 AOS 提供了一種互斥鎖持有的方式),其實就是 加鎖的時候通過CAS 演算法,將執行緒物件放到一個雙向鏈表中,每次獲 取鎖的時候,看下當前維護的那個執行緒 ID 和當前請求的執行緒 ID 是否 一樣,一樣就可重入了,
13、除了 ReetrantLock?你還接觸過 JUC 中的哪些并發工具?
通常所說的并發包(JUC)也就是 java.util.concurrent 及其子包,集中了 Java并發的各種基礎工具類,具體主要包括幾個方面:
? 提供了 CountDownLatch、CyclicBarrier、Semaphore等,比Synchronized 更加高級,可以實作更加豐富多執行緒操作的同步結構,
? 提供了 ConcurrentHashMap、有序的 ConcunrrentSkipListMap,或者通過類似快斬訓制實作執行緒安全的動態陣列 CopyOnWriteArrayList 等各種執行緒安全的容器,
? 提供了 ArrayBlockingQueue、 SynchorousQueue 或針對特定場景的
PriorityBlockingQueue 等,各種并發佇列實作,
? 強大的 Executor 框架,可以創建各種不同型別的執行緒池,調度任務運行等,
14、請談談 ReadWriteLock 和 StampedLock)
雖然 ReentrantLock 和 Synchronized 簡單實用,但是行為上有一定局限性,要么不占,要么獨占,實際應用場景中,有時候不需要大量 競爭的寫操作,而是以并發讀取為主,為了進一步優化并發操作的粒 度,Java 提供了讀寫鎖,讀寫鎖基于的原理是多個讀操作不需要互斥,如果讀鎖試圖鎖定時,寫鎖是被某個執行緒持有,讀鎖將無法獲得,而只好等待對方操作 結束,這樣就可以自動保證不會讀取到有爭議的資料,

讀寫鎖看起來比 Synchronized 的粒度似乎細一些,但在實際應用 中,其表現也并不盡如人意,主要還是因為相對比較大的開銷,所以,JDK 在后期引入了 StampedLock,在提供類似讀寫鎖的同時,還支持優化讀模式,優化讀基于假設,大多數情況下讀操作并不會和寫 操作沖突,其邏輯是先試著修改,然后通過 validate 方法確認是否進入了寫模式,如果沒有進入,就成功避免了開銷;如果進入,則嘗試獲取讀鎖,

15、如何讓 Java 的執行緒彼此同步?你了解過哪些同步器?請分別介紹下
JUC 中的同步器三個主要的成員:CountDownLatch、CyclicBarrier 和Semaphore,通過它們可以方便地實作很多執行緒之間協作的功能,CountDownLatch 叫倒計數,允許一個或多個執行緒等待某些操作完成,看幾個場景:
? 跑步比賽,裁判需要等到所有的運動員(“其他執行緒”)都跑到終點 (達到目標),才能去算排名和頒獎,
? 模擬并發,我需要啟動 100 個執行緒去同時訪問某一個地址,我希望它 們能同時并發,而不是一個一個的去執行,
用法:CountDownLatch 構造方法指明計數數量,被等待執行緒呼叫countDown 將計數器減 1,等待執行緒使用 await 進行執行緒等待,一 個簡單的例子:

CyclicBarrier 叫回圈柵欄,它實作讓一組執行緒等待至某個狀態之后再全部同時執行,而且當所有等待執行緒被釋放后,CyclicBarrier 可以被 重復使用,CyclicBarrier 的典型應用場景是用來等待并發執行緒結束,CyclicBarrier 的主要方法是 await(),await() 每被呼叫一次,計數便 會減少 1,并阻塞住當前執行緒,當計數減至 0 時,阻塞解除,所有在 此 CyclicBarrier 上面阻塞的執行緒開始運行,
在這之后,如果再次呼叫 await(),計數就又會變成 N-1,新一輪重新開始,這便是 Cyclic 的含義所在,CyclicBarrier.await() 帶有回傳值,用來表示當前執行緒是第幾個到達這個 Barrier 的執行緒,
舉例說明如下:

Semaphore,Java 版本的信號量實作,用于控制同時訪問的執行緒個數,來
達到限制通用資源訪問的目的,其原理是通過 acquire() 獲取一個許可,如果沒有就等待,而 release() 釋放一個許可,
如果 Semaphore 的數值被初始化為1,那么一個執行緒就可以通過 acquire進入互斥狀態,本質上和互斥鎖是非常相似的,
但是區別也非常明顯,比如互斥鎖是有持有者的,而對于 Semaphore 這種計數器結構,雖然有類似功能,但其實不存在真正意義的持有者,除非我們進行擴展包裝,
16、CyclicBarrier 和 CountDownLatch 看起來很相似?請對比下呢?
它們的行為有一定相似度,區別主要在于:
? CountDownLatch 是不可以重置的,所以無法重用,CyclicBarrier 沒有這種限制,可以重用,
? CountDownLatch 的基本操作組合是 countDown/await,呼叫 await 的執行緒阻塞等待 countDown 足夠的次數,不管你是在一個執行緒還是多個執行緒里 countDown,只要次數足夠即可,CyclicBarrier 的基本操作組合就是await,當所有的伙伴都呼叫了 await,才會繼續進行任務,并自動進行重置,
CountDownLatch 目的是讓一個執行緒等待其他 N 個執行緒達到某個條件后, 自己再去做某個事(通過 CyclicBarrier 的第二個構造方法 public CyclicBarrier(int parties, Runnable barrierAction),在新執行緒里做事可以達到同樣的效果),而 CyclicBarrier 的目的是讓 N 多 執行緒互相等待直到所有的都達到某個狀態,然后這 N 個執行緒再繼續執行各自后續(通過CountDownLatch 在某些場合也能完成類似的效果),
17、Java 中的執行緒池是如何實作的?
? 在 Java 中,所謂的執行緒池中的“執行緒”,其實是被抽象為了一個靜態 內部類 Worker,它基于 AQS 實作,存放在執行緒池的HashSet workers 成員變數中;
? 而需要執行的任務則存放在成員變數 workQueue(BlockingQueue workQueue)中,
這樣,整個執行緒池實作的基本思想就是:從 workQueue 中不斷取出 需要執行的任務,放在 Workers 中進行處理,
18、創建執行緒池的幾個核心構造引數?
Java 中的執行緒池的創建其實非常靈活,我們可以通過配置不同的參 數, 創建出行為不同的執行緒池,這幾個引數包括:
? corePoolSize:執行緒池的核心執行緒數,
? maximumPoolSize:執行緒池允許的最大執行緒數,
? keepAliveTime:超過核心執行緒數時閑置執行緒的存活時間,
? workQueue:任務執行前保存任務的佇列,保存由 execute 方法提交的Runnable 任務 ,
19、執行緒池中的執行緒是怎么創建的?是一開始就隨著執行緒池的啟動創建好的嗎?
顯然不是的,執行緒池默認初始化后不啟動 Worker,等待有請求時才啟動,
每當我們呼叫 execute() 方法添加一個任務時,執行緒池會做如下判 斷:
? 如果正在運行的執行緒數量小于 corePoolSize,那么馬上創建執行緒運行這個任務;
? 如果正在運行的執行緒數量大于或等于 corePoolSize,那么將這個任務放入佇列;
? 如果這時候佇列滿了,而且正在運行的執行緒數量小于
maximumPoolSize,那么還是要創建非核心執行緒立刻運行這個任務;
? 如果佇列滿了,而且正在運行的執行緒數量大于或等于 maximumPoolSize,那么執行緒池會拋出例外 RejectExecutionException,當一個執行緒完成任務時,它會從佇列中取下一個任務來執行,當一個執行緒無事可做,超過一定的時間(keepAliveTime)時,執行緒池會判斷,
如果當前運行的執行緒數大于 corePoolSize,那么這個執行緒就被停掉,所以執行緒池的所有任務完成后,它最侄訓收縮到 corePoolSize 的大小,
20、既然提到可以通過配置不同引數創建出不同的執行緒池?那么 Java 中默認實作好的執行緒池又有哪些呢?請比較它們的異同)
1. SingleThreadExecutor 執行緒池
這個執行緒池只有一個核心執行緒在作業,也就是相當于單執行緒串行執行所 有任務,如果這個唯一的執行緒因為例外結束,那么會有一個新的執行緒來 替代它,此執行緒池保證所有任務的執行順序按照任務的提交順序執行,
- corePoolSize:1,只有一個核心執行緒在作業,
- maximumPoolSize: 1,
- keepAliveTime: 0L,
- workQueue:new LinkedBlockingQueue<Runnable>()其緩沖佇列是無界的
2.FixedThreadPool 執行緒池
FixedThreadPool 是固定大小的執行緒池,只有核心執行緒,每次提交一個任務就創建一個執行緒,直到執行緒達到執行緒池的最大大小,執行緒池的大小一旦達到最大值就會保持不變,如果某個執行緒因為執行例外而結束,那么執行緒池會補充一個新執行緒,
FixedThreadPool 多數針對一些很穩定很固定的正規并發執行緒,多用于服務器,
- corePoolSize: nThreads
- maximumPoolSize: nThreads
- keepAliveTime: 0L
- workQueue:new LinkedBlockingQueue<Runnable>()其緩沖佇列是無界的
3.CachedThreadPool 執行緒池
CachedThreadPool 是無界執行緒池,如果執行緒池的大小超過了處理任務所需要的執行緒,那么就會回收部分空閑(60秒不執行任務)執行緒,當 任務數增加時,此執行緒池又可以智能的添加新執行緒來處理任務,執行緒池大小完全依賴于作業系統(或者說 JVM)能夠創建的最大執行緒大小,SynchronousQueue 是一個是緩沖區為 1 的阻塞佇列,快取型池子通常用于執行一些生存期很短的異步型任務,因此在一些面向連接的 daemon 型 SERVER 中用得不多,但對于生存期短的異步任務,它是 Executor 的首選,
- corePoolSize: 0
- maximumPoolSize: Integer.MAX_VALUE
- keepAliveTime: 60L
- workQueue:new SynchronousQueue<Runnable>()
一個是緩沖區為1 的阻塞佇列,
4.ScheduledThreadPool執行緒池
ScheduledThreadPool核心執行緒池固定,大小無限的執行緒池,此執行緒池支持定時以及周期性執行任務的需求,創建一個周期性執行任務的執行緒池,如果閑置,非核心執行緒池會在DEFAULT_KEEPALIVEMILLIS時間內回收
- corePoolSize: corePoolSize
- maximumPoolSize: Integer.MAX_VALUE
- keepAliveTime: DEFAULT_KEEPALIVE_MILLIS
- workQueue:new DelayedWorkQueue()
21、如何在 Java 執行緒池中提交執行緒?
執行緒池最常用的提交任務的方法有兩種:
1. execute(): ExecutorService.execute 方法接收一個例,它用來執行一個任務:

2. submit(): ExecutorService.submit() 方法回傳的是 Future 物件,可以用
isDone() 來查詢 Future 是否已經完成,當任務完成時, 它具有一個結果, 可以呼叫 get() 來獲取結果,也可以不用 isDone() 進行檢查就直接呼叫get(),在這種情況下,get() 將阻塞,直至結果準備就緒,

22、什么是 Java 的記憶體模型?Java 中各個執行緒是怎么彼此看到對方的變數的?
Java 的記憶體模型定義了程式中各個變數的訪問規則,即在虛擬機中將 變數存盤到記憶體和從記憶體中取出這樣的底層細節,此處的變數包括實體欄位、靜態欄位和構成陣列物件的元素,但是不包括區域變數和方法引數, 因為這些是執行緒私有的,不會被共享,所以不存在競爭問題,
Java 中各個執行緒是怎么彼此看到對方的變數的呢?Java 中定義了主內 存與作業記憶體的概念:
所有的變數都存盤在主記憶體,每條執行緒還有自己的作業記憶體,保存了被 該執行緒使用到的變數的主記憶體副本拷貝,
執行緒對變數的所有操作(讀取、賦值)都必須在作業記憶體中進行,不能直接讀寫主記憶體的變數,不同的執行緒之間也無法直接訪問對方作業記憶體的變數,執行緒間變數值的傳遞需要通過主記憶體,
23、請談談 volatile 有什么特點?為什么它能保證變數對所有執行緒的可見性?
關鍵字 volatile 是 Java 虛擬機提供的最輕量級的同步機制,當一個 變數被定義成 volatile 之后,具備兩種特性:
1. 保證此變數對所有執行緒的可見性,當一條執行緒修改了這個變數的值,新值對于其他執行緒是可以立即得知的,而普通變數做不到這一點,
2. 禁止指令重排序優化,普通變數僅僅能保證在該方法執行程序中,得到正確結果,但是不保證程式代碼的執行順序,
Java 的記憶體模型定義了 8 種記憶體間操作:
lock 和 unlock
? 把一個變數標識為一條執行緒獨占的狀態,
? 把一個處于鎖定狀態的變數釋放出來,釋放之后的變數才能被其他執行緒鎖定,
read 和 write
? 把一個變數值從主記憶體傳輸到執行緒的作業記憶體,以便 load,
? 把 store 操作從作業記憶體得到的變數的值,放入主記憶體的變數中,
load 和 store
? 把 read 操作從主記憶體得到的變數值放入作業記憶體的變數副本中,
? 把作業記憶體的變數值傳送到主記憶體,以便 write,
use 和 assgin
? 把作業記憶體變數值傳遞給執行引擎,
? 將執行引擎值傳遞給作業記憶體變數值,
volatile 的實作基于這 8 種記憶體間操作,保證了一個執行緒對某個 volatile 變數的修改,一定會被另一個執行緒看見,即保證了可見性,
24、既然 volatile 能夠保證執行緒間的變數可見性?是不是就意味著基于volatile變數的運算就是并發安全的?
顯然不是的,基于 volatile 變數的運算在并發下不一定是安全的,volatile 變數在各個執行緒的作業記憶體,不存在一致性問題(各個執行緒的作業記憶體中volatile 變數,每次使用前都要重繪到主記憶體),
但是 Java 里面的運算并非原子操作,導致 volatile 變數的運算在并發下一樣是不安全的,
25、請對比下 volatile 對比 Synchronized 的異同)
Synchronized 既能保證可見性,又能保證原子性,而 volatile 只能保證可見性,無法保證原子性,
ThreadLocal 和 Synchonized 都用于解決多執行緒并發訪問,防止任務在共享資源上產生沖突,但是 ThreadLocal 與 Synchronized 有本質的區別,Synchronized 用于實作同步機制,是利用鎖的機制使變數或代碼塊在某一時刻只能被一個執行緒訪問,是一種 “以時間換空間” 的方式,
而 ThreadLocal 為每一個執行緒都提供了變數的副本,使得每個執行緒在某一時間訪問到的并不是同一個物件,根除了對變數的共享,是一種 “以空間換時間” 的方式,
26、請談談 ThreadLocal 是怎么解決并發安全的?
ThreadLocal 這是 Java 提供的一種保存執行緒私有資訊的機制,因為 其在整個執行緒生命周期內有效,所以可以方便地在一個執行緒關聯的不同業務模塊之間傳遞資訊,比如事務 ID、Cookie 等背景關系相關資訊,ThreadLocal 為每一個執行緒維護變數的副本,把共享資料的可見范圍限 制在同一個執行緒之內,其實作原理是,在 ThreadLocal 類中有一個 Map,用于存盤每一個執行緒的變數的副本,
27、很多人都說要慎用 ThreadLocal?談談你的理解?使用ThreadLocal 需要注意些什么?
使用 ThreadLocal 要注意 remove!
ThreadLocal 的實作是基于一個所謂的 ThreadLocalMap,在ThreadLocalMap 中,它的 key 是一個弱參考,
通常弱參考都會和參考佇列配合清理機制使用,但是 ThreadLocal 是個例外,它并沒有這么做,
這意味著,廢棄專案的回收依賴于顯式地觸發,否則就要等待執行緒結束, 進而回收相應 ThreadLocalMap!這就是很多 OOM 的來源,所 以通常都會建議,應用一定要自己負責 remove,并且不要和執行緒池配 合,因為worker 執行緒往往是不會退出的,
并發編程(下)
1、Java start 如何呼叫到run方法
java層面: start -> start0() -> native start0()
C(jvm)層面: JVM_StartThread()
OS層面: pthread_create() 這里會回呼jvm的run方法
java呼叫 native方法,native 方法對應著 頭檔案,頭檔案會動態鏈接到 c檔案,c檔案會呼叫 系統函式,
2、synchronized 關鍵字的底層原理,synchronize鎖是如何實作的?
-
首先每個類都由Objec派生出,每個物件都有ObjectMonitor,當執行緒發生同步,會去嘗試將ObjectMonitor 的 owner 設定為自己,如果沒有獲得就會進入entryList中,
- 獲取鎖,monitor 的計數器就會加1,owner 就指向當前執行緒,同時synchronized 是支持重入鎖,也就是同一個執行緒對同一個物件多次加鎖,每加鎖一次,計數器就會加1,
- 獲取鎖,執行緒進入同步塊,虛擬機就會設定 monitorenter進入同步塊,退出同步就會設定為monitorexit,為了防止同步中出現例外,設定了第二個monitorexit,
- 當退出同步,計數器就變為 0,owner 設定為 null,entryList中執行緒就會CAS去競爭獲取物件monitor關聯的鎖,只有一個執行緒可以獲取到鎖,
- 當遇到wait就將同步的執行緒放入waitSet中,
- 當物件呼叫notify,就會隨機從waitSet取一個執行緒,放到entryList中,然后執行緒去競爭monitor,
- 當物件呼叫notifyAll,就會從waitSet將持有該物件的所有的執行緒,放到entryList中,然后執行緒去競爭monitor,
wait
wait 會將執行緒從entryList 放回到waitSet中,
3、notify 區別和 notifyAll
notify 會從waitSet《等待佇列》中隨機拿取那一個執行緒放到entryList《阻塞佇列》中
notifyAll 會將waitSet所有執行緒都放到entryList中,喚醒哪個不確定,因為不確定誰競爭到了monitor,
4、synchronize鎖優化鎖膨脹程序?
首先synchronize的鎖的狀態在物件頭中,
64位jdk 對應的 物件頭中一共128個位元組, 64個位元組為 mark word 64個位元組為klass word,我們主要看 mark word 結構,
無鎖:主要的頭資訊 lock(鎖狀態) : 01 2個位元組,biased_lock(是否偏向鎖)1個位元組:0,年輕代年齡:4個位元組(用于晉升到老年代閾值),indentity_hashcode:物件標識hash碼 25個位元組 剩下的 26位元組沒有用,
偏向鎖:主要的頭資訊 lock: 01,biased_lock:1,thread:54位 當先執行緒id,age:4位元組
輕量鎖:主要的頭資訊 lock:00
重量鎖:主要的頭資訊 lock:10
GC:主要的頭資訊 lock:11
物件頭我們可以使用 openjdk 的 jol插件測驗列印頭資訊,
- 從無鎖到偏向鎖
- 默認開啟延遲偏向鎖,jvm運行默認超過4s,那么物件就會開啟偏向鎖,
當第一個執行緒來訪問它的時候,它會修改 ThreadId 改為當前執行緒的id,之后再訪問這個物件時,只要對比ThreadID,一樣就不會再CAS,
他默認第一次會呼叫os加鎖,可以修改 os上鎖函式 列印系統執行緒id,再修改C檔案 列印 c的執行緒id,當開啟一個執行緒,然后同步只列印一次 系統執行緒id和 c的執行緒id,而開啟兩個執行緒,對同一個物件加鎖,會發現 系統執行緒id和c的執行緒id 同步列印,
多個執行緒通過CAS來獲取鎖,偏向上個擁有的執行緒,是樂觀鎖,
一般是單個執行緒執行
- 從偏向鎖到輕量級鎖
- 當前為無鎖,直接修改為輕量級鎖,
- 當前為偏向鎖,并且偏向的執行緒不是當前執行緒,他會判斷該鎖的偏向執行緒是否存活,沒有存活,將偏向鎖變為無鎖,然后變為輕量級鎖,
多個執行緒,通過CAS來獲取鎖,是樂觀鎖,
一般是多個執行緒,交替執行,
- 從輕量級鎖到重量級鎖
- 輕量級鎖自旋一定次數或者一個執行緒在持有鎖,一個在自旋,另外一個執行緒來訪時,輕量級膨脹為重量級鎖,
- 物件呼叫了wait()也會變為重量級鎖,
重量級鎖,是呼叫了os函式加鎖,使除了擁有鎖的執行緒以為的執行緒都是阻塞,防止 CPU 空轉,是悲觀鎖,
一般是多個執行緒,競爭執行,
5、AQS原理
AQS 全稱 AbstractQueuedSynchronizer 抽象的佇列同步器,他是一個抽象類,
AQS 通過 CLH 佇列 一個帶有虛擬頭節點的雙向鏈表,來喚醒執行緒是否可以競爭獲取鎖,
他主要有兩種方式:一種是獨占方式:只有一個執行緒能執行;一種是共享方式,多個執行緒可以同時執行,
我主要研究了獨占方式的AQS 實作,ReentrantLock的實作方式,
ReentrantLock中 有個 內部類也就 sync 類,他繼承了 AQS抽象類,
AQS 結構:head 頭結點,tail 尾結點,state 加鎖次數,exclusiveOwnerThread 當前占有鎖的執行緒,
Node 結構: pre 上個節點,next 下個節點,waitState 節點等待狀態,node當前執行緒,
lock加鎖步驟:整體步驟 嘗試加鎖 tryAcquire(),封裝執行緒為node 初始化佇列,喚醒佇列競爭鎖,重置interrupt狀態,
嘗試加鎖 tryAcquire()
- 首先呼叫tryAcquire ,主要判斷 aqs 中的 state 是否 0 ,為0 兩種情況一種是 長時間為自由鎖狀態,一種是 短暫剛釋放鎖到自由鎖狀態
- 如果為 0 ,判斷是否有 head 和 head 是否有 next 節點 并且 node 執行緒是否是當前執行緒 ,主要目的就是判斷是否有佇列,以及第二節點是否為當前節點,
- 如果沒有佇列或者下個node 當前執行緒,直接CAS 嘗試獲取鎖,獲取鎖成功回傳 true,失敗回傳fasle,
- 然后判斷是否是重入鎖,也就是判斷當前執行緒是否是 aqs 中占有鎖的執行緒,如果是重入鎖 state +1 回傳 true
- 其他情況,有佇列且不是重入鎖,且第二個節點執行緒不是當前執行緒,回傳false,
封裝執行緒為node 初始化佇列
- 先將當前執行緒,封裝成 node,
- 判斷 head 是否為 null,
- 不為 null 說明已經存在佇列,直接設定 node pre 為 tail,自己設定為新 tail,
- 為 null 說明不存在佇列,直接死回圈進行以下步驟
-
- 先判斷 tail 是否為null,為 null 通過 CAS 設定一個空節點, 賦值給 head,同時 tail = head,
- 如果 tail 不為 null ,將 node 的 pre 設定為 tail,同時 CAS 將 node 設定為 新 tail,跳出回圈
喚醒佇列競爭鎖
- 死回圈,判斷當前節點 pre 是否是 head ,目的是判斷自己是否為 佇列的第二個節點,
- 如果是 佇列的第二個節點,就CAS 嘗試獲取鎖,走 tryAcquire()方法,
-
- 獲取鎖成功,aqs 就是當前node的執行緒,設定 當前 node 為head ,舊的 head 斷開連接,方便 gc回收,
- 獲取失敗走下面步驟,
- 獲取失敗 或者 不是第二個節點
-
- 首先將 上個節點 設定為 waitState 為 -1,默認waitState 為 0,目的是為了解鎖用,
- 然后再次回圈到這里,執行LockSupport.park(),等待被喚醒,
- 如果被喚醒,會呼叫 Thread.interrupted(),這個方法對lock()沒什么作用,主要是為了lockInterruptor()方法,他會拋例外,
- 被喚醒后繼續走,回圈邏輯嘗試獲取鎖,
- 出現例外,會走 finally 曲線當前執行緒獲取鎖,
重置 interrupt,當Thread.interrupted 為true,主要目的是為了保持執行緒的 interrupt 的狀態一直,
非公平鎖會上來就嘗試獲取鎖,獲取鎖失敗就走公平鎖邏輯,也就是一朝排隊,永久排隊,
unlock解鎖步驟:公平鎖和非公平鎖一直,
- unLock(),呼叫AQS的 release(1),解鎖,
- 嘗試解鎖,tryRelease(),該方法回傳 true 解鎖成功,false 解鎖失敗,
-
- 首先 state -1 得到 c,
- 當前執行緒不是 AQS 中占有鎖的執行緒,直接拋例外,
- c = 0 解鎖成功,將 AQS 的 站有鎖的執行緒設定為null,其他情況 回傳 false,(比如重入鎖 state -1 可能大于 0)
- 解鎖成功,則需要判斷是否需要喚醒其他節點,
-
- 通過 是否有 head 判斷是否存在佇列,因為只有一個執行緒 可能不會初始化佇列,沒有佇列不需要喚醒,
- 有佇列,再判斷 head 的waitState 是否為 0,不等于 0 說明 佇列還有其他節點需要喚醒, 等于 0, head 為 tail ,佇列不需要喚醒,
- 在判斷head next 節點正常情況是 next node 不為 null,且waitState 為 <= 0 ,直接LockSupport.unPack()喚醒下個一個節點,
- 極端情況 next node 可能為 null 或者 next node 的 > 0(比如放棄索取執行緒,),那么我們可以通過從鏈表尾往前遍歷,找到離當前 node 后面最近的節點,且該node 的 waitState <= 0;
7、ReentrantLock 和 synchronized 區別
相同點
- 都實作了多執行緒同步和記憶體可見性語意,
- 都是可重入鎖,
不同點
- 同步實作機制不同
synchronized通過 Java 物件頭鎖標記和 Monitor 物件實作同步,- ReentrantLock 通過CAS、AQS(AbstractQueuedSynchronizer)和 LockSupport(用于阻塞和解除阻塞)實作同步,
- 使用方式不同
synchronized可以修飾實體方法(鎖住實體物件)、靜態方法(鎖住類物件)、代碼塊(顯示指定鎖物件),ReentrantLock 顯示呼叫 tryLock 和 lock 方法,需要在finally塊中釋放鎖,- 功能豐富程度不同
synchronized不可設定等待時間、不可被中斷(interrupted),- ReentrantLock 提供有限時間等候鎖(設定過期時間)、可中斷鎖(lockInterruptibly)、condition(提供 await、signal 等方法)等豐富功能
- 鎖型別不同
synchronized只支持非公平鎖,- ReentrantLock 提供公平鎖和非公平鎖實作,
8、Lock 高級功能?
CountDownLatch
減法計數器,減為0,執行本執行緒任務
場景: 某個執行緒,需要等其他執行緒執行完,再繼續執行,(當設定了await時間,那么時間到了主執行緒就繼續執行了,)
CyclicBarrier
加分計數器,回圈屏障
場景:當前執行緒任務,需要等其他執行緒全部到達,再一起執行,
Semaphore
停車場
場景: 同一時間可執行固定數量的執行緒,(acquire()是并發執行,如果是tryAcquire()則不一定是并發執行,可能會串行執行)
讀寫鎖
不同執行緒,讀讀不互斥,其他都互斥,
9、簡述下CAS?
CAS有三個引數,第一個引數是指標(原來的值),第二引數是預期值,第三個引數是新值,
首先拿到舊值,然后比較交換的時候,判斷預期值是不是舊值,如果一樣就賦值為新值,否則就不交換,
因為CAS在主要是 MESI協議,將高速快取區的對應要修改的條目加獨占鎖,通過總線通知其他的處理器,然后來比較修改,
CAS雖然高效的解決了原子操作問題,但仍然存在三大問題:
1.ABA問題:如果變數V初次讀取的時候值是A,后來變成了B,然后又變成了A,你本來期望的值是第一個A才會設定新值,第二個A跟期望不符合,但卻也能設定新值,
針對這種情況,java并發包中提供了一個帶有標記的原子參考類AtomicStampedReference,它可以通過控制變數值的版本號來保證CAS的正確性,比較兩個值的參考是否一致,如果一致,才會設定新值, 打一個比方,如果有一家蛋糕店,為了挽留客戶,絕對為貴賓卡里余額小于20元的客戶一次性贈送20元,刺激消費者充值和消費,但條件是,每一位客戶只能被贈送一次,此時,如果很不幸的,用戶正好正在進行消費,就在贈予金額到賬的同時,他進行了一次消費,使得總金額又小于20元,并且正好累計消費了20元,使得消費、贈予后的金額等于消費前、贈予前的金額,這時,后臺的贈予行程就會誤以為這個賬戶還沒有贈予,所以,存在被多次贈予的可能,但使用 AtomicStampedReference 就可以很好的解決這個問題,
2.無限回圈問題(自旋):看原始碼可知,Atomic類設定值的時候會進入一個無限回圈,只要不成功,就會不停的回圈再次嘗試,在高并發時,如果大量執行緒頻繁修改同一個值,可能會導致大量執行緒執行compareAndSet()方法時需要回圈N次才能設定成功,即大量執行緒執行一個重復的慷訓圈(自旋),造成大量開銷,
解決無線回圈問題可以使用java8中的LongAdder, 有點像1.8的ConcurrentHashMap,高并發情況,new 一個 2的冪次方的陣列,最大為cpu的核數,采用對陣列分段CAS的方式,進行修改每個陣列下標值,獲取總數的時候采用原值 + 陣列每個下標值的累加,
3.多變數原子問題:只能保證一個共享變數的原子操作,一般的Atomic類,只能保證一個變數的原子性,但如果是多個變數呢?
可以用AtomicReference,這個是封裝自定義物件的,多個變數可以放一個自定義物件里,然后他會檢查這個物件的參考是不是同一個,如果多個執行緒同時對一個物件變數的參考進行賦值,用AtomicReference的CAS操作可以解決并發沖突問題, 但是如果遇到ABA問題,AtomicReference就無能為力了,需要使用AtomicStampedReference來解決,
11、interrupt()方法 中斷幾種 區別
-
interrupt() 執行緒標記為中斷,拋例外.
- Interrupted() 判斷執行緒是否中斷,并且重置為false.
- isInterrupted() 判斷執行緒是否中斷.
12、Runnale 和 Callable 區別
-
callable 執行的 call,runnable 執行的是 run
- callable 可以獲取future 物件,可以獲取回傳值,run 方法不行,
13、執行緒的幾種狀態?
-
新建(new)新建一個執行緒物件
- 可運行(runnable)呼叫 start 的方法,但是沒有湖區 cpu 使用權,
- 運行(running)呼叫run方法,獲得cpu使用權
- 阻塞(blocked)呼叫了sleep(),wait()或者運行時 等待獲取鎖,
- 死亡(dead)執行緒執行完了,或者例外退出了 run()方法,
14、執行緒池幾種狀態?
-
running 新建執行緒池
- shutdown 呼叫 shutdown()不在接受新任務,但是會繼續執行已經添加的任務,
- stop 呼叫 shutdownNow()不在接受新任務,同時不會執行已添加任務,并且終止正在執行的執行緒,
- tidying 任務執行緒停止 和 佇列為空的狀態,
- terminated 在 tidying 狀態呼叫 terminated()方法,執行緒池銷毀,
15、執行緒池引數介紹?
核心執行緒數
最大執行緒數
執行緒空閑時間
阻塞佇列
飽和策略
執行緒工廠
16、執行緒池的分發
-
新任務,先判斷核心執行緒數是否全部再執行,沒有就新建一個執行,
- 核心執行緒數全部在執行,那么就去判斷佇列中是否已滿,未滿添加到佇列中
- 已滿,就判斷是否達到了最大執行緒數,沒有達到就新建執行緒去執行當前執行緒,
- 已經達到了,就呼叫執行緒池的飽和策略
17、幾種執行緒池
一般是使用 ThreadPoolExecutor 自己根據業務,CPU核數設定,
CPU 密集型,一般是 核心執行緒數和CPU核數+1,因為 CPU一直在運行,CPU 利用率高,
IO 密集型,一般是 2倍核數+1,CPU 利用低,其他執行緒可以繼續使用CPU,提高CPU利用率,
- 固定核心執行緒數,無線佇列,沒有空閑時間的,適合壓力較大的服務器,可以控制執行緒數,合理利用資源,
- 單一執行緒,無線佇列,沒有空閑時間,適合串型任務,按順序執行的任務,
- 沒有核心執行緒數,只有 maxInteger 大的 最大核心執行緒數,無線佇列,有較短的空閑時間,適合并發高,周期短的任務,
- 定時執行緒,固定執行緒數,采用延遲或定時的方式來執行任務,
18、執行緒池的飽和策略
-
不處理,拋例外
- 不處理,不拋例外
- 讓呼叫者的執行緒處理任務
- 丟棄佇列頭訊息,接下來直接執行當前任務
- 自己實作 rejectExcutionHandler 介面,自定義策略,
19、submit() 和 execut()區別?
-
接受引數不同,execut 引數為 runnable,submit 可用時 runnable,callable
- submit 可以通過 futureTask 獲取回傳值,execut 是沒有回傳值的
- submit 最終也是 呼叫了 execut,
20、執行緒池是如何做到執行緒復用的?
-
通過 Work 的 runWork 方法,
- 該方法 第一次 通過 Work 的 firstTask 獲取任務,
- 之后會 回圈通過 getTask 從 workQueue 中不停地獲取任務
- 并直接呼叫task的( task 是實作了runnable ) run 方法來執行任務,這樣就保證了每個執行緒都始終在一個回圈中,反復獲取任務,然后執行任務,從而實作了執行緒的復用,
- 當 getTask 回傳 null,就會銷毀執行緒,
21、空閑執行緒超時銷毀如何實作的?
-
首先執行緒池會將 新建的 work 放進 一個 set集合里,works,
- getTask時候,先判讀 works大小 是否超過核心執行緒數
- 超過核心執行緒數,使用 poll() + 空閑時間,去獲取 task,poll()他是非阻塞的,
- 沒用超過核心執行緒數,使用的是 take() take 是阻塞的,一直等待回去執行緒,
- 當 poll 空閑時間到了也沒有獲取到任務,回傳null,
- runWork 回圈條件不成立,跳出回圈,最侄訓呼叫 銷毀 work 邏輯,
19、FutureTask
FutureTask是Future介面的一個唯一實作類,
FutureTask實作了Runnable,因此它既可以通過Thread包裝來直接執行,也可以提交給ExecuteService來執行,
FutureTask實作了Futrue可以直接通過get()函式獲取執行結果,該函式會阻塞,直到結果回傳,
20、Threadlocal起什么作用?
執行緒隔離,保證執行緒安全,
21、Threadlocal內部實作原理
-
每個 Thread 都有 ThreadLocalMap,
- ThreadLocalMap 內部為 entry陣列,entry 物件 key 為ThreadLocal 變數,value 是存的變數的值,
- entry 的 key 也就是 ThreadLocal 為 弱參考,
- 那么也就是說 ThreadLocal,容易被GC 回收掉,
- set 會判斷是否初始化了 map,沒有就初始化,同時將當前 threadLocal 作為 key, 變數值作為 value 存入,同時可能會觸發回收 失效的值,
- get 也會判斷是否初始化 map,沒有就初始化,
- 初始化 默認 entry 長度為 16,閾值為 2/3 長度,
22、ThreadLocal 引發的記憶體泄漏問題?
Entry 的 key 是弱參考,那么就是說 ThreadLocal 容易被 GC 回收掉,
當 key 被 GC 為 null,但是 Entry 本身被 Map 參考著,而 Entry 又 參考著 不為null 的 Value,
我們執行緒一直存活,且一直不呼叫 get,set, remove 方法,那么這條鏈一直存在,不會被 GC 回收,導致記憶體泄漏,
所以最好一旦資料不使用,最好直接 remove 掉,
其實,ThreadLocalMap的設計中已經考慮到這種情況,也加上了一些防護措施:在ThreadLocal的get(),set(),remove()的時候都會清除執行緒ThreadLocalMap里所有key為null的value,
ThreadLocal 什么時候可能會出現執行緒不安全問題,
當 Entry 的 value ,為共享變數的時候,比如加了 static ,那么就會出現不安全問題,
23、JMM
Java的記憶體模型是分為主記憶體和執行緒的作業記憶體兩部分進行作業的,
作業記憶體從主記憶體read資料,
load到作業記憶體中,
執行緒對資料進行use,
然后將資料assign到作業記憶體,
從作業記憶體store處理過的資料,
最后將新資料write進主記憶體,
24、硬體層面原理
主記憶體
CPU暫存器:
CPU寫緩沖器:暫存修改的變數,發送訊息給總線通知就完事,等到總線通知其他處理器全部回傳了收到ack,它會修改高速快取,優化了CPU不需要串行等待其他CPU回傳 ack,再寫入高速快取,
CPU無效佇列:快取失效變數,立刻回傳ack給總線收到訊息,優化了CPU不需要串行等待變更高速快取,再發送ack通知到總線,
CPU高速快取:快取著主記憶體的資料
總線:接受發送通知每個處理器
作業流程
- 某個CPU 對本地記憶體資料需要修改,先在寫快取器修改,然后發送 invalidate 訊息到總線,其他CPU處理器會不停的嗅探總線,當嗅探到變數需要變更,會將變數放到無效佇列里,回傳 invalidate ack 訊息給總線,之后會根據無效佇列,將對應高速快取變數標記為失效標記 I,
- 當 CPU 收到所有其他的CPU發來的 invalidate ack 訊息,就會從 寫緩沖器 取出資料,鎖定高速快取中的條目 標記 E 獨占鎖,然后將寫快取器的資料寫到 高速快取(主記憶體),標記為 M,
解決可見性:
Store屏障(flush操作):強制要求寫操作必須阻塞等待到其他的處理器回傳 invalidate ack 之后,加鎖,然后修改資料到 高速快取,效果就是,要求一個西曹佐必須刷到高速快取(或者主記憶體),不能停留在寫快取中,
Load屏障(refresh操作):從高速快取中讀取資料的時候,如果發現無效佇列里面有一個 invalidate 訊息,此時會立馬強制根據那個 invalidate 訊息把自己本地高速快取的資料,設定為 I(過期),然后就可以強制從其他處理器的高速快取中加載最新的值,
解決有序性:
Acquire屏障(StoreStore屏障):會強制讓寫資料的操作全部按照順序寫入寫緩沖器里,他不會讓你第一個寫到寫緩沖器去,第二個寫直接修改高速快取,
Resource屏障(StoreLoad屏障):他會強制先將寫緩沖器里的資料寫入高速快取中,接著讀資料的時候強制清空無效佇列,對立面的 validate 訊息全部過期掉高速快取中的條目,然后強制從主記憶體里重新加載資料,
25、你知道Java記憶體模型中的原子性、有序性、可見性是什么嗎?
可見性:是指執行緒之間的可見性,一個執行緒修改的狀態對另一個執行緒是可見的,也就是一個執行緒修改的結果,另一個執行緒馬上就能看到,(加 Load屏障執行 refresh指令,加Store屏障執行 flush操作,)
原子性:執行緒必須是獨立執行的,沒有人影響我的,一定是我自己執行成功之后,別人才可以執行,(ObjectMonitor物件 加鎖)
有序性:
java ->javac(靜態編譯)->class -> jit(動態編譯)-> 機器碼指令 -> 處理器, (為了加速程式的執行速度,在一定規則的情況下發生指令重排序, javac, jit ,處理器 三個層次都會發生指令重排,)
代碼必須是按順序執行的,不能重排序,(在進入代碼加Acquire屏障和之后加Release屏障,保證代碼塊的不和屏障之外的代碼發送指令重排)
synchronized關鍵字,同時可以保證原子性、可見性以及有序性的
- 原子性:加鎖和釋放鎖的機制,ObjectMonitor,保證只有一個執行緒能進入同步塊,(加鎖和釋放鎖)
- 可見性,在monitorenter 之后 Load 屏障, monitorexit 之后加 Store 屏障,他在同步代碼塊對變數做的寫操作,都會在釋放鎖的時候,全部強制執行flush操作,在進入同步代碼塊的時候,對變數的讀操作,全部會強制執行refresh的操作,(記憶體屏障+MESI協議,)
- 有序性,同步開始加 Acquire 屏障 ,同步之后加 Release 屏障,通過記憶體屏障來保證同步代碼內部的指令可以重排,但是同步代碼塊內部的指令和外面的指令是不能重排的,(記憶體屏障)
26、volatile關鍵字有什么作用?
先說jmm抽象原理 -> 硬體原理 -> MESI協議 -> 記憶體屏障保證了可見性(load屏障 refresh操作,store屏障 flush操作),有序性(acquire,release屏障 保證指令之間不能重排)-> 原子性(ObjectMonitor 結構,加鎖原理,)
講清楚volatile關鍵字,直接問你volatile關鍵字的理解,對前面的一些問題,這個時候你就應該自己去主動從記憶體模型開始講起,原子性、可見性、有序性的理解,volatile關鍵字的原理
volatile關鍵字是用來解決可見性和有序性,大量用在開源專案,主要用在有讀有寫的多執行緒場景,
- 可見性:volatile 讀之前加 Load 屏障, 寫 之后加 Store 屏障,保證讀之前 MESI快取一致性協議執行 refresh操作,寫之后執行 flush 操作,
- 有序性:volatile修飾的變數讀寫前面加 Acquire 屏障和之后加 Release 屏障,保證代碼塊的不和屏障之外的代碼發送指令重排,避免前后的讀寫操作發生指令重排,
27、double check單例實踐
執行緒1: MyObject myObj = new MyObject(); => 這個是我們自己寫的一行代碼
步驟1:以MyObject類作為原型,給他的物件實體分配一塊記憶體空間,objRef就是指向了分配好的記憶體空間的地址的參考,指標
objRef = allocate(MyObject.class);
步驟2:就是針對分配好記憶體空間的一個物件實體,執行他的建構式,對這個物件實體進行初始化的操作,執行我們自己寫的建構式里的一些代碼,對各個實體變數賦值,初始化的邏輯
invokeConstructor(objRef);
步驟3:上兩個步驟搞定之后,一個物件實體就搞定了,此時就是把objRef指標指向的記憶體地址,賦值給我們自己的參考型別的變數,myObj就可以作為一個類似指標的概念指向了MyObject物件實體的記憶體地址
myObj = objRef;
有可能JIT動態編譯為了加速程式的執行速度,因為步驟2是在初始化一個物件實體,這個步驟是有可能很耗時的,比如說你可能會在里面執行一些網路的通信,磁盤檔案的讀寫,都有可能JIT動態編譯,指令重排,為了加速程式的執行性能和效率,可能會重排為,步驟1 -> 步驟3 -> 步驟2
執行緒1,剛剛執行完了步驟1和步驟3,步驟2還沒執行,此時myObj已經不是null了,但是MyObject物件實體內部的resource是null
執行緒2,直接呼叫myObj.execute()方法, 此時內部會呼叫resource.execute()方法,但是此時resource是null,直接導致空指標
如果加了 Volatile 關鍵字,步驟1,2,3是需要一起完成,其他執行緒才可使用,
防止指令重排序有什么好處?如何實作防止指令重排序的?
防止指令重排序好處:保證代碼的有序性,規則制定了在一些特殊情況下,不允許編譯器、指令器對你寫的代碼進行指令重排,必須保證你的代碼的有序性,(這句話要說,然后找個幾條happen-before 原則說說就可以了,)
happen-before 原則
- 程式次序規則:一個執行緒內,按照代碼順序,書寫在前面的操作先行發生于書寫在后面的操作
- 鎖定規則:一個unLock操作先行發生于后面對同一個鎖的lock操作,比如說在代碼里有先對一個lock.lock(),lock.unlock(),lock.lock()
- volatile變數規則:對一個volatile變數的寫操作先行發生于后面對這個volatile變數的讀操作,volatile變數寫,再是讀,必須保證是先寫,再讀
- 傳遞規則:如果操作A先行發生于操作B,而操作B又先行發生于操作C,則可以得出操作A先行發生于操作C
- 執行緒啟動規則:Thread物件的start()方法先行發生于此執行緒的每個一個動作,thread.start(),thread.interrupt()
- 執行緒中斷規則:對執行緒interrupt()方法的呼叫先行發生于被中斷執行緒的代碼檢測到中斷事件的發生
- 執行緒終結規則:執行緒中所有的操作都先行發生于執行緒的終止檢測,我們可以通過Thread.join()方法結束、Thread.isAlive()的回傳值手段檢測到執行緒已經終止執行
- 物件終結規則:一個物件的初始化完成先行發生于他的finalize()方法的開始
并發編程面試合輯 word檔案下載地址:鏈接:https://pan.baidu.com/s/1nwlBO2tYXDDl7OjGhs4e4Q
提取碼:1111爆肝一周,不眠不休!就為 點贊+好評+收藏 三連
分布式中間件面試合輯
分布式呼叫RPC篇
1 什么是 RPC ?
- RPC (Remote Procedure Call)即遠程程序呼叫,是分布式系統常見的一種通信方法,它允許程式呼叫另一個地址空間(通常是共享網路的另一臺機器上)的程序或函式,而不用程式員顯式編碼這個遠程呼叫的細節,
- 除 RPC 之外,常見的多系統資料互動方案還有分布式訊息佇列、HTTP 請求呼叫、資料庫和分布式快取等,
- 其中 RPC 和 HTTP 呼叫是沒有經過中間件的,它們是端到端系統的直接資料互動,
簡單的說
- RPC就是從一臺機器(客戶端)上通過引數傳遞的方式呼叫另一臺機器(服務器)上的一個函式或方法(可以統稱為服務)并得到回傳的結果,
- RPC會隱藏底層的通訊細節(不需要直接處理Socket通訊或Http通訊),
- 客戶端發起請求,服務器回傳回應(類似于Http的作業方式)RPC在使用形式上像呼叫本地函式(或方法)一樣去呼叫遠程的函式(或方法),
2 為什么我們要用RPC?
RPC 的主要目標是讓構建分布式應用更容易,在提供強大的遠程呼叫能力時不損失本地呼叫的語意簡潔性,為實作該目標,RPC 框架需提供一種透明呼叫機制讓使用者不必顯式的區分本地呼叫和遠程呼叫,
3 RPC需要解決的三個問題
RPC要達到的目標:遠程呼叫時,要能夠像本地呼叫一樣方便,讓呼叫者感知不到遠程呼叫的邏輯,
- Call ID映射,我們怎么告訴遠程機器我們要呼叫哪個函式呢?在本地呼叫中,函式體是直接通過函式指標來指定的,我們呼叫具體函式,編譯器就自動幫我們呼叫它相應的函式指標,但是在遠程呼叫中,是無法呼叫函式指標的,因為兩個行程的地址空間是完全不一樣,所以,在RPC中,所有的函式都必須有自己的一個ID,這個ID在所有行程中都是唯一確定的,客戶端在做遠程程序呼叫時,必須附上這個ID,然后我們還需要在客戶端和服務端分別維護一個 {函式 <--> Call ID} 的對應表,兩者的表不一定需要完全相同,但相同的函式對應的Call ID必須相同,當客戶端需要進行遠程呼叫時,它就查一下這個表,找出相應的Call ID,然后把它傳給服務端,服務端也通過查表,來確定客戶端需要呼叫的函式,然后執行相應函式的代碼,
- 序列化和反序列化,客戶端怎么把引數值傳給遠程的函式呢?在本地呼叫中,我們只需要把引數壓到堆疊里,然后讓函式自己去堆疊里讀就行,但是在遠程程序呼叫時,客戶端跟服務端是不同的行程,不能通過記憶體來傳遞引數,甚至有時候客戶端和服務端使用的都不是同一種語言(比如服務端用C++,客戶端用Java或者Python),這時候就需要客戶端把引數先轉成一個位元組流,傳給服務端后,再把位元組流轉成自己能讀取的格式,這個程序叫序列化和反序列化,同理,從服務端回傳的值也需要序列化反序列化的程序,
- 網路傳輸,遠程呼叫往往是基于網路的,客戶端和服務端是通過網路連接的,所有的資料都需要通過網路傳輸,因此就需要有一個網路傳輸層,網路傳輸層需要把Call ID和序列化后的引數位元組流傳給服務端,然后再把序列化后的呼叫結果傳回客戶端,只要能完成這兩者的,都可以作為傳輸層使用,因此,它所使用的協議其實是不限的,能完成傳輸就行,盡管大部分RPC框架都使用TCP協議,但其實UDP也可以,而gRPC干脆就用了HTTP2,Java的Netty也屬于這層的東西,
4 實作高可用RPC框架需要考慮到的問題
- 既然系統采用分布式架構,那一個服務勢必會有多個實體,要解決如何獲取實體的問題,所以需要一個服務注冊中心,比如在Dubbo中,就可以使用Zookeeper作為注冊中心,在呼叫時,從Zookeeper獲取服務的實體串列,再從中選擇一個進行呼叫;
- 如何選擇實體呢?就要考慮負載均衡,例如dubbo提供了4種負載均衡策略;
- 如果每次都去注冊中心查詢串列,效率很低,那么就要加快取;
- 客戶端總不能每次呼叫完都等著服務端回傳資料,所以就要支持異步呼叫;
- 服務端的介面修改了,老的介面還有人在用,這就需要版本控制;
- 服務端總不能每次接到請求都馬上啟動一個執行緒去處理,于是就需要執行緒池;
5 理論結構模型

RPC 服務端通過RpcServer去匯出(export)遠程介面方法,而客戶端通過RpcClient去匯入(import)遠程介面方法,客戶端像呼叫本地方法一樣去呼叫遠程介面方法,RPC 框架提供介面的代理實作,實際的呼叫將委托給代理RpcProxy,代理封裝呼叫資訊并將呼叫轉交給RpcInvoker去實際執行,在客戶端的RpcInvoker通過連接器RpcConnector去維持與服務端的通道RpcChannel,并使用RpcProtocol執行協議編碼(encode)并將編碼后的請求訊息通過通道發送給服務端,
RPC 服務端接收器RpcAcceptor接收客戶端的呼叫請求,同樣使用RpcProtocol執行協議解碼(decode),
解碼后的呼叫資訊傳遞給RpcProcessor去控制處理呼叫程序,最后再委托呼叫給RpcInvoker去實際執行并回傳呼叫結果,

分布式限流Zookeeper篇
1.ZooKeeper 是什么?
ZooKeeper 是一個分布式的,開放原始碼的分布式應用程式協調服務,是Google 的 Chubby 一個開源的實作,它是集群的管理者,監視著集群中各個節點的狀態根據節點提交的反饋進行下一步合理操作,最終,將簡單易用的介面和性能高效、功能穩定的系統提供給用戶,
客戶端的讀請求可以被集群中的任意一臺機器處理,如果讀請求在節點上注冊了監聽器,這個監聽器也是由所連接的 zookeeper 機器來處理,對于寫請求,這些請求會同時發給其他 zookeeper 機器并且達成一致后,請求才會回傳成功,因此,隨著 zookeeper 的集群機器增多,讀請求的吞吐會提高但是寫請求的吞吐會下降,
有序性是 zookeeper 中非常重要的一個特性,所有的更新都是全域有序的,每個更新都有一個唯一的時間戳,這個 時間戳稱為 zxid(Zookeeper Transaction Id),而讀請求只會相對于更新有序,也就是讀請求的回傳 結果中會帶有這個 zookeeper 最新的 zxid,
2. ZooKeeper 提供了什么?
1、檔案系統
2、通知機制
3. Zookeeper 檔案系統
Zookeeper 提供一個多層級的節點命名空間(節點稱為 znode),與檔案系統不同的是,這些節點都可以設定關聯的資料,而檔案系統中只有檔案節點 可以存放資料而目錄節點不行,Zookeeper 為了保證高吞吐和低延遲,在記憶體中維護了這個樹狀的目錄結構,這種特性使得 Zookeeper 不能用于存放大量的資料,每個節點的存放資料上限為 1M,
4.說一說四種型別的 znode
1、PERSISITENT-持久化目錄節點
客戶端與 zookeeper 斷開連接后,該節點依舊存在
2、PERSISITENT_SEQUENTIAL-持久化順序編號目錄節點
客戶端與 zookeeper 斷開連接后,該節點依舊存在,只是 Zookeeper 給該節點名稱進行順序編號
3、EPHEMERAL-臨時目錄節點
客戶端與 zookeeper 斷開連接后,該節點被洗掉
4、EPHEMERALSE_QUENTIAL-臨時順序編號目錄節點
客戶端與 zookeeper 斷開連接后,該節點被洗掉,只是 Zookeeper 給該節點名稱進行順序編號

5. Zookeeper 通知機制
client 端會對某個 znode 建立一個 watcher 事件,當該 znode 發生變化時,這些 client 會收到 zk 的通知,然后 client 可以根據 znode 變化來做出業務上的改變等,
6. Zookeeper 做了什么?
1、命名服務
2、配置管理
3、集群管理
4、分布式鎖
5、佇列管理
7. zk 的命名服務(檔案系統)
命名服務是指通過指定的名字來獲取資源或者服務的地址,利用 zk 創建一個全域的路徑,即是唯一的路徑,這個路徑就可以作為一個名字,指向 集群中的集群,提供的服務的地址,或者一個遠程的物件等等,
8. zk 的配置管理(檔案系統、通知機制)
程式分布式的部署在不同的機器上,將程式的配置資訊放在 zk 的 znode 下,當有配置發生改變時,也就是 znode 發生變化時,可以通過改變 zk 中某個目錄節點的內容,利用 watcher 通知給各個客戶端,從而更改配置,
9. Zookeeper 集群管理(檔案系統、通知機制)
所謂集群管理不外乎兩點:是否有機器退出和加入、選舉 master,
對于第一點,所有機器約定在父目錄下創建臨時目錄節點,然后監聽父目 錄節點的子節點變化訊息,一旦有機器掛掉,該機器與 zookeeper 的連接斷開,其所創建的臨時目錄節點被洗掉,所有其他機器都收到通知:某個兄弟目錄被洗掉,于是,所有人都知道:它上船了,
新機器加入也是類似,所有機器收到通知:新兄弟目錄加入,highcount 又有了,對于第二點,我們稍微改變 一下,所有機器創建臨時順序編號目錄節點,每次選取編號最小的機器作為 master 就好,
10. Zookeeper 分布式鎖(檔案系統、通知 機制)
有了 zookeeper 的一致性檔案系統,鎖的問題變得容易,鎖服務可以分為兩類,一個是保持獨占,另一個是控制時序,
對于第一類,我們將 zookeeper 上的一個 znode 看作是一把鎖,通過createznode 的方式來實作,所有客戶端都去創建/distribute_lock節點,最終成功創建的那個客戶端也即擁有了這把鎖,用完洗掉掉自己創建的distribute_lock節點就釋放出鎖,
對于第二類,/distribute_lock已經預先存在,所有客戶端在它下面創建臨時順序編號目錄節點,和選 master 一樣,編號最小的獲得鎖,用完洗掉,依次方便,
11. 獲取分布式鎖的流程

在獲取分布式鎖的時候在 locker 節點下創建臨時順序節點,釋放鎖的時候洗掉該臨時節點,客戶端呼叫 createNode 方法在 locker 下創建臨時順序節點,
然后呼叫 getChildren(“locker”)來獲取 locker 下面的所有子節點,注意此時不用設定任何 Watcher,客戶端獲取到所有的子節點 path 之后,如果發現自己創建的節點在所有創建的子節點序號最小,那么就認為該客戶端獲取到了鎖,如果發現自己創建的節點并非 locker 所有子節點中最小的,說明自己還沒有獲取到鎖,此時客戶端需要找到比自己小的那個節點,然后對 其呼叫 exist()方法,同時對其注冊事件監聽器,之后,讓這個被關注的節點洗掉,則客戶端的 Watcher 會收到相應通知,此時再次判斷自己創建的節點是否是 locker 子節點中序號最小的,如果是則獲取到了鎖,如果不是則重復以上步驟繼續獲取到比自己小的一個節點并注冊監聽,當前這個程序中還需要許多的邏輯判斷,

代碼的實作主要是基于互斥鎖,獲取分布式鎖的重點邏輯在于BaseDistributedLock,實作了基于 Zookeeper 實作分布式鎖的細節,
12. Zookeeper 佇列管理(檔案系統、通知機制)
兩種型別的佇列:
1、同步佇列,當一個佇列的成員都聚齊時,這個佇列才可用,否則一直 等待所有成員到達,
2、佇列按照 FIFO 方式進行入隊和出隊操作,
第一類,在約定目錄下創建臨時目錄節點,監聽節點數目是否是我們要求的數目,
第二類,和分布式鎖服務中的控制時序場景基本原理一致,入列有編號, 出列按編號,在特定的目錄下創建 PERSISTENT_SEQUENTIAL 節點,創建成功時 Watcher 通知等待的佇列,佇列洗掉序列號最小的節點用以 消費,此場景下 Zookeeper 的 znode 用于訊息存盤,znode 存盤的資料就是訊息佇列中的訊息內容,SEQUENTIAL 序列號就是訊息的編號,按序取出即可,由于創建的節點是持久化的,所以不必擔心佇列訊息的丟失問題,
13. Zookeeper 資料復制
Zookeeper 作為一個集群提供一致的資料服務,自然,它要在所有機器間做資料復制,資料復制的好處:
1、容錯:一個節點出錯,不致于讓整個系統停止作業,別的節點可以接管它的作業;
2、提高系統的擴展能力 :把負載分布到多個節點上,或者增加節點來提高系統的負載能力;
3、提高性能:讓客戶端本地訪問就近的節點,提高用戶訪問速度,從客戶端讀寫訪問的透明度來看,資料復制集群系統分下面兩種:
1、寫主(WriteMaster) :對資料的修改提交給指定的節點,讀無此限制,可以讀取任何一個節點,這種情況下客戶端需要對讀與寫進行區別,俗稱讀寫分離;
2、寫任意(Write Any):對資料的修改可提交給任意的節點,跟讀一樣,這種情況下,客戶端對集群節點的角色與變化透明,
對 zookeeper 來說,它采用的方式是寫任意,通過增加機器,它的讀吞吐能力和回應能力擴展性非常好,而寫,隨著機器的增多吞吐能力肯定下降 (這也是它建立 observer 的原因),而回應能力則取決于具體實作方式,是延遲復制保持最終一致性,還是立即復制快速回應,
14. Zookeeper 作業原理
Zookeeper 的核心是原子廣播,這個機制保證了各個 Server 之間的同步,實作這個機制的協議叫做 Zab 協議,Zab 協議有兩種模式,它們分別是恢復模式(選主)和廣播模式(同步),當服務啟動或者在領導者崩潰后,Zab 就進入了恢復模式,當領導者被選舉出來,且大多數 Server 完成了和 leader 的狀態同步以后,恢復模式就結束了,狀態同步保證了 leader 和 Server 具有相同的系統狀態,
15. Zookeeper 是如何保證事務的順序一致性的?
zookeeper 采用了遞增的事務 Id 來標識,所有的 proposal(提議)都在被提出的時候加上了 zxid,zxid 實際 上是一個 64 位的數字,高 32 位是 epoch(時期; 紀元; 世; 新時代)用來標識 leader 是否發生改變,如果有 新的leader 產生出來,epoch 會自增,低 32 位用來遞增計數,當新產生proposal 的時候,會依據資料庫的兩階段程序,首先會向其他的 server 發出事務執行請求,如果超過半數的機器都能執行并且能夠成功,那么就會 開始執行,
16. Zookeeper 下 Server 作業狀態
每個 Server 在作業程序中有三種狀態:
LOOKING:當前 Server 不知道 leader 是誰,正在搜尋 LEADING:當前Server 即為選舉出來的 leader FOLLOWING:leader 已經選舉出來,當前Server 與之同步
17. zookeeper 是如何選取主 leader 的?
當 leader 崩潰或者 leader 失去大多數的 follower,這時 zk 進入恢復模式,恢復模式需要重新選舉出一個新的 leader,讓所有的 Server 都恢復到一個正確的狀態,Zk 的選舉演算法有兩種:
一種是基于 basic paxos 實作的,
另外一種是基于 fast paxos 演算法實作的,系統默認的選舉演算法為 fast paxos,
1、Zookeeper 選主流程(basic paxos)
(1) 選舉執行緒由當前 Server 發起選舉的執行緒擔任,其主要功能是對投票結果進行統計,并選出推薦的 Server;
(2) 選舉執行緒首先向所有 Server 發起一次詢問(包括自己);
(3) 選舉執行緒收到回復后,驗證是否是自己發起的詢問(驗證 zxid 是否一致),然后獲取對方的 id(myid),并存盤到當前詢問物件串列中,最后獲取對方提議的 leader 相關資訊(id,zxid),并將這些資訊存盤到當次選舉的投票記錄表中;
(4) 收到所有 Server 回復以后,就計算出 zxid 最大的那個 Server,并將這個 Server 相關資訊設定成下一次要投票的 Server;
(5) 執行緒將當前 zxid 最大的 Server 設定為當前 Server 要推薦的 Leader,
(6) 如果此時獲勝的 Server 獲得 n/2 + 1 的 Server 票數,設定當前推薦的 leader 為獲勝的 Server,將根據獲勝的 Server 相關資訊設定自己的狀態,否則, 繼續這個程序,直到 leader 被選舉出來, 通過流程分析我們可以得出:要使 Leader 獲得多數 Server 的支持,則 Server 總數必須是奇數 2n+1,且存活的 Server 的數目不得少于 n+1. 每個 Server 啟動后都會重復以上流程,在恢復模式下,如果是剛從崩潰狀態恢復的或者剛啟動的 server 還會從磁盤快照中恢復資料和會話資訊,zk 會記錄事務日志并定期進行快照, 方便在恢復時進行狀態恢復,

2、Zookeeper 選主流程(basic paxos)
fast paxos 流程是在選舉程序中,某 Server 首先向所有 Server 提議自己要成為 leader,當其它 Server 收到提議以后,解決 epoch 和 zxid 的沖突, 并接受對方的提議,然后向對方發送接受提議完成的訊息,重復這個流 程,最后一定能選舉出 Leader,
18. Zookeeper 同步流程
選完 Leader 以后,zk 就進入狀態同步程序,
1、Leader 等待 server 連接;
2、Follower 連接 leader,將最大的 zxid 發送給 leader;
3、Leader 根據 follower 的 zxid 確定同步點;
4、完成同步后通知 follower 已經成為 uptodate 狀態;

19. 分布式通知和協調實作方式
對于系統調度來說:操作人員發送通知實際是通過控制臺改變某個節點的狀態,然后 zk 將這些變化發送給注冊了這個節點的 watcher 的所有客戶端,
對于執行情況匯報:每個作業行程都在某個目錄下創建一個臨時節點,并攜 帶作業的進度資料,這樣匯總的行程可以監控目錄子節點的變化獲得作業進度的實時的全域情況,
20. 機器中為什么會有 leader?
在分布式環境中,有些業務邏輯只需要集群中的某一臺機器進行執行,其他的機器可以共享這個結果,這樣可以大大減少重復計算,提高性能,于 是就需要進行 leader 選舉,
21. zk 節點宕機如何處理?
Zookeeper 本身也是集群,推薦配置不少于 3 個服務器,Zookeeper 自身也要保證當一個節點宕機時,其他節點會繼續提供服務,
- 如果是一個 Follower 宕機,還有 2 臺服務器提供訪問,因為 Zookeeper上的資料是有多個副本的,資料并不會丟失;
- 如果是一個 Leader 宕機,Zookeeper 會選舉出新的 Leader,
ZK 集群的機制是只要超過半數的節點正常,集群就能正常提供服務,只有在 ZK 節點掛得太多,只剩一半或不到一半節點能作業,集群才失效,所以
- 3 個節點的 cluster 可以掛掉 1 個節點(leader 可以得到 2 票>1.5)
- 2 個節點的 cluster 就不能掛掉任何 1 個節點了(leader 可以得到 1 票<=1)
22. Zookeeper 負載均衡和 nginx 負載均衡區別
zk 的負載均衡是可以調控,nginx 只是能調權重,其他需要可控的都需要自己寫插件;但是 nginx 的吞吐量比 zk 大很多,應該說按業務選擇用哪種方式,
23. Zookeeper watch 機制
Watch 機制官方宣告:一個 Watch 事件是一個一次性的觸發器,當被設定了 Watch 的資料發生了改變的時候,則服務器將這個改變發送給設定了Watch 的客戶端,以便通知它們,
Zookeeper 機制的特點:
1、一次性觸發資料發生改變時,一個 watcher event 會被發送到 client,但是 client 只會收到一次這樣的資訊,
2、watcher event 異步發送 watcher 的通知事件從 server 發送到 client 是異步的,這就存在一個問題,不同的客戶端和服務器之間通過 socket 進行通信,由于網路延遲或其他因素導致客戶端在不通的時刻監聽到事件,由于 Zookeeper 本身提供了 ordering guarantee,即客戶端監聽事件后, 才會感知它所監視 znode 發生了 變化,所以我們使用 Zookeeper 不能期望能夠監控到節點每次的變化,Zookeeper 只能保證最終的一致性,而無法保證強一致性,
3、資料監視 Zookeeper 有資料監視和子資料監視 getdata() and exists()設定資料監視,getchildren()設定了子節點監視,
4、注冊 watcher getData、exists、getChildren
5、觸發 watcher create、delete、setData
6、setData()會觸發 znode 上設定的 data watch(如果 set 成功的話),一個成功的 create() 操作會觸發被創建的 znode 上的資料 watch,以及其父節點上的 child watch,而一個成功的 delete()操作將會同時觸發一個 znode 的 data watch 和 child watch(因為這樣就沒有子節點了),同時也會觸發其父節點的 child watch,
7、當一個客戶端連接到一個新的服務器上時,watch 將會被以任意會話事件觸發,當與一個服務器失去連接的時候,是無法接收到 watch 的,而當 client 重新連接時,如果需要的話,所有先前注冊過的 watch,都會被重新注冊,通常這是完全透明的,只有在一個特殊情況下,watch 可能會丟失:對于一個未創建的 znode 的 exist watch,如果在客戶端斷開連接期間被創建了,并且隨后在客戶端連接上之前又洗掉了,這種情況下,這個watch 事件可能會被丟失,
8、Watch 是輕量級的,其實就是本地 JVM 的 Callback,服務器端只是存了是否有設定了 Watcher 的布爾型別
分布式負載均衡Nginx篇
1、請解釋一下什么是 Nginx?
Nginx 是一個 web 服務器和反向代理服務器,用于 HTTP、HTTPS、SMTP、POP3 和 IMAP 協議,
2、請列舉 Nginx 的一些特性
Nginx 服務器的特性包括:
反向代理/L7 負載均衡器嵌入式 Perl 解釋器
動態二進制升級
可用于重新撰寫 URL,具有非常好的 PCRE 支持
3、請解釋 Nginx 如何處理 HTTP 請求
Nginx 使用反應器模式,主事件回圈等待作業系統發出準備事件的信號, 這樣資料就可以從套接字讀取,在該實體中讀取到緩沖區并進行處理,單個執行緒可以提供數萬個并發連接,
4、在 Nginx 中?如何使用未定義的服務器名稱來阻止處理請求?
只需將請求洗掉的服務器就可以定義為:
Server {listen 80;server_name “ “ ;return 444;
}
這里,服務器名被保留為一個空字串,它將在沒有“主機”頭欄位的情況 下匹配請求,而一個特殊的 Nginx 的非標準代碼 444 被回傳,從而終止連接
5、 使用“反向代理服務器”的優點是什么?
反向代理服務器可以隱藏源服務器的存在和特征,它充當互聯網云和 web 服務器之間的中間層,這對于安全方面來說是很好的,特別是當您使用web 托管服務時,
6、請列舉 Nginx 服務器的最佳用途
Nginx 服務器的最佳用法是在網路上部署動態 HTTP 內容,使用 SCGI、WSGI 應用程式服務器、用于腳本的 FastCGI 處理程式,它還可以作為負載均衡器,
7、請解釋 Nginx 服務器上的 Master 和 Worker 行程分別是什么?
Master 行程:讀取及評估配置和維持
Worker 行程:處理請求
8、請解釋你如何通過不同于 80 的埠開啟 Nginx?
為了通過一個不同的埠開啟 Nginx,你必須進入/etc/Nginx/sites- enabled/,如果這是默認檔案,那么你必須打開名為“default”的檔案,編輯檔案,并放置在你想要的埠:
Like server { listen 81; }
9、請解釋是否有可能將 Nginx 的錯誤替換為 502 錯誤、503?
502 =錯誤網關 503 =服務器超載有可能,但是可以確保fastcgi_intercept_errors被設定為 ON,并使用錯誤頁面指令,
Location / {fastcgi_pass 127.0.01:9001;fastcgi_intercept_error s on;
error_page 502 =503/error_page.html;#...}
10、在 Nginx 中?解釋如何在 URL 中保留雙斜線?
要在 URL 中保留雙斜線,就必須使用merge_slashes_off;
語法:merge_slashes[on/off]
默認值: merge_slashes on
環境: http,server
11、請解釋ngx_http_upstream_module的作用是什么?
用于定義可通過 fastcgi 傳遞、proxy 傳遞、uwsgi 傳遞、memcached 傳遞和 scgi 傳遞指令來參考的服務器組,
12、請解釋什么是 C10K 問題?
C10K 問題是指無法同時處理大量客戶端(10,000)的網路套接字,
13、請陳述stub_status和 sub_filter 指令的作用是什么?
stub_status指令:該指令用于了解 Nginx 當前狀態的當前狀態,如當前的活 動連接,接受和處理當前讀/寫/等待連接的總數
Sub_filter指令:它用于搜索和替換回應中的內容,并快速修復陳舊的資料
14、解釋 Nginx 是否支持將請求壓縮到上游?
您可以使用 Nginx 模塊 gunzip 將請求壓縮到上游,gunzip 模塊是一個過濾器,它可以對不支持“gzip”編碼方法的客戶機或服務器使用“內容編 碼:gzip”來解壓縮回應,
15、解釋如何在 Nginx 中獲得當前的時間?
要獲得 Nginx 的當前時間,必須使用 SSI 模塊、$date_gmt和$date_local的變數
Proxy_set_header THE-TIME $date_gmt;
16、用 Nginx 服務器解釋-s 的目的是什么?
用于運行 Nginx -s 引數的可執行檔案,
17、解釋如何在 Nginx 服務器上添加模塊?
在編譯程序中,必須選擇 Nginx 模塊,因為 Nginx 不支持模塊的運行時間選擇
分布式訊息通訊RabbitMQ篇
1、RabbitMQ 中的 broker 是指什么?cluster 又是指什么?
broker 是指一個或多個 erlang node 的邏輯分組,且 node 上運行著RabbitMQ 應用程式,cluster 是在 broker 的基礎之上,增加了 node 之間共享元資料的約束,
2、什么是元資料?元資料分為哪些型別?包括哪些內容?與 cluster 相關的元資料 有哪些?元資料是如何保存的?元資料在 cluster 中是如何分布的?
在非 cluster 模式下,元資料主要分為 Queue 元資料(queue 名字和屬性等)、 Exchange 元資料(exchange 名字、型別和屬性等)、Binding 元資料(存放路由關系的查找表)、Vhost 元資料(vhost 范圍內針對前三者的名字空間約束和安全屬性設定),在 cluster 模式下,還包括 cluster 中 node 位置資訊和 node 關系資訊,元資料按照 erlang node 的型別確定是僅保存于RAM 中,還是同時保存在 RAM 和 disk 上,元資料在 cluster 中是全 node 分布的,
3、RAM node 和 disk node 的區別?
RAM node 僅將 fabric(即 queue、exchange 和 binding等 RabbitMQ基礎構件)相關元資料保存到記憶體中,但 disk node 會在記憶體和磁盤中均進行存盤,RAM node 上唯一會存盤到磁盤上的元資料是 cluster 中使用的 disk node 的地址,要求在 RabbitMQ cluster 中至少存在一個 disk node ,
4、RabbitMQ 上的一個 queue 中存放的 message 是否有數量限制?
可以認為是無限制,因為限制取決于機器的記憶體,但是訊息過多會導致處理效率的下降,
5、vhost 是什么?起什么作用?
vhost 可以理解為虛擬 broker ,即 mini-RabbitMQ server,其內部均含有獨立的 queue、exchange 和 binding 等,但最最重要的是,其擁有獨立的權限系統,可以做到 vhost 范圍的用戶控制,當然,從 RabbitMQ 的全域角度,vhost 可以作為不同權限隔離 的手段(一個典型的例子就是不同的應用可以跑在不同的 vhost 中),
6、在單 node 系統和多 node 構成的 cluster 系統中宣告 queue、exchange ?以及進行 binding 會有什么不同?
當你在單 node 上宣告 queue 時,只要該 node 上相關元資料進行了變更,你就會得到 Queue.Declare-ok 回應;而在 cluster 上宣告 queue ,則要求 cluster 上的全部 node 都要進行元資料成功更新,才會得到Queue.Declare-ok 回應,另外,若 node 型別 為 RAM node 則變更的資料僅保存在記憶體中,若型別為 disk node 則還要變更保存在磁盤上的資料,
7、客戶端連接到 cluster 中的任意 node 上是否都能正常作業?
是的,客戶端感覺不到有何不同,
8、若 cluster 中擁有某個 queue 的 owner node 失效了?且該 queue 被宣告具有 durable 屬性?是否能夠成功從其他 node 上重新宣告該queue ?
不能,在這種情況下,將得到 404 NOT_FOUND 錯誤,只能等 queue 所屬的 node 恢復后才能使用該 queue ,但若該 queue 本身不具有 durable 屬性,則可在其他 node 上重新宣告,
9、cluster 中 node 的失效會對 consumer 產生什么影響?若是在cluster 中創建了 mirrored queue ?這時 node 失效會對 consumer 產生什么影響?
若是 consumer 所連接的那個 node 失效(無論該 node 是否為 consumer 所訂閱 queue 的 owner node),則 consumer 會在發現 TCP 連接斷開時, 按標準行為執行重連邏輯,并根據“Assume Nothing”原則重建相應的fabric 即可,若是失效的 node 為 consumer 訂閱 queue 的owner node,則 consumer 只能通過 Consumer Cancellation Notification 機制來檢測與該 queue 訂閱關系的終止,否則會出現傻等卻沒有任何訊息來 到的問題,
10、能夠在地理上分開的不同資料中心使用 RabbitMQ cluster 么?
不能,
第一,你無法控制所創建的 queue 實際分布在 cluster 里的哪個 node 上 (一般使用 HAProxy + cluster 模型時都是這樣),這可能會導致各種跨地域訪問時的常見問題;
第二,Erlang 的 OTP 通信框架對延遲的容忍度有限,這可能會觸發各種超時,導致業務疲于處理;
第三,在廣域網上的連接失效問題將導致經典的“腦裂”問題,而
RabbitMQ 目前無法處理(該問題主要是說 Mnesia),
11、為什么 heavy RPC 的使用場景下不建議采用 disk node
heavy RPC 是指在業務邏輯中高頻呼叫 RabbitMQ 提供的 RPC 機制,導致不斷創建、 銷毀 reply queue ,進而造成 disk node 的性能問題(因為會針對元資料不斷寫盤),所以在使用 RPC 機制時需要考慮自身的業務場景,
12、向不存在的 exchange 發 publish 訊息會發生什么?向不存在的queue 執行consume 動作會發生什么?
都會收到 Channel.Close 信令告之不存在(內含原因 404 NOT_FOUND),
13、 routing_key和binding_key的最大長度是多少?
255位元組
14、RabbitMQ 允許發送的 message 最大可達多大?
根據 AMQP 協議規定,訊息體的大小由 64-bit 的值來指定,所以你就可以知道到底能發多大的資料了,
15、什么情況下 producer 不主動創建 queue 是安全的?
1. message 是允許丟失的;
2. 實作了針對未處理訊息的 republish 功能(例如采用 Publisher Confirm 機制),
16、“dead letter”queue 的用途?
當訊息被 RabbitMQ server 投遞到 consumer 后,但 consumer 卻通過Basic.Reject 進行了拒絕時(同時設定 requeue=false),那么該訊息會被放入“dead letter”queue 中, 該 queue 可用于排查 message 被 reject 或undeliver 的原因,
17、為什么說保證 message 被可靠持久化的條件是 queue 和 exchange
具有 durable 屬性?同時 message 具有 persistent 屬性才行?
binding 關系可以表示為 exchange – binding – queue ,從檔案中我們知道,若要求投遞的 message 能夠不丟失,要求 message 本身設定persistent 屬性,要求 exchange 和 queue 都設定 durable 屬性,其實這問題可以這么想,若 exchange 或 queue 未設定 durable 屬性,則在其crash 之后就會無法恢復,那么即使 message 設定了 persistent 屬性,仍然存在 message 雖然能恢復但卻無處容身的問題;同理,若 message 本身未設定 persistent 屬性,則 message 的持久化更無從談起,
18、什么情況下會出現 blackholed 問題?
blackholed 問題是指,向 exchange 投遞了 message ,而由于各種原因導致該 message 丟失,但發送者卻不知道,可導致 blackholed 的情況:
1. 向未系結 queue 的 exchange 發送 message;
2.exchange 以binding_key key_A系結了queue queue_A,但向該exchange 發送 message 使用的routing_key卻是key_B
19、如何防止出現 blackholed 問題?
沒有特別好的辦法,只能在具體實踐中通過各種方式保證相關 fabric 的存在,另外,如果在執行 Basic.Publish 時設定 mandatory=true ,則在遇到可能出現 blackholed 情況 時,服務器會通過回傳 Basic.Return 告之當前message 無法被正確投遞(內含原因 312 NO_ROUTE),
20、Consumer Cancellation Notification 機制用于什么場景?
用于保證當鏡像 queue 中 master 掛掉時,連接到 slave 上的 consumer 可以收到自身 consume 被取消的通知,進而可以重新執行 consume 動作從新選出的 master 出獲得訊息,若不采用該機制,連接到 slave 上的consumer 將不會感知 master 掛掉這個事情,導致后續無法再收到新master 廣播出來的 message ,另外,因為在鏡像 queue 模式下,存在將message 進行 requeue 的可能,所以實作 consumer 的邏輯時需要能夠正確處理出現重復 message 的情況,
21、Basic.Reject 的用法是什么?
該信令可用于 consumer 對收到的 message 進行 reject ,若在該信令中設定 requeue=true,則當 RabbitMQ server 收到該拒絕信令后,會將該message 重新發送到下一個處于 consume 狀態的 consumer 處(理論上仍可能將該訊息發送給當前 consumer),若設定 requeue=false ,則RabbitMQ server 在收到拒絕信令后,將直接將該 message 從 queue 中移除,
另外一種移除 queue 中 message 的小技巧是,consumer 回復 Basic.Ack但不對獲取到的 message 做任何處理,而 Basic.Nack 是對 Basic.Reject 的擴展,以支持一次拒絕多條 message的能力,
22、為什么不應該對所有的 message 都使用持久化機制?
首先,必然導致性能的下降,因為寫磁盤比寫 RAM 慢的多,message 的吞吐量可能有 10 倍的差距,
其次,message 的持久化機制用在RabbitMQ 的內置 cluster 方案時會出現“坑爹”問題,矛盾點在于,若message 設定了 persistent 屬性,但 queue 未設定 durable 屬性,那么當該 queue 的 owner node 出現例外后,在未重建該 queue 前,發往該queue 的 message 將被 blackholed ;若 message 設定了 persistent 屬性, 同時 queue 也設定了 durable 屬性,那么當 queue 的 owner node 例外且無法重啟的情況下,則該 queue 無法在其他 node 上重建,只能等待其owner node 重啟后,才能恢復該 queue 的使用,而在這段時間內發送給該 queue 的 message 將被 blackholed ,
所以,是否要對 message 進行持久化,需要綜合考慮性能需要,以及可能遇到的問題,若想達到100,000 條/秒以上的訊息吞吐量(單 RabbitMQ 服務器),則要么使用其他的方式來確保 message 的可靠 delivery ,要么使用非常快速的存盤系統以支持全持久化(例如使 用 SSD),另外一種處理原則是:僅對關鍵訊息作持久化處理(根據業務重要程度),且應該保證關鍵訊息的量不會導致性能瓶頸,
23、RabbitMQ 中的 cluster、mirrored queue?以及 warrens 機制分別用于解決 什么問題?存在哪些問題?
cluster是為了解決當 cluster 中的任意 node 失效后,producer 和consumer 均可以 通過其他 node 繼續作業,即提高了可用性;另外可以通過增加 node 數量增加 cluster 的訊息吞吐量的目的,cluster 本身不負責message 的可靠性問題(該問題由 producer 通 過各種機制自行解決);cluster 無法解決跨資料中心的問題(即腦裂問題),
另外,在 cluster 前使用 HAProxy 可以解決 node 的選擇問題,即業務無需知道 cluster 中多個 node 的 ip 地址,可以利用 HAProxy 進行失效 node 的探測,可以作負載均衡,
Mirrored queue 是為了解決使用 cluster 時所創建的 queue 的完整資訊僅存在于單一 node 上的問題,從另一個角度增加可用性,若想正確使用該功能,需要保證:
1.consumer 需要支持 Consumer Cancellation Notification 機制;
2.consumer 必須能夠正確處理重復 message ,
Warrens是為了解決 cluster 中 message 可能被 blackholed 的問題,即不能接受 producer 不停 republish message 但 RabbitMQ server 無回應的情況,
Warrens 有兩種構成方式:
一種模型是兩臺獨立的 RabbitMQ server + HAProxy ,其中兩個 server 的狀態分別為 active 和 hot-standby ,該模型的特點為:兩臺 server 之間無任何資料共享和協議互動,兩臺 server 可以基于不同的 RabbitMQ 版本,
另一種模型為兩臺共享存盤的 RabbitMQ server + keepalived,其中兩個server 的狀態分 別為 active 和 cold-standby,
該模型的特點為:兩臺 server 基于共享存盤可以做到完全 恢復,要求必須基于完全相同的 RabbitMQ 版本,
Warrens 模型存在的問題:
對于第一種模型,雖然理論上講不會丟失訊息,但若在該模型上使用持久化機制,就會出現這樣一種情況:
即若作為 active 的 server 例外后,持久化 在該 server 上的訊息將暫時無法被 consume ,因為此時該 queue 將無法在作為 hot- standby 的 server 上被重建,所以,只能等到例外的 active server 恢復后,才能從其上的 queue 中獲取相應的 message 進行處理,而對于業務來說,需要具有:a.感知 AMQP 連接斷開后重建各種 fabric 的能力;b.感知 active server 恢復的能力;c.切換回 active server 的時機控制,以及切回后,針對 message 先后順序產生的變化進行處理的能力,
對于第二種模型,因為是基于共享存盤的模式,所以導致 active server 例外的條件,可能同樣會導致 cold-standby server 例外;另外,在該模型下, 要求 active 和 cold-standby 的 server 必須具有相同的 node 名和 UID ,否則將產生訪問權限問題;最后,由于該模型是冷備方案,故無法保證 cold- standby server 能在你要求的時限內成功啟動,
分布式訊息通訊Kafka篇
1.Kafka 的設計是什么樣的呢?
Kafka 將訊息以 topic 為單位進行歸納
將向 Kafka topic 發布訊息的程式成為 producers.
將預訂 topics 并消費訊息的程式成為 consumer.
Kafka 以集群的方式運行,可以由一個或多個服務組成,每個服務叫做一個 broker. producers 通過網路將訊息發送到 Kafka 集群,集群向消費者提供訊息
2. 資料傳輸的事物定義有哪三種?
資料傳輸的事務定義通常有以下三種級別:
(1) 最多一次: 訊息不會被重復發送,最多被傳輸一次,但也有可能一次不傳輸
(2) 最少一次: 訊息不會被漏發送,最少被傳輸一次,但也有可能被重復傳輸.
(3) 精確的一次(Exactly once): 不會漏傳輸也不會重復傳輸,每個訊息都傳輸被一次而且僅僅被傳輸一次,這是大家所期望的
3. Kafka 判斷一個節點是否還活著有那兩個條件?
(1) 節點必須可以維護和 ZooKeeper 的連接,Zookeeper 通過心跳機制檢查每個節點的連接
(2) 如果節點是個 follower,他必須能及時的同步 leader 的寫操作,延時不能太久
4. producer 是否直接將資料發送到 broker 的 leader(主節點)?
producer 直接將資料發送到 broker 的 leader(主節點),不需要在多個節點進行分發,為了幫助 producer 做到這點,所有的 Kafka 節點都可以及時的告知:哪些節點是活動的,目標 topic 目標磁區的 leader 在哪,這樣producer 就可以直接將訊息發送到目的地了
5、Kafa consumer 是否可以消費指定磁區訊息?
Kafa consumer 消費訊息時,向 broker 發出"fetch"請求去消費特定磁區的訊息,consumer 指定訊息在日志中的偏移量(offset),就可以消費從這個位置開始的訊息,customer 擁有 了 offset 的控制權,可以向后回滾去重新消費之前的訊息,這是很有意義的
6、Kafka 訊息是采用 Pull 模式?還是 Push 模式?
Kafka 最初考慮的問題是,customer 應該從 brokes 拉取訊息還是 brokers 將訊息推送到 consumer,也就是 pull 還 push,在這方面,Kafka 遵循了一種大部分訊息系統共同的傳統的設計:producer 將訊息推送到 broker, consumer 從 broker 拉取訊息.
一些訊息系統比如 Scribe 和 Apache Flume 采用了 push 模式,將訊息推送到下游的 consumer,這樣做有好處也有壞處:由 broker 決定訊息推送的速率,對于不同消費速率的 consumer 就不太好處理了,訊息系統都致力于讓 consumer 以最大的速率最快速的消費訊息,但不幸的是,push 模式下,當 broker 推送的速率遠大于 consumer 消費的速率時, consumer 恐怕就要崩潰了,最終Kafka 還是選取了傳統的 pull 模式.
Pull 模式的另外一個好處是 consumer 可以自主決定是否批量的從 broker 拉取資料,Push 模式必須在不知道下游 consumer 消費能力和消費策略的情況下決定是立即推送每條訊息還是快取之后批量推送,如果為了避免consumer 崩潰而采用較低的推送速率,將可能導致一 次只推送較少的訊息而造成浪費,Pull 模式下,consumer 就可以根據自己的消費能力去決定這些策略
Pull 有個缺點是,如果 broker 沒有可供消費的訊息,將導致 consumer 不斷在回圈中輪詢, 直到新訊息到 t 達,為了避免這點,Kafka 有個引數可以讓 consumer 阻塞知道新訊息到達 (當然也可以阻塞知道訊息的數量達到某個特定的量這樣就可以批量發
7. Kafka 存盤在硬碟上的訊息格式是什么?
訊息由一個固定長度的頭部和可變長度的位元組陣列組成,頭部包含了一個 版本號和 CRC32 校驗碼,
? 訊息長度: 4 bytes (value: 1+4+n)
? 版本號: 1 byte
?CRC 校驗碼: 4 bytes
? 具體的訊息: n bytes
8. Kafka 高效檔案存盤設計特點:
(1).Kafka 把 topic 中一個 parition 大檔案分成多個小檔案段,通過多個小檔案段,就容易定期清除或洗掉已經消費完檔案,減少磁盤占用,
(2).通過索引資訊可以快速定位 message 和確定 response 的最大大小,
(3).通過 index 元資料全部映射到 memory,可以避免 segment file 的 IO 磁盤操作,
(4).通過索引檔案稀疏存盤,可以大幅降低 index 檔案元資料占用空間大小,
9. Kafka 與傳統訊息系統之間有三個關鍵區別
(1).Kafka 持久化日志,這些日志可以被重復讀取和無限期保留
(2).Kafka 是一個分布式系統:它以集群的方式運行,可以靈活伸縮,在內部通過復制資料提升容錯能力和高可用性
(3).Kafka 支持實時的流式處理
10. Kafka 創建 Topic 時如何將磁區放置到不同的 Broker 中
? 副本因子不能大于 Broker 的個數;
? 第一個磁區(編號為 0)的第一個副本放置位置是隨機從 brokerList 選擇的;
? 其他磁區的第一個副本放置位置相對于第 0 個磁區依次往后移,也就是如果我們有 5 個 Broker,5個磁區,假設第一個磁區放在第四個 Broker上,那么第二個磁區將會放在第五個 Broker 上;第三個磁區將會放在第一個 Broker 上;第四個磁區將會放在第二個 Broker 上,依次類推;
? 剩余的副本相對于第一個副本放置位置其實是由 nextReplicaShift 決定的,而這個數也是 隨機產生的
11. Kafka 新建的磁區會在哪個目錄下創建
在啟動 Kafka 集群之前,我們需要配置好 log.dirs 引數,其值是 Kafka 資料的存放目錄,這個引數可以配置多個目錄,目錄之間使用逗號分隔,通 常這些目錄是分布在不同的磁盤上用于提高讀寫性能,
當然我們也可以配置 log.dir 引數,含義一樣,只需要設定其中一個即可,如果 log.dirs 引數只配置了一個目錄,那么分配到各個 Broker 上的磁區肯定只能在這個目錄下創建檔案要用于存放資料,
但是如果 log.dirs 引數配置了多個目錄,那么 Kafka 會在哪個檔案央中創建磁區目錄呢? 答案是:Kafka 會在含有磁區目錄最少的檔案央中創建新的磁區目錄,磁區目錄名為 Topic 名+磁區 ID,注意,是磁區檔案要總數最少的目錄,而不是磁盤使用量最少的目錄!也就是說,如果你給 log.dirs 引數新增了一個新的磁盤,新的磁區目錄肯定是先在這個新的磁盤上創建直到這個新的磁盤目錄擁有的磁區目錄不是最少為止,
12. partition 的資料如何保存到硬碟
topic 中的多個 partition 以檔案央的形式保存到 broker,每個磁區序號從 0遞增,且訊息有序
Partition 檔案下有多個 segment(xxx.index,xxx.log)
segment 檔案里的大小和組態檔大小一致可以根據要求修改 默認為 1g 如果大小大于 1g 時,會滾動一個新的 segment 并且以上一個 segment 最后一條訊息的偏移量命名
13. kafka 的 ack 機制
request.required.acks 有三個值 0 1 -1
0:生產者不會等待 broker 的 ack,這個延遲最低但是存盤的保證最弱當server 掛掉的時候就會丟資料
1:服務端會等待 ack 值 leader 副本確認接收到訊息后發送 ack 但是如果leader 掛掉后他不確保是否復制完成新 leader 也會導致資料丟失
-1:同樣在1的基礎上服務端會等所有的follower的副本受到資料后才會受到leader發出的 ack,這樣資料不會丟失
14. Kafka 的消費者如何消費資料
消費者每次消費資料的時候,消費者都會記錄消費的物理偏移量(offset)的位置 等到下次消費時,他會接著上次位置繼續消費
15. 消費者負載均衡策略
一個消費者組中的一個分片對應一個消費者成員,他能保證每個消費者成員都能訪問,如果組中成員太多會有空閑的成員
16. 資料有序
一個消費者組里它的內部是有序的
消費者組與消費者組之間是無序的
17. kafaka 生產資料時資料的分組策略
生產者決定資料產生到集群的哪個 partition 中每一條訊息都是以(key,value)格式
Key是由生產者發送資料傳入所以生產者(key)決定了資料產生到集群的哪個 partition
分布式訊息通訊ActiveMQ篇
1.什么是 ActiveMQ?
activeMQ 是一種開源的,實作了 JMS1.1 規范的,面向訊息(MOM)的中間件,為應用程式提供高效的、可擴展的、穩定的和安全的企業級訊息通信
2. ActiveMQ 服務器宕機怎么辦?
這得從 ActiveMQ 的儲存機制說起,在通常的情況下,非持久化訊息是存盤在記憶體中的,持久化訊息是存盤在檔案中的,它們的最大限制在組態檔的<systemusage>節點中配置,但是,在非持久化訊息堆 積到一定程度,記憶體告急的時候,ActiveMQ 會將記憶體中的非持久化訊息寫入臨時檔案中,以騰出記憶體, 雖然都保存到了檔案里,但它和持久化訊息的區別是,重啟后持久化訊息會從檔案中恢復,非持久化的臨 時檔案會直接洗掉,那如果檔案增大到達了配置中的最大限制的時候會發生什么?我做了以下實驗:
設定 2G 左右的持久化檔案限制,大量生產持久化訊息直到檔案達到最大限制,此時生產者阻塞,但消費者可正常連接并消費訊息,等訊息消費掉 一部分,檔案洗掉又騰出空間之后,生產者又可繼續發送訊息,服務自動 恢復正常,
設定 2G 左右的臨時檔案限制,大量生產非持久化訊息并寫入臨時檔案, 在達到最大限制時,生產者阻塞,消費者可正常連接但不能消費訊息,或 者原本慢速消費的消費者,消費突然停止,整個系統可連接, 但是無法提供服務,就這樣掛了,
具體原因不詳,解決方案:盡量不要用非持久化訊息,非要用的話,將臨時檔案限制盡可能的調大,
3. 丟訊息怎么辦?
這得從 java 的 java.net.SocketException 例外說起,簡單點說就是當網路發送方發送一堆資料,然后呼叫 close 關閉連接之后,這些發送的資料都在接收者的快取里,接收者如果呼叫 read 方法仍舊能從快取中讀取這些資料,盡管對方已經關閉了連接,但是當接收者嘗試發送資料時,由于此時連接已關閉,所以會發生例外,這個很好理解,不過需要注意的是,當 發生 SocketException 后,原本快取區中資料也作廢了,此時接收者再次呼叫 read 方法去讀取快取中的資料,就會報 Software caused connection abort: recv failed 錯誤,
通過抓包得知,ActiveMQ 會每隔 10 秒發送一個心跳包,這個心跳包是服務器發送給客戶端的,用來判斷客戶端死沒死,如果你看過上面第一條, 就會知道非持久化訊息堆積到一定程度會寫到檔案里,這個寫 的程序會阻塞所有動作,而且會持續 20 到 30 秒,并且隨著記憶體的增大而增大,當客戶端發完訊息呼叫 connection.close()時,會期待服務器對于關閉連接的回答,如果超過 15 秒沒回答就直接呼叫 socket 層的 close 關閉 tcp 連接了,這時客戶端發出的訊息其實還在服務器的快取里等待處理,不過由于服務器心跳包的設定,導致發生了 java.net.SocketException 例外,把快取里的資料作廢了,沒處理的訊息全部丟失,
解決方案:用持久化訊息,或者非持久化訊息及時處理不要堆積,或者啟動事務,啟動事務后,commit()方法會負責任的等待服務器的回傳,也就不 會關閉連接導致訊息丟失了,
4. 持久化訊息非常慢如何處理
默認的情況下,非持久化的訊息是異步發送的,持久化的訊息是同步發送的,遇到慢一點的硬碟,發送訊息的速度是無法忍受的,但是在開啟事務的情況下,訊息都是異步發送的,效率會有 2 個數量級的提升,所以在發送持久化訊息時,請務必開啟事務模式,其實發送非持久化訊息時也建議開啟事務,因為根本不會影響性能,
5 訊息的不均勻消費
有時在發送一些訊息之后,開啟 2 個消費者去處理訊息,會發現一個消費者處理了所有的訊息,另一個消費者根本沒收到訊息,原因在于 ActiveMQ 的 prefetch 機制,當消費者去獲取訊息時,不會一條一條去獲取,而是一次性獲取一批,默認是 1000 條,這些預獲取的訊息,在還沒確認消費之前,在管理控制臺還是可以看見這些訊息的,但是不會再分配給其他消費者,此時這些訊息的狀態應該算作“已分配未消 費”,如果訊息最后被消費,則會在服務器端被洗掉,如果消費者崩潰,則這些訊息會被 重新分配給新的消費者,但是如果消費者既不消費確認,又不崩潰,那這些訊息就永遠躺在消費者的快取區里無法處理,更通常的情況是,消費這些訊息非常耗時,你開了 10 個消費者去處理,結果發現只有一臺機器吭哧吭哧處理,另外 9 臺啥事不干,
解決方案:將 prefetch 設為 1,每次處理 1 條訊息,處理完再去取,這樣也慢不了多少,
6. 死信佇列如果你想在訊息處理失敗后,不被服務器洗掉,還能被其他消費者處理或重試,可以關閉AUTO_ACKNOWLEDGE,將 ack 交由程式自己處理,那如果使用了AUTO_ACKNOWLEDGE,訊息是什么時候被確認的,還有沒有阻止訊息確認的方法?
有,兩種,
一種是呼叫 consumer.receive()方法,該方法將阻塞直到獲得并回傳一條訊息,這種情況下,訊息回傳給方法呼叫者之后就自動被確認了,
另一種方法是采用 listener 回呼函式,在有訊息到達時,會呼叫 listener 介面的 onMessage 方法,在這種情況下,在 onMessage 方法執行完畢后,訊息才會被確認,此時只要在方法中拋出例外,該訊息就 不會被確認,
那么問題來了,如果一條訊息不能被處理,會被退回服務器重新分配,如果只有一個消費者,該訊息又會重新被獲取,重新拋例外, 就算有多個消費者,往往在一個服務器上不能處理的訊息,在另外的服務器上依然不能被處理,難道就這么退回 --獲取--報錯死回圈了嗎?在重試 6 次后,ActiveMQ 認為這條訊息是“有毒”的,將會把訊息丟到死信佇列里,如果你的訊息不見 了,去 ActiveMQ.DLQ 里找找,說不定就躺在那里,
7. ActiveMQ 中的訊息重發時間間隔和重發次數嗎?
ActiveMQ:是 Apache 出品,最流行的,能力強勁的開源訊息總線,是一個完全支持 JMS1.1 和 J2EE 1.4 規范的 JMS Provider 實作,JMS(Java 訊息服務):是一個 Java 平臺中關于面向訊息中間件 (MOM)的 API,用于在兩個應用程式之間,或分布式系統中發送訊息,進行異步通信,
首先,我們得大概了解下,在哪些情況下,ActiveMQ 服務器會將訊息重發給消費者,這里為簡單起見,假定采用的訊息發送模式為佇列(即訊息發送者和訊息接收者),
1、如果訊息接收者在處理完一條訊息的處理程序后沒有對 MOM 進行應答,則該訊息將由 MOM 重發.
2、如果我們對某個佇列設定了預讀引數(consumer.prefetchSize),如果訊息 接收者在處理第一條訊息時(沒向 MOM 發送訊息接收確認)就宕機了,則預讀數量的所有訊息都將被重發!
3、如果 Session 是事務的,則只要訊息接收者有一條訊息沒有確認,或發送訊息期間 MOM 或客戶端某一方突然宕機了,則該事務范圍中的所有訊息 MOM 都將重發,
4、說到這里,大家可能會有疑問,ActiveMQ 訊息服務器怎么知道消費者客戶端到底是訊息正在
處理中 還沒來得急對訊息進行應答還是已經處理完成了沒有應答或是宕機了根本沒機會應答呢?其實在所有的客戶端機器上, 記憶體中都運行著一套客戶端的 ActiveMQ 環境,該環境負責快取發來的訊息,負責維持著 和 ActiveMQ 服務器的訊息通訊,負責失效轉移(fail-over) 等,所有的判斷和處理都是由這套客戶端環境來完成的,我們可以來對 ActiveMQ 的重發策略(Redelivery Policy)來進行自定義配置,其中的配置引數主要有以下幾個:可用的屬性默認值說明
- l collisionAvoidanceFactor 默認值 0.15 , 設定防止沖突范圍的正負百分比,只有啟用 useCollisionAvoidance 引數時才生效,
- l maximumRedeliveries 默認值 6 , 最大重傳次數,達到最大重連次數后拋出例外,為-1 時不限制次數,為 0 時表示不進行重傳,
- l maximumRedeliveryDelay 默認值-1, 最大傳送延遲,只在useExponentialBackOff 為 true時有效 (V5.5),假設首次重連間隔為10ms,倍數為 2,那么第二次重連時間間隔為 20ms,第三次重連時間間隔為 40ms,當重連時間間隔大的最大重連時間間隔時,以后每次重連時間間隔都為最大重連時間間隔,
- l initialRedeliveryDelay 默認值 1000L, 初始重發延遲時間
- l redeliveryDelay 默認值 1000L, 重發延遲時間,當 initialRedeliveryDelay=0時生效(v5.4)
- l useCollisionAvoidance 默認值 false, 啟用防止沖突功能,因為訊息接收時是可以使用多執行緒并發處理的,應該是為了重發的安全性,避開所有并發 執行緒都在同一個時間點進行訊息接收處理,所有執行緒在同一個時間點處理 時會發生什么問題呢?應該沒有問題,只是為了平衡 broker處理性能,不會有時很忙,有時很空閑,
- l useExponentialBackOff 默認值 false, 啟用指數倍數遞增的方式增加延遲時間,
- l backOffMultiplier 默認值 5, 重連時間間隔遞增倍數,只有值大于 1 和啟用 useExponentialBackOff 引數時才生效,
分布式資料庫Reids篇
1、redis 和 memcached 什么區別?為什么高并發下有時單執行緒的redis比多執行緒的 memcached 效率要高?
區別:
1. mc 可快取圖片和視頻,rd 支持除 k/v 更多的資料結構;
2. rd 可以使用虛擬記憶體,rd 可持久化和 aof 災難恢復,rd 通過主從支持資料備份; 3.rd 可以做訊息佇列,
原因:mc 多執行緒模型引入了快取一致性和鎖,加鎖帶來了性能損耗,
2、redis 主從復制如何實作的?redis 的集群模式如何實作?redis 的 key是如何尋址的?
主從復制實作:主節點將自己記憶體中的資料做一份快照,將快照發給從節 點,從節點將數 據恢復到記憶體中,之后再每次增加新資料的時候,主節點以類似于 mysql 的二進制日志方 式將陳述句發送給從節點,從節點拿到主節點發送過來的陳述句進行重放,
分片方式:
-客戶端分片
-基于代理的分片
● Twemproxy
● codis
-路由查詢分片
● Redis-cluster(本身提供了自動將資料分散到 Redis Cluster 不同節點的能力,整個資料集合的某個資料子集存盤在哪個節點對于用戶來說是透明的) redis-cluster 分片原理:Cluster 中有一個 16384 長度的槽(虛擬槽),編號分別為 0-16383, 每個 Master 節點都會負責一部分的槽,當有某個 key 被映射到某個 Master 負責的槽,那么這個 Master 負責為這個 key 提供服 務,至于哪個 Master 節點負責哪個槽,可以由用戶指定,也可以在初始化的時候自動生成,只有 Master 才擁有槽的所有權,Master 節點維護著一個 16384/8 位元組的位序列,Master 節點用 bit 來標識對于某個槽自己是否擁有,比如對于編號為 1 的槽,Master 只要判斷序列的第二位(索引從0 開始)是不是為 1 即可,這種結構很容易添加或者洗掉節點,比如如果我想新添加個節點 D, 我需要從節點 A、B、 C 中的部分槽到 D 上,
3、使用 redis 如何設計分布式鎖?說一下實作思路?使用 zk 可以嗎?如何實作?這兩種有什么區別?
redis:
1. 執行緒 A setnx(上鎖的物件,超時時的時間戳 t1),如果回傳 true,獲得鎖,
2. 執行緒 B 用 get 獲取 t1,與當前時間戳比較,判斷是是否超時,沒超時 false,
若超時執行第 3 步;
3. 計算新的超時時間 t2,使用 getset 命令回傳 t3(該值可能其他執行緒已經修改過),如果 t1==t3,獲得鎖,如果 t1!=t3 說明鎖被其他執行緒獲取了,
4. 獲取鎖后,處理完業務邏輯,再去判斷鎖是否超時,如果沒超時洗掉 鎖,如果已超時,不用處理(防止洗掉其他執行緒的鎖),
zk:
1. 客戶端對某個方法加鎖時,在 zk 上的與該方法對應的指定節點的目錄下,生成一個唯一的瞬時有序節點 node1;
2. 客戶端獲取該路徑下所有已經創建的子節點,如果發現自己創建的node1 的序號是最小的,就認為這個客戶端獲得了鎖,
3. 如果發現 node1 不是最小的,則監聽比自己創建節點序號小的最大的節點,進入等待,
4. 獲取鎖后,處理完邏輯,洗掉自己創建的 node1 即可,區別:zk 性能差一些,開銷大,實作簡單,
4、知道 redis 的持久化嗎?底層如何實作的?有什么優點缺點?
RDB(Redis DataBase:在不同的時間點將 redis 的資料生成的快照同步到磁盤等介質上):記憶體到硬碟的快照,定期更新,
缺點:耗時,耗性能(fork+io 操作),易丟失資料,
AOF(Append Only File:將redis所執行過的所有指令都記錄下來,在下次redis重啟時,只需要執行指令就可以了):寫日志,
缺點:體積大,恢復速度慢,
bgsave 做鏡像全量持久化,aof 做增量持久化,因為 bgsave 會消耗比較長的時間,不夠實時,在停機的時候會導致大量的資料丟失,需要 aof 來配合,在 redis 實體重啟時,優先使用 aof 來恢復記憶體的狀態,如果沒有aof 日志,就會使用 rdb 檔案來恢復,Redis 會定期做 aof 重寫,壓縮 aof 檔案日志大小,Redis4.0 之后有了混合持久化的功能,將 bgsave 的全量和 aof 的增量做了融合處理,這樣既保證了恢復的效率又兼顧了資料的安全性,bgsave 的原理,fork 和 cow, fork 是指 redis 通過創建子行程來進行 bgsave 操作,cow 指的是 copy on write,子行程創建后,父子行程共享資料段,父行程繼續提供讀寫服務,寫臟的頁面資料會逐漸和子行程分離開來,
5、redis 過期策略都有哪些?LRU 演算法知道嗎?寫一下 java 代碼實作?
過期策略:
定時過期(一 key 一定時器),惰性過期:只有使用 key 時才判斷 key 是否已過期,過期則清除,定期過期:前兩者折中,
LRU:new LinkedHashMap<K, V>(capacity, DEFAULT_LOAD_FACTORY, true);
//第三個引數置為 true,代表 linkedlist 按訪問順序排序,可作為 LRU 快取
;設為 false 代表 按插入順序排序,可作為 FIFO 快取
LRU 演算法實作:
1. 通過雙向鏈表來實作,新資料插入到鏈表頭部;
2. 每當快取命中(即快取 資料被訪問),則將資料移到鏈表頭部;
3. 當 鏈 表 滿 的 時 候 , 將 鏈 表 尾 部 的 數 據 丟 棄 ,LinkedHashMap:HashMap 和雙向鏈表合二為一即是 LinkedHashMap,HashMap 是無序 的,LinkedHashMap 通過維護一個額外的雙向鏈表保證了迭代順序,該迭代順序可以是插入順序(默認),也可以是訪問順序,
6、快取穿透、快取擊穿、快取雪崩解決方案?
快取穿透:指查詢一個一定不存在的資料,如果從存盤層查不到資料則不寫入快取,這將導致這個不存在的資料每次請求都要到 DB 去查詢,可能導致 DB 掛掉,
解決方案:
1. 查詢回傳的資料為空,仍把這個空結果進行快取,但過期時間會比較短;
2. 布 隆過濾器:將所有可能存在的資料哈希到一個足夠大的 bitmap 中,一個一定不存在的資料會被這個 bitmap 攔截掉,從而避免了對 DB 的查 詢,
快取擊穿:對于設定了過期時間的 key,快取在某個時間點過期的時候,恰好這時間點對這個 Key 有大量的并發請求過來,這些請求發現快取過期一般都會從后端 DB 加載資料并回設到快取,這個時候大并發的請求可能會瞬間把 DB 壓垮,
解決方案:
1. 使用互斥鎖:當快取失效時,不立即去load db,先使用如Redis的setnx去設定一個互斥鎖,當操作成功回傳時再進行load db的操作并回設快取,否則重試get快取的方法,
2. 永遠不過期:物理不過期,但邏輯過期(后臺異步執行緒去重繪),
快取雪崩: 設定快取時采用了相同的過期時間,導致快取在某一時刻同時失效,請求全部轉發到 DB,DB 瞬時壓力過重雪崩,與快取擊穿的區別:雪崩是很多 key,擊穿是某一個 key 快取,
解決方案:將快取失效時間分散開,比如可以在原有的失效時間基礎上增加一個隨機值, 比如 1-5 分鐘隨機,這樣每一個快取的過期時間的重復率就會降低,就很難引發集體失效的事件,
7、在選擇快取時?什么時候選擇 redis?什么時候選擇 memcached?
選擇 redis 的情況:
1、復雜資料結構,value 的資料是哈希,串列,集合,有序集合等這種情況下,會選擇 redis, 因為 memcache 無法滿足這些資料結構,最典型的的使用場景是,用戶訂單串列,用戶訊息,帖子評論等,
2、需要進行資料的持久化功能,但是注意,不要把 redis 當成資料庫使用,如果 redis 掛了,記憶體能夠快速恢復熱資料,不會將壓力瞬間壓在資料庫上,沒有 cache 預熱的程序,對于只讀和資料一致性要求不高的場景可以采用持久化存盤
3、高可用,redis 支持集群,可以實作主動復制,讀寫分離,而對于memcache 如果想要實作高可用,需要進行二次開發,
4、存盤的內容比較大,memcache 存盤的 value 最大為 1M,
選擇 memcache 的場景:
1、純 KV,資料量非常大的業務,使用 memcache 更合適,原因是
a、memcache 的記憶體分配采用的是預分配記憶體池的管理方式,能夠省去記憶體分配的時間,redis 是臨時申請空間,可能導致碎片化,
b、虛擬記憶體使用,memcache 將所有的資料存盤在物理記憶體里,redis 有自己的 vm 機制,理論上能夠存盤比物理記憶體更多的資料,當資料超量時, 引發 swap,把冷資料重繪到磁盤上,從這點上,資料量大時,memcache 更快
c、網路模型,memcache 使用非阻塞的 IO 復用模型,redis 也是使用非阻塞的 IO 復用模型,但是 redis 還提供了一些非 KV 存盤之外的排序,聚合功能,復雜的 CPU 計算,會阻塞整個 IO 調度,從這點上由于 redis 提供的功能較多,memcache 更快些,
d、執行緒模型,memcache使用多執行緒,主執行緒監聽,worker子執行緒接受請 求,執行讀寫,這個程序可能存在鎖沖突,redis 使用的單執行緒,雖然無鎖沖突,但是難以利用多核的特性提升吞吐量,
8、快取與資料庫不一致怎么辦?
假設采用的儲存分離,讀寫分離的資料庫,
如果一個執行緒 A 先洗掉快取資料,然后將資料寫入到主庫當中,這個時候,主庫和從庫同步沒有完成,執行緒 B 從快取當中讀取資料失敗,從從庫當中讀取到舊資料,然后更新至快取,這個時候,快取當中的就是舊的資料,
發生上述不一致的原因在于,主從庫資料不一致問題,加入了快取之后, 主從不一致的時間被拉長了
處理思路:在從庫有資料更新之后,將快取當中的資料也同時進行更新,即當從庫發生了資料更新之后,向快取發出洗掉,淘汰這段時間寫入的舊數 據,
9、主從資料庫不一致如何解決
場景描述,對于主從庫,讀寫分離,如果主從庫更新同步有時差,就會導致主從庫資料的不一致
1、忽略這個資料不一致,在資料一致性要求不高的業務下,未必需要時時一致性
2、強制讀主庫,使用一個高可用的主庫,資料庫讀寫都在主庫,添加一 個快取,提升資料讀取的性能,
3、選擇性讀主庫,添加一個快取,用來記錄必須讀主庫的資料,將哪個庫,哪個表,哪個主鍵,作為快取的 key,設定快取失效的時間為主從庫同步的時間,如果快取當中有這個資料,直接讀取主庫,如果快取當中沒有 這個主鍵,就到對應的從庫中讀取,
10、Redis 常見的性能問題和解決方案
1、master 最好不要做持久化作業,如 RDB 記憶體快照和 AOF 日志檔案
2、如果資料比較重要,某個 slave 開啟 AOF 備份,策略設定成每秒同步一次
3、為了主從復制的速度和連接的穩定性,master 和 Slave 最好在一個局域網內
4、盡量避免在壓力大的主庫上增加從庫
5、主從復制不要采用網狀結構,盡量是線性結構,Master<--Slave1<----Slave2 ....
11、Redis 的資料淘汰策略有哪些
voltile-lru 從已經設定過期時間的資料集中挑選最近最少使用的資料淘汰
voltile-ttl 從已經設定過期時間的資料庫集當中挑選將要過期的資料
voltile-random 從已經設定過期時間的資料集任意選擇淘汰資料 allkeys-lru從資料集中挑選最近最少使用的資料淘汰
allkeys-random 從資料集中任意選擇淘汰的資料 no-eviction 禁止驅逐資料
12、Redis 當中有哪些資料結構
字串 String、字典 Hash、串列 List、集合 Set、有序集合 SortedSet,如果是高級用戶,那么還會有,如果你是 Redis 中高級用戶,還需要加上下面幾種資料結構 HyperLogLog、 Geo、Pub/Sub,
13、假如 Redis 里面有 1 億個 key?其中有 10w 個 key 是以某個固定的已知的前綴開頭的?如果將它們全部找出來?
使用 keys 指令可以掃出指定模式的 key 串列,
對方接著追問:如果這個 redis 正在給線上的業務提供服務,那使用 keys指令會有什么問題?
這個時候你要回答 redis 關鍵的一個特性:redis 的單執行緒的,
keys 指令會導致執行緒阻塞一段時間,線上服務會停頓,直到指令執行完畢,服務才能恢復,這個時候可以使用 scan 指 令,scan 指令可以無阻塞的提取出指定模式的 key 串列,但是會有一定的重復概率,在客 戶端做一次去重就可以了,但是整體所花費的時間會比直接用 keys 指令長,
14、使用 Redis 做過異步佇列嗎?是如何實作的
使用 list 型別保存資料資訊,rpush 生產訊息,lpop 消費訊息,當 lpop 沒有訊息時,可以 sleep 一段時間,然后再檢查有沒有資訊,如果不想sleep 的話,可以使用 blpop, 在沒 有資訊的時候,會一直阻塞,直到資訊的到來,redis 可以通過 pub/sub 主題訂閱模式實作一個生產者,多個消費者,當然也存在一定的缺點,當消費者下線時,生產的訊息會丟失,
15、Redis 如何實作延時佇列
使用 sortedset,使用時間戳做 score, 訊息內容作為 key,呼叫 zadd 來生產訊息,消費者使用 zrangbyscore 獲取 n 秒之前的資料做輪詢處理,
16、什么是 Redis?簡述它的優缺點?
Redis 本質上是一個 Key-Value 型別的記憶體資料庫,很像 memcached,整個資料庫統統加載在記憶體當中進行操作,定期通過異步操作把資料庫資料flush 到硬碟上進行保存,
因為是純記憶體操作,Redis 的性能非常出色,每秒可以處理超過 10 萬次讀寫操作,是已知性能最快的 Key-Value DB,Redis 的出色之處不僅僅是性能,Redis 最大的魅力是支持保存多種資料
結構,此外單個 value 的最大限制是 1GB,不像 memcached 只能保存1MB 的資料,因此 Redis 可以用來實作很多有用的功能,
比方說用他的 List 來做 FIFO 雙向鏈表,實作一個輕量級的高性 能訊息佇列服務,用他的 Set 可以做高性能的 tag 系統等等,
另外Redis也可以對存入的Key-Value設定expire時間,因此也可以被當作一個功能加強版的memcac hed 來用,Redis 的主要缺點是資料庫容量受到物理記憶體的限制,不能用作海量資料的高性能讀寫,因此 Redis 適合的場景主要局限在較小資料量的高性能操作和運算上,
17、Redis 相比 memcached 有哪些優勢?
(1) memcached所有的值均是簡單的字串,redis作為其替代者,支持更為豐富的資料型別
(2) redis的速度比memcached快很多
(3) redis可以持久化其資料
18、Redis 支持哪幾種資料型別?
String、List、Set、Sorted Set、hashes
19、Redis 主要消耗什么物理資源?
記憶體,
20、Redis 的全稱是什么?
Remote Dictionary Server,
21、Redis 有哪幾種資料淘汰策略?
noeviction:回傳錯誤當記憶體限制達到并且客戶端嘗試執行會讓更多記憶體被 使用的命令(大部分的寫入指令,但 DEL 和幾個例外)
allkeys-lru: 嘗試回收最少使用的鍵(LRU),使得新添加的資料有空間存放,volatile-lru: 嘗試回收最少使用的鍵(LRU),但僅限于在過期集合的鍵,使得新添加的資料有空間存放,
allkeys-random: 回收隨機的鍵使得新添加的資料有空間存放,
volatile-random: 回收隨機的鍵使得新添加的資料有空間存放,但僅限于在過期集合的鍵,
volatile-ttl: 回收在過期集合的鍵,并且優先回收存活時間(TTL)較短的鍵,使得新添加的資料有空間存放,
22、Redis 官方為什么不提供 Windows 版本?
因為目前 Linux 版本已經相當穩定,而且用戶量很大,無需開發 windows版本,反而會帶來兼容性等問題,
23、一個字串型別的值能存盤最大容量是多少?
512M
24、為什么 Redis 需要把所有資料放到記憶體中?
Redis 為了達到最快的讀寫速度將資料都讀到記憶體中,并通過異步的方式將資料寫入磁盤,
所以 redis 具有快速和資料持久化的特征,如果不將資料放在記憶體中,磁盤 I/O 速度會嚴重影響 redis 的性能,在記憶體越來越便宜的今天,redis將會越來越受歡迎,如果設定了最大使用的記憶體,則資料已有記錄數達到記憶體限值后不能繼續插入新值,
25、Redis 集群方案應該怎么做?都有哪些方案?
1. codis,
目前用的最多的集群方案,基本和twemproxy一致的效果,但它支持在 節點數量改變情況下,舊節點資料可恢復到新 hash 節點,
2. redis cluster3.0自帶的集群,特點在于他的分布式演算法不是一致性hash,而是hash槽的概念,以及自身支持節點設定從節點,具體看官方檔案介紹,
3. 在業務代碼層實作,起幾個毫無關聯的 redis 實體,在代碼層,對key 進行hash計算,然后去對應的 redis 實體操作資料,這種方式對 hash 層代碼要求比較高,考慮部分包括,節點失效后的替代演算法方 案,資料震蕩后的自動腳本恢復,實體的監控,等等,
26、Redis 集群方案什么情況下會導致整個集群不可用?
有 A,B,C 三個節點的集群,在沒有復制模型的情況下,如果節點 B 失敗了,那么整個集群就會以為缺少 5 501-11000 這個范圍的槽而不可用,
27、MySQL 里有 2000w 資料?redis 中只存 20w 的資料?如何保證redis 中的資料都是熱點資料?
redis 記憶體資料集大小上升到一定大小的時候,就會施行資料淘汰策略,
28、Redis 有哪些適合的場景?
(1) 會話快取(Session Cache)
最常用的一種使用Redis的情景是會話快取(session cache),用Redis快取會話比其他存盤(如Mem cached)的優勢在于:Redis 提供持久化,當維護一個不是嚴格要求一致性的快取時,如果用戶的購物車 資訊全部丟失,大部分人都會不高興的,現在,他們還會這樣嗎?幸運的是,隨著 Redis 這些年的改進,很容易找到怎么恰當的使用Redis來快取會話的檔案,甚至廣為人知的商業平臺 Magento 也提供 Redis 的插件,
(2) 全頁快取(FPC)
除基本的會話 token 之外,Redis 還提供很簡便的 FPC 平臺,回到一致性問題,即使重啟了 Redis 實 例,因為有磁盤的持久化,用戶也不會看到頁面加載速度的下降,這是一個極大改進,類似 PHP 本地 FPC,再次以 Magento 為例,Magento 提供一個插件來使用 Redis 作為全頁快取后端,此外,對 WordPress 的用戶來說,Pantheon 有一個非常好的插件 wp- redis,這個插件能幫助你以最快速度加載你曾瀏覽過的頁面,
(3) 佇列
Reids在記憶體存盤引擎領域的一大優點是提供 list 和 set 操作,這使得Redis能作為一個很好的訊息佇列平臺來使用,Redis作為佇列使用的操 作,就類似于本地程式語言(如Python)對 list 的 push/pop 操作,
如果你快速的在 Google 中搜索“Redis queues”,你馬上就能找到大量的開源專案,這些專案的目的就是 利用 Redis 創建非常好的后端工具,以滿足各種佇列需求,例如,Celery 有一個后臺就是使用 Redis 作 為 broker,你可以從這里去查看,
(4) 排行榜/計數器
Redis 在記憶體中對數字進行遞增或遞減的操作實作的非常好,集合(Set)和有序集合(Sorted Set)也使得我們在執行這些操作的時候變的非常簡單,Redis 只是正好提供了這兩種資料結構, 所以,我們要從排序集合中獲取到排名最靠前的 10 個用戶–我們稱之為“user_scores”我們只需要像下面一樣執行即可:
當然,這是假定你是根據你用戶的分數做遞增的排序,如果你想回傳用戶及用戶的分數,你需要這樣執行:
ZRANGE user_scores 0 10 WITHSCORES
Agora Games 就是一個很好的例子,用 Ruby 實作的,它的排行榜就是使用 Redis 來存盤資料的,你可以在這里看到,
(5) 發布/訂閱
最后(但肯定不是最不重要的)是 Redis 的發布/訂閱功能,發布/訂閱的使用場景確實非常多,我已看見人們在社交網路連接中使用,還可作為基于發布/訂閱的腳本觸發器,甚至用 Redis 的發布/訂閱功能來建 立聊天系統!
29、Redis 支持的 Java 客戶端都有哪些?官方推薦用哪個?
Redisson、Jedis、lettuce 等等,官方推薦使用 Redisson,
30、Redis 和 Redisson 有什么關系?
Redisson 是一個高級的分布式協調 Redis 客服端,能幫助用戶在分布式環境中輕松實作一些 Java 的物件 (Bloom filter, BitSet, Set, SetMultimap, ScoredSortedSet, SortedSet, Map, ConcurrentMap, List, List Multimap, Queue, BlockingQueue, Deque, BlockingDeque, Semaphore, Lock, ReadWriteLock, Atomi cLong, CountDownLatch, Publish / Subscribe, HyperLogLog),
31、Jedis 與 Redisson 對比有什么優缺點?
Jedis 是 Redis 的 Java 實作的客戶端,其 API 提供了比較全面的 Redis 命令的支持;
Redisson 實作了分布式和可擴展的 Java 資料結構,和 Jedis 相比,功能較為簡單,不支持字串操作,不支持排序、事務、管道、磁區等 Redis 特性,Redisson 的宗旨是促進使用者對 Redis 的關注分離,從而讓使用者能夠將精力更集中地放在處理業務邏輯上,
32、Redis 如何設定密碼及驗證密碼?
設定密碼:config set requirepass 123456 授權密碼:auth 123456
33、說說 Redis 哈希槽的概念?
Redis 集群沒有使用一致性 hash,而是引入了哈希槽的概念,Redis 集群有16384 個哈希槽,每個 key 通過 CRC16 校驗后對 16384 取模來決定放置哪個槽,集群的每個節點負責一部分 hash 槽,
34、Redis 集群的主從復制模型是怎樣的?
為了使在部分節點失敗或者大部分節點無法通信的情況下集群仍然可用, 所以集群使用了主從復制模型,每個節點都會有 N-1 個復制品.
35、Redis 集群會有寫操作丟失嗎?為什么?
Redis 并不能保證資料的強一致性,這意味在實際中集群在特定的條件下可能會丟失寫操作,
36、Redis 集群之間是如何復制的?
異步復制
37、Redis 集群最大節點個數是多少?
16384 個,
38、Redis 集群如何選擇資料庫?
Redis 集群目前無法做資料庫選擇,默認在 0 資料庫,
39、怎么測驗 Redis 的連通性?
ping
40、Redis 中的管道有什么用?
一次請求/回應服務器能實作處理新的請求即使舊的請求還未被回應,這樣就可以將多個命令發送到服務器,而不用等待回復,最后在一個步驟中讀 取該答復,這就是管道(pipelining),是一種幾十年來廣泛使用的技術,
例如許多 POP3協議已經實作支持這個功能,大大加快了從服務器下載新郵件的程序,
41、怎么理解 Redis 事務?
事務是一個單獨的隔離操作:事務中的所有命令都會序列化、按順序地執行,事務在執行的程序中,不會被其他客戶端發送來的命令請求所打斷,事務是一個原子操作:事務中的命令要么全部被執行,要么全部都不執行,
42、Redis 事務相關的命令有哪幾個?
MULTI、EXEC、DISCARD、WATCH
43、Redis key的過期時間和永久有效分別怎么設定?
EXPIRE 和 PERSIST 命令,
44、Redis 如何做記憶體優化?
盡可能使用散串列(hashes),散串列(是說散串列里面存盤的數少)使用的內 存非常小,所以你應該盡可能的將你的資料模型抽象到一個散串列里面,比如你的 web 系統中有一個用戶物件,不要為這個用戶的名稱,姓氏, 郵箱,密碼設定單獨的 key,而是應該把這個用戶的所有資訊存盤到一張散串列里面,
45、Redis 回收行程如何作業的?
一個客戶端運行了新的命令,添加了新的資料,
Redis檢查記憶體使用情況,如果大于 maxmemory 的限制, 則根據設定好的策略進行回收,一個新的命令被執行,等等,
所以我們不斷地穿越記憶體限制的邊界,通過不斷達到邊界然后不斷地回識訓到邊界以下,
如果一個命令的結果導致大量記憶體被使用(例如很大的集合的交集保存到一個新的鍵),不用多久記憶體限制就會被這個記憶體使用量超越,
分布式資料庫MongoDB篇
1. 你說的 NoSQL 資料庫是什么意思?NoSQL 與 RDBMS 直接有什么區別?為什么要使用和不使用 NoSQL 資料庫?說一說 NoSQL 資料庫的幾個優點?
NoSQL 是非關系型資料庫,NoSQL = Not Only SQL,
關系型資料庫采用的結構化的資料,NoSQL 采用的是鍵值對的方式存盤資料,在處理非結構化/半結構化的大資料時;在水平方向上進行擴展時;隨時應對動態增加的資料項時可以優 先考慮使用 NoSQL 資料庫,
在考慮資料庫的成熟度;支持;分析和商業智能;管理及專業性等問題時,應優先考慮關系型資料庫,
2. NoSQL 資料庫有哪些型別?
例如:MongoDB, Cassandra, CouchDB, Hypertable, Redis, Riak, Neo4j, HBASE, Couchbase, MemcacheDB, RevenDB and Voldemort are the examples of NoSQL databases.
3. MySQL 與 MongoDB 之間最基本的差別是什么?
MySQL 和 MongoDB 兩者都是免費開源的資料庫,MySQL 和 MongoDB 有許多基本差別包括資料的表示(data representation),查詢,關系,事務,schema 的設計和定義,標準化(normalization),速度和性能,
通過比較 MySQL 和 MongoDB,實際上我們是在比較關系型和非關系型資料庫,即資料存盤結構不同,
4. 你怎么比較 MongoDB、CouchDB 及 CouchBase?
MongoDB 和 CouchDB 都是面向檔案的資料庫,MongoDB 和 CouchDB 都是開源 NoSQL 資料庫的最典型代表, 除了都以檔案形式存盤外它們沒有其他的共同點,MongoDB 和 CouchDB 在資料模型實作、介面、物件存盤以及復制方法等方面有很多不同,
5. MongoDB 成為最好 NoSQL 資料庫的原因是什么?
以下特點使得 MongoDB 成為最好的 NoSQL 資料庫:
● 面向檔案的
● 高性能
● 高可用性
● 易擴展性
● 豐富的查詢語言
6. 32 位系統上有什么細微差別?
journaling 會激活額外的記憶體映射檔案,這將進一步抑制 32 位版本上的資料庫大小,因此,現在 journaling 在 32 位系統上默認是禁用的,
7. journal 回放在條目(entry)不完整時(比如恰巧有一個中途故障了)會遇到問題嗎?
每個 journal (group)的寫操作都是一致的,除非它是完整的否則在恢復程序中它不會回放,
8. 分析器在 MongoDB 中的作用是什么?
MongoDB 中包括了一個可以顯示資料庫中每個操作性能特點的資料庫分析器,通過這個分析器你可以找到比預期慢的查詢(或寫操作);利用這一信 息,比如,可以確定是否需要添加索引,
9. 名字空間(namespace)是什么?
MongoDB 存盤 BSON 物件在叢集(collection)中,資料庫名字和叢集名字以句點連接起來叫做名字空間 (namespace),
10. 如果用戶移除物件的屬性?該屬性是否從存盤層中洗掉?
是的,用戶移除屬性然后物件會重新保存(re-save()),
11. 能否使用日志特征進行安全備份?
是的,
12. 允許空值 null 嗎?
對于物件成員而言,是的,然而用戶不能夠添加空值(null)到資料庫叢集(collection)因為空值不是物件, 然而用戶能夠添加空物件{},
13. 更新操作立刻 fsync 到磁盤?
不會,磁盤寫操作默認是延遲執行的,寫操作可能在兩三秒(默認在 60 秒內)后到達磁盤,例如,如果一秒內資料庫收到一千個對一個物件遞增的操作,僅重繪磁盤一次,(注意,盡管 fsync 選項在命令列和經 過getLastError_old 是有效的)(譯者:也許是坑人的面試題??),
14. 如何執行事務/加鎖?
MongoDB 沒有使用傳統的鎖或者復雜的帶回滾的事務,因為它設計的宗旨是輕量,快速以及可預計的高性能,可以把它類比成 MySQL MylSAM 的自動提交模式,通過精簡對事務的支持,性能得到了提升,特別是在一個可能會穿過多個服務器的系統里,
15. 為什么我的資料檔案如此龐大?
MongoDB 會積極的預分配預留空間來防止檔案系統碎片,
16. 啟用備份故障恢復需要多久?
從備份資料庫宣告主資料庫宕機到選出一個備份資料庫作為新的主資料庫將花費 10 到 30 秒時間,這期間在主資料庫上的操作將會失敗--包括寫入和強一致性讀取(strong consistent read)操作,然而,你還 能在第二資料庫上執行最終一致性查詢(eventually consistent query)(在 slaveOk 模式下),即使在這段時間里,
17. 什么是 master 或 primary?
它是當前備份集群(replica set)中負責處理所有寫入操作的主要節點/成員,在一個備份集群中,當失效備援(failover)事件發生時,一個另外的成員會 變成 primary,
18. 什么是 secondary 或 slave?
Seconday 從當前的 primary 上復制相應的操作,它是通過跟蹤復制
oplog(local.oplog.rs)做到的,
19. 我必須呼叫 getLastError 來確保寫操作生效了么?
不用,不管你有沒有呼叫 getLastError(又叫"Safe Mode")服務器做的操作都一樣,呼叫 getLastError 只 是為了確認寫操作成功提交了,當然,你經常想得到確認,但是寫操作的安全性和是否生效不是由這個決定的,
20. 我應該啟動一個集群分片(sharded)還是一個非集群分片的 MongoDB環境?
為開發便捷起見,我們建議以非集群分片(unsharded)方式開始一個MongoDB 環境,除非一臺服務器不足以存放你的初始資料集,從非集群分片升級到集群分片(sharding)是無縫的,所以在你的資料集還不 是很大的時候沒必要考慮集群分片(sharding),
21. 分片(sharding)和復制(replication)是怎樣作業的?
每一個分片(shard)是一個磁區資料的邏輯集合,分片可能由單一服務器或者集群組成,我們推薦為每一個分片(shard)使用集群,
22. 資料在什么時候才會擴展到多個分片(shard)里?
MongoDB 分片是基于區域(range)的,所以一個集合(collection)中的所有的物件都被存放到一個塊 (chunk)中,只有當存在多余一個塊的時候,才會有多個分片獲取資料的選項,現在,每個默認塊的大小 是 64Mb,所以你需要至少 64 Mb 空間才可以實施一個遷移,
23. 當我試圖更新一個正在被遷移的塊(chunk)上的檔案時會發生什么?
更新操作會立即發生在舊的分片(shard)上,然后更改才會在所有權轉移(ownership transfers)前復制到新的分片上,
24. 如果在一個分片(shard)停止或者很慢的時候?我發起一個查詢會怎樣?
如果一個分片(shard)停止了,除非查詢設定了“Partial”選項,否則查詢會回傳一個錯誤,如果一個分片(shard)回應很慢,MongoDB 則會等待它的回應,
25. 我可以把 moveChunk 目錄里的舊檔案洗掉嗎?
沒問題,這些檔案是在分片(shard)進行均衡操作(balancing)的時候產生的臨時檔案,一旦這些操作已經 完成,相關的臨時檔案也應該被洗掉掉,但目前清理作業是需要手動的,所以請小心地考慮再釋放這些檔案的空間,
26. 怎么查看 Mongo 正在使用的鏈接?
db._adminCommand("connPoolStats");
27. 如果塊移動操作(moveChunk)失敗了?我需要手動清除部分轉移的檔案嗎?
不需要,移動操作是一致(consistent)并且是確定性的(deterministic);一次失 敗后,移動操作會不斷重試; 當完成后,資料只會出現在新的分片里(shard),
28. 如果我在使用復制技術(replication)?可以一部分使用日志(journaling)而其他部分則不使用嗎?
可以,
29. 當更新一個正在被遷移的塊(Chunk)上的檔案時會發生什么?
更新操作會立即發生在舊的塊(Chunk)上,然后更改才會在所有權轉移前復制到新的分片上,
30. MongoDB 在 A:{B,C}上建立索引?查詢 A:{B,C}和 A:{C,B}都會使用索引嗎?
不會,只會在 A:{B,C}上使用索引,
31 如果一個分片(Shard)停止或很慢的時候?發起一個查詢會怎樣?
如果一個分片停止了,除非查詢設定了“Partial”選項,否則查詢會回傳一個錯誤,如果一個分片回應很慢,MongoDB 會等待它的回應,
32. MongoDB 支持存盤程序嗎?如果支持的話?怎么用?
MongoDB 支持存盤程序,它是 javascript 寫的,保存在 db.system.js 表中,
33. 如何理解 MongoDB 中的 GridFS 機制?MongoDB 為何使用 GridFS來存盤檔案?
GridFS 是一種將大型檔案存盤在 MongoDB 中的檔案規范,使用 GridFS 可以將大檔案分隔成多個小檔案存放,這樣我們能夠有效的保存大檔案, 而且解決了 BSON 物件有限制的問題,
分布式資料庫Memcached
1、memcached是怎么作業的?
Memcached的神奇來自兩階段哈希(two-stagehash),Memcached就像一個巨大的、存盤了很多<key,value>對的哈希表,通過key,可以存盤或查詢任意的資料,
客戶端可以把資料存盤在多臺 memcached 上,當查詢資料時,客戶端首先參考節點串列計算出 key 的哈希值(階段一哈希),進而選中一個節點;客戶端將請求發送給選中的節點,然后 memcached 節點通過一個內部的哈希演算法(階段二哈希),查找真正的資料(item),
舉個列子,假設有3 個客服端 1 2 3 臺 memcached A,B,C,
Client 1 想把資料”barbaz”以key “foo”存盤,Client 1 首先參考節點串列(A,B,C)計算 key “foo”的哈希值,假設 memcached B 被選中,接著,Client 1 直接 connect 到memcached B 通過 key “foo”把數
據”barbaz”存盤進去,Client 2 使用與 Client 1 相同的客戶端庫(意味著階段一的哈希演算法相同),也擁有同樣的 memcached 串列(A,B,C), 于是,經過相同的哈希計算(階段一),Client 2 計算出 key “foo”在 memcached B 上,然后它直接請求 memcached B,得到資料”barbaz”,各種客戶端在 memcached 中資料的存盤形式是不同的(perl Storable php serialize,java hibernate,JSON等),一些客戶端實作的哈希演算法也不一樣,但是,memcached 服務器端的行為總是一致的,
最后,從實作的角度看,memcached 是一個非阻塞的、基于事件的服務器程式,這種架構可以很好地解決 C10K problem,并具有極佳的可擴展性,
可以參考 A Story of Caching ,這篇文章簡單解釋了客戶端與 memcached
是如何互動的,
2、memcached 最大的優勢是什么?
請仔細閱讀上面的問題(即 memcached 是如何作業的),Memcached 最大的好處就是它帶來了極佳的水平可擴展性,特別是在一個巨大的系統中,由于客戶端自己做了一次哈希,那么我們很容易增加大量 memcached 到集群中,memcached 之間沒有相互通信,因此不會增加 memcached 的負載;沒有多播協議,不會網路通信量爆炸(implode),memcached 的集群很好用,記憶體不夠了?增加幾臺 memcached 吧;CPU 不夠用了?再增加幾臺吧;有多余的記憶體?在增加幾臺吧,不要浪費了,
基于 memcached 的基本原則,可以相當輕松地構建出不同型別的快取架構,除了這篇 FAQ,在其他地方很容易找到詳細資料的,
3、memcached 和 MySQL 的query cache 相比有什么優缺點?
把 memcached 引入應用中,還是需要不少作業量的,MySQL 有個使用方便的 query cache,可以自動地快取 SQL 查詢的結果,被快取的 SQL 查詢可以被反復地快速執行,Memcached 與之相比,怎么樣呢? MySQL 的 query cache 是集中式的,連接到該 query cache 的 MySQL 服務器都會受益,
● 當您修改表時,MySQL 的query cache 會立刻被重繪(flush),存盤一個memcached item 只需要很少的時間,但是當寫操作很頻繁時,MySQL 的query cache 會經常讓所有快取資料都失效,
● 在多核 CPU 上 MySQL 的 query cache 會遇到擴展問題(scalability issues),在多核 CPU 上 query cache 會增加一個全域鎖(global lock)、由于需要重繪更多的快取資料,速度會變得更慢,
● 在 MySQL 的 query cache 中,我們是不能存盤任意的資料的(只能是SQL 查詢結果),而利用 memcached,我們可以搭建出各種高效的快取,比如,可以執行多個獨立的查詢,構建出一個用戶物件(user object),然后將用戶物件快取到 memcached 中,而 query cache 是 SQL 陳述句級別的, 不可能做到這一點,在小的網站中,query cache 會有所幫助,但隨著網站規模的增加,query cache 的弊將大于利,
● query cache 能夠利用的記憶體容量受到 MySQL 服務器空閑記憶體空間的限制,給資料庫服務器增加更多的記憶體來快取資料,固然是很好的,但是, 有了 memcached,只要您有空閑的記憶體,都可以用來增加 memcached 集群的規模,然后您就可以快取更多的資料,
4、memcached 和服務器的 local cache (比如 PHP 的 APC、mmap 檔案等,相比?有什么優缺點2
首先,local cache 有許多與上面(query cache)相同的問題,local cache 能夠利用的記憶體容量受到(單臺)服務器空閑記憶體空間的限制,不過,local cache 有一點比 memcached 和 query cache 都要好,那就是它不但可以存盤任意的資料,而且沒有網路存取的延遲,
● local cache 的資料查詢更快,考慮把 highly common 的資料放在 local cache 中吧,如果每個頁面都需要加載一些數量較少的資料,考慮把它們放在 local cache 吧,
● local cache 缺少集體失效(group invalidation)的特性,在 memcached 集群中,洗掉或更新一個key 會讓所有的觀察者覺察到,但是在 local cache 中我們只能通知所有的服務器重繪 cache(很慢,不具擴展性),或者僅僅依賴快取超時失效機制,
● local cache 面臨著嚴重的記憶體限制,這一點上面已經提到,
5、memcached 的 cache 機制是怎樣的?
Memcached 主要的 cache 機制是 LRU 最近最少用)演算法+超時失效,當存資料到 memcached 中,可以指定該資料在快取中可以呆多久 Which is forever,or some time in the future,如果 memcached 的記憶體不夠用了,過期的 slabs 會優先被替換,接著就輪到最老的未被使用的 slabs
6、memcached 如何實作冗余機制?
不實作!我們對這個問題感到很驚訝,Memcached 應該是應用的快取層,它的設計本身就不帶有任何冗余機制,如果一個 Memcached 節點失去了所有資料,您應該可以從資料源(比如資料庫)再次獲取到資料,您應該特別注意,您的應用應該可以容忍節點的失效,不要寫一些糟糕的查詢代 碼,寄希望于 memcached 來保證一切!如果您擔心節點失效會大大加重資料庫的負擔,那么您可以采取一些辦法,比如您可以增加更多的節點(來減少丟失一個節點的影響),熱備節點(在其他節點 down 了的時候接管 IP)等等,
7、memcached 如何處理容錯的?
不處理!在 memcached 節點失效的情況下,集群沒有必要做任何容錯處理,如果發生了節點失效,應對的措施完全取決于用戶,節點失效時,下面列出幾種方案供您選擇:
● 忽略它!失效節點被恢復或替換之前,還有很多其他節點可以應對節點失效帶來的影響,
● 把失效的節點從節點串列中移除,做這個操作千萬要小心!在默認情況下(余數式哈希演算法),客戶端添加或移除節點,會導致所有的快取資料 不可用!因為哈希參照的節點串列變化了,大部分 key 會因為哈希值的改變而被映射到(與原來)不同的節點上,
● 啟動熱節點,接管失效節點所占用的 IP,這樣可以防止哈希紊亂(hashing chaos),
● 如果希望添加和移除節點,而不影響原先的哈希結果,可以使用一致性 哈希演算法(consistent hashing),您可以百度一下一致性哈希演算法,支持一致性哈希的客戶端已經很成熟,而且被廣泛使用,去嘗試一下吧!
● 兩次哈希(reshing),當客戶端存取資料時,如果發現一個節點(down) 了,就再做一次哈希(哈希演算法與前一次不同),重新選擇另一個節點(需要 注意的時,客戶端并沒有把 down 的節點從節點串列中移除,下次還是有可能先哈希到它),如果某個節點時好時壞,兩次哈希的方法就有風險了, 好的節點和壞的節點上都可能存在臟資料(stale data),
8、如何將 memcached 中 item 批量匯入匯出?
您不應該這樣做!Memcached 是一個非阻塞的服務器,任何可能導致memcached 暫停或瞬時拒絕服務的操作都應該值得深思熟慮,向memcached 中批量匯入資料往往不是您真正想要的!想象看,如果快取資料在匯出匯入之間發生了變化,您就需要處理臟資料了;如果快取資料在匯出匯入之間過期了,您又怎么處理這些資料呢?
因此,批量匯出匯入資料并不像您想象中的那么有用,不過在一個場景倒是很有用,如果您有大量的從不變化的資料,并且希望快取很快熱(warm) 起來,批量匯入快取資料是很有幫助的,雖然這個場景并不典型,但卻經常發生,因此我們會考慮在將來實作批量匯出匯入的功能,
9、我需要把 memcached 中的 item 批量匯出匯入?怎么辦?
如果需要批量匯出匯入,最可能的原因一般是重新生成快取資料需要消耗很長的時間,或者資料庫壞了讓您飽受痛苦,如果一個 memcached 節點 down 了讓您很痛苦,那么您還會陷入其他很多麻煩,您的系統太脆弱了,您需要做一些優化作業,比如處理“集群”問題(比如 memcached 節點都失效了,反復的查詢讓您的資料庫不堪重負... 這個問題在 FAQ 的其他提到過),或者優化不好的查詢,記住,Memcached 并不是您逃避優化查詢的借口,如果您的麻煩僅僅是重新生成快取資料需要消耗很長時間(15 秒到超過 5分鐘),您可以考慮重新使用資料庫,這里給出一些提示:
● 使用 MogileFS (或者 CouchDB 等類似的軟體)在存盤 item,把 item 計算出來并 dump 磁盤上,MogileFS 可以很方便地覆寫 item,并提供快速地訪問,您甚至可以把 MogileFS 中的 item 快取在 memcached 中,這樣可以加快讀取速度,MogileFS+Memcached 的組合可以加快快取不命中時的回應速度,提高網站的可用性,
● 重新使用 MySQL,MySQL 的 InnoDB 主鍵查詢的速度非常快,如果大部分快取資料都可以放到 VARCHAR 欄位中,那么主鍵查詢的性能將更好,從 memcached 中按 key 查詢幾乎等價于 MySQL 的主鍵查詢:將 key 哈希到 64-bit 的整數,然后將資料存盤到 MySQL 中,您可以把原始(不做哈希)的 key 存盤都普通的欄位中,然后建立二級索引來加快查詢...key 被動地失效,批量洗掉失效的 key,等等,
● 上面的方法都可以引入 memcached,在重啟 memcached 的時候仍然提供很好的性能,由于不需要當心”hot”的 item 被 memcached LRU 演算法突然淘汰,用戶再也不用花幾分鐘來等待重新生成快取資料(當快取資料突然從記憶體中消失時),因此上面的方法可以全面提高性能,
10、memcached 是如何做身份驗證的?
沒有身份認證機制!memcached 是運行在應用下層的軟體(身份驗證應該是應用上層的職責),memcached 的客戶端和服務器端之所以是輕量級的, 部分原因就是完全沒有實作身份驗證機制,這樣,memcached 可以很快地創建新連接,服務器端也無需任何配置,
如果您希望限制訪問,您可以使用防火墻,或者讓 memcached 監聽 unix domain socket,
11、memcached 的多執行緒是什么?如何使用它們?
執行緒就是定律(threads rule)!在 Steven Grimm 和 Facebook 的努力下, memcached 1.2 及更高版本擁有了多執行緒模式,多執行緒模式允許memcached 能夠充分利用多個 CPU,并在 CPU 之間共享所有的快取資料,memcached 使用一種簡單的鎖機制來保證資料更新操作的互斥,相比在同一個物理機器上運行多個 memcached 實體,這種方式能夠更有效地處理 multi gets,
如果您的系統負載并不重,也許您不需要啟用多執行緒作業模式,如果您在運行一個擁有大規模硬體的、龐大的網站,您將會看到多執行緒的好處, 簡單地總結一下:命令決議(memcached 在這里花了大部分時間)可以運行在多執行緒模式下,memcached 內部對資料的操作是基于很多全域鎖的(因此這部分作業不是多執行緒的),未來對多執行緒模式的改進,將移除大量的全域鎖,提高 memcached 在負載極高的場景下的性能,
12、memcached 能接受的 key 的最大長度是多少?
key 的最大長度是 250 個字符,需要注意的是,250 是 memcached 服務器端內部的限制,如果您使用的客戶端支持”key 的前綴”或類似特性,那么key(前綴+原始 key)的最大長度是可以超過250個字符的,我們推薦使用使用較短的 key ,因為可以節省記憶體和帶寬,
13、memcached 對 item 的過期時間有什么限制?
過期時間最大可以達到 30 天.memcached 把傳入的過期時間(時間段)解釋成時間點后,一旦到了這個時間點,memcached 就把 item 置為失效狀 態,這是一個簡單但 obscure 的機制,
14、memcached 最大能存盤多大的單個 item?
1MB,如果你的資料大于 1MB,可以考慮在客戶端壓碩訓拆分到多個 key
分布式中間件面試合輯 word檔案下載地址:鏈接:https://pan.baidu.com/s/1TGkSSnUoRjBXGTm0teXkeA
提取碼:1111爆肝一周,不眠不休!就為 點贊+好評+收藏 三連
性能調優合集
JVM性能優化面試篇
1、描述下Java類加載程序
Java類加載需要經歷以下7個程序∶
1.加載
加載是類加載的第一個程序,在這個階段,將完成以下三件事情∶
- 通過一個類的全限定名獲取該類的二進制流,
- 將該二進制流中的靜態存盤結構轉化為方法去運行時資料結構,
- 在記憶體中生成該類的 Class 物件,作為該類的資料訪問入口,
2.驗證
驗證的目的是為了確保 CLass 檔案的位元組流中的資訊不回危害到虛擬機.
在該階段主要完成以下四鐘驗證∶
- 檔案格式驗證∶ 驗證位元組流是否符合 Class 檔案的規范,如主次版本號是否在當前虛擬機范圍內,常量池中的常量是否有不被支持的型別.
- 元資料驗證∶對位元組碼描述的資訊進行語意分析,如這個類是否有父類,是否集成了不被繼承的類等,
- 位元組碼驗證∶ 是整個驗證程序中最復雜的一個階段,通過驗證資料流和控制流的分析,確定程式語意是否正確,主要針對方法體的驗證,如∶方法中的型別轉換是否正確,跳轉指令是否正確等,
- 符號參考驗證∶ 這個動作在后面的決議程序中發生,主要是為了確保決議動作能正確執行,
3.準備
準備階段是為類的靜態變數分配記憶體并將其初始化為默認值,這些記憶體都將在方法區中進行分配,準備階段不分配類中的實體變數的記憶體,實體變數將會在物件實體化時隨著物件一起分配在Java 堆中,
pubLic static int value=123;//在準備階段value初始值為0 ,在初始化階段才會變為123 ,
4.決議
該階段主要完成符號參考到直接參考的轉換動作,決議動作并不一定在初始化動作完成之前,也有可能在初始化之后,
5.初始化
初始化是類加載的最后一步,前面的類加載程序,除了在加載階段用戶應用程式可以通過自定義類加載器參與之外,其余動作完全由虛擬機主導和控制,到了初始化階段,才真正開始執行類中的定義的iava程式代碼,
6.使用
7.卸載
2、Java記憶體分配
- 暫存器∶我們無法控制,
- 靜態域∶ static定義的靜態成員,
- 常量池∶ 編譯時被確定并保存在.class 檔案中的(finaU)常量值和一些文本修飾的符號參考(類和介面的全限定名、欄位的名稱和描述符,方法和名稱和描述符),
- 非 RAM 存盤∶硬碟等永久存盤空間,
- 堆記憶體∶new 創建的物件和陣列,由Java 虛擬機自動垃圾回收器管理,存取速度慢,
- 堆疊記憶體∶ 基本型別的變數和物件的參考變數(堆記憶體空間的訪問地址),速度快,可以共享,但是大小與生存期必須確定,缺乏靈活性,
1.Java 堆的結構是什么樣子的?什么是堆中的永久代(Perm Gen space) ?
- JVM 的堆是運行時資料區,所有類的實體和陣列都是在堆上分配記憶體,它在 JVM 啟動的時候被創建,物件所占的堆記憶體是由自動記憶體管理系統也就是垃圾收集器回收,
- 堆記憶體是由存活和死亡的物件組成的,存活的物件是應用可以訪問的,不會被垃圾回收,死亡的物件是應用不可訪問尚且還沒有被垃圾收集器回收掉的物件,一直到垃圾收集器把這些物件回收掉之前,他們會一直占據堆記憶體空間,
3、描述一下JVM加載Class檔案的原理機制?
Java 語言是一種具有動態性的解釋型語言,類(Class)只有被加載到JVM 后才能運行,當運行指定程式時,JVM 會將編譯生成的 .class檔案按照需求和一定的規則加載到記憶體中,并組織成為—個完整的,Java 應用程式,這個加載程序是由類加載器完成,具體來說,就是由ClassLoader 和它的子類來實作的,類加載器本身也是一個類,其實質是把類檔案從硬碟讀取到記憶體中,
類的加載方式分為隱式加載和顯示加載,隱式加載指的是程式在使用new等方式創
建物件時,會隱式地呼叫類的加載器把對應的類加載到JVM 中,顯示加載指的是通過直接調
用 class.forName() 方法來把所需的類加載到JVM中,
任何一個工程專案都是由許多類組成的,當程式啟動時,只把需要的類加載到JVM 中,其他類只有被使用到的時候才會被加載,采用這種方法一方面可以加快加載速度,另一方面可以節約程式運行時對記憶體的開銷,此外,在Java 語言中,每個類或介面都對應一個class 檔案,這些檔案可以被看成是一個個可以被動態加載的單元,因此當只有部分類被修改時,只需要重新編譯變化的類即可,而不需要重新編譯所有檔案,因此加快了編譯速度,
在Java 語言中,類的加載是動態的,它并不會一次性將所有類全部加載后再運行,而是保證程式運行的基礎類(例如基類)完全加載到JVM中,至于其他類,則在需要的時候才加載,類加載的主要步驟∶
- 裝載,根據查找路徑找到相應的class 檔案,然后匯入,
- 鏈接,鏈接又可分為3個小步∶
- 檢查,檢查待加載的class 檔案的正確性,
- 準備,給類中的靜態變數分配存盤空間,
- 決議,將符號參考轉換為直接參考這一步可選)
- 初始化,對靜態變數和靜態代碼塊執行初始化作業,
4、GC 是什么?為什么會有GC
要有 GC?GC 是垃圾收集的意思(GabageCollection),記憶體處理是編程 人員容易出現問題
的地方,忘記或者錯誤的記憶體回識訓導致程式或 系統的不穩定甚至崩潰,Java 提供的 GC 功
能可以自動監測物件 是否超過作用域從而達到自動回收記憶體的目的,Java 語言沒有提 供釋放
已分配記憶體的顯示操作方法,
5、簡述 Java 垃圾回識訓制,
在 Java 中,程式員是不需要顯示的去釋放一個物件的記憶體的,而 是由虛擬機自行執行,在 JVM中,有一個垃圾回收執行緒,它是低 優先級的,在正常情況下是不會執行的,只有在虛擬機空閑或者當 前堆記憶體不足時,才會觸發執行,掃面那些沒有被任何參考的物件,并將它們添加到要回收的集合中,進行回收,
6、如何判斷一個物件是否存活?(或者 GC 物件的判定方法)
判斷一個物件是否存活有兩種方法∶
1.參考計數法
所謂參考計數法就是給每一個物件設定一個參考計數器,每當有— 個地方參考這個物件時,
就將計數器加一,參考失效時,計數器就 減一,當一個物件的參考計數器為零時,說明此對
象沒有被參考, 也就是"死物件",將會被垃圾回收.
參考計數法有一個缺陷就是無法解決回圈參考問題,也就是說當對 象 A參考物件 B,物件 B 又參考者物件 A,那么此時 A、B 對 象的參考計數器都不為零,也就造成無法完成垃圾回收,所以主流的虛擬機都沒有采用這種演算法,
2.可達性演算法(參考鏈法)
該演算法的思想是∶從一個被稱為 GC,Roots 的物件開始向下搜索,如果一個物件到 GC Roots 沒有任何參考鏈相連時,則說明此對 象不可用,在 Java 中可以作為 GC Roots的物件有以下幾種∶
- 虛擬機堆疊中參考的物件
- 方法區類靜態屬性參考的物件
- 方法區常量池參考的物件
- 本地方法堆疊 JNl 參考的物件
雖然這些演算法可以判定一個物件是否能被回收,但是當滿足上述條件時,一個物件并不一定會被回收,當一個物件不可達 GC Root 時,這個物件并不會立馬被回收,而是處于一個死緩的階段,若要 被真正的回收需要經歷兩次標記.
如果物件在可達性分析中沒有與 GC Root 的參考鏈,那么此時就 會被第一次標記并且進行一次篩選,篩選的條件是是否有必要執行
finalize()方法,當物件沒有覆寫 finalize()方法或者已被虛擬機呼叫過,那么就認為是沒必要的,如果該物件有必要執行 finalize()方法,那么這個物件將會放在一個稱為 F-Queue 的對 佇列中,虛擬機會觸發一個 FinaLize()執行緒去執行,此執行緒是低 優先級的,并且虛擬機不會承諾一直等待它運行完,這是因為如果 finalize() 執行緩慢或者發生了死鎖,那么就會造成 F-Queue 佇列一直等待,造成了記憶體回收系統的崩潰,GC 對處于F-Queue 中的物件進行第二次被標記,這時,該物件將被移除"即將回收"集合,等待回收,
7、垃圾回收的優點和原理,并考慮 2 種回識訓制,
Java 語言中一個顯著的特點就是引入了垃圾回識訓制,使 C+ 程式員最頭疼的記憶體管理的問題迎刃而解,它使得 Java 程式員在 撰寫程式的時候不再需要考慮記憶體管理,由于有個垃圾回識訓制, Java 中的物件不再有"作用域"的概念,只有物件的參考才有"作用域",垃圾回收可以有效的防止記憶體泄露,有效的使用可以使 用的記憶體,垃圾回收器通常是作為—個單獨的低級別的執行緒運行, 不可預知的情況下對記憶體堆中已經死亡的或者長時間沒有使用的 物件進行清楚和回收,程式員不能實時的呼叫垃圾回收器對某個對 象或所有物件進行垃圾回收,
回識訓制有分代復制垃圾回收和標記垃圾回收, 增量垃圾回收,
8、垃圾回收器的基本原理是什么?垃圾回收器可以馬上回收記憶體嗎?有什么辦法主動通知虛擬機進行垃圾回收?
對于 GC 來說,當程式員創建物件時,GC就開始監控這個物件 的地t,大小以及使用情況,通常,GC采用有向圖的方式記錄和 管理堆(heap)中的所有物件,通過這種方式確定哪些物件是"可達的",哪些物件是"不可達的",當 GC 確定一些物件為"不 可達"時,GC 就有責任回收這些記憶體空間,可以,程式員可以手動執行 System.gc(),通知 GC運行,但是 Java 語言規范并不 保證 GC 一定會執行,
9、Java 中會存在記憶體泄漏嗎,請簡單描述,
所謂記憶體泄露就是指一個不再被程式使用的物件或變數一直被占 據在記憶體中,Java 中有垃圾回識訓制,它可以保證一物件不再被參考的時候,即物件變成了孤兒的時候,物件將自動被垃圾回收器 從記憶體中清除掉,由于 Java 使用有向圖的方式進行垃圾回收管理, 可以消除參考回圈的問題,例如有兩個物件,相互參考,只要它們 和根行程不可達的,那么 GC 也是可以回收它們的,例如下面的 代碼可以看到這種情況的記憶體回收∶
import java.io.IOException; public class GarbageTest {
/**
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {
// TODO Auto-generated method stub try { gcTest();
} catch (IOException e) {
// TODO Auto-generated catch block e.printStackTrace();
}
System.out.println("has exited gcTest!"); System.in.read();
System.in.read(); System.out.println("out begin gc!"); for(int i=0;i<100;i++)
{
System.gc(); System.in.read(); System.in.read(); }
}
private static void gcTest() throws IOException { System.in.read();
System.in.read();
Person p1 = new Person(); System.in.read(); System.in.read();
Person p2 = new Person(); p1.setMate(p2); p2.setMate(p1);
System.out.println("before exit gctest!"); System.in.read(); System.in.read();
System.gc(); System.out.println("exit gctest!");
}
private static class Person {
oid setMate(Person other) {
mate = other;
}
}
}
Java 中的記憶體泄露的情況:長生命周期的物件持有短生命周期對 象的參考就很可能發生記憶體泄露,盡管短生命周期物件已經不再需 要,但是因為長生命周期物件持有它的參考而導致不能被回收,這 就是 Java 中記憶體泄露的發生場景,通俗地說,就是程式員可能創 建了一個物件,以后一直不再使用這個物件,這個物件卻一直被引 用,即這個物件無用但是卻無法被垃圾回收器回收的,這就是 java 中可能出現記憶體泄露的情況,例如,快取系統,我們加載了一個對 象放在快取中 (例如放在一個全域 map 物件中),然后一直不再 使用它,這個物件一直被快取參考,但卻不再被使用,
檢查 Java中的記憶體泄露,一定要讓程式將各種分支情況都完整執 行到程式結束,然后看某個物件是否被使用過,如果沒有,則才能判定這個物件屬于記憶體泄露,
如果一個外部類的實體物件的方法回傳了一個內部類的實體物件, 這個內部類物件被長期參考了,即使那個外部類實體物件不再被使用,但由于內部類持久外部類的實體物件,這個外部類物件將不會 被垃圾回收,這也會造成記憶體泄露,
下面內容來自于網上(主要特點就是清空堆疊中的某個元素,并不是徹底把它從陣列中拿掉,而是把存盤的總數減少,本人寫得可以比這個好,在拿掉某個元素時,順便也讓它從陣列中消失,將那個元素所在的位置的值設定為 null 即可):
我實在想不到比那個堆疊更經典的例子了,以致于我還要參考別人的例子,下面的例子不是我想到的,是書上看到的,當然如果沒有在書上看到,可能過一段時間我自己也想的到,可是那時我說是我 自己想到的也沒有人相信的
public class Stack {
private Object[] elements=new Object[10]; private int size = 0;
public void push(Object
e){ ensureCapacity();
elements[size++] = e;
}
public Object pop(){
if( size == 0) throw new EmptyStackException(); return element
s[--size];
}
private void ensureCapacity(){ if(elements.length == size){ Object[] oldElements = elements;
elements = new Object[2 * elements.length+1]; System.arraycopy (oldElements,0, elements, 0,
size);
}
}
}
上面的原理應該很簡單,假如堆疊加了 10 個元素,然后全部彈 出來,雖然堆疊是空的,沒有我們要的東西,但是這是個物件是無 法回收的,這個才符合了記憶體泄露的兩個條件:無用,無法回收,但是就是存在這樣的東西也不一定會導致什么樣的后果,如果這個 堆疊用的比較少,也就浪費了幾個 K記憶體而已,反正我們的記憶體都上G了,哪里會有什么影響,再說這個東西很快就會被回收的,有什么關系,
public class Bad{
public static Stack s=Stack(); static{ s.push(new Object());
s.pop(); //這里有—個物件發生記憶體泄露
s.push(new Object()); //上面的物件可以被回收了,等于是自 愈了
}
}
下面看兩個例子,
因為是static,就一直存在到程式退出,但是我們也可以看到它有自愈功能,就是說如果你Stack最多有100個物件,那么最多也就只有100個物件無法被回收其實這個應該很容易理解,Stack內部持有100個參考,最壞的情況就是他們都是無用的,因為我們一旦放新的進取,以前的參考自然消失!
記憶體泄露的另外一種情況:當一個物件被存盤進 HashSet 集合中 以后,就不能修改這個物件中的那些參與計算哈希值的欄位了否則,物件修改后的哈希值與最初存盤進 HashSet集合中時的哈希值就不同了,在這種情況下,即使在 contains 方法使用該物件的當前參考作為的引數去 HashSet 集合中檢索物件,也將回傳找不到物件的結果,這也會導致無法合中檢索物件,也將回傳找不 到物件的結果,這也會導致無法從 HashSet 集合中單獨洗掉當前物件,造成記憶體泄露,
10、什么是深拷貝、什么是淺拷貝
簡單來講就是復制、克隆,
Person p=new Person(“張三”);
淺拷貝就是對物件中的資料成員進行簡單賦值,如果存在動態成員或者指標就會報錯,
深拷貝就是對物件中存在的動態成員或指標重新開辟記憶體空間,
11、System.gc() 和 Runtime.gc() 會做什么事情?
這兩個方法用來提示 JVM 要進行垃圾回收,但是,立即開始還是 延遲進行垃圾回收是取決于 JVM 的,
12、finalize() 方法什么時候被呼叫?解構式 (finalization) 的 目的是什么?
垃圾回收器(garbage colector)決定回收某物件時,就會運行該 物件的finalize() 方法 但是在 Java 中很不幸,如果記憶體總是充 足的,那么垃圾回收可能永遠不會進行,也就是說 filalize() 可能 永遠不被執行,顯然指望它做收尾作業是靠不住的,那么 finalize() 究竟是做什么的呢? 它最主要的用途是回收特殊渠道 申請的記憶體,Java 程式有垃圾回收器,所以一般情況下記憶體問題 不用程式員操心,但有一種 JNI(Java Native Interface)呼叫non-Java 程式(C 或 C++), finalize() 的作業就是回收這部 分的記憶體,
13、如果物件的參考被置為 null?垃圾收集器是否會立即釋放物件占 用的記憶體?
不會,在下一個垃圾回收周期中,這個物件將是可被回收的,
14、什么是分布式垃圾回收(DGC)?它是如何作業的?
DGC 叫做分布式垃圾回收,RMI 使用 DGC 來做自動垃圾回收,因為 RMI 包含了跨虛擬機的遠程物件的參考,垃圾回收是很困難 的,DGC 使用參考計數演算法來給遠程物件提供自動記憶體管理,
15、串行(serial)收集器和吞吐量(throughput)收集器的區別是什么?
吞吐量收集器使用并行版本的新生代垃圾收集器,它用于中等規模 和大規模資料的應用程式,而串行收集器對大多數的小應用(在 現代處理器上需要大概 100M 左右的記憶體)就足夠了,
16、在 Java 中物件什么時候可以被垃圾回收?
當物件對當前使用這個物件的應用程式變得不可觸及的時候,這個 物件就可以被回收了,
17、簡述 Java 記憶體分配與回收策率以及 Minor GC 和 Major GC)
- 物件優先在堆的 Eden 區分配
- 大物件直接進入老年代
- 長期存活的物件將直接進入老年代
當 Eden 區沒有足夠的空間進行分配時,虛擬機會執行一次 Minor GC,Minor GC 通常發生在
新生代的 Eden 區,在這個區 的物件生存期短,往往發生 Gc 的頻率較高,回收速度比較快; FullGC/Major GC 發生在老年代,一般情況下,觸發老年代 GC 的時候不會觸發 Minor GC,但是通過
配置,可以在 Full GC 之 前進行一次 Minor GC 這樣可以加快老年代的回收速度,
18、JVM 的永久代中會發生垃圾回收么?
垃圾回收不會發生在永久代,如果永久代滿了或者是超過了臨界值, 會觸發完全垃圾回收(Full GC),
注: Java 8 中已經移除了永久代,新加了一個叫做元資料區的 native 記憶體區,
19、Java 中垃圾收集的方法有哪些?
標記 - 清除:這是垃圾收集演算法中最基礎的,根據名字就可以知道,它的思想就是標記哪些要被回收的物件,然后統一回收,這種方法很簡單,但是會有兩個主要問題:
1. 效率不高,標記和清除的效率都很低;
2. 會產生大量不連續的記憶體碎片,導致以后程式在分配較大的物件時,由于沒有充足的連續記憶體而提前觸發一次 GC 動作,
復制演算法:為了解決效率問題,復制演算法將可用記憶體按容量劃分為相等的兩部分,然后每次只使用其中的一塊,當一塊記憶體用完時,就將還存活的對 象復制到第二塊記憶體上,然后一次性清楚完第一塊記憶體,再將第二塊上的 物件復制到第一塊,但是這種方式,記憶體的代價太高,每次基本上都要浪 費一般的記憶體,于是將該演算法進行了改進,記憶體區域不再是按照 1:1 去劃分,而 是將記憶體劃分為 8:1:1 三部分,較大那份記憶體交 Eden 區,其余是兩塊較小的記憶體區叫 Survior 區,每次都會優先使用 Eden 區, 若 Eden 區滿,就將物件復制到第二塊記憶體區上,然后清除 Eden 區,如果此時存活的物件太多,以至于 Survivor 不夠時,會將這 些物件通過分配擔保機制復制到老年代中,(java 堆又分為新生代和老年代)
標記 - 整理:該演算法主要是為了解決標記 - 清除,產生大量記憶體 碎片的問題;當物件存活率較高時,也解決了復制演算法的效率問題,它的不同之處就是在清除物件的時候現將可回收物件移動到一端, 然后清除掉端邊界以外的物件,這樣就不會產生記憶體碎片了,
分代收集:現在的虛擬機垃圾收集大多采用這種方式,它根據物件的生存周期,將堆分為新生代和老年代,在新生代中,由于物件生存期短,每次回 收都會有大量物件死去,那么這時就采用復制演算法,老年代里的物件存活率較高,沒有額外的空間進行分配擔保,
20、什么是類加載器?類加載器有哪些?
實作通過類的權限定名獲取該類的二進制位元組流的代碼塊叫做類加載器, 主要有一下四種類加載器:
- 啟動類加載器(BootstrapClassLoader)用來加載Java核 心類別庫,無法被Java 程式直接參考,
- 擴展類加載器(extensions class loader):它用來加載 Java 的擴展庫,Java 虛擬機的實作會提供一個擴展庫目錄,該類 加載器在此目錄里面查找并加載 Java 類,
- 系統類加載器(system class loader):它根據 Java 應用 的類路徑(CLASSPATH)來加載 Java 類,一般來說,Java 應用的類都是由它來完成加載的,可以通過 ClassLoader.getSystemClassLoader() 來獲取它,
- 用戶自定義類加載器,通過繼承 java.lang.ClassLoader 類 的方式實作,
21、類加載器雙親委派模型機制?
當一個類收到了類加載請求時,不會自己先去加載這個類,而是將其委派給父類,由父類去加載,如果此時父類不能加載,反饋給子類,由子類去完成類的加載,
22、一個新系統開發完畢之后如何設定JVM引數?
首先應該估算一下自己負責的系統每個核心介面每秒多少次請求,每次請求會創建多少個物件,每個物件大概多大,每秒鐘會使用多少記憶體空間?
這樣接著就可以估算出來Eden區大概多長時間會占滿?
然后就可以估算出來多長時間會發生一次Young GC,而且可以估算一下發生Young GC的時候,會有多少物件存活下來,會有多少對
象升入老年代里,老年代物件增長的速率大概是多少,多久之后會觸發一次Full GC,
通過一連串的估算,就可以合理的分配年輕代和老年代的空間,還有Eden和Survivor的空間
原則就是:盡可能讓每次Young GC后存活物件遠遠小于Survivor區域,避免物件頻繁進入老年代觸發Full GC,
最理想的狀態下,就是系統幾乎不發生Full GC,老年代應該就是穩定占用一定的空間,就是那些長期存活的物件在躲過15次Young
GC后升入老年代自然占用的,然后平時主要就是幾分鐘發生一次Young GC,耗時幾毫秒,
23、在壓測之后合理調整JVM引數
任何一個新系統上線都得進行壓測,此時在模擬線上壓力的場景下,可以用jstat等工具去觀察JVM的運行記憶體模型:
Eden區的物件增長速率多塊?
Young GC頻率多高?
一次Young GC多長耗時?
Young GC過后多少物件存活?
老年代的物件增長速率多高?
Full GC頻率多高?
一次Full GC耗時?
壓測時可以完全精準的通過jstat觀察出來上述JVM運行指標,讓我們對JVM運行時的情況了如指掌,然后就可以盡可能的優化JVM的內
存分配,盡量避免物件頻繁進入老年代,盡量讓系統僅僅有Young GC,
24、線上系統的監控和優化
系統上線之后,務必進行一定的監控,高大上的做法就是通過Zabbix、Open-Falcon之類的工具來監控機器和JVM的運行,頻繁Full
GC就要報警,
比較差一點的做法,就是在機器上運行jstat,讓其把監控資訊寫入一個檔案,每天定時檢查一下看一看,
一旦發現頻繁Full GC的情況就要進行優化,優化的核心思路是類似的:通過jstat分析出來系統的JVM運行指標,找到Full GC的核心問
題,然后優化一下JVM的引數,盡量讓物件別進入老年代,減少Full GC的頻率,
25、線上頻繁Full GC的幾種表現
其實通過之前的各種案例,大家可以總結出來,一旦系統發生頻繁Full GC,大概看到的一些表象如下:
機器CPU負載過高;
頻繁Full GC報警;
系統無法處理請求或者處理過慢
所以一旦發生上述幾個情況,大家第一時間得想到是不是發生了頻繁Full GC,
26、頻繁Full GC的幾種常見原因
頻繁Full GC的原因:
系統承載高并發請求,或者處理資料量過大,導致Young GC很頻繁,而且每次Young GC過后存活物件太多,記憶體分配不合理,
Survivor區域過小,導致物件頻繁進入老年代,頻繁觸發Full GC,
系統一次性加載過多資料進記憶體,搞出來很多大物件,導致頻繁有大物件進入老年代,必然頻繁觸發Full GC
系統發生了記憶體泄漏,莫名其妙創建大量的物件,始終無法回收,一直占用在老年代里,必然頻繁觸發Full GC
Metaspace(永久代)因為加載類過多觸發Full GC
誤呼叫System.gc()觸發Full GC
其實常見的頻繁Full GC原因無非就上述那幾種,所以大家在線上處理Full GC的時候,就從這幾個角度入手去分析即可,核心利器就是
jstat,
如果jstat分析發現Full GC原因是第一種,那么就合理分配記憶體,調大Survivor區域即可,
如果jstat分析發現是第二種或第三種原因,也就是老年代一直有大量物件無法回收掉,年輕代升入老年代的物件病不多,那么就dump
出來記憶體快照,然后用MAT工具進行分析即可
通過分析,找出來什么物件占用記憶體過多,然后通過一些物件的參考和執行緒執行堆疊的分析,找到哪塊代碼弄出來那么多的物件的,接
著優化代碼即可,
如果jstat分析發現記憶體使用不多,還頻繁觸發Full GC,必然是第四種和第五種,此時對應的進行優化即可,
27、發生FullGC
- youngGC 之前 ,空間擔保機制,老年代的連續空間小于 新生代所有物件總大小 和 歷次晉升的平均大小
- youngGC 之后 新生代幸存的物件大小 老年代放不下了,
- 大物件放不下老年代
- 老年代的大小 達到閾值了
- 元空間滿了
- 你呼叫了 System.gc
28、可能發生OOM的地方
元空間(class太多了)
第一種原因,很多工程師他不懂JVM的運行原理,在上線系統的時候對Metaspace區域直接用默認的引數,即根本不設定其大小
這會導致默認的Metaspace區域可能才幾十MB而已,此時對于一個稍微大型一點的系統,因為他自己有很多類,還依賴了很多外
部的jar包有有很多的類,幾十MB的Metaspace很容易就不夠了
第二種原因,就是很多人寫系統的時候會用cglib之類的技術動態生成一些類,一旦代碼中沒有控制好,導致你生成的類過于多的
時候,就很容易把Metaspace給塞滿,進而引發記憶體溢位
堆疊記憶體
出現了死回圈呼叫,或者是無限制的遞回呼叫,
堆記憶體
物件太多,且都是存活的,即使GC過后還是沒有空間了,此時放不下新的物件
29、如何定位OOM
- jvm 需要加上 列印 gc.log ,配置記憶體溢位引數, 記憶體溢位的時候匯出來一份記憶體快照到我們指定的位置
- 發生oom 會推送通知,
- 然后看日志
- 看看到底是堆記憶體溢位?還是堆疊記憶體溢位?或者是Metaspace記憶體溢位?首先得確定一下具體的溢位型別
- 看看是哪個執行緒運行代碼的時候記憶體溢位了,因為Tomcat運行的時候不光有自己的作業執行緒,我們寫的代碼也可能會創建一些執行緒出來
- 接著通過 mat 分析dump 是什么物件導致的,找到對應的代碼行,
使用工具
jstat 查看 fgc ygc 次數 時長,每個空間的使用大小
jmap dump 檔案
mat 查看堆的分配大小
jstack 查看死鎖
30、生成環境如何設定堆記憶體大小
- QPS * 物件的大小 * 物件數量 算出 每秒產生的大小,比如每秒20M
- 每個事件大概可以處理的時間,比如1秒,
- 那么可以算出大概每秒產生 20M 的垃圾,
- 比如2核4G的機器
- 分配2GJVM,1.5G堆,可以設定500兆 年輕代,其中設定比例為8:1:1,每個 survivor區為 50M,Eden區400兆,700兆老年代,300兆元空間,
- 元空間設定,防止我們用了反射,默認幾十兆不夠用,
31、jvm記憶體分為哪幾塊
java虛擬機堆疊,本地方法堆疊,堆,程式計數器,方法區
32、說說JVM的垃圾回收演算法?
復制
標記清除
標記整理
分代演算法
收集器
- serial
- ParNew
- cms
- g1
CMS流程
- 初始標記,會發生 STW ,主要標記GC ROOT,
- 并發標記,比較費時間,主要是從GC ROOT 追蹤那些參考的物件,也就是標記那些存活的物件和不存活的物件
- 重新標記,會發生 STW ,重新標記下在第二階段里新創建的一些物件,還有一些已有物件可能失去參考變成垃圾的情況
- 并發清除,
33、G1是什么
- 他最大的一個特點,就是把Java堆記憶體拆分為多個大小相等的Region,
- 每個region為 2的 倍數,比如 1M,2M,
- 然后G1也會有新生代和老年代的概念,但是只不過是邏輯上的概念,最開始默認新生代5%,
- G1還有一個特點,就是可以讓我們設定一個垃圾回收的預期停頓時間,在一個時間內,垃圾回收導致的系統停頓時間不能超過多久,G1全權給你負責,保證達到這個目標,
- 他會追蹤每個Region里的回收價值,也就是計算每個region 需要耗費多長時間,可以回收掉多少垃圾
- 新生代的 Eden 區和 Survivor 區,也是可以設定比例的,大小隨著空間變化,新生代達到了60%,就會發生ygc,
- 老年代達到了 45% ,會發生 mixedGC,新生代和老年代一起GC,
34、G1流程
- 初始標記
- 并發標記
- 最終標記
- 混合回收(回收老年代,新生代)可以設定分幾次回收,默認8次,當回收到5%(可配置)就不回收了,
35、物件什么時候轉移到老年代?
- 大物件
- ygc之后年輕代放不下
- 動態年齡機制,ygc之后物件的生命從小到大排序,當超過survivor區的一半,就將年齡大的物件放到老年代,
- 連續15次 ygc之后,物件放到老年代
36、常量存盤在哪里?
運行時常量存在方法區,字串常量存在堆區,
37、自定義加載器
- 創建一個類繼承ClassLoader抽象類
- 重寫findClass()方法
- 在findClass()方法中呼叫defineClass()
Tomcat調優面試篇
1、你會怎樣給Tomcat進行調優
1.JVM引數調優
-Xms<size>表示 JVM 初始化堆的大小, Xmx<size>表示 JVM 堆的最大值,這兩個值的大小一般根據需要進行設定,當應用程式需要的記憶體超出堆的最大值時虛擬機就會提示記憶體溢位,并且導致應用服務崩潰,因此一般建議堆的最大值設定為可用記憶體的最大值的80%,在catalina.bat 中,設定 JAVA_OPTS='-Xms256m- Xmx512m',表示初始化記憶體為256MB,可以使用的最大記憶體為512MB,
2.禁用DNS查詢
當web應用程式想要記錄客戶端的資訊時,它也會記錄客戶端的IP地址或者通過域名服務器查找機器名轉換為IP地址,DNS查詢需要占用網路,并且包括可能從很多很遠的服務器或者不起作用的服務器上去獲取對應的IP的程序,這樣會消耗一定的時間,為了消除DNS查詢對性能的影響我們可以關閉DNS查詢,方式是修改 server.xmL 檔案中的lenableLookups,引數值:
Tomcat4
<Connector
className="org.apache.coyote.tomcat4.CoyoteConnector"port="80"
minProcessors="5"maxProcessors="75"enableLookups="false"redirectPort="8443"
acceptCount="100"debug="0"connectionTimeout="20000"
useURIValidationHack="false"disableUploadTimeout="true"/>
Tomcat5<Connectorport="80"maxThreads="150"minSpareThreads="25"
maxSpareThreads="75"enableLookups="false"redirectPort="8443"acceptCount="100"debug="0"
connectionTimeout="20000"
disableUploadTimeout="true"/>
3.調整執行緒數
通過應用程式的連接器 (Connector)進行性能控制的的引數是創建的處理請求的執行緒數,Tomcat 使用執行緒池加速回應速度來處理請求,在 Java中執行緒是程式運行時的路徑,是在一個程式中與其它控制執行緒無關的、能夠獨立運行的代碼段,它們共享相同的地址空間,多執行緒幫助程式員寫出CPU最大利用率的高效程式,使空閑時間保持最低,從而接更多的請求,Tomcat4中可以通過修改 minProcessors 和 maxProcessors 的值來控制執行緒數,這些值在安裝后就已經設定為默認值并且是足夠使用的,但是隨著站點的擴容而改大這些值,minProcessors 服務器啟動時創建的處理請求的執行緒數應該足夠處理一個小量的負載,也就是說,如果一天內每秒僅發生5次單擊事件, 并且每個請求任務處理需要1秒鐘,那么預先設定執行緒數為5就足夠了,但在你的站點訪問量較大時就需要設定更大的執行緒數,指定為引數 maxProcessors 的值,maxProcessors 的值也是有上限的,應防止流量不可控制(或者惡意的服務攻擊),從而導致超出了虛擬機使用記憶體的大小,如果要加大并發連接數,應同時加大這兩個引數,web server允許的最大連接數還受制于作業系統的內核引數設定,通常Windows是2000個左右,Linux是1000個左右,
在 Tomcat5 對這些引數進行了調整,請看下面屬性∶
- maxThreads Tomcat 使用執行緒來處理接收的每個請求,這個值表示 Tomcat可創建的最大的執行緒數,
- acceptCount 指定當所有可以使用的處理請求的執行緒數都被使用時,可以放到處理佇列中的請求數,超過這個數的請求將不予處理,
- connnection Timeout 網路連接超時,單位∶ 毫秒,設定為0表示永不超時,這樣設定有隱患的,通常可設定為30000毫秒,
- minSpareThreadsTomcat 初始化時創建的執行緒數,
- maxSpareThreads 一旦創建的執行緒超過這個值,Tomcat 就會關閉不再需要的 socket 執行緒,
最好的方式是多設定幾次并且進行測驗,觀察回應時間和記憶體使用情況,在不同的機器、作業系統或虛擬機組合的情況下可能會不同,而且并不是所有人的web站點的流量都是一樣的,因此沒有一刀切的方案來確定執行緒數的值,
2、如何加大Tomcat連接數
在 Tomcat 組態檔 server,xmL 中的 <Connector/>配置中,和連接數相關的引數有∶
- minProcessors ∶ 最小空閑連接執行緒數,用于提高系統處理性能,默認值為10∶
- maxProcessors : 最大連接執行緒數,即∶ 并發處理的最大請求數,默認值為75
- acceptCount∶允許的最大連接數,應大于等于maxProcessors,默認值為100
- enabLeLookups∶是否反查域名,取值為∶ true或false,為了提高處理能力,應設定為falseconnection
- connectionTimeout∶網路連接超時,單位∶ 毫秒,設定為0表示永不超時,這樣設定有隱患的,通常可設定為30000
- web server :允許的最大連接數還受制于作業系統的內核引數設定,通常Windows是2000個左右,Linux是1000個左右,
tomcat5的配置引數:
<ConnectoTport="8686"
maxThreads="150"minSpareThreads="25"maxSpareThreads="75"
enableLookups="false"redirectPort="8443"acceptCount="100"
debug="o"connectionTimeout="20000" disabLeUploadTimeout="true"/>
對于其他埠的偵聽配置,以此類推,
3、怎樣加大Tomcat的記憶體
首先檢查程式有沒有陷入死回圈這個問題主要還是由這個問題
java.Lang.0utOfMemoryError∶Java heap space引起的,第一次出現這樣的的問題以后,
引發了其他的問題,在網上一查可能是JAVA的堆疊設定太小的原因,根據網上的答案大致有
這兩種解決方法∶
1、設定環境變數
解決方法∶手動設定 Heap size
修改 TOMCAT_HOME/bin/catalina. sh
setJAVA_OPTS=-Xms32m-Xmx512m
可以根據自己機器的記憶體進行更改,
2、java-Xms32m-Xmx800m classNamel
在執行JAVA類檔案時加上這個引數,其中className是需要執行的全類路徑名,這個解決問題了,而且執行的速度比沒有設定的時候快很多,如果在測驗的時候可能會用
Eclispe這時候就需要在 Eclipse->run-arguments 中的 WM arguments 中輸入-Xms32m-Xmx800m 這個引數就可以了,
后來在Ecilpse中修改了啟動引數,在VMarguments加入了 -Xms32m-Xmx800m,問題解決,
一、java.lang.0utOfMemoryError∶PermGen space
PermGen space 的全稱是 Permanent Generation space ,是指記憶體的永久保存區域,
這塊記憶體主要是被JVM存放Class和Meta資訊的,Class在被Loader時就會被放到 PermGen space 中,它和存放類實體(nstance)的Heap區域不同,GC(Garbage Collection)不會在主程式運行期對 PermGen space進行清理,所以如果你的應用中有很多CLASS的話,就很可能出現 PermGen space 錯誤,這種錯誤常見在web服務器對JSP進行 preco mpile 的時候,如果你的 WEB APP 下都用了大量的第三方jar,其大小超過了jvm默認的大小(4M)那么就會產生此錯誤資訊了,
解決方法∶
手動設定MaxPermSize大小修改TOMCAT_H0ME/bin/catalina.sh在 "echo"Using
CATALINA_BASE∶$CATALINA_BASE""上面加入以下行∶JAVA_OPTS="-server-
XX∶PermSize=64M-XX∶MaxPermSize=128m
建議∶將相同的第三方jar檔案移置到tomcat/shared/Lib目錄下,這樣可以達到減少jar檔案重復占用記憶體的目的,
二、java.Lang.0ut0fMemoryError:Java heap space Heap size 設定
JVM堆的設定是指java程式運行程序中JVM可以調配使用的記憶體空間的設定.JVM在啟動的時候會自動設定 Heap sizel的值,其初始空間(即-Xms)是物理記憶體的1/64,最大空間(-Xmx)是物理記憶體的1/4,可以利用JVM提供的一-Xmn-Xms-Xmx 等選項可進行設定,Heap size 的大小是 Young Generation 和 TenuredGeneraion之和,
提示∶ 在JVM中如果98%的時間是用于GC且可用的 Heap size 不足2%的時候將拋出此例外資訊,
提示∶ Heap Size 最大不要超過可用物理記憶體的80%,一般的要將 Xms 和-Xmx選項設定為相同,而-Xmn 為1/4的 -Xmx值,
解決方法∶ 手動設定 Heap size修改 TOMCAT_HOME/bin/catalina.sh在"echo"Using CATALINABASE∶$CATALINA_BASE""上面加入以下行∶ JAVA_OPTS="-server-Xms800m-Xmx800m-XX:MaxNewSize=256m"
三、實體給出以下1G記憶體環境下java jvml的引數設定參考∶
JAVA_OPTS="-server-Xms800m-Xmx800m-XX:PermSize=64M-XX:MaxNewSize=256m-XX:MaxPermSize=128m- Djava.awt. headless=true"很大的web工程,用tomcat默認分配的記憶體空間無法啟動,如果不是在 myeclipse中啟動tomcat可以對tomcat這樣設定∶
T0MCAT_HOME/bin/cataLina.bat 中添加這樣一句話∶
set JAVA_OPTS=-server-Xms2048m-Xmx4096m-XX:PermSize=512M-XX:MaxPermSize=1024M-Duser.timezone=GMT+08或者set JAVA_OPTS=-Xmx1024M-Xms512M-XX:MaxPermSize=256m如果要在myeclipse中啟動,上述的修改就不起作用了,可如下設定∶ Myeclipse->preferences->myeclipse->servers->tomcat->tomcat×.x->JDK 面板中的 0ptional Java WM arguments 中添加∶Xmx1024M-Xms512M-XX:MaxPermSize=256m
以上是轉貼,但本人遇見的問題是∶ 在myeclipse中啟動Tomcat時,提示"Java.Lang.0utOfMemoryError∶Java heap space",
解決辦法就是∶
MyecLipse->preferences→>myecLipse>servers->tomcat->tomcat×.×->JDK 面板中的Optional Java VM arguments 中添加∶-Xmx1024M-Xms512M- XX:MaxPermSize=256m
4、tomcat中如何禁止列目錄下的檔案
在 {tomcat_home}/conf/web.xmL中,把listings引數設定成false即可,
如下∶
<init-param>
<φparam-name>listings</param-name><param-value>false</param-value>
</init-param>
<init-param>
<param-name>Listings</param-name><param-value>false</param-value>
</init-param>
5、Tomcat有幾種部署方式 tomcat中四種部署專案方法
第一種方法∶
在 tomcat 中的 conf目錄中,在 server.xml中的,<host/節點中添加
<Context path="/hello"
docBase="D:/ecLipse3.2.2/forwebtooLsworkspacehello/WebRoot"deb ug="O"
priviLeged="true",
</Context>
至于 Context 節點屬性,可詳細見相關檔案,
第二種方法∶
將 web 專案檔案件拷貝到 webapps 目錄中,
第三種方法∶
在conf目錄中,新建 CataLina\Loca Lhost 目錄,在該目錄中新建一個xmL 檔案,名字可以隨意取,只要和當前檔案中的檔案名不重復就行了,該 xmL 檔案的內容為∶
<Context path="/hello"docBase="D:eclipse3.2.2forwebtoolsworksp aceheloWebRoot"
debug="o"priviLeged="true">
</Context>
第3個方法有個優點,可以定義別名,服務器端運行的專案名稱為 path,外部訪問的 URL 則使用XML的檔案名,這個方法很方便的隱藏了專案的名稱,對一些專案名稱被固定不能更換,但外部訪問時又想換個路徑,非常有效,
第2、3還有優點,可以定義一些個性配置,如資料源的配置等,
第四種方法∶
可以用 tomcat在線后臺管理器,一般 tomcat 都打開了,直接上傳 warl就可以,
6、Tomcat的優化經驗
Tomcat 作為 web 服務器,它的處理性能直接關系到用戶體驗,下面是種常見的優化措施∶
- 去掉對 web.xmL的監視,把 jsp 提前編輯成 ServLet,存的情況,加大 tomcat 使用的 jvm 的記憶體,
- 服務器資源:服務器所能提供CPU、記憶體、硬碟的性能對處理能力有決定性影響,
- 對于高并發情況下會有大量的運算,那么CPU的速度會直接影響到處理速度,
- 記憶體在大量資料處理的情況下,將會有較大的記憶體容量需求,可以用-Xmx-Xms-XX∶MaxPermSize 等引數對記憶體不同功能塊進行劃分,我們之前就遇到過記憶體分配不足,導致虛擬機一直處于 fuLL GC ,從而導致處理能力嚴重下降,
- 硬碟主要問題就是讀寫性能,當大量檔案進行讀寫時,磁盤極容易成為性能瓶頸,最好的辦法還是利用下面提到的快取,
- 利用快取和壓縮
對于靜態頁面最好是能夠快取起來,這樣就不必每次從磁盤上讀,這里我們采用了Nginx作為快取服務器,將圖片、css、js檔案都進行了快取,有效的減少了后端tomcat的訪問,另外,為了能加快網路傳輸速度,開啟 gzip壓縮也是必不可少的,但考慮到tomcat已經需要處理很多東西了,所以把這個壓縮的作業就交給前端的Nginx來完成,
除了文本可以用gzip壓縮,其實很多圖片也可以用影像處理工具預先進行壓縮,找到一個平衡點可以讓畫質損失很小而檔案可以減小很多,曾經我就見過一個圖片從300多kb壓縮到幾十kb,自己幾乎看不出來區別,
- 采用集群
單個服務器性能總是有限的,最好的辦法自然是實作橫向擴展,那么組建 tomcat集群是有效提升性能的手段,我們還是采用了Nginx來作為請求分流的服務器,后端多個tomcat共享session來協同作業,可以參考之前寫的《利用nginx+tomcat+memcached組建web服務器負載均衡》,
- 優化tomcat引數
這里以tomcat7的引數配置為例,需要修改conf/server.xml檔案,主要是優化連接配置,關閉客戶端dns查詢,
<Connector port="8080"
protocol="org.apache.coyote.httpl1.Http11NioProtocol"
connectionTimeout="20000"
redirectPort="8443n ma×Threads="500*
minSpareThreads="20" acceptCount="100"
disabLeUpLoadTimeoute"true"
enableLookups="false"
URIEncoding="UTF-8"/>
MySQL調優面試篇
1、為什么要分庫分表(設計高并發系統的時候,資料庫層面該如何設計)?
分表
比如你單表都幾千萬資料了,你確定你能扛住么?絕對不行,單表資料量太大,會極大影響你的 sql 執行的性能,到了后面你的 sql 可能就跑的很慢了,一般來說,就以我的經驗來看,單表到幾百萬的時候,性能就會相對差一些了,你就得分表了,
分表是啥意思?
就是把一個表的資料放到多個表中,然后查詢的時候你就查一個表,比如按照用戶 id 來分表,將一個用戶的資料就放在一個表中,然后操作的時候你對一個用戶就操作那個表就好了,這樣可以控制每個表的資料量在可控的范圍內,比如每個表就固定在 200 萬以內,
分庫
分庫是啥意思?
就是你一個庫一般我們經驗而言,最多支撐到并發 2000,一定要擴容了,而且一個健康的單庫并發值你最好保持在每秒 1000 左右,不要太大,那么你可以將一個庫的資料拆分到多個庫中,訪問的時候就訪問一個庫好了,
| # | 分庫分表前 | 分庫分表后 |
| 并發支撐情況 | MySQL 單機部署,扛不住高并發 | MySQL從單機到多機,能承受的并發增加了多倍 |
| 磁盤使用情況 | MySQL 單機磁盤容量幾乎撐滿 | 拆分為多個庫,資料庫服務器磁盤使用率大大降低 |
| SQL 執行性能 | 單表資料量太大,SQL 越跑越慢 | 單表資料量減少,SQL 執行效率明顯提升 |
這就是所謂的分庫分表,
2、用過哪些分庫分表中間件?不同的分庫分表中間件都有什么優點和缺點?
Sharding-jdbc
當當開源的,屬于 client 層(專案直接依賴jar)方案,目前已經更名為 ShardingSphere(后文所提到的 Sharding-jdbc,等同于 ShardingSphere),確實之前用的還比較多一些,因為 SQL 語法支持也比較多,沒有太多限制,支持分庫分表、讀寫分離、分布式 id 生成、柔性事務(最大努力送達型事務、TCC 事務),而且確實之前使用的公司會比較多一些(這個在官網有登記使用的公司,可以看到從 2017 年一直到現在,是有不少公司在用的),目前社區也還一直在開發和維護,還算是比較活躍,個人認為算是一個現在也可以選擇的方案,
Mycat
屬于 proxy 層(類似于中間件)方案,支持的功能非常完善,而且目前應該是非常火的而且不斷流行的資料庫中間件,社區很活躍,也有一些公司開始在用了,但是確實相比于 Sharding jdbc 來說,年輕一些,經歷的錘煉少一些,
總結
Sharding-jdbc 這種 client 層方案的優點在于不用部署,運維成本低,不需要代理層的二次轉發請求,性能很高,
缺點但是如果遇到升級啥的需要各個系統都重新升級版本再發布,各個系統都需要耦合 Sharding-jdbc 的依賴;
Mycat 這種 proxy 層方案的缺點在于需要部署,自己運維一套中間件,運維成本高,但是好處在于對于各個專案是透明的,如果遇到升級之類的都是自己中間件那里搞就行了,
通常來說,這兩個方案其實都可以選用,但是我個人建議中小型公司選用 Sharding-jdbc,client 層方案輕便,而且維護成本低,不需要額外增派人手,而且中小型公司系統復雜度會低一些,專案也沒那么多;但是中大型公司最好還是選用 Mycat 這類 proxy 層方案,因為可能大公司系統和專案非常多,團隊很大,人員充足,那么最好是專門弄個人來研究和維護 Mycat,然后大量專案直接透明使用即可
3、你們具體是如何對資料庫如何進行垂直拆分或水平拆分的?
水平拆分的意思,就是把一個表的資料給弄到多個庫的多個表里去,但是每個庫的表結構都一樣,只不過每個庫表放的資料是不同的,所有庫表的資料加起來就是全部資料,水平拆分的意義,就是將資料均勻放更多的庫里,然后用多個庫來扛更高的并發,還有就是用多個庫的存盤容量來進行擴容,

垂直拆分的意思,就是把一個有很多欄位的表給拆分成多個表,或者是多個庫上去,每個庫表的結構都不一樣,每個庫表都包含部分欄位,一般來說,會將較少的訪問頻率很高的欄位放到一個表里去,然后將較多的訪問頻率很低的欄位放到另外一個表里去,因為資料庫是有快取的,你訪問頻率高的行欄位越少,就可以在快取里快取更多的行,性能就越好,這個一般在表層面做的較多一些,

好了,無論分庫還是分表,上面說的那些資料庫中間件都是可以支持的,就是基本上那些中間件可以做到你分庫分表之后,中間件可以根據你指定的某個欄位值,比如說 userid,自動路由到對應的庫上去,然后再自動路由到對應的表里去,
4、你的專案里該如何分庫分表?
一般來說,垂直拆分,你可以在表層面來做,對一些欄位特別多的表做一下拆分;水平拆分,你可以說是并發承載不了,或者是資料量太大,容量承載不了,你給拆了,按什么欄位來拆,你自己想好;分表,你考慮一下,你如果哪怕是拆到每個庫里去,并發和容量都 ok 了,但是每個庫的表還是太大了,那么你就分表,將這個表分開,保證每個表的資料量并不是很大,
分庫分表的方式(水平拆分)
一種是按照 range 來分,就是每個庫一段連續的資料,這個一般是按比如時間范圍來的,但是這種一般較少用,因為很容易產生熱點問題,大量的流量都打在最新的資料上了,
或者是按照某個欄位 hash 一下均勻分散,這個較為常用,
range 來分,好處在于說,擴容的時候很簡單,因為你只要預備好,給每個月都準備一個庫就可以了,到了一個新的月份的時候,自然而然,就會寫新的庫了;缺點,但是大部分的請求,都是訪問最新的資料,實際生產用 range,要看場景,
hash 分發,好處在于說,可以平均分配每個庫的資料量和請求壓力;壞處在于說擴容起來比較麻煩,會有一個資料遷移的程序,之前的資料需要重新計算 hash 值重新分配到不同的庫或表,
5、現在有一個未分庫分表的系統,未來要分庫分表,如何設計才可以讓系統從未分庫分表動態切換到分庫分表上?
停機遷移方案:
1. 網站或者app 掛個公告,0-6點進行運維,無法訪問,
2. 接著到0點停機,系統停掉,沒有有流量寫入了,此時老的單庫單表資料庫靜止了,然后通過寫好一個導數的一次性工具系統,然后直接啟動連接到新的分庫分表上去,開多臺機器執行,
3. 修改服務的新的資料庫地址配置資訊,重新發布系統,然后驗證一下,就完事了,如果有問題,沒到3點重試,否則直接回滾到單表,第二天凌晨重試,
4. 600萬資料 可以通過 開3臺機器(每個機器每小時大概可以遷移180萬的資料量) 每臺機器多執行緒開20個執行緒跑,
不停機雙寫遷移方案
1、簡單來說,就是在線上系統里面,之前所有寫庫的地方,增刪改操作,除了對老庫增刪改,都加上對新庫的增刪改,這就是所謂的雙寫,同時寫倆庫,老庫和新庫,
2、然后系統部署之后,新庫資料差太遠,用之前說的導數工具,跑起來讀老庫資料寫新庫,寫的時候要根據 gmt_modified 這類欄位判斷這條資料最后修改的時間,除非是讀出來的資料在新庫里沒有,或者是比新庫的資料新才會寫,簡單來說,就是不允許用老資料覆寫新資料,
3、導完一輪之后,有可能資料還是存在不一致,那么就程式自動做一輪校驗,比對新老庫每個表的每條資料,接著如果有不一樣的,就針對那些不一樣的,從老庫讀資料再次寫,反復回圈,直到兩個庫每個表的資料都完全一致為止,
4、接著當資料完全一致了,就 ok 了,基于僅僅使用分庫分表的最新代碼,重新部署一次,不就僅僅基于分庫分表在操作了么,還沒有幾個小時的停機時間,很穩,所以現在基本玩兒資料遷移之類的,都是這么干的,
6、如何設計可以動態擴容縮容的分庫分表方案?
一開始上來就是 32 個庫,每個庫 32 個表,那么總共是 1024 張表,
我可以告訴各位,這個分法,
第一,基本上國內的互聯網肯定都是夠用了
第二,第二,無論是并發支撐還是資料量支撐都沒問題,
每個庫正常承載的寫入并發量是 1000,那么 32 個庫就可以承載 32 * 1000 = 32000 的寫并發,如果每個庫承載 1500 的寫并發,32 * 1500 = 48000 的寫并發,接近 5 萬每秒的寫入并發,前面再加一個MQ,削峰,每秒寫入 MQ 8 萬條資料,每秒消費 5 萬條資料,
有些除非是國內排名非常靠前的這些公司,他們的最核心的系統的資料庫,可能會出現幾百臺資料庫的這么一個規模,128 個庫,256 個庫,512 個庫,
1024 張表,假設每個表放 500 萬資料,在 MySQL 里可以放 50 億條資料,
每秒 5 萬的寫并發,總共 50 億條資料,對于國內大部分的互聯網公司來說,其實一般來說都夠了
剛開始的時候,這個庫可能就是邏輯庫,建在一個資料庫上的,就是一個 MySQL 服務器可能建了 n 個庫,比如 32 個庫,后面如果要拆分,就是不斷在庫和 MySQL 服務器之間做遷移就可以了,然后系統配合改一下配置即可,
比如說最多可以擴展到 32 個資料庫服務器,每個資料庫服務器是一個庫,如果還是不夠?最多可以擴展到 1024 個資料庫服務器,每個資料庫服務器上面一個庫一個表,因為最多是 1024 個表,
這么搞,是不用自己寫代碼做資料遷移的,都交給 DBA 來搞好了,但是 DBA 確實是需要做一些庫表遷移的作業,但是總比你自己寫代碼,然后抽資料導資料來的效率高得多吧,
哪怕是要減少庫的數量,也很簡單,其實說白了就是按倍數縮容就可以了,然后修改一下路由規則,
這里對步驟做一個總結:
1. 設定好幾臺資料庫服務器,每臺服務器上幾個庫,每個庫多少個表,推薦是 32 庫 * 32 表,對于大部分公司來說,可能幾年都夠了,
2. 路由的規則,orderId 模 32 = 庫,orderId / 32 模 32 = 表
3. 擴容的時候,申請增加更多的資料庫服務器,裝好 MySQL,呈倍數擴容,4 臺服務器,擴到 8 臺服務器,再到 16 臺服務器,
4. 由 DBA 負責將原先資料庫服務器的庫,遷移到新的資料庫服務器上去,庫遷移是有一些便捷的工具的,
5. 我們這邊就是修改一下配置,調整遷移的庫所在資料庫服務器的地址,
6. 重新發布系統,上線,原先的路由規則變都不用變,直接可以基于 n 倍的資料庫服務器的資源,繼續進行線上系統的提供服務,
7、分庫分表之后,id 主鍵如何處理?
資料庫自增id
l 通過單獨的獲取id服務器,獲取id
優點:可以確保每個id是唯一的,
缺點:高并發的話,就會有瓶頸
適合的場景:這種適合資料量大,但是并發量比較低的場景,
l 設定資料庫 sequence 或者表自增欄位步長
比如說,現在有 8 個服務節點,每個服務節點使用一個 sequence 功能來產生 ID,每個 sequence 的起始 ID 不同,并且依次遞增,步長都是 8,
適合的場景:可以防止產生的ID重復時,這種方案實作起來比較簡單,也能到達性能目標,但是服務節點固定,步長也固定,將來還要增加服務節點,就不好搞了,
UUID
好處就是本地生成,不要基于資料庫來了;不好之處就是,UUID 太長了、占用空間大,作為主鍵性能太差了;更重要的是,UUID 不具有有序性,會導致 B+ 樹索引在寫的時候有過多的隨機寫操作(連續的 ID 可以產生部分順序寫),還有,由于在寫的時候不能產生有順序的 append 操作,而需要進行 insert 操作,將會讀取整個 B+ 樹節點到記憶體,在插入這條記錄后會將整個節點寫回磁盤,這種操作在記錄占用空間比較大的情況下,性能下降明顯,
適合的場景:如果你是要隨機生成個什么檔案名、編號之類的,你可以用 UUID,但是作為主鍵是不能用 UUID 的,
獲取系統當前時間
這個就是獲取當前時間即可,但是問題是,并發很高的時候,比如一秒并發幾千,會有重復的情況,這個是肯定不合適的,基本就不用考慮了,
適合的場景:一般如果用這個方案,是將當前時間跟很多其他的業務欄位拼接起來,作為一個 id,如果業務上你覺得可以接受,那么也是可以的,你可以將別的業務欄位值跟當前時間拼接起來,組成一個全域唯一的編號,
snowflake 演算法
snowflake 演算法是 twitter 開源的分布式 id 生成演算法,采用 Scala語言實作,id 是 64 位 long 型的,
l 1 bit:不用,代表是正數
l 41 bits:代表的是時間戳,單位是毫秒,換算成年大概是69年
l 10 bits:記錄作業機器id,最多可以不是 2^10 機器 1024,其中 5 bits 代表機房id 32個機房,5 bits 代表機器id 32臺機器
l 12 bits:這里用來記錄同一個毫秒內產生的不同id,12 bits 可以代表的最大正整數是 2^12 - 1 = 4096 ,也就是說可以用這個 12 bits 代表的數字來區分同一個毫秒內的 4096 個不同的 id,
獲取id流程:
首先自己搞一個服務,然后對每個機房的每個機器都初始化這么一個id,剛開始這個機房的這個機器的序號就是 0,然后每次接收到一個請求,說這個機房的這個機器要生成一個 id,你就找到對應的 Worker 生成,
通過 synchronized保證執行緒安全 獲取 id ,41 bit是當前毫秒單位的一個時間戳,然后傳進來的 32 以內的機房 id 和 32 以內的機器 id,
1. 如果跟上次生成 id 的時間還在一個毫秒內,毫秒記憶體序列+1;毫秒內序列溢位,超過 4096 ,序列重置為 0,阻塞到下一個毫秒,重新獲取時間戳,
2. 如果發生了系統時間回呼,也就是當前時間戳大于上一次的時間戳,
l 方案一是發現時鐘回撥后,算出來回撥多少,保存為時間偏移量,然后后面每次獲取時間戳都加上偏移量,每回撥一次更新一次偏移量
l 方案二是,只在第一次生成id或啟動時獲取時間戳并保存下來,每生成一個id,就計下數,每個毫秒數能生成的id數是固定的,到生成滿了,再把時間戳加一,這樣就不依賴于系統時間了,每個毫秒數的使用率也最高
優點:
高性能高可用:不依賴資料庫,完全純記憶體
容量大:每秒中能生成數百萬的自增ID,
ID自增:存入資料庫中,索引效率高,
缺點:
嚴重依賴系統時間,系統時間被回呼,或者改變,可能會造成id沖突或者重復,
8、如何實作 MySQL 的讀寫分離?
就是基于主從復制架構,簡單來說,就是搞一個主庫,掛多個從庫,然后我們就單單只是寫主庫,然后主庫會自動把資料給同步到從庫上去,
9、MySQL 主從復制原理的是啥?
主要原理
主庫將變更寫入 binlog 日志,然后從庫連接到主庫之后,從庫有一個 IO 執行緒,將主庫的 binlog 日志拷貝到自己本地,寫入一個 relay 日志中,接著從庫中有一個 SQL 執行緒從 relay日志讀取 binlog,然后執行 binlog 日志中的內容,也就是在自己本地再次執行一遍 SQL,這樣就可以保證自己跟主庫的資料是一樣的,
延時問題
這里有過一個非常重要的一點,就是從庫同步主庫資料的程序是串行化的,也就是說主庫上并行的操作,在從庫上會串行執行,所以這就是一個非常重要的點了,由于從庫從主庫拷貝日志以及串行執行 SQL 的特點,在高并發場景下,從庫的資料一定會比主庫慢一些,是有延時的,
壓測過,大概 主庫并發量 1000/s 延時 幾毫秒,2000/s 延時 幾十毫秒,當達到4000 , 5000/s,主庫都快死了,此時從庫 延時會達到幾秒鐘,
大概在 mysql5.6版本之后, 從庫 IO執行緒,讀取主庫的binlog 日志的時候,也可以支持多執行緒讀取,
宕機問題,對應的 半同步復制 和 并行復制
場景:當主庫突然宕機,然后恰好資料還沒有同步到從庫,那么有些資料可能從庫上是還沒有的,有些資料可能就丟失了,
所以 MySQL 實際上在這一塊有兩個機制,一個是半同步復制,用來解決主庫資料丟失問題;一個是并行復制,用來解決主從同步延時問題,
半同步復制
也就是 semi-sync 復制,指的是主庫寫入 bingo 日志之后嗎,就會將強制此時立即將資料同步到從庫,從庫將日志寫入自己本地的 relay log 之后,接著會回傳一個 ack 主庫,主庫接收到至少一個從庫的 ack 之后才會認為操作完成了,
并行復制
指的是從庫開啟多個 SQL 執行緒,并行讀取 relay log 中不同 庫的日志, 然后并發重放不同庫的日志,這是庫級別的并行,
10、MySQL 主從同步延時問題(精華)
場景:先插入一條資料,再把它查出來,然后更新這個條資料,在生產環境達到 2000/s,這個時候,主從復制延遲大概是小幾十毫秒,那么一些資料,在高峰期時候沒有更新,因為延時,導致查詢從庫不能立刻查到,(查詢時候 沒有資料為null ,更新 通過id =null,導致更新失敗)
l 重寫代碼,寫代碼的時候,插入之后,直接更新,不要查詢再更新,
l 如果必須要查詢,插入之后,設定直連主庫查詢,再更新,(不推薦這種方法,這種讀寫分離的意義就喪失了)
l 分庫,將一個主庫分為多個主庫,每個主庫的寫并發就減少了幾倍,此時主從延時可以忽略不計,
l 打開 MySQL 支持的并發復制,多個庫并行復制,但是這種,在高并發,某個庫單庫并發達到 2000/s,并發復制還是沒有意義的,
11、事務特性
l 原子性
l 一致性
l 隔離性
l 持久性
12、事務的隔離級別
隔離性存在的問題
l 臟寫:兩個事務都更新一個資料,結果有個一人回滾了把另外一個人更新的資料也回滾沒了,
l 臟讀:一個事務讀到了另外一個事務沒有提交的時候修改資料,結果另外一個事務回滾了,下次讀就讀不到了,
l 不可重復讀:就是多次讀一條資料,別的事務老是修改資料值還提交了,多次讀到的值不同,
l 幻讀:就是范圍查詢,每次查到的資料不同,有時候別的事務插入了新的值,就會讀到更多的資料,
幾種隔離級別
l RU:可以讀到別人沒有提交的事務修改的資料,只能避免臟寫,
l RC:可以讀到人家提交的事務修改過的資料,避免的臟讀、臟寫,
l RR:不會讀到別的已經提交事務修改的資料,避免了臟讀,臟寫,不可重復讀,
l 串行:讓事務都串行執行,可以避免所有問題,
Mysql實作MVCC機制,是基于 undo log 多版本鏈條+ReadView機制來做的,默認RR隔離級別,就可以避免以上所有問題,
undo log
每次開啟事務,增刪改操作都會記錄一下當前事務的id,修改的值 同時會指向 上個事務的 undo log,
ReadView
m_ids:當前所有未提交的事務id,
min_trx_id:當前未提交的最小的事務id,
max_trx_id:當前未提交的下一次的事務id,
creator_id:當前事務的id,
Mysql 在 RC情況下,避免了臟讀,臟寫,但是沒有避免不可重復讀
在一個事務里,每次查詢都會重新開啟 readView,當readView 有其他事務提交之后,下次再開啟 readView,那么未提交的事務 ids 中就沒有剛剛提交的事務id,那么順著 undo log 版本鏈讀取的時候,會讀剛剛已經提交的事務,
RR情況,避免了 臟寫,臟讀,不可重復讀,幻讀,
在一個事務里,只會開啟一個 readView,每次查詢都會按照相同的 readView,這樣讀取 undo log 版本鏈得到的結果都一樣,
避免臟寫
通過鎖機制,來避免的臟寫,
當多個事務同時想對一條資料寫的時候,第一個事務會創建一個鎖,里面包含著自己的 事務id 和 等待狀態 fasle,然后把鎖跟這行事務關聯在一起,
另一個事務過來想的時候,發現這條記錄被加鎖,那么他就會排隊等待,同時他也生成一個鎖,里面 包含自己的 事務id 和 等待狀態為 true,
當第一個事務提交了,釋放鎖,他會喚醒排在他后面的鎖,改為fasle,那么后面的事務就獲取到鎖了,
13、MySQL存盤引擎
myisam
不支持事務,不支持外鍵約束,二級(復制)索引,索引檔案和資料檔案分開的,適用于那種少量的插入,大量的查詢的場景,
innodb
支持事務,走聚簇索引,支持高并發,高可用等成熟的資料庫架構,比如分庫分表,讀寫分離,主備架構,
14、聚簇索引和二級索引
l 聚簇索引:葉子節點存的是完整的資料,像innodb的主鍵索用的就是聚簇索引,
l 二級索引:葉子節點存的是索引,如果是myisam存盤引擎,他最終葉子節點存的還有地址值,他還要回表到一個hash表中找到具體的資料,innodb 他存的是索引資料和主鍵id,他要回表到主鍵索引,找到完整的資料,
15、MySQL索引結構
B- 樹
l 根節點至少包含2個孩子,
l 每個節點最多含有m個孩子(m>=2,m 代表幾階,也就是每個節點最多可以存盤幾個關鍵字)
l 除了根節點和葉節點外,其他節點至少有 ceil(m/2)個孩子,
l 所有的葉子結點都在同一層,
l 非葉子結點存盤資料(關鍵字)的個數=指向兒子的指標個數-1;
l 資料(關鍵字)集合分布在整棵樹中,任何一個資料(關鍵字)出現且只出現在一個節點中,(資料分散,對磁盤讀寫更加隨機,代價比較高)
l 搜索可能在非葉子節點結束,(搜索,不穩定)
B+樹
l 非葉子結點的子樹指標和節點存盤的索引個數相同,同時指標P[i]的區間在P[i] 到 P[i+1],前閉后開,(B樹是前開,后開,)
l 相比B - 樹, 所有葉子結點增加一個指標,指向下個一個葉子結點,(這樣有利于對資料庫的掃描,可以支持范圍搜索)
l 所有關鍵字都在葉子結點,(1. 穩定的查找效率,每次都是從根節點找到葉子結點 2. 這樣都同一在同一個板塊存資料,這樣磁盤讀寫代價更低,)
B+樹好處:
l 每次從根節點查找,查找效率穩定,
l 葉子結點順序相連,有利于資料庫的掃描,
l 資料都存盤在葉子結點,有利于磁盤的讀寫,
16、索引優化
排查慢sql
l 慢查詢的開啟并捕獲或開啟全域日志
l explain + 慢SQL分析
合理使用索引
l 主鍵自動建立唯一索引
l 頻繁作為查詢條件的欄位應該創建索引
l 頻繁更新的欄位不適合創建索引---因為每次更新不單單是更新了記錄還會更新索引
l 單鍵/組合索引的選擇問題,who? (在高并發下傾向創建組合索引)
l 組合索引,遵守最左匹配原則
l 查詢中排序的欄位,排序欄位若通過索引去訪問將大大提高排序速度
l 利用索引字串值的前綴(如果是字串,使用字串前綴)
l 盡量使用覆寫索引進行查詢,避免回表帶來的性能損耗,
l 聯合索引,like/范圍查/存在函式無效
17、索引優缺點
索引優勢
IO成本優勢
類似大學圖書館建書目索引,提高資料檢索的效率,降低資料庫的IO成本
CPU消耗低
通過索引列對資料進行排序,降低資料排序的成本,降低了CPU的消耗
索引缺點
空間上的代價
一個索引都為對應一棵B+樹,樹中每一個節點都是一個資料頁,一個頁默認會占用16KB的存盤空間,所以一個索引也是會占用磁盤空間的,
時間上的代價
索引是對資料的排序,那么當對表中的資料進行增、刪、改操作時,都需要去維護修改內容涉及到的B+樹索引,所以在進行增、刪、改操作時可能需要額外的時間進行一些記錄移動,頁面分裂、頁面回收等操作來維護好排序,
18、鎖機制
讀鎖與寫鎖
l 讀鎖:共享鎖、Shared Locks、S鎖,
l 寫鎖:排他鎖、Exclusive Locks、X鎖,
讀操作
select ...lock in share mode; (顯示鎖)
將查找到的資料加上一個S鎖,允許其他事務繼續獲取這些記錄的S鎖,不能獲取這些記錄的X鎖(會阻塞)
select ... for update; (顯示鎖)
將查找到的資料加上一個X鎖,不允許其他事務獲取這些記錄的S鎖和X鎖,
寫操作
l DELETE:洗掉一條資料時,先對記錄加X鎖,再執行洗掉操作,
l INSERT:插入一條記錄時,會先加隱式鎖來保護這條新插入的記錄在本事務提交前不被別的事務訪問到,
l UPDATE
1. 如果被更新的列,修改前后沒有導致存盤空間變化,那么會先給記錄加X鎖,再直接對記錄進行修改,
2. 如果被更新的列,修改前后導致存盤空間發生了變化,那么會先給記錄加X鎖,然后將記錄刪掉,再Insert一條新記錄,
隱式鎖:一個事務插入一條記錄后,還未提交,這條記錄會保存本次事務id,而其他事務如果想來讀取這個記錄會發現事務id不對應,所以相當于在插入一條記錄時,隱式的給這條記錄加了一把隱式鎖,
19、什么是悲觀鎖、樂觀鎖
l 悲觀鎖: 用的就是資料庫的行鎖,認為資料庫會發生并發沖突,直接上來就把資料鎖住,其他事務不能修改,直至提交了當前事務,
l 樂觀鎖: 其實是一種思想,認為不會鎖定的情況下去更新資料,如果發現不對勁,才不更新(回滾),在資料庫中往往添加一個version欄位來實作,
行鎖范圍分類
l LOCK_REC_NOT_GAP: 單個行記錄上的鎖,行鎖
l LOCK_GAP:間隙鎖,鎖定一個范圍,但不包括記錄本身,GAP鎖的目的,是為了防止同一事務的兩次當前讀 ,出現幻讀的情況,(間隙鎖會鎖從你查的id到最近小的id之間會鎖住)
l LOCK_ORDINARY:(Next_Key) 鎖定一個范圍,并且鎖定記錄本身,對于行的查詢,都是采用該方法,主要目的是解決幻讀的問題,
20、如何做到避免死鎖
l 盡量控制事務大小,減少鎖定資源量和時間長度
l 合理設計索引,盡量縮小鎖的范圍
l 在同一個事務中,盡可能做到一次鎖定所需要的所有資源,減少死鎖概率降
l 盡可能低級別事務隔離
性能優化面試合輯 word檔案下載地址:鏈接:https://pan.baidu.com/s/1E8Tl7Hm0-diPQvJchglciw
提取碼:1111爆肝一周,不眠不休!就為 點贊+好評+收藏 三連
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/281687.html
標籤:其他
上一篇:線性規劃對偶問題
