寫本文之前,其實我自己已經(jīng)開源了一個 Java學(xué)習(xí)指南的文檔,里面包含了一些基礎(chǔ)知識和一些偏后端(Java方向)的知識。到目前為止收獲了 6.1k star 以及 1.5 k fork,close 了 10個 pr 以及 10 個issue。開源只是為了讓更多的人看到和參與進(jìn)來,這樣文檔的正確性和質(zhì)量才能很好的保證。畢竟,我個人能力、時間以及知識廣度和深度有限,一份好的項目的誕生肯定離不開和其他人的共同努力。 另外,我個人覺得不論你是前端還是后端(部分內(nèi)容可能會偏 Java 方向一點)都能從本文中學(xué)到東西。 本人技術(shù)水品有限,歡迎各位指正!寫的不好的話,請多見諒! 目錄
關(guān)注我:私信回復(fù)“架構(gòu)資料”獲取往期Java高級架構(gòu)資料、源碼、筆記、視頻 Dubbo、Redis、Netty、zookeeper、Spring cloud、分布式、高并發(fā)等架構(gòu)技術(shù) 前言不論是校招還是社招都避免不了各種面試、筆試,如何去準(zhǔn)備這些東西就顯得格外重要。不論是筆試還是面試都是有章可循的,我這個“有章可循”說的意思只是說應(yīng)對技術(shù)面試是可以提前準(zhǔn)備。 我其實特別不喜歡那種臨近考試就提前背啊記啊各種題的行為,非常反對!我覺得這種方法特別極端,而且在稍有一點經(jīng)驗的面試官面前是根本沒有用的。建議大家還是一步一個腳印踏踏實實地走。 運籌帷幄之后,決勝千里之外!不打毫無準(zhǔn)備的仗,我覺得大家可以先從下面幾個方面來準(zhǔn)備面試:
“80%的offer掌握在20%的人手中” 這句話也不是不無道理的。決定你面試能否成功的因素中實力固然占有很大一部分比例,但是如果你的心態(tài)或者說運氣不好的話,依然無法拿到滿意的 offer。運氣暫且不談,就拿心態(tài)來說,千萬不要因為面試失敗而氣餒或者說懷疑自己的能力,面試失敗之后多總結(jié)一下失敗的原因,后面你就會發(fā)現(xiàn)自己會越來越強大。 另外,大家要明確的很重要的幾點是:
筆主能力有限,如果有不對的地方或者和你想法不同的地方,敬請雅正、不舍賜教。 一 簡歷該如何寫
1.1 為什么說簡歷很重要? 假如你是網(wǎng)申,你的簡歷必然會經(jīng)過HR的篩選,一張簡歷HR可能也就花費10秒鐘看一下,然后HR就會決定你這一關(guān)是Fail還是Pass。 假如你是內(nèi)推,如果你的簡歷沒有什么優(yōu)勢的話,就算是內(nèi)推你的人再用心,也無能為力。 另外,就算你通過了篩選,后面的面試中,面試官也會根據(jù)你的簡歷來判斷你究竟是否值得他花費很多時間去面試。 1.2 這3點你必須知道
1.3 兩大法則了解一下 目前寫簡歷的方式有兩種普遍被認(rèn)可,一種是 STAR, 一種是 FAB。 STAR法則(Situation Task Action Result):
FAB 法則(Feature Advantage Benefit):
1.4 項目經(jīng)歷怎么寫? 簡歷上有一兩個項目經(jīng)歷很正常,但是真正能把項目經(jīng)歷很好的展示給面試官的非常少。對于項目經(jīng)歷大家可以考慮從如下幾點來寫:
1.5 專業(yè)技能該怎么寫? 先問一下你自己會什么,然后看看你意向的公司需要什么。一般HR可能并不太懂技術(shù),所以他在篩選簡歷的時候可能就盯著你專業(yè)技能的關(guān)鍵詞來看。對于公司有要求而你不會的技能,你可以花幾天時間學(xué)習(xí)一下,然后在簡歷上可以寫上自己了解這個技能。比如你可以這樣寫:
1.6 開源程序員簡歷模板分享
二 計算機網(wǎng)絡(luò)常見面試點總結(jié)計算機網(wǎng)絡(luò)常見問題回顧
下面列舉幾個常見問題的回答! 2.1 TCP、UDP 協(xié)議的區(qū)別 UDP 在傳送數(shù)據(jù)之前不需要先建立連接,遠(yuǎn)地主機在收到 UDP 報文后,不需要給出任何確認(rèn)。雖然 UDP 不提供可靠交付,但在某些情況下 UDP 確是一種最有效的工作方式(一般用于即時通信),比如: QQ 語音、 QQ 視頻 、直播等等 TCP 提供面向連接的服務(wù)。在傳送數(shù)據(jù)之前必須先建立連接,數(shù)據(jù)傳送結(jié)束后要釋放連接。 TCP 不提供廣播或多播服務(wù)。由于 TCP 要提供可靠的,面向連接的運輸服務(wù)(TCP的可靠體現(xiàn)在TCP在傳遞數(shù)據(jù)之前,會有三次握手來建立連接,而且在數(shù)據(jù)傳遞時,有確認(rèn)、窗口、重傳、擁塞控制機制,在數(shù)據(jù)傳完后,還會斷開連接用來節(jié)約系統(tǒng)資源),這一難以避免增加了許多開銷,如確認(rèn),流量控制,計時器以及連接管理等。這不僅使協(xié)議數(shù)據(jù)單元的首部增大很多,還要占用許多處理機資源。TCP 一般用于文件傳輸、發(fā)送和接收郵件、遠(yuǎn)程登錄等場景。 2.2 在瀏覽器中輸入url地址 ->> 顯示主頁的過程 百度好像最喜歡問這個問題。
圖片來源:《圖解HTTP》 總體來說分為以下幾個過程:
2.3 各種協(xié)議與HTTP協(xié)議之間的關(guān)系 一般面試官會通過這樣的問題來考察你對計算機網(wǎng)絡(luò)知識體系的理解。 圖片來源:《圖解HTTP》 2.4 HTTP長連接、短連接 在HTTP/1.0中默認(rèn)使用短連接。也就是說,客戶端和服務(wù)器每進(jìn)行一次HTTP操作,就建立一次連接,任務(wù)結(jié)束就中斷連接。當(dāng)客戶端瀏覽器訪問的某個HTML或其他類型的Web頁中包含有其他的Web資源(如JavaScript文件、圖像文件、CSS文件等),每遇到這樣一個Web資源,瀏覽器就會重新建立一個HTTP會話。 而從HTTP/1.1起,默認(rèn)使用長連接,用以保持連接特性。使用長連接的HTTP協(xié)議,會在響應(yīng)頭加入這行代碼: Connection:keep-alive復(fù)制代碼 在使用長連接的情況下,當(dāng)一個網(wǎng)頁打開完成后,客戶端和服務(wù)器之間用于傳輸HTTP數(shù)據(jù)的TCP連接不會關(guān)閉,客戶端再次訪問這個服務(wù)器時,會繼續(xù)使用這一條已經(jīng)建立的連接。Keep-Alive不會永久保持連接,它有一個保持時間,可以在不同的服務(wù)器軟件(如Apache)中設(shè)定這個時間。實現(xiàn)長連接需要客戶端和服務(wù)端都支持長連接。 HTTP協(xié)議的長連接和短連接,實質(zhì)上是TCP協(xié)議的長連接和短連接。 2.5 TCP 三次握手和四次揮手(面試??? 為了準(zhǔn)確無誤地把數(shù)據(jù)送達(dá)目標(biāo)處,TCP協(xié)議采用了三次握手策略。 漫畫圖解: 圖片來源:《圖解HTTP》 簡單示意圖:
三次握手的目的是建立可靠的通信信道,說到通訊,簡單來說就是數(shù)據(jù)的發(fā)送與接收,而三次握手最主要的目的就是雙方確認(rèn)自己與對方的發(fā)送與接收是正常的。 第一次握手:Client 什么都不能確認(rèn);Server 確認(rèn)了對方發(fā)送正常 第二次握手:Client 確認(rèn)了:自己發(fā)送、接收正常,對方發(fā)送、接收正常;Server 確認(rèn)了:自己接收正常,對方發(fā)送正常 第三次握手:Client 確認(rèn)了:自己發(fā)送、接收正常,對方發(fā)送、接收正常;Server 確認(rèn)了:自己發(fā)送、接收正常,對方發(fā)送接收正常 所以三次握手就能確認(rèn)雙發(fā)收發(fā)功能都正常,缺一不可。
接收端傳回發(fā)送端所發(fā)送的 SYN 是為了告訴發(fā)送端,我接收到的信息確實就是你所發(fā)送的信號了。
雙方通信無誤必須是兩者互相發(fā)送信息都無誤。傳了 SYN,證明發(fā)送方到接收方的通道沒有問題,但是接收方到發(fā)送方的通道還需要 ACK 信號來進(jìn)行驗證。 斷開一個 TCP 連接則需要“四次揮手”:
任何一方都可以在數(shù)據(jù)傳送結(jié)束后發(fā)出連接釋放的通知,待對方確認(rèn)后進(jìn)入半關(guān)閉狀態(tài)。當(dāng)另一方也沒有數(shù)據(jù)再發(fā)送的時候,則發(fā)出連接釋放通知,對方確認(rèn)后就完全關(guān)閉了TCP連接。 舉個例子:A 和 B 打電話,通話即將結(jié)束后,A 說“我沒啥要說的了”,B回答“我知道了”,但是 B 可能還會有要說的話,A 不能要求 B 跟著自己的節(jié)奏結(jié)束通話,于是 B 可能又巴拉巴拉說了一通,最后 B 說“我說完了”,A 回答“知道了”,這樣通話才算結(jié)束。 三 Linux3.1 簡單介紹一下 Linux 文件系統(tǒng)?
在Linux操作系統(tǒng)中,所有被操作系統(tǒng)管理的資源,例如網(wǎng)絡(luò)接口卡、磁盤驅(qū)動器、打印機、輸入輸出設(shè)備、普通文件或是目錄都被看作是一個文件。 也就是說在LINUX系統(tǒng)中有一個重要的概念:一切都是文件。其實這是UNIX哲學(xué)的一個體現(xiàn),而Linux是重寫UNIX而來,所以這個概念也就傳承了下來。在UNIX系統(tǒng)中,把一切資源都看作是文件,包括硬件設(shè)備。UNIX系統(tǒng)把每個硬件都看成是一個文件,通常稱為設(shè)備文件,這樣用戶就可以用讀寫文件的方式實現(xiàn)對硬件的訪問。
Linux支持5種文件類型 : Linux的目錄結(jié)構(gòu)如下: Linux文件系統(tǒng)的結(jié)構(gòu)層次鮮明,就像一棵倒立的樹,最頂層是其根目錄: 常見目錄說明:
3.2 一些常見的 Linux 命令了解嗎?
1)打包并壓縮文件: Linux中的打包文件一般是以.tar結(jié)尾的,壓縮的命令一般是以.gz結(jié)尾的。 而一般情況下打包和壓縮是一起進(jìn)行的,打包并壓縮后的文件的后綴名一般.tar.gz。 命令:tar -zcvf 打包壓縮后的文件名 要打包壓縮的文件 其中: z:調(diào)用gzip壓縮命令進(jìn)行壓縮 c:打包文件 v:顯示運行過程 f:指定文件名 比如:加入test目錄下有三個文件分別是 :aaa.txt bbb.txt ccc.txt,如果我們要打包test目錄并指定壓縮后的壓縮包名稱為test.tar.gz可以使用命令:tar -zcvf test.tar.gz aaa.txt bbb.txt ccc.txt或:tar -zcvf test.tar.gz /test/ 2)解壓壓縮包: 命令:tar [-xvf] 壓縮文件 其中:x:代表解壓 示例: 1 將/test下的test.tar.gz解壓到當(dāng)前目錄下可以使用命令:tar -xvf test.tar.gz 2 將/test下的test.tar.gz解壓到根目錄/usr下:tar -xvf xxx.tar.gz -C /usr(- C代表指定解壓的位置)
四 MySQL4.1 說說自己對于 MySQL 常見的兩種存儲引擎:MyISAM與InnoDB的理解 關(guān)于二者的對比與總結(jié):
MyISAM更適合讀密集的表,而InnoDB更適合寫密集的的表。 在數(shù)據(jù)庫做主從分離的情況下,經(jīng)常選擇MyISAM作為主庫的存儲引擎。 一般來說,如果需要事務(wù)支持,并且有較高的并發(fā)讀取頻率(MyISAM的表鎖的粒度太大,所以當(dāng)該表寫并發(fā)量較高時,要等待的查詢就會很多了),InnoDB是不錯的選擇。如果你的數(shù)據(jù)量很大(MyISAM支持壓縮特性可以減少磁盤的空間占用),而且不需要支持事務(wù)時,MyISAM是最好的選擇。 4.2 數(shù)據(jù)庫索引了解嗎? Mysql索引使用的數(shù)據(jù)結(jié)構(gòu)主要有BTree索引 和 哈希索引 。對于哈希索引來說,底層的數(shù)據(jù)結(jié)構(gòu)就是哈希表,因此在絕大多數(shù)需求為單條記錄查詢的時候,可以選擇哈希索引,查詢性能最快;其余大部分場景,建議選擇BTree索引。 Mysql的BTree索引使用的是B數(shù)中的B+Tree,但對于主要的兩種存儲引擎的實現(xiàn)方式是不同的。
4.3 對于大表的常見優(yōu)化手段說一下 當(dāng)MySQL單表記錄數(shù)過大時,數(shù)據(jù)庫的CRUD性能會明顯下降,一些常見的優(yōu)化措施如下:
垂直拆分的優(yōu)點: 可以使得行數(shù)據(jù)變小,在查詢時減少讀取的Block數(shù),減少I/O次數(shù)。此外,垂直分區(qū)可以簡化表的結(jié)構(gòu),易于維護(hù)。 垂直拆分的缺點: 主鍵會出現(xiàn)冗余,需要管理冗余列,并會引起Join操作,可以通過在應(yīng)用層進(jìn)行Join來解決。此外,垂直分區(qū)會讓事務(wù)變得更加復(fù)雜; 水平分區(qū): 保持?jǐn)?shù)據(jù)表結(jié)構(gòu)不變,通過某種策略存儲數(shù)據(jù)分片。這樣每一片數(shù)據(jù)分散到不同的表或者庫中,達(dá)到了分布式的目的。 水平拆分可以支撐非常大的數(shù)據(jù)量。 水平拆分是指數(shù)據(jù)表行的拆分,表的行數(shù)超過200萬行時,就會變慢,這時可以把一張的表的數(shù)據(jù)拆成多張表來存放。舉個例子:我們可以將用戶信息表拆分成多個用戶信息表,這樣就可以避免單一表數(shù)據(jù)量過大對性能造成影響。
五 Redis關(guān)于 redis 必知必會的11個問題!后兩個問題,暫未更新!如有需要,可以關(guān)注,私信回復(fù)“架構(gòu)資料”即可 領(lǐng)取
5.1 redis 簡介 Redis 是一個開源(BSD許可)的,內(nèi)存中的數(shù)據(jù)結(jié)構(gòu)存儲系統(tǒng),它可以用作數(shù)據(jù)庫、緩存和消息中間件。 它支持多種類型的數(shù)據(jù)結(jié)構(gòu),如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 與范圍查詢, bitmaps, hyperloglogs 和 地理空間(geospatial) 索引半徑查詢。 Redis 內(nèi)置了 復(fù)制(replication),LUA腳本(Lua scripting), LRU驅(qū)動事件(LRU eviction),事務(wù)(transactions) 和不同級別的 磁盤持久化(persistence), 并通過 Redis哨兵(Sentinel)和自動 分區(qū)(Cluster)提供高可用性(high availability)。 5.2 為什么要用 redis /為什么要用緩存 主要從“高性能”和“高并發(fā)”這兩點來看待這個問題。 高性能: 假如用戶第一次訪問數(shù)據(jù)庫中的某些數(shù)據(jù)。這個過程會比較慢,因為是從硬盤上讀取的。將該用戶訪問的數(shù)據(jù)存在數(shù)緩存中,這樣下一次再訪問這些數(shù)據(jù)的時候就可以直接從緩存中獲取了。操作緩存就是直接操作內(nèi)存,所以速度相當(dāng)快。如果數(shù)據(jù)庫中的對應(yīng)數(shù)據(jù)改變的之后,同步改變緩存中相應(yīng)的數(shù)據(jù)即可! 高并發(fā): 直接操作緩存能夠承受的請求是遠(yuǎn)遠(yuǎn)大于直接訪問數(shù)據(jù)庫的,所以我們可以考慮把數(shù)據(jù)庫中的部分?jǐn)?shù)據(jù)轉(zhuǎn)移到緩存中去,這樣用戶的一部分請求會直接到緩存這里而不用經(jīng)過數(shù)據(jù)庫。 5.3 為什么要用 redis 而不用 map/guava 做緩存? 緩存分為本地緩存和分布式緩存。以java為例,使用自帶的map或者guava實現(xiàn)的是本地緩存,最主要的特點是輕量以及快速,生命周期隨著 jvm 的銷毀而結(jié)束,并且在多實例的情況下,每個實例都需要各自保存一份緩存,緩存不具有一致性。 使用 redis 或 memcached 之類的稱為分布式緩存,在多實例的情況下,各實例共用一份緩存數(shù)據(jù),緩存具有一致性。缺點是需要保持 redis 或 memcached服務(wù)的高可用,整個程序架構(gòu)上較為復(fù)雜。 5.4 redis 和 memcached 的區(qū)別 對于 redis 和 memcached 我總結(jié)了下面四點。現(xiàn)在公司一般都是用 redis 來實現(xiàn)緩存,而且 redis 自身也越來越強大了!
5.5 redis 常見數(shù)據(jù)結(jié)構(gòu)以及使用場景分析 1. String
String數(shù)據(jù)結(jié)構(gòu)是簡單的key-value類型,value其實不僅可以是String,也可以是數(shù)字。 常規(guī)key-value緩存應(yīng)用; 常規(guī)計數(shù):微博數(shù),粉絲數(shù)等。 2.Hash
Hash 是一個 string 類型的 field 和 value 的映射表,hash 特別適合用于存儲對象,后續(xù)操作的時候,你可以直接僅僅修改這個對象中的某個字段的值。 比如我們可以Hash數(shù)據(jù)結(jié)構(gòu)來存儲用戶信息,商品信息等等。比如下面我就用 hash 類型存放了我本人的一些信息: key=JavaUser293847value={ “id”: 1, “name”: “SnailClimb”, “age”: 22, “l(fā)ocation”: “Wuhan, Hubei”}復(fù)制代碼 3.List
list就是鏈表,Redis list的應(yīng)用場景非常多,也是Redis最重要的數(shù)據(jù)結(jié)構(gòu)之一,比如微博的關(guān)注列表,粉絲列表,消息列表等功能都可以用Redis的 list 結(jié)構(gòu)來實現(xiàn)。 Redis list 的實現(xiàn)為一個雙向鏈表,即可以支持反向查找和遍歷,更方便操作,不過帶來了部分額外的內(nèi)存開銷。 另外可以通過 lrange 命令,就是從某個元素開始讀取多少個元素,可以基于 list 實現(xiàn)分頁查詢,這個很棒的一個功能,基于 redis 實現(xiàn)簡單的高性能分頁,可以做類似微博那種下拉不斷分頁的東西(一頁一頁的往下走),性能高。 4.Set
set對外提供的功能與list類似是一個列表的功能,特殊之處在于set是可以自動排重的。 當(dāng)你需要存儲一個列表數(shù)據(jù),又不希望出現(xiàn)重復(fù)數(shù)據(jù)時,set是一個很好的選擇,并且set提供了判斷某個成員是否在一個set集合內(nèi)的重要接口,這個也是list所不能提供的??梢曰?set 輕易實現(xiàn)交集、并集、差集的操作。 比如:在微博應(yīng)用中,可以將一個用戶所有的關(guān)注人存在一個集合中,將其所有粉絲存在一個集合。Redis可以非常方便的實現(xiàn)如共同關(guān)注、共同粉絲、共同喜好等功能。這個過程也就是求交集的過程,具體命令如下: sinterstore key1 key2 key3 將交集存在key1內(nèi)復(fù)制代碼 5.Sorted Set
和set相比,sorted set增加了一個權(quán)重參數(shù)score,使得集合中的元素能夠按score進(jìn)行有序排列。 舉例: 在直播系統(tǒng)中,實時排行信息包含直播間在線用戶列表,各種禮物排行榜,彈幕消息(可以理解為按消息維度的消息排行榜)等信息,適合使用 Redis 中的 SortedSet 結(jié)構(gòu)進(jìn)行存儲。 5.6 redis 設(shè)置過期時間 Redis中有個設(shè)置時間過期的功能,即對存儲在 redis 數(shù)據(jù)庫中的值可以設(shè)置一個過期時間。作為一個緩存數(shù)據(jù)庫,這是非常實用的。如我們一般項目中的token或者一些登錄信息,尤其是短信驗證碼都是有時間限制的,按照傳統(tǒng)的數(shù)據(jù)庫處理方式,一般都是自己判斷過期,這樣無疑會嚴(yán)重影響項目性能。 我們set key的時候,都可以給一個expire time,就是過期時間,通過過期時間我們可以指定這個 key 可以存貨的時間。 如果假設(shè)你設(shè)置一個一批 key 只能存活1個小時,那么接下來1小時后,redis是怎么對這批key進(jìn)行刪除的? 定期刪除+惰性刪除。 通過名字大概就能猜出這兩個刪除方式的意思了。
但是僅僅通過設(shè)置過期時間還是有問題的。我們想一下:如果定期刪除漏掉了很多過期 key,然后你也沒及時去查,也就沒走惰性刪除,此時會怎么樣?如果大量過期key堆積在內(nèi)存里,導(dǎo)致redis內(nèi)存塊耗盡了。怎么解決這個問題呢? redis 內(nèi)存淘汰機制。 5.7 redis 內(nèi)存淘汰機制(MySQL里有2000w數(shù)據(jù),Redis中只存20w的數(shù)據(jù),如何保證Redis中的數(shù)據(jù)都是熱點數(shù)據(jù)?) redis 配置文件 redis.conf 中有相關(guān)注釋,我這里就不貼了,大家可以自行查閱或者通過這個網(wǎng)址查看: download.redis.io/redis-stabl… redis 提供 6種數(shù)據(jù)淘汰策略:
備注: 關(guān)于 redis 設(shè)置過期時間以及內(nèi)存淘汰機制,我這里只是簡單的總結(jié)一下,后面會專門寫一篇文章來總結(jié)! 5.8 redis 持久化機制(怎么保證 redis 掛掉之后再重啟數(shù)據(jù)可以進(jìn)行恢復(fù)) 很多時候我們需要持久化數(shù)據(jù)也就是將內(nèi)存中的數(shù)據(jù)寫入到硬盤里面,大部分原因是為了之后重用數(shù)據(jù)(比如重啟機器、機器故障之后回復(fù)數(shù)據(jù)),或者是為了防止系統(tǒng)故障而將數(shù)據(jù)備份到一個遠(yuǎn)程位置。 Redis不同于Memcached的很重一點就是,Redis支持持久化,而且支持兩種不同的持久化操作。Redis的一種持久化方式叫快照(snapshotting,RDB),另一種方式是只追加文件(append-only file,AOF).這兩種方法各有千秋,下面我會詳細(xì)這兩種持久化方法是什么,怎么用,如何選擇適合自己的持久化方法。 快照(snapshotting)持久化(RDB) Redis可以通過創(chuàng)建快照來獲得存儲在內(nèi)存里面的數(shù)據(jù)在某個時間點上的副本。Redis創(chuàng)建快照之后,可以對快照進(jìn)行備份,可以將快照復(fù)制到其他服務(wù)器從而創(chuàng)建具有相同數(shù)據(jù)的服務(wù)器副本(Redis主從結(jié)構(gòu),主要用來提高Redis性能),還可以將快照留在原地以便重啟服務(wù)器的時候使用。 快照持久化是Redis默認(rèn)采用的持久化方式,在redis.conf配置文件中默認(rèn)有此下配置: save 900 1 #在900秒(15分鐘)之后,如果至少有1個key發(fā)生變化,Redis就會自動觸發(fā)BGSAVE命令創(chuàng)建快照。save 300 10 #在300秒(5分鐘)之后,如果至少有10個key發(fā)生變化,Redis就會自動觸發(fā)BGSAVE命令創(chuàng)建快照。save 60 10000 #在60秒(1分鐘)之后,如果至少有10000個key發(fā)生變化,Redis就會自動觸發(fā)BGSAVE命令創(chuàng)建快照。復(fù)制代碼 AOF(append-only file)持久化 與快照持久化相比,AOF持久化 的實時性更好,因此已成為主流的持久化方案。默認(rèn)情況下Redis沒有開啟AOF(append only file)方式的持久化,可以通過appendonly參數(shù)開啟: appendonly yes復(fù)制代碼 開啟AOF持久化后每執(zhí)行一條會更改Redis中的數(shù)據(jù)的命令,Redis就會將該命令寫入硬盤中的AOF文件。AOF文件的保存位置和RDB文件的位置相同,都是通過dir參數(shù)設(shè)置的,默認(rèn)的文件名是appendonly.aof。 在Redis的配置文件中存在三種不同的 AOF 持久化方式,它們分別是: appendfsync always #每次有數(shù)據(jù)修改發(fā)生時都會寫入AOF文件,這樣會嚴(yán)重降低Redis的速度appendfsync everysec #每秒鐘同步一次,顯示地將多個寫命令同步到硬盤appendfsync no #讓操作系統(tǒng)決定何時進(jìn)行同步復(fù)制代碼 為了兼顧數(shù)據(jù)和寫入性能,用戶可以考慮 appendfsync everysec選項 ,讓Redis每秒同步一次AOF文件,Redis性能幾乎沒受到任何影響。而且這樣即使出現(xiàn)系統(tǒng)崩潰,用戶最多只會丟失一秒之內(nèi)產(chǎn)生的數(shù)據(jù)。當(dāng)硬盤忙于執(zhí)行寫入操作的時候,Redis還會優(yōu)雅的放慢自己的速度以便適應(yīng)硬盤的最大寫入速度。 補充內(nèi)容:AOF 重寫 AOF重寫可以產(chǎn)生一個新的AOF文件,這個新的AOF文件和原有的AOF文件所保存的數(shù)據(jù)庫狀態(tài)一樣,但體積更小。 AOF重寫是一個有歧義的名字,該功能是通過讀取數(shù)據(jù)庫中的鍵值對來實現(xiàn)的,程序無須對現(xiàn)有AOF文件進(jìn)行任伺讀入、分析或者寫人操作。 在執(zhí)行 BGREWRITEAOF 命令時,Redis 服務(wù)器會維護(hù)一個 AOF 重寫緩沖區(qū),該緩沖區(qū)會在子進(jìn)程創(chuàng)建新AOF文件期間,記錄服務(wù)器執(zhí)行的所有寫命令。當(dāng)子進(jìn)程完成創(chuàng)建新AOF文件的工作之后,服務(wù)器會將重寫緩沖區(qū)中的所有內(nèi)容追加到新AOF文件的末尾,使得新舊兩個AOF文件所保存的數(shù)據(jù)庫狀態(tài)一致。最后,服務(wù)器用新的AOF文件替換舊的AOF文件,以此來完成AOF文件重寫操作 5.9 緩存雪崩和緩存穿透問題解決方案 緩存雪崩 簡介:緩存同一時間大面積的失效,所以,后面的請求都會落到數(shù)據(jù)庫上,造成數(shù)據(jù)庫短時間內(nèi)承受大量請求而崩掉。 解決辦法(中華石杉老師在他的視頻中提到過):
緩存穿透 簡介:一般是黑客故意去請求緩存中不存在的數(shù)據(jù),導(dǎo)致所有的請求都落到數(shù)據(jù)庫上,造成數(shù)據(jù)庫短時間內(nèi)承受大量請求而崩掉。 解決辦法: 有很多種方法可以有效地解決緩存穿透問題,最常見的則是采用布隆過濾器,將所有可能存在的數(shù)據(jù)哈希到一個足夠大的bitmap中,一個一定不存在的數(shù)據(jù)會被 這個bitmap攔截掉,從而避免了對底層存儲系統(tǒng)的查詢壓力。另外也有一個更為簡單粗暴的方法(我們采用的就是這種),如果一個查詢返回的數(shù)據(jù)為空(不管是數(shù) 據(jù)不存在,還是系統(tǒng)故障),我們?nèi)匀话堰@個空結(jié)果進(jìn)行緩存,但它的過期時間會很短,最長不超過五分鐘。 六 Java6.1 Java 基礎(chǔ)知識
重載: 發(fā)生在同一個類中,方法名必須相同,參數(shù)類型不同、個數(shù)不同、順序不同,方法返回值和訪問修飾符可以不同,發(fā)生在編譯時。 重寫: 發(fā)生在父子類中,方法名、參數(shù)列表必須相同,返回值范圍小于等于父類,拋出的異常范圍小于等于父類,訪問修飾符范圍大于等于父類;如果父類方法訪問修飾符為 private 則子類就不能重寫該方法。
可變性 簡單的來說:String 類中使用 final 關(guān)鍵字字符數(shù)組保存字符串,private?final?char?value[],所以 String 對象是不可變的。而StringBuilder 與 StringBuffer 都繼承自 AbstractStringBuilder 類,在 AbstractStringBuilder 中也是使用字符數(shù)組保存字符串char[]value 但是沒有用 final 關(guān)鍵字修飾,所以這兩種對象都是可變的。 StringBuilder 與 StringBuffer 的構(gòu)造方法都是調(diào)用父類構(gòu)造方法也就是 AbstractStringBuilder 實現(xiàn)的,大家可以自行查閱源碼。 AbstractStringBuilder.java abstract class AbstractStringBuilder implements Appendable, CharSequence { char[] value; int count; AbstractStringBuilder() { } AbstractStringBuilder(int capacity) { value = new char[capacity]; }復(fù)制代碼 線程安全性 String 中的對象是不可變的,也就可以理解為常量,線程安全。AbstractStringBuilder 是 StringBuilder 與 StringBuffer 的公共父類,定義了一些字符串的基本操作,如 expandCapacity、append、insert、indexOf 等公共方法。StringBuffer 對方法加了同步鎖或者對調(diào)用的方法加了同步鎖,所以是線程安全的。StringBuilder 并沒有對方法進(jìn)行加同步鎖,所以是非線程安全的。 性能 每次對 String 類型進(jìn)行改變的時候,都會生成一個新的 String 對象,然后將指針指向新的 String 對象。StringBuffer 每次都會對 StringBuffer 對象本身進(jìn)行操作,而不是生成新的對象并改變對象引用。相同情況下使用 StirngBuilder 相比使用 StringBuffer 僅能獲得 10%~15% 左右的性能提升,但卻要冒多線程不安全的風(fēng)險。 對于三者使用的總結(jié):
裝箱:將基本類型用它們對應(yīng)的引用類型包裝起來; 拆箱:將包裝類型轉(zhuǎn)換為基本數(shù)據(jù)類型;
== : 它的作用是判斷兩個對象的地址是不是相等。即,判斷兩個對象是不是同一個對象。(基本數(shù)據(jù)類型==比較的是值,引用數(shù)據(jù)類型==比較的是內(nèi)存地址) equals() : 它的作用也是判斷兩個對象是否相等。但它一般有兩種使用情況:
舉個例子: public class test1 { public static void main(String[] args) { String a = new String('ab'); // a 為一個引用 String b = new String('ab'); // b為另一個引用,對象的內(nèi)容一樣 String aa = 'ab'; // 放在常量池中 String bb = 'ab'; // 從常量池中查找 if (aa == bb) // true System.out.println('aa==bb'); if (a == b) // false,非同一對象 System.out.println('a==b'); if (a.equals(b)) // true System.out.println('aEQb'); if (42 == 42.0) { // true System.out.println('true'); } }}復(fù)制代碼 說明:
final關(guān)鍵字主要用在三個地方:變量、方法、類。
6.2 Java 集合框架
補充:數(shù)據(jù)結(jié)構(gòu)基礎(chǔ)之雙向鏈表 雙向鏈表也叫雙鏈表,是鏈表的一種,它的每個數(shù)據(jù)結(jié)點中都有兩個指針,分別指向直接后繼和直接前驅(qū)。所以,從雙向鏈表中的任意一個結(jié)點開始,都可以很方便地訪問它的前驅(qū)結(jié)點和后繼結(jié)點。一般我們都構(gòu)造雙向循環(huán)鏈表,如下圖所示,同時下圖也是LinkedList 底層使用的是雙向循環(huán)鏈表數(shù)據(jù)結(jié)構(gòu)。
Vector類的所有方法都是同步的??梢杂蓛蓚€線程安全地訪問一個Vector對象、但是一個線程訪問Vector的話代碼要在同步操作上耗費大量的時間。 Arraylist不是同步的,所以在不需要保證線程安全時時建議使用Arraylist。
①JDK1.8之前 JDK1.8 之前 HashMap 底層是 數(shù)組和鏈表 結(jié)合在一起使用也就是 鏈表散列。HashMap 通過 key 的 hashCode 經(jīng)過擾動函數(shù)處理過后得到 hash 值,然后通過 (n - 1) & hash 判斷當(dāng)前元素存放的位置(這里的 n 指的時數(shù)組的長度),如果當(dāng)前位置存在元素的話,就判斷該元素與要存入的元素的 hash 值以及 key 是否相同,如果相同的話,直接覆蓋,不相同就通過拉鏈法解決沖突。 所謂擾動函數(shù)指的就是 HashMap 的 hash 方法。使用 hash 方法也就是擾動函數(shù)是為了防止一些實現(xiàn)比較差的 hashCode() 方法 換句話說使用擾動函數(shù)之后可以減少碰撞。 JDK 1.8 HashMap 的 hash 方法源碼: JDK 1.8 的 hash方法 相比于 JDK 1.7 hash 方法更加簡化,但是原理不變。 static final int hash(Object key) { int h; // key.hashCode():返回散列值也就是hashcode // ^ :按位異或 // >>>:無符號右移,忽略符號位,空位都以0補齊 return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }復(fù)制代碼 對比一下 JDK1.7的 HashMap 的 hash 方法源碼. static int hash(int h) { // This function ensures that hashCodes that differ only by // constant multiples at each bit position have a bounded // number of collisions (approximately 8 at default load factor). h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4);}復(fù)制代碼 相比于 JDK1.8 的 hash 方法 ,JDK 1.7 的 hash 方法的性能會稍差一點點,因為畢竟擾動了 4 次。 所謂 “拉鏈法” 就是:將鏈表和數(shù)組相結(jié)合。也就是說創(chuàng)建一個鏈表數(shù)組,數(shù)組中每一格就是一個鏈表。若遇到哈希沖突,則將沖突的值加到鏈表中即可。 ②JDK1.8之后 相比于之前的版本, JDK1.8之后在解決哈希沖突時有了較大的變化,當(dāng)鏈表長度大于閾值(默認(rèn)為8)時,將鏈表轉(zhuǎn)化為紅黑樹,以減少搜索時間。 TreeMap、TreeSet以及JDK1.8之后的HashMap底層都用到了紅黑樹。紅黑樹就是為了解決二叉查找樹的缺陷,因為二叉查找樹在某些情況下會退化成一個線性結(jié)構(gòu)。 設(shè)計模式 設(shè)計模式比較常見的就是讓你手寫一個單例模式(注意單例模式的幾種不同的實現(xiàn)方法)或者讓你說一下某個常見的設(shè)計模式在你的項目中是如何使用的,另外面試官還有可能問你抽象工廠和工廠方法模式的區(qū)別、工廠模式的思想這樣的問題。 建議把代理模式、觀察者模式、(抽象)工廠模式好好看一下,這三個設(shè)計模式也很重要。 七 數(shù)據(jù)結(jié)構(gòu)數(shù)據(jù)結(jié)構(gòu)比較常問的就是:二叉樹、紅黑樹(很可能讓你手繪一個紅黑樹出來哦!)、二叉查找樹(BST)、平衡二叉樹(Self-balancing binary search tree)、B-樹,B+樹與B*樹的優(yōu)缺點比較、 LSM 樹這些知識點。 數(shù)據(jù)結(jié)構(gòu)很重要,而且學(xué)起來也相對要難一些。建議學(xué)習(xí)數(shù)據(jù)結(jié)構(gòu)一定要循序漸進(jìn)的來,一步一個腳印的走好。一定要搞懂原理,最好自己能用代碼實現(xiàn)一遍。 八 算法常見的加密算法、排序算法都需要自己提前了解一下,排序算法最好自己能夠獨立手寫出來。 我覺得面試中最刺激、最有壓力或者說最有挑戰(zhàn)的一個環(huán)節(jié)就是手撕算法了。面試中大部分算法題目都是來自于Leetcode、劍指offer上面,建議大家可以每天擠出一點時間刷一下算法題。 九 SpringSpring一般是不可避免的,如果你的簡歷上注明了你會Spring Boot或者Spring Cloud的話,那么面試官也可能會同時問你這兩個技術(shù),比如他可能會問你springboot和spring的區(qū)別。 所以,一定要謹(jǐn)慎對待寫在簡歷上的東西,一定要對簡歷上的東西非常熟悉。 另外,AOP實現(xiàn)原理、動態(tài)代理和靜態(tài)代理、Spring IOC的初始化過程、IOC原理、自己怎么實現(xiàn)一個IOC容器? 這些東西都是經(jīng)常會被問到的。 9.1 Spring Bean 的作用域 9.2 Spring 事務(wù)中的隔離級別 TransactionDefinition 接口中定義了五個表示隔離級別的常量:
9.3 Spring 事務(wù)中的事務(wù)傳播行為 支持當(dāng)前事務(wù)的情況:
不支持當(dāng)前事務(wù)的情況:
其他情況:
9.4 AOP AOP思想的實現(xiàn)一般都是基于 代理模式 ,在JAVA中一般采用JDK動態(tài)代理模式,但是我們都知道,JDK動態(tài)代理模式只能代理接口而不能代理類。因此,Spring AOP 會這樣子來進(jìn)行切換,因為Spring AOP 同時支持 CGLIB、ASPECTJ、JDK動態(tài)代理。
9.5 IOC Spring IOC的初始化過程: 十 實際場景題我覺得實際場景題就是對你的知識運用能力以及思維能力的考察。建議大家在平時養(yǎng)成多思考問題的習(xí)慣,這樣面試的時候碰到這樣的問題就不至于慌了。另外,如果自己實在不會就給面試官委婉的說一下,面試官可能會給你提醒一下。切忌不懂裝懂,亂答一氣。 面試官可能會問你類似這樣的問題:①假設(shè)你要做一個銀行app,有可能碰到多個人同時向一個賬戶打錢的情況,有可能碰到什么問題,如何解決(鎖)②你是怎么保證你的代碼質(zhì)量和正確性的?③下單過程中是下訂單減庫存還是付款減庫存,分析一下兩者的優(yōu)劣;④同時給10萬個人發(fā)工資,怎么樣設(shè)計并發(fā)方案,能確保在1分鐘內(nèi)全部發(fā)完。⑤如果讓你設(shè)計xxx系統(tǒng)的話,你會如何設(shè)計。 寫在最后最后,再強調(diào)幾點:
另外,我個人覺得面試也像是一場全新的征程,失敗和勝利都是平常之事。所以,勸各位不要因為面試失敗而灰心、喪失斗志。也不要因為面試通過而沾沾自喜,等待你的將是更美好的未來,繼續(xù)加油! 初次之外,筆主也在這里給自己挖一個坑,關(guān)于 dubbo、zookeeper 等內(nèi)容我會在后續(xù)做一個系統(tǒng)總結(jié)。保證大家看了之后,一定有收獲! |
|