主頁 >  其他 > 【面經】Java崗位常見面試題

【面經】Java崗位常見面試題

2022-03-21 08:01:01 其他

面向物件

什么是面向物件?

對比面向程序,是兩種不同的處理問題的角度

面向程序更注重事情的每一個步驟及順序,面向物件更注重事情有哪些參與者(物件)、及各自需要做什么

比如:洗衣機洗衣服

面向程序會將任務拆解成一系列的步驟(函式),1、打開洗衣機----->2、放衣服----->3、放洗衣粉----->4、清洗 >5、烘干

面向物件會拆出人和洗衣機兩個物件: 人:打開洗衣機 放衣服 放洗衣粉 洗衣機:清洗 烘干

從以上例子能看出,面向程序比較直接高效,而面向物件更易于復用、擴展和維護

面向物件

封裝:封裝的意義,在于明確標識出允許外部使用的所有成員函式和資料項內部細節對外部呼叫透明,外部呼叫無需修改或者關心內部實作

1、javabean的屬性私有,提供getset對外訪問,因為屬性的賦值或者獲取邏輯只能由javabean本身決 定,而不能由外部胡亂修改

private String name;
public void setName(String name){ this.name = "tuling_"+name;
}

該name有自己的命名規則,明顯不能由外部直接賦值

2、orm框架

操作資料庫,我們不需要關心鏈接是如何建立的、sql是如何執行的,只需要引入mybatis,調方法即可

繼承:繼承基類的方法,并做出自己的改變和/或擴展

子類共性的方法或者屬性直接使用父類的,而不需要自己再定義,只需擴展自己個性化的

多型:基于物件所屬類的不同,外部對同一個方法的呼叫,實際執行的邏輯不同, 繼承,方法重寫,父類參考指向子類物件

父型別別 變數名 = new 子類物件 ;

變數名.方法名();

無法呼叫子類特有的功能

JDK JRE JVM

JDK:

Java Develpment Kit java 開發工具

JRE:

Java Runtime Environment java運行時環境JVM:

java Virtual Machine java 虛擬機

==和equals比較

==對比的是堆疊中的值,基本資料型別是變數值,參考型別是堆中記憶體物件的地址

equals:object中默認也是采用==比較,通常會重寫Object

public boolean equals(Object obj) { return (this == obj);
}

String

上述代碼可以看出,String類中被復寫的equals()方法其實是比較兩個字串的內容,

public boolean equals(Object anObject) { if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject; int n = value.length;
if (n == anotherString.value.length) { char v1[] = value;
char v2[] = anotherString.value; int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i]) return false;
i++;
}
return true;
}
}
return false;
}
public class StringDemo {
public static void main(String args[]) { String str1 = "Hello";
String str2 = new String("Hello"); String str3 = str2; // 參考傳遞
System.out.println(str1 == str2); // false System.out.println(str1 == str3); // false System.out.println(str2 == str3); // true System.out.println(str1.equals(str2)); // true System.out.println(str1.equals(str3)); // true System.out.println(str2.equals(str3)); // true
}
}

hashCode與equals

hashCode介紹:

hashCode() 的作用是獲取哈希碼,也稱為散列碼;它實際上是回傳一個int整數,這個哈希碼的作用是確定該物件在哈希表中的索引位置,hashCode() 定義在JDK的Object.java中,Java中的任何類都包含有hashCode() 函式,

散串列存盤的是鍵值對(key-value),它的特點是:能根據“鍵”快速的檢索出對應的“值”,這其中就利用到了散列碼!(可以快速找到所需要的物件)

為什么要有hashCode:

以“HashSet如何檢查重復”為例子來說明為什么要有hashCode:

物件加入HashSet時,HashSet會先計算物件的hashcode值來判斷物件加入的位置,看該位置是否有 值,如果沒有、HashSet會假設物件沒有重復出現,但是如果發現有值,這時會呼叫equals()方法來 檢查兩個物件是否真的相同,如果兩者相同,HashSet就不會讓其加入操作成功,如果不同的話,就會重新散列到其他位置,這樣就大大減少了equals的次數,相應就大大提高了執行速度,

如果兩個物件相等,則hashcode一定也是相同的

兩個物件相等,對兩個物件分別呼叫equals方法都回傳true 兩個物件有相同的hashcode值,它們也不一定是相等的

因此,equals方法被覆寫過,則hashCode方法也必須被覆寫

hashCode()的默認行為是對堆上的物件產生獨特值,如果沒有重寫hashCode(),則該class的兩個物件無論如何都不會相等(即使這兩個物件指向相同的資料)

final

最終的

修飾類:表示類不可被繼承

修飾方法:表示方法不可被子類覆寫,但是可以多載修飾變數:表示變數一旦被賦值就不可以更改它的值,

修飾成員變數

如果final修飾的是類變數,只能在靜態初始化塊中指定初始值或者宣告該類變數時指定初始值,如果final修飾的是成員變數,可以在非靜態初始化塊、宣告該變數或者構造器中執行初始值,

修飾區域變數

系統不會為區域變數進行初始化,區域變數必須由程式員顯示初始化,因此使用final修飾區域變數時, 即可以在定義時指定默認值(后面的代碼不能對變數再賦值),也可以不指定默認值,而在后面的代碼 中對final變數賦初值(僅一次)

public class FinalVar {
final static int a = 0;//再宣告的時候就需要賦值 或者靜態代碼塊賦值
/** static{
a = 0;
}
*/
final int b = 0;//再宣告的時候就需要賦值 或者代碼塊中賦值 或者構造器賦值
/*{
b = 0;
}*/
public static void main(String[] args) {
final int localA; //區域變數只宣告沒有初始化,不會報錯,與final無關,localA = 0;//在使用之前一定要賦值
//localA = 1; 但是不允許第二次賦值
}
}

修飾基本型別資料和參考型別資料

如果是基本資料型別的變數,則其數值一旦在初始化之后便不能更改;

如果是參考型別的變數,則在對其初始化之后便不能再讓其指向另一個物件,但是參考的值是可變的

public class FinalReferenceTest{ public static void main(){
final int[] iArr={1,2,3,4}; iArr[2]=-3;//合法
iArr=null;//非法,對iArr不能重新賦值
final Person p = new Person(25);
p.setAge(24);//合法
p=null;//非法
}
}

為什么區域內部類和匿名內部類只能訪問區域final變數?

編譯之后會生成兩個class檔案,Test.class Test1.class

public class Test {
public static void main(String[] args) {
}
//區域final變數a,b
public void test(final int b) {//jdk8在這里做了優化, 不用寫,語法糖,但實際上也是有的,也不能修改
final int a = 10;
//匿名內部類new Thread(){
public void run() { System.out.println(a); System.out.println(b);
};
}.start();
}
}
class OutClass {
private int age = 12;
public void outPrint(final int x) { class InClass {
public void InPrint() {
System.out.println(x); System.out.println(age);
}
}
new InClass().InPrint();
}
}

首先需要知道的一點是: 內部類和外部類是處于同一個級別的,內部類不會因為定義在方法中就會隨著方法的執行完畢就被銷毀,

這里就會產生問題:當外部類的方法結束時,區域變數就會被銷毀了,但是內部類物件可能還存在(只有沒有人再參考它時,才會死亡),

這里就出現了一個矛盾:內部類物件訪問了一個不存在的變數,

為了解決這個問題,就將區域變數復制了一份作為內部類的成員變數,這樣當區域變數死亡后,內部類仍可以訪問它,實際訪問的是區域變數的"copy",這樣就好像延長了區域變數的生命周期

將區域變數復制為內部類的成員變數時,必須保證這兩個變數是一樣的,也就是如果我們在內部類中修 改了成員變數,方法中的區域變數也得跟著改變,怎么解決問題呢?

就將區域變數設定為final,對它初始化后,我就不讓你再去修改這個變數,就保證了內部類的成員變數和方法的區域變數的一致性,這實際上也是一種妥協,使得區域變數與內部類內建立的拷貝保持一致,

String、StringBuffer、StringBuilder

String是final修飾的,不可變,每次操作都會產生新的String物件StringBuffer和StringBuilder都是在原物件上操作

StringBuffer是執行緒安全的,StringBuilder執行緒不安全的

StringBuffer方法都是synchronized修飾的性能:StringBuilder > StringBuffer > String

場景:經常需要改變字串內容時使用后面兩個

優先使用StringBuilder,多執行緒使用共享變數時使用StringBuffer

多載和重寫的區別

多載: 發生在同一個類中,方法名必須相同,引數型別不同、個數不同、順序不同,方法回傳值和訪問修飾符可以不同,發生在編譯時,

重寫: 發生在父子類中,方法名、引數串列必須相同,回傳值范圍小于等于父類,拋出的例外范圍小于等于父類,訪問修飾符范圍大于等于父類;如果父類方法訪問修飾符為private則子類就不能重寫該方 法,

public int add(int a,String b) public String add(int a,String b)
//編譯報錯

介面和抽象類的區別

抽象類可以存在普通成員函式,而介面中只能存在public abstract 方法,

抽象類中的成員變數可以是各種型別的,而介面中的成員變數只能是public static final型別的,抽象類只能繼承一個,介面可以實作多個,

介面的設計目的,是對類的行為進行約束(更準確的說是一種“有”約束,因為介面不能規定類不可以有 什么行為),也就是提供一種機制,可以強制要求不同的類具有相同的行為,它只約束了行為的有無, 但不對如何實作行為進行限制,

而抽象類的設計目的,是代碼復用,當不同的類具有某些相同的行為(記為行為集合A),且其中一部分行為的實作方式一致時(A的非真子集,記為B),可以讓這些類都派生于一個抽象類,在這個抽象類中實 現了B,避免讓所有的子類來實作B,這就達到了代碼復用的目的,而A減B的部分,留給各個子類自己 實作,正是因為A-B在這里沒有實作,所以抽象類不允許實體化出來(否則當呼叫到A-B時,無法執行),

抽象類是對類本質的抽象,表達的是 is a 的關系,比如: BMW is a Car ,抽象類包含并實作子類的通用特性,將子類存在差異化的特性進行抽象,交由子類去實作,

而介面是對行為的抽象,表達的是 like a 的關系,比如: Bird like a Aircraft (像飛行器一樣可以飛),但其本質上 is a Bird ,介面的核心是定義行為,即實作類可以做什么,至于實作類主體是誰、是如何實作的,介面并不關心,

使用場景:當你關注一個事物的本質的時候,用抽象類;當你關注一個操作的時候,用介面,

抽象類的功能要遠超過介面,但是,定義抽象類的代價高,因為高級語言來說(從實際設計上來說也 是)每個類只能繼承一個類,在這個類中,你必須繼承或撰寫出其所有子類的所有共性,雖然介面在功 能上會榷訓許多,但是它只是針對一個動作的描述,而且你可以在一個類中同時實作多個介面,在設計 階段會降低難度

List和Set的區別

List:有序,按物件進入的順序保存物件,可重復,允許多個Null元素物件,可以使用Iterator取出所有元素,在逐一遍歷,還可以使用get(int index)獲取指定下標的元素

Set:無序,不可重復,最多允許有一個Null元素物件,取元素時只能用Iterator介面取得所有元 素,在逐一遍歷各個元素

ArrayList和LinkedList區別

ArrayList:基于動態陣列,連續記憶體存盤,適合下標訪問(隨機訪問),擴容機制:因為陣列長度固定,超出長度存資料時需要新建陣列,然后將老陣列的資料拷貝到新陣列,如果不是尾部插入資料還會 涉及到元素的移動(往后復制一份,插入新元素),使用尾插法并指定初始容量可以極大提升性能、甚 至超過linkedList(需要創建大量的node物件)

LinkedList:基于鏈表,可以存盤在分散的記憶體中,適合做資料插入及洗掉操作,不適合查詢:需要逐一遍歷

遍歷LinkedList必須使用iterator不能使用for回圈,因為每次for回圈體內通過get(i)取得某一元素時都需 要對list重新進行遍歷,性能消耗極大,

另外不要試圖使用indexOf等回傳元素索引,并利用其進行遍歷,使用indexlOf對list進行了遍歷,當結 果為空時會遍歷整個串列,

HashMap和HashTable有什么區別?其底層實作是什么?

區別 :

  1. HashMap方法沒有synchronized修飾,執行緒非安全,HashTable執行緒安全;
  2. HashMap允許key和value為null,而HashTable不允許

底層實作:陣列+鏈表實作

jdk8開始鏈表高度到8、陣列長度超過64,鏈表轉變為紅黑樹,元素以內部類Node節點存在計算key的hash值,二次hash然后對陣列長度取模,對應到陣列下標,

如果沒有產生hash沖突(下標位置沒有元素),則直接創建Node存入陣列,

如果產生hash沖突,先進行equal比較,相同則取代該元素,不同,則判斷鏈表高度插入鏈表鏈表高度達到8,并且陣列長度到64則轉變為紅黑樹,長度低于6則將紅黑樹轉回鏈表

key為null,存在下標0的位置

陣列擴容

ConcurrentHashMap原理,jdk7和jdk8版本的區別

jdk7:

資料結構:ReentrantLock+Segment+HashEntry,一個Segment中包含一個HashEntry陣列,每個HashEntry又是一個鏈表結構

元素查詢:二次hash,第一次Hash定位到Segment,第二次Hash定位到元素所在的鏈表的頭部鎖:Segment分段鎖 Segment繼承了ReentrantLock,鎖定操作的Segment,其他的Segment不受影響,并發度為segment個數,可以通過建構式指定,陣列擴容不會影響其他的segment

get方法無需加鎖,volatile保證jdk8:

資料結構:synchronized+CAS+Node+紅黑樹,Node的val和next都用volatile修飾,保證可見性查找,替換,賦值操作都使用CAS鎖:鎖鏈表的head節點,不影響其他元素的讀寫,鎖粒度更細,效率更高,擴容時,阻塞所有的讀寫操作、并發擴容

讀操作無鎖:

Node的val和next使用volatile修飾,讀寫執行緒對該變數互相可見陣列用volatile修飾,保證擴容時被讀執行緒感知

什么是位元組碼?采用位元組碼的好處是什么?

java中的編譯器和解釋器:

Java中引入了虛擬機的概念,即在機器和編譯程式之間加入了一層抽象的虛擬的機器,這臺虛擬的機器在任何平臺上都提供給編譯程式一個的共同的介面,

編譯程式只需要面向虛擬機,生成虛擬機能夠理解的代碼,然后由解釋器來將虛擬機代碼轉換為特定系 統的機器碼執行,在Java中,這種供虛擬機理解的代碼叫做 位元組碼(即擴展名為 .class的檔案),它不面向任何特定的處理器,只面向虛擬機,

每一種平臺的解釋器是不同的,但是實作的虛擬機是相同的,Java源程式經過編譯器編譯后變成位元組 碼,位元組碼由虛擬機解釋執行,虛擬機將每一條要執行的位元組碼送給解釋器,解釋器將其翻譯成特定機 器上的機器碼,然后在特定的機器上運行,這也就是解釋了Java的編譯與解釋并存的特點,

Java源代碼---->編譯器---->jvm可執行的Java位元組碼(即虛擬指令)---->jvm---->jvm中解釋器 >機器可執行的二進制機器碼 >程式運行,

采用位元組碼的好處:

Java語言通過位元組碼的方式,在一定程度上解決了傳統解釋型語言執行效率低的問題,同時又保留了解釋型語言可移植的特點,所以Java程式運行時比較高效,而且,由于位元組碼并不專對一種特定的機器, 因此,Java程式無須重新編譯便可在多種不同的計算機上運行,

Java中的例外體系

Java中的所有例外都來自頂級父類Throwable,Throwable下有兩個子類Exception和Error,

Error是程式無法處理的錯誤,一旦出現這個錯誤,則程式將被迫停止運行,

Exception不會導致程式停止,又分為兩個部分RunTimeException運行時例外和CheckedException檢 查例外,

RunTimeException常常發生在程式運行程序中,會導致程式當前執行緒執行失敗,CheckedException常常發生在程式編譯程序中,會導致程式編譯不通過,

Java類加載器

JDK自帶有三個類加載器:bootstrap ClassLoader、ExtClassLoader、AppClassLoader,

BootStrapClassLoader是ExtClassLoader的父類加載器,默認負責加載%JAVA_HOME%lib下的jar包和class檔案,

ExtClassLoader是AppClassLoader的父類加載器,負責加載%JAVA_HOME%/lib/ext檔案夾下的jar包和class 類 , AppClassLoader是自定義類加載器的父類,負責加載classpath下的類檔案,系統類加載器,執行緒上下 文加載器

繼承ClassLoader實作自定義類加載器

雙親委托模型

雙親委派模型的好處:

主要是為了安全性,避免用戶自己撰寫的類動態替換 Java的一些核心類,比如 String,

同時也避免了類的重復加載,因為 JVM中區分不同類,不僅僅是根據類名,相同的 class檔案被不同的 ClassLoader加載就是不同的兩個類

GC如何判斷物件可以被回收

參考計數法:每個物件有一個參考計數屬性,新增一個參考時計數加1,參考釋放時計數減1,計 數為0時可以回收,

可達性分析法:從 GC Roots 開始向下搜索,搜索所走過的路徑稱為參考鏈,當一個物件到 GC

Roots 沒有任何參考鏈相連時,則證明此物件是不可用的,那么虛擬機就判斷是可回收物件,

參考計數法,可能會出現A 參考了 B,B 又參考了 A,這時候就算他們都不再使用了,但因為相互參考 計數器=1 永遠無法被回收,

GC Roots的物件有:

虛擬機堆疊(堆疊幀中的本地變數表)中參考的物件方法區中類靜態屬性參考的物件

方法區中常量參考的物件

本地方法堆疊中JNI(即一般說的Native方法)參考的物件

可達性演算法中的不可達物件并不是立即死亡的,物件擁有一次自我拯救的機會,物件被系統宣告死亡至 少要經歷兩次標記程序:第一次是經過可達性分析發現沒有與GC Roots相連接的參考鏈,第二次是在由虛擬機自動建立的Finalizer佇列中判斷是否需要執行finalize()方法,

當物件變成(GC Roots)不可達時,GC會判斷該物件是否覆寫了finalize方法,若未覆寫,則直接將其回收,否則,若物件未執行過finalize方法,將其放入F-Queue佇列,由一低優先級執行緒執行該佇列中物件的finalize方法,執行finalize方法完畢后,GC會再次判斷該物件是否可達,若不可達,則進行回收,否 則,物件“復活”

每個物件只能觸發一次finalize()方法

由于finalize()方法運行代價高昂,不確定性大,無法保證各個物件的呼叫順序,不推薦大家使用,建議遺忘它,

執行緒的生命周期?執行緒有幾種狀態

執行緒通常有五種狀態,創建,就緒,運行、阻塞和死亡狀態,

阻塞的情況又分為三種:

  1. 等待阻塞:運行的執行緒執行wait方法,該執行緒會釋放占用的所有資源,JVM會把該執行緒放入“等待池”中,進入這個狀態后,是不能自動喚醒的,必須依靠其他執行緒呼叫notify或notifyAll方法才能被喚醒,wait是object類的方法
  2. 同步阻塞:運行的執行緒在獲取物件的同步鎖時,若該同步鎖被別的執行緒占用,則JVM會把該執行緒放入“鎖池”中,
  3. 其他阻塞:運行的執行緒執行sleep或join方法,或者發出了I/O請求時,JVM會把該執行緒置為阻塞狀 態,當sleep狀態超時、join等待執行緒終止或者超時、或者I/O處理完畢時,執行緒重新轉入就緒狀態,

sleep是Thread類的方法

  1. 新建狀態(New):新創建了一個執行緒物件,
  2. 就緒狀態(Runnable):執行緒物件創建后,其他執行緒呼叫了該物件的start方法,該狀態的執行緒位于可運行執行緒池中,變得可運行,等待獲取CPU的使用權,
  3. 運行狀態(Running):就緒狀態的執行緒獲取了CPU,執行程式代碼,
  4. 阻塞狀態(Blocked):阻塞狀態是執行緒因為某種原因放棄CPU使用權,暫時停止運行,直到執行緒進入就緒狀態,才有機會轉到運行狀態,
  5. 死亡狀態(Dead):執行緒執行完了或者因例外退出了run方法,該執行緒結束生命周期,

