我是如何進入阿里巴巴的-面向春招應屆生Java面試指南(九)

基礎篇

基本功

面向對象的特徵

1.final, finally, finalize 的區別 final—修飾符(關鍵字)若是一個類被聲明爲final,意味着它不能再派生出新的子類,不能做爲父類被繼承。所以一個類不能既被聲明爲 abstract的,又被聲明爲final的。將變量或方法聲明爲final,能夠保證它們在使用中不被改變。被聲明爲final的變量必須在聲明時給定初值,而在之後的引用中只能讀取,不可修改。被聲明爲final的方法也一樣只能使用,不能重載。 finally—再異常處理時提供 finally 塊來執行任何清除操做。若是拋出一個異常,那麼相匹配的 catch 子句就會執行,而後控制就會進入 finally 塊(若是有的話)。 finalize—方法名。Java 技術容許使用 finalize() 方法在垃圾收集器將對象從內存中清除出去以前作必要的清理工做。這個方法是由垃圾收集器在肯定這個對象沒有被引用時對這個對象調用的。它是在 Object 類中定義的,所以全部的類都繼承了它。子類覆蓋 finalize() 方法以整理系統資源或者執行其餘清理工做。finalize() 方法是在垃圾收集器刪除對象以前對這個對象調用的。html

公衆號推薦

  • 全網惟一一個從0開始幫助Java開發者轉作大數據領域的公衆號~java

  • 大數據技術與架構或者搜索import_bigdata關注~web

  • 海量【java和大數據的面試題+視頻資料】整理在公衆號,關注後能夠下載~面試

2.int 和 Integer 有什麼區別算法

1,不管如何,Integer與new Integer不會相等。不會經歷拆箱過程,new出來的對象存放在堆,而非new的Integer常量則在常量池(在方法區),他們的內存地址不同,因此爲false。 2,兩個都是非new出來的Integer,若是數在-128到127之間,則是true,不然爲false。由於java在編譯Integer i2 = 128的時候,被翻譯成:Integer i2 = Integer.valueOf(128);而valueOf()函數會對-128到127之間的數進行緩存。 3,兩個都是new出來的,都爲false。仍是內存地址不同。 4,int和Integer(不管new否)比,都爲true,由於會把Integer自動拆箱爲int再去比spring

class TestInteger {  
    public static void main(String[] args) {  
        int i = 128;  
        Integer i2 = 128;  
        Integer i3 = new Integer(128);  
        System.out.println(i == i2); //Integer會自動拆箱爲int,因此爲true  
        System.out.println(i == i3); //true,理由同上  
        Integer i4 = 127;//編譯時被翻譯成:Integer i4 = Integer.valueOf(127);  
        Integer i5 = 127;  
        System.out.println(i4 == i5);//true  
        Integer i6 = 128;  
        Integer i7 = 128;  
        System.out.println(i6 == i7);//false  
        Integer i8 = new Integer(127);  
        System.out.println(i5 == i8); //false  
        Integer i9 = new Integer(128);  
        Integer i10 = new Integer(123);  
        System.out.println(i9 == i10);  //false  
    }  
}  。

複製代碼

3.重載和重寫的區別數據庫

  1. Override 特色

  一、覆蓋的方法的標誌必需要和被覆蓋的方法的標誌徹底匹配,才能達到覆蓋的效果;編程

  二、覆蓋的方法的返回值必須和被覆蓋的方法的返回一致;後端

  三、覆蓋的方法所拋出的異常必須和被覆蓋方法的所拋出的異常一致,或者是其子類;設計模式

  四、被覆蓋的方法不能爲private,不然在其子類中只是新定義了一個方法,並無對其進行覆蓋。

2.Overload 特色

  一、在使用重載時只能經過不一樣的參數樣式。例如,不一樣的參數類型,不一樣的參數個數,不一樣的參數順序(固然,同一方法內的幾個參數類型必須不同,例如能夠是fun(int, float), 可是不能爲fun(int, int));

  二、不能經過訪問權限、返回類型、拋出的異常進行重載;

  三、方法的異常類型和數目不會對重載形成影響;

  四、對於繼承來講,若是某一方法在父類中是訪問權限是priavte,那麼就不能在子類對其進行重載,若是定義的話,也只是定義了一個新方法,而不會達到重載的效果。 4.抽象類和接口有什麼區別

第一點. 接口是抽象類的變體,接口中全部的方法都是抽象的。而抽象類是聲明方法的存在而不去實現它的類。 第二點. 接口能夠多繼承,抽象類不行 第三點. 接口定義方法,不能實現,而抽象類能夠實現部分方法。 第四點. 接口中基本數據類型爲static 而抽類象不是的。 當你關注一個事物的本質的時候,用抽象類;當你關注一個操做的時候,用接口。 抽象類的功能要遠超過接口,可是,定義抽象類的代價高。由於高級語言來講(從實際設計上來講也是)每一個類只能繼承一個類。在這個類中,你必須繼承或編寫出其全部子類的

說說反射的用途及實現 Java反射機制主要用於實現如下功能。 (1)在運行時判斷任意一個對象所屬的類型。 (2)在運行時構造任意一個類的對象。 (3)在運行時判斷任意一個類所具備的成員變量和方法。 (4)在運行時調用任意一個對象的方法,甚至能夠調用private方法。 注意:上述功能都是在運行時環境中,而不是在編譯時環境中。

說說自定義註解的場景及實現 restful下方法上定義@LoggedIn判斷是否須要登陸

HTTP 請求的 GET 與 POST 方式的區別

  1. GET和POST與數據如何傳遞沒有關係

GET和POST是由HTTP協議定義的。在HTTP協議中,Method和Data(URL, Body, Header)是正交的兩個概念,也就是說,使用哪一個Method與應用層的數據如何傳輸是沒有相互關係的。

HTTP沒有要求,若是Method是POST數據就要放在BODY中。也沒有要求,若是Method是GET,數據(參數)就必定要放在URL中而不能放在BODY中。

那麼,網上流傳甚廣的這個說法是從何而來的呢?我在HTML標準中,找到了類似的描述。這和網上流傳的說法一致。可是這只是HTML標準對HTTP協議的用法的約定。怎麼能當成GET和POST的區別呢?

並且,現代的Web Server都是支持GET中包含BODY這樣的請求。雖然這種請求不可能從瀏覽器發出,可是如今的Web Server又不是隻給瀏覽器用,已經徹底地超出了HTML服務器的範疇了。

  1. HTTP協議對GET和POST都沒有對長度的限制

HTTP協議明確地指出了,HTTP頭和Body都沒有長度的要求。而對於URL長度上的限制,有兩方面的緣由形成:

  1. 瀏覽器。聽說早期的瀏覽器會對URL長度作限制。聽說IE對URL長度會限制在2048個字符內(流傳很廣,並且無數同事都表示認同)。但我本身試了一下,我構造了90K的URL經過IE9訪問live.com,是正常的。網上的東西,哪怕是Wikipedia上的,也不能信。
  2. 服務器。URL長了,對服務器處理也是一種負擔。本來一個會話就沒有多少數據,如今若是有人惡意地構造幾個幾M大小的URL,並不停地訪問你的服務器。服務器的最大併發數顯然會降低。另外一種攻擊方式是,把告訴服務器Content-Length是一個很大的數,而後只給服務器發一點兒數據,嘿嘿,服務器你就傻等着去吧。哪怕你有超時設置,這種故意的次次訪問超時也能讓服務器吃不了兜着走。有鑑於此,多數服務器出於安全啦、穩定啦方面的考慮,會給URL長度加限制。可是這個限制是針對全部HTTP請求的,與GET、POST沒有關係。

session 與 cookie 區別

是一個在客戶端一個在服務端。由於Cookie存在客戶端因此用戶能夠看見,因此也能夠編輯僞造,不是十分安全。

Session過多的時候會消耗服務器資源,因此大型網站會有專門的Session服務器,而Cookie存在客戶端因此沒什麼問題。

域的支持範圍不同,比方說a.com的Cookie在a.com下都能用,而www.a.com的Session在api.a.com下都不能用,解決這個問題的辦法是JSONP或者跨域資源共享。

session 分佈式處理

1.基於數據庫的Session共享 2.基於NFS共享文件系統 3.基於memcached 的session,如何保證 memcached 自己的高可用性? 4. 基於resin/tomcat web容器自己的session複製機制 5. 基於TT/Redis 或 jbosscache 進行 session 共享。 6. 基於cookie 進行session共享

JDBC 流程

1)加載驅動程序。 2)創建鏈接。 3)建立語句。 4)執行語句。 5)處理ResultSet

MVC 設計思想

