面試題:
- HashMap底層實現原理,紅黑樹,B+樹,B樹的結構原理,volatile關鍵字,CAS(比較與交換)實現原理
- Spring的AOP和IOC是什麼?使用場景有哪些?Spring事務,事務的屬性,傳播行爲,數據庫隔離級別
- Spring和SpringMVC,MyBatis以及SpringBoot的註解分別有哪些?SpringMVC的工作原理,SpringBoot框架的優點,MyBatis框架的優點
- SpringCould組件有哪些,他們的作用是什麼?(說七八個)微服務的CAP是什麼?BASE是什麼?
- 設計模式(說五六個)
- Redis支持的數據類型以及使用場景,持久化,哨兵機制,緩存擊穿,緩存穿透
- 線程是什麼,有幾種實現方式,它們之間的區別是什麼,線程池實現原理,JUC併發包,ThreadLocal與Lock和Synchronize區別
- 分佈式事務(不同系統之間如何保證數據的一致性(A系統寫入數據,B系統因爲某些原因沒有寫入成功,造成數據不一致))
- 安全性問題(數據篡改(拿到別人的URL,篡改數據(金額)發送給系統))
- 索引使用的限制條件,sql優化有哪些,數據同步問題(緩存,數據庫數據同步)
- 初始化Bean對象有幾個步驟,它的生命週期
- JVM內存模型,算法,垃圾回收器,調優,類加載機制(雙親委派),創建一個對象,這個對象在內存中是怎麼分配的?
- 如何設計一個秒殺系統,(高併發高可用分佈式集羣)
- 悲觀鎖,樂觀鎖,讀寫鎖,行鎖,表鎖,自旋鎖,死鎖,分佈式鎖,線程同步鎖,公平鎖,非公平鎖分別是什麼
- 堆溢出,棧溢出的出現場景以及解決方案
- 說出幾種MQ之間的區別,以及爲什麼使用這種MQ,消息重複發送(冪等性),消息發送失敗,消息掉包,長時間收不到消息,發送的消息太大造成接收不成功
- 單點登錄實現原理
- 假如有上億條數據,你如何快速找到其中一條你想要的數據(幾種簡單的算法)
- Dubbo的運行原理,支持什麼協議,與SpringCould相比它爲什麼效率要高一些,Zookeeper底層原理
- 假如你帶一個團隊,讓你設計一個系統,你需要考慮哪些
答案:
HashMap底層實現原理,紅黑樹,B+樹,B樹的結構原理,volatile關鍵字,CAS(比較與交換)實現原理
首先HashMap是Map的一個實現類,而Map存儲形式是鍵值對(key,value)的。可以看成是一個一個的Entry。Entry所存放的位置是由key來決定的。
注意:光理論是不夠的,在此送大家十套2020最新Java架構實戰十套教程+大廠面試題庫,進裙: 783802103 裙文件自行獲取一起交流進步哦!
Map中的key是無序的且不可重複的,所有的key可以看成是一個set集合,如果出現Map中的key如果是自定義類的對象,則必須重寫hashCode和equals方法,因爲如果不重寫,使用的是Object類中的hashCode和equals方法,比較的是內存地址值不是比內容。
Map中的value是無序的可重複的,所有的value可以看成是Collection集合,Map中的value如果是自定義類的對象必須重寫equals方法。
至於要重寫hashCode和equals分別做什麼用,拿hashMap底層原理來說:
當我們向HashMap中存放一個元素(k1,v1),先根據k1的hashCode方法來決定在數組中存放的位置。
如果這個位置沒有其它元素,將(k1,v1)直接放入Node類型的數組中,這個數組初始化容量是16,默認的加載因子是0.75,也就是當元素加到12的時候,底層會進行擴容,擴容爲原來的2倍。如果該位置已經有其它元素(k2,v2),那就調用k1的equals方法和k2進行比較二個元素是否相同,如果結果爲true,說明二個元素是一樣的,用v1替換v2,如果返回值爲false,二個元素不一樣,就用鏈表的形式將(k1,v1)存放。
不過當鏈表中的數據較多時,查詢的效率會下降,所以在JDK1.8版本後做了一個升級,hashmap就是當鏈表中的元素達到8並且元素數量大於64時,會將鏈表替換成紅黑樹纔會樹化時,會將鏈表替換成紅黑樹,來提高查找效率。因爲對於搜索,插入,刪除操作多的情況下,使用紅黑樹的效率要高一些。
原因是因爲紅黑樹是一種特殊的二叉查找樹,二叉查找樹所有節點的左子樹都小於該節點,所有節點的右子樹都大於該節點,就可以通過大小比較關係來進行快速的檢索。
在紅黑樹上插入或者刪除一個節點之後,紅黑樹就發生了變化,可能不滿足紅黑樹的5條性質,也就不再是一顆紅黑樹了,而是一顆普通的樹,可以通過左旋和右旋,使這顆樹重新成爲紅黑樹。紅黑樹的5條性質(根節點是黑色,每個節點是黑色或者是紅色,每個葉子節點是黑色,如果一個節點是紅色它的子節點必須是黑色的,從一個節點到該節點的子孫外部節點的所有路徑上包含相同數目的黑點)
而且像這種二叉樹結構比較常見的使用場景是Mysql二種引擎的索引,Myisam使用的是B樹,InnoDB使用的是B+樹。
首先B樹它的每個節點都是Key.value的二元組,它的key都是從左到右遞增的排序,value中存儲數據。這種模式在讀取數據方面的性能很高,因爲有單獨的索引文件,Myisam 的存儲文件有三個.frm是表的結構文件,.MYD是數據文件,.MYI是索引文件。不過Myisam 也有些缺點它只支持表級鎖,不支持行級鎖也不支持事務,外鍵等,所以一般用於大數據存儲。
然後是InnoDB,它的存儲文件相比Myisam少一個索引文件,它是以 ID 爲索引的數據存儲,數據現在都被存在了葉子結點,索引在非葉結點上。而這些節點分散在索引頁上。在InnoDB裏,每個頁默認16KB,假設索引的是8B的long型數據,每個key後有個頁號4B,還有6B的其他數據,那麼每個頁的扇出係數爲16KB/(8B+4B+6B)≈1000,即每個頁可以索引1000個key。在高度h=3時,s=1000^3=10億!!也就是說,InnoDB通過三次索引頁的I/O,即可索引10億的key,而非葉節點這一行存儲的索引,數量就多了,I/O的次數就少了。而Myisam在每個節點都存儲數據和索引,這樣就減少了每頁存儲的索引數量。而且InnoDB它還支持行級,表級鎖,也支持事務,外鍵.
另外對於HashMap實際使用過程中還是會出現一些線程安全問題:
HashMap是線程不安全的,在多線程環境下,使用Hashmap進行put操作會引起死循環,導致CPU利用率接近100%,而且會拋出併發修改異常,導致原因是併發爭取線程資源,修改數據導致的,一個線程正在寫,一個線程過來爭搶,導致線程寫的過程被其他線程打斷,導致數據不一致。
HashTable是線程安全的,只不過實現代價卻太大了,簡單粗暴,get/put所有相關操作都是synchronized的,這相當於給整個哈希表加了一把大鎖。多線程訪問時候,只要有一個線程訪問或操作該對象,那其他線程只能阻塞,相當於將所有的操作串行化,在競爭激烈的併發場景中性能就會非常差。
爲了應對hashmap在併發環境下不安全問題可以使用,ConcurrentHashMap大量的利用了volatile,CAS等技術來減少鎖競爭對於性能的影響。
在JDK1.7版本中ConcurrentHashMap避免了對全局加鎖,改成了局部加鎖(分段鎖),分段鎖技術,將數據分成一段一段的存儲,然後給每一段數據配一把鎖,當一個線程佔用鎖訪問其中一個段數據的時候,其他段的數據也能被其他線程訪問,能夠實現真正的併發訪問。不過這種結構的帶來的副作用是Hash的過程要比普通的HashMap要長。
所以在JDK1.8版本中CurrentHashMap內部中的value使用volatile修飾,保證併發的可見性以及禁止指令重排,只不過volatile不保證原子性,使用爲了確保原子性,採用CAS(比較交換)這種樂觀鎖來解決。
CAS 操作包含三個操作數 —— 內存位置(V)、預期原值(A)和新值(B)。
如果內存地址裏面的值和A的值是一樣的,那麼就將內存裏面的值更新成B。CAS是通過無限循環來獲取數據的,若果在第一輪循環中,a線程獲取地址裏面的值被b線程修改了,那麼a線程需要自旋,到下次循環纔有可能機會執行。
volatile有三個特性:可見性,不保證原子性,禁止指令重排。
可見性:線程1從主內存中拿數據1到自己的線程工作空間進行操作(假設是加1)這個時候數據1已經改爲數據2了,將數據2寫回主內存時通知其他線程(線程2,線程3),主內存中的數據1已改爲數據2了,讓其他線程重新拿新的數據(數據2)。
不保證原子性:線程1從主內存中拿了一個值爲1的數據到自己的工作空間裏面進行加1的操作,值變爲2,寫回主內存,然後還沒有來得及通知其他線程,線程1就被線程2搶佔了,CPU分配,線程1被掛起,線程2還是拿着原來主內存中的數據值爲1進行加1,值變成2,寫回主內存,將主內存值爲2的替換成2,這時線程1的通知到了,線程2重新去主內存拿值爲2的數據。
禁止指令重排:首先指令重排是程序執行的時候不總是從上往下執行的,就像高考答題,可以先做容易的題目再做難的,這時做題的順序就不是從上往下了。禁止指令重排就杜絕了這種情況。
(一般面試官開始問你會從java基礎問起,一問大多數會問到集合這一塊,而集合問的較多的是HashMap,這個時候你就可以往這些方向帶着面試官問你,而且擴展的深度也夠,所以上面的乾貨夠你說個十來分鐘吧,第一個問題拿下後,面試官心裏至少簡單你的基礎夠紮實,第一印象分就留下了)
Spring的AOP和IOC是什麼?使用場景有哪些?Spring事務與數據庫事務,傳播行爲,數據庫隔離級別
AOP:面向切面編程。
即在一個功能模塊中新增其他功能,比方說你要下樓取個快遞,你同事對你說幫我也取一下唄,你就順道取了。在工作中如果系統中有些包和類中沒有使用AOP,例如日誌,事務和異常處理,那麼就必須在每個類和方法中去實現它們。 代碼糾纏每個類和方法中都包含日誌,事務以及異常處理甚至是業務邏輯。在一個這樣的方法中,很難分清代碼中實際做的是什麼處理。AOP 所做的就是將所有散落各處的事務代碼集中到一個事務切面中。
場景
比方說我現在要弄一個日誌,記錄某些個接口調用的方法時間。使用Aop我可以在這個接口前插入一段代碼去記錄開始時間,在這個接口後面去插入一段代碼記錄結束時間。
又或者你去訪問數據庫,而你不想管事務(太煩),所以,Spring在你訪問數據庫之前,自動幫你開啓事務,當你訪問數據庫結束之後,自動幫你提交/回滾事務!
異常處理你可以開啓環繞通知,一旦運行接口報錯,環繞通知捕獲異常跳轉異常處理頁面。
動態代理
Spring AOP使用的動態代理,所謂的動態代理就是說AOP框架不會去修改字節碼,而是在內存中臨時爲方法生成一個AOP對象,這個AOP對象包含了目標對象的全部方法,並且在特定的切點做了增強處理,並回調原對象的方法。它的動態代理主要有兩種方式,JDK動態代理和CGLIB動態代理。JDK動態代理通過反射來接收被代理的類,並且要求被代理的類必須實現一個接口。JDK動態代理的核心是InvocationHandler接口和Proxy類。如果目標類沒有實現接口,那麼Spring AOP會選擇使用CGLIB來動態代理目標類。CGLIB是一個代碼生成的類庫,可以在運行時動態的生成某個類的子類,注意,CGLIB是通過繼承的方式做的動態代理,因此如果某個類被標記爲final,那麼它是無法使用CGLIB做動態代理的。
IOC:依賴注入或者叫做控制反轉。
正常情況下我們使用一個對象時都是需要new Object()的。而ioc是把需要使用的對象提前創建好,放到spring的容器裏面。
所有需要使用的類都會在spring容器中登記,告訴spring你是個什麼東西,你需要什麼東西,然後spring會在系統運行到適當的時候,把你要的東西主動給你,同時也把你交給其他需要你的東西。所有的類的創建、銷燬都由 spring來控制,也就是說控制對象生存週期的不再是引用它的對象,而是spring。DI(依賴注入)其實就是IOC的另外一種說法,其實它們是同一個概念的不同角度描述。
場景:
正常情況下我們使用一個對象時都是需要new Object() 的。而ioc是把需要使用的對象提前創建好,放到spring的容器裏面。需要使用的時候直接使用就行,而且可以設置單例或多例,非常靈活。
我們在service層想調用另外一個service的方法,不需要去new了,直接把它交給spring管理,然後用註解的方式引入就能使用。
IOC三種注入方式
(1)XML:Bean實現類來自第三方類庫,例如DataSource等。需要命名空間等配置,例如:context,aop,mvc。
(2)註解:在開發的類使用@Controller,@Service等註解
(3)Java配置類:通過代碼控制對象創建邏輯的場景。例如:自定義修改依賴類庫。
什麼是事務?
事務是訪問並可能更新數據庫中各種數據項的一個程序執行單元。
Spring事務與數據庫事務關係?
Spring的事務是對數據庫的事務的封裝,最後本質的實現還是在數據庫,假如數據庫不支持事務的話,Spring的事務是沒有作用的。所以說Spring事務的底層依賴MySQL的事務,Spring是在代碼層面利用AOP實現,執行事務的時候使用TransactionInceptor進行攔截,然後處理。本質是對方法前後進行攔截,然後在目標方法開始之前創建或者加入一個事務,執行完目標方法之後根據執行的情況提交或者回滾。
屬性(特性)
A(原子性):要麼全部完成,要麼完全不起作用
C(一致性):一旦事務完成(不管成功還是失敗),業務處於一致的狀態,而不會是部分完成,部分失敗。
I(隔離性):多事務會同時處理相同的數據,因此每個事務都應該與其他事務隔離開來,防止數據損壞。
D(持久性):一旦事務完成,無論發生什麼系統錯誤,它的結果都不應該受到影響,事務的結果被寫到持久化存儲器中。
什麼叫事務傳播行爲?
傳播,至少有兩個東西,纔可以發生傳播。單體不存在傳播這個行爲。事務傳播行爲就是當一個事務方法被另一個事務方法調用時,這個事務方法應該如何進行。
Spring支持7中事務傳播行爲
propagation_required(需要傳播):當前沒有事務則新建事務,有則加入當前事務
propagation_supports(支持傳播):支持當前事務,如果當前沒有事務則以非事務方式執行
propagation_mandatory(強制傳播):使用當前事務,如果沒有則拋出異常
propagation_nested(嵌套傳播):如果當前存在事務,則在嵌套事務內執行,如果當前沒有事務,則執行需要傳播行爲。
propagation_never(絕不傳播):以非事務的方式執行,如果當前有事務則拋出異常
propagation_requires_new(傳播需要新的):新建事務,如果當前有事務則把當前事務掛起
propagation_not_supported(不支持傳播):以非事務的方式執行,如果當前有事務則把當前事務掛起
數據庫事務的隔離級別
數據庫事務的隔離級別有4個,由低到高依次爲Read uncommitted、Read committed、Repeatable read、Serializable,這四個級別可以逐個解決髒讀、不可重複讀、幻讀這幾類問題。
√: 可能出現 ×: 不會出現
說明 | 髒讀 | 不可重複讀 | 幻讀 |
Read uncommitted | √ | √ | √ |
Read committed | × | √ | √ |
Repeatable read | × | × | √ |
Serializable | × | × | × |
注意:我們討論隔離級別的場景,主要是在多個事務併發的情況下,因此,接下來的講解都圍繞事務併發。
Read uncommitted 讀未提交
公司發工資了,領導把20000元打到廖志偉的賬號上,但是該事務並未提交,而廖志偉正好去查看賬戶,發現工資已經到賬,是20000元整,非常高興。可是不幸的是,領導發現發給廖志偉的工資金額不對,是16000元,於是迅速回滾了事務,修改金額後,將事務提交,最後廖志偉實際的工資只有16000元,廖志偉空歡喜一場。
出現上述情況,即我們所說的髒讀,兩個併發的事務,“事務A:領導給廖志偉發工資”、“事務B:廖志偉查詢工資賬戶”,事務B讀取了事務A尚未提交的數據。當隔離級別設置爲Read uncommitted時,就可能出現髒讀,如何避免髒讀,請看下一個隔離級別。
Read committed 讀提交
廖志偉拿着工資卡去消費,系統讀取到卡里確實有2000元,而此時她的老婆也正好在網上轉賬,把廖志偉工資卡的2000元轉到另一賬戶,並在廖志偉之前提交了事務,當廖志偉扣款時,系統檢查到廖志偉的工資卡已經沒有錢,扣款失敗,廖志偉十分納悶,明明卡里有錢,爲何…
出現上述情況,即我們所說的不可重複讀,兩個併發的事務,“事務A:廖志偉消費”、“事務B:廖志偉的老婆網上轉賬”,事務A事先讀取了數據,事務B緊接了更新了數據,並提交了事務,而事務A再次讀取該數據時,數據已經發生了改變。當隔離級別設置爲Read committed時,避免了髒讀,但是可能會造成不可重複讀。大多數數據庫的默認級別就是Read committed,比如Sql Server , Oracle。如何解決不可重複讀這一問題,請看下一個隔離級別。
Repeatable read 重複讀
當廖志偉拿着工資卡去消費時,一旦系統開始讀取工資卡信息(即事務開始),廖志偉的老婆就不可能對該記錄進行修改,也就是廖志偉的老婆不能在此時轉賬。這就避免了不可重複讀。廖志偉的老婆工作在銀行部門,她時常通過銀行內部系統查看廖志偉的信用卡消費記錄。有一天,她正在查詢到廖志偉當月信用卡的總消費金額(select sum(amount) from transaction where month = 本月)爲80元,而廖志偉此時正好在外面胡吃海喝後在收銀臺買單,消費1000元,即新增了一條1000元的消費記錄(insert transaction … ),並提交了事務,隨後廖志偉的老婆將廖志偉當月信用卡消費的明細打印到A4紙上,卻發現消費總額爲1080元,廖志偉的老婆很詫異,以爲出現了幻覺,幻讀就這樣產生了。當隔離級別設置爲Repeatable read時,可以避免不可重複讀,但會出現幻讀。注:MySQL的默認隔離級別就是Repeatable read。
Serializable 序列化
Serializable是最高的事務隔離級別,同時代價也花費最高,性能很低,一般很少使用,在該級別下,事務順序執行,不僅可以避免髒讀、不可重複讀,還避免了幻像讀。
Spring和SpringMVC,MyBatis以及SpringBoot的註解分別有哪些?SpringMVC的工作原理,SpringBoot框架的優點,MyBatis框架的優點
Spring註解:
聲明bean的註解 | |
@Component | 組件,沒有明確的角色 |
@Service | 在業務邏輯層使用(service層) |
@Repository | 在數據訪問層使用(dao層) |
@Controller | 在展現層使用,控制器的聲明(C) |
注入bean的註解 | |
@Autowired | 由Spring提供 |
@Resource | 由JSR-250提供 |
java配置類相關注解 | |
@Bean | 註解在方法上,聲明當前方法的返回值爲一個bean,替代xml中的方式(方法上) |
@Configuration | 聲明當前類爲配置類,其中內部組合了@Component註解,表明這個類是一個bean(類上) |
@ComponentScan | 用於對Component進行掃描,相當於xml中的(類上) |
切面(AOP)相關注解 | |
@Aspect | 聲明一個切面(類上) 使用@After、@Before、@Around定義建言(advice),可直接將攔截規則(切點)作爲參數。 |
@After | 在方法執行之後執行(方法上) @Before 在方法執行之前執行(方法上) @Around 在方法執行之前與之後執行(方法上) |
@PointCut | 聲明切點 在java配置類中使用@EnableAspectJAutoProxy註解開啓Spring對AspectJ代理的支持(類上) |
@Value註解 | |
@Value 爲屬性注入值 | 注入操作系統屬性@Value("#{systemProperties['os.name']}")String osName; 注入表達式結果@Value("#{ T(java.lang.Math).random() * 100 }") String randomNumber; 注入其它bean屬性@Value("#{domeClass.name}")String name; 注入文件資源@Value("classpath:com/hgs/hello/test.txt")String Resource file; 注入網站資源@Value("http://www.cznovel.com")Resource url; 注入配置文件Value("${book.name}")String bookName; |
異步相關 | |
@EnableAsync | 配置類中,通過此註解開啓對異步任務的支持,敘事性AsyncConfigurer接口(類上) |
@Async | 在實際執行的bean方法使用該註解來申明其是一個異步任務(方法上或類上所有的方法都將異步,需要@EnableAsync開啓異步任務) |
定時任務相關 | |
@EnableScheduling | 在配置類上使用,開啓計劃任務的支持(類上) |
@Scheduled | 來申明這是一個任務,包括cron,fixDelay,fixRate等類型(方法上,需先開啓計劃任務的支持) |
SpringMVC註解
@EnableWebMvc | 在配置類中開啓Web MVC的配置支持,如一些ViewResolver或者MessageConverter等,若無此句,重寫WebMvcConfigurerAdapter方法(用於對SpringMVC的配置)。 |
@Controller | 聲明該類爲SpringMVC中的Controller |
@RequestMapping | 用於映射Web請求,包括訪問路徑和參數(類或方法上) |
@ResponseBody | 支持將返回值放在response內,而不是一個頁面,通常用戶返回json數據(返回值旁或方法上) |
@RequestBody | 允許request的參數在request體中,而不是在直接連接在地址後面。(放在參數前) |
@PathVariable | 用於接收路徑參數,比如@RequestMapping(“/hello/{name}”)申明的路徑,將註解放在參數中前,即可獲取該值,通常作爲Restful的接口實現方法。 |
@RestController | 該註解爲一個組合註解,相當於@Controller和@ResponseBody的組合,註解在類上,意味着,該Controller的所有方法都默認加上了@ResponseBody。 |
@ControllerAdvice | 通過該註解,我們可以將對於控制器的全局配置放置在同一個位置,註解了@Controller的類的方法可使用@ExceptionHandler、@InitBinder、@ModelAttribute註解到方法上, 這對所有註解了 @RequestMapping的控制器內的方法有效。 |
@ExceptionHandler | 用於全局處理控制器裏的異常 |
@InitBinder | 用來設置WebDataBinder,WebDataBinder用來自動綁定前臺請求參數到Model中。 |
@ModelAttribute | 本來的作用是綁定鍵值對到Model裏,在@ControllerAdvice中是讓全局的@RequestMapping都能獲得在此處設置的鍵值對。 |
Mybatis註解:(偷個懶,不使用表格了,嘻嘻)
-
增刪改查:@Insert、@Update、@Delete、@Select、@MapKey、@Options、@SelelctKey、@Param、@InsertProvider、@UpdateProvider、@DeleteProvider、@SelectProvider
-
結果集映射:@Results、@Result、@ResultMap、@ResultType、@ConstructorArgs、@Arg、@One、@Many、@TypeDiscriminator、@Case
-
緩存:@CacheNamespace、@Property、@CacheNamespaceRef、@Flush
SpringBoot註解:
- @SpringBootApplication:申明讓spring boot自動給程序進行必要的配置,這個配置等同於:
- @Configuration ,@EnableAutoConfiguration 和 @ComponentScan 三個配置。
- @ResponseBody:表示該方法的返回結果直接寫入HTTP response body中,一般在異步獲取數據時使用,用於構建RESTful的api。在使用@RequestMapping後,返回值通常解析爲跳轉路徑,加上@esponsebody後返回結果不會被解析爲跳轉路徑,而是直接寫入HTTP response body中。比如異步獲取json數據,加上@Responsebody後,會直接返回json數據。該註解一般會配合@RequestMapping一起使用。
- @Controller:用於定義控制器類,在spring項目中由控制器負責將用戶發來的URL請求轉發到對應的服務接口(service層),一般這個註解在類中,通常方法需要配合註解@RequestMapping。
- @RestController:用於標註控制層組件(如struts中的action),@ResponseBody和@Controller的合集。
- @RequestMapping:提供路由信息,負責URL到Controller中的具體函數的映射。
- @EnableAutoConfiguration:SpringBoot自動配置(auto-configuration):嘗試根據你添加的jar依賴自動配置你的Spring應用。例如,如果你的classpath下存在HSQLDB,並且你沒有手動配置任何數據庫連接beans,那麼我們將自動配置一個內存型(in-memory)數據庫”。你可以將@EnableAutoConfiguration或者@SpringBootApplication註解添加到一個@Configuration類上來選擇自動配置。如果發現應用了你不想要的特定自動配置類,你可以使用@EnableAutoConfiguration註解的排除屬性來禁用它們。
- @ComponentScan:表示將該類自動發現掃描組件。個人理解相當於,如果掃描到有@Component、@Controller、@Service等這些註解的類,並註冊爲Bean,可以自動收集所有的Spring組件,包括@Configuration類。我們經常使用@ComponentScan註解搜索beans,並結合@Autowired註解導入。可以自動收集所有的Spring組件,包括@Configuration類。我們經常使用@ComponentScan註解搜索beans,並結合@Autowired註解導入。如果沒有配置的話,Spring Boot會掃描啓動類所在包下以及子包下的使用了@Service,@Repository等註解的類。
- @Configuration:相當於傳統的xml配置文件,如果有些第三方庫需要用到xml文件,建議仍然通過@Configuration類作爲項目的配置主類——可以使用@ImportResource註解加載xml配置文件。
- @Import:用來導入其他配置類。
- @ImportResource:用來加載xml配置文件。
- @Repository:使用@Repository註解可以確保DAO或者repositories提供異常轉譯,這個註解修飾的DAO或者repositories類會被ComponetScan發現並配置,同時也不需要爲它們提供XML配置項。
- @Bean:用@Bean標註方法等價於XML中配置的bean
- @AutoWired:自動導入依賴的bean。byType方式。把配置好的Bean拿來用,完成屬性、方法的組裝,它可以對類成員變量、方法及構造函數進行標註,完成自動裝配的工作。當加上(required=false)時,就算找不到bean也不報錯。
- @Qualifier:當有多個同一類型的Bean時,可以用@Qualifier(“name”)來指定。與@Autowired配合使用。@Qualifier限定描述符除了能根據名字進行注入,但能進行更細粒度的控制如何選擇候選者,具體使用方式如下:
- @Resource(name=”name”,type=”type”):沒有括號內內容的話,默認byName。與@Autowired幹類似的事。
SpringMVC的工作原理:
SpringBoot框架的優點:
- --創建獨立的 Spring 應用程序 ;
- --嵌入的 Tomcat 、 Jetty 或者 Undertow,無須部署 WAR 文件:
- --允許通過 Maven 來根據需要獲取 starter;
- --儘可能地自動配置 Spring;
- --提供生產就緒型功能,如指標、健康檢查和外部配置;
- --絕對沒有代碼生成,對 XML 沒有要求配置 。
MyBatis框架的優點:
- JDBC相比,減少了50%以上的代碼量,消除了JDBC大量冗餘的代碼,不需要手動開關連接
- 很好的與各種數據庫兼容(因爲MyBatis使用JDBC來連接數據庫,所以只要JDBC支持的數據庫MyBatis都支持,而JDBC提供了可擴展性,所以只要這個數據庫有針對Java的jar包就可以就可以與MyBatis兼容),開發人員不需要考慮數據庫的差異性。
- 提供了很多第三方插件(分頁插件 / 逆向工程)
- SQL寫在XML裏,從程序代碼中徹底分離,解除sql與程序代碼的耦合,便於統一管理和優化,並可重用。
- 提供映射標籤,支持對象與數據庫的ORM字段關係映射。
SpringCould組件有哪些,他們的作用是什麼(說七八個)?微服務的CAP是什麼?BASE是什麼?
先講五大核心組件,(偷個懶,嘻嘻)這裏我引用一位大佬講解的,原文地址是:https://juejin.im/post/5be13b83f265da6116393fc7
一、業務場景介紹
先來給大家說一個業務場景,假設咱們現在開發一個電商網站,要實現支付訂單的功能,流程如下:
- 創建一個訂單後,如果用戶立刻支付了這個訂單,我們需要將訂單狀態更新爲“已支付”
- 扣減相應的商品庫存
- 通知倉儲中心,進行發貨
- 給用戶的這次購物增加相應的積分
針對上述流程,我們需要有訂單服務、庫存服務、倉儲服務、積分服務。整個流程的大體思路如下:
- 用戶針對一個訂單完成支付之後,就會去找訂單服務,更新訂單狀態
- 訂單服務調用庫存服務,完成相應功能
- 訂單服務調用倉儲服務,完成相應功能
- 訂單服務調用積分服務,完成相應功能
至此,整個支付訂單的業務流程結束
下圖這張圖,清晰表明了各服務間的調用過程:
好!有了業務場景之後,咱們就一起來看看Spring Cloud微服務架構中,這幾個組件如何相互協作,各自發揮的作用以及其背後的原理。
二、Spring Cloud核心組件:Eureka
咱們來考慮第一個問題:訂單服務想要調用庫存服務、倉儲服務,或者積分服務,怎麼調用?
- 訂單服務壓根兒就不知道人家庫存服務在哪臺機器上啊!他就算想要發起一個請求,都不知道發送給誰,有心無力!
- 這時候,就輪到Spring Cloud Eureka出場了。Eureka是微服務架構中的註冊中心,專門負責服務的註冊與發現。
咱們來看看下面的這張圖,結合圖來仔細剖析一下整個流程:
如上圖所示,庫存服務、倉儲服務、積分服務中都有一個Eureka Client組件,這個組件專門負責將這個服務的信息註冊到Eureka Server中。說白了,就是告訴Eureka Server,自己在哪臺機器上,監聽着哪個端口。而Eureka Server是一個註冊中心,裏面有一個註冊表,保存了各服務所在的機器和端口號
訂單服務裏也有一個Eureka Client組件,這個Eureka Client組件會找Eureka Server問一下:庫存服務在哪臺機器啊?監聽着哪個端口啊?倉儲服務呢?積分服務呢?然後就可以把這些相關信息從Eureka Server的註冊表中拉取到自己本地緩存起來。
這時如果訂單服務想要調用庫存服務,不就可以找自己本地的Eureka Client問一下庫存服務在哪臺機器?監聽哪個端口嗎?收到響應後,緊接着就可以發送一個請求過去,調用庫存服務扣減庫存的那個接口!同理,如果訂單服務要調用倉儲服務、積分服務,也是如法炮製。
總結一下:
- Eureka Client:負責將這個服務的信息註冊到Eureka Server中
- Eureka Server:註冊中心,裏面有一個註冊表,保存了各個服務所在的機器和端口號
三、Spring Cloud核心組件:Feign
現在訂單服務確實知道庫存服務、積分服務、倉庫服務在哪裏了,同時也監聽着哪些端口號了。但是新問題又來了:難道訂單服務要自己寫一大堆代碼,跟其他服務建立網絡連接,然後構造一個複雜的請求,接着發送請求過去,最後對返回的響應結果再寫一大堆代碼來處理嗎?
這是上述流程翻譯的代碼片段,咱們一起來看看,體會一下這種絕望而無助的感受!!!
友情提示,前方高能:
看完上面那一大段代碼,有沒有感到後背發涼、一身冷汗?實際上你進行服務間調用時,如果每次都手寫代碼,代碼量比上面那段要多至少幾倍,所以這個事壓根兒就不是地球人能幹的。
既然如此,那怎麼辦呢?別急,Feign早已爲我們提供好了優雅的解決方案。來看看如果用Feign的話,你的訂單服務調用庫存服務的代碼會變成啥樣?
看完上面的代碼什麼感覺?是不是感覺整個世界都乾淨了,又找到了活下去的勇氣!沒有底層的建立連接、構造請求、解析響應的代碼,直接就是用註解定義一個 FeignClient接口,然後調用那個接口就可以了。人家Feign Client會在底層根據你的註解,跟你指定的服務建立連接、構造請求、發起靕求、獲取響應、解析響應,等等。這一系列髒活累活,人家Feign全給你幹了。
那麼問題來了,Feign是如何做到這麼神奇的呢?很簡單,Feign的一個關鍵機制就是使用了動態代理。咱們一起來看看下面的圖,結合圖來分析:
- 首先,如果你對某個接口定義了@FeignClient註解,Feign就會針對這個接口創建一個動態代理
- 接着你要是調用那個接口,本質就是會調用 Feign創建的動態代理,這是核心中的核心
- Feign的動態代理會根據你在接口上的@RequestMapping等註解,來動態構造出你要請求的服務的地址
- 最後針對這個地址,發起請求、解析響應
四、Spring Cloud核心組件:Ribbon
說完了Feign,還沒完。現在新的問題又來了,如果人家庫存服務部署在了5臺機器上,如下所示:
- 192.168.169:9000
- 192.168.170:9000
- 192.168.171:9000
- 192.168.172:9000
- 192.168.173:9000
這下麻煩了!人家Feign怎麼知道該請求哪臺機器呢?
- 這時Spring Cloud Ribbon就派上用場了。Ribbon就是專門解決這個問題的。它的作用是負載均衡,會幫你在每次請求時選擇一臺機器,均勻的把請求分發到各個機器上
- Ribbon的負載均衡默認使用的最經典的Round Robin輪詢算法。這是啥?簡單來說,就是如果訂單服務對庫存服務發起10次請求,那就先讓你請求第1臺機器、然後是第2臺機器、第3臺機器、第4臺機器、第5臺機器,接着再來—個循環,第1臺機器、第2臺機器。。。以此類推。
此外,Ribbon是和Feign以及Eureka緊密協作,完成工作的,具體如下:
- 首先Ribbon會從 Eureka Client裏獲取到對應的服務註冊表,也就知道了所有的服務都部署在了哪些機器上,在監聽哪些端口號。
- 然後Ribbon就可以使用默認的Round Robin算法,從中選擇一臺機器
- Feign就會針對這臺機器,構造併發起請求。
對上述整個過程,再來一張圖,幫助大家更深刻的理解:
五、Spring Cloud核心組件:Hystrix
在微服務架構裏,一個系統會有很多的服務。以本文的業務場景爲例:訂單服務在一個業務流程裏需要調用三個服務。現在假設訂單服務自己最多隻有100個線程可以處理請求,然後呢,積分服務不幸的掛了,每次訂單服務調用積分服務的時候,都會卡住幾秒鐘,然後拋出—個超時異常。
咱們一起來分析一下,這樣會導致什麼問題?
- 如果系統處於高併發的場景下,大量請求涌過來的時候,訂單服務的100個線程都會卡在請求積分服務這塊。導致訂單服務沒有一個線程可以處理請求
- 然後就會導致別人請求訂單服務的時候,發現訂單服務也掛了,不響應任何請求了
上面這個,就是微服務架構中恐怖的服務雪崩問題,
如下圖所示:
如上圖,這麼多服務互相調用,要是不做任何保護的話,某一個服務掛了,就會引起連鎖反應,導致別的服務也掛。比如積分服務掛了,會導致訂單服務的線程全部卡在請求積分服務這裏,沒有一個線程可以工作,瞬間導致訂單服務也掛了,別人請求訂單服務全部會卡住,無法響應。
但是我們思考一下,就算積分服務掛了,訂單服務也可以不用掛啊!爲什麼?
- 我們結合業務來看:支付訂單的時候,只要把庫存扣減了,然後通知倉庫發貨就OK了
- 如果積分服務掛了,大不了等他恢復之後,慢慢人肉手工恢復數據!爲啥一定要因爲一個積分服務掛了,就直接導致訂單服務也掛了呢?不可以接受!
現在問題分析完了,如何解決?
這時就輪到Hystrix閃亮登場了。Hystrix是隔離、熔斷以及降級的一個框架。啥意思呢?說白了,Hystrix會搞很多個小小的線程池,比如訂單服務請求庫存服務是一個線程池,請求倉儲服務是一個線程池,請求積分服務是一個線程池。每個線程池裏的線程就僅僅用於請求那個服務。
打個比方:現在很不幸,積分服務掛了,會咋樣?
當然會導致訂單服務裏那個用來調用積分服務的線程都卡死不能工作了啊!但由於訂單服務調用庫存服務、倉儲服務的這兩個線程池都是正常工作的,所以這兩個服務不會受到任何影響。
這個時候如果別人請求訂單服務,訂單服務還是可以正常調用庫存服務扣減庫存,調用倉儲服務通知發貨。只不過調用積分服務的時候,每次都會報錯。但是如果積分服務都掛了,每次調用都要去卡住幾秒鐘幹啥呢?有意義嗎?當然沒有!所以我們直接對積分服務熔斷不就得了,比如在5分鐘內請求積分服務直接就返回了,不要去走網絡請求卡住幾秒鐘,這個過程,就是所謂的熔斷!
那人家又說,兄弟,積分服務掛了你就熔斷,好歹你乾點兒什麼啊!別啥都不幹就直接返回啊?沒問題,咱們就來個降級:每次調用積分服務,你就在數據庫裏記錄一條消息,說給某某用戶增加了多少積分,因爲積分服務掛了,導致沒增加成功!這樣等積分服務恢復了,你可以根據這些記錄手工加一下積分。這個過程,就是所謂的降級。
爲幫助大家更直觀的理解,接下來用一張圖,梳理一下Hystrix隔離、熔斷和降級的全流程:
六、Spring Cloud核心組件:Zuul
說完了Hystrix,接着給大家說說最後一個組件:Zuul,也就是微服務網關。這個組件是負責網絡路由的。不懂網絡路由?行,那我給你說說,如果沒有Zuul的日常工作會怎樣?
假設你後臺部署了幾百個服務,現在有個前端兄弟,人家請求是直接從瀏覽器那兒發過來的。打個比方:人家要請求一下庫存服務,你難道還讓人家記着這服務的名字叫做inventory-service?部署在5臺機器上?就算人家肯記住這一個,你後臺可有幾百個服務的名稱和地址呢?難不成人家請求一個,就得記住一個?你要這樣玩兒,那真是友誼的小船,說翻就翻!
上面這種情況,壓根兒是不現實的。所以一般微服務架構中都必然會設計一個網關在裏面,像android、ios、pc前端、微信小程序、H5等等,不用去關心後端有幾百個服務,就知道有一個網關,所有請求都往網關走,網關會根據請求中的一些特徵,將請求轉發給後端的各個服務。
而且有一個網關之後,還有很多好處,比如可以做統一的降級、限流、認證授權、安全,等等。
七、總結:
最後再來總結一下,上述幾個Spring Cloud核心組件,在微服務架構中,分別扮演的角色:
- Eureka:各個服務啓動時,Eureka Client都會將服務註冊到Eureka Server,並且Eureka Client還可以反過來從Eureka Server拉取註冊表,從而知道其他服務在哪裏
- Ribbon:服務間發起請求的時候,基於Ribbon做負載均衡,從一個服務的多臺機器中選擇一臺
- Feign:基於Feign的動態代理機制,根據註解和選擇的機器,拼接請求URL地址,發起請求
- Hystrix:發起請求是通過Hystrix的線程池來走的,不同的服務走不同的線程池,實現了不同服務調用的隔離,避免了服務雪崩的問題
- Zuul:如果前端、移動端要調用後端系統,統一從Zuul網關進入,由Zuul網關轉發請求給對應的服務
以上就是我們通過一個電商業務場景,闡述了Spring Cloud微服務架構幾個核心組件的底層原理。
文字總結還不夠直觀?沒問題!我們將Spring Cloud的5個核心組件通過一張圖串聯起來,再來直觀的感受一下其底層的架構原理:
五大核心組件講完了,面試官心中已經知道你對SpringCould的有一定的瞭解了,但這還不夠,你如果講到這個層面,部分面試官還會繼續問,因爲你講解的這些其他面試者也講過,可能也就你講的比較細一些,但本質還是和他們差不了太多,有些公司可能集中招人,負責面試的可能就一個,你想想他這一天可以面試多少個人,這個時候你就需要繼續拓展其他組件,來突出你的不同了。
Spring Cloud Sleuth(服務鏈路追蹤),Spring Cloud Bus(消息總線),Spring Cloud Config(分佈式配置中心)之類的,這裏我就不繼續寫了,給上一個SpringCould專欄(一位大佬寫的,挺不錯的)你去看看吧,最好能實現動手敲上一套,後面你會發現自己對SpringCould的理解遠超其他人。專欄地址是:https://blog.csdn.net/forezp/article/details/70148833
CAP 定論
一個分佈式系統最多隻能同時滿足一致性(Consistency)、可用性(Availability)和分區容錯性(Partition tolerance)這三項中的兩項。
C 一致性即更新操作成功並返回客戶端完成後,所有節點在同一時間的數據完全一致。
A 可用性服務一直可用,而且是正常響應時間。
P 分區容錯性即分佈式系統在遇到某節點或網絡分區故障的時候,仍然能夠對外提供滿足一致性和可用性的服務。
- 對於多數大型互聯網應用的場景,一般保證滿足 P 和 A,捨棄 C(一致性無法保證,退而求其次保證最終一致性)。雖然某些地方會影響客戶體驗,但沒達到造成用戶流失的嚴重程度。如原來同步架構的時候如果沒有庫存,就馬上告訴客戶庫存不足無法下單。但在微服務框架下訂單和庫存可能是兩個微服務對應兩個數據庫,用戶下單時訂單服務是立即生成的,很可能過了一會系統通知你訂單被取消掉(最終一致性)。就像搶購“小米手機”一樣,幾十萬人在排隊,排了很久告訴你沒貨了,明天再來吧。
- 對於涉及到錢財這樣不能有一絲讓步的場景,C 必須保證。網絡發生故障寧可停止服務,這是保證 CA,捨棄 P。
- 還有一種是保證 CP,捨棄 A。例如網絡故障事只讀不寫。
BASE
BASE 是 Basically Available(基本可用)、Soft state(軟狀態)和 Eventually consistent (最終一致性)三個短語的縮寫。是對 CAP 中 AP 的一個擴展
- 基本可用:分佈式系統在出現故障時,允許損失部分可用功能,保證核心功能可用。
- 軟狀態:允許系統中存在中間狀態,這個狀態不影響系統可用性,這裏指的是 CAP 中的不一致。
- 最終一致:最終一致是指經過一段時間後,所有節點數據都將會達到一致。
BASE 解決了 CAP 中理論沒有網絡延遲,在 BASE 中用軟狀態和最終一致,保證了延遲後的一致性。BASE 和 ACID 是相反的,它完全不同於 ACID 的強一致性模型,而是通過犧牲強一致性來獲得可用性,並允許數據在一段時間內是不一致的,但最終達到一致狀態。
設計模式
1. 根據目的來分
根據模式是用來完成什麼工作來劃分,這種方式可分爲創建型模式、結構型模式和行爲型模式 3 種。
- 創建型模式:用於描述“怎樣創建對象”,它的主要特點是“將對象的創建與使用分離”。GoF 中提供了單例、原型、工廠方法、抽象工廠、建造者等 5 種創建型模式。
- 結構型模式:用於描述如何將類或對象按某種佈局組成更大的結構,GoF 中提供了代理、適配器、橋接、裝飾、外觀、享元、組合等 7 種結構型模式。
- 行爲型模式:用於描述類或對象之間怎樣相互協作共同完成單個對象都無法單獨完成的任務,以及怎樣分配職責。GoF 中提供了模板方法、策略、命令、職責鏈、狀態、觀察者、中介者、迭代器、訪問者、備忘錄、解釋器等 11 種行爲型模式。
2. 根據作用範圍來分
根據模式是主要用於類上還是主要用於對象上來分,這種方式可分爲類模式和對象模式兩種。
- 類模式:用於處理類與子類之間的關係,這些關係通過繼承來建立,是靜態的,在編譯時刻便確定下來了。GoF中的工廠方法、(類)適配器、模板方法、解釋器屬於該模式。
- 對象模式:用於處理對象之間的關係,這些關係可以通過組合或聚合來實現,在運行時刻是可以變化的,更具動態性。GoF 中除了以上 4 種,其他的都是對象模式。
3.設計模式的功能
- 1、FACTORY 工廠方法:追MM少不了請吃飯了,麥當勞的雞翅和肯德基的雞翅都是MM愛吃的東西,雖然口味有所不同,但不管你帶MM去麥當勞或肯德基,只管向服務員說“來四個雞翅”就行了。麥當勞和肯德基就是生產雞翅的Factory 工廠模式:客戶類和工廠類分開。消費者任何時候需要某種產品,只需向工廠請求即可。消費者無須修改就可以接納新產品。缺點是當產品修改時,工廠類也要做相應的修改。如:如何創建及如何向客戶端提供。
- 2、BUILDER建造者模式:MM最愛聽的就是“我愛你”這句話了,見到不同地方的MM,要能夠用她們的方言跟她說這句話哦,我有一個多種語言翻譯機,上面每種語言都有一個按鍵,見到MM我只要按對應的鍵,它就能夠用相應的語言說出“我愛你”這句話了,國外的MM也可以輕鬆搞掂,這就是我的“我愛你”builder。(這一定比美軍在伊拉克用的翻譯機好賣) 建造模式:將產品的內部表象和產品的生成過程分割開來,從而使一個建造過程生成具有不同的內部表象的產品對象。建造模式使得產品內部表象可以獨立的變化,客戶不必知道產品內部組成的細節。建造模式可以強制實行一種分步驟進行的建造過程。
- 3、FACTORY METHOD抽象工廠:請MM去麥當勞吃漢堡,不同的MM有不同的口味,要每個都記住是一件煩人的事情,我一般採用Factory Method模式,帶着MM到服務員那兒,說“要一個漢堡”,具體要什麼樣的漢堡呢,讓MM直接跟服務員說就行了。 工廠方法模式:核心工廠類不再負責所有產品的創建,而是將具體創建的工作交給子類去做,成爲一個抽象工廠角色,僅負責給出具體工廠類必須實現的接口,而不接觸哪一個產品類應當被實例化這種細節。
- 4、PROTOTYPE 原型模式:跟MM用QQ聊天,一定要說些深情的話語了,我搜集了好多肉麻的情話,需要時只要copy出來放到QQ裏面就行了,這就是我的情話prototype了。(100塊錢一份,你要不要) 原始模型模式:通過給出一個原型對象來指明所要創建的對象的類型,然後用複製這個原型對象的方法創建出更多同類型的對象。原始模型模式允許動態的增加或減少產品類,產品類不需要非得有任何事先確定的等級結構,原始模型模式適用於任何的等級結構。缺點是每一個類都必須配備一個克隆方法。
- 5、SINGLETON 單態模式:俺有6個漂亮的老婆,她們的老公都是我,我就是我們家裏的老公Sigleton,她們只要說道“老公”,都是指的同一個人,那就是我(剛纔做了個夢啦,哪有這麼好的事) 單例模式:單例模式確保某一個類只有一個實例,而且自行實例化並向整個系統提供這個實例單例模式。單例模式只應在有真正的“單一實例”的需求時纔可使用。 [b:9ceca65206]結構型模式[/b:9ceca65206]
- 6、ADAPTER 適配器模式:在朋友聚會上碰到了一個美女Sarah,從香港來的,可我不會說粵語,她不會說普通話,只好求助於我的朋友kent了,他作爲我和Sarah之間的Adapter,讓我和Sarah可以相互交談了(也不知道他會不會耍我) 適配器(變壓器)模式:把一個類的接口變換成客戶端所期待的另一種接口,從而使原本因接口原因不匹配而無法一起工作的兩個類能夠一起工作。適配類可以根據參數返還一個合適的實例給客戶端。
- 7、BRIDGE 橋樑模式:早上碰到MM,要說早上好,晚上碰到MM,要說晚上好;碰到MM穿了件新衣服,要說你的衣服好漂亮哦,碰到MM新做的髮型,要說你的頭髮好漂亮哦。不要問我“早上碰到MM新做了個髮型怎麼說”這種問題,自己用BRIDGE組合一下不就行了 橋樑模式:將抽象化與實現化脫耦,使得二者可以獨立的變化,也就是說將他們之間的強關聯變成弱關聯,也就是指在一個軟件系統的抽象化和實現化之間使用組合/聚合關係而不是繼承關係,從而使兩者可以獨立的變化。
- 8、COMPOSITE合成模式:Mary今天過生日。“我過生日,你要送我一件禮物。”“嗯,好吧,去商店,你自己挑。”“這件T恤挺漂亮,買,這條裙子好看,買,這個包也不錯,買。”“喂,買了三件了呀,我只答應送一件禮物的哦。”“什麼呀,T恤加裙子加包包,正好配成一套呀,小姐,麻煩你包起來。”“……”,MM都會用Composite模式了,你會了沒有? 合成模式:合成模式將對象組織到樹結構中,可以用來描述整體與部分的關係。合成模式就是一個處理對象的樹結構的模式。合成模式把部分與整體的關係用樹結構表示出來。合成模式使得客戶端把一個個單獨的成分對象和由他們複合而成的合成對象同等看待。
- 9、DECORATOR裝飾模式:Mary過完輪到Sarly過生日,還是不要叫她自己挑了,不然這個月伙食費肯定玩完,拿出我去年在華山頂上照的照片,在背面寫上“最好的的禮物,就是愛你的Fita”,再到街上禮品店買了個像框(賣禮品的MM也很漂亮哦),再找隔壁搞美術設計的Mike設計了一個漂亮的盒子裝起來……,我們都是Decorator,最終都在修飾我這個人呀,怎麼樣,看懂了嗎? 裝飾模式:裝飾模式以對客戶端透明的方式擴展對象的功能,是繼承關係的一個替代方案,提供比繼承更多的靈活性。動態給一個對象增加功能,這些功能可以再動態的撤消。1增加由一些基本功能的排列組合而產生的非常大量的功能。
- 10、FACADE門面模式:我有一個專業的Nikon相機,我就喜歡自己手動調光圈、快門,這樣照出來的照片才專業,但MM可不懂這些,教了半天也不會。幸好相機有Facade設計模式,把相機調整到自動檔,只要對準目標按快門就行了,一切由相機自動調整,這樣MM也可以用這個相機給我拍張照片了。 門面模式:外部與一個子系統的通信必須通過一個統一的門面對象進行。門面模式提供一個高層次的接口,使得子系統更易於使用。每一個子系統只有一個門面類,而且此門面類只有一個實例,也就是說它是一個單例模式。但整個系統可以有多個門面類。
- 11、FLYWEIGHT享元模式:每天跟MM發短信,手指都累死了,最近買了個新手機,可以把一些常用的句子存在手機裏,要用的時候,直接拿出來,在前面加上MM的名字就可以發送了,再不用一個字一個字敲了。共享的句子就是Flyweight,MM的名字就是提取出來的外部特徵,根據上下文情況使用。 享元模式:FLYWEIGHT在拳擊比賽中指最輕量級。享元模式以共享的方式高效的支持大量的細粒度對象。享元模式能做到共享的關鍵是區分內蘊狀態和外蘊狀態。內蘊狀態存儲在享元內部,不會隨環境的改變而有所不同。外蘊狀態是隨環境的改變而改變的。外蘊狀態不能影響內蘊狀態,它們是相互獨立的。將可以共享的狀態和不可以共享的狀態從常規類中區分開來,將不可以共享的狀態從類裏剔除出去。客戶端不可以直接創建被共享的對象,而應當使用一個工廠對象負責創建被共享的對象。享元模式大幅度的降低內存中對象的數量。
- 12、PROXY代理模式:跟MM在網上聊天,一開頭總是“hi,你好”,“你從哪兒來呀?”“你多大了?”“身高多少呀?”這些話,真煩人,寫個程序做爲我的Proxy吧,凡是接收到這些話都設置好了自己的回答,接收到其他的話時再通知我回答,怎麼樣,酷吧。 代理模式:代理模式給某一個對象提供一個代理對象,並由代理對象控制對源對象的引用。代理就是一個人或一個機構代表另一個人或者一個機構採取行動。某些情況下,客戶不想或者不能夠直接引用一個對象,代理對象可以在客戶和目標對象直接起到中介的作用。客戶端分辨不出代理主題對象與真實主題對象。代理模式可以並不知道真正的被代理對象,而僅僅持有一個被代理對象的接口,這時候代理對象不能夠創建被代理對象,被代理對象必須有系統的其他角色代爲創建並傳入。
- 13、CHAIN OF RESPONSIBLEITY責任鏈模式:晚上去上英語課,爲了好開溜坐到了最後一排,哇,前面坐了好幾個漂亮的MM哎,找張紙條,寫上“Hi,可以做我的女朋友嗎?如果不願意請向前傳”,紙條就一個接一個的傳上去了,糟糕,傳到第一排的MM把紙條傳給老師了,聽說是個老處女呀,快跑! 責任鏈模式:在責任鏈模式中,很多對象由每一個對象對其下家的引用而接起來形成一條鏈。請求在這個鏈上傳遞,直到鏈上的某一個對象決定處理此請求。客戶並不知道鏈上的哪一個對象最終處理這個請求,系統可以在不影響客戶端的情況下動態的重新組織鏈和分配責任。處理者有兩個選擇:承擔責任或者把責任推給下家。一個請求可以最終不被任何接收端對象所接受。
- 14、COMMAND命令模式:俺有一個MM家裏管得特別嚴,沒法見面,只好藉助於她弟弟在我們倆之間傳送信息,她對我有什麼指示,就寫一張紙條讓她弟弟帶給我。這不,她弟弟又傳送過來一個COMMAND,爲了感謝他,我請他吃了碗雜醬麪,哪知道他說:“我同時給我姐姐三個男朋友送COMMAND,就數你最小氣,才請我吃麪。” 命令模式:命令模式把一個請求或者操作封裝到一個對象中。命令模式把發出命令的責任和執行命令的責任分割開,委派給不同的對象。命令模式允許請求的一方和發送的一方獨立開來,使得請求的一方不必知道接收請求的一方的接口,更不必知道請求是怎麼被接收,以及操作是否執行,何時被執行以及是怎麼被執行的。系統支持命令的撤消。
- 15、INTERPRETER解釋器模式:俺有一個《泡MM真經》,上面有各種泡MM的攻略,比如說去吃西餐的步驟、去看電影的方法等等,跟MM約會時,只要做一個Interpreter,照着上面的腳本執行就可以了。 解釋器模式:給定一個語言後,解釋器模式可以定義出其文法的一種表示,並同時提供一個解釋器。客戶端可以使用這個解釋器來解釋這個語言中的句子。解釋器模式將描述怎樣在有了一個簡單的文法後,使用模式設計解釋這些語句。在解釋器模式裏面提到的語言是指任何解釋器對象能夠解釋的任何組合。在解釋器模式中需要定義一個代表文法的命令類的等級結構,也就是一系列的組合規則。每一個命令對象都有一個解釋方法,代表對命令對象的解釋。命令對象的等級結構中的對象的任何排列組合都是一個語言。
- 16、ITERATOR迭代子模式:我愛上了Mary,不顧一切的向她求婚。 Mary:“想要我跟你結婚,得答應我的條件” 我:“什麼條件我都答應,你說吧” Mary:“我看上了那個一克拉的鑽石” 我:“我買,我買,還有嗎?” Mary:“我看上了湖邊的那棟別墅” 我:“我買,我買,還有嗎?” Mary:“我看上那輛法拉利跑車” 我腦袋嗡的一聲,坐在椅子上,一咬牙:“我買,我買,還有嗎?” …… 迭代子模式:迭代子模式可以順序訪問一個聚集中的元素而不必暴露聚集的內部表象。多個對象聚在一起形成的總體稱之爲聚集,聚集對象是能夠包容一組對象的容器對象。迭代子模式將迭代邏輯封裝到一個獨立的子對象中,從而與聚集本身隔開。迭代子模式簡化了聚集的界面。每一個聚集對象都可以有一個或一個以上的迭代子對象,每一個迭代子的迭代狀態可以是彼此獨立的。迭代算法可以獨立於聚集角色變化。
- 17、MEDIATOR調停者模式:四個MM打麻將,相互之間誰應該給誰多少錢算不清楚了,幸虧當時我在旁邊,按照各自的籌碼數算錢,賺了錢的從我這裏拿,賠了錢的也付給我,一切就OK啦,俺得到了四個MM的電話。 調停者模式:調停者模式包裝了一系列對象相互作用的方式,使得這些對象不必相互明顯作用。從而使他們可以鬆散偶合。當某些對象之間的作用發生改變時,不會立即影響其他的一些對象之間的作用。保證這些作用可以彼此獨立的變化。調停者模式將多對多的相互作用轉化爲一對多的相互作用。調停者模式將對象的行爲和協作抽象化,把對象在小尺度的行爲上與其他對象的相互作用分開處理。
- 18、MEMENTO備忘錄模式:同時跟幾個MM聊天時,一定要記清楚剛纔跟MM說了些什麼話,不然MM發現了會不高興的哦,幸虧我有個備忘錄,剛纔與哪個MM說了什麼話我都拷貝一份放到備忘錄裏面保存,這樣可以隨時察看以前的記錄啦。 備忘錄模式:備忘錄對象是一個用來存儲另外一個對象內部狀態的快照的對象。備忘錄模式的用意是在不破壞封裝的條件下,將一個對象的狀態捉住,並外部化,存儲起來,從而可以在將來合適的時候把這個對象還原到存儲起來的狀態。
- 19、OBSERVER觀察者模式:想知道咱們公司最新MM情報嗎?加入公司的MM情報郵件組就行了,tom負責蒐集情報,他發現的新情報不用一個一個通知我們,直接發佈給郵件組,我們作爲訂閱者(觀察者)就可以及時收到情報啦 觀察者模式:觀察者模式定義了一種一隊多的依賴關係,讓多個觀察者對象同時監聽某一個主題對象。這個主題對象在狀態上發生變化時,會通知所有觀察者對象,使他們能夠自動更新自己。
- 20、STATE 狀態模式:跟MM交往時,一定要注意她的狀態哦,在不同的狀態時她的行爲會有不同,比如你約她今天晚上去看電影,對你沒興趣的MM就會說“有事情啦”,對你不討厭但還沒喜歡上的MM就會說“好啊,不過可以帶上我同事麼?”,已經喜歡上你的MM就會說“幾點鐘?看完電影再去泡吧怎麼樣?”,當然你看電影過程中表現良好的話,也可以把MM的狀態從不討厭不喜歡變成喜歡哦。 狀態模式:狀態模式允許一個對象在其內部狀態改變的時候改變行爲。這個對象看上去象是改變了它的類一樣。狀態模式把所研究的對象的行爲包裝在不同的狀態對象裏,每一個狀態對象都屬於一個抽象狀態類的一個子類。狀態模式的意圖是讓一個對象在其內部狀態改變的時候,其行爲也隨之改變。狀態模式需要對每一個系統可能取得的狀態創立一個狀態類的子類。當系統的狀態變化時,系統便改變所選的子類。
- 21、STRATEGY 策略模式:跟不同類型的MM約會,要用不同的策略,有的請電影比較好,有的則去吃小吃效果不錯,有的去海邊浪漫最合適,單目的都是爲了得到MM的芳心,我的追MM錦囊中有好多Strategy哦。 策略模式:策略模式針對一組算法,將每一個算法封裝到具有共同接口的獨立的類中,從而使得它們可以相互替換。策略模式使得算法可以在不影響到客戶端的情況下發生變化。策略模把行爲和環境分開。環境類負責維持和查詢行爲類,各種算法在具體的策略類中提供。由於算法和環境獨立開來,算法的增減,修改都不會影響到環境和客戶端。
- 22、TEMPLATE METHOD模板方法模式:看過《如何說服女生上牀》這部經典文章嗎?女生從認識到上牀的不變的步驟分爲巧遇、打破僵局、展開追求、接吻、前戲、動手、愛撫、進去八大步驟(Template method),但每個步驟針對不同的情況,都有不一樣的做法,這就要看你隨機應變啦(具體實現); 模板方法模式:模板方法模式準備一個抽象類,將部分邏輯以具體方法以及具體構造子的形式實現,然後聲明一些抽象方法來迫使子類實現剩餘的邏輯。不同的子類可以以不同的方式實現這些抽象方法,從而對剩餘的邏輯有不同的實現。先制定一個頂級邏輯框架,而將邏輯的細節留給具體的子類去實現。
- 23、VISITOR訪問者模式:情人節到了,要給每個MM送一束鮮花和一張卡片,可是每個MM送的花都要針對她個人的特點,每張卡片也要根據個人的特點來挑,我一個人哪搞得清楚,還是找花店老闆和禮品店老闆做一下Visitor,讓花店老闆根據MM的特點選一束花,讓禮品店老闆也根據每個人特點選一張卡,這樣就輕鬆多了; 訪問者模式:訪問者模式的目的是封裝一些施加於某種數據結構元素之上的操作。一旦這些操作需要修改的話,接受這個操作的數據結構可以保持不變。訪問者模式適用於數據結構相對未定的系統,它把數據結構和作用於結構上的操作之間的耦合解脫開,使得操作集合可以相對自由的演化。訪問者模式使得增加新的操作變的很容易,就是增加一個新的訪問者類。訪問者模式將有關的行爲集中到一個訪問者對象中,而不是分散到一個個的節點類中。當使用訪問者模式時,要將儘可能多的對象瀏覽邏輯放在訪問者類中,而不是放到它的子類中。訪問者模式可以跨過幾個類的等級結構訪問屬於不同的等級結構的成員類。
Redis支持的數據類型以及使用場景,持久化,哨兵機制,緩存擊穿,緩存穿透
簡單介紹一個redis?
redis是內存中的數據結構存儲系統,一個key-value類型的非關係型數據庫,可持久化的數據庫,相對於關係型數據庫(數據主要存在硬盤中),性能高,因此我們一般用redis來做緩存使用;並且redis支持豐富的數據類型,比較容易解決各種問題,因此redis可以用來作爲註冊中心,數據庫、緩存和消息中間件。Redis的Value支持5種數據類型,string、hash、list、set、zset(sorted set);
String類型:一個key對應一個value
Hash類型:它的key是string類型,value又是一個map(key-value),適合存儲對象。
List類型:按照插入順序的字符串鏈表(雙向鏈表),主要命令是LPUSH和RPUSH,能夠支持反向查找和遍歷
Set類型:用哈希表類型的字符串序列,沒有順序,集合成員是唯一的,沒有重複數據,底層主要是由一個value永遠爲null的hashmap來實現的。
zset類型:和set類型基本一致,不過它會給每個元素關聯一個double類型的分數(score),這樣就可以爲成員排序,並且插入是有序的。
你還用過其他的緩存嗎?這些緩存有什麼區別?都在什麼場景下去用?
對於緩存瞭解過redis和memcache
Memcache和redis的區別:
數據支持的類型:redis不僅僅支持簡單的k/v類型的數據,同時還支持list、set、zset、hash等數據結構的存儲;memcache只支持簡單的k/v類型的數據,key和value都是string類型
可靠性:memcache不支持數據持久化,斷電或重啓後數據消失,但其穩定性是有保證的;redis支持數據持久化和數據恢復,允許單點故障,但是同時也會付出性能的代價
性能上:對於存儲大數據,memcache的性能要高於redis
應用場景:
Memcache:適合多讀少寫,大數據量的情況(一些官網的文章信息等)
Redis:適用於對讀寫效率要求高、數據處理業務複雜、安全性要求較高的系統
案例:分佈式系統,存在session之間的共享問題,因此在做單點登錄的時候,我們利用redis來模擬了session的共享,來存儲用戶的信息,實現不同系統的session共享;
對redis的持久化了解不?
redis的持久化方式有兩種:
RDB(半持久化方式):按照配置不定期的通過異步的方式、快照的形式直接把內存中的數據持久化到磁盤的一個dump.rdb文件(二進制的臨時文件)中,redis默認的持久化方式,它在配置文件(redis.conf)中。
優點:只包含一個文件,將一個單獨的文件轉移到其他存儲媒介上,對於文件備份、災難恢復而言,比較實用。
缺點:系統一旦在持久化策略之前出現宕機現象,此前沒有來得及持久化的數據將會產生丟失
RDB持久化配置:
Redis會將數據集的快照dump到dump.rdb文件中。此外,我們也可以通過配置文件來修改Redis服務器dump快照的頻率,在打開6379.conf文件之後,我們搜索save,可以看到下面的配置信息:
save 900 1 #在900秒(15分鐘)之後,如果至少有1個key發生變化,則dump內存快照。
save 300 10 #在300秒(5分鐘)之後,如果至少有10個key發生變化,則dump內存快照。
save 60 10000 #在60秒(1分鐘)之後,如果至少有10000個key發生變化,則dump內存快照。
AOF(全持久化的方式):把每一次數據變化都通過write()函數將你所執行的命令追加到一個appendonly.aof文件裏面,Redis默認是不支持這種全持久化方式的,需要在配置文件(redis.conf)中將appendonly no改成appendonly yes
優點:數據安全性高,對日誌文件的寫入操作採用的是append模式,因此在寫入過程中即使出現宕機問題,也不會破壞日誌文件中已經存在的內容;
缺點:對於數量相同的數據集來說,aof文件通常要比rdb文件大,因此rdb在恢復大數據集時的速度大於AOF;
AOF持久化配置:
在Redis的配置文件中存在三種同步方式,它們分別是:
appendfsync always #每次有數據修改發生時都會都調用fsync刷新到aof文件,非常慢,但是安全;
appendfsync everysec #每秒鐘都調用fsync刷新到aof文件中,很快,但是可能丟失一秒內的數據,推薦使用,兼顧了速度和安全;
appendfsync no #不會自動同步到磁盤上,需要依靠OS(操作系統)進行刷新,效率快,但是安全性就比較差;
二種持久化方式區別:
AOF在運行效率上往往慢於RDB,每秒同步策略的效率是比較高的,同步禁用策略的效率和RDB一樣高效;
如果緩存數據安全性要求比較高的話,用aof這種持久化方式(比如項目中的購物車);
如果對於大數據集要求效率高的話,就可以使用默認的。而且這兩種持久化方式可以同時使用。
做過redis的集羣嗎?你們做集羣的時候搭建了幾臺,都是怎麼搭建的?
Redis的數據是存放在內存中的,不適合存儲大數據,大數據存儲一般公司常用hadoop中的Hbase或者MogoDB。redis主要用來處理高併發的,用我們的項目來說,電商項目如果併發大的話,一臺單獨的redis是不能足夠支持我們的併發,這就需要我們擴展多臺設備協同合作,即用到集羣。
Redis搭建集羣的方式有多種,例如:客戶端分片、Twemproxy、Codis等,但是redis3.0之後就支持redis-cluster集羣,這種方式採用的是無中心結構,每個節點保存數據和整個集羣的狀態,每個節點都和其他所有節點連接。如果使用的話就用redis-cluster集羣。集羣這塊是公司運維搭建的,具體怎麼搭建不是太瞭解。
我們項目中redis集羣主要搭建了6臺,3主(爲了保證redis的投票機制)3從(高可用),每個主服務器都有一個從服務器,作爲備份機。所有的節點都通過PING-PONG機制彼此互相連接;客戶端與redis集羣連接,只需要連接集羣中的任何一個節點即可;Redis-cluster中內置了16384個哈希槽,Redis-cluster把所有的物理節點映射到【0-16383】slot上,負責維護。
redis有事務嗎?
Redis是有事務的,redis中的事務是一組命令的集合,這組命令要麼都執行,要不都不執行,保證一個事務中的命令依次執行而不被其他命令插入。redis的事務是不支持回滾操作的。redis事務的實現,需要用到MULTI(事務的開始)和EXEC(事務的結束)命令 ;
緩存穿透
緩存查詢一般都是通過key去查找value,如果不存在對應的value,就要去數據庫中查找。如果這個key對應的value在數據庫中也不存在,並且對該key併發請求很大,就會對數據庫產生很大的壓力,這就叫緩存穿透
解決方案:
1.對所有可能查詢的參數以hash形式存儲,在控制層先進行校驗,不符合則丟棄。
2.將所有可能存在的數據哈希到一個足夠大的bitmap中,一個一定不存在的數據會被這個bitmap攔截掉,從而避免了對底層存儲系統的查詢壓力。
3.如果一個查詢返回的數據爲空(不管是數 據不存在,還是系統故障),我們仍然把這個空結果進行緩存,但它的過期時間會很短,最長不超過五分鐘。
緩存雪崩
當緩存服務器重啓或者大量緩存集中在一段時間內失效,發生大量的緩存穿透,這樣在失效的瞬間對數據庫的訪問壓力就比較大,所有的查詢都落在數據庫上,造成了緩存雪崩。 這個沒有完美解決辦法,但可以分析用戶行爲,儘量讓失效時間點均勻分佈。大多數系統設計者考慮用加鎖或者隊列的方式保證緩存的單線程(進程)寫,從而避免失效時大量的併發請求落到底層存儲系統上。
解決方案:
1.在緩存失效後,通過加鎖或者隊列來控制讀數據庫寫緩存的線程數量。比如對某個key只允許一個線程查詢數據和寫緩存,其他線程等待。
2.可以通過緩存reload機制,預先去更新緩存,再即將發生大併發訪問前手動觸發加載緩存
3.不同的key,設置不同的過期時間,讓緩存失效的時間點儘量均勻
4.做二級緩存,或者雙緩存策略。A1爲原始緩存,A2爲拷貝緩存,A1失效時,可以訪問A2,A1緩存失效時間設置爲短期,A2設置爲長期。
redis的安全機制(你們公司redis的安全這方面怎麼考慮的?)
漏洞介紹:redis默認情況下,會綁定在bind 0.0.0.0:6379,這樣就會將redis的服務暴露到公網上,如果在沒有開啓認證的情況下,可以導致任意用戶在訪問目標服務器的情況下,未授權就可訪問redis以及讀取redis的數據,攻擊者就可以在未授權訪問redis的情況下可以利用redis的相關方法,成功在redis服務器上寫入公鑰,進而可以直接使用私鑰進行直接登錄目標主機;
解決方案:
- 禁止一些高危命令。修改redis.conf文件,用來禁止遠程修改DB文件地址,比如 rename-command FLUSHALL "" 、rename-command CONFIG"" 、rename-command EVAL “”等;
- 以低權限運行redis服務。爲redis服務創建單獨的用戶和根目錄,並且配置禁止登錄;
- 爲redis添加密碼驗證。修改redis.conf文件,添加requirepass mypassword;
- 禁止外網訪問redis。修改redis.conf文件,添加或修改 bind 127.0.0.1,使得redis服務只在當前主機使用;
- 做log監控,及時發現攻擊;
- redis的哨兵機制(redis2.6以後出現的)
哨兵機制:
監控:監控主數據庫和從數據庫是否正常運行;
提醒:當被監控的某個redis出現問題的時候,哨兵可以通過API向管理員或者其他應用程序發送通知;
自動故障遷移:主數據庫出現故障時,可以自動將從數據庫轉化爲主數據庫,實現自動切換;
具體的配置步驟參考的網上的文檔。要注意的是,如果master主服務器設置了密碼,記得在哨兵的配置文件(sentinel.conf)裏面配置訪問密碼
redis中對於生存時間的應用
Redis中可以使用expire命令設置一個鍵的生存時間,到時間後redis會自動刪除;
應用場景:
- 設置限制的優惠活動的信息;
- 一些及時需要更新的數據,積分排行榜;
- 手機驗證碼的時間;
- 限制網站訪客訪問頻率;
線程是什麼,有幾種實現方式,它們之間的區別是什麼,線程池實現原理,JUC併發包,ThreadLocal與Lock和Synchronize區別
什麼是線程?講個故事給你聽,讓你沒法去背這個題,地址:https://blog.csdn.net/java_wxid/article/details/94131223
有幾種實現方式?
- 繼承Thread類
- 實現Runnable接口
- 實現Callable接口
- 線程池方式
優缺點
1.繼承Thread類
- 優點 、代碼簡單 。
- 缺點 、該類無法集成別的類。
2.實現Runnable接口
- 優點 、繼承其他類。 同一實現該接口的實例可以共享資源。
- 缺點 、代碼複雜
3.實現Callable
- 優點 、可以獲得異步任務的返回值
4.線程池 、實現自動化裝配,易於管理,循環利用資源。
代碼實現案例:
-
繼承Thread類,並重寫裏面的run方法
-
class A extends Thread{
-
public void run(){
-
for(int i=1;i<=100;i++){
-
System.out.println("-----------------"+i);
-
}
-
}
-
}
-
A a = new A();
-
a.start();
-
實現Runnable接口,並實現裏面的run方法
-
class B implements Runnable{
-
public void run(){
-
for(int i=1;i<=100;i++){
-
System.out.println("-----------------"+i);
-
}
-
}
-
}
-
B b = new B();
-
Thread t = new Thread(b);
-
t.start();
-
實現Callable
-
class A implements Callable<String>{
-
public String call() throws Exception{
-
//...
-
}
-
}
-
FutureTask<String> ft = new FutureTask<>(new A());
-
new Thread(ft).start();
-
線程池
-
ExcutorService es = Executors.newFixedThreadPool(10);
-
es.submit(new Runnable(){//任務});
-
es.submit(new Runnable(){//任務});
-
...
-
es.shutdown();
問題擴展
在Java中Lock接口比synchronized塊的優勢是什麼?你需要實現一個高效的緩存,它允許多個用戶讀,但只允許一個用戶寫,以此來保持它的完整性,你會怎樣去實現它?
整體上來說Lock是synchronized的擴展版,Lock提供了無條件的、可輪詢的(tryLock方法)、定時的(tryLock帶參方法)、可中斷的(lockInterruptibly)、可多條件隊列的(newCondition方法)鎖操作。另外Lock的實現類基本都支持非公平鎖(默認)和公平鎖,synchronized只支持非公平鎖,當然,在大部分情況下,非公平鎖是高效的選擇。
線程池的實現原理:https://blog.csdn.net/java_wxid/article/details/101844786
JUC併發包:
- volatile的三大特性:https://blog.csdn.net/java_wxid/article/details/97611028
- CompareAndSwap底層原理:https://blog.csdn.net/java_wxid/article/details/97611037
- AtomicReference原子引用:https://blog.csdn.net/java_wxid/article/details/97611046
- CountDownLatch倒計時器:https://blog.csdn.net/java_wxid/article/details/99168098
- CyclicBarrier循環柵欄:https://blog.csdn.net/java_wxid/article/details/99171155
- Semaphore信號燈:https://blog.csdn.net/java_wxid/article/details/99174538
ThreadLocal與Lock和Synchronize區別
ThreadLocal與Lock和Synchronize區別
ThreadLocal爲每一個線程都提供了變量的副本,使得每個線程在某一時間訪問到的並不是同一個對象,這樣就隔離了多個線程對數據的數據共享。ThreadLocal採用了“以空間換時間”的方式,爲每一個線程都提供了一份變量,因此可以同時訪問而互不影響。
synchronized是利用鎖的機制,使變量或代碼塊在某一時該只能被一個線程訪問。同步機制採用了“以時間換空間”的方式,僅提供一份變量,讓不同的線程排隊訪問。
如果一個代碼塊被synchronized關鍵字修飾,當一個線程獲取了對應的鎖,並執行該代碼塊時,其他線程便只能一直等待直至佔有鎖的線程釋放鎖。事實上,佔有鎖的線程釋放鎖一般會是以下三種情況之一:
佔有鎖的線程執行完了該代碼塊,然後釋放對鎖的佔有;
佔有鎖線程執行發生異常,此時JVM會讓線程自動釋放鎖;
佔有鎖線程進入 WAITING 狀態從而釋放鎖,例如在該線程中調用wait()方法等。
synchronized 是Java語言的內置特性,可以輕鬆實現對臨界資源的同步互斥訪問。那麼,爲什麼還會出現Lock呢?試考慮以下三種情況:
Case 1 :
在使用synchronized關鍵字的情形下,假如佔有鎖的線程由於要等待IO或者其他原因(比如調用sleep方法)被阻塞了,但是又沒有釋放鎖,那麼其他線程就只能一直等待,別無他法。這會極大影響程序執行效率。因此,就需要有一種機制可以不讓等待的線程一直無期限地等待下去(比如只等待一定的時間 (解決方案:tryLock(long time, TimeUnit unit)) 或者 能夠響應中斷 (解決方案:lockInterruptibly())),這種情況可以通過 Lock 解決。
Case 2 :
我們知道,當多個線程讀寫文件時,讀操作和寫操作會發生衝突現象,寫操作和寫操作也會發生衝突現象,但是讀操作和讀操作不會發生衝突現象。但是如果採用synchronized關鍵字實現同步的話,就會導致一個問題,即當多個線程都只是進行讀操作時,也只有一個線程在可以進行讀操作,其他線程只能等待鎖的釋放而無法進行讀操作。因此,需要一種機制來使得當多個線程都只是進行讀操作時,線程之間不會發生衝突。同樣地,Lock也可以解決這種情況 (解決方案:ReentrantReadWriteLock) 。
Case 3 :
我們可以通過Lock得知線程有沒有成功獲取到鎖 (解決方案:ReentrantLock) ,但這個是synchronized無法辦到的。
上面提到的三種情形,我們都可以通過Lock來解決,但 synchronized 關鍵字卻無能爲力。事實上,Lock 是 java.util.concurrent.locks包 下的接口,Lock 實現提供了比 synchronized 關鍵字 更廣泛的鎖操作,它能以更優雅的方式處理線程同步問題。也就是說,Lock提供了比synchronized更多的功能。但是要注意以下幾點:
1)synchronized是Java的關鍵字,因此是Java的內置特性,是基於JVM層面實現的。而Lock是一個Java接口,是基於JDK層面實現的,通過這個接口可以實現同步訪問;
2)採用synchronized方式不需要用戶去手動釋放鎖,當synchronized方法或者synchronized代碼塊執行完之後,系統會自動讓線程釋放對鎖的佔用;而 Lock則必須要用戶去手動釋放鎖,如果沒有主動釋放鎖,就有可能導致死鎖現象。
關於讀寫鎖:https://blog.csdn.net/java_wxid/article/details/99165717
分佈式事務(不同系統之間如何保證數據的一致性(A系統寫入數據,B系統因爲某些原因沒有寫入成功,造成數據不一致))
關於分佈式事物我看了有一篇博文感覺寫的很好,這裏我就引用他的地址:
https://www.cnblogs.com/soundcode/p/5590710.html
安全性問題(數據篡改(拿到別人的URL,篡改數據(金額)發送給系統))
- 方法一:對插入的操作進行校驗:一個請求的URL傳入進來,根據參數找到對應的用戶關聯表,查詢到用戶的userid和用戶登錄後保存到redis中的userid進行對比。例如:傳入參數爲(訂單id)和(優惠券id),拿(訂單id)查詢該訂單的用戶id,拿來和登錄的用戶id進行對比,判斷是否爲本人操作。拿(優惠券id)查詢用戶表是否領取了該優惠券,該優惠券是否可用。
- 方法二:前端傳入一個加密的信息數據,後端給這個給這個數據解密,判斷是否爲同一用戶。例如:將用戶id+項目id+密鑰生成一個token,傳入後端解密,拿到用戶id,項目id,密鑰對比是否一致
- 方法三:權限框架:可以指定某些角色,用戶的登錄名稱密碼正確纔可以訪問,修改。例如:1.Spring Security 2.apache shiro
索引使用的限制條件,sql優化有哪些,數據同步問題(緩存和數據庫),緩存優化
索引使用的限制條件,sql優化有哪些
-
a,選取最適用的字段:在創建表的時候,爲了獲得更好的性能,我們可以將表中字段的寬度設得儘可能小。另外一
-
個提高效率的方法是在可能的情況下,應該儘量把字段設置爲NOTNULL,
-
b,使用連接(JOIN)來代替子查詢(Sub-Queries)
-
c,使用聯合(UNION)來代替手動創建的臨時表
-
d,事物:
-
a)要麼語句塊中每條語句都操作成功,要麼都失敗。換句話說,就是可以保持數據庫中數據的一致性和完整
-
性。事物以BEGIN關鍵字開始,COMMIT關鍵字結束。在這之間的一條SQL操作失敗,那麼,ROLLBACK命令就可以
-
把數據庫恢復到BEGIN開始之前的狀態。
-
b) 是當多個用戶同時使用相同的數據源時,它可以利用鎖定數據庫的方法來爲用戶提供一種安全的訪問方
-
式,這樣可以保證用戶的操作不被其它的用戶所幹擾。
-
e,減少表關聯,加入冗餘字段
-
f,使用外鍵:鎖定表的方法可以維護數據的完整性,但是它卻不能保證數據的關聯性。這個時候我們就可以使用外鍵。
-
g,使用索引
-
h,優化的查詢語句
-
i,集羣
-
j,讀寫分離
-
k,主從複製
-
l,分表
-
m,分庫
-
o,適當的時候可以使用存儲過程
-
限制:儘量用全職索引,最左前綴:查詢從索引的最左前列開始並且不跳過索引中的列;索引列上不操作,範圍之
-
後全失效; 不等空值還有OR,索引影響要注意;like以通配符%開頭索引失效會變成全表掃描的操作,字符串不
-
加單引號索引失效
數據同步問題(緩存和數據庫),緩存優化
-
1.降低後端負載:對於高消耗的SQL:join結果集、分組統計結果;對這些結果進行緩存。
-
2.加速請求響應
-
3.大量寫合併爲批量寫:如計數器先redis累加再批量寫入DB
-
4.超時剔除:例如expire
-
5.主動更新:開發控制生命週期(最終一致性,時間間隔比較短)
-
6.緩存空對象
-
7.布隆過濾器攔截
-
8.命令本身的效率:例如sql優化,命令優化
-
9.網絡次數:減少通信次數
-
10.降低接入成本:長連/連接池,NIO等。
-
11.IO訪問合併
-
目的:要減少緩存重建次數、數據儘可能一致、減少潛在危險。
-
解決方案:
-
1.互斥鎖setex,setnx:
-
如果 set(nx 和 ex) 結果爲 true,說明此時沒有其他線程重建緩存,那麼當前線程執行緩存構建邏輯。
-
如果 setnx(nx 和 ex) 結果爲 false,說明此時已經有其他線程正在執行構建緩存的工作,那麼當前線程將休
-
息指定時間 ( 例如這裏是 50 毫秒,取決於構建緩存的速度 ) 後,重新執行函數,直到獲取到數據。
-
2永遠不過期:
-
熱點key,無非是併發特別大一級重建緩存時間比較長,如果直接設置過期時間,那麼時間到的時候,巨大的訪
-
問量會壓迫到數據庫上,所以要給熱點key的val增加一個邏輯過期時間字段,併發訪問的時候,判斷這個邏輯
-
字段的時間值是否大於當前時間,大於了說明要對緩存進行更新了,那麼這個時候,依然讓所有線程訪問老的
-
緩存,因爲緩存並沒有設置過期,但是另開一個線程對緩存進行重構。等重構成功,即執行了redis set操作
-
之後,所有的線程就可以訪問到重構後的緩存中的新的內容了
-
從緩存層面來看,確實沒有設置過期時間,所以不會出現熱點 key 過期後產生的問題,也就是“物理”不過期。
-
從功能層面來看,爲每個 value 設置一個邏輯過期時間,當發現超過邏輯過期時間後,會使用單獨的線程去構建緩存。
-
一致性問題:
-
1.先刪除緩存,然後在更新數據庫,如果刪除緩存失敗,那就不要更新數據庫,如果說刪除緩存成功,而更新
-
數據庫失敗,那查詢的時候只是從數據庫裏查了舊的數據而已,這樣就能保持數據庫與緩存的一致性。
-
2.先去緩存裏看下有沒有數據,如果沒有,可以先去隊列裏看是否有相同數據在做更新,發現隊列裏有一個請
-
求了,那麼就不要放新的操作進去了,用一個while(true)循環去查詢緩存,循環個200MS左右再次發送到
-
隊列裏去,然後同步等待緩存更新完成。
初始化Bean對象有幾個步驟,它的生命週期
之前寫過,這裏就給一個地址:https://blog.csdn.net/java_wxid/article/details/84391519
。。。。。(待完善中)
關於面試答案說明:這裏的答案我後面慢慢補,你們先看着,如果覺得自己技術能力強的可以在評論下方留言,儘量精簡語言將知識點擴展多些,合適的我會採用
關於背面試題說明:對於上面的面試題其實都是一些比較常見的,高頻率的題目,能回答上來的有很多人,我相信你是可以做到的,但你聊的真的足夠深入嗎?講解的真的夠全面嗎?拿下面第一題來說,面試官一般都直接問你HashMap實現原理,但是要是換一個問法,比如:影響HashMap性能有哪些因素?HashMap爲什麼存取效率那麼高?如果只是死記硬背總有那麼幾道題達不上來吧,相信大部分面試者就只會講一些在網上找到的答案,沒有擴展,面試官聽到你的回答,其實他已經聽過很多遍了,講出花來在面試官耳朵裏也就那麼回事,你拿什麼和別人拉開差距,所有請不要死記硬背。
關於薪資方面說明:即便你真的能講的很細,很全,有時也並不是所有地區,所有時間段都能拿到16k,行情是一直都在變化的,這裏我所說的16k,僅在上海地區,並且2019年年底面試所瞭解到的,其他地區面試情況如何本人並不清楚,年後疫情薪資方面,我看了確實有所下滑,要求很提高了很多,所以不用一直在評論下方說什麼,會背的人太多了,拿不了16k;像我學了半年的大部分能回答出來可以拿多少?都是開源的東西,能值16K,你別鬧了;我在這裏吐槽一句:能拿多少是以自己的能力爲標準的,但影響你薪資的,不僅僅只是能力,時間,地點,運氣等等,各方面因素都有的。(我有個朋友,前二年月薪拿32k呢,現在薪資都降到26了)
關於工作說明:能拿到16k,說明你已經具備了java中級開發的能力了,這一階段已經不侷限於CRUD了,已經可以獨立負責一個模塊開發,有一定的性能優化能力了,而不是隻要面試過關就可以的,你的編碼能力,獨立開發的能力,對業務理解的能力,和團隊溝通的能力要達到相應的水平。