sleep()、wait()、join()、yield()的區別

鎖池

所有需要競爭同步鎖的執行緒都會放在鎖池當中,比如當前物件的鎖已經被其中一個執行緒得到,則其他線 程需要在這個鎖池進行等待,當前面的執行緒釋放同步鎖后鎖池中的執行緒去競爭同步鎖,當某個執行緒得到 后會進入就緒佇列進行等待cpu資源分配,

等待池

當我們呼叫wait()方法后,執行緒會放到等待池當中,等待池的執行緒是不會去競爭同步鎖,只有呼叫了notify()或notifyAll()后等待池的執行緒才會開始去競爭鎖,notify()是隨機從等待池選出一個執行緒放 到鎖池,而notifyAll()是將等待池的所有執行緒放到鎖池當中

1、sleep 是 Thread 類的靜態本地方法,wait 則是 Object 類的本地方法,

2、sleep方法不會釋放lock,但是wait會釋放,而且會加入到等待佇列中,

sleep就是把cpu的執行資格和執行權釋放出去,不再運行此執行緒,當定時時間結束再取回cpu資源,參與cpu 的調度,獲取到cpu資源后就可以繼續運行了,而如果sleep時該執行緒有鎖,那么sleep不會釋放這個鎖,而 是把鎖帶著進入了凍結狀態,也就是說其他需要這個鎖的執行緒根本不可能獲取到這個鎖,也就是說無法執行程 序,如果在睡眠期間其他執行緒呼叫了這個執行緒的interrupt方法,那么這個執行緒也會拋出interruptexception例外回傳,這點和wait是一樣的,

3、sleep方法不依賴于同步器synchronized,但是wait需要依賴synchronized關鍵字,

4、sleep不需要被喚醒(休眠之后推出阻塞),但是wait需要(不指定時間需要被別人中斷),

5、sleep 一般用于當前執行緒休眠,或者輪循暫停操作,wait 則多用于多執行緒之間的通信,

6、sleep 會讓出 CPU 執行時間且強制背景關系切換,而 wait 則不一定,wait 后可能還是有機會重新競爭到鎖繼續執行的,

yield()執行后執行緒直接進入就緒狀態,馬上釋放了cpu的執行權,但是依然保留了cpu的執行資格, 所以有可能cpu下次進行執行緒調度還會讓這個執行緒獲取到執行權繼續執行

join()執行后執行緒進入阻塞狀態,例如在執行緒B中呼叫執行緒A的join(),那執行緒B會進入到阻塞佇列,直到執行緒A結束或中斷執行緒

public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(new Runnable() {
@Override
public void run() { try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("22222222");
}
});
t1.start();
t1.join();
// 這行代碼必須要等t1全部執行完畢,才會執行
System.out.println("1111");
}
22222222
1111

對執行緒安全的理解

不是執行緒安全、應該是記憶體安全,堆是共享記憶體,可以被所有執行緒訪問

當多個執行緒訪問一個物件時,如果不用進行額外的同步控制或其他的協調操作,呼叫這個物件的行為都可以獲 得正確的結果,我們就說這個物件是執行緒安全的

是行程和執行緒共有的空間,分全域堆和區域堆,全域堆就是所有沒有分配的空間,區域堆就是用戶分 配的空間,堆在作業系統對行程初始化的時候分配,運行程序中也可以向系統要額外的堆,但是用完了 要還給作業系統,要不然就是記憶體泄漏,

在Java中,堆是Java虛擬機所管理的記憶體中最大的一塊,是所有執行緒共享的一塊記憶體區域,在虛 擬機啟動時創建,堆所存在的記憶體區域的唯一目的就是存放物件實體,幾乎所有的物件實體以及 陣列都在這里分配記憶體,

堆疊是每個執行緒獨有的,保存其運行狀態和區域自動變數的,堆疊在執行緒開始的時候初始化,每個執行緒的堆疊 互相獨立,因此,堆疊是執行緒安全的,作業系統在切換執行緒的時候會自動切換堆疊,堆疊空間不需要在高級語 言里面顯式的分配和釋放,

目前主流作業系統都是多任務的,即多個行程同時運行,為了保證安全,每個行程只能訪問分配給自己 的記憶體空間,而不能訪問別的行程的,這是由作業系統保障的,

在每個行程的記憶體空間中都會有一塊特殊的公共區域,通常稱為堆(記憶體),行程內的所有執行緒都可以 訪問到該區域,這就是造成問題的潛在原因,

Thread、Runable的區別

Thread和Runnable的實質是繼承關系,沒有可比性,無論使用Runnable還是Thread,都會new

Thread,然后執行run方法,用法上,如果有復雜的執行緒操作需求,那就選擇繼承Thread,如果只是簡單的執行一個任務,那就實作runnable,

//會賣出多一倍的票public class Test {
public static void main(String[] args) {
// TODO Auto-generated method stub
new MyThread().start(); new MyThread().start();
}
static class MyThread extends Thread{ private int ticket = 5;
public void run(){ while(true){
System.out.println("Thread ticket = " + ticket--); if(ticket < 0){
break;
}
}
}
}
}
//正常賣出
public class Test2 {
public static void main(String[] args) {
// TODO Auto-generated method stub MyThread2 mt=new MyThread2();
new Thread(mt).start(); new Thread(mt).start();
}
static class MyThread2 implements Runnable{ private int ticket = 5;
public void run(){ while(true){
System.out.println("Runnable ticket = " + ticket--); if(ticket < 0){
break;
}
}
}
}
}

原因是:MyThread創建了兩個實體,自然會賣出兩倍,屬于用法錯誤

對守護執行緒的理解

守護執行緒:為所有非守護執行緒提供服務的執行緒;任何一個守護執行緒都是整個JVM中所有非守護執行緒的保姆;

守護執行緒類似于整個行程的一個默默無聞的小嘍嘍;它的生死無關重要,它卻依賴整個行程而運行;哪 天其他執行緒結束了,沒有要執行的了,程式就結束了,理都沒理守護執行緒,就把它中斷了;

注意: 由于守護執行緒的終止是自身無法控制的,因此千萬不要把IO、File等重要操作邏輯分配給它;因為它不靠譜;

守護執行緒的作用是什么?

舉例, GC垃圾回收執行緒:就是一個經典的守護執行緒,當我們的程式中不再有任何運行的Thread,程式就不會再產生垃圾,垃圾回收器也就無事可做,所以當垃圾回收執行緒是JVM上僅剩的執行緒時,垃圾回收線 程會自動離開,它始終在低級別的狀態中運行,用于實時監控和管理系統中的可回收資源,

應用場景:

(1)來為其它執行緒提供服務支持的情況;

(2) 或者在任何情況下,程式結束時,這個執行緒必須正常且立刻關閉,就可以作為守護執行緒來使用;反之,如果一個正在執行某個操作的執行緒必須要 正確地關閉掉否則就會出現不好的后果的話,那么這個執行緒就不能是守護執行緒,而是用戶執行緒,通常都 是些關鍵的事務,比方說,資料庫錄入或者更新,這些操作都是不能中斷的,