equals 與 == 的區別 == 比較的是變量(棧)內存中存放的對象的(堆)內存地址,用來判斷兩個對象的地址是否相同,便是否是指相同一個對象。比較的是真正意義上的指針操做。 equals用來比較的是兩個對象的內容是否相等,因爲全部的類都是繼承自java.lang.Object類的,因此適用於全部對象,若是沒有對該方法進行覆蓋的話,調用的仍然是Object類中的方法,而Object中的equals方法返回的倒是==的判斷。

集合

List 和 Set 區別 List:1.能夠容許重複的對象。 2.能夠插入多個null元素。 3.是一個有序容器,保持了每一個元素的插入順序,輸出的順序就是插入的順序。 4.經常使用的實現類有 ArrayList、LinkedList 和 Vector。ArrayList 最爲流行,它提供了使用索引的隨意訪問,而 LinkedList 則對於常常須要從 List 中添加或刪除元素的場合更爲合適。 Set:1.不容許重複對象 2. 無序容器,你沒法保證每一個元素的存儲順序,TreeSet經過 Comparator 或者 Comparable 維護了一個排序順序。 3. 只容許一個 null 元素 4.Set 接口最流行的幾個實現類是 HashSet、LinkedHashSet 以及 TreeSet。最流行的是基於 HashMap 實現的 HashSet;TreeSet 還實現了 SortedSet 接口,所以 TreeSet 是一個根據其 compare() 和 compareTo() 的定義進行排序的有序容器。 1.Map不是collection的子接口或者實現類。Map是一個接口。 2.Map 的 每一個 Entry 都持有兩個對象,也就是一個鍵一個值,Map 可能會持有相同的值對象但鍵對象必須是惟一的。 3. TreeMap 也經過 Comparator 或者 Comparable 維護了一個排序順序。 4. Map 裏你能夠擁有隨意個 null 值但最多隻能有一個 null 鍵。 5.Map 接口最流行的幾個實現類是 HashMap、LinkedHashMap、Hashtable 和 TreeMap。(HashMap、TreeMap最經常使用)

Arraylist 與 LinkedList 區別

  1. ArrayList是實現了基於動態數組的數據結構,而LinkedList是基於鏈表的數據結構;
  2. 對於隨機訪問get和set,ArrayList要優於LinkedList,由於LinkedList要移動指針;
  3. 對於添加和刪除操做add和remove,通常你們都會說LinkedList要比ArrayList快,由於ArrayList要移動數據。可是實際狀況並不是這樣,對於添加或刪除,LinkedList和ArrayList並不能明確說明誰快誰慢

ArrayList 與 Vector 區別

  1. Vector & ArrayList 1) Vector的方法都是同步的(Synchronized),是線程安全的(thread-safe),而ArrayList的方法不是,因爲線程的同步必然要影響性能,所以,ArrayList的性能比Vector好。 2) 當Vector或ArrayList中的元素超過它的初始大小時,Vector會將它的容量翻倍,而ArrayList只增長50%的大小,這樣,ArrayList就有利於節約內存空間。

HashMap 和 Hashtable 的區別 Hashtable和HashMap它們的性能方面的比較相似 Vector和ArrayList,好比Hashtable的方法是同步的,而HashMap的不是。

HashSet 和 HashMap 區別 (1)HashSet是set的一個實現類,hashMap是Map的一個實現類,同時hashMap是hashTable的替代品(爲何後面會講到). (2)HashSet以對象做爲元素,而HashMap以(key-value)的一組對象做爲元素,且HashSet拒絕接受重複的對象.HashMap能夠看做三個視圖:key的Set,value的Collection,Entry的Set。 這裏HashSet就是其實就是HashMap的一個視圖。 HashSet內部就是使用Hashmap實現的,和Hashmap不一樣的是它不須要Key和Value兩個值。 往hashset中插入對象其實只不過是內部作了 public boolean add(Object o) { return map.put(o, PRESENT)==null; }

HashMap 和 ConcurrentHashMap 的區別

HashMap 的工做原理及代碼實現

ConcurrentHashMap 的工做原理及代碼實現 jdk1.7中採用Segment + HashEntry的方式進行實現 1.8中放棄了Segment臃腫的設計,取而代之的是採用Node + CAS + Synchronized來保證併發安全進行實現

線程

1.建立線程的方式及實現

1)繼承Thread類建立線程 2)實現Runnable接口建立線程 3)使用Callable和Future建立線程

2.sleep() 、join()、yield()有什麼區別

sleep()   sleep()方法須要指定等待的時間,它可讓當前正在執行的線程在指定的時間內暫停執行,進入阻塞狀態,該方法既可讓其餘同優先級或者高優先級的線程獲得執行的機會,也可讓低優先級的線程獲得執行機會。可是sleep()方法不會釋放「鎖標誌」,也就是說若是有synchronized同步塊,其餘線程仍然不能訪問共享數據。    wait()   wait()方法須要和notify()及notifyAll()兩個方法一塊兒介紹,這三個方法用於協調多個線程對共享數據的存取,因此必須在synchronized語句塊內使用,也就是說,調用wait(),notify()和notifyAll()的任務在調用這些方法前必須擁有對象的鎖。注意,它們都是Object類的方法,而不是Thread類的方法。   wait()方法與sleep()方法的不一樣之處在於,wait()方法會釋放對象的「鎖標誌」。當調用某一對象的wait()方法後,會使當前線程暫停執行,並將當前線程放入對象等待池中,直到調用了notify()方法後,將從對象等待池中移出任意一個線程並放入鎖標誌等待池中,只有鎖標誌等待池中的線程能夠獲取鎖標誌,它們隨時準備爭奪鎖的擁有權。當調用了某個對象的notifyAll()方法,會將對象等待池中的全部線程都移動到該對象的鎖標誌等待池。   除了使用notify()和notifyAll()方法,還能夠使用帶毫秒參數的wait(long timeout)方法,效果是在延遲timeout毫秒後,被暫停的線程將被恢復到鎖標誌等待池。   此外,wait(),notify()及notifyAll()只能在synchronized語句中使用,可是若是使用的是ReenTrantLock實現同步,該如何達到這三個方法的效果呢?解決方法是使用ReenTrantLock.newCondition()獲取一個Condition類對象,而後Condition的await(),signal()以及signalAll()分別對應上面的三個方法。

yield()   yield()方法和sleep()方法相似,也不會釋放「鎖標誌」,區別在於,它沒有參數,即yield()方法只是使當前線程從新回到可執行狀態,因此執行yield()的線程有可能在進入到可執行狀態後立刻又被執行,另外yield()方法只能使同優先級或者高優先級的線程獲得執行機會,這也和sleep()方法不一樣。

join()   join()方法會使當前線程等待調用join()方法的線程結束後才能繼續執行 3.說說 CountDownLatch 原理 www.jianshu.com/p/38c39e00e… 4.說說 CyclicBarrier 原理 www.jianshu.com/p/060761df1… 說說 Semaphore 原理

說說 Exchanger 原理 www.jianshu.com/p/1eab24ca3… 說說 CountDownLatch 與 CyclicBarrier 區別

ThreadLocal 原理分析

ThreadLocal提供了set和get訪問器用來訪問與當前線程相關聯的線程局部變量。 能夠從ThreadLocal的get函數中看出來,其中getmap函數是用t做爲參數,這裏t就是當前執行的線程。

從而得知,get函數就是從當前線程的threadlocalmap中取出當前線程對應的變量的副本【注意,變量是保存在線程中的,而不是保存在ThreadLocal變量中】。當前線程中,有一個變量引用名字是threadLocals,這個引用是在ThreadLocal類中createmap函數內初始化的。每一個線程都有一個這樣的threadLocals引用的ThreadLocalMap,以ThreadLocal和ThreadLocal對象聲明的變量類型做爲參數。這樣,咱們所使用的ThreadLocal變量的實際數據,經過get函數取值的時候,就是經過取出Thread中threadLocals引用的map,而後從這個map中根據當前threadLocal做爲參數,取出數據。如今,變量的副本從哪裏取出來的(本文章提出的第一個問題)已經確認解決了。

ThreadLocal操做值的時候是取得當前線程的ThreadLocalMap對象,而後把值設置到了這個對象中,這樣對於不一樣的線程獲得的就是不一樣的ThreadLocalMap,那麼向其中保存值或者修改值都只是會影響到當前線程,這樣就保證了線程安全。

講講線程池的實現原理

