java http get_我是如何进入阿里巴巴的-面向春招应届生Java面试指南(九)
基礎(chǔ)篇
基本功
面向?qū)ο蟮奶卣?/h2>
1.final, finally, finalize 的區(qū)別 final—修飾符(關(guān)鍵字)如果一個(gè)類(lèi)被聲明為final,意味著它不能再派生出新的子類(lèi),不能作為父類(lèi)被繼承。因此一個(gè)類(lèi)不能既被聲明為 abstract的,又被聲明為final的。將變量或方法聲明為final,可以保證它們?cè)谑褂弥胁槐桓淖儭1宦暶鳛閒inal的變量必須在聲明時(shí)給定初值,而在以后的引用中只能讀取,不可修改。被聲明為final的方法也同樣只能使用,不能重載。 finally—再異常處理時(shí)提供 finally 塊來(lái)執(zhí)行任何清除操作。如果拋出一個(gè)異常,那么相匹配的 catch 子句就會(huì)執(zhí)行,然后控制就會(huì)進(jìn)入 finally 塊(如果有的話)。 finalize—方法名。Java 技術(shù)允許使用 finalize() 方法在垃圾收集器將對(duì)象從內(nèi)存中清除出去之前做必要的清理工作。這個(gè)方法是由垃圾收集器在確定這個(gè)對(duì)象沒(méi)有被引用時(shí)對(duì)這個(gè)對(duì)象調(diào)用的。它是在 Object 類(lèi)中定義的,因此所有的類(lèi)都繼承了它。子類(lèi)覆蓋 finalize() 方法以整理系統(tǒng)資源或者執(zhí)行其他清理工作。finalize() 方法是在垃圾收集器刪除對(duì)象之前對(duì)這個(gè)對(duì)象調(diào)用的。
公眾號(hào)推薦
- 全網(wǎng)唯一一個(gè)從0開(kāi)始幫助Java開(kāi)發(fā)者轉(zhuǎn)做大數(shù)據(jù)領(lǐng)域的公眾號(hào)~
- 大數(shù)據(jù)技術(shù)與架構(gòu)或者搜索import_bigdata關(guān)注~
- 海量【java和大數(shù)據(jù)的面試題+視頻資料】整理在公眾號(hào),關(guān)注后可以下載~
http://2.int 和 Integer 有什么區(qū)別
1,無(wú)論如何,Integer與new Integer不會(huì)相等。不會(huì)經(jīng)歷拆箱過(guò)程,new出來(lái)的對(duì)象存放在堆,而非new的Integer常量則在常量池(在方法區(qū)),他們的內(nèi)存地址不一樣,所以為false。 2,兩個(gè)都是非new出來(lái)的Integer,如果數(shù)在-128到127之間,則是true,否則為false。因?yàn)閖ava在編譯Integer i2 = 128的時(shí)候,被翻譯成:Integer i2 = Integer.valueOf(128);而valueOf()函數(shù)會(huì)對(duì)-128到127之間的數(shù)進(jìn)行緩存。 3,兩個(gè)都是new出來(lái)的,都為false。還是內(nèi)存地址不一樣。 4,int和Integer(無(wú)論new否)比,都為true,因?yàn)闀?huì)把Integer自動(dòng)拆箱為int再去比
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會(huì)自動(dòng)拆箱為int,所以為true System.out.println(i == i3); //true,理由同上 Integer i4 = 127;//編譯時(shí)被翻譯成: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.重載和重寫(xiě)的區(qū)別
1、覆蓋的方法的標(biāo)志必須要和被覆蓋的方法的標(biāo)志完全匹配,才能達(dá)到覆蓋的效果;
2、覆蓋的方法的返回值必須和被覆蓋的方法的返回一致;
3、覆蓋的方法所拋出的異常必須和被覆蓋方法的所拋出的異常一致,或者是其子類(lèi);
4、被覆蓋的方法不能為private,否則在其子類(lèi)中只是新定義了一個(gè)方法,并沒(méi)有對(duì)其進(jìn)行覆蓋。
2.Overload 特點(diǎn)
1、在使用重載時(shí)只能通過(guò)不同的參數(shù)樣式。例如,不同的參數(shù)類(lèi)型,不同的參數(shù)個(gè)數(shù),不同的參數(shù)順序(當(dāng)然,同一方法內(nèi)的幾個(gè)參數(shù)類(lèi)型必須不一樣,例如可以是fun(int, float), 但是不能為fun(int, int));
2、不能通過(guò)訪問(wèn)權(quán)限、返回類(lèi)型、拋出的異常進(jìn)行重載;
3、方法的異常類(lèi)型和數(shù)目不會(huì)對(duì)重載造成影響;
4、對(duì)于繼承來(lái)說(shuō),如果某一方法在父類(lèi)中是訪問(wèn)權(quán)限是priavte,那么就不能在子類(lèi)對(duì)其進(jìn)行重載,如果定義的話,也只是定義了一個(gè)新方法,而不會(huì)達(dá)到重載的效果。 4.抽象類(lèi)和接口有什么區(qū)別
第一點(diǎn). 接口是抽象類(lèi)的變體,接口中所有的方法都是抽象的。而抽象類(lèi)是聲明方法的存在而不去實(shí)現(xiàn)它的類(lèi)。 第二點(diǎn). 接口可以多繼承,抽象類(lèi)不行 第三點(diǎn). 接口定義方法,不能實(shí)現(xiàn),而抽象類(lèi)可以實(shí)現(xiàn)部分方法。 第四點(diǎn). 接口中基本數(shù)據(jù)類(lèi)型為static 而抽類(lèi)象不是的。 當(dāng)你關(guān)注一個(gè)事物的本質(zhì)的時(shí)候,用抽象類(lèi);當(dāng)你關(guān)注一個(gè)操作的時(shí)候,用接口。 抽象類(lèi)的功能要遠(yuǎn)超過(guò)接口,但是,定義抽象類(lèi)的代價(jià)高。因?yàn)楦呒?jí)語(yǔ)言來(lái)說(shuō)(從實(shí)際設(shè)計(jì)上來(lái)說(shuō)也是)每個(gè)類(lèi)只能繼承一個(gè)類(lèi)。在這個(gè)類(lèi)中,你必須繼承或編寫(xiě)出其所有子類(lèi)的
說(shuō)說(shuō)反射的用途及實(shí)現(xiàn) Java反射機(jī)制主要用于實(shí)現(xiàn)以下功能。 (1)在運(yùn)行時(shí)判斷任意一個(gè)對(duì)象所屬的類(lèi)型。 (2)在運(yùn)行時(shí)構(gòu)造任意一個(gè)類(lèi)的對(duì)象。 (3)在運(yùn)行時(shí)判斷任意一個(gè)類(lèi)所具有的成員變量和方法。 (4)在運(yùn)行時(shí)調(diào)用任意一個(gè)對(duì)象的方法,甚至可以調(diào)用private方法。 注意:上述功能都是在運(yùn)行時(shí)環(huán)境中,而不是在編譯時(shí)環(huán)境中。
說(shuō)說(shuō)自定義注解的場(chǎng)景及實(shí)現(xiàn) restful下方法上定義@LoggedIn判斷是否需要登錄
HTTP 請(qǐng)求的 GET 與 POST 方式的區(qū)別
GET和POST是由HTTP協(xié)議定義的。在HTTP協(xié)議中,Method和Data(URL, Body, Header)是正交的兩個(gè)概念,也就是說(shuō),使用哪個(gè)Method與應(yīng)用層的數(shù)據(jù)如何傳輸是沒(méi)有相互關(guān)系的。
HTTP沒(méi)有要求,如果Method是POST數(shù)據(jù)就要放在BODY中。也沒(méi)有要求,如果Method是GET,數(shù)據(jù)(參數(shù))就一定要放在URL中而不能放在BODY中。
那么,網(wǎng)上流傳甚廣的這個(gè)說(shuō)法是從何而來(lái)的呢?我在HTML標(biāo)準(zhǔn)中,找到了相似的描述。這和網(wǎng)上流傳的說(shuō)法一致。但是這只是HTML標(biāo)準(zhǔn)對(duì)HTTP協(xié)議的用法的約定。怎么能當(dāng)成GET和POST的區(qū)別呢?
而且,現(xiàn)代的Web Server都是支持GET中包含BODY這樣的請(qǐng)求。雖然這種請(qǐng)求不可能從瀏覽器發(fā)出,但是現(xiàn)在的Web Server又不是只給瀏覽器用,已經(jīng)完全地超出了HTML服務(wù)器的范疇了。
HTTP協(xié)議明確地指出了,HTTP頭和Body都沒(méi)有長(zhǎng)度的要求。而對(duì)于URL長(zhǎng)度上的限制,有兩方面的原因造成:
session 與 cookie 區(qū)別
是一個(gè)在客戶端一個(gè)在服務(wù)端。因?yàn)镃ookie存在客戶端所以用戶可以看見(jiàn),所以也可以編輯偽造,不是十分安全。
Session過(guò)多的時(shí)候會(huì)消耗服務(wù)器資源,所以大型網(wǎng)站會(huì)有專門(mén)的Session服務(wù)器,而Cookie存在客戶端所以沒(méi)什么問(wèn)題。
域的支持范圍不一樣,比方說(shuō)http://a.com的Cookie在http://a.com下都能用,而http://www.a.com的Session在http://api.a.com下都不能用,解決這個(gè)問(wèn)題的辦法是JSONP或者跨域資源共享。
session 分布式處理
1.基于數(shù)據(jù)庫(kù)的Session共享 2.基于NFS共享文件系統(tǒng) 3.基于memcached 的session,如何保證 memcached 本身的高可用性? 4. 基于resin/tomcat web容器本身的session復(fù)制機(jī)制 5. 基于TT/Redis 或 jbosscache 進(jìn)行 session 共享。 6. 基于cookie 進(jìn)行session共享
JDBC 流程
1)加載驅(qū)動(dòng)程序。 2)建立連接。 3)創(chuàng)建語(yǔ)句。 4)執(zhí)行語(yǔ)句。 5)處理ResultSet
MVC 設(shè)計(jì)思想
equals 與 == 的區(qū)別 == 比較的是變量(棧)內(nèi)存中存放的對(duì)象的(堆)內(nèi)存地址,用來(lái)判斷兩個(gè)對(duì)象的地址是否相同,即是否是指相同一個(gè)對(duì)象。比較的是真正意義上的指針操作。 equals用來(lái)比較的是兩個(gè)對(duì)象的內(nèi)容是否相等,由于所有的類(lèi)都是繼承自java.lang.Object類(lèi)的,所以適用于所有對(duì)象,如果沒(méi)有對(duì)該方法進(jìn)行覆蓋的話,調(diào)用的仍然是Object類(lèi)中的方法,而Object中的equals方法返回的卻是==的判斷。
集合
List 和 Set 區(qū)別 List:1.可以允許重復(fù)的對(duì)象。 2.可以插入多個(gè)null元素。 3.是一個(gè)有序容器,保持了每個(gè)元素的插入順序,輸出的順序就是插入的順序。 4.常用的實(shí)現(xiàn)類(lèi)有 ArrayList、LinkedList 和 Vector。ArrayList 最為流行,它提供了使用索引的隨意訪問(wèn),而 LinkedList 則對(duì)于經(jīng)常需要從 List 中添加或刪除元素的場(chǎng)合更為合適。 Set:1.不允許重復(fù)對(duì)象 2. 無(wú)序容器,你無(wú)法保證每個(gè)元素的存儲(chǔ)順序,TreeSet通過(guò) Comparator 或者 Comparable 維護(hù)了一個(gè)排序順序。 3. 只允許一個(gè) null 元素 4.Set 接口最流行的幾個(gè)實(shí)現(xiàn)類(lèi)是 HashSet、LinkedHashSet 以及 TreeSet。最流行的是基于 HashMap 實(shí)現(xiàn)的 HashSet;TreeSet 還實(shí)現(xiàn)了 SortedSet 接口,因此 TreeSet 是一個(gè)根據(jù)其 compare() 和 compareTo() 的定義進(jìn)行排序的有序容器。 1.Map不是collection的子接口或者實(shí)現(xiàn)類(lèi)。Map是一個(gè)接口。 2.Map 的 每個(gè) Entry 都持有兩個(gè)對(duì)象,也就是一個(gè)鍵一個(gè)值,Map 可能會(huì)持有相同的值對(duì)象但鍵對(duì)象必須是唯一的。 3. TreeMap 也通過(guò) Comparator 或者 Comparable 維護(hù)了一個(gè)排序順序。 4. Map 里你可以擁有隨意個(gè) null 值但最多只能有一個(gè) null 鍵。 5.Map 接口最流行的幾個(gè)實(shí)現(xiàn)類(lèi)是 HashMap、LinkedHashMap、Hashtable 和 TreeMap。(HashMap、TreeMap最常用)
Arraylist 與 LinkedList 區(qū)別
ArrayList 與 Vector 區(qū)別
HashMap 和 Hashtable 的區(qū)別 Hashtable和HashMap它們的性能方面的比較類(lèi)似 Vector和ArrayList,比如Hashtable的方法是同步的,而HashMap的不是。
HashSet 和 HashMap 區(qū)別 (1)HashSet是set的一個(gè)實(shí)現(xiàn)類(lèi),hashMap是Map的一個(gè)實(shí)現(xiàn)類(lèi),同時(shí)hashMap是hashTable的替代品(為什么后面會(huì)講到). (2)HashSet以對(duì)象作為元素,而HashMap以(key-value)的一組對(duì)象作為元素,且HashSet拒絕接受重復(fù)的對(duì)象.HashMap可以看作三個(gè)視圖:key的Set,value的Collection,Entry的Set。 這里HashSet就是其實(shí)就是HashMap的一個(gè)視圖。 HashSet內(nèi)部就是使用Hashmap實(shí)現(xiàn)的,和Hashmap不同的是它不需要Key和Value兩個(gè)值。 往hashset中插入對(duì)象其實(shí)只不過(guò)是內(nèi)部做了 public boolean add(Object o) { return map.put(o, PRESENT)==null; }
HashMap 和 ConcurrentHashMap 的區(qū)別
HashMap 的工作原理及代碼實(shí)現(xiàn)
ConcurrentHashMap 的工作原理及代碼實(shí)現(xiàn) jdk1.7中采用Segment + HashEntry的方式進(jìn)行實(shí)現(xiàn) 1.8中放棄了Segment臃腫的設(shè)計(jì),取而代之的是采用Node + CAS + Synchronized來(lái)保證并發(fā)安全進(jìn)行實(shí)現(xiàn)
線程
1.創(chuàng)建線程的方式及實(shí)現(xiàn)
1)繼承Thread類(lèi)創(chuàng)建線程 2)實(shí)現(xiàn)Runnable接口創(chuàng)建線程 3)使用Callable和Future創(chuàng)建線程
2.sleep() 、join()、yield()有什么區(qū)別
sleep() sleep()方法需要指定等待的時(shí)間,它可以讓當(dāng)前正在執(zhí)行的線程在指定的時(shí)間內(nèi)暫停執(zhí)行,進(jìn)入阻塞狀態(tài),該方法既可以讓其他同優(yōu)先級(jí)或者高優(yōu)先級(jí)的線程得到執(zhí)行的機(jī)會(huì),也可以讓低優(yōu)先級(jí)的線程得到執(zhí)行機(jī)會(huì)。但是sleep()方法不會(huì)釋放“鎖標(biāo)志”,也就是說(shuō)如果有synchronized同步塊,其他線程仍然不能訪問(wèn)共享數(shù)據(jù)。 wait() wait()方法需要和notify()及notifyAll()兩個(gè)方法一起介紹,這三個(gè)方法用于協(xié)調(diào)多個(gè)線程對(duì)共享數(shù)據(jù)的存取,所以必須在synchronized語(yǔ)句塊內(nèi)使用,也就是說(shuō),調(diào)用wait(),notify()和notifyAll()的任務(wù)在調(diào)用這些方法前必須擁有對(duì)象的鎖。注意,它們都是Object類(lèi)的方法,而不是Thread類(lèi)的方法。 wait()方法與sleep()方法的不同之處在于,wait()方法會(huì)釋放對(duì)象的“鎖標(biāo)志”。當(dāng)調(diào)用某一對(duì)象的wait()方法后,會(huì)使當(dāng)前線程暫停執(zhí)行,并將當(dāng)前線程放入對(duì)象等待池中,直到調(diào)用了notify()方法后,將從對(duì)象等待池中移出任意一個(gè)線程并放入鎖標(biāo)志等待池中,只有鎖標(biāo)志等待池中的線程可以獲取鎖標(biāo)志,它們隨時(shí)準(zhǔn)備爭(zhēng)奪鎖的擁有權(quán)。當(dāng)調(diào)用了某個(gè)對(duì)象的notifyAll()方法,會(huì)將對(duì)象等待池中的所有線程都移動(dòng)到該對(duì)象的鎖標(biāo)志等待池。 除了使用notify()和notifyAll()方法,還可以使用帶毫秒?yún)?shù)的wait(long timeout)方法,效果是在延遲timeout毫秒后,被暫停的線程將被恢復(fù)到鎖標(biāo)志等待池。 此外,wait(),notify()及notifyAll()只能在synchronized語(yǔ)句中使用,但是如果使用的是ReenTrantLock實(shí)現(xiàn)同步,該如何達(dá)到這三個(gè)方法的效果呢?解決方法是使用ReenTrantLock.newCondition()獲取一個(gè)Condition類(lèi)對(duì)象,然后Condition的await(),signal()以及signalAll()分別對(duì)應(yīng)上面的三個(gè)方法。
yield() yield()方法和sleep()方法類(lèi)似,也不會(huì)釋放“鎖標(biāo)志”,區(qū)別在于,它沒(méi)有參數(shù),即yield()方法只是使當(dāng)前線程重新回到可執(zhí)行狀態(tài),所以執(zhí)行yield()的線程有可能在進(jìn)入到可執(zhí)行狀態(tài)后馬上又被執(zhí)行,另外yield()方法只能使同優(yōu)先級(jí)或者高優(yōu)先級(jí)的線程得到執(zhí)行機(jī)會(huì),這也和sleep()方法不同。
join() join()方法會(huì)使當(dāng)前線程等待調(diào)用join()方法的線程結(jié)束后才能繼續(xù)執(zhí)行 3.說(shuō)說(shuō) CountDownLatch 原理 https://www.jianshu.com/p/38c39e00ee4c 4.說(shuō)說(shuō) CyclicBarrier 原理 https://www.jianshu.com/p/060761df128b 說(shuō)說(shuō) Semaphore 原理
說(shuō)說(shuō) Exchanger 原理 https://www.jianshu.com/p/1eab24ca3a22 說(shuō)說(shuō) CountDownLatch 與 CyclicBarrier 區(qū)別
ThreadLocal 原理分析
ThreadLocal提供了set和get訪問(wèn)器用來(lái)訪問(wèn)與當(dāng)前線程相關(guān)聯(lián)的線程局部變量。 可以從ThreadLocal的get函數(shù)中看出來(lái),其中g(shù)etmap函數(shù)是用t作為參數(shù),這里t就是當(dāng)前執(zhí)行的線程。
從而得知,get函數(shù)就是從當(dāng)前線程的threadlocalmap中取出當(dāng)前線程對(duì)應(yīng)的變量的副本【注意,變量是保存在線程中的,而不是保存在ThreadLocal變量中】。當(dāng)前線程中,有一個(gè)變量引用名字是threadLocals,這個(gè)引用是在ThreadLocal類(lèi)中createmap函數(shù)內(nèi)初始化的。每個(gè)線程都有一個(gè)這樣的threadLocals引用的ThreadLocalMap,以ThreadLocal和ThreadLocal對(duì)象聲明的變量類(lèi)型作為參數(shù)。這樣,我們所使用的ThreadLocal變量的實(shí)際數(shù)據(jù),通過(guò)get函數(shù)取值的時(shí)候,就是通過(guò)取出Thread中threadLocals引用的map,然后從這個(gè)map中根據(jù)當(dāng)前threadLocal作為參數(shù),取出數(shù)據(jù)。現(xiàn)在,變量的副本從哪里取出來(lái)的(本文章提出的第一個(gè)問(wèn)題)已經(jīng)確認(rèn)解決了。
ThreadLocal操作值的時(shí)候是取得當(dāng)前線程的ThreadLocalMap對(duì)象,然后把值設(shè)置到了這個(gè)對(duì)象中,這樣對(duì)于不同的線程得到的就是不同的ThreadLocalMap,那么向其中保存值或者修改值都只是會(huì)影響到當(dāng)前線程,這樣就保證了線程安全。
講講線程池的實(shí)現(xiàn)原理
Java線程池的工廠類(lèi):Executors類(lèi), 初始化4種類(lèi)型的線程池: newFixedThreadPool() 說(shuō)明:初始化一個(gè)指定線程數(shù)的線程池,其中corePoolSize == maxiPoolSize,使用LinkedBlockingQuene作為阻塞隊(duì)列 特點(diǎn):即使當(dāng)線程池沒(méi)有可執(zhí)行任務(wù)時(shí),也不會(huì)釋放線程。 newCachedThreadPool() 說(shuō)明:初始化一個(gè)可以緩存線程的線程池,默認(rèn)緩存60s,線程池的線程數(shù)可達(dá)到Integer.MAX_VALUE,即2147483647,內(nèi)部使用SynchronousQueue作為阻塞隊(duì)列; 特點(diǎn):在沒(méi)有任務(wù)執(zhí)行時(shí),當(dāng)線程的空閑時(shí)間超過(guò)keepAliveTime,會(huì)自動(dòng)釋放線程資源;當(dāng)提交新任務(wù)時(shí),如果沒(méi)有空閑線程,則創(chuàng)建新線程執(zhí)行任務(wù),會(huì)導(dǎo)致一定的系統(tǒng)開(kāi)銷(xiāo); 因此,使用時(shí)要注意控制并發(fā)的任務(wù)數(shù),防止因創(chuàng)建大量的線程導(dǎo)致而降低性能。 newSingleThreadExecutor() 說(shuō)明:初始化只有一個(gè)線程的線程池,內(nèi)部使用LinkedBlockingQueue作為阻塞隊(duì)列。 特點(diǎn):如果該線程異常結(jié)束,會(huì)重新創(chuàng)建一個(gè)新的線程繼續(xù)執(zhí)行任務(wù),唯一的線程可以保證所提交任務(wù)的順序執(zhí)行 newScheduledThreadPool() 特定:初始化的線程池可以在指定的時(shí)間內(nèi)周期性的執(zhí)行所提交的任務(wù),在實(shí)際的業(yè)務(wù)場(chǎng)景中可以使用該線程池定期的同步數(shù)據(jù)。 總結(jié):除了newScheduledThreadPool的內(nèi)部實(shí)現(xiàn)特殊一點(diǎn)之外,其它線程池內(nèi)部都是基于ThreadPoolExecutor類(lèi)(Executor的子類(lèi))實(shí)現(xiàn)的。
線程池的幾種方式
ThreadPoolExecutor(corePoolSize,maxPoolSize,keepAliveTime,timeUnit,workQueue,threadFactory,handle);
方法參數(shù): corePoolSize:核心線程數(shù) maxPoolSize:最大線程數(shù) keepAliveTime:線程存活時(shí)間(在corePore<*<maxPoolSize情況下有用) timeUnit:存活時(shí)間的時(shí)間單位 workQueue:阻塞隊(duì)列(用來(lái)保存等待被執(zhí)行的任務(wù)) 注:關(guān)于workQueue參數(shù)的取值,JDK提供了4種阻塞隊(duì)列類(lèi)型供選擇: ArrayBlockingQueue:基于數(shù)組結(jié)構(gòu)的有界阻塞隊(duì)列,按FIFO排序任務(wù); inkedBlockingQuene:基于鏈表結(jié)構(gòu)的阻塞隊(duì)列,按FIFO排序任務(wù),吞吐量通常要高于
SynchronousQuene:一個(gè)不存儲(chǔ)元素的阻塞隊(duì)列,每個(gè)插入操作必須等到另一個(gè)線程調(diào)用移除操作,否則插入操作一直處于阻塞狀態(tài),吞吐量通常要高于ArrayBlockingQuene; PriorityBlockingQuene:具有優(yōu)先級(jí)的無(wú)界阻塞隊(duì)列; threadFactory:線程工廠,主要用來(lái)創(chuàng)建線程; handler:表示當(dāng)拒絕處理任務(wù)時(shí)的策略,有以下四種取值 注:當(dāng)線程池的飽和策略,當(dāng)阻塞隊(duì)列滿了,且沒(méi)有空閑的工作線程,如果繼續(xù)提交任務(wù),必須采取一種策略處理該任務(wù),線程池提供了4種策略: ThreadPoolExecutor.AbortPolicy:丟棄任務(wù)并拋出RejectedExecutionException異常。 ThreadPoolExecutor.DiscardPolicy:也是丟棄任務(wù),但是不拋出異常。 ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊(duì)列最前面的任務(wù),然后重新嘗試執(zhí)行任務(wù)(重復(fù)此過(guò)程) ThreadPoolExecutor.CallerRunsPolicy:由調(diào)用線程處理該任務(wù) 當(dāng)然也可以根據(jù)應(yīng)用場(chǎng)景實(shí)現(xiàn)RejectedExecutionHandler接口,自定義飽和策略,如記錄日志或持久化存儲(chǔ)不能處理的任務(wù)。
線程的生命周期 當(dāng)線程被創(chuàng)建并啟動(dòng)以后,它既不是一啟動(dòng)就進(jìn)入了執(zhí)行狀態(tài),也不是一直處于執(zhí)行狀態(tài)。在線程的生命周期中,它要經(jīng)過(guò)新建(New)、就緒(Runnable)、運(yùn)行(Running)、阻塞(Blocked)和死亡(Dead)5種狀態(tài)。尤其是當(dāng)線程啟動(dòng)以后,它不可能一直"霸占"著CPU獨(dú)自運(yùn)行,所以CPU需要在多條線程之間切換,于是線程狀態(tài)也會(huì)多次在運(yùn)行、阻塞之間切換 鎖機(jī)制 說(shuō)說(shuō)線程安全問(wèn)題
volatile 實(shí)現(xiàn)原理 volatile 關(guān)鍵字,具有兩個(gè)特性:1. 內(nèi)存的可見(jiàn)性,2. 禁止指令重排序優(yōu)化。 內(nèi)存可見(jiàn)性是指:被 volatile 關(guān)鍵字修飾的變量,當(dāng)線程要對(duì)這個(gè)變量執(zhí)行的寫(xiě)操作,都不會(huì)寫(xiě)入本地緩存,而是直接刷入主內(nèi)存中。當(dāng)線程讀取被 volatile 關(guān)鍵字修飾的變量時(shí),也是直接從主內(nèi)存中讀取。(簡(jiǎn)單的說(shuō),一個(gè)線程修改的狀態(tài)對(duì)另一個(gè)線程是可見(jiàn)的)。注意:volatile 不能保證原子性。 禁止指令重排序優(yōu)化:有volatile修飾的變量,賦值后多執(zhí)行了一個(gè) “l(fā)oad addl $0x0, (%esp)” 操作,這個(gè)操作相當(dāng)于一個(gè)內(nèi)存屏障,保證指令重排序時(shí)不會(huì)把后面的指令重排序到內(nèi)存屏障之前的位置。
synchronize 實(shí)現(xiàn)原理 synchronized 代碼塊是通過(guò) monitorenter 和 monitorexit 指令實(shí)現(xiàn)的。synchronized 方法雖然在 vm 字節(jié)碼層面并沒(méi)有任何特別的指令來(lái)實(shí)現(xiàn)被 synchronized 修飾的方法,而是在 Class 文件的方法表中將該方法的 access_flags 字段中的 synchronized 標(biāo)志位置1,表示該方法是同步方法。鎖的實(shí)現(xiàn)有偏向鎖、輕量級(jí)鎖和重量級(jí)鎖,其中偏向鎖和輕量級(jí)鎖是 JDK 針對(duì)鎖的優(yōu)化措施。在多線程的競(jìng)爭(zhēng)下鎖會(huì)升級(jí),依次從偏向鎖 -> 輕量級(jí)鎖 -> 重量級(jí)鎖,這里的鎖只能升級(jí)但不能降級(jí)。在 Java 對(duì)象頭中的 Mark Word 中存儲(chǔ)了關(guān)于鎖的標(biāo)志位,其中:無(wú)鎖和偏向鎖為 00, 輕量級(jí)鎖為 01,重量級(jí)鎖為 10。 引入偏向鎖主要目的是:為了在無(wú)多線程競(jìng)爭(zhēng)的情況下盡量減少不必要的輕量級(jí)鎖執(zhí)行路徑(在無(wú)競(jìng)爭(zhēng)的情況下把整個(gè)同步都消除掉,連 CAS 操作都不做了)。 引入輕量級(jí)鎖的主要目的是:在沒(méi)有多線程競(jìng)爭(zhēng)的前提下,減少傳統(tǒng)的重量級(jí)鎖使用操作系統(tǒng)互斥量產(chǎn)生的性能消耗(在無(wú)競(jìng)爭(zhēng)的情況下使用 CAS 操作去消除同步使用的互斥量)。 重量級(jí)鎖通過(guò)對(duì)象內(nèi)部的監(jiān)視器(monitor)實(shí)現(xiàn),其中 monitor 的本質(zhì)是依賴于底層操作系統(tǒng)的 Mutex Lock 實(shí)現(xiàn),操作系統(tǒng)實(shí)現(xiàn)線程之間的切換需要從用戶態(tài)到內(nèi)核態(tài)的切換,切換成本非常高。
synchronized 與 lock 的區(qū)別 兩者區(qū)別: 1.首先synchronized是java內(nèi)置關(guān)鍵字,在jvm層面,Lock是個(gè)java類(lèi); 2.synchronized無(wú)法判斷是否獲取鎖的狀態(tài),Lock可以判斷是否獲取到鎖; 3.synchronized會(huì)自動(dòng)釋放鎖(a 線程執(zhí)行完同步代碼會(huì)釋放鎖 ;b 線程執(zhí)行過(guò)程中發(fā)生異常會(huì)釋放鎖),Lock需在finally中手工釋放鎖(unlock()方法釋放鎖),否則容易造成線程死鎖; 4.用synchronized關(guān)鍵字的兩個(gè)線程1和線程2,如果當(dāng)前線程1獲得鎖,線程2線程等待。如果線程1阻塞,線程2則會(huì)一直等待下去,而Lock鎖就不一定會(huì)等待下去,如果嘗試獲取不到鎖,線程可以不用一直等待就結(jié)束了; 5.synchronized的鎖可重入、不可中斷、非公平,而Lock鎖可重入、可判斷、可公平(兩者皆可) 6.Lock鎖適合大量同步的代碼的同步問(wèn)題,synchronized鎖適合代碼少量的同步問(wèn)題。
CAS 樂(lè)觀鎖 樂(lè)觀鎖 樂(lè)觀鎖( Optimistic Locking)其實(shí)是一種思想。相對(duì)悲觀鎖而言,樂(lè)觀鎖假設(shè)認(rèn)為數(shù)據(jù)一般情況下不會(huì)造成沖突,所以在數(shù)據(jù)進(jìn)行提交更新的時(shí)候,才會(huì)正式對(duì)數(shù)據(jù)的沖突與否進(jìn)行檢測(cè),如果發(fā)現(xiàn)沖突了,則讓返回用戶錯(cuò)誤的信息,讓用戶決定如何去做。 上面提到的樂(lè)觀鎖的概念中其實(shí)已經(jīng)闡述了他的具體實(shí)現(xiàn)細(xì)節(jié):主要就是兩個(gè)步驟:沖突檢測(cè)和數(shù)據(jù)更新。其實(shí)現(xiàn)方式有一種比較典型的就是Compare and Swap(CAS)。 CAS CAS是項(xiàng)樂(lè)觀鎖技術(shù),當(dāng)多個(gè)線程嘗試使用CAS同時(shí)更新同一個(gè)變量時(shí),只有其中一個(gè)線程能更新變量的值,而其它線程都失敗,失敗的線程并不會(huì)被掛起,而是被告知這次競(jìng)爭(zhēng)中失敗,并可以再次嘗試。 CAS 操作包含三個(gè)操作數(shù) —— 內(nèi)存位置(V)、預(yù)期原值(A)和新值(B)。如果內(nèi)存位置的值與預(yù)期原值相匹配,那么處理器會(huì)自動(dòng)將該位置值更新為新值。否則,處理器不做任何操作。無(wú)論哪種情況,它都會(huì)在 CAS 指令之前返回該位置的值。(在 CAS 的一些特殊情況下將僅返回 CAS 是否成功,而不提取當(dāng)前值。)CAS 有效地說(shuō)明了“我認(rèn)為位置 V 應(yīng)該包含值 A;如果包含該值,則將 B 放到這個(gè)位置;否則,不要更改該位置,只告訴我這個(gè)位置現(xiàn)在的值即可。”這其實(shí)和樂(lè)觀鎖的沖突檢查+數(shù)據(jù)更新的原理是一樣的。
ABA 問(wèn)題
樂(lè)觀鎖用到的機(jī)制就是CAS,Compare and Swap。 CAS有3個(gè)操作數(shù),內(nèi)存值V,舊的預(yù)期值A(chǔ),要修改的新值B。當(dāng)且僅當(dāng)預(yù)期值A(chǔ)和內(nèi)存值V相同時(shí),將內(nèi)存值V修改為B,否則什么都不做。 樂(lè)觀鎖的業(yè)務(wù)場(chǎng)景及實(shí)現(xiàn)方式 CAS看起來(lái)很爽,但是會(huì)導(dǎo)致“ABA問(wèn)題”。 CAS算法實(shí)現(xiàn)一個(gè)重要前提需要取出內(nèi)存中某時(shí)刻的數(shù)據(jù),而在下時(shí)刻比較并替換,那么在這個(gè)時(shí)間差類(lèi)會(huì)導(dǎo)致數(shù)據(jù)的變化。 比如說(shuō)一個(gè)線程one從內(nèi)存位置V中取出A,這時(shí)候另一個(gè)線程two也從內(nèi)存中取出A,并且two進(jìn)行了一些操作變成了B,然后two又將V位置的數(shù)據(jù)變成A,這時(shí)候線程one進(jìn)行CAS操作發(fā)現(xiàn)內(nèi)存中仍然是A,然后one操作成功。盡管線程one的CAS操作成功,但是不代表這個(gè)過(guò)程就是沒(méi)有問(wèn)題的。如果鏈表的頭在變化了兩次后恢復(fù)了原值,但是不代表鏈表就沒(méi)有變化。因此前面提到的原子操作AtomicStampedReference/AtomicMarkableReference就很有用了。這允許一對(duì)變化的元素進(jìn)行原子操作。
核心篇
數(shù)據(jù)存儲(chǔ)
MySQL 索引使用的注意事項(xiàng)
說(shuō)說(shuō)反模式設(shè)計(jì)
說(shuō)說(shuō)分庫(kù)與分表設(shè)計(jì)
分庫(kù)與分表帶來(lái)的分布式困境與應(yīng)對(duì)之策
說(shuō)說(shuō) SQL 優(yōu)化之道
1、應(yīng)盡量避免在 where 子句中使用!=或<>操作符,否則將引擎放棄使用索引而進(jìn)行全表掃描。 2、對(duì)查詢進(jìn)行優(yōu)化,應(yīng)盡量避免全表掃描,首先應(yīng)考慮在 where 及 order by 涉及的列上建立索引。 3、應(yīng)盡量避免在 where 子句中對(duì)字段進(jìn)行 null 值判斷,否則將導(dǎo)致引擎放棄使用索引而進(jìn)行全表掃描,如: select id from t where num is null 可以在num上設(shè)置默認(rèn)值0,確保表中num列沒(méi)有null值,然后這樣查詢: select id from t where num=0 4、盡量避免在 where 子句中使用 or 來(lái)連接條件,否則將導(dǎo)致引擎放棄使用索引而進(jìn)行全表掃描,如: 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 5、下面的查詢也將導(dǎo)致全表掃描:(不能前置百分號(hào)) select id from t where name like ‘�c%’ 若要提高效率,可以考慮全文檢索。 6、in 和 not in 也要慎用,否則會(huì)導(dǎo)致全表掃描,如: select id from t where num in(1,2,3) 對(duì)于連續(xù)的數(shù)值,能用 between 就不要用 in 了: select id from t where num between 1 and 3 7、如果在 where 子句中使用參數(shù),也會(huì)導(dǎo)致全表掃。因?yàn)镾QL只有在運(yùn)行時(shí)才會(huì)解析局部變量,但優(yōu)化程序不能將訪問(wèn)計(jì)劃的選擇推遲到運(yùn)行時(shí);它必須在編譯時(shí)進(jìn)行選擇。然 而,如果在編譯時(shí)建立訪問(wèn)計(jì)劃,變量的值還是未知的,因而無(wú)法作為索引選擇的輸入項(xiàng)。如下面語(yǔ)句將進(jìn)行全表掃描: select id from t where num=@num 可以改為強(qiáng)制查詢使用索引: select id from t with(index(索引名)) where num=@num 8、應(yīng)盡量避免在 where 子句中對(duì)字段進(jìn)行表達(dá)式操作,這將導(dǎo)致引擎放棄使用索引而進(jìn)行全表掃描。如: select id from t where num/2=100 應(yīng)改為: select id from t where num=100*2 9、應(yīng)盡量避免在where子句中對(duì)字段進(jìn)行函數(shù)操作,這將導(dǎo)致引擎放棄使用索引而進(jìn)行全表掃描。如: select id from t where substring(name,1,3)=’abc’–name以abc開(kāi)頭的id select id from t where datediff(day,createdate,’2005-11-30′)=0–’2005-11-30′生成的id 應(yīng)改為: select id from t where name like ‘a(chǎn)bc%’ select id from t where createdate>=’2005-11-30′ and createdate<’2005-12-1′ 10、不要在 where 子句中的“=”左邊進(jìn)行函數(shù)、算術(shù)運(yùn)算或其他表達(dá)式運(yùn)算,否則系統(tǒng)將可能無(wú)法正確使用索引。 11、在使用索引字段作為條件時(shí),如果該索引是復(fù)合索引,那么必須使用到該索引中的第一個(gè)字段作為條件時(shí)才能保證系統(tǒng)使用該索引,否則該索引將不會(huì)被使 用,并且應(yīng)盡可能的讓字段順序與索引順序相一致。 12、不要寫(xiě)一些沒(méi)有意義的查詢,如需要生成一個(gè)空表結(jié)構(gòu): select col1,col2 into #t from t where 1=0 這類(lèi)代碼不會(huì)返回任何結(jié)果集,但是會(huì)消耗系統(tǒng)資源的,應(yīng)改成這樣: create table #t(…) 13、很多時(shí)候用 exists 代替 in 是一個(gè)好的選擇: select num from a where num in(select num from b) 用下面的語(yǔ)句替換: select num from a where exists(select 1 from b where num=a.num) 14、并不是所有索引對(duì)查詢都有效,SQL是根據(jù)表中數(shù)據(jù)來(lái)進(jìn)行查詢優(yōu)化的,當(dāng)索引列有大量數(shù)據(jù)重復(fù)時(shí),SQL查詢可能不會(huì)去利用索引,如一表中有字段 sex,male、female幾乎各一半,那么即使在sex上建了索引也對(duì)查詢效率起不了作用。 15、索引并不是越多越好,索引固然可以提高相應(yīng)的 select 的效率,但同時(shí)也降低了 insert 及 update 的效率,因?yàn)?insert 或 update 時(shí)有可能會(huì)重建索引,所以怎樣建索引需要慎重考慮,視具體情況而定。一個(gè)表的索引數(shù)最好不要超過(guò)6個(gè),若太多則應(yīng)考慮一些不常使用到的列上建的索引是否有 必要。 16.應(yīng)盡可能的避免更新 clustered 索引數(shù)據(jù)列,因?yàn)?clustered 索引數(shù)據(jù)列的順序就是表記錄的物理存儲(chǔ)順序,一旦該列值改變將導(dǎo)致整個(gè)表記錄的順序的調(diào)整,會(huì)耗費(fèi)相當(dāng)大的資源。若應(yīng)用系統(tǒng)需要頻繁更新 clustered 索引數(shù)據(jù)列,那么需要考慮是否應(yīng)將該索引建為 clustered 索引。 17、盡量使用數(shù)字型字段,若只含數(shù)值信息的字段盡量不要設(shè)計(jì)為字符型,這會(huì)降低查詢和連接的性能,并會(huì)增加存儲(chǔ)開(kāi)銷(xiāo)。這是因?yàn)橐嬖谔幚聿樵兒瓦B接時(shí)會(huì) 逐個(gè)比較字符串中每一個(gè)字符,而對(duì)于數(shù)字型而言只需要比較一次就夠了。 18、盡可能的使用 varchar/nvarchar 代替 char/nchar ,因?yàn)槭紫茸冮L(zhǎng)字段存儲(chǔ)空間小,可以節(jié)省存儲(chǔ)空間,其次對(duì)于查詢來(lái)說(shuō),在一個(gè)相對(duì)較小的字段內(nèi)搜索效率顯然要高些。 19、任何地方都不要使用 select * from t ,用具體的字段列表代替“*”,不要返回用不到的任何字段。 20、盡量使用表變量來(lái)代替臨時(shí)表。如果表變量包含大量數(shù)據(jù),請(qǐng)注意索引非常有限(只有主鍵索引)。 21、避免頻繁創(chuàng)建和刪除臨時(shí)表,以減少系統(tǒng)表資源的消耗。 22、臨時(shí)表并不是不可使用,適當(dāng)?shù)厥褂盟鼈兛梢允鼓承├谈行?#xff0c;例如,當(dāng)需要重復(fù)引用大型表或常用表中的某個(gè)數(shù)據(jù)集時(shí)。但是,對(duì)于一次性事件,最好使 用導(dǎo)出表。 23、在新建臨時(shí)表時(shí),如果一次性插入數(shù)據(jù)量很大,那么可以使用 select into 代替 create table,避免造成大量 log ,以提高速度;如果數(shù)據(jù)量不大,為了緩和系統(tǒng)表的資源,應(yīng)先create table,然后insert。 24、如果使用到了臨時(shí)表,在存儲(chǔ)過(guò)程的最后務(wù)必將所有的臨時(shí)表顯式刪除,先 truncate table ,然后 drop table ,這樣可以避免系統(tǒng)表的較長(zhǎng)時(shí)間鎖定。 25、盡量避免使用游標(biāo),因?yàn)橛螛?biāo)的效率較差,如果游標(biāo)操作的數(shù)據(jù)超過(guò)1萬(wàn)行,那么就應(yīng)該考慮改寫(xiě)。 26、使用基于游標(biāo)的方法或臨時(shí)表方法之前,應(yīng)先尋找基于集的解決方案來(lái)解決問(wèn)題,基于集的方法通常更有效。 27、與臨時(shí)表一樣,游標(biāo)并不是不可使用。對(duì)小型數(shù)據(jù)集使用 FAST_FORWARD 游標(biāo)通常要優(yōu)于其他逐行處理方法,尤其是在必須引用幾個(gè)表才能獲得所需的數(shù)據(jù)時(shí)。在結(jié)果集中包括“合計(jì)”的例程通常要比使用游標(biāo)執(zhí)行的速度快。如果開(kāi)發(fā)時(shí) 間允許,基于游標(biāo)的方法和基于集的方法都可以嘗試一下,看哪一種方法的效果更好。 28、在所有的存儲(chǔ)過(guò)程和觸發(fā)器的開(kāi)始處設(shè)置 SET NOCOUNT ON ,在結(jié)束時(shí)設(shè)置 SET NOCOUNT OFF 。無(wú)需在執(zhí)行存儲(chǔ)過(guò)程和觸發(fā)器的每個(gè)語(yǔ)句后向客戶端發(fā)送 DONE_IN_PROC 消息。 29、盡量避免向客戶端返回大數(shù)據(jù)量,若數(shù)據(jù)量過(guò)大,應(yīng)該考慮相應(yīng)需求是否合理。 30、盡量避免大事務(wù)操作,提高系統(tǒng)并發(fā)能力。MySQL 遇到的死鎖問(wèn)題
存儲(chǔ)引擎的 InnoDB 與 MyISAM
數(shù)據(jù)庫(kù)索引的原理
為什么要用 B-tree
聚集索引與非聚集索引的區(qū)別
limit 20000 加載很慢怎么解決
1.子查詢優(yōu)化法 先找出第一條數(shù)據(jù),然后大于等于這條數(shù)據(jù)的id就是要獲取的數(shù)據(jù) 2.倒排表優(yōu)化法 倒排表法類(lèi)似建立索引,用一張表來(lái)維護(hù)頁(yè)數(shù),然后通過(guò)高效的連接得到數(shù)據(jù) 缺點(diǎn):只適合數(shù)據(jù)數(shù)固定的情況,數(shù)據(jù)不能刪除,維護(hù)頁(yè)表困難 3.反向查找優(yōu)化法 當(dāng)偏移超過(guò)一半記錄數(shù)的時(shí)候,先用排序,這樣偏移就反轉(zhuǎn)了 缺點(diǎn):order by優(yōu)化比較麻煩,要增加索引,索引影響數(shù)據(jù)的修改效率,并且要知道總記錄數(shù),偏移大于數(shù)據(jù)的一半 4.limit限制優(yōu)化法 把limit偏移量限制低于某個(gè)數(shù)。。超過(guò)這個(gè)數(shù)等于沒(méi)數(shù)據(jù),我記得alibaba的dba說(shuō)過(guò)他們是這樣做的 5.只查索引法選擇合適的分布式主鍵方案
最常見(jiàn)的方式。利用數(shù)據(jù)庫(kù),全數(shù)據(jù)庫(kù)唯一。
優(yōu)點(diǎn):
1)簡(jiǎn)單,代碼方便,性能可以接受。
2)數(shù)字ID天然排序,對(duì)分頁(yè)或者需要排序的結(jié)果很有幫助。
缺點(diǎn): 1)不同數(shù)據(jù)庫(kù)語(yǔ)法和實(shí)現(xiàn)不同,數(shù)據(jù)庫(kù)遷移的時(shí)候或多數(shù)據(jù)庫(kù)版本支持的時(shí)候需要處理。 2)在單個(gè)數(shù)據(jù)庫(kù)或讀寫(xiě)分離或一主多從的情況下,只有一個(gè)主庫(kù)可以生成。有單點(diǎn)故障的風(fēng)險(xiǎn)。 3)在性能達(dá)不到要求的情況下,比較難于擴(kuò)展。 4)如果遇見(jiàn)多個(gè)系統(tǒng)需要合并或者涉及到數(shù)據(jù)遷移會(huì)相當(dāng)痛苦。 5)分表分庫(kù)的時(shí)候會(huì)有麻煩。 優(yōu)化方案: 1)針對(duì)主庫(kù)單點(diǎn),如果有多個(gè)Master庫(kù),則每個(gè)Master庫(kù)設(shè)置的起始數(shù)字不一樣,步長(zhǎng)一樣,可以是Master的個(gè)數(shù)。比如:Master1 生成的是 1,4,7,10,Master2生成的是2,5,8,11 Master3生成的是 3,6,9,12。這樣就可以有效生成集群中的唯一ID,也可以大大降低ID生成數(shù)據(jù)庫(kù)操作的負(fù)載。 2. UUID 常見(jiàn)的方式。可以利用數(shù)據(jù)庫(kù)也可以利用程序生成,一般來(lái)說(shuō)全球唯一。 優(yōu)點(diǎn) 1)簡(jiǎn)單,代碼方便。 2)生成ID性能非常好,基本不會(huì)有性能問(wèn)題。 3)全球唯一,在遇見(jiàn)數(shù)據(jù)遷移,系統(tǒng)數(shù)據(jù)合并,或者數(shù)據(jù)庫(kù)變更等情況下,可以從容應(yīng)對(duì)。 缺點(diǎn): 1)沒(méi)有排序,無(wú)法保證趨勢(shì)遞增。 2)UUID往往是使用字符串存儲(chǔ),查詢的效率比較低。 3)存儲(chǔ)空間比較大,如果是海量數(shù)據(jù)庫(kù),就需要考慮存儲(chǔ)量的問(wèn)題。 4)傳輸數(shù)據(jù)量大 5)不可讀。 4. Redis生成ID 當(dāng)使用數(shù)據(jù)庫(kù)來(lái)生成ID性能不夠要求的時(shí)候,我們可以嘗試使用Redis來(lái)生成ID。這主要依賴于Redis是單線程的,所以也可以用生成全局唯一的ID。可以用Redis的原子操作 INCR和INCRBY來(lái)實(shí)現(xiàn)。 5. Twitter的snowflake算法
ObjectId 規(guī)則
聊聊 MongoDB 使用場(chǎng)景
倒排索引
聊聊 ElasticSearch 使用場(chǎng)景
緩存使用
Redis 有哪些類(lèi)型 Redis 內(nèi)部結(jié)構(gòu) 聊聊 Redis 使用場(chǎng)景 Redis 持久化機(jī)制 Redis 如何實(shí)現(xiàn)持久化 Redis 集群方案與實(shí)現(xiàn) Redis 為什么是單線程的 緩存奔潰 緩存降級(jí) 使用緩存的合理性問(wèn)題 消息隊(duì)列 消息隊(duì)列的使用場(chǎng)景 消息的重發(fā)補(bǔ)償解決思路 消息的冪等性解決思路 消息的堆積解決思路 自己如何實(shí)現(xiàn)消息隊(duì)列 如何保證消息的有序性框架
Spring
BeanFactory 和 ApplicationContext 有什么區(qū)別 BeanFacotry是spring中比較原始的Factory。如XMLBeanFactory就是一種典型的BeanFactory。原始的BeanFactory無(wú)法支持spring的許多插件,如AOP功能、Web應(yīng)用等。 ApplicationContext接口,它由BeanFactory接口派生而來(lái),因而提供BeanFactory所有的功能。ApplicationContext以一種更向面向框架的方式工作以及對(duì)上下文進(jìn)行分層和實(shí)現(xiàn)繼承,ApplicationContext包還提供了以下的功能: ? MessageSource, 提供國(guó)際化的消息訪問(wèn) ? 資源訪問(wèn),如URL和文件 ? 事件傳播 ? 載入多個(gè)(有繼承關(guān)系)上下文 ,使得每一個(gè)上下文都專注于一個(gè)特定的層次,比如應(yīng)用的web層 Spring Bean 的生命周期 Spring上下文中的Bean也類(lèi)似,【Spring上下文的生命周期】 1. 實(shí)例化一個(gè)Bean,也就是我們通常說(shuō)的new 2. 按照Spring上下文對(duì)實(shí)例化的Bean進(jìn)行配置,也就是IOC注入 3. 如果這個(gè)Bean實(shí)現(xiàn)了BeanNameAware接口,會(huì)調(diào)用它實(shí)現(xiàn)的setBeanName(String beanId)方法,此處傳遞的是Spring配置文件中Bean的ID 4. 如果這個(gè)Bean實(shí)現(xiàn)了BeanFactoryAware接口,會(huì)調(diào)用它實(shí)現(xiàn)的setBeanFactory(),傳遞的是Spring工廠本身(可以用這個(gè)方法獲取到其他Bean) 5. 如果這個(gè)Bean實(shí)現(xiàn)了ApplicationContextAware接口,會(huì)調(diào)用setApplicationContext(ApplicationContext)方法,傳入Spring上下文,該方式同樣可以實(shí)現(xiàn)步驟4,但比4更好,以為ApplicationContext是BeanFactory的子接口,有更多的實(shí)現(xiàn)方法 6. 如果這個(gè)Bean關(guān)聯(lián)了BeanPostProcessor接口,將會(huì)調(diào)用postProcessBeforeInitialization(Object obj, String s)方法,BeanPostProcessor經(jīng)常被用作是Bean內(nèi)容的更改,并且由于這個(gè)是在Bean初始化結(jié)束時(shí)調(diào)用After方法,也可用于內(nèi)存或緩存技術(shù) 7. 如果這個(gè)Bean在Spring配置文件中配置了init-method屬性會(huì)自動(dòng)調(diào)用其配置的初始化方法 8. 如果這個(gè)Bean關(guān)聯(lián)了BeanPostProcessor接口,將會(huì)調(diào)用postAfterInitialization(Object obj, String s)方法 注意:以上工作完成以后就可以用這個(gè)Bean了,那這個(gè)Bean是一個(gè)single的,所以一般情況下我們調(diào)用同一個(gè)ID的Bean會(huì)是在內(nèi)容地址相同的實(shí)例 9. 當(dāng)Bean不再需要時(shí),會(huì)經(jīng)過(guò)清理階段,如果Bean實(shí)現(xiàn)了DisposableBean接口,會(huì)調(diào)用其實(shí)現(xiàn)的destroy方法 10. 最后,如果這個(gè)Bean的Spring配置中配置了destroy-method屬性,會(huì)自動(dòng)調(diào)用其配置的銷(xiāo)毀方法 以上10步驟可以作為面試或者筆試的模板,另外我們這里描述的是應(yīng)用Spring上下文Bean的生命周期,如果應(yīng)用Spring的工廠也就是BeanFactory的話去掉第5步就Ok了Spring IOC 如何實(shí)現(xiàn)IOC容器就是具有依賴注入功能的容器,IOC容器負(fù)責(zé)實(shí)例化、定位、配置應(yīng)用程序中的對(duì)象及建立這些對(duì)象間的依賴。應(yīng)用程序無(wú)需直接在代碼中new相關(guān)的對(duì)象,應(yīng)用程序由IOC容器進(jìn)行組裝。在Spring中BeanFactory是IOC容器的實(shí)際代表者。 Spring IOC容器如何知道哪些是它管理的對(duì)象呢?這就需要配置文件,Spring IOC容器通過(guò)讀取配置文件中的配置元數(shù)據(jù),通過(guò)元數(shù)據(jù)對(duì)應(yīng)用中的各個(gè)對(duì)象進(jìn)行實(shí)例化及裝配。一般使用基于xml配置文件進(jìn)行配置元數(shù)據(jù),而且Spring與配置文件完全解耦的,可以使用其他任何可能的方式進(jìn)行配置元數(shù)據(jù),比如注解、基于java文件的、基于屬性文件的配置都可以。說(shuō)說(shuō) Spring AOP Spring AOP 實(shí)現(xiàn)原理 1.IOC 許多應(yīng)用都是通過(guò)彼此間的相互合作來(lái)實(shí)現(xiàn)業(yè)務(wù)邏輯的,如類(lèi)A要調(diào)用類(lèi)B的方法,以前我們都是在類(lèi)A中,通過(guò)自身new一個(gè)類(lèi)B,然后在調(diào)用類(lèi)B的方法,現(xiàn)在我們把new類(lèi)B的事情交給spring來(lái)做,在我們調(diào)用的時(shí)候,容器會(huì)為我們實(shí)例化。 2. IOC容器的初始化過(guò)程 資源定位,即定義bean的xml-------》載入--------》IOC容器注冊(cè),注冊(cè)beanDefinition IOC容器的初始化過(guò)程,一般不包含bean的依賴注入的實(shí)現(xiàn),在spring IOC設(shè)計(jì)中,bean的注冊(cè)和依賴注入是兩個(gè)過(guò)程,,依賴注入一般發(fā)生在應(yīng)用第一次索取bean的時(shí)候,但是也可以在xm中配置,在容器初始化的時(shí)候,這個(gè)bean就完成了初始化。 3. 三種注入方式,構(gòu)造器、接口、set注入,我們常用的是set注入 4. bean是如何創(chuàng)建--- 工廠模式 5. 數(shù)據(jù)是如何注入-------反射 6.AOP 面向切面編程,在我們的應(yīng)用中,經(jīng)常需要做一些事情,但是這些事情與核心業(yè)務(wù)無(wú)關(guān),比如,要記錄所有update*方法的執(zhí)行時(shí)間時(shí)間,操作人等等信息,記錄到日志, 通過(guò)spring的AOP技術(shù),就可以在不修改update*的代碼的情況下完成該需求。 7.AOP的實(shí)現(xiàn)原理------代理動(dòng)態(tài)代理(cglib 與 JDK) 靜態(tài)代理是通過(guò)在代碼中顯式定義一個(gè)業(yè)務(wù)實(shí)現(xiàn)類(lèi)一個(gè)代理,在代理類(lèi)中對(duì)同名的業(yè)務(wù)方法進(jìn)行包裝,用戶通過(guò)代理類(lèi)調(diào)用被包裝過(guò)的業(yè)務(wù)方法; JDK動(dòng)態(tài)代理是通過(guò)接口中的方法名,在動(dòng)態(tài)生成的代理類(lèi)中調(diào)用業(yè)務(wù)實(shí)現(xiàn)類(lèi)的同名方法; CGlib動(dòng)態(tài)代理是通過(guò)繼承業(yè)務(wù)類(lèi),生成的動(dòng)態(tài)代理類(lèi)是業(yè)務(wù)類(lèi)的子類(lèi),通過(guò)重寫(xiě)業(yè)務(wù)方法進(jìn)行代理;Spring 事務(wù)實(shí)現(xiàn)方式 實(shí)現(xiàn)spring事務(wù)的四種方式分別為: (1)編程式事務(wù)管理:需要手動(dòng)編寫(xiě)代碼,在實(shí)際開(kāi)發(fā)中很少使用 (2)聲明式事務(wù)管理: (2.1)基于TransactionProxyFactoryBean的方式,需要為每個(gè)進(jìn)行事務(wù)管理的類(lèi)做相應(yīng)配置 (2.2)基于AspectJ的XML方式,不需要改動(dòng)類(lèi),在XML文件中配置好即可 (2.3)基于注解的方式,配置簡(jiǎn)單,需要在業(yè)務(wù)層類(lèi)中添加注解Spring 事務(wù)底層原理 工作原理及實(shí)現(xiàn) a、劃分處理單元——IOC 由于spring解決的問(wèn)題是對(duì)單個(gè)數(shù)據(jù)庫(kù)進(jìn)行局部事務(wù)處理的,具體的實(shí)現(xiàn)首相用spring中的IOC劃分了事務(wù)處理單元。并且將對(duì)事務(wù)的各種配置放到了ioc容器中(設(shè)置事務(wù)管理器,設(shè)置事務(wù)的傳播特性及隔離機(jī)制)。 b、AOP攔截需要進(jìn)行事務(wù)處理的類(lèi) Spring事務(wù)處理模塊是通過(guò)AOP功能來(lái)實(shí)現(xiàn)聲明式事務(wù)處理的,具體操作(比如事務(wù)實(shí)行的配置和讀取,事務(wù)對(duì)象的抽象),用TransactionProxyFactoryBean接口來(lái)使用AOP功能,生成proxy代理對(duì)象,通過(guò)TransactionInterceptor完成對(duì)代理方法的攔截,將事務(wù)處理的功能編織到攔截的方法中。 讀取ioc容器事務(wù)配置屬性,轉(zhuǎn)化為spring事務(wù)處理需要的內(nèi)部數(shù)據(jù)結(jié)構(gòu)(TransactionAttributeSourceAdvisor),轉(zhuǎn)化為T(mén)ransactionAttribute表示的數(shù)據(jù)對(duì)象。 c、對(duì)事物處理實(shí)現(xiàn)(事務(wù)的生成、提交、回滾、掛起) spring委托給具體的事務(wù)處理器實(shí)現(xiàn)。實(shí)現(xiàn)了一個(gè)抽象和適配。適配的具體事務(wù)處理器:DataSource數(shù)據(jù)源支持、hibernate數(shù)據(jù)源事務(wù)處理支持、JDO數(shù)據(jù)源事務(wù)處理支持,JPA、JTA數(shù)據(jù)源事務(wù)處理支持。這些支持都是通過(guò)設(shè)計(jì)PlatformTransactionManager、AbstractPlatforTransaction一系列事務(wù)處理的支持。 為常用數(shù)據(jù)源支持提供了一系列的TransactionManager。 d、結(jié)合 PlatformTransactionManager實(shí)現(xiàn)了TransactionInterception接口,讓其與TransactionProxyFactoryBean結(jié)合起來(lái),形成一個(gè)Spring聲明式事務(wù)處理的設(shè)計(jì)體系。 如何自定義注解實(shí)現(xiàn)功能Spring MVC 運(yùn)行流程Spring MVC 啟動(dòng)流程Spring 的單例實(shí)現(xiàn)原理 大家真正要記住的是Spring對(duì)bean實(shí)例的創(chuàng)建是采用單例注冊(cè)表的方式進(jìn)行實(shí)現(xiàn)的,而這個(gè)注冊(cè)表的緩存是HashMap對(duì)象,如果配置文件中的配置信息不要求使用單例,Spring會(huì)采用新建實(shí)例的方式返回對(duì)象實(shí)例Spring 框架中用到了哪些設(shè)計(jì)模式 Spring框架中使用到了大量的設(shè)計(jì)模式,下面列舉了比較有代表性的: 代理模式—在AOP和remoting中被用的比較多。 單例模式—在spring配置文件中定義的bean默認(rèn)為單例模式。 模板方法—用來(lái)解決代碼重復(fù)的問(wèn)題。比如. RestTemplate, JmsTemplate, JpaTemplate。 工廠模式—BeanFactory用來(lái)創(chuàng)建對(duì)象的實(shí)例。 適配器--spring aop 裝飾器--spring data hashmapper 觀察者-- spring 時(shí)間驅(qū)動(dòng)模型 回調(diào)--Spring ResourceLoaderAware回調(diào)接口 Spring 其他產(chǎn)品(Srping Boot、Spring Cloud、Spring Secuirity、Spring Data、Spring AMQP 等)Netty
為什么選擇 Netty 說(shuō)說(shuō)業(yè)務(wù)中,Netty 的使用場(chǎng)景原生的 NIO 在 JDK 1.7 版本存在 epoll bug https://www.cnblogs.com/JAYIT/p/8241634.html Selector BUG出現(xiàn)的原因 若Selector的輪詢結(jié)果為空,也沒(méi)有wakeup或新消息處理,則發(fā)生空輪詢,CPU使用率100%, Netty的解決辦法 對(duì)Selector的select操作周期進(jìn)行統(tǒng)計(jì),每完成一次空的select操作進(jìn)行一次計(jì)數(shù), 若在某個(gè)周期內(nèi)連續(xù)發(fā)生N次空輪詢,則觸發(fā)了epoll死循環(huán)bug。 重建Selector,判斷是否是其他線程發(fā)起的重建請(qǐng)求,若不是則將原SocketChannel從舊的Selector上去除注冊(cè),重新注冊(cè)到新的Selector上,并將原來(lái)的Selector關(guān)閉。什么是TCP 粘包/拆包、TCP粘包/拆包的解決辦法 發(fā)生TCP粘包或拆包有很多原因,現(xiàn)列出常見(jiàn)的幾點(diǎn),可能不全面,歡迎補(bǔ)充, 1、要發(fā)送的數(shù)據(jù)大于TCP發(fā)送緩沖區(qū)剩余空間大小,將會(huì)發(fā)生拆包。 2、待發(fā)送數(shù)據(jù)大于MSS(最大報(bào)文長(zhǎng)度),TCP在傳輸前將進(jìn)行拆包。 3、要發(fā)送的數(shù)據(jù)小于TCP發(fā)送緩沖區(qū)的大小,TCP將多次寫(xiě)入緩沖區(qū)的數(shù)據(jù)一次發(fā)送出去,將會(huì)發(fā)生粘包。 4、接收數(shù)據(jù)端的應(yīng)用層沒(méi)有及時(shí)讀取接收緩沖區(qū)中的數(shù)據(jù),將發(fā)生粘包。 粘包、拆包解決辦法 通過(guò)以上分析,我們清楚了粘包或拆包發(fā)生的原因,那么如何解決這個(gè)問(wèn)題呢?解決問(wèn)題的關(guān)鍵在于如何給每個(gè)數(shù)據(jù)包添加邊界信息,常用的方法有如下幾個(gè): 1、發(fā)送端給每個(gè)數(shù)據(jù)包添加包首部,首部中應(yīng)該至少包含數(shù)據(jù)包的長(zhǎng)度,這樣接收端在接收到數(shù)據(jù)后,通過(guò)讀取包首部的長(zhǎng)度字段,便知道每一個(gè)數(shù)據(jù)包的實(shí)際長(zhǎng)度了。 2、發(fā)送端將每個(gè)數(shù)據(jù)包封裝為固定長(zhǎng)度(不夠的可以通過(guò)補(bǔ)0填充),這樣接收端每次從接收緩沖區(qū)中讀取固定長(zhǎng)度的數(shù)據(jù)就自然而然的把每個(gè)數(shù)據(jù)包拆分開(kāi)來(lái)。 3、可以在數(shù)據(jù)包之間設(shè)置邊界,如添加特殊符號(hào),這樣,接收端通過(guò)這個(gè)邊界就可以將不同的數(shù)據(jù)包拆分開(kāi)。Netty 線程模型 Netty通過(guò)Reactor模型基于多路復(fù)用器接收并處理用戶請(qǐng)求,內(nèi)部實(shí)現(xiàn)了兩個(gè)線程池,boss線程池和work線程池,其中boss線程池的線程負(fù)責(zé)處理請(qǐng)求的accept事件,當(dāng)接收到accept事件的請(qǐng)求時(shí),把對(duì)應(yīng)的socket封裝到一個(gè)NioSocketChannel中,并交給work線程池,其中work線程池負(fù)責(zé)請(qǐng)求的read和write事件,由對(duì)應(yīng)的Handler處理。 單線程模型:所有I/O操作都由一個(gè)線程完成,即多路復(fù)用、事件分發(fā)和處理都是在一個(gè)Reactor線程上完成的。既要接收客戶端的連接請(qǐng)求,向服務(wù)端發(fā)起連接,又要發(fā)送/讀取請(qǐng)求或應(yīng)答/響應(yīng)消息。一個(gè)NIO 線程同時(shí)處理成百上千的鏈路,性能上無(wú)法支撐,速度慢,若線程進(jìn)入死循環(huán),整個(gè)程序不可用,對(duì)于高負(fù)載、大并發(fā)的應(yīng)用場(chǎng)景不合適。 多線程模型:有一個(gè)NIO 線程(Acceptor) 只負(fù)責(zé)監(jiān)聽(tīng)服務(wù)端,接收客戶端的TCP 連接請(qǐng)求;NIO 線程池負(fù)責(zé)網(wǎng)絡(luò)IO 的操作,即消息的讀取、解碼、編碼和發(fā)送;1 個(gè)NIO 線程可以同時(shí)處理N 條鏈路,但是1 個(gè)鏈路只對(duì)應(yīng)1 個(gè)NIO 線程,這是為了防止發(fā)生并發(fā)操作問(wèn)題。但在并發(fā)百萬(wàn)客戶端連接或需要安全認(rèn)證時(shí),一個(gè)Acceptor 線程可能會(huì)存在性能不足問(wèn)題。 主從多線程模型:Acceptor 線程用于綁定監(jiān)聽(tīng)端口,接收客戶端連接,將SocketChannel 從主線程池的Reactor 線程的多路復(fù)用器上移除,重新注冊(cè)到Sub 線程池的線程上,用于處理I/O 的讀寫(xiě)等操作,從而保證mainReactor只負(fù)責(zé)接入認(rèn)證、握手等操作;說(shuō)說(shuō) Netty 的零拷貝 Netty的零拷貝實(shí)現(xiàn)? Netty的接收和發(fā)送ByteBuffer采用DIRECT BUFFERS,使用堆外直接內(nèi)存進(jìn)行Socket讀寫(xiě),不需要進(jìn)行字節(jié)緩沖區(qū)的二次拷貝。堆內(nèi)存多了一次內(nèi)存拷貝,JVM會(huì)將堆內(nèi)存Buffer拷貝一份到直接內(nèi)存中,然后才寫(xiě)入Socket中。ByteBuffer由ChannelConfig分配,而ChannelConfig創(chuàng)建ByteBufAllocator默認(rèn)使用Direct BufferCompositeByteBuf 類(lèi)可以將多個(gè) ByteBuf 合并為一個(gè)邏輯上的 ByteBuf, 避免了傳統(tǒng)通過(guò)內(nèi)存拷貝的方式將幾個(gè)小Buffer合并成一個(gè)大的Buffer。addComponents方法將 header 與 body 合并為一個(gè)邏輯上的 ByteBuf, 這兩個(gè) ByteBuf 在CompositeByteBuf 內(nèi)部都是單獨(dú)存在的, CompositeByteBuf 只是邏輯上是一個(gè)整體通過(guò) FileRegion 包裝的FileChannel.tranferTo方法 實(shí)現(xiàn)文件傳輸, 可以直接將文件緩沖區(qū)的數(shù)據(jù)發(fā)送到目標(biāo) Channel,避免了傳統(tǒng)通過(guò)循環(huán)write方式導(dǎo)致的內(nèi)存拷貝問(wèn)題。通過(guò) wrap方法, 我們可以將 byte[] 數(shù)組、ByteBuf、ByteBuffer等包裝成一個(gè) Netty ByteBuf 對(duì)象, 進(jìn)而避免了拷貝操作。Selector BUG:若Selector的輪詢結(jié)果為空,也沒(méi)有wakeup或新消息處理,則發(fā)生空輪詢,CPU使用率100%,Netty的解決辦法:對(duì)Selector的select操作周期進(jìn)行統(tǒng)計(jì),每完成一次空的select操作進(jìn)行一次計(jì)數(shù),若在某個(gè)周期內(nèi)連續(xù)發(fā)生N次空輪詢,則觸發(fā)了epoll死循環(huán)bug。重建Selector,判斷是否是其他線程發(fā)起的重建請(qǐng)求,若不是則將原SocketChannel從舊的Selector上去除注冊(cè),重新注冊(cè)到新的Selector上,并將原來(lái)的Selector關(guān)閉。Netty 內(nèi)部執(zhí)行流程 服務(wù)端依次發(fā)生的步驟 建立服務(wù)端監(jiān)聽(tīng)套接字ServerSocketChannel,以及對(duì)應(yīng)的管道pipeline; 啟動(dòng)boss線程,將ServerSocketChannel注冊(cè)到boss線程持有的selector中,并將注冊(cè)返回的selectionKey賦值給ServerSocketChannel關(guān)聯(lián)的selectionKey變量; 在ServerSocketChannel對(duì)應(yīng)的管道中觸發(fā)channelRegistered事件; 綁定IP和端口 觸發(fā)channelActive事件,并將ServerSocketChannel關(guān)聯(lián)的selectionKey的OP_ACCEPT位置為1。 客戶端發(fā)起connect請(qǐng)求后,boss線程正在運(yùn)行的select循環(huán)檢測(cè)到了該ServerSocketChannel的ACCEPT事件就緒,則通過(guò)accept系統(tǒng)調(diào)用建立一個(gè)已連接套接字SocketChannel,并為其創(chuàng)建對(duì)應(yīng)的管道; 在服務(wù)端監(jiān)聽(tīng)套接字對(duì)應(yīng)的管道中觸發(fā)channelRead事件; channelRead事件由ServerBootstrapAcceptor的channelRead方法響應(yīng):為已連接套接字對(duì)應(yīng)的管道加入ChannelInitializer處理器;啟動(dòng)一個(gè)worker線程,并將已連接套接字的注冊(cè)任務(wù)加入到worker線程的任務(wù)隊(duì)列中; worker線程執(zhí)行已連接套接字的注冊(cè)任務(wù):將已連接套接字注冊(cè)到worker線程持有的selector中,并將注冊(cè)返回的selectionKey賦值給已連接套接字關(guān)聯(lián)的selectionKey變量;在已連接套接字對(duì)應(yīng)的管道中觸發(fā)channelRegistered事件;channelRegistered事件由ChannelInitializer的channelRegistered方法響應(yīng):將自定義的處理器(譬如EchoServerHandler)加入到已連接套接字對(duì)應(yīng)的管道中;在已連接套接字對(duì)應(yīng)的管道中觸發(fā)channelActive事件;channelActive事件由已連接套接字對(duì)應(yīng)的管道中的inbound處理器的channelActive方法響應(yīng);將已連接套接字關(guān)聯(lián)的selectionKey的OP_READ位置為1;至此,worker線程關(guān)聯(lián)的selector就開(kāi)始監(jiān)聽(tīng)已連接套接字的READ事件了。 在worker線程運(yùn)行的同時(shí),Boss線程接著在服務(wù)端監(jiān)聽(tīng)套接字對(duì)應(yīng)的管道中觸發(fā)channelReadComplete事件。 客戶端向服務(wù)端發(fā)送消息后,worker線程正在運(yùn)行的selector循環(huán)會(huì)檢測(cè)到已連接套接字的READ事件就緒。則通過(guò)read系統(tǒng)調(diào)用將消息從套接字的接受緩沖區(qū)中讀到AdaptiveRecvByteBufAllocator(可以自適應(yīng)調(diào)整分配的緩存的大小)分配的緩存中; 在已連接套接字對(duì)應(yīng)的管道中觸發(fā)channelRead事件; channelRead事件由EchoServerHandler處理器的channelRead方法響應(yīng):執(zhí)行write操作將消息存儲(chǔ)到ChannelOutboundBuffer中; 在已連接套接字對(duì)應(yīng)的管道中觸發(fā)ChannelReadComplete事件; ChannelReadComplete事件由EchoServerHandler處理器的channelReadComplete方法響應(yīng):執(zhí)行flush操作將消息從ChannelOutboundBuffer中flush到套接字的發(fā)送緩沖區(qū)中; 客戶端依次發(fā)生的步驟 建立套接字SocketChannel,以及對(duì)應(yīng)的管道pipeline; 啟動(dòng)客戶端線程,將SocketChannel注冊(cè)到客戶端線程持有的selector中,并將注冊(cè)返回的selectionKey賦值給SocketChannel關(guān)聯(lián)的selectionKey變量; 觸發(fā)channelRegistered事件; channelRegistered事件由ChannelInitializer的channelRegistered方法響應(yīng):將客戶端自定義的處理器(譬如EchoClientHandler)按順序加入到管道中; 向服務(wù)端發(fā)起connect請(qǐng)求,并將SocketChannel關(guān)聯(lián)的selectionKey的OP_CONNECT位置為1; 開(kāi)始三次握手,客戶端線程正在運(yùn)行的select循環(huán)檢測(cè)到了該SocketChannel的CONNECT事件就緒,則將關(guān)聯(lián)的selectionKey的OP_CONNECT位置為0,再通過(guò)調(diào)用finishConnect完成連接的建立; 觸發(fā)channelActive事件; channelActive事件由EchoClientHandler的channelActive方法響應(yīng),通過(guò)調(diào)用ctx.writeAndFlush方法將消息發(fā)往服務(wù)端; 首先將消息存儲(chǔ)到ChannelOutboundBuffer中;(如果ChannelOutboundBuffer存儲(chǔ)的所有未flush的消息的大小超過(guò)高水位線writeBufferHighWaterMark(默認(rèn)值為64 * 1024),則會(huì)觸發(fā)ChannelWritabilityChanged事件) 然后將消息從ChannelOutboundBuffer中flush到套接字的發(fā)送緩沖區(qū)中;(如果ChannelOutboundBuffer存儲(chǔ)的所有未flush的消息的大小小于低水位線,則會(huì)觸發(fā)ChannelWritabilityChanged事件)Netty 重連實(shí)現(xiàn) 使用 Netty 實(shí)現(xiàn)心跳機(jī)制的關(guān)鍵就是利用 IdleStateHandler 來(lái)產(chǎn)生對(duì)應(yīng)的 idle 事件. 一般是客戶端負(fù)責(zé)發(fā)送心跳的 PING 消息, 因此客戶端注意關(guān)注 ALL_IDLE 事件, 在這個(gè)事件觸發(fā)后, 客戶端需要向服務(wù)器發(fā)送 PING 消息, 告訴服務(wù)器"我還存活著". 服務(wù)器是接收客戶端的 PING 消息的, 因此服務(wù)器關(guān)注的是 READER_IDLE 事件, 并且服務(wù)器的 READER_IDLE 間隔需要比客戶端的 ALL_IDLE 事件間隔大(例如客戶端ALL_IDLE 是5s 沒(méi)有讀寫(xiě)時(shí)觸發(fā), 因此服務(wù)器的 READER_IDLE 可以設(shè)置為10s) 當(dāng)服務(wù)器收到客戶端的 PING 消息時(shí), 會(huì)發(fā)送一個(gè) PONG 消息作為回復(fù). 一個(gè) PING-PONG 消息對(duì)就是一個(gè)心跳交互. 在client在連接服務(wù)器的方法上加上一個(gè)監(jiān)聽(tīng),當(dāng)發(fā)生斷線的時(shí)候進(jìn)行重新連接即可。unb微服務(wù)篇
微服務(wù) 前后端分離是如何做的 微服務(wù)哪些框架 你怎么理解 RPC 框架 說(shuō)說(shuō) RPC 的實(shí)現(xiàn)原理 1)服務(wù)消費(fèi)方(client)調(diào)用以本地調(diào)用方式調(diào)用服務(wù); 2)client stub接收到調(diào)用后負(fù)責(zé)將方法、參數(shù)等組裝成能夠進(jìn)行網(wǎng)絡(luò)傳輸?shù)南Ⅲw; 3)client stub找到服務(wù)地址,并將消息發(fā)送到服務(wù)端; 4)server stub收到消息后進(jìn)行解碼; 5)server stub根據(jù)解碼結(jié)果調(diào)用本地的服務(wù); 6)本地服務(wù)執(zhí)行并將結(jié)果返回給server stub; 7)server stub將返回結(jié)果打包成消息并發(fā)送至消費(fèi)方; 8)client stub接收到消息,并進(jìn)行解碼; 9)服務(wù)消費(fèi)方得到最終結(jié)果。 使用到的技術(shù) 1、動(dòng)態(tài)代理 生成 client stub和server stub需要用到 Java 動(dòng)態(tài)代理技術(shù) ,我們可以使用JDK原生的動(dòng)態(tài)代理機(jī)制,可以使用一些開(kāi)源字節(jié)碼工具框架 如:CgLib、Javassist等。2、序列化 為了能在網(wǎng)絡(luò)上傳輸和接收 Java對(duì)象,我們需要對(duì)它進(jìn)行 序列化和反序列化操作。 * 序列化:將Java對(duì)象轉(zhuǎn)換成byte[]的過(guò)程,也就是編碼的過(guò)程; * 反序列化:將byte[]轉(zhuǎn)換成Java對(duì)象的過(guò)程;可以使用Java原生的序列化機(jī)制,但是效率非常低,推薦使用一些開(kāi)源的、成熟的序列化技術(shù),例如:protobuf、Thrift、hessian、Kryo、Msgpack關(guān)于序列化工具性能比較可以參考:jvm-serializers3、NIO 當(dāng)前很多RPC框架都直接基于netty這一IO通信框架,比如阿里巴巴的HSF、dubbo,Hadoop Avro,推薦使用Netty 作為底層通信框架。4、服務(wù)注冊(cè)中心 可選技術(shù): * Redis * Zookeeper * Consul * Etcd說(shuō)說(shuō) Dubbo 的實(shí)現(xiàn)原理 dubbo作為rpc框架,實(shí)現(xiàn)的效果就是調(diào)用遠(yuǎn)程的方法就像在本地調(diào)用一樣。如何做到呢?就是本地有對(duì)遠(yuǎn)程方法的描述,包括方法名、參數(shù)、返回值,在dubbo中是遠(yuǎn)程和本地使用同樣的接口;然后呢,要有對(duì)網(wǎng)絡(luò)通信的封裝,要對(duì)調(diào)用方來(lái)說(shuō)通信細(xì)節(jié)是完全不可見(jiàn)的,網(wǎng)絡(luò)通信要做的就是將調(diào)用方法的屬性通過(guò)一定的協(xié)議(簡(jiǎn)單來(lái)說(shuō)就是消息格式)傳遞到服務(wù)端;服務(wù)端按照協(xié)議解析出調(diào)用的信息;執(zhí)行相應(yīng)的方法;在將方法的返回值通過(guò)協(xié)議傳遞給客戶端;客戶端再解析;在調(diào)用方式上又可以分為同步調(diào)用和異步調(diào)用;簡(jiǎn)單來(lái)說(shuō)基本就這個(gè)過(guò)程你怎么理解 RESTful 說(shuō)說(shuō)如何設(shè)計(jì)一個(gè)良好的 API 如何理解 RESTful API 的冪等性如何保證接口的冪等性 全局唯一ID 如果使用全局唯一ID,就是根據(jù)業(yè)務(wù)的操作和內(nèi)容生成一個(gè)全局ID,在執(zhí)行操作前先根據(jù)這個(gè)全局唯一ID是否存在,來(lái)判斷這個(gè)操作是否已經(jīng)執(zhí)行。如果不存在則把全局ID,存儲(chǔ)到存儲(chǔ)系統(tǒng)中,比如數(shù)據(jù)庫(kù)、Redis等。如果存在則表示該方法已經(jīng)執(zhí)行。 從工程的角度來(lái)說(shuō),使用全局ID做冪等可以作為一個(gè)業(yè)務(wù)的基礎(chǔ)的微服務(wù)存在,在很多的微服務(wù)中都會(huì)用到這樣的服務(wù),在每個(gè)微服務(wù)中都完成這樣的功能,會(huì)存在工作量重復(fù)。另外打造一個(gè)高可靠的冪等服務(wù)還需要考慮很多問(wèn)題,比如一臺(tái)機(jī)器雖然把全局ID先寫(xiě)入了存儲(chǔ),但是在寫(xiě)入之后掛了,這就需要引入全局ID的超時(shí)機(jī)制。使用全局唯一ID是一個(gè)通用方案,可以支持插入、更新、刪除業(yè)務(wù)操作。但是這個(gè)方案看起來(lái)很美但是實(shí)現(xiàn)起來(lái)比較麻煩,下面的方案適用于特定的場(chǎng)景,但是實(shí)現(xiàn)起來(lái)比較簡(jiǎn)單。 去重表這種方法適用于在業(yè)務(wù)中有唯一標(biāo)的插入場(chǎng)景中,比如在以上的支付場(chǎng)景中,如果一個(gè)訂單只會(huì)支付一次,所以訂單ID可以作為唯一標(biāo)識(shí)。這時(shí),我們就可以建一張去重表,并且把唯一標(biāo)識(shí)作為唯一索引,在我們實(shí)現(xiàn)時(shí),把創(chuàng)建支付單據(jù)和寫(xiě)入去去重表,放在一個(gè)事務(wù)中,如果重復(fù)創(chuàng)建,數(shù)據(jù)庫(kù)會(huì)拋出唯一約束異常,操作就會(huì)回滾。 插入或更新這種方法插入并且有唯一索引的情況,比如我們要關(guān)聯(lián)商品品類(lèi),其中商品的ID和品類(lèi)的ID可以構(gòu)成唯一索引,并且在數(shù)據(jù)表中也增加了唯一索引。這時(shí)就可以使用InsertOrUpdate操作。在MySQL數(shù)據(jù)庫(kù)中如下:insert into goods_category (goods_id,category_id,create_time,update_time) values(#{goodsId},#{categoryId},now(),now()) on DUPLICATE KEY UPDATE update_time=now()多版本控制 這種方法適合在更新的場(chǎng)景中,比如我們要更新商品的名字,這時(shí)我們就可以在更新的接口中增加一個(gè)版本號(hào),來(lái)做冪等說(shuō)說(shuō) CAP 定理、 BASE 理論 CAP理論 一個(gè)經(jīng)典的分布式系統(tǒng)理論。CAP理論告訴我們:一個(gè)分布式系統(tǒng)不可能同時(shí)滿足一致性(C:Consistency)、可用性(A:Availability)和分區(qū)容錯(cuò)性(P:Partition tolerance)這三個(gè)基本需求,最多只能同時(shí)滿足其中兩項(xiàng)。 1、一致性 在分布式環(huán)境下,一致性是指數(shù)據(jù)在多個(gè)副本之間能否保持一致的特性。在一致性的需求下,當(dāng)一個(gè)系統(tǒng)在數(shù)據(jù)一致的狀態(tài)下執(zhí)行更新操作后,應(yīng)該保證系統(tǒng)的數(shù)據(jù)仍然處于一直的狀態(tài)。 對(duì)于一個(gè)將數(shù)據(jù)副本分布在不同分布式節(jié)點(diǎn)上的系統(tǒng)來(lái)說(shuō),如果對(duì)第一個(gè)節(jié)點(diǎn)的數(shù)據(jù)進(jìn) 行了更新操作并且更新成功后,卻沒(méi)有使得第二個(gè)節(jié)點(diǎn)上的數(shù)據(jù)得到相應(yīng)的更新,于是在對(duì)第二個(gè)節(jié)點(diǎn)的數(shù)據(jù)進(jìn)行讀取操作時(shí),獲取的依然是老數(shù)據(jù)(或稱為臟數(shù) 據(jù)),這就是典型的分布式數(shù)據(jù)不一致的情況。在分布式系統(tǒng)中,如果能夠做到針對(duì)一個(gè)數(shù)據(jù)項(xiàng)的更新操作執(zhí)行成功后,所有的用戶都可以讀取到其最新的值,那么 這樣的系統(tǒng)就被認(rèn)為具有強(qiáng)一致性 2、可用性 可用性是指系統(tǒng)提供的服務(wù)必須一直處于可用的狀態(tài),對(duì)于用戶的每一個(gè)操作請(qǐng)求總是能夠在有限的時(shí)間內(nèi)返回結(jié)果。這里的重點(diǎn)是"有限時(shí)間內(nèi)"和"返回結(jié)果"。 "有限時(shí)間內(nèi)"是指,對(duì)于用戶的一個(gè)操作請(qǐng)求,系統(tǒng)必須能夠在指定的時(shí)間內(nèi)返回對(duì) 應(yīng)的處理結(jié)果,如果超過(guò)了這個(gè)時(shí)間范圍,那么系統(tǒng)就被認(rèn)為是不可用的。另外,"有限的時(shí)間內(nèi)"是指系統(tǒng)設(shè)計(jì)之初就設(shè)計(jì)好的運(yùn)行指標(biāo),通常不同系統(tǒng)之間有很 大的不同,無(wú)論如何,對(duì)于用戶請(qǐng)求,系統(tǒng)必須存在一個(gè)合理的響應(yīng)時(shí)間,否則用戶便會(huì)對(duì)系統(tǒng)感到失望。 "返回結(jié)果"是可用性的另一個(gè)非常重要的指標(biāo),它要求系統(tǒng)在完成對(duì)用戶請(qǐng)求的處理后,返回一個(gè)正常的響應(yīng)結(jié)果。正常的響應(yīng)結(jié)果通常能夠明確地反映出隊(duì)請(qǐng)求的處理結(jié)果,即成功或失敗,而不是一個(gè)讓用戶感到困惑的返回結(jié)果。 3、分區(qū)容錯(cuò)性 分區(qū)容錯(cuò)性約束了一個(gè)分布式系統(tǒng)具有如下特性:分布式系統(tǒng)在遇到任何網(wǎng)絡(luò)分區(qū)故障的時(shí)候,仍然需要能夠保證對(duì)外提供滿足一致性和可用性的服務(wù),除非是整個(gè)網(wǎng)絡(luò)環(huán)境都發(fā)生了故障。 網(wǎng)絡(luò)分區(qū)是指在分布式系統(tǒng)中,不同的節(jié)點(diǎn)分布在不同的子網(wǎng)絡(luò)(機(jī)房或異地網(wǎng)絡(luò)) 中,由于一些特殊的原因?qū)е逻@些子網(wǎng)絡(luò)出現(xiàn)網(wǎng)絡(luò)不連通的狀況,但各個(gè)子網(wǎng)絡(luò)的內(nèi)部網(wǎng)絡(luò)是正常的,從而導(dǎo)致整個(gè)系統(tǒng)的網(wǎng)絡(luò)環(huán)境被切分成了若干個(gè)孤立的區(qū)域。 需要注意的是,組成一個(gè)分布式系統(tǒng)的每個(gè)節(jié)點(diǎn)的加入與退出都可以看作是一個(gè)特殊的網(wǎng)絡(luò)分區(qū)。 既然一個(gè)分布式系統(tǒng)無(wú)法同時(shí)滿足一致性、可用性、分區(qū)容錯(cuò)性三個(gè)特點(diǎn),所以我們就需要拋棄一樣: 用一張表格說(shuō)明一下: 選 擇 說(shuō) 明 CA 放棄分區(qū)容錯(cuò)性,加強(qiáng)一致性和可用性,其實(shí)就是傳統(tǒng)的單機(jī)數(shù)據(jù)庫(kù)的選擇 AP 放棄一致性(這里說(shuō)的一致性是強(qiáng)一致性),追求分區(qū)容錯(cuò)性和可用性,這是很多分布式系統(tǒng)設(shè)計(jì)時(shí)的選擇,例如很多NoSQL系統(tǒng)就是如此 CP 放棄可用性,追求一致性和分區(qū)容錯(cuò)性,基本不會(huì)選擇,網(wǎng)絡(luò)問(wèn)題會(huì)直接讓整個(gè)系統(tǒng)不可用 需要明確的一點(diǎn)是,對(duì)于一個(gè)分布式系統(tǒng)而言,分區(qū)容錯(cuò)性是一個(gè)最基本的要求。因?yàn)?既然是一個(gè)分布式系統(tǒng),那么分布式系統(tǒng)中的組件必然需要被部署到不同的節(jié)點(diǎn),否則也就無(wú)所謂分布式系統(tǒng)了,因此必然出現(xiàn)子網(wǎng)絡(luò)。而對(duì)于分布式系統(tǒng)而言,網(wǎng) 絡(luò)問(wèn)題又是一個(gè)必定會(huì)出現(xiàn)的異常情況,因此分區(qū)容錯(cuò)性也就成為了一個(gè)分布式系統(tǒng)必然需要面對(duì)和解決的問(wèn)題。因此系統(tǒng)架構(gòu)師往往需要把精力花在如何根據(jù)業(yè)務(wù) 特點(diǎn)在C(一致性)和A(可用性)之間尋求平衡。BASE理論 BASE是Basically Available(基本可用)、Soft state(軟狀態(tài))和Eventually consistent(最終一致性)三個(gè)短語(yǔ)的縮寫(xiě)。BASE理論是對(duì)CAP中一致性和可用性權(quán)衡的結(jié)果,其來(lái)源于對(duì)大規(guī)模互聯(lián)網(wǎng)系統(tǒng)分布式實(shí)踐的總結(jié), 是基于CAP定理逐步演化而來(lái)的。BASE理論的核心思想是:即使無(wú)法做到強(qiáng)一致性,但每個(gè)應(yīng)用都可以根據(jù)自身業(yè)務(wù)特點(diǎn),采用適當(dāng)?shù)姆绞絹?lái)使系統(tǒng)達(dá)到最終一致性。接下來(lái)看一下BASE中的三要素: 1、基本可用 基本可用是指分布式系統(tǒng)在出現(xiàn)不可預(yù)知故障的時(shí)候,允許損失部分可用性----注意,這絕不等價(jià)于系統(tǒng)不可用。比如: (1)響應(yīng)時(shí)間上的損失。正常情況下,一個(gè)在線搜索引擎需要在0.5秒之內(nèi)返回給用戶相應(yīng)的查詢結(jié)果,但由于出現(xiàn)故障,查詢結(jié)果的響應(yīng)時(shí)間增加了1~2秒 (2)系統(tǒng)功能上的損失:正常情況下,在一個(gè)電子商務(wù)網(wǎng)站上進(jìn)行購(gòu)物的時(shí)候,消費(fèi)者幾乎能夠順利完成每一筆訂單,但是在一些節(jié)日大促購(gòu)物高峰的時(shí)候,由于消費(fèi)者的購(gòu)物行為激增,為了保護(hù)購(gòu)物系統(tǒng)的穩(wěn)定性,部分消費(fèi)者可能會(huì)被引導(dǎo)到一個(gè)降級(jí)頁(yè)面 2、軟狀態(tài) 軟狀態(tài)指允許系統(tǒng)中的數(shù)據(jù)存在中間狀態(tài),并認(rèn)為該中間狀態(tài)的存在不會(huì)影響系統(tǒng)的整體可用性,即允許系統(tǒng)在不同節(jié)點(diǎn)的數(shù)據(jù)副本之間進(jìn)行數(shù)據(jù)同步的過(guò)程存在延時(shí) 3、最終一致性 最終一致性強(qiáng)調(diào)的是所有的數(shù)據(jù)副本,在經(jīng)過(guò)一段時(shí)間的同步之后,最終都能夠達(dá)到一個(gè)一致的狀態(tài)。因此,最終一致性的本質(zhì)是需要系統(tǒng)保證最終數(shù)據(jù)能夠達(dá)到一致,而不需要實(shí)時(shí)保證系統(tǒng)數(shù)據(jù)的強(qiáng)一致性。 總的來(lái)說(shuō),BASE理論面向的是大型高可用可擴(kuò)展的分布式系統(tǒng),和傳統(tǒng)的事物ACID特性是相反的,它完全不同于ACID的強(qiáng)一致性模型,而是通過(guò)犧牲強(qiáng)一致性來(lái)獲得可用性,并允許數(shù)據(jù)在一段時(shí)間內(nèi)是不一致的,但最終達(dá)到一致?tīng)顟B(tài)。但同時(shí),在實(shí)際的分布式場(chǎng)景中,不同業(yè)務(wù)單元和組件對(duì)數(shù)據(jù)一致性的要求是不同的,因此在具體的分布式系統(tǒng)架構(gòu)設(shè)計(jì)過(guò)程中,ACID特性和BASE理論往往又會(huì)結(jié)合在一起。怎么考慮數(shù)據(jù)一致性問(wèn)題 說(shuō)說(shuō)最終一致性的實(shí)現(xiàn)方案 你怎么看待微服務(wù) 微服務(wù)與 SOA 的區(qū)別 如何拆分服務(wù) 微服務(wù)如何進(jìn)行數(shù)據(jù)庫(kù)管理 如何應(yīng)對(duì)微服務(wù)的鏈?zhǔn)秸{(diào)用異常 資源隔離 重試 熔斷 對(duì)于快速追蹤與定位問(wèn)題 微服務(wù)的安全 分布式 談?wù)剺I(yè)務(wù)中使用分布式的場(chǎng)景 Session 分布式方案 分布式鎖的場(chǎng)景 分布是鎖的實(shí)現(xiàn)方案 分布式事務(wù)集群與負(fù)載均衡的算法與實(shí)現(xiàn) Dubbo 負(fù)載均衡策略提供下列四種方式: Random LoadBalance 隨機(jī),按權(quán)重設(shè)置隨機(jī)概率。 Dubbo的默認(rèn)負(fù)載均衡策略 在一個(gè)截面上碰撞的概率高,但調(diào)用量越大分布越均勻,而且按概率使用權(quán)重后也比較均勻,有利于動(dòng)態(tài)調(diào)整提供者權(quán)重。RoundRobin LoadBalance 輪循,按公約后的權(quán)重設(shè)置輪循比率。 存在慢的提供者累積請(qǐng)求問(wèn)題,比如:第二臺(tái)機(jī)器很慢,但沒(méi)掛,當(dāng)請(qǐng)求調(diào)到第二臺(tái)時(shí)就卡在那,久而久之,所有請(qǐng)求都卡在調(diào)到第二臺(tái)上。LeastActive LoadBalance 最少活躍調(diào)用數(shù),相同活躍數(shù)的隨機(jī),活躍數(shù)指調(diào)用前后計(jì)數(shù)差。 使慢的提供者收到更少請(qǐng)求,因?yàn)樵铰奶峁┱叩恼{(diào)用前后計(jì)數(shù)差會(huì)越大。ConsistentHash LoadBalance 一致性Hash,相同參數(shù)的請(qǐng)求總是發(fā)到同一提供者。 當(dāng)某一臺(tái)提供者掛時(shí),原本發(fā)往該提供者的請(qǐng)求,基于虛擬節(jié)點(diǎn),平攤到其它提供者,不會(huì)引起劇烈變動(dòng)。說(shuō)說(shuō)分庫(kù)與分表設(shè)計(jì) 分庫(kù)與分表帶來(lái)的分布式困境與應(yīng)對(duì)之策公眾號(hào)推薦
- 全網(wǎng)唯一一個(gè)從0開(kāi)始幫助Java開(kāi)發(fā)者轉(zhuǎn)做大數(shù)據(jù)領(lǐng)域的公眾號(hào)~
- 大數(shù)據(jù)技術(shù)與架構(gòu)或者搜索import_bigdata關(guān)注~
- 海量【java和大數(shù)據(jù)的面試題+視頻資料】整理在公眾號(hào),關(guān)注后可以下載~
總結(jié)
以上是生活随笔為你收集整理的java http get_我是如何进入阿里巴巴的-面向春招应届生Java面试指南(九)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 运营商服务器系统,浪潮服务器助力运营商三
- 下一篇: java美元兑换,(Java实现) 美元