thread.setDaemon(true)必須在thread.start(之前設定,否則會跑出一個IllegalThreadStateException例外,你不能把正在運行的常規執行緒設定為守護執行緒,

在Daemon執行緒中產生的新執行緒也是Daemon的,

守護執行緒不能用于去訪問固有資源,比如讀寫操作或者計算邏輯,因為它會在任何時候甚至在一個操作 的中間發生中斷,

Java自帶的多執行緒框架,比如ExecutorService,會將守護執行緒轉換為用戶執行緒,所以如果要使用后臺線 程就不能用Java的執行緒池,

ThreadLocal的原理和使用場景

每一個Thread 物件均含有一個ThreadLocalMap 型別的成員變數threadLocals ,它存盤本執行緒中所有ThreadLocal物件及其對應的值

ThreadLocalMap 由一個個Entry 物件構成

Entry 繼承自WeakReference<ThreadLocal<?>> ,一個Entry 由ThreadLocal 物件和Object 構成,由此可見, Entry 的key是ThreadLocal物件,并且是一個弱參考,當沒指向key的強參考后,該key就會被垃圾收集器回收

當執行set方法時,ThreadLocal首先會獲取當前執行緒物件,然后獲取當前執行緒的ThreadLocalMap物件,再以當前ThreadLocal物件為key,將值存盤進ThreadLocalMap物件中,

get方法執行程序類似,ThreadLocal首先會獲取當前執行緒物件,然后獲取當前執行緒的ThreadLocalMap物件,再以當前ThreadLocal物件為key,獲取對應的value,

 

由于每一條執行緒均含有各自私有的ThreadLocalMap容器,這些容器相互獨立互不影響,因此不會存在 執行緒安全性問題,從而也無需使用同步機制來保證多條執行緒訪問容器的互斥性,

使用場景:

1、在進行物件跨層傳遞的時候,使用ThreadLocal可以避免多次傳遞,打破層次間的約束,

2、執行緒間資料隔離

3、進行事務操作,用于存盤執行緒事務資訊,

4、資料庫連接,Session會話管理,

Spring框架在事務開始時會給當前執行緒系結一個Jdbc Connection,在整個事務程序都是使用該執行緒系結的

connection來執行資料庫操作,實作了事務的隔離性,Spring框架里面就是用的ThreadLocal來實作這種隔離

ThreadLocal記憶體泄露原因,如何避免

記憶體泄露為程式在申請記憶體后,無法釋放已申請的記憶體空間,一次記憶體泄露危害可以忽略,但記憶體泄露 堆積后果很嚴重,無論多少記憶體,遲早會被占光,不再會被使用的物件或者變數占用的記憶體不能被回收,就是記憶體泄露,

強參考:使用最普遍的參考(new),一個物件具有強參考,不會被垃圾回收器回收,當記憶體空間不足,

Java虛擬機寧愿拋出OutOfMemoryError錯誤,使程式例外終止,也不回收這種物件,

如果想取消強參考和某個物件之間的關聯,可以顯式地將參考賦值為null,這樣可以使JVM在合適的時間就會回收該物件,

弱參考:JVM進行垃圾回收時,無論記憶體是否充足,都會回收被弱參考關聯的物件,在java中,用


java.lang.ref.WeakReference類來表示,可以在快取中使用弱參考,

ThreadLocal的實作原理,每一個Thread維護一個ThreadLocalMap,key為使用弱參考的ThreadLocal實體,value為執行緒變數的副本

hreadLocalMap使用ThreadLocal的弱參考作為key,如果一個ThreadLocal不存在外部強參考時, Key(ThreadLocal)勢必會被GC回收,這樣就會導致ThreadLocalMap中key為null, 而value還存在著強參考,只有thead執行緒退出以后,value的強參考鏈條才會斷掉,但如果當前執行緒再遲遲不結束的話,這 些key為null的Entry的value就會一直存在一條強參考鏈(紅色鏈條)

key 使用強參考

當hreadLocalMap的key為強參考回收ThreadLocal時,因為ThreadLocalMap還持有ThreadLocal的強 參考,如果沒有手動洗掉,ThreadLocal不會被回收,導致Entry記憶體泄漏,

key 使用弱參考

當ThreadLocalMap的key為弱參考回收ThreadLocal時,由于ThreadLocalMap持有ThreadLocal的弱 參考,即使沒有手動洗掉,ThreadLocal也會被回收,當key為null,在下一次ThreadLocalMap呼叫set(),get(),remove()方法的時候會被清除value值,

因此,ThreadLocal記憶體泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一樣長,如果沒有手動洗掉對應key就會導致記憶體泄漏,而不是因為弱參考,

ThreadLocal正確的使用方法

每次使用完ThreadLocal都呼叫它的remove()方法清除資料

將ThreadLocal變數定義成private static,這樣就一直存在ThreadLocal的強參考,也就能保證任何時候都能通過ThreadLocal的弱參考訪問到Entry的value值,進而清除掉 ,

并發、并行、串行的區別

串行在時間上不可能發生重疊,前一個任務沒搞定,下一個任務就只能等著并行在時間上是重疊的,兩個任務在同一時刻互不干擾的同時執行,

并發允許兩個任務彼此干擾,統一時間點、只有一個任務運行,交替執行

并發的三大特性

原子性

原子性是指在一個操作中cpu不可以在中途暫停然后再調度,即不被中斷操作,要不全部執行完成,要 不都不執行,就好比轉賬,從賬戶A向賬戶B轉1000元,那么必然包括2個操作:從賬戶A減去1000元, 往賬戶B加上1000元,2個操作必須全部完成,

private long count = 0;
public void calc() { count++;
}

1:將 count 從主存讀到作業記憶體中的副本中

2:+1的運算

3:將結果寫入作業記憶體

4:將作業記憶體的值刷回主存(什么時候刷入由作業系統決定,不確定的)

那程式中原子性指的是最小的操作單元,比如自增操作,它本身其實并不是原子性操作,分了3步的, 包括讀取變數的原始值、進行加1操作、寫入作業記憶體,所以在多執行緒中,有可能一個執行緒還沒自增完,可能才執行到第二部,另一個執行緒就已經讀取了值,導致結果錯誤,那如果我們能保證自增操作是 一個原子性的操作,那么就能保證其他執行緒讀取到的一定是自增后的資料,

關鍵字:synchronized

可見性

當多個執行緒訪問同一個變數時,一個執行緒修改了這個變數的值,其他執行緒能夠立即看得到修改的值,

若兩個執行緒在不同的cpu,那么執行緒1改變了i的值還沒重繪到主存,執行緒2又使用了i,那么這個i值肯定還是之前的,執行緒1對變數的修改執行緒沒看到這就是可見性問題,

//執行緒1

boolean stop = false; while(!stop){

doSomething();

}

//執行緒2

stop = true;

如果執行緒2改變了stop的值,執行緒1一定會停止嗎?不一定,當執行緒2更改了stop變數的值之后,但是還沒來得及寫入主存當中,執行緒2轉去做其他事情了,那么執行緒1由于不知道執行緒2對stop變數的更改,因此還會一直回圈下去,

關鍵字:volatile、synchronized、final

有序性

虛擬機在進行代碼編譯時,對于那些改變順序之后不會對最終結果造成影響的代碼,虛擬機不一定會按 照我們寫的代碼的順序來執行,有可能將他們重排序,實際上,對于有些代碼進行重排序之后,雖然對 變數的值沒有造成影響,但有可能會出現執行緒安全問題,

int a = 0;
bool flag = false;
public void write() {
a = 2; //1
flag = true; //2
}
public void multiply() { if (flag) { //3
int ret = a * a;//4
}
}

write方法里的1和2做了重排序,執行緒1先對flag賦值為true,隨后執行到執行緒2,ret直接計算出結果, 再到執行緒1,這時候a才賦值為2,很明顯遲了一步

關鍵字:volatile、synchronized

volatile本身就包含了禁止指令重排序的語意,而synchronized關鍵字是由“一個變數在同一時刻只允許 一條執行緒對其進行lock操作”這條規則明確的,

synchronized關鍵字同時滿足以上三種特性,但是volatile關鍵字不滿足原子性,

在某些情況下,volatile的同步機制的性能確實要優于鎖(使用synchronized關鍵字或java.util.concurrent包里面的鎖),因為volatile的總開銷要比鎖低,

我們判斷使用volatile還是加鎖的唯一依據就是volatile的語意能否滿足使用的場景(原子性)

volatile

  • 保證被volatile修飾的共享變數對所有執行緒總是可見的,也就是當一個執行緒修改了一個被volatile修 飾共享變數的值,新值總是可以被其他執行緒立即得知,
//執行緒1
boolean stop = false; while(!stop){
doSomething();
}
//執行緒2
stop = true;
如果執行緒2改變了stop的值,執行緒1一定會停止嗎?不一定,當執行緒2更改了stop變數的值之后,但是還沒來得及寫入主存當中,執行緒2轉去做其他事情了,那么執行緒1由于不知道執行緒2對stop變數的更改,因此還會一直回圈下去,
● 禁止指令重排序優化,
int a = 0;
bool flag = false;
public void write() {
a = 2; //1
flag = true; //2
}
public void multiply() { if (flag) { //3
int ret = a * a;//4
}
}

write方法里的1和2做了重排序,執行緒1先對flag賦值為true,隨后執行到執行緒2,ret直接計算出結果, 再到執行緒1,這時候a才賦值為2,很明顯遲了一步,

但是用volatile修飾之后就變得不一樣了

第一:使用volatile關鍵字會強制將修改的值立即寫入主存;

第二:使用volatile關鍵字的話,當執行緒2進行修改時,會導致執行緒1的作業記憶體中快取變數stop的快取行無效(反映到硬體層的話,就是CPU的L1或者L2快取中對應的快取行無效);

第三:由于執行緒1的作業記憶體中快取變數stop的快取行無效,所以執行緒1再次讀取變數stop的值時會去主存讀取,

其實是兩個步驟,先加加,然后再賦值,不是原子性操作,所以volatile不能保證執行緒安全,

inc++;

為什么用執行緒池?解釋下執行緒池引數?

1、降低資源消耗;提高執行緒利用率,降低創建和銷毀執行緒的消耗,

2、提高回應速度;任務來了,直接有執行緒可用可執行,而不是先創建執行緒,再執行,

3、提高執行緒的可管理性;執行緒是稀缺資源,使用執行緒池可以統一分配調優監控,

代表核心執行緒數,也就是正常情況下創建作業的執行緒數,這些執行緒創建后并不會 消除,而是一種常駐執行緒corePoolSize代表的是最大執行緒數,它與核心執行緒數相對應,表示最大允許被創建的執行緒maxinumPoolSize數,比如當前任務較多,將核心執行緒數都用完了,還無法滿足需求時,此時就會創建新的執行緒,但 是執行緒池內執行緒總數不會超過最大執行緒數keepAliveTime 、 unit 表示超出核心執行緒數之外的執行緒的空閑存活時間,也就是核心執行緒不會消除,但是超出核心執行緒數的部分執行緒如果空閑一定的時間則會被消除,我們可以通過來設定空閑時間

setKeepAliveTime

workQueue 用來存放待執行的任務,假設我們現在核心執行緒都已被使用,還有任務進來則全部放入佇列,直到整個佇列被放滿但任務還再持續進入則會開始創建新的執行緒

ThreadFactory 實際上是一個執行緒工廠,用來生產執行緒執行任務,我們可以選擇使用默認的創建工廠,產生的執行緒都在同一個組內,擁有相同的優先級,且都不是守護執行緒,當然我們也可以選擇 自定義執行緒工廠,一般我們會根據業務來制定不同的執行緒工廠

Handler 任務拒絕策略,有兩種情況,第一種是當我們呼叫shutdown 等方法關閉執行緒池后,這時候即使執行緒池內部還有沒執行完的任務正在執行,但是由于執行緒池已經關閉,我們再繼續想執行緒 池提交任務就會遭到拒絕,另一種情況就是當達到最大執行緒數,執行緒池已經沒有能力繼續處理新提 交的任務時,這是也就拒絕

簡述執行緒池處理流程

執行緒池中阻塞佇列的作用?為什么是先添加列隊而不是先創建最大執行緒?

1、一般的佇列只能保證作為一個有限長度的緩沖區,如果超出了緩沖長度,就無法保留當前的任務 了,阻塞佇列通過阻塞可以保留住當前想要繼續入隊的任務,

阻塞佇列可以保證任務佇列中沒有任務時阻塞獲取任務的執行緒,使得執行緒進入wait狀態,釋放cpu資源,

阻塞佇列自帶阻塞和喚醒的功能,不需要額外處理,無任務執行時,執行緒池利用阻塞佇列的take方法掛起,從而維持核心執行緒的存活、不至于一直占用cpu資源

2、在創建新執行緒的時候,是要獲取全域鎖的,這個時候其它的就得阻塞,影響了整體效率,

就好比一個企業里面有10個(core)正式工的名額,最多招10個正式工,要是任務超過正式工人數

(task > core)的情況下,工廠領導(執行緒池)不是首先擴招工人,還是這10人,但是任務可以稍微積壓一下,即先放到佇列去(代價低),10個正式工慢慢干,遲早會干完的,要是任務還在繼續增加,超 過正式工的加班忍耐極限了(佇列滿了),就的招外包幫忙了(注意是臨時工)要是正式工加上外包還 是不能完成任務,那新來的任務就會被領導拒絕了(執行緒池的拒絕策略),

執行緒池中執行緒復用原理

執行緒池將執行緒和任務進行解耦,執行緒是執行緒,任務是任務,擺脫了之前通過 Thread 創建執行緒時的一個執行緒必須對應一個任務的限制,

在執行緒池中,同一個執行緒可以從阻塞佇列中不斷獲取新任務來執行,其核心原理在于執行緒池對

Thread 進行了封裝,并不是每次執行任務都會呼叫 Thread.start() 來創建新執行緒,而是讓每個執行緒去執行一個“回圈任務”,在這個“回圈任務”中不停檢查是否有任務需要被執行,如果有則直接執行,也就是呼叫任務中的 run 方法,將 run 方法當成一個普通的方法執行,通過這種方式只使用固定的執行緒就將所有任務的 run 方法串聯起來,

如何實作一個IOC容器

1、組態檔配置包掃描路徑

2、遞回包掃描獲取.class檔案

3、反射、確定需要交給IOC管理的類

4、對需要注入的類進行依賴注入

組態檔中指定需要掃描的包路徑

定義一些注解,分別表示訪問控制層、業務服務層、資料持久層、依賴注入注解、獲取組態檔注 解

從組態檔中獲取需要掃描的包路徑,獲取到當前路徑下的檔案資訊及檔案夾資訊,我們將當前路 徑下所有以.class結尾的檔案添加到一個Set集合中進行存盤

遍歷這個set集合,獲取在類上有指定注解的類,并將其交給IOC容器,定義一個安全的Map用來存盤這些物件

遍歷這個IOC容器,獲取到每一個類的實體,判斷里面是有有依賴其他的類的實體,然后進行遞回注入

spring是什么?

輕量級的開源的J2EE框架,它是一個容器框架,用來裝javabean(java物件),中間層框架(萬能膠) 可以起一個連接作用,比如說把Struts和hibernate粘合在一起運用,可以讓我們的企業開發更快、更簡潔

Spring是一個輕量級的控制反轉(IoC)和面向切面(AOP)的容器框架

--從大小與開銷兩方面而言Spring都是輕量級的,

--通過控制反轉(IoC)的技術達到松耦合的目的

--提供了面向切面編程的豐富支持,允許通過分離應用的業務邏輯與系統級服務進行內聚性的開發

--包含并管理應用物件(Bean)的配置和生命周期,這個意義上是一個容器,

--將簡單的組件配置、組合成為復雜的應用,這個意義上是一個框架,

談談你對AOP的理解

系統是由許多不同的組件所組成的,每一個組件各負責一塊特定功能,除了實作自身核心功能之外,這 些組件還經常承擔著額外的職責,例如日志、事務管理和安全這樣的核心服務經常融入到自身具有核心 業務邏輯的組件中去,這些系統服務經常被稱為橫切關注點,因為它們會跨越系統的多個組件,

當我們需要為分散的物件引入公共行為的時候,OOP則顯得無能為力,也就是說,OOP允許你定義從上到下的關系,但并不適合定義從左到右的關系,例如日志功能,

日志代碼往往水平地散布在所有物件層次中,而與它所散布到的物件的核心功能毫無關系, 在OOP設計中,它導致了大量代碼的重復,而不利于各個模塊的重用,

AOP:將程式中的交叉業務邏輯(比如安全,日志,事務等),封裝成一個切面,然后注入到目標物件

(具體業務邏輯)中去,AOP可以對某個物件或某些物件的功能進行增強,比如物件中的方法進行增強,可以在執行某個方法之前額外的做一些事情,在某個方法執行之后額外的做一些事情

談談你對IOC的理解

容器概念、控制反轉、依賴注入

ioc容器:實際上就是個map(key,value),里面存的是各種物件(在xml里配置的bean節點、

@repository、@service、@controller、@component),在專案啟動的時候會讀取組態檔里面的bean節點,根據全限定類名使用反射創建物件放到map里、掃描到打上上述注解的類還是通過反射創建物件放到map里,

這個時候map里就有各種物件了,接下來我們在代碼里需要用到里面的物件時,再通過DI注入(autowired、resource等注解,xml里bean節點內的ref屬性,專案啟動的時候會讀取xml節點ref屬性根據id注入,也會掃描這些注解,根據型別或id注入;id就是物件名),

控制反轉:

沒有引入IOC容器之前,物件A依賴于物件B,那么物件A在初始化或者運行到某一點的時候,自己必須主動去創建物件B或者使用已經創建的物件B,無論是創建還是使用物件B,控制權都在自己手上,

引入IOC容器之后,物件A與物件B之間失去了直接聯系,當物件A運行到需要物件B的時候,IOC容器會主動創建一個物件B注入到物件A需要的地方,

通過前后的對比,不難看出來:物件A獲得依賴物件B的程序,由主動行為變為了被動行為,控制權顛倒過來了,這就是“控制反轉”這個名稱的由來,

全部物件的控制權全部上繳給“第三方”IOC容器,所以,IOC容器成了整個系統的關鍵核心,它起到了一種類似“粘合劑”的作用,把系統中的所有物件粘合在一起發揮作用,如果沒有這個“粘合劑”,物件與對 象之間會彼此失去聯系,這就是有人把IOC容器比喻成“粘合劑”的由來,

依賴注入:

“獲得依賴物件的程序被反轉了”,控制被反轉之后,獲得依賴物件的程序由自身管理變為了由IOC容器主動注入,依賴注入是實作IOC的方法,就是由IOC容器在運行期間,動態地將某種依賴關系注入到物件之中,

BeanFactory和ApplicationContext有什么區別?

ApplicationContext是BeanFactory的子介面

ApplicationContext提供了更完整的功能:

①繼承MessageSource,因此支持國際化,

②統一的資源檔案訪問方式,

③提供在監聽器中注冊bean的事件,

④同時加載多個組態檔,

⑤載入多個(有繼承關系)背景關系 ,使得每一個背景關系都專注于一個特定的層次,比如應用的web層,

BeanFactroy采用的是延遲加載形式來注入Bean的,即只有在使用到某個Bean時(呼叫getBean()),才對該Bean進行加載實體化,這樣,我們就不能發現一些存在的Spring的配置問 題,如果Bean的某一個屬性沒有注入,BeanFacotry加載后,直至第一次使用呼叫getBean方法 才會拋出例外,

ApplicationContext,它是在容器啟動時,一次性創建了所有的Bean,這樣,在容器啟動時,我 們就可以發現Spring中存在的配置錯誤,這樣有利于檢查所依賴屬性是否注入,

ApplicationContext啟動后預載入所有的單實體Bean,通過預載入單實體bean ,確保當你需要的時候,你就不用等待,因為它們已經創建好了,

相對于基本的BeanFactory,ApplicationContext 唯一的不足是占用記憶體空間,當應用程式配置

Bean較多時,程式啟動較慢,

BeanFactory通常以編程的方式被創建,ApplicationContext還能以宣告的方式創建,如使用ContextLoader,

BeanFactory和ApplicationContext都支持BeanPostProcessor、BeanFactoryPostProcessor的 使用,但兩者之間的區別是:BeanFactory需要手動注冊,而ApplicationContext則是自動注冊,

描述一下Spring Bean的生命周期?

1、決議類得到BeanDefinition

2、如果有多個構造方法,則要推斷構造方法

3、確定好構造方法后,進行實體化得到一個物件

4、對物件中的加了@Autowired注解的屬性進行屬性填充

5、回呼Aware方法,比如BeanNameAware,BeanFactoryAware

6、呼叫BeanPostProcessor的初始化前的方法

7、呼叫初始化方法

8、呼叫BeanPostProcessor的初始化后的方法,在這里會進行AOP

9、如果當前創建的bean是單例的則會把bean放入單例池

10、使用bean

11、Spring容器關閉時呼叫DisposableBean中destory()方法

解釋下Spring支持的幾種bean的作用域,

singleton:默認,每個容器中只有一個bean的實體,單例的模式由BeanFactory自身來維護,該 物件的生命周期是與Spring IOC容器一致的(但在第一次被注入時才會創建),

prototype:為每一個bean請求提供一個實體,在每次注入時都會創建一個新的物件

request:bean被定義為在每個HTTP請求中創建一個單例物件,也就是說在單個請求中都會復用 這一個單例物件,

session:與request范圍類似,確保每個session中有一個bean的實體,在session過期后,bean會隨之失效,

application:bean被定義為在ServletContext的生命周期中復用一個單例物件,websocket:bean被定義為在websocket的生命周期中復用一個單例物件,

global-session:全域作用域,global-session和Portlet應用相關,當你的應用部署在Portlet容器 中作業時,它包含很多portlet,如果你想要宣告讓所有的portlet共用全域的存盤變數的話,那么這全域變數需要存盤在global-session中,全域作用域與Servlet中的session作用域效果相同,

Spring框架中的單例Bean是執行緒安全的么?

Spring中的Bean默認是單例模式的,框架并沒有對bean進行多執行緒的封裝處理,

如果Bean是有狀態的 那就需要開發人員自己來進行執行緒安全的保證,最簡單的辦法就是改變bean的作用域 把 "singleton"改為’‘protopyte’ 這樣每次請求Bean就相當于是 new Bean() 這樣就可以保證執行緒的安全了,

有狀態就是有資料存盤功能

無狀態就是不會保存資料 controller、service和dao層本身并不是執行緒安全的,只是如果只 是呼叫里面的方法,而且多執行緒呼叫一個實體的方***在記憶體中復制變數,這是自己的執行緒的工 作記憶體,是安全的,

Dao會操作資料庫Connection,Connection是帶有狀態的,比如說資料庫事務,Spring的事務管理器 使用Threadlocal為不同執行緒維護了一套獨立的connection副本,保證執行緒之間不會互相影響(Spring 是如何保證事務獲取同一個Connection的)

不要在bean中宣告任何有狀態的實體變數或類變數,如果必須如此,那么就使用ThreadLocal把變數變 為執行緒私有的,如果bean的實體變數或類變數需要在多個執行緒之間共享,那么就只能使用synchronized、lock、CAS等這些實作執行緒同步的方法了,

Spring 框架中都用到了哪些設計模式?

簡單工廠:由一個工廠類根據傳入的引數,動態決定應該創建哪一個產品類,

Spring中的BeanFactory就是簡單工廠模式的體現,根據傳入一個唯一的標識來獲得Bean物件,但是否是 在傳入引數后創建還是傳入引數前創建這個要根據具體情況來定,

工廠方法:

實作了FactoryBean介面的bean是一類叫做factory的bean,其特點是,spring會在使用getBean()調 用獲得該bean時,會自動呼叫該bean的getObject()方法,所以回傳的不是factory這個bean,而是這個bean.getOjbect()方法的回傳值,

單例模式:保證一個類僅有一個實體,并提供一個訪問它的全域訪問點

spring對單例的實作: spring中的單例模式完成了后半句話,即提供了全域的訪問點BeanFactory,但沒有從構造器級別去控制單例,這是因為spring管理的是任意的java物件,

配接器模式:

Spring定義了一個適配介面,使得每一種Controller有一種對應的配接器實作類,讓配接器代替

controller執行相應的方法,這樣在擴展Controller時,只需要增加一個配接器類就完成了SpringMVC的擴展了,

裝飾器模式:動態地給一個物件添加一些額外的職責,就增加功能來說,Decorator模式相比生成子類更為靈活,

Spring中用到的包裝器模式在類名上有兩種表現:一種是類名中含有Wrapper,另一種是類名中含有Decorator,

動態代理:

切面在應用運行的時刻被織入,一般情況下,在織入切面時,AOP容器會為目標物件創建動態的創建一個代理 物件,SpringAOP就是以這種方式織入切面的,

織入:把切面應用到目標物件并創建新的代理物件的程序,

觀察者模式:

spring的事件驅動模型使用的是 觀察者模式 ,Spring中Observer模式常用的地方是listener的實作,

策略模式:

Spring框架的資源訪問Resource介面,該介面提供了更強的資源訪問能力,Spring 框架本身大量使用了

Resource 介面來訪問底層資源,

模板方法:父類定義了骨架(呼叫哪些方法及順序),某些特定方法由子類實作,

最大的好處:代碼復用,減少重復代碼,除了子類要實作的特定方法,其他方法及方法呼叫順序都在父 類中預先寫好了,

refresh方法

Spring事務的實作方式和原理以及隔離級別?

在使用Spring框架時,可以有兩種使用事務的方式,一種是編程式的,一種是申明式的,

@Transactional注解就是申明式的,

首先,事務這個概念是資料庫層面的,Spring只是基于資料庫中的事務進行了擴展,以及提供了一些能讓程式員更加方便操作事務的方式,

比如我們可以通過在某個方法上增加@Transactional注解,就可以開啟事務,這個方法中所有的sql都 會在一個事務中執行,統一成功或失敗,

在一個方法上加了@Transactional注解后,Spring會基于這個類生成一個代理物件,會將這個代理物件 作為bean,當在使用這個代理物件的方法時,如果這個方法上存在@Transactional注解,那么代理邏輯會先把事務的自動提交設定為false,然后再去執行原本的業務邏輯方法,如果執行業務邏輯方法沒有出現例外,那么代理邏輯中就會將事務進行提交,如果執行業務邏輯方法出現了例外,那么則會將事務 進行回滾,

當然,針對哪些例外回滾事務是可以配置的,可以利用@Transactional注解中的rollbackFor屬性進行 配置,默認情況下會對RuntimeException和Error進行回滾,

spring事務隔離級別就是資料庫的隔離級別:外加一個默認級別read uncommitted(未提交讀)

read committed(提交讀、不可重復讀)

repeatable read(可重復讀) serializable(可串行化)

資料庫的配置隔離級別是Read Commited,而Spring配置的隔離級別是Repeatable Read,請問這時隔離級別是以哪一個為準?

以Spring配置的為準,如果spring設定的隔離級別資料庫不支持,效果取決于資料庫

spring事務傳播機制

多個事務方法相互呼叫時,事務如何在這些方法間傳播

方法A是一個事務的方法,方法A執行程序中呼叫了方法B,那么方法B有無事務以及方法B對事務的要求不同都 會對方法A的事務具體執行造成影響,同時方法A的事務對方法B的事務執行也有影響,這種影響具體是什么就 由兩個方法所定義的事務傳播型別所決定,

REQUIRED(Spring默認的事務傳播型別):如果當前沒有事務,則自己新建一個事務,如果當前存在事 務,則加入這個事務

SUPPORTS:當前存在事務,則加入當前事務,如果當前沒有事務,就以非事務方法執行MANDATORY:當前存在事務,則加入當前事務,如果當前事務不存在,則拋出例外,REQUIRES_NEW:創建一個新事務,如果存在當前事務,則掛起該事務,

NOT_SUPPORTED:以非事務方式執行,如果當前存在事務,則掛起當前事務NEVER:不使用事務,如果當前事務存在,則拋出例外

NESTED:如果當前事務存在,則在嵌套事務中執行,否則REQUIRED的操作一樣(開啟一個事務)

和REQUIRES_NEW的區別

REQUIRES_NEW是新建一個事務并且新開啟的這個事務與原有事務無關,而NESTED則是當前存在事務時(我 們把當前事務稱之為父事務)會開啟一個嵌套事務(稱之為一個子事務), 在NESTED情況下父事務回滾時, 子事務也會回滾,而在REQUIRES_NEW情況下,原有事務回滾,不會影響新開啟的事務,

和REQUIRED的區別

REQUIRED情況下,呼叫方存在事務時,則被呼叫方和呼叫方使用同一事務,那么被呼叫方出現例外時,由于 共用一個事務,所以無論呼叫方是否catch其例外,事務都會回滾 而在NESTED情況下,被呼叫方發生例外時,呼叫方可以catch其例外,這樣只有子事務回滾,父事務不受影響

spring事務什么時候會失效?

spring事務的原理是AOP,進行了切面增強,那么失效的根本原因是這個AOP不起作用了!常見情況有如下幾種

1、發生自呼叫,類里面使用this呼叫本類的方法(this通常省略),此時這個this物件不是代理類,而是UserService物件本身!

解決方法很簡單,讓那個this變成UserService的代理類即可!

2、方法不是public的

@Transactional 只能用于 public 的方法上,否則事務不會失效,如果要用在非 public 方法上,可以開啟 AspectJ 代理模式,

3、資料庫不支持事務

4、沒有被spring管理

5、例外被吃掉,事務不會回滾(或者拋出的例外沒有被定義,默認為RuntimeException)

什么是bean的自動裝配,有哪些方式?

開啟自動裝配,只需要在xml組態檔中定義“autowire”屬性,

<bean id="cutomer"  autowire="" />

autowire屬性有五種裝配的方式:

no – 預設情況下,自動配置是通過“ref”屬性手動設定 ,

手動裝配:以value或ref的方式明確指定屬性值都是手動裝配, 需要通過‘ref’屬性來連接bean,

byName-根據bean的屬性名稱進行自動裝配,

Cutomer的屬性名稱是person,Spring會將bean id為person的bean通過setter方法進行自動裝配,

<bean id="cutomer"  autowire="byName"/>

<bean id="person" />

byType-根據bean的型別進行自動裝配,

Cutomer的屬性person的型別為Person,Spirng會將Person型別通過setter方法進行自動裝配,

<bean id="cutomer"  autowire="byType"/>

<bean id="person" />

constructor-類似byType,不過是應用于構造器的引數,如果一個bean與構造器引數的型別形 同,則進行自動裝配,否則導致例外,

Cutomer建構式的引數person的型別為Person,Spirng會將Person型別通過構造方法進行自動裝 配,

<bean id="cutomer"  autowire="construtor"/>

<bean id="person" />

autodetect-如果有默認的構造器,則通過constructor方式進行自動裝配,否則使用byType方式進行自動裝配,

如果有默認的構造器,則通過constructor方式進行自動裝配,否則使用byType方式進行自動裝配,

@Autowired自動裝配bean,可以在欄位、setter方法、建構式上使用,

Spring Boot、Spring MVC 和 Spring 有什么區別

spring是一個IOC容器,用來管理Bean,使用依賴注入實作控制反轉,可以很方便的整合各種框架,提供AOP機制彌補OOP的代碼重復問題、更方便將不同類不同方法中的共同處理抽取成切面、自動注入給方法執行,比如日志、例外等

springmvc是spring對web框架的一個解決方案,提供了一個總的前端控制器Servlet,用來接收請求, 然后定義了一套路由策略(url到handle的映射)及適配執行handle,將handle結果使用視圖決議技術 生成視圖展現給前端

springboot是spring提供的一個快速開發工具包,讓程式員能更方便、更快速的開發spring+springmvc應用,簡化了配置(約定了默認配置),整合了一系列的解決方案(starter機制)、redismongodb、es,可以開箱即用

SpringMVC 作業流程

用戶發送請求至前端控制器 DispatcherServlet,

DispatcherServlet 收到請求呼叫 HandlerMapping 處理器映射器,

處理器映射器找到具體的處理器(可以根據 xml 配置、注解進行查找),生成處理器及處理器攔截器

(如果有則生成)一并回傳給 DispatcherServlet,

DispatcherServlet 呼叫 HandlerAdapter 處理器配接器,

HandlerAdapter 經過適配呼叫具體的處理器(Controller,也叫后端控制器) 6)Controller 執行完成回傳 ModelAndView,

HandlerAdapter 將 controller 執行結果 ModelAndView 回傳給 DispatcherServlet,8) DispatcherServlet 將 ModelAndView 傳給 ViewReslover 視圖決議器,