Java線程池的工廠類:Executors類, 初始化4種類型的線程池: newFixedThreadPool() 說明:初始化一個指定線程數的線程池,其中corePoolSize == maxiPoolSize,使用LinkedBlockingQuene做爲阻塞隊列 特色:即便當線程池沒有可執行任務時,也不會釋放線程。 newCachedThreadPool() 說明:初始化一個能夠緩存線程的線程池,默認緩存60s,線程池的線程數可達到Integer.MAX_VALUE,即2147483647,內部使用SynchronousQueue做爲阻塞隊列; 特色:在沒有任務執行時,當線程的空閒時間超過keepAliveTime,會自動釋放線程資源;當提交新任務時,若是沒有空閒線程,則建立新線程執行任務,會致使必定的系統開銷; 所以,使用時要注意控制併發的任務數,防止因建立大量的線程致使而下降性能。 newSingleThreadExecutor() 說明:初始化只有一個線程的線程池,內部使用LinkedBlockingQueue做爲阻塞隊列。 特色:若是該線程異常結束,會從新建立一個新的線程繼續執行任務,惟一的線程能夠保證所提交任務的順序執行 newScheduledThreadPool() 特定:初始化的線程池能夠在指定的時間內週期性的執行所提交的任務,在實際的業務場景中能夠使用該線程池按期的同步數據。 總結:除了newScheduledThreadPool的內部實現特殊一點以外,其它線程池內部都是基於ThreadPoolExecutor類(Executor的子類)實現的。

線程池的幾種方式

ThreadPoolExecutor(corePoolSize,maxPoolSize,keepAliveTime,timeUnit,workQueue,threadFactory,handle);
方法參數: corePoolSize:核心線程數 maxPoolSize:最大線程數 keepAliveTime:線程存活時間(在corePore<*<maxPoolSize狀況下有用) timeUnit:存活時間的時間單位 workQueue:阻塞隊列(用來保存等待被執行的任務) 注:關於workQueue參數的取值,JDK提供了4種阻塞隊列類型供選擇: ArrayBlockingQueue:基於數組結構的有界阻塞隊列,按FIFO排序任務; inkedBlockingQuene:基於鏈表結構的阻塞隊列,按FIFO排序任務,吞吐量一般要高於
SynchronousQuene:一個不存儲元素的阻塞隊列,每一個插入操做必須等到另外一個線程調用移除操做,不然插入操做一直處於阻塞狀態,吞吐量一般要高於ArrayBlockingQuene; PriorityBlockingQuene:具備優先級的無界阻塞隊列; threadFactory:線程工廠,主要用來建立線程; handler:表示當拒絕處理任務時的策略,有如下四種取值 注:當線程池的飽和策略,當阻塞隊列滿了,且沒有空閒的工做線程,若是繼續提交任務,必須採起一種策略處理該任務,線程池提供了4種策略: ThreadPoolExecutor.AbortPolicy:丟棄任務並拋出RejectedExecutionException異常。 ThreadPoolExecutor.DiscardPolicy:也是丟棄任務,可是不拋出異常。 ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊列最前面的任務,而後從新嘗試執行任務(重複此過程) ThreadPoolExecutor.CallerRunsPolicy:由調用線程處理該任務 固然也能夠根據應用場景實現RejectedExecutionHandler接口,自定義飽和策略,如記錄日誌或持久化存儲不能處理的任務。

線程的生命週期 當線程被建立並啓動之後,它既不是一啓動就進入了執行狀態,也不是一直處於執行狀態。在線程的生命週期中,它要通過新建(New)、就緒(Runnable)、運行(Running)、阻塞(Blocked)和死亡(Dead)5種狀態。尤爲是當線程啓動之後,它不可能一直"霸佔"着CPU獨自運行,因此CPU須要在多條線程之間切換,因而線程狀態也會屢次在運行、阻塞之間切換 鎖機制 說說線程安全問題

volatile 實現原理 volatile 關鍵字,具備兩個特性:1. 內存的可見性,2. 禁止指令重排序優化。 內存可見性是指:被 volatile 關鍵字修飾的變量,當線程要對這個變量執行的寫操做,都不會寫入本地緩存,而是直接刷入主內存中。當線程讀取被 volatile 關鍵字修飾的變量時,也是直接從主內存中讀取。(簡單的說,一個線程修改的狀態對另外一個線程是可見的)。注意:volatile 不能保證原子性。 禁止指令重排序優化:有volatile修飾的變量,賦值後多執行了一個 「load addl $0x0, (%esp)」 操做,這個操做至關於一個內存屏障,保證指令重排序時不會把後面的指令重排序到內存屏障以前的位置。

synchronize 實現原理 synchronized 代碼塊是經過 monitorenter 和 monitorexit 指令實現的。synchronized 方法雖然在 vm 字節碼層面並無任何特別的指令來實現被 synchronized 修飾的方法,而是在 Class 文件的方法表中將該方法的 access_flags 字段中的 synchronized 標誌位置1,表示該方法是同步方法。鎖的實現有偏向鎖、輕量級鎖和重量級鎖,其中偏向鎖和輕量級鎖是 JDK 針對鎖的優化措施。在多線程的競爭下鎖會升級,依次從偏向鎖 -> 輕量級鎖 -> 重量級鎖,這裏的鎖只能升級但不能降級。在 Java 對象頭中的 Mark Word 中存儲了關於鎖的標誌位,其中:無鎖和偏向鎖爲 00, 輕量級鎖爲 01,重量級鎖爲 10。 引入偏向鎖主要目的是:爲了在無多線程競爭的狀況下儘可能減小沒必要要的輕量級鎖執行路徑(在無競爭的狀況下把整個同步都消除掉,連 CAS 操做都不作了)。 引入輕量級鎖的主要目的是:在沒有多線程競爭的前提下,減小傳統的重量級鎖使用操做系統互斥量產生的性能消耗(在無競爭的狀況下使用 CAS 操做去消除同步使用的互斥量)。 重量級鎖經過對象內部的監視器(monitor)實現,其中 monitor 的本質是依賴於底層操做系統的 Mutex Lock 實現,操做系統實現線程之間的切換須要從用戶態到內核態的切換,切換成本很是高。

synchronized 與 lock 的區別 二者區別: 1.首先synchronized是java內置關鍵字,在jvm層面,Lock是個java類; 2.synchronized沒法判斷是否獲取鎖的狀態,Lock能夠判斷是否獲取到鎖; 3.synchronized會自動釋放鎖(a 線程執行完同步代碼會釋放鎖 ;b 線程執行過程當中發生異常會釋放鎖),Lock需在finally中手工釋放鎖(unlock()方法釋放鎖),不然容易形成線程死鎖; 4.用synchronized關鍵字的兩個線程1和線程2,若是當前線程1得到鎖,線程2線程等待。若是線程1阻塞,線程2則會一直等待下去,而Lock鎖就不必定會等待下去,若是嘗試獲取不到鎖,線程能夠不用一直等待就結束了; 5.synchronized的鎖可重入、不可中斷、非公平,而Lock鎖可重入、可判斷、可公平(二者皆可) 6.Lock鎖適合大量同步的代碼的同步問題,synchronized鎖適合代碼少許的同步問題。

CAS 樂觀鎖 樂觀鎖 樂觀鎖( Optimistic Locking)實際上是一種思想。相對悲觀鎖而言,樂觀鎖假設認爲數據通常狀況下不會形成衝突,因此在數據進行提交更新的時候,纔會正式對數據的衝突與否進行檢測,若是發現衝突了,則讓返回用戶錯誤的信息,讓用戶決定如何去作。 上面提到的樂觀鎖的概念中其實已經闡述了他的具體實現細節:主要就是兩個步驟:衝突檢測和數據更新。其實現方式有一種比較典型的就是Compare and Swap(CAS)。 CAS CAS是項樂觀鎖技術,當多個線程嘗試使用CAS同時更新同一個變量時,只有其中一個線程能更新變量的值,而其它線程都失敗,失敗的線程並不會被掛起,而是被告知此次競爭中失敗,並能夠再次嘗試。 CAS 操做包含三個操做數 —— 內存位置(V)、預期原值(A)和新值(B)。若是內存位置的值與預期原值相匹配,那麼處理器會自動將該位置值更新爲新值。不然,處理器不作任何操做。不管哪一種狀況,它都會在 CAS 指令以前返回該位置的值。(在 CAS 的一些特殊狀況下將僅返回 CAS 是否成功,而不提取當前值。)CAS 有效地說明了「我認爲位置 V 應該包含值 A;若是包含該值,則將 B 放到這個位置;不然,不要更改該位置,只告訴我這個位置如今的值便可。」這其實和樂觀鎖的衝突檢查+數據更新的原理是同樣的。

ABA 問題

