Java高級面試題解析(一)

最近,在看一些java高級面試題,我發現我在認真研究一個面試題的時候,我本身的收穫是很大的,咱們在看看面試題的時候,不只僅要看這個問題自己,還要看這個問題的衍生問題,一個問題有些時候多是一個問題羣(若是隻關注問題自己,能夠跳過補充部分)。html

這個是我一個多星期的奮戰結果,把它記錄下來,若有不當,但願你們不吝賜教。前端

 

java 線程池的實現原理,threadpoolexecutor關鍵參數解釋java

原理見下圖 (或者:https://blog.csdn.net/u013332124/article/details/79587436)
實際開發中,直接調用ThreadPoolExecutor的狀況比較少,更多的是使用Executors實現(Executors內部不少地方都是調用的ThreadPoolExecutor)
Executors 是一個工廠類,用於建立線程池,經常使用的有5個api:
newCachedThreadPool():建立無界限線程池
newFixedThreadPool(int) :建立的是有界線線程池(線程個數能夠指定最大數量,若是超過最大數量,則後加入的線程須要等待)
newSingleThreadExecutor() :建立單一線程池,實現以隊列的方式來執行任務
newScheduledThreadPool(...):建立即將執行的任務線程池,每隔多久執行一次
newSingleThreadScheduledExecutor():只有一個線程,用來調度任務在指定時間內執行
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler)
corePoolSize:線程池核心線程數量
maximumPoolSize:線程池最大線程數量( 當workQueue滿了,不能添加任務的時候,這個參數纔會生效。)
keepAliverTime:當活躍線程數大於核心線程數時,空閒的多餘線程最大存活時間
unit:存活時間的單位
workQueue:存聽任務的隊列
handler:超出線程範圍和隊列容量的任務的處理程序


hashmap的原理,容量爲何是2的冪次mysql

HashMap使用的是哈希表(也稱散列表,是根據關鍵碼值(Key value)而直接進行訪問的數據結構),哈希表有多種不一樣的實現方法,經常使用的實現方式是「數組+鏈表」實現,即「鏈表的數組」。
首先,HashMap中存儲的對象爲數組Entry[](每一個Entry就是一個<key,value>),存和取時根據 key 的 hashCode 相關的算法獲得數組的下標,put 和 get 都根據算法的下標取得元素在數組中的位置。
萬一兩個key分別有相同的下標,那麼怎麼處理呢?
使用鏈表,把下標相同的 Entry 放到一個鏈表中,存儲時,存到第一個位置,並把next指向以前的第一個元素(若是是已存在的key,則須要遍歷鏈表),取值時,遍歷鏈表,經過equals方法獲取Entry。
// 存儲時:
int hash = key.hashCode(); // 這個hashCode方法這裏不詳述,只要理解每一個key的hash是一個固定的int值
int index = hash & (Entry[].length-1); // 二進制的按位與運算 
Entry[index] = value;
// 取值時:
int hash = key.hashCode();
int index = hash & (Entry[].length-1); // 二進制的按位與運算
return Entry[index]; // 此處須要遍歷 Entry[index] 的鏈表取值,此處爲簡寫
關於原理細節,可參考文章 https://www.cnblogs.com/holyshengjie/p/6500463.html 前半部分
關於源碼實現,可參考文章 https://www.cnblogs.com/chengxiao/p/6059914.html 【關於爲何覆蓋 equals方法 後須要同時覆蓋 hashCode方法】 在此文末尾
經過以上理解,若是 hashCode方法 寫得很差,好比全部 key 對象 都返回 同一個 int 值,那麼效率也不會高,由於就至關於一個鏈表了。
至於容量爲何是2的冪次?
由於HashMap的機制是,當發生哈希衝突而且size大於閾值的時候,須要進行數組擴容,由於獲取數組下標的方法是【hashCode和數組容量的按位與運算】結果,故只有數組大小爲2^n-1(即二進制全部位都是1,如:3,7,15,31...)才能保按位與運算獲得的 index 均勻分佈。
補充:二進制的按位與運算舉例:23 & 15 = 7
0 1 0 1 1 1
0 0 1 1 1 1    
-------------
0 0 0 1 1 1

 


爲何要同時重寫hashcode和equalsnginx

緣由在上一個問題中已經描述過了,這裏再說一下,注意看main方法中的註釋:
舉個小例子來看看,若是重寫了equals而不重寫hashcode會發生什麼樣的問題
public class MyTest {
private static class Person{
int idCard;
String name;

public Person(int idCard, String name) {
this.idCard = idCard;
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()){
return false;
}
Person person = (Person) o;
//兩個對象是否等值,經過idCard來肯定
return this.idCard == person.idCard;
}

}
public static void main(String []args){
HashMap<Person,String> map = new HashMap<Person, String>();
Person person = new Person(1234,"喬峯");
//put到HashMap中去
map.put(person,"天龍八部"); 
//get取出,從邏輯上講應該能輸出「天龍八部」
System.out.println("結果:"+map.get(new Person(1234,"蕭峯"))); // 結果:null
}
}
要理解爲何要同時重寫hashCode和equals,須要首先理解 HashMap 的原理。

這裏的例子中,因爲沒有重寫hashCode方法,雖然兩個對象的 equals 方法時一致的,可是兩個對象的hashCode 返回的 int 值不一致,致使put操做和get操做時,最終存和取的數組下標不一樣,取出來的就是null 了。

 

 

ConcurrentHashMap如何實現線程安全?面試