ViewReslover 決議后回傳具體 View,

DispatcherServlet 根據 View 進行渲染視圖(即將模型資料填充至視圖中),

DispatcherServlet 回應用戶,

Spring MVC的主要組件?

Handler:也就是處理器,它直接應對著MVC中的C也就是Controller層,它的具體表現形式有很多,可 以是類,也可以是方法,在Controller層中@RequestMapping標注的所有方法都可以看成是一個

Handler,只要可以實際處理請求就可以是Handler

1、HandlerMapping

initHandlerMappings(context),處理器映射器,根據用戶請求的資源uri來查找Handler的,在

SpringMVC中會有很多請求,每個請求都需要一個Handler處理,具體接收到一個請求之后使用哪個Handler進行,這就是HandlerMapping需要做的事,

2、HandlerAdapter

initHandlerAdapters(context),配接器,因為SpringMVC中的Handler可以是任意的形式,只要能處理請求就ok,但是Servlet需要的處理方法的結構卻是固定的,都是以request和response為引數的方 法,

如何讓固定的Servlet處理方法呼叫靈活的Handler來進行處理呢?這就是HandlerAdapter要做的事情,

Handler是用來干活的工具;HandlerMapping用于根據需要干的活找到相應的工具;HandlerAdapter是使用工具干活的人,

3、HandlerExceptionResolver

initHandlerExceptionResolvers(context), 其它組件都是用來干活的,在干活的程序中難免會出現問題,出問題后怎么辦呢?這就需要有一個專門的角色對例外情況進行處理,在SpringMVC中就是

HandlerExceptionResolver,具體來說,此組件的作用是根據例外設定ModelAndView,之后再交給render方法進行渲染,

4、ViewResolver

initViewResolvers(context),ViewResolver用來將String型別的視圖名和Locale決議為View型別的視 圖,View是用來渲染頁面的,也就是將程式回傳的引數填入模板里,生成html(也可能是其它型別) 檔案,這里就有兩個關鍵問題:使用哪個模板?用什么技術(規則)填入引數?這其實是ViewResolver 主要要做的作業,ViewResolver需要找到渲染所用的模板和所用的技術(也就是視圖的型別)進行渲染,具體的渲染程序則交由不同的視圖自己完成,

5、RequestToViewNameTranslator

initRequestToViewNameTranslator(context),ViewResolver是根據ViewName查找View,但有的Handler處理完后并沒有設定View也沒有設定ViewName,這時就需要從request獲取ViewName了, 如何從request中獲取ViewName就是RequestToViewNameTranslator要做的事情了,

RequestToViewNameTranslator在Spring MVC容器里只可以配置一個,所以所有request到

ViewName的轉換規則都要在一個Translator里面全部實作,

6、LocaleResolver

initLocaleResolver(context), 決議視圖需要兩個引數:一是視圖名,另一個是Locale,視圖名是處理器回傳的,Locale是從哪里來的?這就是LocaleResolver要做的事情,LocaleResolver用于從request 決議出Locale,Locale就是zh-cn之類,表示一個區域,有了這個就可以對不同區域的用戶顯示不同的 結果,SpringMVC主要有兩個地方用到了Locale:一是ViewResolver視圖決議的時候;二是用到國際化資源或者主題的時候,

7、ThemeResolver

initThemeResolver(context),用于決議主題,SpringMVC中一個主題對應一個properties檔案,里面 存放著跟當前主題相關的所有資源、如圖片、css樣式等,SpringMVC的主題也支持國際化,同一個主題不同區域也可以顯示不同的風格,SpringMVC中跟主題相關的類有 ThemeResolver、ThemeSource 和Theme,主題是通過一系列資源來具體體現的,要得到一個主題的資源,首先要得到資源的名稱,這是ThemeResolver的作業,然后通過主題名稱找到對應的主題(可以理解為一個配置)檔案,這是

ThemeSource的作業,最后從主題中獲取資源就可以了,

8、MultipartResolver

initMultipartResolver(context),用于處理上傳請求,處理方法是將普通的request包裝成MultipartHttpServletRequest,后者可以直接呼叫getFile方法獲取File,如果上傳多個檔案,還可以呼叫getFileMap得到FileName->File結構的Map,此組件中一共有三個方法,作用分別是判斷是不是上傳 請求,將request包裝成MultipartHttpServletRequest、處理完后清理上傳程序中產生的臨時資源,

9、FlashMapManager

initFlashMapManager(context),用來管理FlashMap的,FlashMap主要用在redirect中傳遞引數,

Spring Boot 自動配置原理?

@Import + @Configuration + Spring spi

自動配置類由各個starter提供,使用@Configuration + @Bean定義配置類,放到META-

INF/spring.factories下

使用Spring spi掃描META-INF/spring.factories下的配置類使用@Import匯入自動配置類

如何理解 Spring Boot 中的 Starter

使用spring + springmvc使用,如果需要引入mybatis等框架,需要到xml中定義mybatis需要的bean

starter就是定義一個starter的jar包,寫一個@Configuration配置類、將這些bean定義在里面,然后在

starter包的META-INF/spring.factories中寫入該配置類,springboot會按照約定來加載該配置類

開發人員只需要將相應的starter包依賴進應用,進行相應的屬性配置(使用默認配置時,不需要配置),就可以直接進行代碼開發,使用對應的功能了,比如
mybatis-spring-boot--starter,spring- boot-starter-redis

什么是嵌入式服務器?為什么要使用嵌入式服務器?

節省了下載安裝tomcat,應用也不需要再打war包,然后放到webapp目錄下再運行 只需要一個安裝了 Java 的虛擬機,就可以直接在上面部署應用程式了

springboot已經內置了tomcat.jar,運行main方法時會去啟動tomcat,并利用tomcat的spi機制加載springmvc

mybatis的優缺點

優點:

1、基于 SQL 陳述句編程,相當靈活,不會對應用程式或者資料庫的現有設計造成任何影響,SQL 寫在XML 里,解除 sql 與程式代碼的耦合,便于統一管理;提供 XML 標簽, 支持撰寫動態 SQL 陳述句, 并可重用,

2、與 JDBC 相比,減少了 50%以上的代碼量,消除了 JDBC 大量冗余的代碼,不需要手動開關連接;

3、很好的與各種資料庫兼容( 因為 MyBatis 使用 JDBC 來連接資料庫,所以只要JDBC 支持的資料庫

MyBatis 都支持),

4、能夠與 Spring 很好的集成;

5、提供映射標簽, 支持物件與資料庫的 ORM 欄位關系映射; 提供物件關系映射標簽, 支持物件關系組件維護,

缺點:

1、SQL 陳述句的撰寫作業量較大, 尤其當欄位多、關聯表多時, 對開發人員撰寫SQL 陳述句的功底有一定要求,

2、SQL 陳述句依賴于資料庫, 導致資料庫移植性差, 不能隨意更換資料庫,

MyBatis 與Hibernate 有哪些不同?

SQL 和 ORM 的爭論,永遠都不會終止開發速度的對比:

Hibernate的真正掌握要比Mybatis難些,Mybatis框架相對簡單很容易上手,但也相對簡陋些,

比起兩者的開發速度,不僅僅要考慮到兩者的特性及性能,更要根據專案需求去考慮究竟哪一個更適合 專案開發,比如:一個專案中用到的復雜查詢基本沒有,就是簡單的增刪改查,這樣選擇hibernate效 率就很快了,因為基本的sql陳述句已經被封裝好了,根本不需要你去寫sql陳述句,這就節省了大量的時間,但是對于一個大型專案,復雜陳述句較多,這樣再去選擇hibernate就不是一個太好的選擇,選擇

mybatis就會加快許多,而且陳述句的管理也比較方便,

開發作業量的對比:

Hibernate和MyBatis都有相應的代碼生成工具,可以生成簡單基本的DAO層方法,針對高級查詢,

Mybatis需要手動撰寫SQL陳述句,以及ResultMap,而Hibernate有良好的映射機制,開發者無需關心SQL的生成與結果映射,可以更專注于業務流程

sql優化方面:

Hibernate的查詢會將表中的所有欄位查詢出來,這一點會有性能消耗,Hibernate也可以自己寫SQL來 指定需要查詢的欄位,但這樣就破壞了Hibernate開發的簡潔性,而Mybatis的SQL是手動撰寫的,所以 可以按需求指定查詢的欄位,

Hibernate HQL陳述句的調優需要將SQL列印出來,而Hibernate的SQL被很多人嫌棄因為太丑了,

MyBatis的SQL是自己手動寫的所以調整方便,但Hibernate具有自己的日志統計,Mybatis本身不帶日 志統計,使用Log4j進行日志記錄,

物件管理的對比:

Hibernate 是完整的物件/關系映射解決方案,它提供了物件狀態管理(state management)的功能, 使開發者不再需要理會底層資料庫系統的細節,也就是說,相對于常見的 JDBC/SQL 持久層方案中需要管理 SQL 陳述句,Hibernate采用了更自然的面向物件的視角來持久化 Java 應用中的資料,

換句話說,使用 Hibernate 的開發者應該總是關注物件的狀態(state),不必考慮 SQL 陳述句的執行,這部分細節已經由 Hibernate 掌管妥當,只有開發者在進行系統性能調優的時候才需要進行了解,而

MyBatis在這一塊沒有檔案說明,用戶需要對物件自己進行詳細的管理,

快取機制對比:

相同點:都可以實作自己的快取或使用其他第三方快取方案,創建配接器來完全覆寫快取行為,

不同點:Hibernate的二級快取配置在SessionFactory生成的組態檔中進行詳細配置,然后再在具體 的表-物件映射中配置是哪種快取,

MyBatis的二級快取配置都是在每個具體的表-物件映射中進行詳細配置,這樣針對不同的表可以自定義不同的快取機制,并且Mybatis可以在命名空間***享相同的快取配置和實體,通過Cache-ref來實作,

兩者比較:因為Hibernate對查詢物件有著良好的管理機制,用戶無需關心SQL,所以在使用二級快取時如果出現臟資料,系統會報出錯誤并提示,

而MyBatis在這一方面,使用二級快取時需要特別小心,如果不能完全確定資料更新操作的波及范圍, 避免Cache的盲目使用,否則,臟資料的出現會給系統的正常運行帶來很大的隱患,

Hibernate功能強大,資料庫無關性好,O/R映射能力強,如果你對Hibernate相當精通,而且對

Hibernate進行了適當的封裝,那么你的專案整個持久層代碼會相當簡單,需要寫的代碼很少,開發速度很快,非常爽,

Hibernate的缺點就是學習門檻不低,要精通門檻更高,而且怎么設計O/R映射,在性能和物件模型之間如何權衡取得平衡,以及怎樣用好Hibernate方面需要你的經驗和能力都很強才行,

iBATIS入門簡單,即學即用,提供了資料庫查詢的自動物件系結功能,而且延續了很好的SQL使用經驗,對于沒有那么高的物件模型要求的專案來說,相當完美,

iBATIS的缺點就是框架還是比較簡陋,功能尚有缺失,雖然簡化了資料系結代碼,但是整個底層資料庫查詢實際還是要自己寫的,作業量也比較大,而且不太容易適應快速資料庫修改,

#{}和${}的區別是什么?

#{}是預編譯處理、是占位符, ${}是字串替換、是拼接符,

Mybatis 在處理#{}時,會將 sql 中的#{}替換為?號,呼叫 PreparedStatement 來賦值;

Mybatis 在處理${}時, 就是把${}替換成變數的值,呼叫 Statement 來賦值;

#{} 的變數替換是在DBMS 中、變數替換后,#{} 對應的變數自動加上單引號

${} 的變數替換是在 DBMS 外、變數替換后,${} 對應的變數不會加上單引號使用#{}可以有效的防止 SQL 注入, 提高系統安全性,

簡述 Mybatis 的插件運行原理,如何撰寫一個插件,

答: Mybatis 只支持針對 ParameterHandler、ResultSetHandler、StatementHandler、Executor 這

4 種介面的插件, Mybatis 使用 JDK 的動態代理, 為需要攔截的介面生成代理物件以實作介面方法攔截功能, 每當執行這 4 種介面物件的方法時,就會進入攔截方法,具體就是 InvocationHandler 的

invoke() 方法, 攔截那些你指定需要攔截的方法,

撰寫插件: 實作 Mybatis 的 Interceptor 介面并復寫 intercept()方法, 然后在給插件撰寫注解, 指定要攔截哪一個介面的哪些方法即可, 在組態檔中配置撰寫的插件,

@Intercepts({@Signature(type = StatementHandler.class, method = "query", args =

{Statement.class, ResultHandler.class}),

@Signature(type = StatementHandler.class, method = "update", args =

{Statement.class}),

@Signature(type = StatementHandler.class, method = "batch", args = { Statement.class })})

@Component

invocation.proceed()執行具體的業務邏輯

索引的基本原理

索參考來快速地尋找那些具有特定值的記錄,如果沒有索引,一般來說執行查詢時遍歷整張表, 索引的原理:就是把無序的資料變成有序的查詢

  1. 把創建了索引的列的內容進行排序
  2. 排序結果生成倒排表
  3. 在倒排表內容上拼上資料地址鏈
  4. 在查詢的時候,先拿到倒排表內容,再取出資料地址鏈,從而拿到具體資料

mysql聚簇和非聚簇索引的區別

都是B+樹的資料結構

聚簇索引:將資料存盤與索引放到了一塊、并且是按照一定的順序組織的,找到索引也就找到了數 據,資料的物理存放順序與索引順序是一致的,即:只要索引是相鄰的,那么對應的資料一定也是 相鄰地存放在磁盤上的

非聚簇索引:葉子節點不存盤資料、存盤的是資料行地址,也就是說根據索引查找到資料行的位置 再取磁盤查找資料,這個就有點類似一本樹的目錄,比如我們要找第三章第一節,那我們先在這個 目錄里面找,找到對應的頁碼后再去對應的頁碼看文章,

優勢:

1、查詢通過聚簇索引可以直接獲取資料,相比非聚簇索引需要第二次查詢(非覆寫索引的情況下)效率 要高

2、聚簇索引對于范圍查詢的效率很高,因為其資料是按照大小排列的

3、聚簇索引適合用在排序的場合,非聚簇索引不適合

劣勢:

1、維護索引很昂貴,特別是插入新行或者主鍵被更新導至要分頁(page split)的時候,建議在大量插入新行后,選在負載較低的時間段,通過OPTIMIZE TABLE優化表,因為必須被移動的行資料可能造成碎片,使用獨享表空間可以榷訓碎片

2、表因為使用UUId(隨機ID)作為主鍵,使資料存盤稀疏,這就會出現聚簇索引有可能有比全表掃面 更慢,所以建議使用int的auto_increment作為主鍵

3、如果主鍵比較大的話,那輔助索引將會變的更大,因為輔助索引的葉子存盤的是主鍵值;過長的主鍵 值,會導致非葉子節點占用占用更多的物理空間

InnoDB中一定有主鍵,主鍵一定是聚簇索引,不手動設定、則會使用unique索引,沒有unique索引, 則會使用資料庫內部的一個行的隱藏id來當作主鍵索引,在聚簇索引之上創建的索引稱之為輔助索引, 輔助索引訪問資料總是需要二次查找,非聚簇索引都是輔助索引,像復合索引、前綴索引、唯一索引, 輔助索引葉子節點存盤的不再是行的物理位置,而是主鍵值

MyISM使用的是非聚簇索引,沒有聚簇索引,非聚簇索引的兩棵B+樹看上去沒什么不同,節點的結構完全一致只是存盤的內容不同而已,主鍵索引B+樹的節點存盤了主鍵,輔助鍵索引B+樹存盤了輔助

鍵,表資料存盤在獨立的地方,這兩顆B+樹的葉子節點都使用一個地址指向真正的表資料,對于表資料 來說,這兩個鍵沒有任何差別,由于索引樹是獨立的,通過輔助鍵檢索無需訪問主鍵的索引樹,

如果涉及到大資料量的排序、全表掃描、count之類的操作的話,還是MyISAM占優勢些,因為索引所占空間小,這些操作是需要在記憶體中完成的,

mysql索引的資料結構,各自優劣

索引的資料結構和具體存盤引擎的實作有關,在MySQL中使用較多的索引有Hash索引,B+樹索引等,

InnoDB存盤引擎的默認索引實作為:B+樹索引,對于哈希索引來說,底層的資料結構就是哈希表,因此在絕大多數需求為單條記錄查詢的時候,可以選擇哈希索引,查詢性能最快;其余大部分場景,建議 選擇BTree索引,

B+樹:

B+樹是一個平衡的多叉樹,從根節點到每個葉子節點的高度差值不超過1,而且同層級的節點間有指標 相互鏈接,在B+樹上的常規檢索,從根節點到葉子節點的搜索效率基本相當,不會出現大幅波動,而且 基于索引的順序掃描時,也可以利用雙向指標快速左右移動,效率非常高,因此,B+樹索引被廣泛應用 于資料庫、檔案系統等場景,

 

哈希索引:

哈希索引就是采用一定的哈希演算法,把鍵值換算成新的哈希值,檢索時不需要類似B+樹那樣從根節點到 葉子節點逐級查找,只需一次哈希演算法即可立刻定位到相應的位置,速度非常快

 

如果是等值查詢,那么哈希索引明顯有絕對優勢,因為只需要經過一次演算法即可找到相應的鍵值;前提 是鍵值都是唯一的,如果鍵值不是唯一的,就需要先找到該鍵所在位置,然后再根據鏈表往后掃描,直 到找到相應的資料;

如果是范圍查詢檢索,這時候哈希索引就毫無用武之地了,因為原先是有序的鍵值,經過哈希演算法后, 有可能變成不連續的了,就沒辦法再利用索引完成范圍查詢檢索;

哈希索引也沒辦法利用索引完成排序,以及like ‘xxx%’ 這樣的部分模糊查詢(這種部分模糊查詢,其實本質上也是范圍查詢);

哈希索引也不支持多列聯合索引的最左匹配規則;

B+樹索引的關鍵字檢索效率比較平均,不像B樹那樣波動幅度大,在有大量重復鍵值情況下,哈希索引的效率也是極低的,因為存在哈希碰撞問題,

索引設計的原則?

查詢更快、占用空間更小

  1. 適合索引的列是出現在where子句中的列,或者連接子句中指定的列
  2. 基數較小的表,索引效果較差,沒有必要在此列建立索引
  3. 使用短索引,如果對長字串列進行索引,應該指定一個前綴長度,這樣能夠節省大量索引空間, 如果搜索詞超過索引前綴長度,則使用索引排除不匹配的行,然后檢查其余行是否可能匹配,
  4. 不要過度索引,索引需要額外的磁盤空間,并降低寫操作的性能,在修改表內容的時候,索引會進 行更新甚至重構,索引列越多,這個時間就會越長,所以只保持需要的索引有利于查詢即可,
  5. 定義有外鍵的資料列一定要建立索引,
  6. 更新頻繁欄位不適合創建索引
  7. 若是不能有效區分資料的列不適合做索引列(如性別,男女未知,最多也就三種,區分度實在太低)
  8. 盡量的擴展索引,不要新建索引,比如表中已經有a的索引,現在要加(a,b)的索引,那么只需要修改原來的索引即可,
  9. 對于那些查詢中很少涉及的列,重復值比較多的列不要建立索引,
  10. 對于定義為text、image和bit的資料型別的列不要建立索引,

什么是最左前綴原則?什么是最左匹配原則

鎖的型別有哪些

基于鎖的屬性分類:共享鎖、排他鎖,

基于鎖的粒度分類:行級鎖(INNODB)、表級鎖(INNODB、MYISAM)、頁級鎖(BDB引擎 )、記錄鎖、間隙鎖、臨鍵鎖,

基于鎖的狀態分類:意向共享鎖、意向排它鎖,共享鎖(Share Lock)