樂觀鎖用到的機制就是CAS,Compare and Swap。 CAS有3個操做數,內存值V,舊的預期值A,要修改的新值B。當且僅當預期值A和內存值V相同時,將內存值V修改成B,不然什麼都不作。 樂觀鎖的業務場景及實現方式 CAS看起來很爽,可是會致使「ABA問題」。 CAS算法實現一個重要前提須要取出內存中某時刻的數據,而在下時刻比較並替換,那麼在這個時間差類會致使數據的變化。 好比說一個線程one從內存位置V中取出A,這時候另外一個線程two也從內存中取出A,而且two進行了一些操做變成了B,而後two又將V位置的數據變成A,這時候線程one進行CAS操做發現內存中仍然是A,而後one操做成功。儘管線程one的CAS操做成功,可是不表明這個過程就是沒有問題的。若是鏈表的頭在變化了兩次後恢復了原值,可是不表明鏈表就沒有變化。所以前面提到的原子操做AtomicStampedReference/AtomicMarkableReference就頗有用了。這容許一對變化的元素進行原子操做。

核心篇

數據存儲

MySQL 索引使用的注意事項

說說反模式設計

說說分庫與分表設計

分庫與分錶帶來的分佈式困境與應對之策

說說 SQL 優化之道

一、應儘可能避免在 where 子句中使用!=或<>操做符,不然將引擎放棄使用索引而進行全表掃描。
二、對查詢進行優化,應儘可能避免全表掃描,首先應考慮在 where 及 order by 涉及的列上創建索引。
三、應儘可能避免在 where 子句中對字段進行 null 值判斷,不然將致使引擎放棄使用索引而進行全表掃描,如:
select id from t where num is null
能夠在num上設置默認值0,確保表中num列沒有null值,而後這樣查詢:
select id from t where num=0
四、儘可能避免在 where 子句中使用 or 來鏈接條件,不然將致使引擎放棄使用索引而進行全表掃描,如:
select id from t where num=10 or num=20
能夠這樣查詢:
select id from t where num=10
union all
select id from t where num=20
五、下面的查詢也將致使全表掃描:(不能前置百分號)
select id from t where name like ‘�c%’
若要提升效率,能夠考慮全文檢索。
六、in 和 not in 也要慎用,不然會致使全表掃描,如:
select id from t where num in(1,2,3)
對於連續的數值,能用 between 就不要用 in 了:
select id from t where num between 1 and 3
七、若是在 where 子句中使用參數,也會致使全表掃。由於SQL只有在運行時纔會解析局部變量,但優化程序不能將訪問計劃的選擇推遲到運行時;它必須在編譯時進行選擇。然 而,若是在編譯時創建訪問計劃,變量的值仍是未知的,於是沒法做爲索引選擇的輸入項。以下面語句將進行全表掃描:
select id from t where num=@num
能夠改成強制查詢使用索引:
select id from t with(index(索引名)) where num=@num
八、應儘可能避免在 where 子句中對字段進行表達式操做,這將致使引擎放棄使用索引而進行全表掃描。如:
select id from t where num/2=100
應改成:
select id from t where num=100*2
九、應儘可能避免在where子句中對字段進行函數操做,這將致使引擎放棄使用索引而進行全表掃描。如:
select id from t where substring(name,1,3)=’abc’–name以abc開頭的id
select id from t where datediff(day,createdate,’2005-11-30′)=0–’2005-11-30′生成的id
應改成:
select id from t where name like ‘abc%’
select id from t where createdate>=’2005-11-30′ and createdate<’2005-12-1′
十、不要在 where 子句中的「=」左邊進行函數、算術運算或其餘表達式運算,不然系統將可能沒法正確使用索引。
十一、在使用索引字段做爲條件時,若是該索引是複合索引,那麼必須使用到該索引中的第一個字段做爲條件時才能保證系統使用該索引,不然該索引將不會被使 用,而且應儘量的讓字段順序與索引順序相一致。
十二、不要寫一些沒有意義的查詢,如須要生成一個空表結構:
select col1,col2 into #t from t where 1=0
這類代碼不會返回任何結果集,可是會消耗系統資源的,應改爲這樣:
create table #t(…)
1三、不少時候用 exists 代替 in 是一個好的選擇:
select num from a where num in(select num from b)
用下面的語句替換:
select num from a where exists(select 1 from b where num=a.num)
1四、並非全部索引對查詢都有效,SQL是根據表中數據來進行查詢優化的,當索引列有大量數據重複時,SQL查詢可能不會去利用索引,如一表中有字段 sex,male、female幾乎各一半,那麼即便在sex上建了索引也對查詢效率起不了做用。
1五、索引並非越多越好,索引當然能夠提升相應的 select 的效率,但同時也下降了 insert 及 update 的效率,由於 insert 或 update 時有可能會重建索引,因此怎樣建索引須要慎重考慮,視具體狀況而定。一個表的索引數最好不要超過6個,若太多則應考慮一些不常使用到的列上建的索引是否有 必要。
16.應儘量的避免更新 clustered 索引數據列,由於 clustered 索引數據列的順序就是表記錄的物理存儲順序,一旦該列值改變將致使整個表記錄的順序的調整,會耗費至關大的資源。若應用系統須要頻繁更新 clustered 索引數據列,那麼須要考慮是否應將該索引建爲 clustered 索引。
1七、儘可能使用數字型字段,若只含數值信息的字段儘可能不要設計爲字符型,這會下降查詢和鏈接的性能,並會增長存儲開銷。這是由於引擎在處理查詢和鏈接時會 逐個比較字符串中每個字符,而對於數字型而言只須要比較一次就夠了。
1八、儘量的使用 varchar/nvarchar 代替 char/nchar ,由於首先變長字段存儲空間小,能夠節省存儲空間,其次對於查詢來講,在一個相對較小的字段內搜索效率顯然要高些。
1九、任何地方都不要使用 select * from t ,用具體的字段列表代替「*」,不要返回用不到的任何字段。
20、儘可能使用表變量來代替臨時表。若是表變量包含大量數據,請注意索引很是有限(只有主鍵索引)。
2一、避免頻繁建立和刪除臨時表,以減小系統表資源的消耗。
2二、臨時表並非不可以使用,適當地使用它們能夠使某些例程更有效,例如,當須要重複引用大型表或經常使用表中的某個數據集時。可是,對於一次性事件,最好使 用導出表。
2三、在新建臨時表時,若是一次性插入數據量很大,那麼能夠使用 select into 代替 create table,避免形成大量 log ,以提升速度;若是數據量不大,爲了緩和系統表的資源,應先create table,而後insert。
2四、若是使用到了臨時表,在存儲過程的最後務必將全部的臨時表顯式刪除,先 truncate table ,而後 drop table ,這樣能夠避免系統表的較長時間鎖定。
2五、儘可能避免使用遊標,由於遊標的效率較差,若是遊標操做的數據超過1萬行,那麼就應該考慮改寫。
2六、使用基於遊標的方法或臨時表方法以前,應先尋找基於集的解決方案來解決問題,基於集的方法一般更有效。
2七、與臨時表同樣,遊標並非不可以使用。對小型數據集使用 FAST_FORWARD 遊標一般要優於其餘逐行處理方法,尤爲是在必須引用幾個表才能得到所需的數據時。在結果集中包括「合計」的例程一般要比使用遊標執行的速度快。若是開發時 間容許,基於遊標的方法和基於集的方法均可以嘗試一下,看哪種方法的效果更好。
2八、在全部的存儲過程和觸發器的開始處設置 SET NOCOUNT ON ,在結束時設置 SET NOCOUNT OFF 。無需在執行存儲過程和觸發器的每一個語句後向客戶端發送 DONE_IN_PROC 消息。
2九、儘可能避免向客戶端返回大數據量,若數據量過大,應該考慮相應需求是否合理。
30、儘可能避免大事務操做,提升系統併發能力。
複製代碼

MySQL 遇到的死鎖問題

存儲引擎的 InnoDB 與 MyISAM

數據庫索引的原理

爲何要用 B-tree

彙集索引與非彙集索引的區別

limit 20000 加載很慢怎麼解決

1.子查詢優化法
先找出第一條數據,而後大於等於這條數據的id就是要獲取的數據
2.倒排表優化法
倒排表法相似創建索引,用一張表來維護頁數,而後經過高效的鏈接獲得數據
缺點:只適合數據數固定的狀況,數據不能刪除,維護頁表困難
3.反向查找優化法
當偏移超過一半記錄數的時候,先用排序,這樣偏移就反轉了
缺點:order by優化比較麻煩,要增長索引,索引影響數據的修改效率,而且要知道總記錄數,偏移大於數據的一半
4.limit限制優化法
把limit偏移量限制低於某個數。。超過這個數等於沒數據,我記得alibaba的dba說過他們是這樣作的
5.只查索引法
複製代碼