這個問題能夠參考文章:https://blog.csdn.net/V_Axis/article/details/78616700 如下是我的總結:
先說一下HashMap爲何線程不安全(hash碰撞與擴容致使,2點):
一、Entry內部的變量分別是 key、value、next,若是多個線程,在某一時刻同時操做HashMap並執行put操做,而有兩個key的hash值相同(這兩個key分別是a1 和 a2),這個時候須要解決碰撞衝突,不管是從鏈表頭部插入仍是從尾部初入,這個時候兩個線程若是剛好都取到了對應位置的頭結點e1,而最終的結果可想而知,a一、a2兩個數據中勢必會有一個會丟失;
二、在擴容時,當多個線程同時檢測到總數量超過門限值的時候就會同時調用resize操做,各自生成新的數組並rehash後賦給該map底層的數組table,結果最終只有最後一個線程生成的新數組被賦給table變量,其餘線程的均會丟失。
解決HashMap的線程不安全,可使用HashTable或者Collections.synchronizedMap,可是這兩位選手都有一個共同的問題:性能。由於無論是讀仍是寫操做,他們都會給整個集合上鎖(每一個方法都是 synchronized 的),致使同一時間的其餘操做被阻塞。
ConcurrentHashMap 是 HashMap 與 HashTable 的折中,ConcurrentHashMap 在進行操做時,會給集合上鎖,可是隻鎖了部分(segment)。
ConcurrentHashMap 集合 中有多個Segment,Segment其實就是一個HashMap,Segment也包含一個HashEntry數組,數組中的每個HashEntry既是一個鍵值對,也是一個鏈表的頭節點。
Segment對象在 ConcurrentHashMap 集合中有2的n次方個,共同保存在一個名爲segments的數組當中(類比HashMap來理解Segment就好)。換言之,ConcurrentHashMap是一個雙層哈希表。
每一個segment的讀寫是高度自治的,segment之間互不影響。這稱之爲「鎖分段技術」。
補充:
每個segment各自持有鎖,那麼在調用size()方法的時候(size()在實際開發大量使用),怎麼保持一致性呢? 
1.遍歷全部的Segment。
2.把Segment的修改次數累加起來。
3.把Segment的元素數量累加起來。    
4.判斷全部Segment的總修改次數(重複獲取一次2的統計)是否大於上一次的總修改次數(第2步統計的結果)。若是大於,說明統計過程當中有修改,從新統計,嘗試次數+1;若是不是。說明沒有修改,統計結束。
5.若是嘗試次數超過閾值,則對每個Segment加鎖,再從新統計。
6.再次判斷全部Segment的總修改次數是否大於上一次的總修改次數。因爲已經加鎖,次數必定和上次相等。
7.釋放鎖,統計結束。
這個邏輯有些相似於樂觀鎖(參考:https://blog.csdn.net/V_Axis/article/details/78532806)

 

 


介紹Java多線程的5大狀態,以及狀態圖流轉過程redis

1. 新建(NEW):新建立了一個線程對象。
2. 可運行(RUNNABLE):線程對象建立後,其餘線程(好比main線程)調用了該對象的start()方法。該狀態的線程位於可運行線程池中,等待被線程調度選中,獲取cpu 的使用權 。
3. 運行(RUNNING):可運行狀態(runnable)的線程得到了cpu 時間片(timeslice) ,執行程序代碼。
4. 阻塞(BLOCKED):阻塞狀態是指線程由於某種緣由放棄了cpu 使用權,也即讓出了cpu timeslice,暫時中止運行。直到線程進入可運行(runnable)狀態,纔有機會再次得到cpu timeslice 轉到運行(running)狀態。阻塞的狀況分三種:
(一). 等待阻塞:運行(running)的線程執行o.wait()方法,JVM會把該線程放入等待隊列(waitting queue)中。
(二). 同步阻塞:運行(running)的線程在獲取對象的同步鎖時,若該同步鎖被別的線程佔用,則JVM會把該線程放入鎖池(lock pool)中。
(三). 其餘阻塞:運行(running)的線程執行Thread.sleep(long ms)或t.join()方法,或者發出了I/O請求時,JVM會把該線程置爲阻塞狀態。當sleep()狀態超時、join()等待線程終止或者超時、或者I/O處理完畢時,線程從新轉入可運行(runnable)狀態。
5. 死亡(DEAD):線程run()、main() 方法執行結束,或者因異常退出了run()方法,則該線程結束生命週期。死亡的線程不可再次復生。
詳見:https://blog.csdn.net/maijia0754/article/details/79004412

 


介紹下synchronized、Volatile、CAS、AQS,以及各自的使用場景算法

synchronized是經過對象內部的一個叫作監視器鎖(monitor)來實現的,監視器鎖本質又是依賴於底層的操做系統的互斥鎖(Mutex Lock)來實現的。synchronized屬於重量級的,多線程時只能一個線程訪問相關代碼塊,安全性高,但有時會效率低下。
volatile 一般用來修飾變量,能保證變量的內存可見性(即變量的讀取和修改是直接操做原變量<每次讀取前必須先從主內存刷新最新的值,每次寫入後必須當即同步回主內存當中>,而不是copy後的變量),同時能防止指令被重排序(JVM運行時,爲了效率考慮會對某些指令的執行順序進行調整),但沒法保證原子性(如語句:count++; 在多線程高併發時,就會出現數值不許,此時可使用synchronized 或者 ReentrantLock 加鎖)。
CAS(compare and swap 的簡寫,即比較並交換)是樂觀鎖的一個實現,它須要三個參數,分別是內存位置 V,舊的預期值 A 和新的值 B。操做時,先從內存位置讀取到值,而後和預期值A比較。若是相等,則將此內存位置的值改成新值 B,返回 true。若是不相等,說明和其餘線程衝突了,則不作任何改變,返回 false。樂觀鎖中,通常比較操做比的是版本號,CAS中也能夠改舊值比較爲版本號比較(安全性更高)。
AQS(AbstractQueuedSynchronizer)中有兩個重要的成員:
一、成員變量 state。用於表示鎖如今的狀態,用 volatile 修飾,保證內存一致性。同時所用對 state 的操做都是使用 CAS 進行的。state 爲0表示沒有任何線程持有這個鎖,線程持有該鎖後將 state 加1,釋放時減1。屢次持有釋放則屢次加減。
二、還有一個雙向鏈表,鏈表除了頭結點外,每個節點都記錄了線程的信息,表明一個等待線程。這是一個 FIFO 的鏈表。
AQS 請求鎖時有三種可能:
一、若是沒有線程持有鎖,則請求成功,當前線程直接獲取到鎖。
二、若是當前線程已經持有鎖,則使用 CAS 將 state 值加1,表示本身再次申請了鎖,釋放鎖時減1。這就是可重入性的實現。
三、若是由其餘線程持有鎖,那麼將本身添加進等待隊列。
其中,R
eentrantLock 就是 AQS原理實現的,而synchronized 則是jvm層面實現的。
補充:
ReentrantLock 使用代碼實現了和 synchronized 同樣的語義,包括可重入,保證內存可見性和解決競態條件問題等。相比 synchronized,它還有以下好處:
支持以非阻塞方式獲取鎖
能夠響應中斷
能夠限時
支持了公平鎖和非公平鎖(公平鎖是指各個線程在加鎖前先檢查有無排隊的線程,按排隊順序去得到鎖。 非公平鎖是指線程加鎖前不考慮排隊問題,直接嘗試獲取鎖,獲取不到再去隊尾排隊。)
基本用法以下:
public class Counter {

private final Lock lock = new ReentrantLock(); // 若是 new ReentrantLock(true); 則表示公平鎖 private volatile int count; 
public void incr() {
lock.lock();
try {
 count++;
} finally {
 lock.unlock();
}
} 
public int getCount() {
 return count;
}
}

 

 

B+樹和紅黑樹時間複雜度sql

B+數時間複雜度是 O(lgn)
紅黑樹時間複雜度是 O(lgn)
補充:
B+樹是爲磁盤或其餘直接存取輔助設備而設計的一種平衡查找樹,全部記錄節點都是按鍵值的大小順序存放在同一層的葉節點中,各葉節點指針進行鏈接。
(B+樹不詳細說了,詳細網上資料不少)
紅黑樹(RBT)的定義:它或者是一顆空樹,或者是具備一下性質的二叉查找樹:
1.節點非紅即黑。
2.根節點是黑色。
3.全部NULL結點稱爲葉子節點,且認爲顏色爲黑。
4.全部紅節點的子節點都爲黑色。
5.從任一節點到其葉子節點的全部路徑上都包含相同數目的黑節點。
紅黑樹插入:
插入點不能爲黑節點,應插入紅節點。由於你插入黑節點將破壞性質5,因此每次插入的點都是紅結點,可是若他的父節點也爲紅,那豈不是破壞了性質4?對啊,因此要作一些「旋轉」和一些節點的變色。
紅黑樹參考自:http://www.cnblogs.com/fornever/archive/2011/12/02/2270692.html
平衡二叉樹(AVL)適合用於插入刪除次數比較少,但查找多的狀況;紅黑樹的旋轉保持平衡(插入和刪除時會旋轉保持平衡)次數較少,用於搜索時,插入刪除次數多的狀況下咱們就用紅黑樹來取代AVL。

 


若是頻繁老年代回收怎麼分析解決mongodb

老年代頻繁回收,通常是Full GC,Full GC 消耗很大,由於在全部用戶線程中止的狀況下完成回收,而形成頻繁 Full GC 的緣由多是,程序存在問題,或者環境存在問題。
對jvm的GC進行必要的監控,操做以下:
一、使用jps命令(或者ps -eaf|grep java)獲取到當前的java進程(取得進程id,假如pid爲 1234)
二、使用jstat查看GC狀況(如:jstat -gc 1234 1000,後面的1000表示每一個1000毫米打印一次監控),jstat命令能夠參考:https://www.cnblogs.com/yjd_hycf_space/p/7755633.html (此文使用的是jdk8,可是本人親測jstat在jdk7也是這樣的)
jstat -class pid:顯示加載class的數量,及所佔空間等信息。 
jstat -compiler pid:顯示VM實時編譯的數量等信息。 
jstat -gc pid:能夠顯示gc的信息,查看gc的次數,及時間。其中最後五項,分別是young gc的次數,young gc的時間,full gc的次數,full gc的時間,gc的總時間。 
jstat -gccapacity:能夠顯示,VM內存中三代(young,old,perm)對象的使用和佔用大小,如:PGCMN顯示的是最小perm的內存使用量,PGCMX顯示的是perm的內存最大使用量,PGC是當前新生成的perm內存佔用量,PC是但前perm內存佔用量。其餘的能夠根據這個類推, OC是old內純的佔用量。 
jstat -gcnew pid:new對象的信息。 
jstat -gcnewcapacity pid:new對象的信息及其佔用量。 
jstat -gcold pid:old對象的信息。 
jstat -gcoldcapacity pid:old對象的信息及其佔用量。 
jstat -gcpermcapacity pid: perm對象的信息及其佔用量。 
jstat -util pid:統計gc信息統計。 
jstat -printcompilation pid:當前VM執行的信息。 
除了以上一個參數外,還能夠同時加上 兩個數字,如:jstat -printcompilation 3024 250 6是每250毫秒打印一次,一共打印6次,還能夠加上-h3每三行顯示一下標題
三、使用jmap(jmap 是一個能夠輸出全部內存中對象的工具)導出對象文件。如對於java進程(pid=1234),能夠這樣:jmap -histo 1234 > a.log 將對象導出到文件,而後經過查看對象內存佔用大小,返回去代碼裏面找問題。
(也可使用命令,導出對象二進制內容(這樣導出內容比jmap -histo多得多,更耗時,對jvm的消耗也更大):jmap -dump:format=b,file=a.log 1234)
(以上操做本人有親自實驗,jdk版本是7,第2步其實能夠跳過)

 

 

JVM內存模型,新生代和老年的回收機制

新生代的GC:
新生代一般存活時間較短,所以基於複製算法來進行回收,所謂複製算法就是掃描出存活的對象,並複製到一塊新的徹底未使用的空間中。
在Eden和其中一個Survivor,複製到另外一個之間Survivor空間中,而後清理掉原來就是在Eden和其中一個Survivor中的對象。
新生代採用空閒指針的方式來控制GC觸發,指針保持最後一個分配的對象在新生代區間的位置,當有新的對象要分配內存時,用於檢查空間是否足夠,不夠就觸發GC。
當連續分配對象時,對象會逐漸從eden到 survivor,最後到老年代,而後清空繼續裝載,當老年代也滿了後,就會報outofmemory的異常。
老年代的GC:
老年代與新生代不一樣,對象存活的時間比較長,比較穩定,所以採用標記(Mark)算法來進行回收。
掃描出存活的對象,而後再進行回收未被標記的對象,回收後對用空出的空間要麼進行合併,要麼標記出來便於下次進行分配,總之就是要減小內存碎片帶來的效率損耗。
補充:
Java 中的堆也是 GC 收集垃圾的主要區域:
GC 分爲兩種:Minor GC、Full GC ( 或稱爲 Major GC )。
Minor GC 是發生在新生代中的垃圾收集動做,所採用的是複製算法。
Full GC 是發生在老年代的垃圾收集動做,所採用的是標記-清除算法。
Minor GC思路:
當對象在 Eden ( 包括一個 Survivor 區域,這裏假設是 from 區域 ) 出生後,在通過一次 Minor GC 後,若是對象還存活,而且可以被另一塊 Survivor 區域所容納,而且將這些對象的年齡設置爲1,之後對象在 Survivor 區每熬過一次 Minor GC,就將對象的年齡 + 1,當對象的年齡達到某個值時 ( 默認是 15 歲,能夠經過參數 -XX:MaxTenuringThreshold 來設定 ),這些對象就會成爲老年代。
對於一些較大的對象 ( 即須要分配一塊較大的連續內存空間 ) 則是直接進入到老年代。
Full GC 發生的次數不會有 Minor GC 那麼頻繁,而且作一次 Full GC 要比進行一次 Minor GC 的時間更長。
標記-清除算法收集垃圾的時候會產生許多的內存碎片(不連續內存空間)。
標記:標記的過程其實就是,遍歷全部的GC Roots,而後將全部GC Roots可達的對象標記爲存活的對象。
清除:清除的過程將遍歷堆中全部的對象,將沒有標記的對象所有清除掉。
標記-清除算法 容易產生內存碎片,故老年代中會使用標記整理算法(首先標記,但後續不是直接清除,而是將存活的對象都向一端移動,最後直接清理掉邊界之外的內存。)
GC Roots有哪些:
Class - 由系統類加載器(system class loader)加載的對象,這些類是不可以被回收的,他們能夠以靜態字段的方式保存持有其它對象。咱們須要注意的一點就是,經過用戶自定義的類加載器加載的類,除非相應的java.lang.Class實例以其它的某種(或多種)方式成爲roots,不然它們並非roots,.
Thread - 活着的線程
Stack Local - Java方法的local變量或參數(我的理解,就是ThreadLocal 相關的類 )
JNI Local - JNI方法的local變量或參數(JNI 是 Java Native Interface的縮寫,它提供了若干的API實現了Java和其餘語言的通訊(主要是C&C++))
JNI Global - 全局JNI引用
Monitor Used - 用於同步的監控對象
Held by JVM - 用於JVM特殊目的由GC保留的對象,但實際上這個與JVM的實現是有關的。可能已知的一些類型是:系統類加載器、一些JVM知道的重要的異常類、一些用於處理異常的預分配對象以及一些自定義的類加載器等。然而,JVM並無爲這些對象提供其它的信息,所以須要去肯定哪些是屬於"JVM持有"的了。

 


mysql limit分頁如何保證可靠性

limit 查詢,當表中數據量很大時,在十萬條後面 limit 速度會變慢,如:
select XXX from tableA limit 1000000,10; 
上面的語句是取1000000後面的10條記錄,可是這樣會致使mysql將1000000以前的全部數據所有掃描一次,大量浪費了時間。
解決思路1:
直接使用主鍵查詢,如:
-- 此查詢耗時:0.00 秒
select * from couponmodel_user where id > 3000000 and id < 3000000+10;
這種方式會形成某次查詢出來的條數不足10條,有不足,可是優勢就在特別快。 解決思路2: 直接貼我實際操做的代碼吧 -- couponmodel_user 表中有一千萬條數據,此查詢耗時:3.02 秒 select * from couponmodel_user order by id limit 3000000,10; -- 只查id,此查詢耗時:0.85 秒 select id from couponmodel_user order by id limit 3000000,10; -- 經過以上的啓發,修改sql,此查詢耗時:0.84 秒 select a.* from couponmodel_user a,(select id from couponmodel_user order by id limit 3000000,10) b where a.id=b.id order by a.id;
簡單講,就是使用主鍵的複合索引,關聯查詢
思路2解決了思路1的不足,速度也顯著提高,可是問題就是速度還不如思路1快。

 


java nio,bio,aio,操做系統底層nio實現原理

BIO:
同步並阻塞,服務器實現模式爲一個鏈接一個線程,即客戶端有鏈接請求時服務器端就須要啓動一個線程進行處理,若是這個鏈接不作任何事情會形成沒必要要的線程開銷,固然能夠經過線程池機制改善。
BIO方式適用於鏈接數目比較小且固定的架構,JDK1.4之前的惟一選擇,但程序直觀簡單易理解。
NIO: 
同步非阻塞,服務器實現模式爲一個請求一個線程,即客戶端發送的鏈接請求都會註冊到多路複用器上,多路複用器輪詢到鏈接有I/O請求時才啓動一個線程進行處理。
NIO方式適用於鏈接數目多且鏈接比較短(輕操做)的架構,好比聊天服務器,併發侷限於應用中,編程比較複雜,JDK1.4開始支持。
AIO(NIO.2):
異步非阻塞,服務器實現模式爲一個有效請求一個線程,客戶端的I/O請求都是由OS先完成了再通知服務器應用去啓動線程進行處理。
AIO方式使用於鏈接數目多且鏈接比較長(重操做)的架構,好比相冊服務器,充分調用OS參與併發操做,編程比較複雜,JDK7開始支持。
能夠參考文章:https://blog.csdn.net/ty497122758/article/details/78979302 或者 https://www.cnblogs.com/diegodu/p/6823855.html

 


Spring事務傳播機制

PROPAGATION_REQUIRED:有事務就用已有的,沒有就從新開啓一個
PROPAGATION_SUPPORTS:有事務就用已有的,沒有也不會從新開啓
PROPAGATION_MANDATORY:必需要有事務,沒事務拋異常
PROPAGATION_REQUIRES_NEW:開啓新事務,若當前已有事務,掛起當前事務
PROPAGATION_NOT_SUPPORTED:不須要事務,若當前已有事務,掛起當前事務
PROPAGATION_NEVER:不須要事務,若當前已有事務,拋出異常
PROPAGATION_NESTED:嵌套事務,若是外部事務回滾,則嵌套事務也會回滾!!!外部事務提交的時候,嵌套它纔會被提交。嵌套事務回滾不會影響外部事務。

 


線程死鎖排查

使用 jps + jstack,排查步驟:
一、使用命令列出當前運行的java進程的pid: jps -l
二、使用命令查看(假如第1步查到的java進程pid爲 1234):jstack -l 1234
注意第2步中是否有提示 「 Found x deadlock 」 若有,則表示有死鎖存在,其中 x 表示死鎖的個數
以上兩個命令的 -l 選項均可以省略,以上操做本人親自實驗可用。
參考:https://blog.csdn.net/mynamepg/article/details/80758540
補充:
死鎖是指兩個或兩個以上的進程在執行過程當中,因爲競爭資源或者因爲彼此通訊而形成的一種阻塞的現象,若無外力做用,它們都將沒法推動下去。此時稱系統處於死鎖狀態或系統產生了死鎖,這些永遠在互相等待的進程稱爲死鎖進程。

 

 

MySQL引擎及區別,項目用的哪一個,爲何

ISAM:
ISAM執行讀取操做的速度很快,且不佔用大量的內存和存儲資源。
ISAM的兩個主要不足之處在於,不支持事務處理,也不可以容錯(若是你的硬盤崩潰了,那麼數據文件就沒法恢復了,需實時備份數據)。
MyISAM:
MyISAM是MySQL的ISAM擴展格式和缺省的數據庫引擎。提供了索引和字段管理的大量功能,使用一種表格鎖定的機制,來優化多個併發的讀寫操做。
MyISAM的不足仍然是不支持事務管理。
HEAP:
HEAP容許只駐留在內存裏的臨時表格。駐留在內存裏讓HEAP要比ISAM和MYISAM都快。
不足:管理的數據是不穩定的,並且若是在關機以前沒有進行保存,那麼全部的數據都會丟失。在數據行被刪除的時候,HEAP也不會浪費大量的空間。
HEAP表格在你須要使用SELECT表達式來選擇和操控數據的時候很是有用。要記住,在用完表格以後就刪除表格。
InnoDB:
InnoDB數據庫引擎儘管要比ISAM和 MyISAM引擎慢不少,可是InnoDB包括了對事務處理和外來鍵的支持,這兩點都是前兩個引擎所沒有的。如前所述,若是你的設計須要這些特性中的一者 或者二者,那你就要被迫使用後兩個引擎中的一個了。
InnoDB和MyISAM是許多人在使用MySQL時最經常使用的兩個表類型,這兩個表類型各有優劣:
MyISAM類型不支持事務處理等高級處理,而InnoDB類型支持。MyISAM類型的表強調的是性能,其執行數度比InnoDB類型更快,可是不提供事務支持,而InnoDB提供事務支持已經外部鍵等高級數據庫功能。

 


RPC爲何用http作通訊?

RPC(Remote Produce Call)是一種概念,HTTP是一種協議,RPC能夠經過HTTP來實現。RPC也可使用socket來實現,可是問題就是socket是阻塞式的,並很差,大多RPC框架可能會使用netty來實現,而netty支持多種通信協議,HTTP、WebSocket等協議,也可使用自定義的協議進行通信。
之因此使用HTTP協議,我的認爲有多是HTTP協議的特色決定的,還有就是HTTP比較成熟。
補充:
HTTP協議的主要特色歸納以下:
1.支持客戶/服務器模式。
2.簡單快速:客戶向服務器請求服務時,只需傳送請求方法和路徑。請求方法經常使用的有GET、HEAD、POST。每種方法規定了客戶與服務器聯繫的類型不一樣。因爲HTTP協議簡單,使得HTTP服務器的程序規模小,於是通訊速度很快。
3.靈活:HTTP容許傳輸任意類型的數據對象。正在傳輸的類型由Content-Type加以標記。
4.無鏈接:無鏈接的含義是限制每次鏈接只處理一個請求。服務器處理完客戶的請求,並收到客戶的應答後,即斷開鏈接。採用這種方式能夠節省傳輸時間。
5.無狀態:HTTP協議是無狀態協議。無狀態是指協議對於事務處理沒有記憶能力。缺乏狀態意味着若是後續處理須要前面的信息,則它必須重傳,這樣可能致使每次鏈接傳送的數據量增大。另外一方面,在服務器不須要先前信息時它的應答就較快。

 


RPC兩端如何進行負載均衡?

我的理解,常見的算法是輪詢(將請求輪流的分配到後端服務器上,均衡的對待後端的每一臺服務器,而不關心服務器的實際鏈接數和當前的系統負載),擴展一下,是加權輪詢法(每一個主機輪詢的多少)。
好比dubbo,可使用zookeeper做爲註冊中心,將服務註冊到zookeeper,進行負載均衡。

 


mycat分庫分表、讀寫分離的實現

MyCat是mysql中間件,其核心就是分表分庫,具體如何實現分表分庫,由配置文件的配置決定:
配置文件,conf目錄下主要如下三個須要熟悉。
server.xml  Mycat的配置文件,設置帳號、參數等
schema.xml  Mycat對應的物理數據庫和數據庫表的配置
rule.xml  Mycat分片(分庫分表)規則
分庫分表配置:在 rule.xml 中進行配置
讀寫分離配置:schema.xml 中的 dataHost 的 balance 屬性爲一、2 或者 3,由於 爲 0 表示不支持讀寫分離。參考:https://www.cnblogs.com/ivictor/p/5131480.html
補充:
MyCat是一個開源的分佈式數據庫系統,MyCat是mysql中間件,是一個實現了MySQL協議的服務器,前端用戶能夠把它看做是一個數據庫代理,其核心功能是分表分庫,即將一個大表水平分割爲N個小表,存儲在後端MySQL服務器裏或者其餘數據庫裏。

 


分佈式數據如何保證數據一致性

在分佈式系統來講,若是不想犧牲一致性,CAP 理論告訴咱們只能放棄可用性,這顯然不能接受。
強一致:
當更新操做完成以後,任何多個後續進程或者線程的訪問都會返回最新的更新過的值。這種是對用戶最友好的,就是用戶上一次寫什麼,下一次就保證能讀到什麼。根據 CAP 理論,這種實現須要犧牲可用性。
弱一致性:
系統並不保證續進程或者線程的訪問都會返回最新的更新過的值。系統在數據寫入成功以後,不承諾當即能夠讀到最新寫入的值,也不會具體的承諾多久以後能夠讀到。
最終一致性:
弱一致性的特定形式。系統保證在沒有後續更新的前提下,系統最終返回上一次更新操做的值。在沒有故障發生的前提下,不一致窗口的時間主要受通訊延遲,系統負載和複製副本的個數影響。DNS 是一個典型的最終一致性系統。
互聯網系統大多將強一致性需求轉換成最終一致性的需求,既保證一致性,又保證可用性。
保證最終一致性的 3 種解決方案:
一、經典方案 - eBay 模式
核心是將須要分佈式處理的任務經過消息日誌的方式來異步執行。消息日誌能夠存儲到本地文本、數據庫或消息隊列,再經過業務規則自動或人工發起重試。
一個最多見的場景,若是產生了一筆交易,須要在交易表增長記錄,同時還要修改用戶表的金額。這兩個表屬於不一樣的遠程服務,因此就涉及到分佈式事務一致性的問題。
以上場景解決方案:將主要修改操做以及更新用戶表的消息放在一個本地事務來完成。同時爲了不重複消費用戶表消息帶來的問題,增長一個更新記錄表 updates_applied 來記錄已經處理過的消息。
二、 隨着業務規模不斷地擴大,電商網站通常都要面臨拆分之路。
將原來一個單體應用拆分紅多個不一樣職責的子系統。好比之前可能將面向用戶、客戶和運營的功能都放在一個系統裏,如今拆分爲訂單中心、代理商管理、運營系統、報價中心、庫存管理等多個子系統。
仍是和方案1同樣,優先使用異步消息。
拆分多了以後,涉及到一個問題:分佈式事物。
處理分佈式事物,將分佈式事務轉換爲多個本地事務,而後依靠重試等方式達到最終一致性。若是其中的一個事物確實是執行不成功,則考慮回滾整個分佈式事物。
好比 A 同步調用 B 和 C,好比 B 是扣庫存服務,在第一次調用的時候由於某種緣由失敗了,可是重試的時候庫存已經變爲 0,沒法重試成功,這個時候只有回滾 A 和 C 了。
三、 若是分佈式事物交易訂單中是10個步驟,最後一個步驟操做失敗了,其餘步驟都要回滾,步驟過多,能夠這樣:
首先建立一個不可見訂單,其中某一步失敗後,發送一個MQ廢單消息(若是消息發送失敗,本地繼續異步重試),其餘步驟收到消息就能夠進行回滾。
還可使用支付寶的 分佈式事務服務框架 DTS(由支付寶在 2PC <2 Phase Commit, 兩階段提交> 的基礎上改進而來)
參考:https://www.cnblogs.com/wangdaijun/p/7272677.html
補充:
CAP原則,指的是在一個分佈式系統中, Consistency(一致性)、 Availability(可用性)、Partition tolerance(分區容錯性),三者不可得兼。
一致性(C):在分佈式系統中的全部數據備份,在同一時刻是否一樣的值。(等同於全部節點訪問同一份最新的數據副本)
可用性(A):在集羣中一部分節點故障後,集羣總體是否還能響應客戶端的讀寫請求。(對數據更新具有高可用性)
分區容忍性(P):以實際效果而言,分區至關於對通訊的時限要求。系統若是不能在時限內達成數據一致性,就意味着發生了分區的狀況,必須就當前操做在C和A之間作出選擇。

 


高併發請求處理,流量削峯措施有哪些

削峯從本質上來講就是更多地延緩用戶請求,以及層層過濾用戶的訪問需求,聽從「最後落地到數據庫的請求數要儘可能少」的原則。
一、消息隊列解決削峯:使用較多的消息隊列有 ActiveMQ、RabbitMQ、 ZeroMQ、Kafka、MetaMQ、RocketMQ 等。
二、流量削峯漏斗:層層削峯
經過在不一樣的層次儘量地過濾掉無效請求。
經過CDN過濾掉大量的圖片,靜態資源的請求;
再經過相似Redis這樣的分佈式緩存,過濾請求等就是典型的在上游攔截讀請求;
對讀數據不作強一致性校驗,減小由於一致性校驗產生瓶頸的問題;
對寫請求作限流保護,將超出系統承載能力的請求過濾掉(如可使用 nginx 進行限流)
補充:
CDN 的基本原理是普遍採用各類緩存服務器,將這些緩存服務器分佈到用戶訪問相對集中的地區或網絡中,在用戶訪問網站時,利用全局負載技術將用戶的訪問指向距離最近的工做正常的緩存服務器上,由緩存服務器直接響應用戶請求。
CDN的優點很明顯:(1)CDN節點解決了跨運營商和跨地域訪問的問題,訪問延時大大下降;(2)大部分請求在CDN邊緣節點完成,CDN起到了分流做用,減輕了源站的負載。
瀏覽器本地緩存失效後,瀏覽器會向CDN邊緣節點發起請求。相似瀏覽器緩存,CDN邊緣節點也存在着一套緩存機制。
可參考:https://www.cnblogs.com/tinywan/p/6067126.html

 


Redis持久化RDB和AOF 的區別

RDB持久化是指在指定的時間間隔內將內存中的數據集快照寫入磁盤,實際操做過程是fork一個子進程,先將數據集寫入臨時文件,寫入成功後,再替換以前的文件,用二進制壓縮存儲。
AOF持久化以日誌的形式記錄服務器所處理的每個寫、刪除操做,查詢操做不會記錄,以文本的方式記錄,能夠打開文件看到詳細的操做記錄。
RDB是二進制存儲的,恢復和啓動更快,AOF 啓動時須要從新加載執行一次全部操做日誌 速度相對要慢 但優勢是安全性更高。
補充:
RDB優點:
一、一旦採用該方式,那麼你的整個Redis數據庫將只包含一個文件,這對於文件備份而言是很是完美的(好比,你可能打算每一個小時歸檔一次最近24小時的數據,同時還要天天歸檔一次最近30天的數據。經過這樣的備份策略,一旦系統出現災難性故障,咱們能夠很是容易的進行恢復)。
二、對於災難恢復而言,RDB是很是不錯的選擇。由於咱們能夠很是輕鬆的將一個單獨的文件壓縮後再轉移到其它存儲介質上。
三、性能最大化。對於Redis的服務進程而言,在開始持久化時,它惟一須要作的只是fork出子進程,以後再由子進程完成這些持久化的工做,這樣就能夠極大的避免服務進程執行IO操做了。
四、相比於AOF機制,若是數據集很大,RDB的啓動效率會更高。
RDB劣勢:
一、若是你想保證數據的高可用性,即最大限度的避免數據丟失,那麼RDB將不是一個很好的選擇。由於系統一旦在定時持久化以前出現宕機現象,此前沒有來得及寫入磁盤的數據都將丟失。
二、因爲RDB是經過fork子進程來協助完成數據持久化工做的,所以,若是當數據集較大時,可能會致使整個服務器中止服務幾百毫秒,甚至是1秒鐘。
AOF優點:
一、該機制能夠帶來更高的數據安全性,即數據持久性。Redis中提供了3中同步策略,即每秒同步、每修改同步和不一樣步。
二、因爲該機制對日誌文件的寫入操做採用的是append模式,所以在寫入過程當中即便出現宕機現象,也不會破壞日誌文件中已經存在的內容。然而若是咱們本次操做只是寫入了一半數據就出現了系統崩潰問題,不用擔憂,在Redis下一次啓動以前,咱們能夠經過redis-check-aof工具來幫助咱們解決數據一致性的問題。
三、若是日誌過大,Redis能夠自動啓用rewrite機制。即Redis以append模式不斷的將修改數據寫入到老的磁盤文件中,同時Redis還會建立一個新的文件用於記錄此期間有哪些修改命令被執行。所以在進行rewrite切換時能夠更好的保證數據安全性。
四、AOF包含一個格式清晰、易於理解的日誌文件用於記錄全部的修改操做。事實上,咱們也能夠經過該文件完成數據的重建。
AOF劣勢:
一、對於相同數量的數據集而言,AOF文件一般要大於RDB文件。RDB 在恢復大數據集時的速度比 AOF 的恢復速度要快。
二、根據同步策略的不一樣,AOF在運行效率上每每會慢於RDB。

 


MQ底層實現原理

就ActiveMQ來講:
消息通訊機制:
點對點模式(p2p),每一個消息只有1個消費者,它的目的地稱爲queue隊列;
發佈/訂閱模式(pub/sub),每一個消息能夠有多個消費者,並且訂閱一個主題的消費者,只能消費自它訂閱以後發佈的消息。
底層使用的是RPC調用。

 


詳細介紹下分佈式 一致性Hash算法

典型的應用場景是: 有N臺服務器提供緩存服務,須要對服務器進行負載均衡,將請求平均分發到每臺服務器上,每臺機器負責1/N的服務。
Memcached client也選擇這種算法,解決將key-value均勻分配到衆多Memcached server上的問題。
一、一致性哈希將整個哈希值空間組織成一個虛擬的圓環;
二、將各個服務器使用H進行一個哈希(能夠選擇服務器的ip或主機名做爲關鍵字進行哈希,這樣每臺機器就能肯定其在哈希環上的位置);
三、將數據key使用相同的函數H計算出哈希值h,通根據h肯定此數據在環上的位置,今後位置沿環順時針「行走」,第一臺遇到的服務器就是其應該定位到的服務器。
此算法的容錯性較高,某個服務器掛了以後,會自動定位到下一個主機。
詳細參考:https://www.cnblogs.com/moonandstar08/p/5405991.html

 


nginx負載均衡的算法

一、輪詢(默認):
每一個請求按時間順序逐一分配到不一樣的後端服務,若是後端某臺服務器死機,自動剔除故障系統,使用戶訪問不受影響。
二、weight(輪詢權值):
weight的值越大分配到的訪問機率越高,主要用於後端每臺服務器性能不均衡的狀況下。或者僅僅爲在主從的狀況下設置不一樣的權值,達到合理有效的地利用主機資源。
如:
upstream server_myServer_pool { 
server 192.168.0.14 weight=10; 
server 192.168.0.15 weight=10; 
}
這裏配置了的 server_myServer_pool 可在 location 中使用,如: location / { proxy_pass http://server_myServer_pool }
三、ip_hash:
每一個請求按訪問IP的哈希結果分配,使來自同一個IP的訪客固定訪問一臺後端服務器,而且能夠有效解決動態網頁存在的session共享問題。
如:
upstream server_myServer_pool { 
ip_hash; 
server 192.168.0.14:88; 
server 192.168.0.15:80; 
} 
四、fair:
比 weight、ip_hash更加智能的負載均衡算法,fair算法能夠根據頁面大小和加載時間長短智能地進行負載均衡,也就是根據後端服務器的響應時間 來分配請求,響應時間短的優先分配。Nginx自己不支持fair,若是須要這種調度算法,則必須安裝upstream_fair模塊。
如:
upstream server_myServer_pool {
fair; 
server 192.168.0.14:88;
server 192.168.0.15:80;
}
五、url_hash:
按訪問的URL的哈希結果來分配請求,使每一個URL定向到一臺後端服務器,能夠進一步提升後端緩存服務器的效率。Nginx自己不支持url_hash,若是須要這種調度算法,則必須安裝Nginx的hash軟件包。
如:
upstream resinserver{ 
server 10.0.0.10:7777; 
server 10.0.0.11:8888; 
hash $request_uri; 
hash_method crc32; 
}

 


Nginx 的 upstream 目前支持 哪4 種方式的分配

weight(輪詢權值)、ip_hash(訪問IP的哈希結果分配)、fair(根據頁面大小和加載時間長短智能分配,第三方)、url_hash(按訪問的URL的哈希結果分配,第三方)
詳細見上一個問題

 


Dubbo默認使用什麼註冊中心,還有別的選擇嗎?

Dubbo默認使用的註冊中心是Zookeeper,其餘的選擇包括 Multicast、Zookeeper、Redis、Simple 等。

 


mongoDB、redis和memcached的應用場景,各自優點

mongoDB(基於分佈式文件存儲的數據庫):
文檔型的非關係型數據庫,使用bson結構。其優點在於查詢功能比較強大,能存儲海量數據,缺點是比較消耗內存。
通常能夠用來存放評論等半結構化數據,支持二級索引。適合存儲json類型數據,不常常變化。
redis(內存數據庫):
是內存型數據庫,數據保存在內存中,經過tcp直接存取,優點是讀寫性能高。
redis是內存型KV數據庫,不支持二級索引,支持list,set等多種數據格式。適合存儲全局變量,適合讀多寫少的業務場景。很適合作緩存。
memcached(內存Cache):
Memcached基於一個存儲鍵/值對的hashmap,是一個高性能的分佈式內存對象緩存系統,用於動態Web應用以減輕數據庫負載。
性能:都比較高,性能都不是瓶頸,redis 和 memcache 差很少,要大於 mongodb。
操做的便利性:memcache 數據結構單一(key-value);redis 豐富一些,還提供 list、set、hash 等數據結構的存儲;mongodb 支持豐富的數據表達,索引,最相似關係型數據庫。

 


談談你性能優化的實踐案例,優化思路?

我的感受這個問題比較開放,能夠舉例上面的問題來回答:
如:
mysql limit分頁如何保證可靠性
高併發請求處理,流量削峯措施有哪些
或者能夠舉例分佈式架構方面內容:數據庫分表分庫、讀寫分離,使用緩存(分佈式緩存),等等

 


兩千萬用戶併發搶購,你怎麼來設計?

此問題和 《高併發請求處理,流量削峯措施有哪些》這個問題相似,能夠參考這個問題來回答。

 

這些就是這一期總結的問題,有些問題總結的可能不到位,也但願還能有下一期,你們共同進步。

相關文章
相關標籤/搜索