共享鎖又稱讀鎖,簡稱S鎖;當一個事務為資料加上讀鎖之后,其他事務只能對該資料加讀鎖,而不能對 資料加寫鎖,直到所有的讀鎖釋放之后其他事務才能對其進行加持寫鎖,共享鎖的特性主要是為了支持 并發的讀取資料,讀取資料的時候不支持修改,避免出現重復讀的問題,

排他鎖(eXclusive Lock)

排他鎖又稱寫鎖,簡稱X鎖;當一個事務為資料加上寫鎖時,其他請求將不能再為資料加任何鎖,直到該 鎖釋放之后,其他事務才能對資料進行加鎖,排他鎖的目的是在資料修改時候,不允許其他人同時修改,也不允許其他人讀取,避免了出現臟資料和臟讀的問題,

表鎖

表鎖是指上鎖的時候鎖住的是整個表,當下一個事務訪問該表的時候,必須等前一個事務釋放了鎖才能 進行對表進行訪問;

特點: 粒度大,加鎖簡單,容易沖突;

行鎖

行鎖是指上鎖的時候鎖住的是表的某一行或多行記錄,其他事務訪問同一張表時,只有被鎖住的記錄不 能訪問,其他的記錄可正常訪問;

特點:粒度小,加鎖比表鎖麻煩,不容易沖突,相比表鎖支持的并發要高;

記錄鎖(Record Lock)

記錄鎖也屬于行鎖中的一種,只不過記錄鎖的范圍只是表中的某一條記錄,記錄鎖是說事務在加鎖后鎖 住的只是表的某一條記錄,

精準條件命中,并且命中的條件欄位是唯一索引

加了記錄鎖之后資料可以避免資料在查詢的時候被修改的重復讀問題,也避免了在修改的事務未提交前 被其他事務讀取的臟讀問題,

頁鎖

頁級鎖是MySQL中鎖定粒度介于行級鎖和表級鎖中間的一種鎖,表級鎖速度快,但沖突多,行級沖突 少,但速度慢,所以取了折衷的頁級,一次鎖定相鄰的一組記錄,

特點:開銷和加鎖時間界于表鎖和行鎖之間;會出現死鎖;鎖定粒度界于表鎖和行鎖之間,并發度一般

間隙鎖(Gap Lock)

臨建鎖(Next-Key Lock)

屬于行鎖中的一種,間隙鎖是在事務加鎖后其鎖住的是表記錄的某一個區間,當表的相鄰ID之間出現空 隙則會形成一個區間,遵循左開右閉原則,

范圍查詢并且查詢未命中記錄,查詢條件必須命中索引、間隙鎖只會出現在REPEATABLE_READ(重復 讀)的事務級別中,

觸發條件:防止幻讀問題,事務并發的時候,如果沒有間隙鎖,就會發生如下圖的問題,在同一個事務 里,A事務的兩次查詢出的結果會不一樣,

比如表里面的資料ID 為 1,4,5,7,10 ,那么會形成以下幾個間隙區間,-n-1區間,1-4區間,7-10區間,10-n區間 (-n代表負無窮大,n代表正無窮大)

也屬于行鎖的一種,并且它是INNODB的行鎖默認演算法,總結來說它就是記錄鎖和間隙鎖的組合,臨鍵鎖 會把查詢出來的記錄鎖住,同時也會把該范圍查詢內的所有間隙空間也會鎖住,再之它會把相鄰的下一 個區間也會鎖住

觸發條件:范圍查詢并命中,查詢命中了索引,

結合記錄鎖和間隙鎖的特性,臨鍵鎖避免了在范圍查詢時出現臟讀、重復讀、幻讀問題,加了臨鍵鎖之 后,在范圍區間內資料不允許被修改和插入,

如果當事務A加鎖成功之后就設定一個狀態告訴后面的人,已經有人對表里的行加了一個排他鎖了,你們不能對整個表加共享鎖或排它鎖了,那么后面需要對整個表加鎖的人只需要獲取這個狀態 就知道自己是不是可以對表加鎖,避免了對整個索引樹的每個節點掃描是否加鎖,而這個狀態就是 意向鎖,

意向共享鎖

當一個事務試圖對整個表進行加共享鎖之前,首先需要獲得這個表的意向共享鎖,

意向排他鎖

當一個事務試圖對整個表進行加排它鎖之前,首先需要獲得這個表的意向排它鎖,

InnoDB存盤引擎的鎖的演算法

Record lock:單個行記錄上的鎖

Gap lock:間隙鎖,鎖定一個范圍,不包括記錄本身

Next-key lock:record+gap 鎖定一個范圍,包含記錄本身

相關知識點:

  1. innodb對于行的查詢使用next-key lock
  2. Next-locking keying為了解決Phantom Problem幻讀問題
  3. 當查詢的索引含有唯一屬性時,將next-key lock降級為record key
  4. Gap鎖設計的目的是為了阻止多個事務將記錄插入到同一范圍內,而這會導致幻讀問題的產生
  5. 有兩種方式顯式關閉gap鎖:(除了外鍵約束和唯一性檢查外,其余情況僅使用record lock) A.將事務隔離級別設定為RC B. 將引數innodb_locks_unsafe_for_binlog設定為1

關心過業務系統里面的sql耗時嗎?統計過慢查詢嗎?對慢查詢都怎么優化過?

在業務系統中,除了使用主鍵進行的查詢,其他的都會在測驗庫上測驗其耗時,慢查詢的統計主要由運 維在做,會定期將業務中的慢查詢反饋給我們,

慢查詢的優化首先要搞明白慢的原因是什么?是查詢條件沒有命中索引?是load了不需要的資料列?還 是資料量太大?

所以優化也是針對這三個方向來的,

首先分析陳述句,看看是否load了額外的資料,可能是查詢了多余的行并且拋棄掉了,可能是加載了許多結果中并不需要的列,對陳述句進行分析以及重寫,

分析陳述句的執行計劃,然后獲得其使用索引的情況,之后修改陳述句或者修改索引,使得陳述句可以盡 可能的命中索引,

如果對陳述句的優化已經無法進行,可以考慮表中的資料量是否太大,如果是的話可以進行橫向或者 縱向的分表,

事務的基本特性和隔離級別

事務基本特性ACID分別是:

原子性指的是一個事務中的操作要么全部成功,要么全部失敗,

一致性指的是資料庫總是從一個一致性的狀態轉換到另外一個一致性的狀態,比如A轉賬給B100塊錢, 假設A只有90塊,支付之前我們資料庫里的資料都是符合約束的,但是如果事務執行成功了,我們的資料庫資料就破壞約束了,因此事務不能成功,這里我們說事務提供了一致性的保證

隔離性指的是一個事務的修改在最終提交前,對其他事務是不可見的,

持久性指的是一旦事務提交,所做的修改就會永久保存到資料庫中,

隔離性有4個隔離級別,分別是:

read uncommit 讀未提交,可能會讀到其他事務未提交的資料,也叫做臟讀,

用戶本來應該讀取到id=1的用戶age應該是10,結果讀取到了其他事務還沒有提交的事務,結果讀取結果age=20,這就是臟讀,

read commit 讀已提交,兩次讀取結果不一致,叫做不可重復讀,不可重復讀解決了臟讀的問題,他只會讀取已經提交的事務,

用戶開啟事務讀取id=1用戶,查詢到age=10,再次讀取發現結果=20,在同一個事務里同一個查 詢讀取到不同的結果叫做不可重復讀,

repeatable read 可重復復讀,這是mysql的默認級別,就是每次讀取結果都一樣,但是有可能產生幻讀,

serializable 串行,一般是不會使用的,他會給每一行讀取的資料加鎖,會導致大量超時和鎖競爭的問題,

臟讀(Drity Read):某個事務已更新一份資料,另一個事務在此時讀取了同一份資料,由于某些原因, 前一個RollBack了操作,則后一個事務所讀取的資料就會是不正確的,

不可重復讀(Non-repeatable read):在一個事務的兩次查詢之中資料不一致,這可能是兩次查詢程序中間插入了一個事務更新的原有的資料,

幻讀(Phantom Read):在一個事務的兩次查詢中資料筆數不一致,例如有一個事務查詢了幾列(Row)資料,而另一個事務卻在此時插入了新的幾列資料,先前的事務在接下來的查詢中,就會發現有幾列資料 是它先前所沒有的,

ACID靠什么保證的?

A原子性由undo log日志保證,它記錄了需要回滾的日志資訊,事務回滾時撤銷已經執行成功的sql

C一致性由其他三大特性保證、程式代碼要保證業務上的一致性

I隔離性由MVCC來保證

D持久性由記憶體+redo log來保證,mysql修改資料同時在記憶體和redo log記錄這次操作,宕機的時候可以從redo log恢復

InnoDB redo log 寫盤,InnoDB 事務進入 prepare 狀態,

如果前面 prepare 成功,binlog 寫盤,再繼續將事務日志持久化到 binlog,如果持久化成功,那么

InnoDB 事務則進入 commit 狀態(在 redo log 里面寫一個 commit 記錄)

redolog的刷盤會在系統空閑時進行

什么是MVCC

多版本并發控制:讀取資料時通過一種類似快照的方式將資料保存下來,這樣讀鎖就和寫鎖不沖突了, 不同的事務session會看到自己特定版本的資料,版本鏈

MVCC只在 READ COMMITTED 和 REPEATABLE READ 兩個隔離級別下作業,其他兩個隔離級別夠和MVCC不兼容, 因為 READ UNCOMMITTED 總是讀取最新的資料行, 而不是符合當前事務版本的資料行,而 SERIALIZABLE 則會對所有讀取的行都加鎖,

聚簇索引記錄中有兩個必要的隱藏列:

trx_id:用來存盤每次對某條聚簇索引記錄進行修改的時候的事務id, roll_pointer:每次對哪條聚簇索引記錄有修改的時候,都會把老版本寫入undo日志中,這個

roll_pointer就是存了一個指標,它指向這條聚簇索引記錄的上一個版本的位置,通過它來獲得上一個

版本的記錄資訊,(注意插入操作的undo日志沒有這個屬性,因為它沒有老版本)

已提交讀和可重復讀的區別就在于它們生成ReadView的策略不同,

開始事務時創建readview,readView維護當前活動的事務id,即未提交的事務id,排序生成一個陣列 訪問資料,獲取資料中的事務id(獲取的是事務id最大的記錄),對比readview:

如果在readview的左邊(比readview都小),可以訪問(在左邊意味著該事務已經提交)

如果在readview的右邊(比readview都大)或者就在readview中,不可以訪問,獲取roll_pointer,取上一版本重新對比(在右邊意味著,該事務在readview生成之后出現,在readview中意味著該事務還未提交)

已提交讀隔離級別下的事務在每次查詢的開始都會生成一個獨立的ReadView,而可重復讀隔離級別則在第一次讀的時候生成一個ReadView,之后的讀都復用之前的ReadView,

這就是Mysql的MVCC,通過版本鏈,實作多版本,可并發讀-寫,寫-讀,通過ReadView生成策略的不同 實作不同的隔離級別,

分表后非sharding_key的查詢怎么處理,分表后的排序

  1. 可以做一個mapping表,比如這時候商家要查詢訂單串列怎么辦呢?不帶user_id查詢的話你總不能掃全表吧?所以我們可以做一個映射關系表,保存商家和用戶的關系,查詢的時候先通過商家查 詢到用戶串列,再通過user_id去查詢,
  2. 寬表,對資料實時性要求不是很高的場景,比如查詢訂單串列,可以把訂單表同步到離線(實時) 數倉,再基于數倉去做成一張寬表,再基于其他如es提供查詢服務,
  3. 資料量不是很大的話,比如后臺的一些查詢之類的,也可以通過多執行緒掃表,然后再聚合結果的方 式來做,或者異步的形式也是可以的,

union

排序欄位是唯一索引:

首先第一頁的查詢:將各表的結果集進行合并,然后再次排序

第二頁及以后的查詢,需要傳入上一頁排序欄位的最后一個值,及排序方式,

根據排序方式,及這個值進行查詢,如排序欄位date,上一頁最后值為3,排序方式降序,查詢的時候sql為select ... from table where date < 3 order by date desc limit 0,10,這樣再將幾個表的結果合并排序即可,

mysql主從同步原理

mysql主從同步的程序:

Mysql的主從復制中主要有三個執行緒: master(binlog dump thread)、slave(I/O thread 、SQL thread) ,Master一條執行緒和Slave中的兩條執行緒,

主節點 binlog,主從復制的基礎是主庫記錄資料庫的所有變更記錄到 binlog,binlog 是資料庫服務器啟動的那一刻起,保存所有修改資料庫結構或內容的一個檔案,

主節點 log dump 執行緒,當 binlog 有變動時,log dump 執行緒讀取其內容并發送給從節點,從節點 I/O執行緒接收 binlog 內容,并將其寫入到 relay log 檔案中,

從節點的SQL 執行緒讀取 relay log 檔案內容對資料更新進行重放,最終保證主從資料庫的一致性,

注:主從節點使用 binglog 檔案 + position 偏移量來定位主從同步的位置,從節點會保存其已接收到的偏移量,如果從節點發生宕機重啟,則會自動從 position 的位置發起同步,

由于mysql默認的復制方式是異步的,主庫把日志發送給從庫后不關心從庫是否已經處理,這樣會產生 一個問題就是假設主庫掛了,從庫處理失敗了,這時候從庫升為主庫后,日志就丟失了,由此產生兩個 概念,

全同步復制

主庫寫入binlog后強制同步日志到從庫,所有的從庫都執行完成后才回傳給客戶端,但是很顯然這個方式的話性能會受到嚴重影響,

半同步復制

和全同步不同的是,半同步復制的邏輯是這樣,從庫寫入日志成功后回傳ACK確認給主庫,主庫收到至少一個從庫的確認就認為寫操作完成,

簡述MyISAM和InnoDB的區別

MyISAM

不支持事務,但是每次查詢都是原子的; 支持表級鎖,即每次操作是對整個表加鎖; 存盤表的總行數;

一個MYISAM表有三個檔案:索引檔案、表結構檔案、資料檔案;

采用非聚集索引,索引檔案的資料域存盤指向資料檔案的指標,輔索引與主索引基本一致,但是輔索引 不用保證唯一性,

InnoDb:

支持ACID的事務,支持事務的四種隔離級別; 支持行級鎖及外鍵約束:因此可以支持寫并發; 不存盤總行數;

一個InnoDb引擎存盤在一個檔案空間(共享表空間,表大小不受作業系統控制,一個表可能分布在多 個檔案里),也有可能為多個(設定為獨立表空,表大小受作業系統檔案大小限制,一般為2G),受操 作系統檔案大小的限制;

主鍵索引采用聚集索引(索引的資料域存盤資料檔案本身),輔索引的資料域存盤主鍵的值;因此從輔 索引查找資料,需要先通過輔索引找到主鍵值,再訪問輔索引;最好使用自增主鍵,防止插入資料時, 為維持B+樹結構,檔案的大調整,

簡述mysql中索引型別及對資料庫的性能的影響

普通索引:允許被索引的資料列包含重復的值,唯一索引:可以保證資料記錄的唯一性,

主鍵:是一種特殊的唯一索引,在一張表中只能定義一個主鍵索引,主鍵用于唯一標識一條記錄,使用 關鍵字 PRIMARY KEY 來創建,

聯合索引:索引可以覆寫多個資料列,如像INDEX(columnA, columnB)索引,

全文索引:通過建立倒排索引 ,可以極大的提升檢索效率,解決判斷欄位是否包含的問題,是目前搜索引擎使用的一種關鍵技術,可以通過ALTER TABLE table_name ADD FULLTEXT (column);創建全文索引

索引可以極大的提高資料的查詢速度,

通過使用索引,可以在查詢的程序中,使用優化隱藏器,提高系統的性能,

但是會降低插入、洗掉、更新表的速度,因為在執行這些寫操作時,還要操作索引檔案

索引需要占物理空間,除了資料表占資料空間之外,每一個索引還要占一定的物理空間,如果要建立聚 簇索引,那么需要的空間就會更大,如果非聚集索引很多,一旦聚集索引改變,那么所有非聚集索引都 會跟著變,

mysql執行計劃怎么看

執行計劃就是sql的執行查詢的順序,以及如何使用索引查詢,回傳的結果集的行數

EXPLAIN SELECT * from A where X=? and Y=?

1,id :是一個有順序的編號,是查詢的順序號,有幾個 select 就顯示幾行,id的順序是按 select 出現的順序增長的,id列的值越大執行優先級越高越先執行,id列的值相同則從上往下執行,id列的值為NULL最后執行,

2,selectType 表示查詢中每個select子句的型別

SIMPLE: 表示此查詢不包含 UNION 查詢或子查詢PRIMARY: 表示此查詢是最外層的查詢(包含子查詢) SUBQUERY: 子查詢中的第一個 SELECT

UNION: 表示此查詢是 UNION 的第二或隨后的查詢

DEPENDENT UNION: UNION 中的第二個或后面的查詢陳述句, 取決于外面的查詢

UNION RESULT, UNION 的結果

DEPENDENT SUBQUERY: 子查詢中的第一個 SELECT, 取決于外面的查詢. 即子查詢依賴于外層查詢的結果.

DERIVED:衍生,表示匯出表的SELECT(FROM子句的子查詢)

  1. table:表示該陳述句查詢的表
  2. type:優化sql的重要欄位,也是我們判斷sql性能和優化程度重要指標,他的取值型別范圍:

const:通過索引一次命中,匹配一行資料system: 表中只有一行記錄,相當于系統表;

eq_ref:唯一性索引掃描,對于每個索引鍵,表中只有一條記錄與之匹配ref: 非唯一性索引掃描,回傳匹配某個值的所有

range: 只檢索給定范圍的行,使用一個索引來選擇行,一般用于between、<、>;

index: 只 遍 歷 索 引 樹 ; ALL: 表示全表掃描,這個型別的查詢是性能最差的查詢之一, 那么基本就是隨著表的數量增多, 執行效率越慢,

執行效率:

ALL < index < range< ref < eq_ref < const < system,最好是避免ALL和index

  1. possible_keys:它表示Mysql在執行該sql陳述句的時候,可能用到的索引資訊,僅僅是可能,實際不一 定會用到,
  2. key:此欄位是 mysql 在當前查詢時所真正使用到的索引, 他是possible_keys的子集
  3. key_len:表示查詢優化器使用了索引的位元組數,這個欄位可以評估組合索引是否完全被使用,這也是我們優化sql時,評估索引的重要指標
  4. rows:mysql 查詢優化器根據統計資訊,估算該sql回傳結果集需要掃描讀取的行數,這個值相關重要,索引優化之后,掃描讀取的行數越多,說明索引設定不對,或者欄位傳入的型別之類的問題,說明要優化空間越大
  5. filtered:回傳結果的行占需要讀到的行(rows列的值)的百分比,就是百分比越高,說明需要查詢到資料越準確, 百分比越小,說明查詢到的資料量大,而結果集很少

extra

using filesort :表示 mysql 對結果集進行外部排序,不能通過索引順序達到排序效果,一般有

using filesort都建議優化去掉,因為這樣的查詢 cpu 資源消耗大,延時大,

using index:覆寫索引掃描,表示查詢在索引樹中就可查找所需資料,不用掃描表資料檔案,往往說明性能不錯,

using temporary:查詢有使用臨時表, 一般出現于排序, 分組和多表 join 的情況, 查詢效率不高,建議優化,

using where :sql使用了where過濾,效率較高,

RDB 和 AOF 機制

RDB:Redis DataBase

在指定的時間間隔內將記憶體中的資料集快照寫入磁盤,實際操作程序是fork一個子行程,先將資料集寫 入臨時檔案,寫入成功后,再替換之前的檔案,用二進制壓縮存盤,

優點:

1、整個Redis資料庫將只包含一個檔案 dump.rdb,方便持久化,

2、容災性好,方便備份,

3、性能最大化,fork 子行程來完成寫操作,讓主行程繼續處理命令,所以是 IO 最大化,使用單獨子行程來進行持久化,主行程不會進行任何 IO 操作,保證了 redis 的高性能