選擇合適的分佈式主鍵方案

  1. 數據庫自增加序列或字段

最多見的方式。利用數據庫,全數據庫惟一。

優勢:

1)簡單,代碼方便,性能能夠接受。

2)數字ID自然排序,對分頁或者須要排序的結果頗有幫助。

缺點: 1)不一樣數據庫語法和實現不一樣,數據庫遷移的時候或多數據庫版本支持的時候須要處理。 2)在單個數據庫或讀寫分離或一主多從的狀況下,只有一個主庫能夠生成。有單點故障的風險。 3)在性能達不到要求的狀況下,比較難於擴展。 4)若是碰見多個系統須要合併或者涉及到數據遷移會至關痛苦。 5)分表分庫的時候會有麻煩。 優化方案: 1)針對主庫單點,若是有多個Master庫,則每一個Master庫設置的起始數字不同,步長同樣,能夠是Master的個數。好比:Master1 生成的是 1,4,7,10,Master2生成的是2,5,8,11 Master3生成的是 3,6,9,12。這樣就能夠有效生成集羣中的惟一ID,也能夠大大下降ID生成數據庫操做的負載。 2. UUID 常見的方式。能夠利用數據庫也能夠利用程序生成,通常來講全球惟一。 優勢 1)簡單,代碼方便。 2)生成ID性能很是好,基本不會有性能問題。 3)全球惟一,在碰見數據遷移,系統數據合併,或者數據庫變動等狀況下,能夠從容應對。 缺點: 1)沒有排序,沒法保證趨勢遞增。 2)UUID每每是使用字符串存儲,查詢的效率比較低。 3)存儲空間比較大,若是是海量數據庫,就須要考慮存儲量的問題。 4)傳輸數據量大 5)不可讀。 4. Redis生成ID 當使用數據庫來生成ID性能不夠要求的時候,咱們能夠嘗試使用Redis來生成ID。這主要依賴於Redis是單線程的,因此也能夠用生成全局惟一的ID。能夠用Redis的原子操做 INCR和INCRBY來實現。 5. Twitter的snowflake算法

ObjectId 規則

聊聊 MongoDB 使用場景

倒排索引

聊聊 ElasticSearch 使用場景

緩存使用

Redis 有哪些類型
Redis 內部結構
聊聊 Redis 使用場景
Redis 持久化機制
Redis 如何實現持久化
Redis 集羣方案與實現
Redis 爲何是單線程的
緩存奔潰
緩存降級
使用緩存的合理性問題
消息隊列
消息隊列的使用場景
消息的重發補償解決思路
消息的冪等性解決思路
消息的堆積解決思路
本身如何實現消息隊列
如何保證消息的有序性
複製代碼

框架

Spring

BeanFactory 和 ApplicationContext 有什麼區別
BeanFacotry是spring中比較原始的Factory。如XMLBeanFactory就是一種典型的BeanFactory。原始的BeanFactory沒法支持spring的許多插件,如AOP功能、Web應用等。 
ApplicationContext接口,它由BeanFactory接口派生而來,於是提供BeanFactory全部的功能。ApplicationContext以一種更向面向框架的方式工做以及對上下文進行分層和實現繼承,ApplicationContext包還提供瞭如下的功能: 
  • MessageSource, 提供國際化的消息訪問  
  • 資源訪問,如URL和文件  
  • 事件傳播  
  • 載入多個(有繼承關係)上下文 ,使得每個上下文都專一於一個特定的層次,好比應用的web層  

Spring Bean 的生命週期
Spring上下文中的Bean也相似,【Spring上下文的生命週期】
1. 實例化一個Bean,也就是咱們一般說的new
2. 按照Spring上下文對實例化的Bean進行配置,也就是IOC注入
3. 若是這個Bean實現了BeanNameAware接口,會調用它實現的setBeanName(String beanId)方法,此處傳遞的是Spring配置文件中Bean的ID
4. 若是這個Bean實現了BeanFactoryAware接口,會調用它實現的setBeanFactory(),傳遞的是Spring工廠自己(能夠用這個方法獲取到其餘Bean)
5. 若是這個Bean實現了ApplicationContextAware接口,會調用setApplicationContext(ApplicationContext)方法,傳入Spring上下文,該方式一樣能夠實現步驟4,但比4更好,覺得ApplicationContext是BeanFactory的子接口,有更多的實現方法
6. 若是這個Bean關聯了BeanPostProcessor接口,將會調用postProcessBeforeInitialization(Object obj, String s)方法,BeanPostProcessor常常被用做是Bean內容的更改,而且因爲這個是在Bean初始化結束時調用After方法,也可用於內存或緩存技術
7. 若是這個Bean在Spring配置文件中配置了init-method屬性會自動調用其配置的初始化方法
8. 若是這個Bean關聯了BeanPostProcessor接口,將會調用postAfterInitialization(Object obj, String s)方法
注意:以上工做完成之後就能夠用這個Bean了,那這個Bean是一個single的,因此通常狀況下咱們調用同一個ID的Bean會是在內容地址相同的實例
9. 當Bean再也不須要時,會通過清理階段,若是Bean實現了DisposableBean接口,會調用其實現的destroy方法
10. 最後,若是這個Bean的Spring配置中配置了destroy-method屬性,會自動調用其配置的銷燬方法
以上10步驟能夠做爲面試或者筆試的模板,另外咱們這裏描述的是應用Spring上下文Bean的生命週期,若是應用Spring的工廠也就是BeanFactory的話去掉第5步就Ok了

Spring IOC 如何實現
 IOC容器就是具備依賴注入功能的容器,IOC容器負責實例化、定位、配置應用程序中的對象及創建這些對象間的依賴。應用程序無需直接在代碼中new相關的對象,應用程序由IOC容器進行組裝。在Spring中BeanFactory是IOC容器的實際表明者。
Spring IOC容器如何知道哪些是它管理的對象呢?這就須要配置文件,Spring IOC容器經過讀取配置文件中的配置元數據,經過元數據對應用中的各個對象進行實例化及裝配。通常使用基於xml配置文件進行配置元數據,並且Spring與配置文件徹底解耦的,能夠使用其餘任何可能的方式進行配置元數據,好比註解、基於java文件的、基於屬性文件的配置均可以。

說說 Spring AOP
Spring AOP 實現原理
1.IOC
許多應用都是經過彼此間的相互合做來實現業務邏輯的,如類A要調用類B的方法,之前咱們都是在類A中,經過自身new一個類B,而後在調用類B的方法,如今咱們把new類B的事情交給spring來作,在咱們調用的時候,容器會爲咱們實例化。
2. IOC容器的初始化過程
資源定位,即定義bean的xml-------》載入--------》IOC容器註冊,註冊beanDefinition
IOC容器的初始化過程,通常不包含bean的依賴注入的實現,在spring IOC設計中,bean的註冊和依賴注入是兩個過程,,依賴注入通常發生在應用第一次索取bean的時候,可是也能夠在xm中配置,在容器初始化的時候,這個bean就完成了初始化。
3. 三種注入方式,構造器、接口、set注入,咱們經常使用的是set注入
4. bean是如何建立---  工廠模式
5. 數據是如何注入-------反射
6.AOP
面向切面編程,在咱們的應用中,常常須要作一些事情,可是這些事情與核心業務無關,好比,要記錄全部update*方法的執行時間時間,操做人等等信息,記錄到日誌,
經過spring的AOP技術,就能夠在不修改update*的代碼的狀況下完成該需求。
7.AOP的實現原理------代理

動態代理(cglib 與 JDK)
靜態代理是經過在代碼中顯式定義一個業務實現類一個代理,在代理類中對同名的業務方法進行包裝,用戶經過代理類調用被包裝過的業務方法;
JDK動態代理是經過接口中的方法名,在動態生成的代理類中調用業務實現類的同名方法;
CGlib動態代理是經過繼承業務類,生成的動態代理類是業務類的子類,經過重寫業務方法進行代理;

Spring 事務實現方式
實現spring事務的四種方式分別爲:
(1)編程式事務管理:須要手動編寫代碼,在實際開發中不多使用
(2)聲明式事務管理:
(2.1)基於TransactionProxyFactoryBean的方式,須要爲每一個進行事務管理的類作相應配置
(2.2)基於AspectJ的XML方式,不須要改動類,在XML文件中配置好便可
(2.3)基於註解的方式,配置簡單,須要在業務層類中添加註解