4.相對于資料集大時,比 AOF 的啟動效率更高,

缺點:

1、資料安全性低,RDB 是間隔一段時間進行持久化,如果持久化之間 redis 發生故障,會發生資料丟失,所以這種方式更適合資料要求不嚴謹的時候)

2、由于RDB是通過fork子行程來協助完成資料持久化作業的,因此,如果當資料集較大時,可能會導致整個服務器停止服務幾百毫秒,甚至是1秒鐘,

AOF:Append Only File

以日志的形式記錄服務器所處理的每一個寫、洗掉操作,查詢操作不會記錄,以文本的方式記錄,可以 打開檔案看到詳細的操作記錄

優點

1、資料安全,Redis中提供了3中同步策略,即每秒同步、每修改同步和不同步,事實上,每秒同步也是異步完成的,其效率也是非常高的,所差的是一旦系統出現宕機現象,那么這一秒鐘之內修改的資料 將會丟失,而每修改同步,我們可以將其視為同步持久化,即每次發生的資料變化都會被立即記錄到磁 盤中,,

2、通過 append 模式寫檔案,即使中途服務器宕機也不會破壞已經存在的內容,可以通過 redis- check-aof 工具解決資料一致性問題,

3、AOF 機制的 rewrite 模式,定期對AOF檔案進行重寫,以達到壓縮的目的

缺點:

1、AOF 檔案比 RDB 檔案大,且恢復速度慢,

2、資料集大的時候,比 rdb 啟動效率低,

3、運行效率沒有RDB高

AOF檔案比RDB更新頻率高,優先使用AOF還原資料,

AOF比RDB更安全也更大RDB性能比AOF好

如果兩個都配了優先加載AOF

Redis的過期鍵的洗掉策略

Redis是key-value資料庫,我們可以設定Redis中快取的key的過期時間,Redis的過期策略就是指當Redis中快取的key過期了,Redis如何處理,

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

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

(expires字典會保存所有設定了過期時間的key的過期時間資料,其中,key是指向鍵空間中的某個鍵的 指標,value是該鍵的毫秒精度的UNIX時間戳表示的過期時間,鍵空間是指該Redis集群中保存的所有鍵,)

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

Redis執行緒模型、單執行緒快的原因

Redis基于Reactor模式開發了網路事件處理器,這個處理器叫做檔案事件處理器 file event handler,這個檔案事件處理器,它是單執行緒的,所以 Redis 才叫做單執行緒的模型,它采用IO多路復用機制來同時監聽多個Socket,根據Socket上的事件型別來選擇對應的事件處理器來處理這個事件,可以實作高性能 的網路通信模型,又可以跟內部其他單執行緒的模塊進行對接,保證了 Redis 內部的執行緒模型的簡單性,

檔案事件處理器的結構包含4個部分:多個Socket、IO多路復用程式、檔案事件分派器以及事件處理器

(命令請求處理器、命令回復處理器、連接應答處理器等),

多個 Socket 可能并發的產生不同的操作,每個操作對應不同的檔案事件,但是IO多路復用程式會監聽多個 Socket,會將 Socket 放入一個佇列中排隊,每次從佇列中取出一個 Socket 給事件分派器,事件分派器把 Socket 給對應的事件處理器,

然后一個 Socket 的事件處理完之后,IO多路復用程式才會將佇列中的下一個 Socket 給事件分派器,檔案事件分派器會根據每個 Socket 當前產生的事件,來選擇對應的事件處理器來處理,

單執行緒快的原因:

  1. 純記憶體操作
  2. 核心是基于非阻塞的IO多路復用機制
  3. 單執行緒反而避免了多執行緒的頻繁背景關系切換帶來的性能問題

簡述Redis事務實作

1、事務開始

MULTI命令的執行,標識著一個事務的開始,MULTI命令會將客戶端狀態的 flags 屬性中打開

REDIS_MULTI 標識來完成的,

2、命令入隊

當一個客戶端切換到事務狀態之后,服務器會根據這個客戶端發送來的命令來執行不同的操作,如果客 戶端發送的命令為MULTIEXECWATCHDISCARD中的一個,立即執行這個命令,否則將命令放入一 個事務佇列里面,然后向客戶端回傳QUEUED 回復

如果客戶端發送的命令為 EXEC、DISCARD、WATCH、MULTI 四個命令的其中一個,那么服務器立即執行這個命令,

如果客戶端發送的是四個命令以外的其他命令,那么服務器并不立即執行這個命令,

首先檢查此命令的格式是否正確,如果不正確,服務器會在客戶端狀態(redisClient)的 flags 屬性關閉 REDIS_MULTI 標識,并且回傳錯誤資訊給客戶端,

如果正確,將這個命令放入一個事務佇列里面,然后向客戶端回傳 QUEUED 回復事務佇列是按照FIFO的方式保存入隊的命令

3、事務執行

客戶端發送 EXEC 命令,服務器執行 EXEC 命令邏輯,

如果客戶端狀態的 flags 屬性不包含 REDIS_MULTI 標識,或者包含 REDIS_DIRTY_CAS 或者

REDIS_DIRTY_EXEC 標識,那么就直接取消事務的執行,

否則客戶端處于事務狀態(flags 有 REDIS_MULTI 標識),服務器會遍歷客戶端的事務佇列,然后執行事務佇列中的所有命令,最后將回傳結果全部回傳給客戶端;

redis 不支持事務回滾機制,但是它會檢查每一個事務中的命令是否錯誤,

Redis 事務不支持檢查那些程式員自己邏輯錯誤,例如對 String 型別的資料庫鍵執行對 HashMap 型別的操作!

WATCH 命令是一個樂觀鎖,可以為 Redis 事務提供 check-and-set (CAS)行為,可以監控一個或多個鍵,一旦其中有一個鍵被修改(或洗掉),之后的事務就不會執行,監控一直持續到EXEC 命令,

MULTI命令用于開啟一個事務,它總是回傳OK,MULTI執行之后,客戶端可以繼續向服務器發送 任意多條命令,這些命令不會立即被執行,而是被放到一個佇列中,當EXEC命令被呼叫時,所有隊 列 中 的 命 令 才 會 被 執 行 , EXEC:執行所有事務塊內的命令,回傳事務塊內所有命令的回傳值,按命令執行的先后順序排 列,當操作被打斷時,回傳空值 nil ,

通過呼叫DISCARD,客戶端可以清空事務佇列,并放棄執行事務, 并且客戶端會從事務狀態中退出,

UNWATCH命令可以取消watch對所有key的監控,

redis集群方案

主從

哨兵模式:

sentinel,哨兵是 redis 集群中非常重要的一個組件,主要有以下功能:

集群監控:負責監控 redis master 和 slave 行程是否正常作業,

訊息通知:如果某個 redis 實體有故障,那么哨兵負責發送訊息作為報警通知給管理員,故障轉移:如果 master node 掛掉了,會自動轉移到 slave node 上,

配置中心:如果故障轉移發生了,通知 client 客戶端新的 master 地址,

哨兵用于實作 redis 集群的高可用,本身也是分布式的,作為一個哨兵集群去運行,互相協同作業,

故障轉移時,判斷一個 master node 是否宕機了,需要大部分的哨兵都同意才行,涉及到了分布式選舉

即使部分哨兵節點掛掉了,哨兵集群還是能正常作業的哨兵通常需要 3 個實體,來保證自己的健壯性,

哨兵 + redis 主從的部署架構,是不保證資料零丟失的,只能保證 redis 集群的高可用性,

對于哨兵 + redis 主從這種復雜的部署架構,盡量在測驗環境和生產環境,都進行充足的測驗和演練,

Redis Cluster是一種服務端Sharding技術,3.0版本開始正式提供,采用slot(槽)的概念,一共分成

16384個槽,將請求發送到任意節點,接收到請求的節點會將查詢請求發送到正確的節點上執行

方案說明

通過哈希的方式,將資料分片,每個節點均分存盤一定哈希槽(哈希值)區間的資料,默認分配了

16384 個槽位

每份資料分片會存盤在多個互為主從的多節點上

資料寫入先寫主節點,再同步到從節點(支持配置為阻塞同步) 同一分片多個節點間的資料不保持強一致性

讀取資料時,當客戶端操作的key沒有分配在該節點上時,redis會回傳轉向指令,指向正確的節點擴容時需要需要把舊節點的資料遷移一部分到新節點

redis cluster 架構下,每個 redis 要放開兩個埠號,比如一個是 6379,另外一個就是 加1w 的埠號,比如 16379,

16379 埠號是用來進行節點間通信的,也就是 cluster bus 的通信,用來進行故障檢測、配置更新、故障轉移授權,cluster bus 用了另外一種二進制的協議,gossip 協議,用于節點間進行高效的資料交換,占用更少的網路帶寬和處理時間,

優點

無中心架構,支持動態擴容,對業務透明

具備Sentinel的監控和自動Failover(故障轉移)能力

客戶端不需要連接集群所有節點,連接集群中任何一個可用節點即可高性能,客戶端直連redis服務,免去了proxy代理的損耗

缺點

運維也很復雜,資料遷移需要人工干預只能使用0號資料庫

不支持批量操作(pipeline管道操作) 分布式邏輯和存盤模塊耦合等

Redis Sharding是Redis Cluster出來之前,業界普遍使用的多Redis實體集群方法,其主要思想是采用哈希演算法將Redis資料的key進行散列,通過hash函式,特定的key會映射到特定的Redis節點上,Java redis客戶端驅動jedis,支持Redis Sharding功能,即ShardedJedis以及結合快取池的

ShardedJedisPool

優點

優勢在于非常簡單,服務端的Redis實體彼此獨立,相互無關聯,每個Redis實體像單服務器一樣運行, 非常容易線性擴展,系統的靈活性很強

缺點

由于sharding處理放到客戶端,規模進一步擴大時給運維帶來挑戰,

客戶端sharding不支持動態增刪節點,服務端Redis實體群拓撲結構有變化時,每個客戶端都需要更新調整,連接不能共享,當應用規模增大時,資源浪費制約優化

redis 主從復制的核心原理

通過執行slaveof命令或設定slaveof選項,讓一個服務器去復制另一個服務器的資料,主資料庫可以進行讀寫操作,當寫操作導致資料變化時會自動將資料同步給從資料庫,而從資料庫一般是只讀的,并接 受主資料庫同步過來的資料,一個主資料庫可以擁有多個從資料庫,而一個從資料庫只能擁有一個主數 據庫,

全量復制:

  1. 主節點通過bgsave命令fork子行程進行RDB持久化,該程序是非常消耗CPU、記憶體(頁表復制)、硬 盤IO的
  2. 主節點通過網路將RDB檔案發送給從節點,對主從節點的帶寬都會帶來很大的消耗
  3. 從節點清空老資料、載入新RDB檔案的程序是阻塞的,無法回應客戶端的命令;如果從節點執行bgrewriteaof,也會帶來額外的消耗

部分復制:

  • 復制偏移量:執行復制的雙方,主從節點,分別會維護一個復制偏移量offset
  • 復制積壓緩沖區:主節點內部維護了一個固定長度的、先進先出(FIFO)佇列 作為復制積壓緩沖區, 當主從節點offset的差距過大超過緩沖區長度時,將無法執行部分復制,只能執行全量復制,
  • 服務器運行ID(runid):每個Redis節點,都有其運行ID,運行ID由節點在啟動時自動生成,主節點 會將自己的運行ID發送給從節點,從節點會將主節點的運行ID存起來, 從節點Redis斷開重連的時候,就是根據運行ID來判斷同步的進度:

如果從節點保存的runid與主節點現在的runid相同,說明主從節點之前同步過,主節點會繼續嘗試使用部分復制(到底能不能部分復制還要看offset和復制積壓緩沖區的情況);

如果從節點保存的runid與主節點現在的runid不同,說明從節點在斷線前同步的Redis節點并不是當前的主節點,只能進行全量復制,

程序原理:

快取雪崩、快取穿透、快取擊穿

快取雪崩是指快取同一時間大面積的失效,所以,后面的請求都會落到資料庫上,造成資料庫短時間內 承受大量請求而崩掉,

解決方案:

快取資料的過期時間設定隨機,防止同一時間大量資料過期現象發生,

給每一個快取資料增加相應的快取標記,記錄快取是否失效,如果快取標記失效,則更新資料緩 存,

快取預熱互斥鎖

快取穿透是指快取和資料庫中都沒有的資料,導致所有的請求都落到資料庫上,造成資料庫短時間內承 受大量請求而崩掉,

解決方案

介面層增加校驗,如用戶鑒權校驗,id做基礎校驗,id<=0的直接攔截;

從快取取不到的資料,在資料庫中也沒有取到,這時也可以將key-value對寫為key-null,快取有 效時間可以設定短點,如30秒(設定太長會導致正常情況也沒法使用),這樣可以防止攻擊用戶 反復用同一個id暴力攻擊

采用布隆過濾器,將所有可能存在的資料哈希到一個足夠大的 bitmap 中,一個一定不存在的資料會被這個 bitmap 攔截掉,從而避免了對底層存盤系統的查詢壓力

快取擊穿是指快取中沒有但資料庫中有的資料(一般是快取時間到期),這時由于并發用戶特別多,同 時讀快取沒讀到資料,又同時去資料庫去取資料,引起資料庫壓力瞬間增大,造成過大壓力,和快取雪 崩不同的是,快取擊穿指并發查同一條資料,快取雪崩是不同資料都過期了,很多資料都查不到從而查 資料庫,

解決方案

設定熱點資料永遠不過期,加互斥鎖

分布式/微服務

CAP理論,BASE理論

Consistency (一致性):

即更新操作成功并回傳客戶端后,所有節點在同一時間的資料完全一致, 對于客戶端來說,一致性指的是并發訪問時更新過的資料如何獲取的問題,從服務端來看,則是更新如何復制分布到整個系統,以保證資料最終一致,Availability (可用性):

即服務一直可用,而且是正常回應時間,系統能夠很好的為用戶服務,不出現用戶操作失敗或者訪問超 時等用戶體驗不好的情況,

Partition Tolerance (磁區容錯性):

即分布式系統在遇到某節點或網路磁區故障的時候,仍然能夠對外提供滿足一致性和可用性的服務,分 區容錯性要求能夠使應用雖然是一個分布式系統,而看上去卻好像是在一個可以運轉正常的整體,比如 現在的分布式系統中有某一個或者幾個機器宕掉了,其他剩下的機器還能夠正常運轉滿足系統需求,對 于用戶而言并沒有什么體驗上的影響,

CP和AP:磁區容錯是必須保證的,當發生網路磁區的時候,如果要繼續服務,那么強一致性和可用性只能 2 選 1

BASE是Basically Available(基本可用)、Soft state(軟狀態)和Eventually consistent(最終一致性)

BASE理論是對CAP中一致性和可用性權衡的結果,其來源于對大規模互聯網系統分布式實踐的總結, 是基于CAP定理逐步演化而來的,BASE理論的核心思想是:即使無法做到強一致性,但每個應用都可以根據自身業務特點,采用適當的方式來使系統達到最終一致性,

基本可用:

回應時間上的損失: 正常情況下,處理用戶請求需要 0.5s 回傳結果,但是由于系統出現故障,處理用戶請求的時間變為 3 s,

系統功能上的損失:正常情況下,用戶可以使用系統的全部功能,但是由于系統訪問量突然劇增, 系統的部分非核心功能無法使用,

軟狀態:資料同步允許一定的延遲

最終一致性:系統中所有的資料副本,在經過一段時間的同步后,最終能夠達到一個一致的狀態,不要 求實時

負載均衡演算法、型別

1、輪詢法

將請求按順序輪流地分配到后端服務器上,它均衡地對待后端的每一臺服務器,而不關心服務器實際的 連接數和當前的系統負載,

2、隨機法

通過系統的隨機演算法,根據后端服務器的串列大小值來隨機選取其中的一臺服務器進行訪問,由概率統 計理論可以得知,隨著客戶端呼叫服務端的次數增多,

其實際效果越來越接近于平均分配呼叫量到后端的每一臺服務器,也就是輪詢的結果,

3、源地址哈希法

源地址哈希的思想是根據獲取客戶端的IP地址,通過哈希函式計算得到的一個數值,用該數值對服務器 串列的大小進行取模運算,得到的結果便是客服端要訪問服務器的序號,采用源地址哈希法進行負載均 衡,同一IP地址的客戶端,當后端服務器串列不變時,它每次都會映射到同一臺后端服務器進行訪問,

4、加權輪詢法

不同的后端服務器可能機器的配置和當前系統的負載并不相同,因此它們的抗壓能力也不相同,給配置 高、負載低的機器配置更高的權重,讓其處理更多的請;而配置低、負載高的機器,給其分配較低的權 重,降低其系統負載,加權輪詢能很好地處理這一問題,并將請求順序且按照權重分配到后端,

5、加權隨機法

與加權輪詢法一樣,加權隨機法也根據后端機器的配置,系統的負載分配不同的權重,不同的是,它是 按照權重隨機請求后端服務器,而非順序,

6、最小連接數法

最小連接數演算法比較靈活和智能,由于后端服務器的配置不盡相同,對于請求的處理有快有慢,它是根 據后端服務器當前的連接情況,動態地選取其中當前

積壓連接數最少的一臺服務器來處理當前的請求,盡可能地提高后端服務的利用效率,將負責合理地分 流到每一臺服務器,

型別:

DNS 方式實作負載均衡硬體負載均衡:F5 和 A10 軟體負載均衡:

Nginx 、 HAproxy 、 LVS ,其中的區別:

Nginx :七層負載均衡,支持 HTTP、E-mail 協議,同時也支持 4 層負載均衡; HAproxy :支持七層規則的,性能也很不錯,OpenStack 默認使用的負載均衡軟體就是HAproxy;

LVS :運行在內核態,性能是軟體負載均衡中最高的,嚴格來說作業在三層,所以更通用一些,

適用各種應用服務,

分布式架構下,Session 共享有什么方案

1、采用無狀態服務,拋棄session

2、存入cookie(有安全風險)

3、服務器之間進行 Session 同步,這樣可以保證每個服務器上都有全部的 Session 資訊,不過當服務器數量比較多的時候,同步是會有延遲甚至同步失敗;

4、 IP 系結策略

使用 Nginx (或其他復雜均衡軟硬體)中的 IP 系結策略,同一個 IP 只能在指定的同一個機器訪問,但是這樣做失去了負載均衡的意義,當掛掉一臺服務器的時候,會影響一批用戶的使用,風險很大;

5、使用 Redis 存盤

把 Session 放到 Redis 中存盤,雖然架構上變得復雜,并且需要多訪問一次 Redis ,但是這種方案帶來的好處也是很大的:

實作了 Session 共享;

可以水平擴展(增加 Redis 服務器);

服務器重啟 Session 不丟失(不過也要注意 Session 在 Redis 中的重繪/失效機制); 不僅可以跨服務器 Session 共享,甚至可以跨平臺(例如網頁端和 APP 端),

簡述你對RPC、RMI的理解

RPC:在本地呼叫遠程的函式,遠程程序呼叫,可以跨語言實作 httpClient

RMI:遠程方法呼叫,java中用于實作RPC的一種機制,RPC的java版本,是J2EE的網路呼叫機制,跨JVM呼叫物件的方法,面向物件的思維方式

直接或間接實作介面 java.rmi.Remote 成為存在于服務器端的遠程物件,供客戶端訪問并提供一定的服務

遠程物件必須實作
java.rmi.server.UniCastRemoteObject類,這樣才能保證客戶端訪問獲得遠程物件 時,該遠程物件將會把自身的一個拷貝以Socket的形式傳輸給客戶端,此時客戶端所獲得的這個拷貝稱為“存根”,而服務器端本身已存在的遠程物件則稱之為“骨架”,其實此時的存根是客戶端的一個代理, 用于與服務器端的通信,而骨架也可認為是服務器端的一個代理,用于接收客戶端的請求之后呼叫遠程 方法來回應客戶端的請求,

public interface IService extends Remote {
String service(String content) throws RemoteException;
}
public class ServiceImpl extends UnicastRemoteObject implements IService {
private String name;
public ServiceImpl(String name) throws RemoteException { this.name = name;
}
@Override
public String service(String content) { return "server >> " + content;
}
}
public class Server {
public static void main(String[] args) { try {
IService service02 = new ServiceImpl("service02"); Context namingContext = new InitialContext();
namingContext.rebind("rmi://127.0.0.1/service02", service02);
} catch (Exception e) { e.printStackTrace();
}
System.out.println("000000!");
}
}
public class Client {
public static void main(String[] args) { String url = "rmi://127.0.0.1/"; try {
Context namingContext = new InitialContext();
IService service02 = (IService) namingContext.lookup(url + "service02");
Class stubClass = service02.getClass(); System.out.println(service02 + " is " + stubClass.getName());
//com.sun.proxy.$Proxy0
Class[] interfaces = stubClass.getInterfaces(); for (Class c : interfaces) {
System.out.println("implement" + c.getName() + " interface");
}
System.out.println(service02.service("hello"));
} catch (Exception e) { e.printStackTrace();
}
}
}

分布式id生成方案

uuid

時鐘序列, 計數器

全域唯一的IEEE機器識別號,如果有網卡,從網卡MAC地址獲得,沒有網卡以其他方式獲得,

優點:代碼簡單,性能好(本地生成,沒有網路消耗),保證唯一(相對而言,重復概率極低可以忽 略)

缺點:

每次生成的ID都是無序的,而且不是全數字,且無法保證趨勢遞增,

UUID生成的是字串,字串存盤性能差,查詢效率慢,寫的時候由于不能產生順序的append 操作,需要進 行insert操作,導致頻繁的頁分裂,這種操作在記錄占用空間比較大的情況下,性 能下降比較大,還會增加讀 取磁盤次數

UUID長度過長,不適用于存盤,耗費資料庫性能,ID無一定業務含義,可讀性差,

有資訊安全問題,有可能泄露mac地址

時間戳

當前日期和時間

資料庫自增序列

 

單機模式:

優點:

實作簡單,依靠資料庫即可,成本小, ID數字化,單調自增,滿足資料庫存盤和查詢性能,具有一定的業務可讀性,(結合業務code)

缺點:

強依賴DB,存在單點問題,如果資料庫宕機,則業務不可用,

DB生成ID性能有限,單點資料庫壓力大,無法扛高并發場景,

資訊安全問題,比如暴露訂單量,url查詢改一下id查到別人的訂單

資料庫高可用:多主模式做負載,基于序列的起始值和步長設定,不同的初始值,相同的步長,步長大 于節點數

優點:

解決了ID生成的單點問題,同時平衡了負載,缺點:

系統擴容困難:系統定義好步長之后,增加機器之后調整步長困難, 資料庫壓力大:每次獲取一個ID都必須讀寫一次資料庫,

主從同步的時候:電商下單->支付insert master db select資料 ,因為資料同步延遲導致查不到這個數 據,加cache(不是最好的解決方式)資料要求比較嚴謹的話查master主庫,

Leaf-segment

采用每次獲取一個ID區間段的方式來解決,區間段用完之后再去資料庫獲取新的號段,這樣一來可以大 大減輕資料庫的壓力

核心欄位:biz_tag,max_id,step

biz_tag用來區分業務,max_id表示該biz_tag目前所被分配的ID號段的最大值,step表示每次分配 的號段長度,原來每次獲取ID都要訪問資料庫,現在只需要把Step設定的足夠合理如1000,那么現在 可以在1000個ID用完之后再去訪問資料庫

優點:

擴張靈活,性能強能夠撐起大部分業務場景,

ID號碼是趨勢遞增的,滿足資料庫存盤和查詢性能要求,

可用性高,即使ID生成服務器不可用,也能夠使得業務在短時間內可用,為排查問題爭取時間, 缺點:

可能存在多個節點同時請求ID區間的情況,依賴DB

雙buffer:將獲取一個號段的方式優化成獲取兩個號段,在一個號段用完之后不用立馬去更新號段,還 有一個快取號段備用,這樣能夠有效解決這種沖突問題,而且采用雙buffer的方式,在當前號段消耗了

10%的時候就去檢查下一個號段有沒有準備好,如果沒有準備好就去更新下一個號段,當當前號段用完了 就切換到下一個已經快取好的號段去使用,同時在下一個號段消耗到10%的時候,又去檢測下一個號段有 沒有準備好,如此往復,

優點:

基于JVM存盤雙buffer的號段,減少了資料庫查詢,減少了網路依賴,效率更高,

缺點:

segment號段長度是固定的,業務量大時可能會頻繁更新號段,因為原本分配的號段會一下用完 如果號段長度設定的過長,但凡快取中有號段沒有消耗完,其他節點重新獲取的號段與之前相比可

能跨度會很 大,動態調整Step

基于redis、mongodb、zk等中間件生成雪花演算法

生成一個64bit的整性數字

第一位符號位固定為0,41位時間戳,10位workId,12位序列號 位數可以有不同實作

優點:

每個毫秒值包含的ID值很多,不夠可以變動位數來增加,性能佳(依賴workId的實作), 時間戳值在高位,中間是固定的機器碼,自增的序列在低位,整個ID是趨勢遞增的,

能夠根據業務場景資料庫節點布置靈活挑戰bit位劃分,靈活度高, 缺點:

強依賴于機器時鐘,如果時鐘回撥,會導致重復的ID生成,所以一般基于此的演算法發現時鐘回撥, 都會拋例外處 理,阻止ID生成,這可能導致服務不可用,

分布式鎖解決方案

需要這個鎖獨立于每一個服務之外,而不是在服務里面,

資料庫:利用主鍵沖突控制一次只有一個執行緒能獲取鎖,非阻塞、不可重入、單點、失效時間

Zookeeper分布式鎖:

zk通過臨時節點,解決了死鎖的問題,一旦客戶端獲取到鎖之后突然掛掉(Session連接斷開),那么這個臨 時節點就會自動洗掉掉,其他客戶端自動獲取鎖,臨時順序節點解決驚群效應

Redis分布式鎖:setNX,單執行緒處理網路請求,不需要考慮并發安全性 所有服務節點設定相同的key,回傳為0、則鎖獲取失敗

setnx

問題:

1、早期版本沒有超時引數,需要單獨設定,存在死鎖問題(中途宕機)

2、后期版本提供加鎖與設定時間原子操作,但是存在任務超時,鎖自動釋放,導致并發問題,加鎖與釋 放鎖不是同一執行緒問題

洗掉鎖:判斷執行緒唯一標志,再洗掉

可重入性及鎖續期沒有實作,通過redisson解決(類似AQS的實作,看門狗監聽機制)

redlock:意思的機制都只操作單節點、即使Redis通過sentinel保證高可用,如果這個master節點由于 某些原因發生了主從切換,那么就會出現鎖丟失的情況(redis同步設定可能資料丟失),redlock從多個節點申請鎖,當一半以上節點獲取成功、鎖才算獲取成功,redission有相應的實作

分布式事務解決方案

XA規范:分布式事務規范,定義了分布式事務模型

四個角色:事務管理器(協調者TM)、資源管理器(參與者RM),應用程式AP,通信資源管理器CRM 全域事務:一個橫跨多個資料庫的事務,要么全部提交、要么全部回滾

JTA事務時java對XA規范的實作,對應JDBC的單庫事務

兩階段協議:

第一階段( prepare ) :每個參與者執行本地事務但不提交,進入 ready 狀態,并通知協調者已經準備就緒,

第二階段( commit ) 當協調者確認每個參與者都 ready 后,通知參與者進行 commit 操作;如果有參與者 fail ,則發送 rollback 命令,各參與者做回滾,

問題:

單點故障:一旦事務管理器出現故障,整個系統不可用(參與者都會阻塞住)

資料不一致:在階段二,如果事務管理器只發送了部分 commit 訊息,此時網路發生例外,那么只有部分參與者接收到 commit 訊息,也就是說只有部分參與者提交了事務,使得系統資料不一致,

回應時間較長:參與者和協調者資源都被鎖住,提交或者回滾之后才能釋放

不確定性:當協事務管理器發送 commit 之后,并且此時只有一個參與者收到了 commit,那么當該參與者與事務管理器同時宕機之后,重新選舉的事務管理器無法確定該條訊息是否提交成功,

 

三階段協議:主要是針對兩階段的優化,解決了2PC單點故障的問題,但是性能問題和不一致問題仍然沒有根本解決

引入了超時機制解決參與者阻塞的問題,超時后本地提交,2pc只有協調者有超時機制

第一階段:CanCommit階段,協調者詢問事務參與者,是否有能力完成此次事務,如果都回傳yes,則進入第二階段有一個回傳no或等待回應超時,則中斷事務,并向所有參與者發送abort請求

第二階段:PreCommit階段,此時協調者會向所有的參與者發送PreCommit請求,參與者收到后 開始執行事務操作,參與者執行完事務操作后(此時屬于未提交事務的狀態),就會向協調者反饋“Ack”表示我已經準備好提交了,并等待協調者的下一步指令,

第三階段:DoCommit階段, 在階段二中如果所有的參與者節點都回傳了Ack,那么協調者就會從

“預提交狀態”轉變為“提交狀態”,然后向所有的參與者節點發送"doCommit"請求,參與者節點在 收到提交請求后就會各自執行事務提交操作,并向協調者節點反饋“Ack”訊息,協調者收到所有參與者的Ack訊息后完成事務, 相反,如果有一個參與者節點未完成PreCommit的反饋或者反饋超時,那么協調者都會向所有的參與者節點發送abort請求,從而中斷事務,

TCC(補償事務):Try、Confirm、Cancel

針對每個操作,都要注冊一個與其對應的確認和補償(撤銷)操作

Try操作做業務檢查及資源預留,Confirm做業務確認操作,Cancel實作一個與Try相反的操作既回滾操 作,TM首先發起所有的分支事務的try操作,任何一個分支事務的try操作執行失敗,TM將會發起所有分支事務的Cancel操作,若try操作全部成功,TM將會發起所有分支事務的Confirm操作,其中

Confirm/Cancel操作若執行失敗,TM會進行重試,

TCC模型對業務的侵入性較強,改造的難度較大,每個操作都需要有 try 、 confirm 、 cancel 三個介面實作

confirm 和 cancel 介面還必須實作冪等性,

訊息佇列的事務訊息:

發送prepare訊息到訊息中間件發送成功后,執行本地事務

如果事務執行成功,則commit,訊息中間件將訊息下發至消費端(commit前,訊息不會被消費)

如果事務執行失敗,則回滾,訊息中間件將這條prepare訊息洗掉

消費端接收到訊息進行消費,如果消費失敗,則不斷重試

如何實作介面的冪等性

唯一id,每次操作,都根據操作和內容生成唯一的id,在執行之前先判斷id是否存在,如果不存在則執行后續操作,并且保存到資料庫或者redis等,

服務端提供發送token的介面,業務呼叫介面前先獲取token,然后呼叫業務介面請求時,把token 攜帶過去,務器判斷token是否存在redis中,存在表示第一次請求,可以繼續執行業務,執行業務完成后,最后需要把redis中的token洗掉

建去重表,將業務中有唯一標識的欄位保存到去重表,如果表中存在,則表示已經處理過了 版本控制,增加版本號,當版本號符合時,才能更新資料

狀態控制,例如訂單有狀態已支付 未支付 支付中 支付失敗,當處于未支付的時候才允許修改為支付中等

簡述ZAB 協議

ZAB 協議是為分布式協調服務 Zookeeper 專門設計的一種支持崩潰恢復的原子廣播協議,實作分布式資料一致性

所有客戶端的請求都是寫入到 Leader 行程中,然后,由 Leader 同步到其他節點,稱為 Follower,在集群資料同步的程序中,如果出現 Follower 節點崩潰或者 Leader 行程崩潰時,都會通過 Zab 協議來保證資料一致性

ZAB 協議包括兩種基本的模式:

崩潰恢復和訊息廣播

訊息廣播:

集群中所有的事務請求都由 Leader 節點來處理,其他服務器為 Follower,Leader 將客戶端的事務請求轉換為事務 Proposal,并且將 Proposal 分發給集群中其他所有的 Follower,

完成廣播之后,Leader 等待 Follwer 反饋,當有過半數的 Follower 反饋資訊后,Leader 將再次向集群內 Follower 廣播 Commit 資訊,Commit 資訊就是確認將之前的 Proposal 提交,

Leader 節點的寫入是一個兩步操作,第一步是廣播事務操作,第二步是廣播提交操作,其中過半數指的是反饋的節點數 >=N/2+1,N 是全部的 Follower 節點數量,

崩潰恢復:

初始化集群,剛剛啟動的時候

Leader 崩潰,因為故障宕機

Leader 失去了半數的機器支持,與集群中超過一半的節點斷連

此時開啟新一輪 Leader 選舉,選舉產生的 Leader 會與過半的 Follower 進行同步,使資料一致,當與過半的機器同步完成后,就退出恢復模式, 然后進入訊息廣播模式

整個 ZooKeeper 集群的一致性保證就是在上面兩個狀態之前切換,當 Leader 服務正常時,就是正常的訊息廣播模式;當 Leader 不可用時,則進入崩潰恢復模式,崩潰恢復階段會進行資料同步,完成以后,重新進入訊息廣播階段,

Zxid 是 Zab 協議的一個事務編號,Zxid 是一個 64 位的數字,其中低 32 位是一個簡單的單調遞增計數器,針對客戶端每一個事務請求,計數器加 1;而高 32 位則代表 Leader 周期年代的編號,

Leader 周期( epoch),可以理解為當前集群所處的年代或者周期,每當有一個新的 Leader 選舉出現時,就會從這個 Leader 服務器上取出其本地日志中最大事務的 Zxid,并從中讀取 epoch 值,然后加 1,以此作為新的周期 ID,高 32 位代表了每代 Leader 的唯一性,低 32 位則代表了每代 Leader 中事務的唯一性,

zab節點的三種狀態:

following:服從leader的命令leading:負責協調事務election/looking:選舉狀態

zk的資料模型和節點型別

資料模型:樹形結構

zk維護的資料主要有:客戶端的會話(session)狀態及資料節點(dataNode)資訊,

zk在記憶體中構造了個DataTree的資料結構,維護著path到dataNode的映射以及dataNode間的樹狀層 級關系,為了提高讀取性能,集群中每個服務節點都是將資料全量存盤在記憶體中,所以,zk最適于讀多 寫少且輕量級資料的應用場景,

資料僅存盤在記憶體是很不安全的,zk采用事務日志檔案及快照檔案的方案來落盤資料,保障資料在不丟 失的情況下能快速恢復,

樹中的每個節點被稱為— Znode

Znode 兼具檔案和目錄兩種特點,可以做路徑標識,也可以存盤資料,并可以具有子 Znode,具有增、刪、改、查等操作,

Znode 具有原子性操作,讀操作將獲取與節點相關的所有資料,寫操作也將 替換掉節點的所有資料,另外,每一個節點都擁有自己的 ACL(訪問控制列 表),這個串列規定了用戶的權限,即限定了特定用戶對目標節點可以執行的操作

Znode 存盤資料大小有限制,每個 Znode 的資料大小至多 1M,常規使用中應該遠小于此值,

Znode 通過路徑參考,如同 Unix 中的檔案路徑,路徑必須是絕對的,因此他們必須由斜杠字符來開頭,除此以外,他們必須是唯一的,也就是說每一個路徑只有一個表示,因此這些路徑不能改變,在

ZooKeeper 中,路徑由 Unicode 字串組成,并且有一些限制,字串"/zookeeper"用以保存管理資訊,比如關鍵配額資訊,

持久節點:一旦創建、該資料節點會一直存盤在zk服務器上、即使創建該節點的客戶端與服務端的會話 關閉了、該節點也不會被洗掉

臨時節點:當創建該節點的客戶端會話因超時或發生例外而關閉時、該節點也相應的在zk上被洗掉 ,

有序節點:不是一種單獨種類的節點、而是在持久節點和臨時節點的基礎上、增加了一個節點有序的性 質 ,

簡述zk的命名服務、配置管理、集群管理

命名服務:

通過指定的名字來獲取資源或者服務地址,Zookeeper可以創建一個全域唯一的路徑,這個路徑就可以作為一個名字,被命名的物體可以是集群中的機器,服務的地址,或者是遠程的物件等,一些分布式服 務框架(RPC、RMI)中的服務地址串列,通過使用命名服務,客戶端應用能夠根據特定的名字來獲取資源的物體、服務地址和提供者資訊等

配置管理:

實際專案開發中,經常使用.properties或者xml需要配置很多資訊,如資料庫連接資訊、fps地址埠等 等,程式分布式部署時,如果把程式的這些配置資訊保存在zk的znode節點下,當你要修改配置,即

znode會發生變化時,可以通過改變zk中某個目錄節點的內容,利用watcher通知給各個客戶端,從而 更改配置,

集群管理:

集群管理包括集群監控和集群控制,就是監控集群機器狀態,剔除機器和加入機器,zookeeper可以方便集群機器的管理,它可以實時監控znode節點的變化,一旦發現有機器掛了,該機器就會與zk斷開連接,對應的臨時目錄節點會被洗掉,其他所有機器都收到通知,新機器加入也是類似,

講下Zookeeper watch機制

客戶端,可以通過在znode上設定watch,實作實時監聽znode的變化

Watch事件是一個一次性的觸發器,當被設定了Watch的資料發生了改變的時候,則服務器將這個改變發送給設定了Watch的客戶端

父節點的創建,修改,洗掉都會觸發Watcher事件,子節點的創建,洗掉會觸發Watcher事件,

一次性:一旦被觸發就會移除,再次使用需要重新注冊,因為每次變動都需要通知所有客戶端,一次性 可以減輕壓力,3.6.0默認持久遞回,可以觸發多次

輕量:只通知發生了事件,不會告知事件內容,減輕服務器和帶寬壓力

Watcher 機制包括三個角色:客戶端執行緒、客戶端的 WatchManager 以及 ZooKeeper 服務器

  1. 客戶端向 ZooKeeper 服務器注冊一個 Watcher 監聽,
  2. 把這個監聽資訊存盤到客戶端的 WatchManager 中
  3. 當 ZooKeeper 中的節點發生變化時,會通知客戶端,客戶端會呼叫相應 Watcher 物件中的回呼方法,watch回呼是串行同步的

zk和eureka的區別

zk:CP設計(強一致性),目標是一個分布式的協調系統,用于進行資源的統一管理,當節點crash后,需要進行leader的選舉,在這個期間內,zk服務是不可用的,

eureka:AP設計(高可用),目標是一個服務注冊發現系統,專門用于微服務的服務發現注冊,

Eureka各個節點都是平等的,幾個節點掛掉不會影響正常節點的作業,剩余的節點依然可以提供注冊和查詢服務,而Eureka的客戶端在向某個Eureka注冊時如果發現連接失敗,會自動切換至其他節點,只要有一臺Eureka還在,就能保證注冊服務可用(保證可用性),只不過查到的資訊可能不是最新的(不保證強一致性)

同時當eureka的服務端發現85%以上的服務都沒有心跳的話,它就會認為自己的網路出了問題,就不會從服務串列中洗掉這些失去心跳的服務,同時eureka的客戶端也會快取服務資訊,eureka對于服務注冊發現來說是非常好的選擇,

Spring Cloud和Dubbo的區別

底層協議:springcloud基于http協議,dubbo基于Tcp協議,決定了dubbo的性能相對會比較好 注冊中心:Spring Cloud 使用的 eureka ,dubbo推薦使用zookeeper

模型定義:dubbo 將一個介面定義為一個服務,SpringCloud 則是將一個應用定義為一個服務

SpringCloud是一個生態,而Dubbo是SpringCloud生態中關于服務呼叫一種解決方案(服務治理)

什么是Hystrix?簡述實作機制

分布式容錯框架