Spring 事務底層原理
工做原理及實現
a、劃分處理單元——IOC
因爲spring解決的問題是對單個數據庫進行局部事務處理的,具體的實現首相用spring中的IOC劃分了事務處理單元。而且將對事務的各類配置放到了ioc容器中(設置事務管理器,設置事務的傳播特性及隔離機制)。
b、AOP攔截須要進行事務處理的類
Spring事務處理模塊是經過AOP功能來實現聲明式事務處理的,具體操做(好比事務實行的配置和讀取,事務對象的抽象),用TransactionProxyFactoryBean接口來使用AOP功能,生成proxy代理對象,經過TransactionInterceptor完成對代理方法的攔截,將事務處理的功能編織到攔截的方法中。
讀取ioc容器事務配置屬性,轉化爲spring事務處理須要的內部數據結構(TransactionAttributeSourceAdvisor),轉化爲TransactionAttribute表示的數據對象。
c、對事物處理實現(事務的生成、提交、回滾、掛起)
spring委託給具體的事務處理器實現。實現了一個抽象和適配。適配的具體事務處理器:DataSource數據源支持、hibernate數據源事務處理支持、JDO數據源事務處理支持,JPA、JTA數據源事務處理支持。這些支持都是經過設計PlatformTransactionManager、AbstractPlatforTransaction一系列事務處理的支持。
爲經常使用數據源支持提供了一系列的TransactionManager。
d、結合
PlatformTransactionManager實現了TransactionInterception接口,讓其與TransactionProxyFactoryBean結合起來,造成一個Spring聲明式事務處理的設計體系。
如何自定義註解實現功能


Spring MVC 運行流程

Spring MVC 啓動流程

Spring 的單例實現原理
你們真正要記住的是Spring對bean實例的建立是採用單例註冊表的方式進行實現的,而這個註冊表的緩存是HashMap對象,若是配置文件中的配置信息不要求使用單例,Spring會採用新建實例的方式返回對象實例

Spring 框架中用到了哪些設計模式
Spring框架中使用到了大量的設計模式,下面列舉了比較有表明性的:
代理模式—在AOP和remoting中被用的比較多。
單例模式—在spring配置文件中定義的bean默認爲單例模式。
模板方法—用來解決代碼重複的問題。好比. RestTemplate, JmsTemplate, JpaTemplate。
工廠模式—BeanFactory用來建立對象的實例。
適配器--spring aop
裝飾器--spring data  hashmapper
觀察者-- spring 時間驅動模型
回調--Spring ResourceLoaderAware回調接口
Spring 其餘產品(Srping Boot、Spring Cloud、Spring Secuirity、Spring Data、Spring AMQP 等)
複製代碼

Netty

爲何選擇 Netty
說說業務中,Netty 的使用場景

原生的 NIO 在 JDK 1.7 版本存在 epoll bug
https://www.cnblogs.com/JAYIT/p/8241634.html
Selector BUG出現的緣由
若Selector的輪詢結果爲空,也沒有wakeup或新消息處理,則發生空輪詢,CPU使用率100%,
Netty的解決辦法
對Selector的select操做週期進行統計,每完成一次空的select操做進行一次計數,
若在某個週期內連續發生N次空輪詢,則觸發了epoll死循環bug。
重建Selector,判斷是不是其餘線程發起的重建請求,若不是則將原SocketChannel從舊的Selector上去除註冊,從新註冊到新的Selector上,並將原來的Selector關閉。

什麼是TCP 粘包/拆包、TCP粘包/拆包的解決辦法
發生TCP粘包或拆包有不少緣由,現列出常見的幾點,可能不全面,歡迎補充,
一、要發送的數據大於TCP發送緩衝區剩餘空間大小,將會發生拆包。
二、待發送數據大於MSS(最大報文長度),TCP在傳輸前將進行拆包。
三、要發送的數據小於TCP發送緩衝區的大小,TCP將屢次寫入緩衝區的數據一次發送出去,將會發生粘包。
四、接收數據端的應用層沒有及時讀取接收緩衝區中的數據,將發生粘包。
粘包、拆包解決辦法
經過以上分析,咱們清楚了粘包或拆包發生的緣由,那麼如何解決這個問題呢?解決問題的關鍵在於如何給每一個數據包添加邊界信息,經常使用的方法有以下幾個:
一、發送端給每一個數據包添加包首部,首部中應該至少包含數據包的長度,這樣接收端在接收到數據後,經過讀取包首部的長度字段,便知道每個數據包的實際長度了。
二、發送端將每一個數據包封裝爲固定長度(不夠的能夠經過補0填充),這樣接收端每次從接收緩衝區中讀取固定長度的數據就天然而然的把每一個數據包拆分開來。
三、能夠在數據包之間設置邊界,如添加特殊符號,這樣,接收端經過這個邊界就能夠將不一樣的數據包拆分開。

Netty 線程模型
Netty經過Reactor模型基於多路複用器接收並處理用戶請求,內部實現了兩個線程池,boss線程池和work線程池,其中boss線程池的線程負責處理請求的accept事件,當接收到accept事件的請求時,把對應的socket封裝到一個NioSocketChannel中,並交給work線程池,其中work線程池負責請求的read和write事件,由對應的Handler處理。
單線程模型:全部I/O操做都由一個線程完成,即多路複用、事件分發和處理都是在一個Reactor線程上完成的。既要接收客戶端的鏈接請求,向服務端發起鏈接,又要發送/讀取請求或應答/響應消息。一個NIO 線程同時處理成百上千的鏈路,性能上沒法支撐,速度慢,若線程進入死循環,整個程序不可用,對於高負載、大併發的應用場景不合適。
多線程模型:有一個NIO 線程(Acceptor) 只負責監聽服務端,接收客戶端的TCP 鏈接請求;NIO 線程池負責網絡IO 的操做,即消息的讀取、解碼、編碼和發送;1 個NIO 線程能夠同時處理N 條鏈路,可是1 個鏈路只對應1 個NIO 線程,這是爲了防止發生併發操做問題。但在併發百萬客戶端鏈接或須要安全認證時,一個Acceptor 線程可能會存在性能不足問題。
主從多線程模型:Acceptor 線程用於綁定監聽端口,接收客戶端鏈接,將SocketChannel 從主線程池的Reactor 線程的多路複用器上移除,從新註冊到Sub 線程池的線程上,用於處理I/O 的讀寫等操做,從而保證mainReactor只負責接入認證、握手等操做;

說說 Netty 的零拷貝
Netty的零拷貝實現?
Netty的接收和發送ByteBuffer採用DIRECT BUFFERS,使用堆外直接內存進行Socket讀寫,不須要進行字節緩衝區的二次拷貝。堆內存多了一次內存拷貝,JVM會將堆內存Buffer拷貝一份到直接內存中,而後才寫入Socket中。ByteBuffer由ChannelConfig分配,而ChannelConfig建立ByteBufAllocator默認使用Direct Buffer

CompositeByteBuf 類能夠將多個 ByteBuf 合併爲一個邏輯上的 ByteBuf, 避免了傳統經過內存拷貝的方式將幾個小Buffer合併成一個大的Buffer。addComponents方法將 header 與 body 合併爲一個邏輯上的 ByteBuf, 這兩個 ByteBuf 在CompositeByteBuf 內部都是單獨存在的, CompositeByteBuf 只是邏輯上是一個總體

經過 FileRegion 包裝的FileChannel.tranferTo方法 實現文件傳輸, 能夠直接將文件緩衝區的數據發送到目標 Channel,避免了傳統經過循環write方式致使的內存拷貝問題。

經過 wrap方法, 咱們能夠將 byte[] 數組、ByteBuf、ByteBuffer等包裝成一個 Netty ByteBuf 對象, 進而避免了拷貝操做。

Selector BUG:若Selector的輪詢結果爲空,也沒有wakeup或新消息處理,則發生空輪詢,CPU使用率100%,

Netty的解決辦法:對Selector的select操做週期進行統計,每完成一次空的select操做進行一次計數,若在某個週期內連續發生N次空輪詢,則觸發了epoll死循環bug。重建Selector,判斷是不是其餘線程發起的重建請求,若不是則將原SocketChannel從舊的Selector上去除註冊,從新註冊到新的Selector上,並將原來的Selector關閉。