阻止故障的連鎖反應,實作熔斷快速失敗,實作優雅降級

提供實時的監控和告警

資源隔離:執行緒隔離,信號量隔離

執行緒隔離:Hystrix會給每一個Command分配一個單獨的執行緒池,這樣在進行單個服務呼叫的時 候,就可以在獨立的執行緒池里面進行,而不會對其他執行緒池造成影響

信號量隔離:客戶端需向依賴服務發起請求時,首先要獲取一個信號量才能真正發起呼叫,由于信 號量的數量有限,當并發請求量超過信號量個數時,后續的請求都會直接拒絕,進入fallback流程,信號量隔離主要是通過控制并發請求量,防止請求執行緒大面積阻塞,從而達到限流和防止雪崩 的目的,

熔斷和降級:呼叫服務失敗后快速失敗

熔斷:為了防止例外不擴散,保證系統的穩定性

降級:撰寫好呼叫失敗的補救邏輯,然后對服務直接停止運行,這樣這些介面就無法正常呼叫,但又不 至于直接報錯,只是服務水平下降

通過HystrixCommand 或者HystrixObservableCommand 將所有的外部系統(或者稱為依賴) 包裝起來,整個包裝物件是單獨運行在一個執行緒之中(這是典型的命令模式),

超時請求應該超過你定義的閾值

為每個依賴關系維護一個小的執行緒池(或信號量); 如果它變滿了,那么依賴關系的請求將立即被拒絕,而不是排隊等待,

統計成功,失敗(由客戶端拋出的例外),超時和執行緒拒絕,

打開斷路器可以在一段時間內停止對特定服務的所有請求,如果服務的錯誤百分比通過閾值,手動 或自動的關閉斷路器,

當請求被拒絕、連接超時或者斷路器打開,直接執行fallback邏輯,近乎實時監控指標和配置變化,

springcloud核心組件及其作用

Eureka:服務注冊與發現

注冊:每個服務都向Eureka登記自己提供服務的元資料,包括服務的ip地址、埠號、版本號、通信協議等,eureka將各個服務維護在了一個服務清單中(雙層Map,第一層key是服務名,第二層key是實體名,value是服務地址加埠),同時對服務維持心跳,剔除不可用的服務,eureka集群各節點相互注冊每個實體中都有一樣的服務清單,

發現:eureka注冊的服務之間呼叫不需要指定服務地址,而是通過服務名向注冊中心咨詢,并獲取所有服務實體清單(快取到本地),然后實作服務的請求訪問,

Ribbon:服務間發起請求的時候,基于Ribbon做負載均衡,從?個服務的多臺機器中選擇?臺 (被呼叫方的服務地址有多個),Ribbon也是通過發起http請求,來進行的呼叫,只不過是通過呼叫服務名的地址來實作的,雖然說Ribbon不用去具體請求服務實體的ip地址或域名了,但是每呼叫一個介面都還要手動去發起Http請求

@RestController
public class ConsumerController { @Autowired
RestTemplate restTemplate; @GetMapping("/ribbon-consumer") public String helloConsumer(){
return restTemplate.getForEntity("http://exampleservice/index",String.class).getBody();
}
}

Feign:基于Feign的動態代理機制,根據注解和選擇的機器,拼接請求URL地址,發起請求 ,簡化服務間的呼叫,在Ribbon的基礎上進行了進一步的封裝,單獨抽出了一個組件,就是Spring Cloud Feign,在引入Spring Cloud Feign后,我們只需要創建一個介面并用注解的方式來配置它,即可完成對服務提供方的介面系結,

呼叫遠程就像呼叫本地服務一樣

@RestController
public class UserController { @GetMapping("/getUser") public String getUser(){
List<String> list = new ArrayList<>();
list.add("張三");
String json = JSON.toJSONString(list); return json;
}
}
@FeignClient(name = "user") public interface UserClient {
@GetMapping("/getUser") String getUser();
}
@RestController
public class TestController { @Resource
UserClient userClient;
@RequestMapping("/test") public String test(){
String user = userClient.getUser(); return user;
}
}

Hystrix:發起請求是通過Hystrix的執行緒池來?的,不同的服務?不同的執行緒池,實作了不同服務調? 的隔離,通過統計介面超時次數回傳默認值,實作服務熔斷和降級

Zuul:如果前端、移動端要調?后端系統,統?從Zuul?關進?,由Zuul?關轉發請求給對應的服務, 通過與Eureka進行整合,將自身注冊為Eureka下的應用,從Eureka下獲取所有服務的實體,來進行服 務的路由,Zuul還提供了一套過濾器機制,開發者可以自己指定哪些規則的請求需要執行校驗邏輯,只有通過校驗邏輯的請求才會被路由到具體服務實體上,否則回傳錯誤提示,

Dubbo 的整體架構設計及分層

五個角色:

注冊中心registry:服務注冊與發現服務提供者provider:暴露服務

服務消費者consumer:呼叫遠程服務

監控中心monitor:統計服務的呼叫次數和呼叫時間容器container:服務允許容器

呼叫流程:

1:container容器負責啟動、加載、運行provider

2:provider在啟動時,向regisitry中心注冊自己提供的服務3:consumer在啟動時,向regisitry中心訂閱自己所需的服務

3:regisitry回傳服務提供者串列給consumer,如果有變更,registry將基于長連接推送變更資料給

consumer

4:consumer呼叫provider服務,基于負載均衡演算法進行呼叫

5:consumer呼叫provider的統計,基于短鏈接定時每分鐘一次統計到monitor

分層:

介面服務層( Service):面向開發者,業務代碼、介面、實作等

配置層( Config):對外配置介面,以 ServiceConfig 和 ReferenceConfig 為中心

服務代理層( Proxy):對生產者和消費者、dubbo都會產生一個代理類封裝呼叫細節,業務層對遠程呼叫無感

服務注冊層( Registry) : 封裝服務地址的注冊和發現, 以服務 URL 為中心路由層( Cluster) : 封裝多個提供者的路由和負載均衡, 并橋接注冊中心監控層( Monitor) : RPC 呼叫次數和呼叫時間監控

遠程呼叫層( Protocal):封裝 RPC 呼叫

資訊交換層( Exchange): 封裝請求回應模式, 同步轉異步

網路傳輸層( Transport):抽象 mina 和 netty 為統一介面,統一網路傳輸介面資料序列化層( Serialize) : 資料傳輸的序列化和反序列化

簡述RabbitMQ的架構設計

Broker:rabbitmq的服務節點

Queue:佇列,是RabbitMQ的內部物件,用于存盤訊息,RabbitMQ中訊息只能存盤在佇列中,生產者投遞訊息到佇列,消費者從佇列中獲取訊息并消費,多個消費者可以訂閱同一個佇列,這時佇列中的 訊息會被平均分攤(輪詢)給多個消費者進行消費,而不是每個消費者都收到所有的訊息進行消費,(注 意:RabbitMQ不支持佇列層面的廣播消費,如果需要廣播消費,可以采用一個交換器通過路由Key系結多個佇列,由多個消費者來訂閱這些佇列的方式,

Exchange:交換器,生產者將訊息發送到Exchange,由交換器將訊息路由到一個或多個佇列中,如果路由不到,或回傳給生產者,或直接丟棄,或做其它處理,

RoutingKey:路由Key,生產者將訊息發送給交換器的時候,一般會指定一個RoutingKey,用來指定這個訊息的路由規則,這個路由Key需要與交換器型別和系結鍵(BindingKey)聯合使用才能最終生效,在交換器型別和系結鍵固定的情況下,生產者可以在發送訊息給交換器時通過指定RoutingKey來決定訊息流向哪里,

Binding:通過系結將交換器和佇列關聯起來,在系結的時候一般會指定一個系結鍵,這樣RabbitMQ

就可以指定如何正確的路由到佇列了,

交換器和佇列實際上是多對多關系,就像關系資料庫中的兩張表,他們通過BindingKey做關聯(多對多關系表),在投遞訊息時,可以通過Exchange和RoutingKey(對應BindingKey)就可以找到相對應的佇列,

信道:信道是建立在Connection 之上的虛擬連接,當應用程式與Rabbit Broker建立TCP連接的時候, 客戶端緊接著可以創建一個AMQP 信道(Channel) ,每個信道都會被指派一個唯一的D,RabbitMQ 處理的每條AMQP 指令都是通過信道完成的,信道就像電纜里的光纖束,一條電纜內含有許多光纖束,允許所有的連接通過多條光線束進行傳輸和接收,

RabbitMQ如何確保訊息發送 ? 訊息接收?

發送方確認機制:

信道需要設定為 confirm 模式,則所有在信道上發布的訊息都會分配一個唯一 ID,

一旦訊息被投遞到queue(可持久化的訊息需要寫入磁盤),信道會發送一個確認給生產者(包含訊息唯一ID),

如果 RabbitMQ 發生內部錯誤從而導致訊息丟失,會發送一條 nack(未確認)訊息給生產者,

所有被發送的訊息都將被 confirm(即 ack) 或者被nack一次,但是沒有對訊息被 confirm 的快慢做任何保證,并且同一條訊息不會既被 confirm又被nack

發送方確認模式是異步的,生產者應用程式在等待確認的同時,可以繼續發送訊息,當確認訊息到達生產者, 生產者的回呼方***被觸發,

ConfirmCallback介面:只確認是否正確到達 Exchange 中,成功到達則回呼

ReturnCallback介面:訊息失敗回傳時回呼

接收方確認機制:

消費者在宣告佇列時,可以指定noAck引數,當noAck=false時,RabbitMQ會等待消費者顯式發回ack信號 后才從記憶體(或者磁盤,持久化訊息)中移去訊息,否則,訊息被消費后會被立即洗掉,

消費者接收每一條訊息后都必須進行確認(訊息接收和訊息確認是兩個不同操作),只有消費者確認了訊息,

RabbitMQ 才能安全地把訊息從佇列中洗掉,

RabbitMQ不會為未ack的訊息設定超時時間,它判斷此訊息是否需要重新投遞給消費者的唯一依據是消費該 訊息的消費者連接是否已經斷開,這么設計的原因是RabbitMQ允許消費者消費一條訊息的時間可以很長,保 證資料的最終一致性;

如果消費者回傳ack之前斷開了鏈接,RabbitMQ 會重新分發給下一個訂閱的消費者,(可能存在訊息重復消費的隱患,需要去重)

RabbitMQ事務訊息

通過對信道的設定實作

  1. channel.txSelect();通知服務器開啟事務模式;服務端會回傳Tx.Select-Ok
  2. channel.basicPublish;發送訊息,可以是多條,可以是消費訊息提交ack
  3. channel.txCommit()提交事務;
  4. channel.txRollback()回滾事務;

消費者使用事務:

  1. autoAck=false,手動提交ack,以事務提交或回滾為準;
  2. autoAck=true,不支持事務的,也就是說你即使在收到訊息之后在回滾事務也是于事無補的,佇列已經把訊息移除了

如果其中任意一個環節出現問題,就會拋出IoException例外,用戶可以攔截例外進行事務回滾,或決定要不要重復訊息,

事務訊息會降低rabbitmq的性能

RabbitMQ死信佇列、延時佇列

  1. 訊息被消費方否定確認,使用 channel.basicNack 或 channel.basicReject ,并且此時requeue 屬性被設定為false ,
  2. 訊息在佇列的存活時間超過設定的TTL時間,
  3. 訊息佇列的訊息數量已經超過最大佇列長度,

那么該訊息將成為“死信”,“死信”訊息會被RabbitMQ進行特殊處理,如果配置了死信佇列資訊,那么該訊息將會被丟進死信佇列中,如果沒有配置,則該訊息將會被丟棄

為每個需要使用死信的業務佇列配置一個死信交換機,這里同一個專案的死信交換機可以共用一個,然 后為每個業務佇列分配一個單獨的路由key,死信佇列只不過是系結在死信交換機上的佇列,死信交換 機也不是什么特殊的交換機,只不過是用來接受死信的交換機,所以可以為任何型別【Direct、Fanout、Topic】

TTL:一條訊息或者該佇列中的所有訊息的最大存活時間

如果一條訊息設定了TTL屬性或者進入了設定TTL屬性的佇列,那么這條訊息如果在TTL設定的時間內沒 有被消費,則會成為“死信”,如果同時配置了佇列的TTL和訊息的TTL,那么較小的那個值將會被使用,

只需要消費者一直消費死信佇列里的訊息

RabbitMQ鏡像佇列機制

鏡像queue有master節點和slave節點,master和slave是針對一個queue而言的,而不是一個node作為所有queue的master,其它node作為slave,一個queue第一次創建的node為它的master節點,其它node為slave節點,

無論客戶端的請求打到master還是slave最終資料都是從master節點獲取,當請求打到master節點時,

master節點直接將訊息回傳給client,同時master節點會通過GM(Guaranteed Multicast)協議將queue的最新狀態廣播到slave節點,GM保證了廣播訊息的原子性,即要么都更新要么都不更新,

當請求打到slave節點時,slave節點需要將請求先重定向到master節點,master節點將將訊息回傳給

client,同時master節點會通過GM協議將queue的最新狀態廣播到slave節點,

如果有新節點加入,RabbitMQ不會同步之前的歷史資料,新節點只會復制該節點加入到集群之后新增的訊息,

簡述kafka架構設計

Consumer Group:消費者組,消費者組內每個消費者負責消費不同磁區的資料,提高消費能力,邏輯上的一個訂閱者,

Topic:可以理解為一個佇列,Topic 將訊息分類,生產者和消費者面向的是同一個 Topic,

Partition:為了實作擴展性,提高并發能力,一個Topic 以多個Partition的方式分布到多個 Broker上,每個 Partition 是一個 有序的佇列,一個 Topic 的每個Partition都有若干個副本(Replica),一個Leader 和若干個 Follower,生產者發送資料的物件,以及消費者消費資料的物件,都是 Leader,Follower負責實時從 Leader 中同步資料,保持和 Leader 資料的同步,Leader 發生故障時,某個Follower 還會成為新的 Leader,

Offset:消費者消費的位置資訊,監控資料消費到什么位置,當消費者掛掉再重新恢復的時候,可以從 消費位置繼續消費,

ZookeeperKafka 集群能夠正常作業,需要依賴于 Zookeeper,Zookeeper 幫助 Kafka 存盤和管理集群資訊,

kafka怎么處理訊息順序、重復發送、重復消費、訊息丟失

Kafka在什么情況下會出現訊息丟失及解決方案?

  1. 訊息發送
  2. 消費

【1】ack=0,不重試

producer發送訊息完,不管結果了,如果發送失敗也就丟失了,

【2】ack=1,leader crash

producer發送訊息完,只等待lead寫入成功就回傳了,leader crash了,這時follower沒來及同步,訊息丟失,

【3】unclean.leader.election.enable 配置true

允許選舉ISR以外的副本作為leader,會導致資料丟失,默認為false,producer發送異步訊息完,只等待lead寫入成功就回傳了,leader crash了,這時ISR中沒有follower,leader從OSR中選舉,因為OSR中本來落后于Leader造成訊息丟失,

解決方案:

1、配置:ack=all / -1,tries > 1,unclean.leader.election.enable : false

producer發送訊息完,等待follower同步完再回傳,如果例外則重試,副本的數量可能影響吞吐量, 不允許選舉ISR以外的副本作為leader,

2、配置:min.insync.replicas > 1

副本指定必須確認寫操作成功的最小副本數量,如果不能滿足這個最小值,則生產者將引發一個例外(要么是NotEnoughReplicas,要么是NotEnoughReplicasAfterAppend),

min.insync.replicas和ack更大的持久性保證,確保如果大多數副本沒有收到寫操作,則生產者將引發異 常,

3、失敗的offset單獨記錄

producer發送訊息,會自動重試,遇到不可恢復例外會拋出,這時可以捕獲例外記錄到資料庫或快取,進行 單獨處理,先commit再處理訊息,如果在處理訊息的時候例外了,但是offset 已經提交了,這條訊息對于該消費者來說就是丟失了,再也不會消費到了,

Kafka是pull?push?優劣勢分析

pull模式:

根據consumer的消費能力進行資料拉取,可以控制速率

可以批量拉取、也可以單條拉取

可以設定不同的提交方式,實作不同的傳輸語意

缺點:如果kafka沒有資料,會導致consumer慷訓圈,消耗資源

解決:通過引數設定,consumer拉取資料為慷訓者沒有達到一定數量時進行阻塞

 

push模式:不會導致consumer回圈等待

缺點:速率固定、忽略了consumer的消費能力,可能導致拒絕服務或者網路擁塞等情況

Kafka中zk的作用

/brokers/ids:臨時節點,保存所有broker節點資訊,存盤broker的物理地址、版本資訊、啟動時間 等,節點名稱為brokerID,broker定時發送心跳到zk,如果斷開則該brokerID會被洗掉

/brokers/topics:臨時節點,節點保存broker節點下所有的topic資訊,每一個topic節點下包含一個固 定的partitions節點,partitions的子節點就是topic的磁區,每個磁區下保存一個state節點、保存著當 前leader磁區和ISR的brokerID,state節點由leader創建,若leader宕機該節點會被洗掉,直到有新的leader選舉產生、重新生成state節點

/consumers/[group_id]/owners/[topic]/[broker_id-partition_id]:維護消費者和磁區的注冊關系

/consumers/[group_id]/offsets/[topic]/[broker_id-partition_id]:磁區訊息的消費進度Offset

client通過topic找到topic樹下的state節點、獲取leader的brokerID,到broker樹中找到broker的物理 地址,但是client不會直連zk,而是通過配置的broker獲取到zk中的資訊

簡述kafka的rebalance機制

consumer group中的消費者與topic下的partion重新匹配的程序何時會產生rebalance:

consumer group中的成員個數發生變化consumer消費超時

group訂閱的topic個數發生變化

group訂閱的topic的磁區數發生變化

coordinator:通常是partition的leader節點所在的broker,負責監控group中consumer的存活, consumer維持到coordinator的心跳,判斷consumer的消費超時

coordinator通過心跳回傳通知consumer進行rebalance

consumer請求coordinator加入組,coordinator選舉產生leader consumer

leader consumer從coordinator獲取所有的consumer,發送syncGroup(分配資訊)給到

coordinator

coordinator通過心跳機制將syncGroup下發給consumer

完成rebalance

leader consumer監控topic的變化,通知coordinator觸發rebalance

如果C1消費訊息超時,觸發rebalance,重新分配后、該訊息會被其他消費者消費,此時C1消費完成提 交offset、導致錯誤

解決:coordinator每次rebalance,會標記一個Generation給到consumer,每次rebalance該Generation會+1,consumer提交offset時,coordinator會比對Generation,不一致則拒絕提交

Kafka的性能好在什么地方

kafka不基于記憶體,而是硬碟存盤,因此訊息堆積能力更強

順序寫:利用磁盤的順序訪問速度可以接近記憶體,kafka的訊息都是append操作,partition是有序的, 節省了磁盤的尋道時間,同時通過批量操作、節省寫入次數,partition物理上分為多個segment存盤, 方便洗掉

傳統:

讀取磁盤檔案資料到內核緩沖區

將內核緩沖區的資料copy到用戶緩沖區

將用戶緩沖區的資料copy到socket的發送緩沖區

將socket發送緩沖區中的資料發送到網卡、進行傳輸

零拷貝:

直接將內核緩沖區的資料發送到網卡傳輸使用的是作業系統的指令支持

kafka不太依賴jvm,主要理由作業系統的pageCache,如果生產消費速率相當,則直接用pageCache

交換資料,不需要經過磁盤IO

在黑夜里夢想著光,心中覆寫悲傷,在悲傷里忍受孤獨,空守一絲溫暖, 我的淚水是無底深海,對你的愛已無言,相信無盡的力量,那是真愛永在, 我的信仰是無底深海,澎湃著心中火焰,燃燒無盡的力量,那是忠誠永在

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/445579.html

標籤:其他

上一篇:撰寫快取友好型程式技巧

下一篇:1、如何抓取Modbus TCP/UDP 資料包實戰

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more