Netty 內部執行流程
服務端依次發生的步驟
創建服務端監聽套接字ServerSocketChannel,以及對應的管道pipeline;
啓動boss線程,將ServerSocketChannel註冊到boss線程持有的selector中,並將註冊返回的selectionKey賦值給ServerSocketChannel關聯的selectionKey變量;
在ServerSocketChannel對應的管道中觸發channelRegistered事件;
綁定IP和端口
觸發channelActive事件,並將ServerSocketChannel關聯的selectionKey的OP_ACCEPT位置爲1。
客戶端發起connect請求後,boss線程正在運行的select循環檢測到了該ServerSocketChannel的ACCEPT事件就緒,則經過accept系統調用創建一個已鏈接套接字SocketChannel,併爲其建立對應的管道;
在服務端監聽套接字對應的管道中觸發channelRead事件;
channelRead事件由ServerBootstrapAcceptor的channelRead方法響應:爲已鏈接套接字對應的管道加入ChannelInitializer處理器;啓動一個worker線程,並將已鏈接套接字的註冊任務加入到worker線程的任務隊列中;
worker線程執行已鏈接套接字的註冊任務:將已鏈接套接字註冊到worker線程持有的selector中,並將註冊返回的selectionKey賦值給已鏈接套接字關聯的selectionKey變量;在已鏈接套接字對應的管道中觸發channelRegistered事件;channelRegistered事件由ChannelInitializer的channelRegistered方法響應:將自定義的處理器(譬如EchoServerHandler)加入到已鏈接套接字對應的管道中;在已鏈接套接字對應的管道中觸發channelActive事件;channelActive事件由已鏈接套接字對應的管道中的inbound處理器的channelActive方法響應;將已鏈接套接字關聯的selectionKey的OP_READ位置爲1;至此,worker線程關聯的selector就開始監聽已鏈接套接字的READ事件了。
在worker線程運行的同時,Boss線程接着在服務端監聽套接字對應的管道中觸發channelReadComplete事件。
客戶端向服務端發送消息後,worker線程正在運行的selector循環會檢測到已鏈接套接字的READ事件就緒。則經過read系統調用將消息從套接字的接受緩衝區中讀到AdaptiveRecvByteBufAllocator(能夠自適應調整分配的緩存的大小)分配的緩存中;
在已鏈接套接字對應的管道中觸發channelRead事件;
channelRead事件由EchoServerHandler處理器的channelRead方法響應:執行write操做將消息存儲到ChannelOutboundBuffer中;
在已鏈接套接字對應的管道中觸發ChannelReadComplete事件;
ChannelReadComplete事件由EchoServerHandler處理器的channelReadComplete方法響應:執行flush操做將消息從ChannelOutboundBuffer中flush到套接字的發送緩衝區中;
客戶端依次發生的步驟
創建套接字SocketChannel,以及對應的管道pipeline;
啓動客戶端線程,將SocketChannel註冊到客戶端線程持有的selector中,並將註冊返回的selectionKey賦值給SocketChannel關聯的selectionKey變量;
觸發channelRegistered事件;
channelRegistered事件由ChannelInitializer的channelRegistered方法響應:將客戶端自定義的處理器(譬如EchoClientHandler)按順序加入到管道中;
向服務端發起connect請求,並將SocketChannel關聯的selectionKey的OP_CONNECT位置爲1;
開始三次握手,客戶端線程正在運行的select循環檢測到了該SocketChannel的CONNECT事件就緒,則將關聯的selectionKey的OP_CONNECT位置爲0,再經過調用finishConnect完成鏈接的創建;
觸發channelActive事件;
channelActive事件由EchoClientHandler的channelActive方法響應,經過調用ctx.writeAndFlush方法將消息發往服務端;
首先將消息存儲到ChannelOutboundBuffer中;(若是ChannelOutboundBuffer存儲的全部未flush的消息的大小超太高水位線writeBufferHighWaterMark(默認值爲64 * 1024),則會觸發ChannelWritabilityChanged事件)
而後將消息從ChannelOutboundBuffer中flush到套接字的發送緩衝區中;(若是ChannelOutboundBuffer存儲的全部未flush的消息的大小小於低水位線,則會觸發ChannelWritabilityChanged事件)

Netty 重連實現
使用 Netty 實現心跳機制的關鍵就是利用 IdleStateHandler 來產生對應的 idle 事件.
通常是客戶端負責發送心跳的 PING 消息, 所以客戶端注意關注 ALL_IDLE 事件, 在這個事件觸發後, 客戶端須要向服務器發送 PING 消息, 告訴服務器"我還存活着".
服務器是接收客戶端的 PING 消息的, 所以服務器關注的是 READER_IDLE 事件, 而且服務器的 READER_IDLE 間隔須要比客戶端的 ALL_IDLE 事件間隔大(例如客戶端ALL_IDLE 是5s 沒有讀寫時觸發, 所以服務器的 READER_IDLE 能夠設置爲10s)
當服務器收到客戶端的 PING 消息時, 會發送一個 PONG 消息做爲回覆. 一個 PING-PONG 消息對就是一個心跳交互.
在client在鏈接服務器的方法上加上一個監聽,當發生斷線的時候進行從新鏈接便可。unb
複製代碼

微服務篇

微服務
先後端分離是如何作的
微服務哪些框架
你怎麼理解 RPC 框架
說說 RPC 的實現原理
1)服務消費方(client)調用以本地調用方式調用服務; 
2)client stub接收到調用後負責將方法、參數等組裝成可以進行網絡傳輸的消息體; 
3)client stub找到服務地址,並將消息發送到服務端; 
4)server stub收到消息後進行解碼; 
5)server stub根據解碼結果調用本地的服務; 
6)本地服務執行並將結果返回給server stub; 
7)server stub將返回結果打包成消息併發送至消費方; 
8)client stub接收到消息,並進行解碼; 
9)服務消費方獲得最終結果。
使用到的技術
一、動態代理 
生成 client stub和server stub須要用到 Java 動態代理技術 ,咱們能夠使用JDK原生的動態代理機制,能夠使用一些開源字節碼工具框架 如:CgLib、Javassist等。

二、序列化 
爲了能在網絡上傳輸和接收 Java對象,咱們須要對它進行 序列化和反序列化操做。 
* 序列化:將Java對象轉換成byte[]的過程,也就是編碼的過程; 
* 反序列化:將byte[]轉換成Java對象的過程;

能夠使用Java原生的序列化機制,可是效率很是低,推薦使用一些開源的、成熟的序列化技術,例如:protobuf、Thrift、hessian、Kryo、Msgpack

關於序列化工具性能比較能夠參考:jvm-serializers

三、NIO 
當前不少RPC框架都直接基於netty這一IO通訊框架,好比阿里巴巴的HSF、dubbo,Hadoop Avro,推薦使用Netty 做爲底層通訊框架。

四、服務註冊中心 
可選技術: 
* Redis 
* Zookeeper 
* Consul 
* Etcd

說說 Dubbo 的實現原理
dubbo做爲rpc框架,實現的效果就是調用遠程的方法就像在本地調用同樣。如何作到呢?就是本地有對遠程方法的描述,包括方法名、參數、返回值,在dubbo中是遠程和本地使用一樣的接口;而後呢,要有對網絡通訊的封裝,要對調用方來講通訊細節是徹底不可見的,網絡通訊要作的就是將調用方法的屬性經過必定的協議(簡單來講就是消息格式)傳遞到服務端;服務端按照協議解析出調用的信息;執行相應的方法;在將方法的返回值經過協議傳遞給客戶端;客戶端再解析;在調用方式上又能夠分爲同步調用和異步調用;簡單來講基本就這個過程

你怎麼理解 RESTful
說說如何設計一個良好的 API
如何理解 RESTful API 的冪等性

如何保證接口的冪等性
全局惟一ID
若是使用全局惟一ID,就是根據業務的操做和內容生成一個全局ID,在執行操做前先根據這個全局惟一ID是否存在,來判斷這個操做是否已經執行。若是不存在則把全局ID,存儲到存儲系統中,好比數據庫、Redis等。若是存在則表示該方法已經執行。
從工程的角度來講,使用全局ID作冪等能夠做爲一個業務的基礎的微服務存在,在不少的微服務中都會用到這樣的服務,在每一個微服務中都完成這樣的功能,會存在工做量重複。另外打造一個高可靠的冪等服務還須要考慮不少問題,好比一臺機器雖然把全局ID先寫入了存儲,可是在寫入以後掛了,這就須要引入全局ID的超時機制。

使用全局惟一ID是一個通用方案,能夠支持插入、更新、刪除業務操做。可是這個方案看起來很美可是實現起來比較麻煩,下面的方案適用於特定的場景,可是實現起來比較簡單。
去重表

 這種方法適用於在業務中有惟一標的插入場景中,好比在以上的支付場景中,若是一個訂單隻會支付一次,因此訂單ID能夠做爲惟一標識。這時,咱們就能夠建一張去重表,而且把惟一標識做爲惟一索引,在咱們實現時,把建立支付單據和寫入去去重表,放在一個事務中,若是重複建立,數據庫會拋出惟一約束異常,操做就會回滾。
插入或更新

這種方法插入而且有惟一索引的狀況,好比咱們要關聯商品品類,其中商品的ID和品類的ID能夠構成惟一索引,而且在數據表中也增長了惟一索引。這時就能夠使用InsertOrUpdate操做。在MySQL數據庫中以下:

insert into goods_category (goods_id,category_id,create_time,update_time) 
values(#{goodsId},#{categoryId},now(),now()) 
on DUPLICATE KEY UPDATE 
update_time=now()

多版本控制 這種方法適合在更新的場景中,好比咱們要更新商品的名字,這時咱們就能夠在更新的接口中增長一個版本號,來作冪等

說說 CAP 定理、 BASE 理論
CAP理論
一個經典的分佈式系統理論。CAP理論告訴咱們:一個分佈式系統不可能同時知足一致性(C:Consistency)、可用性(A:Availability)和分區容錯性(P:Partition tolerance)這三個基本需求,最多隻能同時知足其中兩項。
一、一致性
在分佈式環境下,一致性是指數據在多個副本之間可否保持一致的特性。在一致性的需求下,當一個系統在數據一致的狀態下執行更新操做後,應該保證系統的數據仍然處於一直的狀態。
對於一個將數據副本分佈在不一樣分佈式節點上的系統來講,若是對第一個節點的數據進 行了更新操做而且更新成功後,卻沒有使得第二個節點上的數據獲得相應的更新,因而在對第二個節點的數據進行讀取操做時,獲取的依然是老數據(或稱爲髒數 據),這就是典型的分佈式數據不一致的狀況。在分佈式系統中,若是可以作到針對一個數據項的更新操做執行成功後,全部的用戶均可以讀取到其最新的值,那麼 這樣的系統就被認爲具備強一致性
二、可用性
可用性是指系統提供的服務必須一直處於可用的狀態,對於用戶的每個操做請求老是可以在有限的時間內返回結果。這裏的重點是"有限時間內""返回結果""有限時間內"是指,對於用戶的一個操做請求,系統必須可以在指定的時間內返回對 應的處理結果,若是超過了這個時間範圍,那麼系統就被認爲是不可用的。另外,"有限的時間內"是指系統設計之初就設計好的運行指標,一般不一樣系統之間有很 大的不一樣,不管如何,對於用戶請求,系統必須存在一個合理的響應時間,不然用戶便會對系統感到失望。
"返回結果"是可用性的另外一個很是重要的指標,它要求系統在完成對用戶請求的處理後,返回一個正常的響應結果。正常的響應結果一般可以明確地反映出隊請求的處理結果,即成功或失敗,而不是一個讓用戶感到困惑的返回結果。
三、分區容錯性
分區容錯性約束了一個分佈式系統具備以下特性:分佈式系統在遇到任何網絡分區故障的時候,仍然須要可以保證對外提供知足一致性和可用性的服務,除非是整個網絡環境都發生了故障。
網絡分區是指在分佈式系統中,不一樣的節點分佈在不一樣的子網絡(機房或異地網絡) 中,因爲一些特殊的緣由致使這些子網絡出現網絡不連通的情況,但各個子網絡的內部網絡是正常的,從而致使整個系統的網絡環境被切分紅了若干個孤立的區域。 須要注意的是,組成一個分佈式系統的每一個節點的加入與退出均可以看做是一個特殊的網絡分區。
既然一個分佈式系統沒法同時知足一致性、可用性、分區容錯性三個特色,因此咱們就須要拋棄同樣:
用一張表格說明一下:
選    擇  說    明
CA  放棄分區容錯性,增強一致性和可用性,其實就是傳統的單機數據庫的選擇
AP  放棄一致性(這裏說的一致性是強一致性),追求分區容錯性和可用性,這是不少分佈式系統設計時的選擇,例如不少NoSQL系統就是如此
CP  放棄可用性,追求一致性和分區容錯性,基本不會選擇,網絡問題會直接讓整個系統不可用
須要明確的一點是,對於一個分佈式系統而言,分區容錯性是一個最基本的要求。由於 既然是一個分佈式系統,那麼分佈式系統中的組件必然須要被部署到不一樣的節點,不然也就無所謂分佈式系統了,所以必然出現子網絡。而對於分佈式系統而言,網 絡問題又是一個一定會出現的異常狀況,所以分區容錯性也就成爲了一個分佈式系統必然須要面對和解決的問題。所以系統架構師每每須要把精力花在如何根據業務 特色在C(一致性)和A(可用性)之間尋求平衡。

BASE理論
BASE是Basically Available(基本可用)、Soft state(軟狀態)和Eventually consistent(最終一致性)三個短語的縮寫。BASE理論是對CAP中一致性和可用性權衡的結果,其來源於對大規模互聯網系統分佈式實踐的總結, 是基於CAP定理逐步演化而來的。BASE理論的核心思想是:即便沒法作到強一致性,但每一個應用均可以根據自身業務特色,採用適當的方式來使系統達到最終一致性。接下來看一下BASE中的三要素:
一、基本可用
基本可用是指分佈式系統在出現不可預知故障的時候,容許損失部分可用性----注意,這毫不等價於系統不可用。好比:
(1)響應時間上的損失。正常狀況下,一個在線搜索引擎須要在0.5秒以內返回給用戶相應的查詢結果,但因爲出現故障,查詢結果的響應時間增長了1~2秒
(2)系統功能上的損失:正常狀況下,在一個電子商務網站上進行購物的時候,消費者幾乎可以順利完成每一筆訂單,可是在一些節日大促購物高峯的時候,因爲消費者的購物行爲激增,爲了保護購物系統的穩定性,部分消費者可能會被引導到一個降級頁面
二、軟狀態
軟狀態指容許系統中的數據存在中間狀態,並認爲該中間狀態的存在不會影響系統的總體可用性,即容許系統在不一樣節點的數據副本之間進行數據同步的過程存在延時
三、最終一致性
最終一致性強調的是全部的數據副本,在通過一段時間的同步以後,最終都可以達到一個一致的狀態。所以,最終一致性的本質是須要系統保證最終數據可以達到一致,而不須要實時保證系統數據的強一致性。
總的來講,BASE理論面向的是大型高可用可擴展的分佈式系統,和傳統的事物ACID特性是相反的,它徹底不一樣於ACID的強一致性模型,而是經過犧牲強一致性來得到可用性,並容許數據在一段時間內是不一致的,但最終達到一致狀態。但同時,在實際的分佈式場景中,不一樣業務單元和組件對數據一致性的要求是不一樣的,所以在具體的分佈式系統架構設計過程當中,ACID特性和BASE理論每每又會結合在一塊兒。

怎麼考慮數據一致性問題
說說最終一致性的實現方案
你怎麼看待微服務
微服務與 SOA 的區別
如何拆分服務
微服務如何進行數據庫管理
如何應對微服務的鏈式調用異常 資源隔離  重試 熔斷
對於快速追蹤與定位問題
微服務的安全
分佈式
談談業務中使用分佈式的場景
Session 分佈式方案
分佈式鎖的場景
分佈是鎖的實現方案
分佈式事務

集羣與負載均衡的算法與實現
Dubbo 負載均衡策略提供下列四種方式:
Random LoadBalance 隨機,按權重設置隨機機率。 Dubbo的默認負載均衡策略
在一個截面上碰撞的機率高,但調用量越大分佈越均勻,並且按機率使用權重後也比較均勻,有利於動態調整提供者權重。

RoundRobin LoadBalance 輪循,按公約後的權重設置輪循比率。
存在慢的提供者累積請求問題,好比:第二臺機器很慢,但沒掛,當請求調到第二臺時就卡在那,長此以往,全部請求都卡在調到第二臺上。

LeastActive LoadBalance 最少活躍調用數,相同活躍數的隨機,活躍數指調用先後計數差。
使慢的提供者收到更少請求,由於越慢的提供者的調用先後計數差會越大。

ConsistentHash LoadBalance 一致性Hash,相同參數的請求老是發到同一提供者。
當某一臺提供者掛時,本來發往該提供者的請求,基於虛擬節點,平攤到其它提供者,不會引發劇烈變更。

說說分庫與分表設計
分庫與分錶帶來的分佈式困境與應對之策

複製代碼

公衆號推薦

  • 全網惟一一個從0開始幫助Java開發者轉作大數據領域的公衆號~

  • 大數據技術與架構或者搜索import_bigdata關注~

  • 海量【java和大數據的面試題+視頻資料】整理在公衆號,關注後能夠下載~

相關文章
相關標籤/搜索