自我介紹:
文案:
參考:
面經總結:
項目中可以裝逼的:
1.彈幕娛樂:
直播是眼下最為火爆的行業,而彈幕無疑是直播平台中最流行、最重要的功能之一。本文将講述如何實作相容 PC 浏覽器和移動浏覽器的彈幕。
基本功能
并發與隊列
一般來說,彈幕資料會通過異步請求或 socket 消息傳到前端,這裡會存在一個隐患——資料量可能非常大。如果一收到彈幕資料就馬上渲染出來,在量大的時候:
- 顯示區域不足以放置這麼多的彈幕,彈幕會堆疊在一起;
- 渲染過程會占用大量 CPU 資源,導緻頁面卡頓。
是以在接收和渲染資料之間,要引入隊列做緩沖。把收到的彈幕資料都存入數組(即下文代碼中的 this._queue),再通過輪詢該數組,把彈幕逐條渲染出來:
彈幕的滾動
彈幕的滾動本質上是位移動畫,從顯示區域的右側移動到左側。前端實作位移動畫有兩種方案——DOM 和 canvas。
DOM 方案實作的動畫較為流暢,且一些特殊效果(如文字陰影)較容易實作(隻要在 CSS 中設定對應的屬性即可)。 Canvas 方案的動畫流暢度要差一些,要做特殊效果也不那麼容易,但是它在 CPU 占用上有優勢。
本文将以 DOM 方案實作彈幕的滾動,并通過 CSS 的 transition 和 transform 來實作動畫,這樣可以利用浏覽器渲染過程中的「合成層」機制(有興趣可以查閱
這篇文章),提高性能。彈幕滾動的示例代碼如下:
彈幕的渲染
在 DOM 方案下,每條彈幕對應一個 HTML 元素,把元素的樣式都設定好之後,就可以添加到 HTML 文檔裡面:
由于元素的 left 樣式值設定為 100%,是以它在顯示區域之外。這樣可以在使用者看到這條彈幕之前,做一些“暗箱操作”,包括擷取彈幕的尺寸、占用的軌道數、總位移、位移時間、位移速度。接下來的問題是,要把彈幕顯示在哪個位置呢?
首先,彈幕的文字大小不一定一緻,進而占用的高度也不盡相同。為了能充分利用顯示區域的空間,我們可以把顯示區域劃分為多行,一行即為一條軌道。一條彈幕至少占用一條軌道。而存儲結構方面,可以用二維數組記錄每條軌道中存在的彈幕。下圖是彈幕占用軌道及其對應存儲結構的一個例子:

其次,要防止彈幕重疊。原理其實非常簡單,請看下面這題數學題。假設有起點站、終點站和一條軌道,列車都以勻速運動方式從起點開到終點。列車 A 先發車,請問:如果在某個時刻,列車 B 發車的話,會不會在列車 A 完全進站之前撞上列車 A?
聰明的你可能已經發現,這裡的軌道所對應的就是彈幕顯示區域裡面的一行,列車對應的就是彈幕。解題之前,先過一下已知量:
- 路程 S,對應顯示區域的寬度; - 兩車長度 la 和 lb,對應彈幕的寬度;
- 兩車速度 va 和 vb,已經計算出來了;
- 前車已行走距離 sa,即彈幕元素目前的位置,可以通過讀取樣式值擷取。
那在什麼情況下,兩車不會相撞呢?
- 其一,如果列車 A 沒有完全出站(已行走距離小于車長),則列車 B 不具備發車條件;
- 其二,如果列車 B 的速度小于等于列車 A 的速度,由于 A 先發車,這是肯定撞不上的;
- 其三,如果列車 B 的速度大于列車 A 的速度,那就要看兩者的速度差了:
-
- 列車 A 追上列車 B 所需時間 tba = (sa - la) / (vb - va);
- 列車 A 完全到站所需時間 tad = (s + la - sa) / va; - tba > tad 時,兩車不會撞上。
有了理論支撐,就可以編寫對應的代碼了。
class Danmaku {
// 省略 N 行代碼...
// 把彈幕資料放置到合适的軌道
_addToTrack(data) {
// 單條軌道
let track;
// 軌道的最後一項彈幕資料
let lastItem;
// 彈幕已經走的路程
let distance;
// 彈幕資料最終坐落的軌道索引
// 有些彈幕會占多條軌道,是以 y 是個數組
let y = [];
for (let i = 0; i < this._tracks.length; i++) {
track = this._tracks[i];
if (track.length) {
// 軌道被占用,要計算是否會重疊
// 隻需要跟軌道最後一條彈幕比較即可
lastItem = track[track.length - 1];
// 擷取已滾動距離(即目前的 translateX)
distance = -getTranslateX(lastItem.node);
// 計算最後一條彈幕全部消失前,是否會與新增彈幕重疊
// (對應數學題分析中的三種情況)
// 如果不會重疊,則可以使用目前軌道
if (
(distance > lastItem.width) &&
(
(data.rollSpeed <= lastItem.rollSpeed) ||
((distance - lastItem.width) / (data.rollSpeed - lastItem.rollSpeed) >
(this._totalWidth + lastItem.width - distance) / lastItem.rollSpeed)
)
) {
y.push(i);
} else {
y = [];
}
} else {
// 軌道未被占用
y.push(i);
}
// 有足夠的軌道可以用時,就可以新增彈幕了,否則等下一次輪詢
if (y.length >= data.useTracks) {
data.y = y;
y.forEach((i) => {
this._tracks[i].push(data);
});
break;
}
}
}
// 省略 N 行代碼...
}
隻要彈幕成功入軌(data.y 存在),就可以顯示在對應的位置并執行動畫了:
class Danmaku {
// 省略 N 行代碼...
_renderToDOM {
const data = this._queue[0];
let node = data.node;
if (!data.node) {
// 省略 N 行代碼...
}
this._addToTrack();
if (data.y) {
this._queue.shift();
// 軌道對應的 top 值
node.style.top = data.y[0] * this._trackSize + 'px';
// 動畫參數
node.style.transition = `transform ${data.rollTime}s linear`;
node.style.transform = `translateX(-${data.totalDistance}px)`;
// 動畫結束後移除
node.addEventListener('transitionend', () => {
this._removeFromTrack(data.y, data.autoId);
this._container.removeChild(node);
}, false);
}
}
// 省略 N 行代碼...
}
至此,渲染流程結束,此時的彈幕效果見
此 demo 頁。為了能夠讓大家看清楚渲染過程中的“暗箱操作”,demo 頁中會把顯示區域以外的部分也展示出來。
完善
上一節已經實作了彈幕的基本功能,但仍有一些細節需要完善。
跳過彈幕
仔細觀察上文的
彈幕 demo可以發現,同一條軌道内,彈幕之間的距離偏大。而該 demo 中,隊列輪詢的間隔為 150ms,理應不會有這麼大的間距。
回顧渲染的代碼可以發現,該流程總是先檢查第一條彈幕能不能入軌,倘若不能,那後續的彈幕都會被堵塞,進而導緻彈幕密集度不足。然而,每條彈幕的長度、速度等參數不盡相同,第一條彈幕不具備入軌條件不代表後續的彈幕都不具備。是以,在單次渲染過程中,如果第一條彈幕還不能入軌,可以往後多嘗試幾條。
相關的代碼改動也不大,隻要加個循環就行了:
_renderToDOM() {
// 根據軌道數量每次處理一定數量的彈幕資料。數量越大,彈幕越密集,CPU 占用越高
let count = Math.floor(totalTracks / 3), i;
while (count && i < this._queue.length) {
const data = this._queue[i];
// 省略 N 行代碼...
if (data.y) {
this._queue.splice(i, 1);
// 省略 N 行代碼...
} else {
i++;
}
count--;
}
}
改動後的效果見
,可以看到彈幕密集程度有明顯改善。
彈幕已滾動路程的擷取
防重疊檢測是彈幕渲染過程中執行得最為頻繁的部分,是以其優化顯得特别重要。JavaScript 性能優化的關鍵是:盡可能避免 DOM 操作。而整個防重疊檢測算法中涉及的唯一一處 DOM 操作,就是彈幕已滾動路程的擷取:
distance = -getTranslateX(data.node);
而實際上,這個路程不一定要通過讀取目前樣式值來擷取。因為在勻速運動的情況下,路程=速度×時間,速度是已知的,而時間嘛,隻需要用目前時間減去開始時間就可以得出。先記錄開始時間:
_renderToDOM() {
// 根據軌道數量每次處理一定數量的彈幕資料。數量越大,彈幕越密集,CPU 占用越高
let count = Math.floor(totalTracks / 3), i;
while (count && i < this._queue.length) {
const data = this._queue[i];
// 省略 N 行代碼...
if (data.y) {
this._queue.splice(i, 1);
// 省略 N 行代碼...
node.addEventListener('transitionstart', () => {
data.startTime = Date.now();
}, false);
// 從設定動畫樣式到動畫開始有一定的時間差,是以加上 80 毫秒
data.startTime = Date.now() + 80;
} else {
i++;
}
count--;
}
}
注意,這裡設定了兩次開始時間,一次是在設定動畫樣式、綁定事件之後,另一次是在 transitionstart 事件中。理論上隻需要後者即可。之是以加上前者,還是因為相容性問題——并不是所有浏覽器都支援 transitionstart 事件。
然後,擷取彈幕已滾動路程的代碼就可以優化成:
distance = data.rollSpeed * (Date.now() - data.startTime) / 1000;
别看這個改動很小,前後隻涉及 5 行代碼,但效果是立竿見影的(見
):
浏覽器 | getTranslateX | 勻速公式計算 |
---|---|---|
Chrome | CPU 16%~20% | CPU 13%~16% |
Firefox | 能耗影響 3 | 能耗影響 0.75 |
Safari | CPU 8%~10% | CPU 3%~5% IE |
暫停和恢複
首先要解釋一下為什麼要做暫停和恢複,主要是兩個方面的考慮。
第一個考慮是浏覽器的相容問題。彈幕渲染流程會頻繁調用到 JS 的 setTimeout 以及 CSS 的 transition,如果把目前标簽頁切到背景(浏覽器最小化或切換到其他标簽頁),兩者會有什麼變化呢?請看測試結果:
setTimeout | transition | |
---|---|---|
Chrome/Edge | 延遲加大 | 如果動畫未開始,則等待标簽頁切到前台後才開始 |
Safari/IE 11 | 正常 | |
可見,不同浏覽器的處理方式不盡相同。而從實際場景上考慮,标簽頁切到背景之後,即使渲染彈幕使用者也看不見,白白消耗硬體資源。索性引入一個機制:标簽頁切到背景,則彈幕暫停,切到前台再恢複:
let hiddenProp, visibilityChangeEvent;if (typeof document.hidden !== 'undefined') { hiddenProp = 'hidden'; visibilityChangeEvent = 'visibilitychange';} else if (typeof document.msHidden !== 'undefined') { hiddenProp = 'msHidden'; visibilityChangeEvent = 'msvisibilitychange';} else if (typeof document.webkitHidden !== 'undefined') { hiddenProp = 'webkitHidden'; visibilityChangeEvent = 'webkitvisibilitychange';}document.addEventListener(visibilityChangeEvent, () => { if (document[hiddenProp]) { this.pause(); } else { // 必須異步執行,否則恢複後動畫速度可能會加快,進而導緻彈幕消失或重疊,原因不明 this._resumeTimer = setTimeout(() => { this.resume(); }, 200); }}, false);
先看下暫停滾動的主要代碼(注意已滾動路程 rolledDistance,将用于恢複播放和防重疊):
this._eachDanmakuNode((node, y, id) => { const data = this._findData(y, id); if (data) { // 擷取已滾動距離 data.rolledDistance = -getTranslateX(node); // 移除動畫,計算出彈幕所在的位置,固定樣式 node.style.transition = ''; node.style.transform = `translateX(-${data.rolledDistance}px)`; }});
接下來是恢複滾動的主要代碼:
this._eachDanmakuNode((node, y, id) => { const data = this._findData(y, id); if (data) { // 重新計算滾完剩餘距離需要多少時間 data.rollTime = (data.totalDistance - data.rolledDistance) / data.rollSpeed; data.startTime = Date.now(); node.style.transition = `transform ${data.rollTime}s linear`; node.style.transform = `translateX(-${data.totalDistance}px)`; }});this._render();
防重疊的計算公式也需要修改:
// 新增了 lastItem.rolledDistancedistance = lastItem.rolledDistance + lastItem.rollSpeed * (now - lastItem.startTime) / 1000;
修改後效果見
,可以留意切換浏覽器标簽頁後的效果并與前面幾個 demo 對比。
丢棄排隊時間過長的彈幕
彈幕并發量大時,隊列中的彈幕資料會非常多,而在防重疊機制下,一屏能顯示的彈幕是有限的。這就會出現“供過于求”,導緻彈幕“滞銷”,使用者看到的彈幕将不再“新鮮”(比如視訊已經播到第 10 分鐘,但還在顯示第 3 分鐘時發的彈幕)。
為了應對這種情況,要引入丢棄機制,如果彈幕的庫存比較多,而且這批庫存已經放了很久,就扔掉它。相關代碼改動如下:
while (count && i < this._queue.length) { const data = this._queue[i]; let node = data.node; if (!node) { if (this._queue.length > this._tracks.length * 2 && Date.now() - data.timestamp > 5000 ) { this._queue.splice(i, 1); continue; } } // ...}
2.顯示計數器;
要求:
1.通過首頁來通路的時候開始計時,為使用者生成id(全局唯一)
2.每五分鐘動态模拟登陸人數,并在0點重置.在同一時刻同一個使用者通路時需要和計數器實作一緻性
3.不适用消息中間件,僅用資料庫的方式實作,
3.實時上升熱點(仿照微網誌的排行榜)
更具點贊和好評評論數來對喜愛的景點經行排名
其他位元組的面經:
介紹一些web伺服器的 項目 ,都做了什麼
介紹select,poll,epoll的差別,特點
說一下升序 連結清單 的實作思路,有什麼優化的地方嗎
程序和線程的差別,分别占有什麼
協程了解嗎,協程的主要作用?協程有什麼優點
戶自己控制切換的時機,不再需要陷入系統的核心态。
協程的執行效率非常高。因為子程式切換不是線程切換,而是由程式自身控制。是以,沒有線程切換的開銷,和多線程相比,線程數量越多,相同數量的協程展現出的優勢越明顯。
不需要多線程的鎖機制。
說一下程序間通信的方式?詳細說一下管道和消息隊列
信号、管道、消息隊列、共享記憶體 **
\1. 管道pipe:管道是一種半雙工的通信方式,資料隻能單向流動,而且隻能在具有親緣關系的程序間使用。程序的親緣關系通常是指父子程序關系。
\2. 命名管道FIFO:有名管道也是半雙工的通信方式,但是它允許無親緣關系程序間的通信。
\4. 消息隊列MessageQueue:消息隊列是由消息的連結清單,存放在核心中并由消息隊列辨別符辨別。消息隊列克服了信号傳遞資訊少、管道隻能承載無格式位元組流以及緩沖區大小受限等缺點。
\5. 共享存儲SharedMemory:共享記憶體就是映射一段能被其他程序所通路的記憶體,這段共享記憶體由一個程序建立,但多個程序都可以通路。共享記憶體是最快的 IPC 方式,它是針對其他程序間通信方式運作效率低而專門設計的。它往往與其他通信機制,如信号兩,配合使用,來實作程序間的同步和通信。
\6. 信号量Semaphore:信号量是一個計數器,可以用來控制多個程序對共享資源的通路。它常作為一種鎖機制,防止某程序正在通路共享資源時,其他程序也通路該資源。是以,主要作為程序間以及同一程序内不同線程之間的同步手段。
\7. 套接字Socket:套解口也是一種程序間通信機制,與其他通信機制不同的是,它可用于不同及其間的程序通信。
\8. 信号 ( sinal ) : 信号是一種比較複雜的通信方式,用于通知接收程序某個事件已經發生。
說一下TCP三向交握,能否兩次握手
兩次握手會發生什麼?
用戶端單方面認為通
服務端不同
三次握手有什麼不好的地方嗎?有點懵,面試官提示我DDOS攻擊角度
(SYN洪泛)
timewait狀态,是幹嘛的
1.收到最後應答的ack
2.之前存活的封包死亡。
等到确認
說一下輸入url之後的全過程
1.dns解析為ip(dns協定)
2.發送HTTP請求: tcp建立連接配接,ip傳輸請求,http協定,arp協定将ip轉mac ,ospf負責最近轉發
3.接受http響應
4.解析渲染
說一下OSI七層參考模型,HTTP在那一層,dns在哪一層,tcp、udp在哪一層。
路由器工作在哪一層
說一下mysql和innodb和myisam的差別
mysql索引的存儲方式
b,b+
mysql的最左字首法則?舉了個例子問我能夠比對上嗎
select id,name,stuNo( select )
講一下事務的四種隔離級别,他們分别解決了什麼問題
說一下髒讀,不可重複讀,幻讀(插入)
說一下mysql的預設隔離級别?可重複讀 通過什麼實作的?mvcc
講一下mvcc的了解,如何實作的mvcc(undo,版本,鎖)
講一下mysql有哪幾種鎖,講一下間隙所間隙鎖都加在哪了
講一下mvcc的事務id,他是怎麼判斷哪些事務能夠通路到哪些版本的
熟悉 redis 嗎?講一下 redis 的資料類型
了解常用的消息隊列嗎,kafka之類的?
能來實習嗎?
翻轉 連結清單 區間元素
二:
一面
JAVA的堆和棧的差別
棧記憶體儲的是局部變量,堆記憶體儲的是實體
棧記憶體存儲的是局部變量而堆記憶體存儲的是實體
棧記憶體的更新速度要快于堆記憶體,因為局部變量的生命周期很短
棧記憶體存放的變量生命周期一旦結束就會被釋放,而堆記憶體存放的實體會被垃圾回收機制不定時的回收
2.JAVA的GC垃圾回收機制
垃圾回收機制就是JVM利用一些列算法對記憶體進行管理
回收記憶體空間,删除無用執行個體(沒有被引用的對象)
自動進行,減少了記憶體溢出.但同時也犧牲了一定的性能
優化:将不用的變量和指針置位null
3.垃圾回收機制和調用 System.gc()的差別?
gc函數的作用是程式員提醒虛拟機,希望進行一次垃圾回收,但是虛拟機并不保證垃圾回收一定會進行。什麼時候進行依然取決于虛拟機
4.什麼是多态
一個程式中存在多的同名的不同方法,包括
通過子類對父類的覆寫來實作(重寫)
通過在一個類中方法的重載來實作(重載)
向上繼承:通過子類對象轉化為父類對象來實作
5.重寫和重載的差別
重寫:子類和父類的一種關系,子類繼承父類的方法
重載:同一個類中(包括父類)有多個同名的不同方法
6.程序和線程的差別,線程獨有什麼?
程序是程式的一次執行,程序包含線程,線程是程序中的代碼段。
程序通過線程來實作同時運作不同段的代碼。
線程的記憶體範圍不允許越過程序。
線程共有的部分:方法區和堆
線程獨有的部分:虛拟機棧,本地方法棧,程式計數器
7.你在項目中做過哪些性能優化
代碼優化:
盡量重用對象,出現字元串連接配接時應該使用StringBuilder/StringBuffer代替
盡量使用局部變量(用過釋放)
将常量聲明為static final
字元串變量和字元串常量equals的時候将字元串常量寫在前面
資料庫優化:建立一個具有良好表結構的資料庫
8.如何優化頁面卡頓
拆分代碼段
盡量減少使用layout
簡化DOM結構
9.Handler消息機制
将耗時的操作放在子線程中處理,handler用來在主線程和子線程中傳遞資訊
10.Android自定義View
View是用于建立互動式使用者界面元件(按鈕、文本等)的基礎類
View是所有控件(包括ViewGroup)的父類,它裡面有一些常見的方法,如果我們要自定義View,最簡單的隻需要重寫onDraw(android.graphics.Canvas)即可。
11.常用的開源架構及其原理
Spring架構
12.MVC模式與MVP模式
13.MVC和MVVC怎麼了解,開發中怎麼用的
14.HTTP常見的請求方式及主要操作
15.HTTP和HTTPS
14.TCP的三次握手是什麼?四次揮手
15.用到過的加密算法有哪些?
CA(RSA非對稱的) MD5
16.設計模式有哪些?
17.如何實作AOP,用到什麼技術?
18.JAVA自帶的代理類和cglib有啥不同?
19.消息隊列的作用和場景
用戶端:
作者:位元組研發内推
連結:
https://www.nowcoder.com/discuss/712069?type=post&order=time&pos=&page=1&ncTraceId=&channel=-1&source_id=search_post_nctrack 來源:牛客網
1.程式設計基礎知識:java語言相關, 同步,互斥,單例,線程池等等
2.計算機基礎知識: 堆,棧,編譯,運作,程序,線程,作業系統,IO 等等
3.資料結構&
算法4.網絡: 7層模型, tcp/ip/ http/https , 要融彙貫通,結合實際場景能了解整個流程。
5.資料庫:有的面試官會問,有的不會問, 建議也準備一下這部分,
用戶端開發一般不會聊特别深.
用戶端大牛:
作者:0118101437 https://www.nowcoder.com/discuss/642137?type=post&order=time&pos=&page=1&ncTraceId=&channel=-1&source_id=search_post_nctrack
本人雙非非科班大三
----------------------------------------------------------------
一面:
自我介紹
介紹你參與比較多的[項目]()
說說解決的問題
聊聊加密[算法](),你在[項目]()中怎麼用的?
加密[算法]()你了解多深(介紹了MD5,非對稱加密DES,AES)(我從加密[算法]()提了RSA,CA機構加密的就是用的RSA)
怎麼保證非對稱加密的安全性(原話)
如果讓你設計你會怎樣做?
TCP和UDP的差別(後面所有話題都由這個衍生)
- 我提了可靠傳輸的問題(怎麼保證可靠?你說的幾個點怎麼實作?如果我發了幾個包但是丢了後面幾個怎麼辦?重傳?計數器)
- 效率問題
- HTTP都是基于TCP的嗎?(不是,HTTP3.0基于QUIC,介紹了QUIC的優化問題以及具體實作,我還提了http的擴充性,可以重寫傳輸層協定)
- 那HTTP是一個TCP建立連接配接以後就一個請求就斷開嗎?(不是,HTTP1.1開始支援長連接配接,隻要在字段collection設為[keep]()-alive)
- 那就是連接配接以後就不會斷了?
- 如果讓你設計,你會怎樣控制這個連接配接的開閉
- http是一個線程隻能處理一個連接配接嗎?(2.0的多路複用)
- 假設我有多個連接配接但是那些資料一起湧進來怎麼辦?
- 多路複用讓你設計你會怎樣設計?
- 怎麼控制TCP連接配接數?(他提了個設定最大TCP連接配接數)
- 如果讓你做你會怎樣去斷開沒用的連接配接(我提了可以用OS的排程[算法](),設計一個類似LRU的模型)
- 能介紹一下LRU嗎?(我提了Java的實作妄想讓他讓我寫LRU)
- 具體怎麼做?
- 你怎麼知道斷開哪個連接配接?(如果有新連接配接進來,直接斷開雙向[連結清單]()最後一個連接配接,記錄每個連接配接的占用然後放hashmap裡可以達到O(1)查詢)
- Hashmap怎樣get
- 平常用的什麼語言?
Java幾個修飾符(protect public那些)
final的作用
[算法]():無序數組三數之和,輸出下标
- [排序]()過後下标會亂你怎麼處理?(hashmap記錄原來的索引)
- 那假設我有重複元素怎麼辦?
- value用arraylist存放?(我寫了個大概
- [兩數之和]()會嗎?(動态Hashmap)
- [兩數之和]()提了幾個特殊用例 答上來了
-
優化三數之和[算法]()
反問:評價一下我的這次面試給意見
-
基礎知識挺好,資料結構計算機網絡都學得不錯,[算法]()能力還可以
建議:多去實習,最好來位元組實習,一兩天會有回報
隔天下午收到預約一周後二面的電話
--------------------------------------------------------------------------
二面:
程序和線程有啥差別?(圍繞這塊問了一會作業系統)
寫DCL
請你設計一個系統,将長域名轉化為短連結,中途的處理[算法]()和解析流程,大概怎麼解析
[算法]():[把一個奇數位升序偶數位降序的單連結清單變成升序的,空間複雜度O(1)]()
隔天下午收到預約一周後三面的電話
---------------------------------------------------------------------------
三面,最讓我想不明白的一面......
自我介紹
(這裡面試官說一二面問基礎為主,面試官回報不錯,三面就不問基礎了)
[算法題]():N叉樹,求走M步走到節點x的機率,如果到了目的節點但是步數沒用完算走不到,隻有走到葉子節點而且還沒用完步數的情況才能原地走
JavaGC
[項目]()相關
能實習多久?
在學校成績如何
作者:Ctz
https://www.nowcoder.com/discuss/739501?type=post&order=time&pos=&page=1&ncTraceId=&channel=-1&source_id=search_post_nctrack部分借鑒
2.OS
(1)頁面置換[算法]()?(FIFO、LRU、LFU)
(2)LRU怎麼實作的?(雙向[連結清單]() + [哈希表]())
3.網絡
(1)[用戶端]()請求資源,如何實作斷點續傳?(使用HTTP請求的if-range字段+range字段)
(2)如果資源發生變化,怎麼判斷?(時間戳或ETAG)
(3)HTTP劫持?(不會)
貼小廣告的
事前加密https
事中規避
事後屏蔽
(4)DNS劫持?(用HTTPDNS解決)
輸入百度進入渣渣灰
用httpdns解決
(5)通過HTTPDNS請求域名時,是使用IP還是域名?
将域名替換為ip
(6)如果用IP去請求,如何去實作容災?
(7)HTTP1.0、2.0、3.0的差別?
(8)HTTP3.0用的UDP怎麼做到可靠的?(自己實作了可靠傳輸的機制,例如流量控制、重傳等)
(9)流量控制時怎麼實作的?
(10)假如讓你設計一個類似微信的離線消息機制,例如接收方離線,發送方給他發消息,如何實作?(伺服器緩存發送方的消息,當接收方上線時通過請求來擷取離線消息)
4.DB
(1)索引優缺點和原理?
(2)什麼時候索引會失效?
5.iOS
(1)UITableView的用途、如何展示資料、複用政策?(給幾分鐘時間線上搜相關資料,然後回答)
6.代碼
(1)(Leetcode3)給一個字元串,求無重複字元的最長子串長度
(2)(Leetcode1)[兩數之和]()
https://www.nowcoder.com/discuss/725266?source_id=profile_create_nctrack&channel=-12.網絡
(1)TCP三向交握中,SYN、ACK、seq、ack四個字段的含義?
(2)HTTP和HTTPS的差別?
(3)HTTPS密鑰協商的過程?
(4)如何實作HTTP長連接配接?(1.0裡Connection:Keep-Alive,1.1裡預設開啟長連接配接)
(5)浏覽器斷點續傳,分段下載下傳時,HTTP用的哪個字段?(不會,答了個content-length字段,懂得大佬麻煩說一下)
3.OS
(1)程序和線程的差別?
(2)程序間通信機制?
(3)Linux如何建立程序?(fork,寫時複制)
(4)什麼是僵屍程序?如何處理?(wait/waitpid)
(5)線程同步的方式?(互斥鎖、自旋鎖、讀寫鎖、條件變量、信号量)
(6)互斥鎖和自旋鎖的差別?
(7)死鎖的四個必要條件?
(8)如何避免死鎖?(銀行家[算法]())
(9)線程池如何設計的?(線程池、任務隊列、互斥鎖、條件變量)
(10)線程池裡面的線程是如何實作執行完函數後不結束的(while)
(11)那會不會造成CPU空轉的情況呢?(不會,條件變量,條件不滿足時會自動釋放鎖,然後進入阻塞)
(1)從Person表中查詢Age在18-25之間的資料,根據身高Tall由高到低[排序]()
(2)接着(1)增加 Name 包含A(查詢包含A的Name)
5.代碼
(1)如何判斷兩個[連結清單]()是否相交?
(2)(Leetcode162)一組資料,不存在num[i] = num[i+1],傳回任意一個峰值。峰值:左右都比該值小則該值稱為峰值。
數組測試樣例:
(1) 1232143
(2) 321
(3) 123
最近的部分借鑒::
作者:小辣雞0.0
https://www.nowcoder.com/discuss/735790?type=post&order=time&pos=&page=1&ncTraceId=&channel=-1&source_id=search_post_nctrack- 程序與線程的差別
- 程序之間的通信方式
- 使用者态和核心态的差別
- 守護線程是什麼
- 産生死鎖的原因
- 1.
- 2.
- 3
- 4
- TCP和UDP的差別
- HTTP是基于哪個協定的
- 1.0、1.1、2.0、3.0
- HashMap、HashTable、ConcurrentHashMap底層資料結構,底層原理
- Java的引用類型,強引用、軟引用、弱引用、虛引用的差別
- JVM垃圾回收[算法]()
- GC ROOT有哪些對象
- Java就問這麼多吧(太熟練了)
- MySQL索引為什麼要有B+ Tree
- [算法]():判斷[二叉樹]()和為n的路徑是否存在
二面:(1h 30min)
- MySQL事務特點
- 索引何時失效
- MySQL如何抵抗高并發(詳細)
- 如何設計高并發的系統(詳細)
- 流量非常大的頁面,如何統計PV、UV,保證高并發不出問題(詳細)
- 如何優化SQL
- Java中抽象類和接口差別
- Java中類加載器有哪些
- JVM雙親委派機制
- Java中如何實作C++中的多重繼承
- Java常見的垃圾回收[算法]()
- Java反射機制
- 為啥C++可以多繼承而Java隻有單繼承
- 為什麼String在Java中是不可變的
- 為什麼要有字元串常量池,為什麼要這麼設計
- 為什麼Java不支援運算符重載
- 為什麼Char數組比String數組更适合存儲密碼
- 你對并發和并行的了解
- [源碼]()如何變成可執行檔案
- Java類加載機制 詳細
- 什麼是守護程序、僵屍程序、孤兒程序
- 說一下常見的記憶體配置設定錯誤
- 記憶體已經釋放了,但是繼續使用這個區域的記憶體,會出現什麼問題
- 說一下什麼是緩沖區溢出,它有什麼危害
- 智力題:10分鐘等車的機率是99%,5分鐘等車的機率是多少?
- 智力題:A和B輪流丢硬币,誰先丢出正面誰赢,A赢的機率是多少
- [算法題]()手撕:兩個棧實作隊列,使用線程安全實作(Synchornized 和 ReentrantLock 都實作出來)
- 先手撕一個線程不安全的
- 再撕一個Synchronized實作的
- Synchronized底層原理
- 鎖更新機制
- 再撕一個ReentrantLock實作的
- [算法題]()口述思路:投n個骰子,最後計算和為m,有多少種投法(DP)
三面:(1h)
- 學校成績怎麼樣
- 之前[項目]()和實習都是學習大資料,對[用戶端]()有什麼了解嗎
- 希望轉[用戶端]()嗎
- 深挖[項目]()
- Kafka資料的同步,有了解嗎
- Zoo[keep]()er在[項目]()中做什麼用的
- Zoo[keep]()er叢集選舉[算法]()
- 還了解哪些分布式協調[算法]()(2PC Paxos)?
- Python腳本 a.py 檔案 生成的a.pyc是什麼(不知道)
- javac具體怎麼做的(不知道)
- 編譯原理是沒學過嗎 (沒學過)
- 智力題:1000個蘋果放進 10個盒子,如何配置設定可以讓任何一個人想取多少蘋果就可以通過幾個盒子加起來拿到
- 場景[算法](): 最大的 Top K 變形題,維護小頂堆實作(讨論 10min,寫代碼 20min)
- 反問
較全的借鑒:
作者:Darreni
https://www.nowcoder.com/discuss/700214?type=post&order=time&pos=&page=1&ncTraceId=&channel=-1&source_id=search_post_nctrack- 給出[二叉樹]()前序、中序周遊,寫出樹的後序周遊
- 作業系統:記憶體管理、虛拟記憶體
- Java記憶體管理:分區、JMM、垃圾回收
- 類的加載:靜态變量什麼時候被初始化
- 登陸使用者資料表的設計,考慮安全性、性能、擴充性,什麼是加鹽
其他忘記了...
最後還有一道原創[算法]()(不得不吐槽一下,之前面位元組實習也是原創[算法](),看别人都是[牛客]()網位元組題庫原題羨慕~)
二面
- 介紹[項目]()
- 作業系統怎麼劃分記憶體?堆、棧有什麼差別?
- 操作堆和棧誰比較快?為什麼?
- 為什麼随機IO的速度會比随機IO慢?
- 如何解決電商系統的常見問題?
- 如何解決緩存雪崩、緩存穿透?
- Rand5 實作 Rand7
- 什麼是核心态和使用者态,陷入核心态的方式?什麼是中斷?
兩道[算法](),驗證IP、O(1)
空間翻轉URL(
www.bytedance.com->com.bytedance.www)
三面
1.狂問[項目](),介紹[項目]()難點,抓住[項目]()中的某些點,深入的問相關的知識~
2.單例模式,沒有列舉直接寫了雙重校驗鎖,引起了面試官的不滿,問我是不是單例都要這樣實作。
這一面基本在問[項目]()相關的東西,具體問啥忘記了....
四面
這一面是我經曆過的最難的一次面試(難度感覺比[騰訊]()、位元組後端的面試都難,本以為[用戶端]()會比較友好...)全程重點在作業系統和計算機網絡,給一個具體的場景,分析原理
- 完整描述一次複制操作的過程(面試官強調複制不隻是檔案複制),不是簡單的用Java實作檔案複制,而是描述這次複制操作中的整個過程(作業系統做了什麼?怎麼做?什麼系統調用?有什麼差別?)
- UDP具體應用場景?為什麼DNS用UDP,UDP很脆弱,用UDP會出現什麼問題?具體舉個例子?具體描述DNS劫持的過程
- Http2.0多路複用具體是怎麼實作的?(我說了二進制分幀,然後有流辨別符,是以實作多路複用,面試官不滿,表示他想知道的是具體流程)
- 整個網絡鍊路的性能名額分析,在5層協定的基礎上,逐層分析每一層有那些影響網絡性能的名額,如何提高性能(這個題真的難,我怕了~)
-
開放性題目,如何完善抖音推薦[算法](),讓推薦更加準确?
還有其他一些具體的場景,忘記了...基本上都是關于計網和作業系統的某個場景,描述整個流程,如何實作,所背的Http、TCP全套八股文一點沒用上,一度以為我已經涼了~
總結
總體的面試體驗還是很不錯,就是别人都是三面,然後我可能表現不好被加面,然後四面被暴捶。總的來說,八股文并不是很多,挺多背了很久的八股文完全沒用上。面試前,基本翻了所有位元組[用戶端]()的[面經](),感覺實際面試跟[牛客]()網上的[面經]()内容差距較大,感覺看到的[面經]()多數以八股文為主~ 還是不要過度相信[面經]()。
最後想問問,[用戶端]()真的有那麼勸退嗎 ~ 挂後端被[用戶端]()撈,還要不要接着卷卷其他廠的後端??
還有一個:
作者:thyxsl
https://www.nowcoder.com/discuss/714525?type=post&order=time&pos=&page=1&ncTraceId=&channel=-1&source_id=search_post_nctrack一面 (1小時)
- 手撕[二叉樹]()Z周遊(力扣)
- 編譯原理
- JVM回收[算法]()
- CPP如何實作垃圾回收
- UDP與TCP,以及UDP如何保證可靠傳輸(quic)
- 介紹[項目]()技術棧,深挖[項目]()
- 強引用弱引用軟引用虛引用對比
- 标記對象可回收[算法]()
- JVM記憶體模型
- JVM類加載方式:雙親委派詳細介紹過程
- 介紹[google]() inject(依賴注入架構)及依賴注入的優點
- LRU[算法]()及其實作
- LinkedList HashMap 底層原理
二面(1小時)
- 遞歸的處理過程
- 設計模式(單例模式,建造者模式,代理模式,工程模式等)
- [項目]()技術棧,深挖[項目]()
- 常用[算法]()對比(DP, 回溯, 貪心,分支限界)
- 遞歸寫個斐波那契
- [項目]()中遇到的難題
- 數組[連結清單]()差別
- 頁面置換[算法](),内部碎片産生
- 程序線程
- 設計作業系統需要實作哪些功能:程序管理,檔案管理,記憶體管理,IO管理等
- 未來發展移動端發展趨勢
三面(1.5小時)
- [項目]()中使用的Hash[算法]()生成token過程
- HashMap 結構,擴容,負載因子,putVal過程
- 手撕删除[連結清單]()中所有重複結點(遞歸寫出來了,疊代沒有)
- 介紹MQ(RabbitMQ與Kafka)
- 介紹微服務
- 是否願意轉[用戶端]()
- hashTable是否線程安全,如何實作線程安全
- 場景題: 不使用消息隊列,如何實作服務之間資料通信(Http長輪詢 和 類似于實作釋出訂閱機制的中間件如[redis]())
- [redis]() 與 MC 對比, 為何選在[redis]()
- 介紹小程式的優缺點
- 未來[職業規劃]()
同部門的:
作者:三七二十一°
https://www.nowcoder.com/discuss/752059?type=post&order=time&pos=&page=1&ncTraceId=&channel=-1&source_id=search_post_nctrack一面:
聊[項目]()
tcp在哪層,ip在哪層
tcp四次揮手為什麼四次
https握手過程【這裡答的不太好,面試官也一直扣,後面幫我糾正了下
頁置換[算法]()有哪些
belady現象了解嗎
程序線程的差別,程序和線程的切換
線程共享資源的保護
cpu怎麼實作原子操作的 **
1、使用總線鎖
2、使用緩存鎖
3、CAS
頁面置換算法
記憶體管理
垃圾回收
資料庫事物
事物隔離級别
髒讀幻讀的解決
提高隔離界别
智力題:有 9 個球,其中 8 個球品質相同,有 1 個球比較重。要求用 2 次天平,找出比較重的那個球
[算法題]():兩個棧實作隊列
摳[項目]()
[算法題]():口述前中後層序周遊,mod5轉mod7【口述,用1-5随機數生成1-7】,bash博弈【也是口述】,力扣5【[最長回文子串](),這個讓寫代碼了】
tcp如何進行流量控制的
tcp保活計時器【[用戶端]()挂掉時一段時間之後自動釋放那個】
還是https,對稱加密還是非對稱加密,證書認證
程序之間的通信方式
1.管道:速度慢,容量有限,隻有父子程序能通訊
2.FIFO:任何程序間都能通訊,但速度慢
3.消息隊列:容量受到系統限制,且要注意第一次讀的時候,要考慮上一次沒有讀完資料的問題
4.信号量:不能傳遞複雜消息,隻能用來同步
5.共享記憶體區:能夠很容易控制容量,速度快,但要保持同步,比如一個程序在寫的時候,另一個程序要注意讀寫的問題,相當于線程中的線程安全,當然,共享記憶體區同樣可以用作線程間通訊,不過沒這個必要,線程間本來就已經共享了同一程序内的一塊記憶體
自旋鎖和互斥鎖的差別【這塊好像沒答好
消息隊列的作用,使用場景
子網路遮罩的作用,給兩對ip位址和子網路遮罩,判斷他們是不是在同一個網段
DNS的作用
ARP協定的作用,協定原理
恒生:
問[項目]()
Java基本資料類型有哪些?分别占多少位元組?什麼時候用int,什麼時候用long?
在Java中一共有8種基本資料類型,其中有4種整型,2種浮點類型,1種用于表示Unicode編碼的字元單元的字元類型和1種用于表示真值的boolean類型。
反射了解嗎?說一下應用場景
反射:運作狀态
對于任意一個類,都能夠知道這個類的所有屬性和方法;
對于任意一個對象,都能夠調用它的任意方法和屬性
4.JDBC 的資料庫的連接配接
- 通過Class.forName()加載資料庫的驅動程式 (通過反射加載,前提是引入相關了Jar包)
- 通過 DriverManager 類進行資料庫的連接配接,連接配接的時候要輸入資料庫的連接配接位址、使用者名、密碼
- 通過Connection 接口接收連接配接
CLass.forName()位元組碼已經加載到java虛拟機中,去得到位元組碼;java虛拟機中還沒有生成位元組碼 用類加載器進行加載,加載的位元組碼緩沖到虛拟機中。
5.泛型擦除中
1.getclass.getmethod.invoke();
1、可以用于改進設計模式
工廠模式的改進
要新增實作類的話,隻要将傳入的參數改變就好,無需更改工廠内的代碼。
2、 反射機制的典型應用---Tomcat伺服器(SSM架構中使用)
(1). Tomcat(App)首先讀取配置檔案web.xml中配置好的Servlet的子類名稱
(2).Tomcat根據讀取到的用戶端實作的Servlet子類的類名字元串去尋找對應的位元組碼檔案。如果找到就将其加載到記憶體。
(3).Tomcat通過預先設定好的Java反射處理機制解析位元組碼檔案并建立相應的執行個體對象。之後調用所需要的方法。
【最後】Tomcat一啟動,使用者自定義的Servlet的子類通過Tomcat内部的反射架構也随之運作。
3、在項目中處處可見,AOP和IOC都是基于反射實作的
例:攔截器,注解、XML文檔、token登入攔截
Java異常有哪些?介紹下你遇到過的異常
集合類架構:
1.ArrayList:和Vector對比
預設建立的是一個空集合,它的初始容量是 10
調用add()方法先判斷大小
擴容的源碼:
grow()方法中:
Vector2倍
加鎖,線程安全
HashMap
JDK1.8 之前HashMap 底層 數組和連結清單結合形成連結清單散列
HashMap 通過key 的 hashCode 經過擾動函數處理過後得到 hash 值,然後通過 (數組長度 - 1) & hash 判斷目前元素
存放的位置(這⾥的 n 指的是數組的⻓度)
為什麼是(n - 1):
因為在hash值和 那個長度做按位與運算 最後一位都是0,需要充分利用空間.
如果目前位置存在元素的話,就判斷該元素與要存
⼊的元素的 hash 值以及 key 是否相同,如果相同的話,直接覆寫,不相同就通過拉鍊法解決沖
突。
所謂擾動函數指的就是 HashMap 的 hash ⽅法。使⽤ hash ⽅法也就是擾動函數是為了防⽌⼀
些實作⽐較差的 hashCode() ⽅法 換句話說使⽤擾動函數之後可以減少碰撞。
源碼中:
1.8
右移16位擾動4次
介紹下static關鍵字的使用場景。什麼時候要用到static?
靜态方法的好處就是不用生成類的執行個體就可以直接調用。
static方法修飾的成員不再屬于某個對象,而是屬于它所在的類。隻需要通過其類名就可以通路,不需要再消耗資源反複建立對象。
在類第一次加載的時候,static就已經在記憶體中了,直到程式結束後,該記憶體才會釋放。
如果不是static修飾的成員函數,在使用完之後就會立即被JVM回收。
使用:
1.寫工具類(JDBC中 getConnection)
2.單例模式:雙重校驗. SingletonInstance
介紹下JVM記憶體區域以及每個區域的作用。類資訊存儲在哪個區域?常量存儲在哪個區域?位元組碼存儲在哪個區域?
1、程式計數器
- 位元組碼指令的位址
- native【底層方法】,那麼計數器為空
⽣命周期:
随着線程的建立⽽建立,随着線程的結束⽽死亡。
4.可以切換線程且之間獨立.
2、虛拟機棧
存儲局部變量表,操作棧,動态連結,方法出口
編譯期完成,不會改變大小.
存放編譯期可知的各種 資料類型 對象引⽤
抛出異常:
1.StackOverflowError
2.OutOfMemory
方法函數調用:
1.return
2.抛出異常
主要是桢棧被彈出
3、本地方法棧
本地方法棧則為虛拟機使用到的native方法服務
4、堆(GC收集的主要區域)
堆是java虛拟機管理記憶體最大的一塊記憶體區域,因為堆存放的對象是線程共享的,是以多線程的時候也需要同步機制。
所有對象執行個體及數組都要在堆上配置設定記憶體
1、-Xms:表示java虛拟機堆區記憶體初始記憶體配置設定的大小,通常為作業系統可用記憶體的1/64大小即可,但仍需按照實際情況進行配置設定。
2、-Xmx:表示java虛拟機堆區記憶體可被配置設定的最大上限,通常為作業系統可用記憶體的1/4大小。
5.方法區
存儲已被虛拟機加載的類資訊、常量、靜态變量,如static修飾的變量加載類的時候就被加載到方法區中。
Spring裡面用到了哪些設計模式?
- 工廠設計模式 : Spring使用工廠模式通過
、BeanFactory
建立 bean 對象。ApplicationContext
- 代理設計模式 : Spring AOP 功能的實作。(JDK CGlib)
- 單例設計模式 : Spring 中的 Bean 預設都是單例的。
- 模闆方法模式 : Spring 中
jdbcTemplate
等以 Template 結尾的對資料庫操作的類,它們就使用到了模闆模式。hibernateTemplate
- 包裝器設計模式 : 我們的項目需要連接配接多個資料庫,而且不同的客戶在每次通路中根據需要會去通路不同的資料庫。這種模式讓我們可以根據客戶的需求能夠動态切換不同的資料源。
- 觀察者模式: Spring 事件驅動模型就是觀察者模式很經典的一個應用。
- 擴充卡模式 :Spring AOP 的增強或通知(Advice)使用到了擴充卡模式、spring MVC 中也是用到了擴充卡模式适配
。Controller
你用過哪些SpringBoot的注解?
自動裝配原理:
@EnableAutoConfigration 注解會導入一個自動配置選擇器去掃描每個jar包的META-INF/xxxx.factories 這個檔案,這個檔案是一個key-value形式的配置檔案,裡面存放了這個jar包依賴的具體依賴的自動配置類。這些自動配置類又通過@EnableConfigurationProperties 注解支援通過xxxxProperties 讀取application.properties/application.yml屬性檔案中我們配置的值。如果我們沒有配置值,就使用預設值,這就是所謂約定>配置的具體落地點。
Spring Boot 中如何解決跨域問題 ?
同源政策:
JSONP 僅僅有GET不符合
CORS 有跨域的:
通過WebMvcConfigurer接口然後重寫addCorsMappings方法解決跨域問題。
當使用者登出狀态時或者token過期時,由于攔截器和跨域的順序有問題,出現了跨域的現象。
一個http請求,先走filter,到達servlet後才進行攔截器的處理,如果我們把cors放在filter裡,就可以優先于權限攔截器執行。
類加載過程
加載-->驗證--> 準備-->解析-->初始化-->使用-->解除安裝
- 項目 中pagehelper的原理是什麼?
- 項目 中使用aop是如何實作降低耦合和提高拓展性的?
-
項目 細節等
4.java的基本資料類型詳細說說
5.一個空字元串占幾個位元組
編碼決定大小 GBK6
源碼時8*int
一個java檔案中有多個類,除去内部類,剩下的每個普通類都會生成一個class檔案
7.volatile的作用是什麼,舉例使用場景
8.hash碰撞的解決方法有哪些
9.講一講jdk裡的hashmap有哪些值得借鑒的細節
10.垃圾回收 算法 有哪些
11.年輕代老年代分别用的什麼 算法
12.gc會停止使用者程序嗎?cms和g1的哪些階段會stw?
13.抽象類和接口的差別
14.jmm講一講
15.線程和程序的差別
16.并行和并發的差別
17.怎麼去結束一個正在運作的線程
18.樂觀鎖與悲觀鎖是什麼?舉個例子
19.linux了解嗎,怎麼檢視占用cpu最高的程序
20.時間複雜度為O(nlogn)的 排序 有哪些
21.快速 排序 最壞情況
22.外部 排序 ,找一億個數中的top50
23.一組數中找到出現次數為奇數的那個數
24.檔案系統和資料庫系統的索引用什麼結構?
\25. 紅黑樹 和 平衡二叉樹 的差別、優點,b+樹和 紅黑樹 比較
26.熟悉哪些資料庫,mysql, redis
27.sql左連接配接右連接配接特點分析
28.對工作地點有什麼看法
29.未來的發展方向
30.反問
-
- 項目 中使用aop是如何實作降低耦合和提高拓展性的?
然後就開始問我[項目](),一開始太緊張答得不太好,面試官似乎對我做的[項目]()好像也不是很感興趣,然後就開始問八股文了,方方面面基本都有問到。
1、Java自身帶的線程池有哪些問題(答了OOM面試官就問我Java堆溢出怎麼排查,這塊不是很熟沒答出來)
2、幾種垃圾回收[算法]()
3、怎麼檢視sql查詢效率
4、MySQL左外連接配接和内連接配接的差別
5、問了spring的AOP,然後就問我動态代理的幾種實作
6、問了我spring中有用到那些設計模式(就問了下也沒問我怎麼實作的)
JVM
講一下有哪些垃圾回收器,說一下吞吐量優先垃圾回收器怎麼使用???
什麼時候會進行full GC(答着答着發現CMS那個failure忘了叫啥了,離譜)
1.jdk動态代理
2.避免死鎖怎麼做
3.索引
4.分布式session
5.mybatis怎麼分頁的,PageHelper實作分頁
6.怎麼保證位元組碼指令的順序,禁止指令重排,voliate關鍵字
7.設計模式,講了單例的實作
8.reids當機了怎麼辦,使用過[redis]()叢集嗎
9.怎麼設計接口限流的
10.mybatis的三級緩存
11.怎麼保證mysql和[redis]()的資料一緻性
嘿嘿,還有一些忘記了哈
\1. 在校活動。
\2. 介紹[項目](),團隊分工
\3. 了解哪些[前端]()技術
\4. 研究所學生階段最有成就感的一件事
\5. 你認為完成一個[項目]()的完整流程有哪些,分别會用到哪些工具?
\6. 你認為5中最重要的一個環節是?
\7. 6的環節你的[項目]()花了多少總工程時間
\8. [項目]()有沒有考慮安全問題,如何解決?
感覺有點兒上來就是技術終面的味道。
二面(32min)
\1. 介紹[項目](),[項目]()難點
\2. [項目]()給你最大的成長
\3. 團隊怎麼分工
\4. [項目]()維護的細節(舉例)
\5. 提高使用者體驗的細節(舉例)
\6. 了解哪些技術棧,哪些比較熟練
問了一些JVM相關的、分布式的ZAB協定、binlog和redolog資料同步等問題。
JVM問了原先公司用什麼垃圾回收器、為什麼新生代分為eden、s0、s1區
數組擴容死循環問題
講講集合(講了得10分鐘,從ArrayList到LinkedList,Set,從HashMap到HashTable到ConcurrentHashMap,資料結構,線程安全問題,哈希碰撞,全講了一遍)
恒生
1.一分鐘自我介紹
2.集合架構 每個怎麼使用 差別
3.JVM記憶體結構,垃圾回收
4.Java多線程,線程池
5.Servlet作用是什麼
6.Spring的倆個特性 IOC AOP
7.SpringMVC的工作流程
8.學習架構遇到的困難,重來一次你會怎麼學習
9.大學期間自我感覺做的好的事情
10.反問
架構問的比較多
自我介紹補充履歷
為什麼選這個專業
[項目]()是自己做的嗎
為什麼不考研,為什麼不,實習工作
[項目]()的職務,做了多久,流程
遇到的困難
理論和實踐差距在哪裡
怎麼學習的
為什麼有科目考60分
團隊中你作為組長怎麼統籌,怎麼解決問題,[項目]()時間,
出現的差錯
工作看中什麼
怎麼對比兩份offer
期望薪資
證書
壓力最大的時間
反問
1 自我介紹
2 八股文環節
2.1 建立一個對象有哪些方式?
2.2 重載和重寫差別?
2.3 如果想阻止一個對象序列化,可以采用什麼方法?
2.4 反射機制是什麼,有哪些實作方法,優點缺點?
2.5 HashMap底層資料結構,擴容機制
2.6 [紅黑樹]()是什麼?如何進行自調整?與HashTable差別?
2,7 String、String StringBuffer 和 StringBuilder 的差別是什麼?
2.8 什麼叫線程安全?ConcurrentHashMap做了哪些優化?
2.9 Synchronized 和 ReentrantLock 有什麼不同?
2.10 線程池是什麼?構造方法有哪些參數?有哪些包和政策?有哪些線程池?
2.11 sleep()和wait() 有什麼差別?
2.12 為什麼線程沒有運作态(隻有runnable)?
2,13 GC機制
2.14 雙親委派機制是什麼?類加載過程是怎樣的?
2.15 存儲過程是什麼,優點?
2.16 資料庫索引
2.17 事務隔離級别?Mysql預設隔離級别是什麼?
2.18 資料庫最左比對原則?Sql怎麼優化?
2.19 緩存雪崩與緩存穿透,解決方法?
2.20 Redis為什麼是單線程?
2.21 緩存有哪幾個實作方式?
2.22 請你談談消息隊列?有哪些應用場景?
2.23 HTTP和HTTPs差別?
2.24 簡述DNS過程
2.25 TcP三次握手具體過程
3 反問環節
自行提問即可
09-15-二面[面經]():
2 我看你是做人工智能的,為什麼不搞[算法]()?
3 你在[項目]()中遇到哪些問題,怎麼解決?
4 你的[職業規劃]()?
5 你對恒生有哪些了解?
6 請用兩個詞形容你自己?
7 為什麼你選擇恒生?
8 你對我們部門有什麼了解?
9 最後反問
作者:披薩大王
https://www.nowcoder.com/discuss/761047?type=post&order=time&pos=&page=1&ncTraceId=&channel=-1&source_id=search_post_nctrack一面: 60分鐘 2021.9.10
- 先介紹了面試流程,自我介紹--》基礎知識的了解---》[項目]()經曆or實習經曆---》反問
- 能實習了這麼久(一年半),是沒課了嗎?有課。那你的課程完成了嗎
- 哪門計算機的專業課學的最好?
- 判斷相同,除了重寫equals,還要重寫什麼?為什麼要重寫這兩個?equals意義是啥?
- final關鍵字的作用?
- 什麼是對象的深拷貝和淺拷貝?實作深拷貝怎麼做?或者說兩個相同類型的對象做到深拷貝怎麼實作?除了序列化還有其他方法嗎?
- 什麼是反射?反射的意義?你能想到的在什麼場景下用到了反射機制?
- 用過哪些類型的資料庫?索引是用哪種資料結構存儲的?
- 說說B+樹
- 為什麼不用B-樹而用B+樹呢?說說B+樹的其他特征?
- 聚集索引和非聚集索引?
- 說說内連接配接和外連接配接
- 索引失效是什麼原因?
- 你知道的Mysql裡面的鎖?
- 遇到過死鎖的情況嗎?簡述了一下,說我說的不夠嚴謹,順序性沒照顧到。
- 說說HTTP協定
- HTTP協定除了get和post還有其他請求嗎?了解head請求嗎?面試官還給我解釋了一下head
- 了解跨域問題嗎?怎麼解決?為什麼後端上加了注解就好用了呢?
- 了解短連接配接和長連結的概念嗎?我在發出一個請求的時候,我在請求頭裡設定什麼可以将其設定成長連結?
- 有操作過伺服器上的東西嗎?假如說你在伺服器上去維護的時候對吧?各種狀态,比如 CPU 營運狀态,網絡的這一個狀态對吧?然後你看到了說這個伺服器上你會發現這個因為每一個假如說你是服務端,有[用戶端]()跟你好多[用戶端]()跟你這進行建鍊對吧?你會發現比如說線下這個伺服器上,你去查的時候,你會發現好多他的網絡的連接配接狀态它是 close wait 的一個,這是什麼?可能是什麼原因導緻的?
- 聊聊限流[算法](),為什麼選擇令牌桶的方式?
- 秒殺[項目]()中哪裡還用到了Redis?
- 怎麼才能保證不超賣?
- [redis]()裡面做庫存扣減用的哪個方法?使用[redis]()的incrby特性來扣減庫存。
- 這個[項目]()是作業還是自己嘗試着去研究的?
- 最近是剛開始面試還是已經面了一段時間了?
- 投遞履歷篩選公司的原則?為什麼
- 你是内推的,那你應該對我們公司有過了解,不需要再跟你多花時間去介紹我們公司主要做什麼?
- 場景題:可能你在學校裡面或者在實習的時候也會遇到這樣的事情。其實比如說你的老師或者你的上級上司給你安排了一個任務,這個任務要的非常的急,以你初步評估來講的,可能在這麼短的時間内沒有辦法很高品質去完整的。這個時候你會去怎麼抉擇?一個是我延期去傳遞,我保證它品質會好一些對吧。一個按時傳遞,但是我沒有辦法保證這個品質多好,這讓你怎麼抉擇呢?
- 再确認幾個資訊,大學對吧?大二開始實習,那課程是實習期間自學的嗎?學習允許嗎?學分修夠了嗎?
- 反問環節
- 當天下午打電話約了二面時間
二面: 20+分鐘 2021.9.16
- 為什麼選擇軟體工程專業
- 這個專業女生比較少,以後加班熬夜沒問題吧
- 在[項目]()中用過hashmap嗎?說說
- 在什麼場景下用到ArrayList?為什麼用到ArrayList?放數組裡不也是一樣嗎?那說說ArrayList的擴容機制吧
- 多線程在[項目]()中用到過嗎?
- 一個線程啟動之後占多少記憶體?
- Concurrenthashmap和hashmap的差別?
- 設計模式了解過嗎?一共有多少種?常用哪些?用模闆型設計模式解決什麼問題?
- 什麼樣的事情能讓你擷取成就?
- 講講最近的那個實習的[項目]()?
- 實習[項目]()裡面比較困難的技術點?怎麼解決的?
- 我們用的不是Java,知道其他的一些語言嗎?
- 對[北森]()了解過嗎?
- 你認為[北森]()是你心目中比較期望的這種這個類型的公司嗎?
- [二分查找]()法的原理?
- 一面給你講過[北森]()的一些技術棧吧?
- 什麼是緩存穿透?怎麼避免?
- 這些技術是哪裡學到的?自己嘗試過嗎?
- 秒殺做的時候主要注意那個點?
- 反問:介紹一下技術棧
作者:牛客737673632号
https://www.nowcoder.com/discuss/750286?type=post&order=time&pos=&page=1&ncTraceId=&channel=-1&source_id=search_post_nctrack一面:(8月31号進行的一面。大概四十多分鐘五十分鐘吧)
1、 自我介紹
2、 你有沒有展現你價值的東西,或者是惑你比較滿意的[項目]()
3、 介紹[項目]()
4、 介紹一下伺服器實作的流程,i/o模型
5、 Epoll的建立流程,線程池
6、 當你接收到資料的時候,用線程池去處理資料,他們之間的互動是怎麼樣的
7、 Epoll的工作原理,各個函數的作用,從監聽樹到就緒[連結清單]()是由什麼觸發的呢,是怎麼将就緒的事件放到就緒[連結清單]()的。
8、 MD5加密會出現什麼問題,如果我現在對它有一個更高的安全要求,可以怎麼做。
9、 中介者模式
10、 map是放在程序裡的?當使用者量過大的時候會出現什麼問題?那你這個問題可以怎麼解決?
11、 數組和[連結清單]()的差別
12、 隊列和棧兩個資料結構的差別
13、 樹的廣度優先周遊
14、 [哈希表]()的實作原理、哈希沖突的解決
15、 生成1-1000的數放到數組裡,但是是随機的不能重複的不能有序。應該怎麼做
16、 TCP的三次握手、四次揮手,連接配接斷開的時候會發送什麼、[用戶端]()和伺服器分别處于什麼狀态
17、 滑動視窗
18、 在網絡通信,發送端發送資料包的時候,是怎麼把資料包傳送給接收端的,也就是他是怎麼找到接收端然後發送包的(具體的過程)
19、 在網路中是如何根據ip和端口号找到對應的主機的。
20、 什麼是阻塞i/o、什麼是非阻塞i/o。阻塞的什麼?
21、 當我們調用sleep的時候,他調用的線程會變成什麼狀态
22、 當我們通路淘寶的時候,從浏覽器到網絡到對方的伺服器中心,中間的具體實作過程。
23、 做[項目]()的時候遇到的最大的挑戰是什麼?是怎麼解決的?
整體面試感覺挺好的,面試官也沒有咄咄逼人的狀态。前面的問題主要是基于[項目]()展開的。問題相對來說都還是比較基礎的,也會問一些比較底層是實作(比如epoll是怎麼把就緒的I/O事件放到就緒[連結清單]()的),但是不多。
過一段時間整理一下二面。二面的時候都被問懵逼了...
二面(一面完了第二天就打電話約二面了,約的是3号面試,結果面試官沒來推到了後面。也是五十分鐘左右 )
一面的時候問了資料庫的知識,我說對資料庫不是很熟悉。。。結果二面傻眼了
沒有自我介紹,上來直接問的
1、 線程間同步互斥有哪些方式
2、 互斥鎖有的是能跨程序使用的,有的是不能跨程序使用的,為什麼有的能跨程序使用有的不能
3、 條件變量用到了什麼場景?用信号量是不是也可以?(可以,那你為什麼用條件變量+互斥鎖)
4、 [二分查找]()的時間複雜度是多少?哪些資料結構支援[二分查找]()
5、 除了有序的資料結構還有其他的嗎?
6、 資料結構的字典,它的查找過程,是怎麼根據key值找到value的?
7、 map是線程安全的嗎
8、 假設一個資料結構不是線程安全的,我們要調用它進行讀操作和寫操作,可以選擇讀加鎖和寫加鎖或者讀寫都加鎖去保證它,那你覺得應該選哪個
9、 當我們讀加鎖的時候,我們讀的時候是不是也會被阻塞?
10、 資料庫的ACID,你了解的一緻性是什麼
11、 隔離級别
12、 隔離級别有哪些實作方式
13、 Vector和list有什麼差別?它是怎麼根據索引下标去得到對應的值的。(糾正了一個問題vector查找的時候時間複雜度不是o(1),當我們查找的時候還是得周遊,它是根據索引下标去得到)
14、 MD5加密
15、 生産者消費者是幹什麼的
16、 你為什麼要進行10個線程的建立和銷毀
17、 你覺得10個10個建立有什麼不好的地方
18、 假設一個場景,我去請求mysql或者[redis](),它報了一個socket逾時的異常,有可能是哪的問題。
19、 檢測網絡是否暢通用什麼指令
20、 TCP的三次握手過程,每一次[用戶端]()和伺服器的互動發送了什麼
21、 怎麼保證緩存的資料和資料庫的資料是一緻的。
22、 假設我寫的時候先寫資料庫然後再寫緩沖區,你覺得這個方法能不能保持一緻性?考慮一下它的并發,有多個線程同時執行寫操作?多個線程要對資料進行修改呢?
複習:
計算機網絡:
加密算法:
散列函數hash
常見的有 MD5、SHA1、SHA256,該類函數特點是函數單向不可逆、對輸入非常敏感、輸出長度固定,針對資料的任何修改都會改變散列函數的結果,用于防止資訊篡改并驗證資料的完整性; 在資訊傳輸過程中,散列函數不能單獨實作資訊防篡改,因為明文傳輸,中間人可以修改資訊之後重新計算資訊摘要,是以需要對傳輸的資訊以及資訊摘要進行加密;
對稱加密
常見的有AES-CBC、DES、3DES、AES-GCM等,相同的密鑰可以用于資訊的加密和解密,掌握密鑰才能擷取資訊,能夠防止資訊竊聽,通信方式是1對1; 對稱加密的優勢是資訊傳輸1對1,需要共享相同的密碼,密碼的安全是保證資訊安全的基礎,伺服器和 N 個用戶端通信,需要維持 N 個密碼記錄,且缺少修改密碼的機制;
非對稱加密
即常見的 RSA 算法,還包括 ECC、DH 等算法,算法特點是,密鑰成對出現,一般稱為公鑰(公開)和私鑰(保密),公鑰加密的資訊隻能私鑰解開,私鑰加密的資訊隻能公鑰解開。是以掌握公鑰的不同用戶端之間不能互相解密資訊,隻能和掌握私鑰的伺服器進行加密通信,伺服器可以實作1對多的通信,用戶端也可以用來驗證掌握私鑰的伺服器身份。 非對稱加密的特點是資訊傳輸1對多,伺服器隻需要維持一個私鑰就能夠和多個用戶端進行加密通信,但伺服器發出的資訊能夠被所有的用戶端解密,且該算法的計算複雜,加密速度慢。
http相關:
http1.0和1.1:
HTTP1.0和HTTP1.1的一些差別
HTTP1.0最早在網頁中使用是在1996年,那個時候隻是使用一些較為簡單的網頁上和網絡請求上,而HTTP1.1則在1999年才開始廣泛應用于現在的各大浏覽器網絡請求中,同時HTTP1.1也是目前使用最為廣泛的HTTP協定。
主要差別主要展現在:
1.緩存處理:
在HTTP1.0中主要使用header裡的 lf-Modified-Since,Expires來做為緩存判斷的标準,HTTP1.1則引入了更多的緩存控制政策例如Entitytag,lf-Unmodified-Since, If-Match, lf-None-Match等更多可供選擇的緩存頭來控制緩存政策。
2.帶寬優化及網絡連接配接的使用:
HTTP1.0中,存在一些浪費帶寬的現象,例如用戶端隻是需要某個對象的一部分,而伺服器卻将整個對象送過來了,并且不支援斷點續傳功能,HTTP1.1則在請求頭引入了range頭域,它允許隻請求資源的某個部分,即傳回碼是206 (Partial Content),這樣就友善了開發者自由的選擇以便于充分利用帶寬和連接配接。
3.錯誤通知的管理:
在HTTP1.1中新增了24個錯誤狀态響應碼,如409 (Conflict)表示請求的資源與資源的目前狀态發生沖突;410 (Gone)表示伺服器上的某個資源被永久性的删除。
4.Host頭處理:
在HTTP1.0中認為每台伺服器都綁定一個唯一的IP位址,是以,請求消息中的URL并沒有傳遞主機名(hostname)。但随着虛拟主機技術的發展,在一台實體伺服器上可以存在多個虛拟主機(Multi-homed Web Servers),并且它們共享一個IP位址。HTTP1.1的請求消息和響應消息都應支援Host頭域,且請求消息中如果沒有Host頭域會報告一個錯誤(400 Bad Request) .
5.長連接配接:
HTTP 1.1支援長連接配接(PersistentConnection)和請求的流水線(Pipelining)處理,在一個TCP連接配接上可以傳送多個HTTP請求和響應,減少了建立和關閉連接配接的消耗和延遲,其中長連接配接也就是對應在HTTP1.1中的Connection: keep-alive,一定程度上彌補了HTTP1.0每次請求都要建立連接配接的缺點。
HTTP1.0 和 1.1 現存的一些問題
HTTP1.0和HTTP1.1可以稱做HTTP1.x,正如上面提到過的,HTTP1.x在傳輸資料時,每次都需要重建立立連接配接,無疑增加了大量的延遲時間,特别是在移動端更為突出。
HTTP1.x在傳輸資料時,所有傳輸的内容都是明文,用戶端和伺服器端都無法驗證對方的身份,這在一定程度上無法保證資料的安全性。
HTTP1.x在使用時,header裡攜帶的内容過大,在—定程度上增加了傳輸的成本,并且每次請求header基本不怎麼變化,尤其在移動端會增加使用者流量。
雖然HTTP1.1支援了keep-alive,來彌補多次建立連接配接産生的延遲,但是keep-alive 使用多了同樣會給服務端帶來大量的性能壓力,并且對于單個檔案被不斷請求的服務(例如圖檔存放網站),keep-alive可能會極大的影響性能,因為它在檔案被請求之後還保持了不必要的連接配接很長時間。同樣keep-alive 也無法解決線頭阻塞(Head-of-line blocking, HOL)問題。
https:
HTTPS與HTTP的一些差別
HTTPS協定需要到CA申請證書,一般免費證書很少,需要交費。
HTTP是超文本傳輸協定,資訊是明文傳輸,HTTPS則是具有安全性的TLS加密傳輸協定。
HTTP和HTTPS使用的是完全不同的連接配接方式,用的預設端口也不一樣,前者是80,後者是443.
HTTPS的連接配接很簡單,HTTPS協定是由TLS+HTTP協定建構的 可進行加密傳輸、身份認證的網絡協定,比HTTP協定安全。
https"中間人":
為什麼需要 CA 認證機構頒發證書?
HTTP 協定被認為不安全是因為傳輸過程容易被監聽者勾線監聽、僞造伺服器,而 HTTPS 協定主要解決的便是網絡傳輸的安全性問題。
首先我們假設不存在認證機構,任何人都可以制作證書,這帶來的安全風險便是經典的 “中間人攻擊” 問題。
“中間人攻擊”的具體過程如下:
過程原理:
- 本地請求被劫持(如DNS劫持等),所有請求均發送到中間人的伺服器
- 中間人伺服器傳回中間人自己的證書
- 用戶端建立随機數,通過中間人證書的公鑰對随機數加密後傳送給中間人,然後憑随機數構造對稱加密對傳輸内容進行加密傳輸
- 中間人因為擁有用戶端的随機數,可以通過對稱加密算法進行内容解密
- 中間人以用戶端的請求内容再向正規網站發起請求
- 因為中間人與伺服器的通信過程是合法的,正規網站通過建立的安全通道傳回加密後的資料
- 中間人憑借與正規網站建立的對稱加密算法對内容進行解密
- 中間人通過與用戶端建立的對稱加密算法對正規内容傳回的資料進行加密傳輸
- 用戶端通過與中間人建立的對稱加密算法對傳回結果資料進行解密
由于缺少對證書的驗證,是以用戶端雖然發起的是 HTTPS 請求,但用戶端完全不知道自己的網絡已被攔截,傳輸内容被中間人全部竊取。
http2.0:
HTTP2.0可以說是SPDY的更新版(其實原本也是基于SPDY設計的)。但是,HTTP2.0跟SPDY仍有不同的地方,主要是以下兩點:
HTTP2.0消息頭的壓縮算法采用HPACK算法,而非SPDY采用的DEFLATE算法。
HTTP2.0設計初期支援明文HTTP傳輸,而SPDY強制使用HTTPS,到後期兩者都需要使用HTTPS。
http3.0:
HTTP 3.0而QUIC是基于傳輸層UDP上的協定,可以定義成:HTTP3.0基于UDP的安全可靠的HTTP2.0協定。
QUIC 協定針對基于TCP和TLS的HTTP2.0協定解決了下面的問題。
1.1 減少了TCP三向交握及TLS握手時間
不管是HTTP1.0/1.1還是HTTPS,HTTP2.0,都使用了TCP進行傳輸。HTTPS和HTTP2還需要使用TLS協定來進行安全傳輸。這就出現了兩個握手延遲,而基于UDP協定的QUIC,因為UDP本身沒有連接配接的概念,連接配接建立時隻需要一次互動,半個握手的時間。差別如下圖:
1.2 多路複用丢包的線頭阻塞問題
QUIC保留了HTTP2.0多路複用的特性,在之前的多路複用過程中,同一個TCP連接配接上有多個stream,假如其中一個stream丢包,在重傳前後的stream都會受到影響,而QUIC中一個連接配接上的多個stream之間沒有依賴。是以當發生丢包時,隻會影響目前的stream,也就避免了線頭阻塞問題。
1.3 優化重傳政策
以往的TCP丢包重傳政策是:在發送端為每一個封包标記一個編号(sequence number),接收端在收到封包時,就會回傳一個帶有對應編号的ACK封包給發送端,告知發送端封包已經确實收到。當發送端在超過一定時間之後還沒有收到回傳的ACK,就會認為封包已經丢失,啟動重新傳送的機制,複用與原來相同的編号重新發送一次封包,確定在接收端這邊沒有任何封包漏接。這樣的機制就會帶來一些問題,假設發送端總共對同一個封包發送了兩次(初始+重傳),使用的都是同一個sequence number:編号N。之後發送端在拿到編号N封包的回傳ACK時,将無法判斷這個帶有編号N的ACK,是接收端在收到初始封包後回傳的ACK。這就會加大後續的重傳計算的耗時。QUIC為了避免這個問題,發送端在傳送封包時,初始與重傳的每一個封包都改用一個新的編号,unique packet number,每一個編号都唯一而且嚴格遞增,這樣每次在收到ACK時,就可以依據編号明确的判斷這個ACK是來自初始封包或者是重傳封包。
1.4 流量控制
通過流量控制可以限制用戶端傳輸資料量的大小,有了流量控制後,接收端就可以隻保留相對應大小的接收 buffer ,優化記憶體被占用的空間。但是如果存在一個流量極慢的stream ,光一個stream就有可能估用掉接收端所有的資源。QUIC為了避免這個潛在的HOL Blocking,采用了連線層(connection flow control)和Stream層的(streamflow control)流量控制,限制單一Stream可以占用的最大buffer size。
1.5 連接配接遷移
TCP連接配接基于四元組(源IP、源端口、目的IP、目的端口),切換網絡時至少會有一個因素發生變化,導緻連接配接發生變化。當連接配接發生變化時,如果還使用原來的TCP連接配接,則會導緻連接配接失敗,就得等原來的連接配接逾時後重建立立連接配接,是以我們有時候發現切換到一個新網絡時,即使新網絡狀況良好,但内容還是需要加載很久。如果實作得好,當檢測到網絡變化時立刻建立新的TCP連接配接,即使這樣,建立新的連接配接還是需要幾百毫秒的時間。QUIC的連接配接不受四元組的影響,當這四個元素發生變化時,原連接配接依然維持。QUIC連接配接不以四元組作為辨別,而是使用一個64位的随機數,這個随機數被稱為Connection lD,對應每個stream,即使IP或者端口發生變化,隻要Connection ID沒有變化,那麼連接配接依然可以維持。
大檔案傳輸:
HTTP斷點續傳(分塊傳輸)(HTTP頭格式非常清楚)
簡述
斷點續傳:指的是在上傳/下載下傳時,将任務(一個檔案或壓縮包)人為的劃分為幾個部分,每一個部分采用一個線程進行上傳/下載下傳,如果碰到網絡故障,可以從已經上傳/下載下傳的部分開始繼續上傳/下載下傳未完成的部分,而沒有必要從頭開始上傳/下載下傳。可以節省時間,提高速度。
有時使用者上傳/下載下傳檔案需要曆時數小時,萬一線路中斷,不具備斷點續傳的 HTTP/FTP 伺服器或下載下傳軟體就隻能從頭重傳,比較好的 HTTP/FTP 伺服器或下載下傳軟體具有斷點續傳能力,允許使用者從上傳/下載下傳斷線的地方繼續傳送,這樣大大減少了使用者的煩惱。
常見的支援斷點續傳的上傳/下載下傳軟體:QQ 旋風、迅雷、快車、電驢、酷6、洋芋、優酷、百度視訊、新浪視訊、騰訊視訊、百度雲等。
在 Linux/Unix 系統下,常用支援斷點續傳的 FTP 用戶端軟體是 lftp。
Range & Content-Range
HTTP1.1 協定(RFC2616)開始支援擷取檔案的部分内容,這為并行下載下傳以及斷點續傳提供了技術支援。它通過在 Header 裡兩個參數實作的,用戶端發請求時對應的是 Range ,伺服器端響應時對應的是 Content-Range。
Range
用于請求頭中,指定第一個位元組的位置和最後一個位元組的位置,一般格式:
Range:(unit=first byte pos)-[last byte pos]
Range 頭部的格式有以下幾種情況:
Range: bytes=0-499 表示第 0-499 位元組範圍的内容
Range: bytes=500-999 表示第 500-999 位元組範圍的内容
Range: bytes=-500 表示最後 500 位元組的内容
Range: bytes=500- 表示從第 500 位元組開始到檔案結束部分的内容
Range: bytes=0-0,-1 表示第一個和最後一個位元組
Range: bytes=500-600,601-999 同時指定幾個範圍
Content-Range
用于響應頭中,在發出帶 Range 的請求後,伺服器會在 Content-Range 頭部傳回目前接受的範圍和檔案總大小。一般格式:
Content-Range: bytes (unit first byte pos) - [last byte pos]/[entity legth]
例如:
Content-Range: bytes 0-499/22400
0-499 是指目前發送的資料的範圍,而 22400 則是檔案的總大小。
而在響應完成後,傳回的響應頭内容也不同:
HTTP/1.1 200 Ok(不使用斷點續傳方式)
HTTP/1.1 206 Partial Content(使用斷點續傳方式)
在實際場景中,會出現一種情況,即在終端發起續傳請求時,URL 對應的檔案内容在伺服器端已經發生變化,此時續傳的資料肯定是錯誤的。如何解決這個問題了?顯然此時需要有一個辨別檔案唯一性的方法。
在 RFC2616 中也有相應的定義,比如實作 Last-Modified 來辨別檔案的最後修改時間,這樣即可判斷出續傳檔案時是否已經發生過改動。同時 FC2616 中還定義有一個 ETag 的頭,可以使用 ETag 頭來放置檔案的唯一辨別。
If-Modified-Since,和 Last-Modified 一樣都是用于記錄頁面最後修改時間的 HTTP 頭資訊,隻是 Last-Modified 是由伺服器往用戶端發送的 HTTP 頭,而 If-Modified-Since 則是由用戶端往伺服器發送的頭,可以看到,再次請求本地存在的 cache 頁面時,用戶端會通過 If-Modified-Since 頭将先前伺服器端發過來的 Last-Modified 最後修改時間戳發送回去,這是為了讓伺服器端進行驗證,通過這個時間戳判斷用戶端的頁面是否是最新的,如果不是最新的,則傳回新的内容,如果是最新的,則傳回 304 告訴用戶端其本地 cache 的頁面是最新的,于是用戶端就可以直接從本地加載頁面了,這樣在網絡上傳輸的資料就會大大減少,同時也減輕了伺服器的負擔。
Etag(Entity Tags)主要為了解決 Last-Modified 無法解決的一些問題。
- 一些檔案也許會周期性的更改,但是内容并不改變(僅改變修改時間),這時候我們并不希望用戶端認為這個檔案被修改了,而重新 GET。
- 某些檔案修改非常頻繁,例如:在秒以下的時間内進行修改(1s 内修改了 N 次),If-Modified-Since 能檢查到的粒度是 s 級的,這種修改無法判斷(或者說 UNIX 記錄 MTIME 隻能精确到秒)。
- 某些伺服器不能精确的得到檔案的最後修改時間。
為此,HTTP/1.1 引入了 Etag。Etag 僅僅是一個和檔案相關的标記,可以是一個版本标記,例如:v1.0.0;或者說 “627-4d648041f6b80” 這麼一串看起來很神秘的編碼。但是 HTTP/1.1 标準并沒有規定 Etag 的内容是什麼或者說要怎麼實作,唯一規定的是 Etag 需要放在 “” 内。
用于判斷實體是否發生改變,如果實體未改變,伺服器發送用戶端丢失的部分,否則發送整個實體。一般格式:
If-Range: Etag | HTTP-Date
也就是說,If-Range 可以使用 Etag 或者 Last-Modified 傳回的值。當沒有 ETage 卻有 Last-modified 時,可以把 Last-modified 作為 If-Range 字段的值。
If-Range: “627-4d648041f6b80”
If-Range: Fri, 22 Feb 2013 03:45:02 GMT
If-Range 必須與 Range 配套使用。如果請求封包中沒有 Range,那麼 If-Range 就會被忽略。如果伺服器不支援 If-Range,那麼 Range 也會被忽略。
如果請求封包中的 Etag 與伺服器目标内容的 Etag 相等,即沒有發生變化,那麼應答封包的狀态碼為 206。如果伺服器目标内容發生了變化,那麼應答封包的狀态碼為 200。
用于校驗的其他 HTTP 頭資訊:If-Match/If-None-Match、If-Modified-Since/If-Unmodified-Since。
Etag 由伺服器端生成,用戶端通過 If-Range 條件判斷請求來驗證資源是否修改。請求一個檔案的流程如下:
第一次請求:
- 用戶端發起 HTTP GET 請求一個檔案。
- 伺服器處理請求,傳回檔案内容以及相應的 Header,其中包括 Etag(例如:627-4d648041f6b80)(假設伺服器支援 Etag 生成并已開啟了 Etag)狀态碼為 200。
第二次請求(斷點續傳):
- 用戶端發起 HTTP GET 請求一個檔案,同時發送 If-Range(該頭的内容就是第一次請求時伺服器傳回的 Etag:627-4d648041f6b80)。
- 伺服器判斷接收到的 Etag 和計算出來的 Etag 是否比對,如果比對,那麼響應的狀态碼為 206;否則,狀态碼為 200。
CURL 實作檢測:
[root@localhost ~]# curl -i --range 0-9 http://www.baidu.com/img/bdlogo.gifHTTP/1.1 206 Partial ContentDate: Mon, 21 Nov 2016 05:26:29 GMTServer: ApacheP3P: CP=" OTI DSP COR IVA OUR IND COM "Set-Cookie: BAIDUID=0CD0E23B4D4F739954DFEDB92BE6CE03:FG=1; expires=Tue, 21-Nov-17 05:26:29 GMT; max-age=31536000; path=/; domain=.baidu.com; version=1Last-Modified: Fri, 22 Feb 2013 03:45:02 GMTETag: "627-4d648041f6b80"Accept-Ranges: bytesContent-Length: 10Cache-Control: max-age=315360000Expires: Thu, 19 Nov 2026 05:26:29 GMTContent-Range: bytes 0-9/1575Connection: Keep-AliveContent-Type: image/gifGIF89a[root@localhost ~]#
能夠找到 Content-Range,則表明伺服器支援斷點續傳。有些伺服器還會傳回 Accept-Ranges,輸出結果 Accept-Ranges: bytes ,說明伺服器支援按位元組下載下傳。
http://blog.csdn.net/liang19890820/article/details/53215087Java基礎
設計模式:
這裡主要講解簡單工廠模式,代理模式,擴充卡模式,單例模式4中設計模式.
1、簡單工廠模式
主要特點是需要在工廠類中做判斷,進而創造相應的産品,當增加新産品時,需要修改工廠類。使用簡單工廠模式,我們隻需要知道具體的産品型号就可以建立一個産品。
缺點:工廠類集中了所有産品類的建立邏輯,如果産品量較大,會使得工廠類變的非常臃腫。
2、代理模式
代理模式:為其它對象提供一種代理以控制這個對象的通路。在某些情況下,一個對象不适合或者不能直接引用另一個對象,而代理對象可以在用戶端和目标對象之間起到中介作用。
優點:
職責清晰。真實的角色隻負責實作實際的業務邏輯,不用關心其它非本職責的事務,通過後期的代理完成具體的任務。這樣代碼會簡潔清晰。
代理對象可以在用戶端和目标對象之間起到中介的作用,這樣就保護了目标對象。
擴充性好。
3、擴充卡模式
擴充卡模式可以将一個類的接口轉換成用戶端希望的另一個接口,使得原來由于接口不相容而不能在一起工作的那些類可以在一起工作。通俗的講就是當我們已經有了一些類,而這些類不能滿足新的需求,此時就可以考慮是否能将現有的類适配成可以滿足新需求的類。擴充卡類需要繼承或依賴已有的類,實作想要的目标接口。
缺點:過多地使用擴充卡,會讓系統非常零亂,不易整體進行把握。比如,明明看到調用的是 A 接口,其實内部被适配成了 B 接口的實作,一個系統如果太多出現這種情況,無異于一場災難。是以如果不是很有必要,可以不使用擴充卡,而是直接對系統進行重構。
4、單例模式
單例模式顧名思義,保證一個類僅可以有一個執行個體化對象,并且提供一個可以通路它的全局接口。實作單例模式必須注意一下幾點:
單例類隻能由一個執行個體化對象。
單例類必須自己提供一個執行個體化對象。
單例類必須提供一個可以通路唯一執行個體化對象的接口。
單例模式分為懶漢和餓漢兩種實作方式。
JVM中:
說一下堆棧的差別?
實體位址
堆的實體位址配置設定對對象是不連續的。是以性能慢些。在GC的時候也要考慮到不連續的配置設定,是以有各種算法。比如,标記-消除,複制,标記-壓縮,分代(即新生代使用複制算法,老年代使用标記——壓縮)
棧使用的是資料結構中的棧,先進後出的原則,實體位址配置設定是連續的。是以性能快。
記憶體分别
堆因為是不連續的,是以配置設定的記憶體是在運作期确認的,是以大小不固定。一般堆大小遠遠大于棧。
棧是連續的,是以配置設定的記憶體大小要在編譯期就确認,大小是固定的。
存放的内容
堆存放的是對象的執行個體和數組。是以該區更關注的是資料的存儲
棧存放:局部變量,操作數棧,傳回結果。該區更關注的是程式方法的執行。
PS:
靜态變量放在方法區
靜态的對象還是放在堆。
程式的可見度
堆對于整個應用程式都是共享、可見的。
棧隻對于線程是可見的。是以也是線程私有。他的生命周期和線程相同。
隊列和棧是什麼?有什麼差別?
隊列和棧都是被用來預存儲資料的。
操作的名稱不同。隊列的插入稱為入隊,隊列的删除稱為出隊。棧的插入稱為進棧,棧的删除稱為出棧。
可操作的方式不同。隊列是在隊尾入隊,隊頭出隊,即兩邊都可操作。而棧的進棧和出棧都是在棧頂進行的,無法對棧底直接進行操作。
操作的方法不同。隊列是先進先出(FIFO),即隊列的修改是依先進先出的原則進行的。新來的成員總是加入隊尾(不能從中間插入),每次離開的成員總是隊列頭上(不允許中途離隊)。而棧為後進先出(LIFO),即每次删除(出棧)的總是目前棧中最新的元素,即最後插入(進棧)的元素,而最先插入的被放在棧的底部,要到最後才能删除。
HotSpot虛拟機對象探秘
對象的建立
說到對象的建立,首先讓我們看看 Java 中提供的幾種對象建立方式:
Header 解釋
使用new關鍵字 調用了構造函數
使用Class的newInstance方法 調用了構造函數
使用Constructor類的newInstance方法 調用了構造函數
使用clone方法 沒有調用構造函數
使用反序列化 沒有調用構造函數
下面是對象建立的主要流程:
虛拟機遇到一條new指令時,先檢查常量池是否已經加載相應的類,如果沒有,必須先執行相應的類加載。類加載通過後,接下來配置設定記憶體。若Java堆中記憶體是絕對規整的,使用“指針碰撞“方式配置設定記憶體;如果不是規整的,就從空閑清單中配置設定,叫做”空閑清單“方式。劃分記憶體時還需要考慮一個問題-并發,也有兩種方式: CAS同步處理,或者本地線程配置設定緩沖(Thread Local Allocation Buffer, TLAB)。然後記憶體空間初始化操作,接着是做一些必要的對象設定(元資訊、哈希碼…),最後執行方法。
為對象配置設定記憶體
類加載完成後,接着會在Java堆中劃分一塊記憶體配置設定給對象。記憶體配置設定根據Java堆是否規整,有兩種方式:
指針碰撞:如果Java堆的記憶體是規整,即所有用過的記憶體放在一邊,而空閑的的放在另一邊。配置設定記憶體時将位于中間的指針訓示器向空閑的記憶體移動一段與對象大小相等的距離,這樣便完成配置設定記憶體工作。
空閑清單:如果Java堆的記憶體不是規整的,則需要由虛拟機維護一個清單來記錄那些記憶體是可用的,這樣在配置設定的時候可以從清單中查詢到足夠大的記憶體配置設定給對象,并在配置設定後更新清單記錄。
選擇哪種配置設定方式是由 Java 堆是否規整來決定的,而 Java 堆是否規整又由所采用的垃圾收集器是否帶有壓縮整理功能決定。
處理并發安全問題
對象的建立在虛拟機中是一個非常頻繁的行為,哪怕隻是修改一個指針所指向的位置,在并發情況下也是不安全的,可能出現正在給對象 A 配置設定記憶體,指針還沒來得及修改,對象 B 又同時使用了原來的指針來配置設定記憶體的情況。解決這個問題有兩種方案:
對配置設定記憶體空間的動作進行同步處理(采用 CAS + 失敗重試來保障更新操作的原子性);
把記憶體配置設定的動作按照線程劃分在不同的空間之中進行,即每個線程在 Java 堆中預先配置設定一小塊記憶體,稱為本地線程配置設定緩沖(Thread Local Allocation Buffer, TLAB)。哪個線程要配置設定記憶體,就在哪個線程的 TLAB 上配置設定。隻有 TLAB 用完并配置設定新的 TLAB 時,才需要同步鎖。通過-XX:+/-UserTLAB參數來設定虛拟機是否使用TLAB。
對象的通路定位
Java程式需要通過 JVM 棧上的引用通路堆中的具體對象。對象的通路方式取決于 JVM 虛拟機的實作。目前主流的通路方式有 句柄 和 直接指針 兩種方式。
指針: 指向對象,代表一個對象在記憶體中的起始位址。
句柄: 可以了解為指向指針的指針,維護着對象的指針。句柄不直接指向對象,而是指向對象的指針(句柄不發生變化,指向固定記憶體位址),再由對象的指針指向對象的真實記憶體位址。
句柄通路
Java堆中劃分出一塊記憶體來作為句柄池,引用中存儲對象的句柄位址,而句柄中包含了對象執行個體資料與對象類型資料各自的具體位址資訊,具體構造如下圖所示:
優勢:引用中存儲的是穩定的句柄位址,在對象被移動(垃圾收集時移動對象是非常普遍的行為)時隻會改變句柄中的執行個體資料指針,而引用本身不需要修改。
直接指針
如果使用直接指針通路,引用 中存儲的直接就是對象位址,那麼Java堆對象内部的布局中就必須考慮如何放置通路類型資料的相關資訊。
優勢:速度更快,節省了一次指針定位的時間開銷。由于對象的通路在Java中非常頻繁,是以這類開銷積少成多後也是非常可觀的執行成本。HotSpot 中采用的就是這種方式。
垃圾回收器:
Serial收集器(複制算法): 新生代單線程收集器,标記和清理都是單線程,優點是簡單高效;
ParNew收集器 (複制算法): 新生代收并行集器,實際上是Serial收集器的多線程版本,在多核CPU環境下有着比Serial更好的表現;
Parallel Scavenge收集器 (複制算法): 新生代并行收集器,追求高吞吐量,高效利用 CPU。吞吐量 = 使用者線程時間/(使用者線程時間+GC線程時間),高吞吐量可以高效率的利用CPU時間,盡快完成程式的運算任務,适合背景應用等對互動相應要求不高的場景;
Serial Old收集器 (标記-整理算法): 老年代單線程收集器,Serial收集器的老年代版本;
Parallel Old收集器 (标記-整理算法): 老年代并行收集器,吞吐量優先,Parallel Scavenge收集器的老年代版本;
G1(Garbage First)收集器 (标記-整理算法): Java堆并行收集器,G1收集器是JDK1.7提供的一個新收集器,G1收集器基于“标記-整理”算法實作,也就是說不會産生記憶體碎片。此外,G1收集器不同于之前的收集器的一個重要特點是:G1回收的範圍是整個Java堆(包括新生代,老年代),而前六種收集器回收的範圍僅限于新生代或老年代。
CMS(Concurrent Mark Sweep)收集器(标記-清除算法): 老年代并行收集器,以擷取最短回收停頓時間為目标的收集器,具有高并發、低停頓的特點,追求最短GC回收停頓時間。
MYSQL:
MyISAM和InnoDB的差別?
- 是否支援行級鎖 :
隻有表級鎖,而MyISAM
支援行級鎖和表級鎖,預設為行級鎖。InnoDB
- 是否支援事務和崩潰後的安全恢複:
注重性能,每次查詢具有原子性,其執行速度比MyISAM
類型更快,但是不提供事務支援。而InnoDB
提供事務支援,具有事務、復原和崩潰修複能力。InnoDB
- 是否支援外鍵:
不支援,而MyISAM
支援。InnoDB
- 是否支援MVCC :
不支援,MyISAM
支援。應對高并發事務,MVCC比單純的加鎖更高效。InnoDB
-
不支援聚集索引,MyISAM
支援聚集索引。InnoDB
-
引擎主鍵索引和其他索引差別不大,葉子節點都包含索引值和行指針。MyISAM
-
引擎二級索引葉子存儲的是索引值和主鍵值(不是行指針),這樣可以減少行移動和資料頁分裂時二級索引的維護工作。innoDB
秋招結束面試和面經的總結(個人向)自我介紹:其他位元組的面經:較全的借鑒:一面二面三面四面總結還有一個:同部門的:恒生:複習:計算機網絡:大檔案傳輸:Java基礎設計模式:JVM中:MYSQL:Redis項目中: -
MVCC 實作原理?
MVCC(
Multiversion concurrency control
) 就是同一份資料保留多版本的一種方式,進而實作并發控制。在查詢的時候,通過read view和版本鍊找到對應版本的資料。
作用:提升并發性能。對于高并發場景,MVCC比行級鎖更有效、開銷更小。
MVCC 實作原理如下:
MVCC 的實作依賴于版本鍊,版本鍊是通過表的三個隐藏字段實作。
-
:目前事務id,通過事務id的大小判斷事務的時間順序。DB_TRX_ID
-
:復原指針,指向目前行記錄的上一個版本,通過這個指針将資料的多個版本連接配接在一起構成undo log版本鍊。DB_ROLL_PRT
-
:主鍵,如果資料表沒有主鍵,InnoDB會自動生成主鍵。DB_ROLL_ID
每條表記錄大概是這樣的:
使用事務更新行記錄的時候,就會生成版本鍊,執行過程如下:
- 用排他鎖鎖住該行;
- 将該行原本的值拷貝到 undo log,作為舊版本用于復原;
- 修改目前行的值,生成一個新版本,更新事務id,使復原指針指向舊版本的記錄,這樣就形成一條版本鍊。
下面舉個例子友善大家了解。
- 初始資料如下,其中DB_ROW_ID和DB_ROLL_PTR為空。
秋招結束面試和面經的總結(個人向)自我介紹:其他位元組的面經:較全的借鑒:一面二面三面四面總結還有一個:同部門的:恒生:複習:計算機網絡:大檔案傳輸:Java基礎設計模式:JVM中:MYSQL:Redis項目中: - 事務A對該行資料做了修改,效果如下:
秋招結束面試和面經的總結(個人向)自我介紹:其他位元組的面經:較全的借鑒:一面二面三面四面總結還有一個:同部門的:恒生:複習:計算機網絡:大檔案傳輸:Java基礎設計模式:JVM中:MYSQL:Redis項目中: - 之後事務B也對該行記錄做了修改,效果如下:
秋招結束面試和面經的總結(個人向)自我介紹:其他位元組的面經:較全的借鑒:一面二面三面四面總結還有一個:同部門的:恒生:複習:計算機網絡:大檔案傳輸:Java基礎設計模式:JVM中:MYSQL:Redis項目中: - 此時undo log有兩行記錄,并且通過復原指針連在一起。
接下來了解下read view的概念。
read view
可以了解成對資料在每個時刻的狀态拍成“照片”記錄下來。這樣擷取某時刻的資料時就還是原來的”照片“上的資料,是不會變的。
在
read view
内部維護一個活躍事務
連結清單,表示生成
read view
的時候還在活躍的事務。這個
包含在建立
read view
之前還未送出的事務,不包含建立
read view
之後送出的事務。
不同隔離級别建立read view的時機不同。
- read committed:每次執行select都會建立新的read_view,保證能讀取到其他事務已經送出的修改。
- repeatable read:在一個事務範圍内,第一次select時更新這個read_view,以後不會再更新,後續所有的select都是複用之前的read_view。這樣可以保證事務範圍内每次讀取的内容都一樣,即可重複讀。
read view的記錄篩選方式
前提:
DATA_TRX_ID
表示每個資料行的最新的事務ID;
up_limit_id
表示目前快照中的最先開始的事務;
low_limit_id
表示目前快照中的最慢開始的事務,即最後一個事務。
- 如果
<DATA_TRX_ID
:說明在建立up_limit_id
時,修改該資料行的事務已送出,該版本的記錄可被目前事務讀取到。read view
-
>=DATA_TRX_ID
:說明目前版本的記錄的事務是在建立low_limit_id
之後生成的,該版本的資料行不可以被目前事務通路。此時需要通過版本鍊找到上一個版本,然後重新判斷該版本的記錄對目前事務的可見性。read view
-
<=up_limit_id
DATA_TRX_ID
:low_limit_i
總結:InnoDB 的
MVCC
是通過
read view
和版本鍊實作的,版本鍊儲存有曆史版本記錄,通過
read view
判斷目前版本的資料是否可見,如果不可見,再從版本鍊中找到上一個版本,繼續進行判斷,直到找到一個可見的版本。
快照讀和目前讀
表記錄有兩種讀取方式。
- 快照讀:讀取的是快照版本。普通的SELECT就是快照讀。通過MVCC來進行并發控制的,不用加鎖。
- 目前讀:讀取的是最新版本。
是目前讀。UPDATE、DELETE、INSERT、SELECT … LOCK IN SHARE MODE、SELECT … FOR UPDATE
快照讀情況下,InnoDB通過mvcc機制避免了幻讀現象。而mvcc機制無法避免目前讀情況下出現的幻讀現象。因為目前讀每次讀取的都是最新資料,這時如果兩次查詢中間有其它事務插入資料,就會産生幻讀。
下面舉個例子說明下:
- 首先,user表隻有兩條記錄,具體如下:
秋招結束面試和面經的總結(個人向)自我介紹:其他位元組的面經:較全的借鑒:一面二面三面四面總結還有一個:同部門的:恒生:複習:計算機網絡:大檔案傳輸:Java基礎設計模式:JVM中:MYSQL:Redis項目中: - 事務a和事務b同時開啟事務
;start transaction
- 事務a插入資料然後送出;
insert
into
`(user_name, user_password, user_mail, user_state)user
(values
,'tyson'
'a'
, 0);`'a'
- 事務b執行全表的update;
update
user
set
`'a'user_name =
`;
- 事務b然後執行查詢,查到了事務a中插入的資料。(下圖左邊是事務b,右邊是事務a)
秋招結束面試和面經的總結(個人向)自我介紹:其他位元組的面經:較全的借鑒:一面二面三面四面總結還有一個:同部門的:恒生:複習:計算機網絡:大檔案傳輸:Java基礎設計模式:JVM中:MYSQL:Redis項目中:
以上就是目前讀出現的幻讀現象。
那麼MySQL如何實作避免幻讀?
- 在快照讀情況下,MySQL通過mvcc來避免幻讀。
- 在目前讀情況下,MySQL通過next-key來避免幻讀(加行鎖和間隙鎖來實作的)。
next-key包括兩部分:行鎖和間隙鎖。行鎖是加在索引上的鎖,間隙鎖是加在索引之間的。
Serializable
隔離級别也可以避免幻讀,會鎖住整張表,并發性極低,一般不會使用。
bin log/redo log/undo log
MySQL日志主要包括查詢日志、慢查詢日志、事務日志、錯誤日志、二進制日志等。其中比較重要的是 bin log(二進制日志)和 redo log(重做日志)和 undo log(復原日志)。
bin log
二進制日志(bin log)是MySQL資料庫級别的檔案,記錄對MySQL資料庫執行修改的所有操作,不會記錄select和show語句,主要用于恢複資料庫和同步資料庫。
redo log
重做日志(redo log)是Innodb引擎級别,用來記錄Innodb存儲引擎的事務日志,不管事務是否送出都會記錄下來,用于資料恢複。當資料庫發生故障,InnoDB存儲引擎會使用redo log恢複到發生故障前的時刻,以此來保證資料的完整性。将參數
innodb_flush_log_at_tx_commit
設定為1,那麼在執行commit時會将redo log同步寫到磁盤。
undo log
除了記錄redo log外,當進行資料修改時還會記錄undo log,undo log用于資料的撤回操作,它保留了記錄修改前的内容。通過undo log可以實作事務復原,并且可以根據undo log回溯到某個特定的版本的資料,實作MVCC。
bin log和redo log有什麼差別?
- bin log會記錄所有日志記錄,包括innoDB、MyISAM等存儲引擎的日志;redo log隻記錄innoDB自身的事務日志。
- bin log隻在事務送出前寫入到磁盤,一個事務隻寫一次;而在事務進行過程,會有redo log不斷寫入磁盤。
- binlog 是邏輯日志,記錄的是SQL語句的原始邏輯;redo log 是實體日志,記錄的是在某個資料頁上做了什麼修改。
大表資料查詢,怎麼優化
- 優化shema、sql語句+索引;
- 第二加緩存,memcached, redis;
- 主從複制,讀寫分離;
- 垂直拆分,根據你子產品的耦合度,将一個大的系統分為多個小的系統,也就是分布式系統;
- 水準切分,針對資料量大的表,這一步最麻煩,最能考驗技術水準,要選擇一個合理的sharding key, 為了有好的查詢效率,表結構也要改動,做一定的備援,應用也要改,sql中盡量帶sharding key,将資料定位到限定的表上去查,而不是掃描全部的表;
【推薦】利用延遲關聯或者子查詢優化超多分頁場景。
說明:MySQL并不是跳過offset行,而是取offset+N行,然後傳回放棄前offset行,傳回N行,那當offset特别大的時候,效率就非常的低下,要麼控制傳回的總頁數,要麼對超過特定門檻值的頁數進行SQL改寫。
正例:先快速定位需要擷取的id段,然後再關聯:
SELECT a.* FROM 表1 a, (select id from 表1 where 條件 LIMIT 100000,20 ) b where a.id=b.id
分庫分表
當單表的資料量達到1000W或100G以後,優化索引、添加從庫等可能對資料庫性能提升效果不明顯,此時就要考慮對其進行切分了。切分的目的就在于減少資料庫的負擔,縮短查詢的時間。
資料切分可以分為兩種方式:垂直劃分和水準劃分。
垂直劃分
垂直劃分資料庫是根據業務進行劃分,例如購物場景,可以将庫中涉及商品、訂單、使用者的表分别劃分出成一個庫,通過降低單庫的大小來提高性能,但這種方式并沒有解決高資料量帶來的性能損耗。同樣的,分表的情況就是将一個大表根據業務功能拆分成一個個子表,例如商品基本資訊和商品描述,商品基本資訊一般會展示在商品清單,商品描述在商品詳情頁,可以将商品基本資訊和商品描述拆分成兩張表。
圖檔來源于網絡
優點:行記錄變小,資料頁可以存放更多記錄,在查詢時減少I/O次數。
缺點:
- 主鍵出現備援,需要管理備援列;
- 會引起表連接配接JOIN操作,可以通過在業務伺服器上進行join來減少資料庫壓力;
- 依然存在單表資料量過大的問題。
水準劃分
水準劃分是根據一定規則,例如時間或id序列值等進行資料的拆分。比如根據年份來拆分不同的資料庫。每個資料庫結構一緻,但是資料得以拆分,進而提升性能。
優點:單庫(表)的資料量得以減少,提高性能;切分出的表結構相同,程式改動較少。
- 分片事務一緻性難以解決
- 跨節點join性能差,邏輯複雜
- 資料分片在擴容時需要遷移
大表怎麼優化?某個表有近千萬資料,CRUD比較慢,如何優化?分庫分表了是怎麼做的?分表分庫了有什麼問題?有用到中間件麼?他們的原理知道麼?
當MySQL單表記錄數過大時,資料庫的CRUD性能會明顯下降,一些常見的優化措施如下:
- 限定資料的範圍: 務必禁止不帶任何限制資料範圍條件的查詢語句。比如:我們當使用者在查詢訂單曆史的時候,我們可以控制在一個月的範圍内。
- 讀/寫分離: 經典的資料庫拆分方案,主庫負責寫,從庫負責讀;
- 緩存: 使用MySQL的緩存,另外對重量級、更新少的資料可以考慮使用應用級别的緩存;
操作一條sql的流程:
查詢語句執行流程?
查詢語句的執行流程如下:
- 權限校驗
- 、查詢緩存、
- 分析器、
- 優化器、
- 權限校驗、
- 執行器、
- 引擎。
舉個例子,查詢語句如下:
select` `* ``from` `user` `where` `id > 1 ``and` `name` `= ``'大彬'``;
- 首先檢查權限,沒有權限則傳回錯誤;
- MySQL以前會查詢緩存,緩存命中則直接傳回,沒有則執行下一步;
- 詞法分析和文法分析。提取表名、查詢條件,檢查文法是否有錯誤;
- 兩種執行方案,先查
還是id > 1
,優化器根據自己的優化 選擇執行效率最好的方案;name = '大彬'
- 校驗權限,有權限就調用資料庫引擎接口,傳回引擎的執行結果。
更新語句執行過程?
更新語句執行流程如下:分析器、權限校驗、執行器、引擎、redo log(prepare 狀态)、binlog、redo log(commit狀态)
舉個例子,更新語句如下:
update user set name = 大彬 where id = 1;
- 先查詢到 id 為1的記錄,有緩存會使用緩存。
- 拿到查詢結果,将 name 更新為 大彬,然後調用引擎接口,寫入更新資料,innodb 引擎将資料儲存在記憶體中,同時記錄 redo log,此時 redo log 進入 prepare 狀态。
- 執行器收到通知後記錄 binlog,然後調用引擎接口,送出 redo log 為送出狀态。
- 更新完成。
為什麼記錄完 redo log,不直接送出,先進入prepare狀态?
假設先寫 redo log 直接送出,然後寫 binlog,寫完 redo log 後,機器挂了,binlog 日志沒有被寫入,那麼機器重新開機後,這台機器會通過 redo log 恢複資料,但是這個時候 binlog 并沒有記錄該資料,後續進行機器備份的時候,就會丢失這一條資料,同時主從同步也會丢失這一條資料。
exist和in的差別?
exists 用于對外表記錄做篩選。
exists 會周遊外表,将外查詢表的每一行,代入内查詢進行判斷。當 exists 裡的條件語句能夠傳回記錄行時,條件就為真,傳回外表目前記錄。反之如果exists裡的條件語句不能傳回記錄行,條件為假,則外表目前記錄被丢棄。
select` `a.* ``from` `A awhere exists(``select` `1 ``from` `B b ``where` `a.id=b.id)
in 是先把後邊的語句查出來放到臨時表中,然後周遊臨時表,将臨時表的每一行,代入外查詢去查找。
select` `* ``from` `Awhere id ``in``(``select` `id ``from` `B)
子查詢的表大的時候,使用exists可以有效減少總的循環次數來提升速度;當外查詢的表大的時候,使用IN可以有效減少對外查詢表循環周遊來提升速度。
Redis
持久化:
RDB是Redis預設的持久化方式。
按照一定的時間将記憶體的資料以快照的形式儲存到硬碟中,對應産生的資料檔案為dump.rdb。
通過配置檔案中的save參數來定義快照的周期。
1、隻有一個檔案 dump.rdb,友善持久化。
2、容災性好,一個檔案可以儲存到安全的磁盤。
3、性能最大化,fork 子程序來完成寫操作,讓主程序繼續處理指令,是以是 IO 最大化。
使用單獨子程序來進行持久化,主程序不會進行任何 IO 操作,保證了 redis 的高性能
4.相對于資料集大時,比 AOF 的啟動效率更高。
1、資料安全性低。RDB 是間隔一段時間進行持久化,如果持久化之間 redis 發生故障,會發生資料丢失。(尚未來得及儲存)
是以這種方式更适合資料要求不嚴謹的時候)
2、AOF(Append-only file)持久化方式: 是指所有的指令行記錄以 redis 指令請 求協定的格式完全持久化存儲)儲存為 aof 檔案。
AOF:持久化
AOF持久化(即Append Only File持久化),則是将Redis執行的每次寫指令記錄到單獨的日志檔案中,當重新開機Redis會重新将持久化的日志中檔案恢複資料。
當兩種方式同時開啟時,資料恢複Redis會優先選擇AOF恢複。
如何選擇合适的持久化方式
當 Redis 重新開機的時候會優先載入AOF檔案來恢複原始的資料,因為在通常情況下AOF檔案儲存的資料集要比RDB檔案儲存的資料集要完整。
有很多使用者都隻使用AOF持久化,但并不推薦這種方式,因為定時生成RDB快照(snapshot)非常便于進行資料庫備份, 并且 RDB 恢複資料集的速度也要比AOF恢複的速度要快,除此之外,使用RDB還可以避免AOF程式的bug。
1、資料安全,aof 持久化可以配置 appendfsync 屬性,有 always,每進行一次 指令操作就記錄到 aof 檔案中一次。
2、通過 append 模式寫檔案,即使中途伺服器當機,可以通過 redis-check-aof 工具解決資料一緻性問題。
3、AOF 機制的 rewrite 模式。AOF 檔案沒被 rewrite 之前(檔案過大時會對指令 進行合并重寫),可以删除其中的某些指令(比如誤操作的 flushall))
缺點:
1、AOF 檔案比 RDB 檔案大,且恢複速度慢。
2、資料集大的時候,比 rdb 啟動效率低。
事務:
Redis的事務總是具有ACID中的一緻性和隔離性(單線程),其他特性是不支援的。當伺服器運作在AOF持久化模式下,并且appendfsync選項的值為always時,事務也具有耐久性。
哨兵:
哨兵用于實作 redis 叢集的高可用,本身也是分布式的,作為一個哨兵叢集去運作,互相協同工作。
故障轉移時,判斷一個 master node 是否當機了,需要大部分的哨兵都同意才行,涉及到了分布式選舉的問題。
即使部分哨兵節點挂掉了,哨兵叢集還是能正常工作的,因為如果一個作為高可用機制重要組成部分的故障轉移系統本身是單點的,那就很坑爹了。
哨兵的核心知識
哨兵至少需要 3 個執行個體,來保證自己的健壯性。
哨兵 + redis 主從的部署架構,是不保證資料零丢失的,隻能保證 redis 叢集的高可用性。
對于哨兵 + redis 主從這種複雜的部署架構,盡量在測試環境和生産環境,都進行充足的測試和演練。
緩存異常
緩存雪崩
緩存雪崩是指緩存同一時間大面積的失效,是以,後面的請求都會落到資料庫上,造成資料庫短時間内承受大量請求而崩掉。
解決方案
緩存資料的過期時間設定随機,防止同一時間大量資料過期現象發生。
一般并發量不是特别多的時候,使用最多的解決方案是加鎖排隊。
給每一個緩存資料增加相應的緩存标記,記錄緩存的是否失效,如果緩存标記失效,則更新資料緩存。
緩存穿透
緩存穿透是指緩存和資料庫中都沒有的資料,導緻所有的請求都落到資料庫上,造成資料庫短時間内承受大量請求而崩掉。
接口層增加校驗,如使用者鑒權校驗,id做基礎校驗,id<=0的直接攔截;
從緩存取不到的資料,在資料庫中也沒有取到,這時也可以将key-value對寫為key-null,緩存有效時間可以設定短點,如30秒(設定太長會導緻正常情況也沒法使用)。這樣可以防止攻擊使用者反複用同一個id暴力攻擊
采用布隆過濾器,将所有可能存在的資料哈希到一個足夠大的 bitmap 中,一個一定不存在的資料會被這個 bitmap 攔截掉,進而避免了對底層存儲系統的查詢壓力
附加
對于空間的利用到達了一種極緻,那就是Bitmap和布隆過濾器(Bloom Filter)。
Bitmap: 典型的就是哈希表
缺點是,Bitmap對于每個元素隻能記錄1bit資訊,如果還想完成額外的功能,恐怕隻能靠犧牲更多的空間、時間來完成了。
布隆過濾器(推薦)
就是引入了k(k>1)k(k>1)個互相獨立的哈希函數,保證在給定的空間、誤判率下,完成元素判重的過程。
它的優點是空間效率和查詢時間都遠遠超過一般的算法,缺點是有一定的誤識别率和删除困難。
Bloom-Filter算法的核心思想就是利用多個不同的Hash函數來解決“沖突”。
Hash存在一個沖突(碰撞)的問題,用同一個Hash得到的兩個URL的值有可能相同。為了減少沖突,我們可以多引入幾個Hash,如果通過其中的一個Hash值我們得出某元素不在集合中,那麼該元素肯定不在集合中。隻有在所有的Hash函數告訴我們該元素在集合中時,才能确定該元素存在于集合中。這便是Bloom-Filter的基本思想。
Bloom-Filter一般用于在大資料量的集合中判定某元素是否存在。
緩存擊穿
緩存擊穿是指緩存中沒有但資料庫中有的資料(一般是緩存時間到期),這時由于并發使用者特别多,同時讀緩存沒讀到資料,又同時去資料庫去取資料,引起資料庫壓力瞬間增大,造成過大壓力。和緩存雪崩不同的是,緩存擊穿指并發查同一條資料,緩存雪崩是不同資料都過期了,很多資料都查不到進而查資料庫。
設定熱點資料永遠不過期。
加互斥鎖,互斥鎖
緩存降級
服務降級的目的,是為了防止Redis服務故障,導緻資料庫跟着一起發生雪崩問題。是以,對于不重要的緩存資料,可以采取服務降級政策,例如一個比較常見的做法就是,Redis出現問題,不去資料庫查詢,而是直接傳回預設值給使用者。
如何保證緩存與資料庫雙寫時的資料一緻性?
你隻要用緩存,就可能會涉及到緩存與資料庫雙存儲雙寫,你隻要是雙寫,就一定會有資料一緻性的問題,那麼你如何解決一緻性問題?
一般來說,就是如果你的系統不是嚴格要求緩存+資料庫必須一緻性的話,緩存可以稍微的跟資料庫偶爾有不一緻的情況,最好不要做這個方案,讀請求和寫請求串行化,串到一個記憶體隊列裡去,這樣就可以保證一定不會出現不一緻的情況
串行化之後,就會導緻系統的吞吐量會大幅度的降低,用比正常情況下多幾倍的機器去支撐線上的一個請求。
還有一種方式就是可能會暫時産生不一緻的情況,但是發生的幾率特别小,就是
先更新資料庫,然後再删除緩存
Redis常見性能問題和解決方案?
Master最好不要做任何持久化工作,包括記憶體快照和AOF日志檔案,特别是不要啟用記憶體快照做持久化。
如果資料比較關鍵,某個Slave開啟AOF備份資料,政策為每秒同步一次。
為了主從複制的速度和連接配接的穩定性,Slave和Master最好在同一個區域網路内。
盡量避免在壓力較大的主庫上增加從庫
Master調用BGREWRITEAOF重寫AOF檔案,AOF在重寫的時候會占大量的CPU和記憶體資源,導緻服務load過高,出現短暫服務暫停現象。
為了Master的穩定性,主從複制不要用圖狀結構,用單向連結清單結構更穩定,即主從關系為:Master<–Slave1<–Slave2<–Slave3…,這樣的結構也友善解決單點故障問題,實作Slave對Master的替換,也即,如果Master挂了,可以立馬啟用Slave1做Master,其他不變。
使用Redis做過異步隊列嗎,是如何實作的
使用list類型儲存資料資訊,rpush生産消息,lpop消費消息,當lpop沒有消息時,可以sleep一段時間,然後再檢查有沒有資訊,如果不想sleep的話,可以使用blpop, 在沒有資訊的時候,會一直阻塞,直到資訊的到來。redis可以通過pub/sub主題訂閱模式實作一個生産者,多個消費者,當然也存在一定的缺點,當消費者下線時,生産的消息會丢失。
Redis如何實作延時隊列
使用sortedset,使用時間戳做score, 消息内容作為key,調用zadd來生産消息,消費者使用zrangbyscore擷取n秒之前的資料做輪詢處理。
Redis回收程序如何工作的?
一個用戶端運作了新的指令,添加了新的資料。
Redis檢查記憶體使用情況,如果大于maxmemory的限制, 則根據設定好的政策進行回收。
一個新的指令被執行,等等。
是以我們不斷地穿越記憶體限制的邊界,通過不斷達到邊界然後不斷地回收回到邊界以下。
如果一個指令的結果導緻大量記憶體被使用(例如很大的集合的交集儲存到一個新的鍵),不用多久記憶體限制就會被這個記憶體使用量超越。
Redis回收使用的是什麼算法?
LRU算法
項目中:
整合SpringBoot+MyBatis搭建基本骨架
本文主要講解mall整合SpringBoot+MyBatis搭建基本骨架,
以商品品牌為例實作基本的CRUD操作及通過PageHelper實作分頁查詢。
配置yaml
# 配置端口server: port: 80spring: # 配置資料源 datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/project?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false username: root password: root# mybatis-plus相關配置mybatis-plus: # 配置mapper的掃描,找到所有的mapper.xml映射檔案 mapper-locations: classpath*:mapper/*Mapper.xml # 搜尋指定包名 type-aliases-package: com.drj.entity configuration: # 這個配置會将執行的sql列印出來,在開發或測試的時候可以用 log-impl: org.apache.ibatis.logging.stdout.StdOutImpl#引入swaggerswagger: enable: true
mall整合Swagger-UI實作線上API文檔
Swagger-UI
是
Swagger-UI
HTML
Javascript
的一個集合,可以動态地根據注解生成線上
CSS
文檔。
API
常用注解
-
:用于修飾Controller類,生成Controller相關文檔資訊@Api
-
:用于修飾Controller類中的方法,生成接口方法相關文檔資訊@ApiOperation
-
:用于修飾接口中的參數,生成接口參數相關文檔資訊@ApiParam
-
:用于修飾實體類的屬性,當實體類是請求參數或傳回結果時,直接生成相關文檔資訊@ApiModelProperty
public Docket createRestApi(){ return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .select() //為目前包下controller生成API文檔 .apis(RequestHandlerSelectors.basePackage("com.macro.mall.tiny.controller")) //為有@Api注解的Controller生成API文檔 // .apis(RequestHandlerSelectors.withClassAnnotation(Api.class))//為有@ApiOperation注解的方法生成API文檔 // .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class)) .paths(PathSelectors.any()) .build(); }
3.整合SpringSecurity和JWT實作認證和授權(一)
本文主要講解整合和
SpringSecurity
實作背景使用者的登入和授權功能,同時改造
JWT
的配置使其可以自動記住登入令牌進行發送。
Swagger-UI
SpringSecurity
SpringSecurity是一個強大的可高度定制的認證和授權架構,對于Spring應用來說它是一套Web安全标準。SpringSecurity注重于為Java應用提供認證和授權功能,像所有的Spring項目一樣,它對自定義需求具有強大的擴充性。
JWT
JWT是JSON WEB TOKEN的縮寫,它是基于 RFC 7519 标準定義的一種可以安全傳輸的的JSON對象,由于使用了數字簽名,是以是可信任和安全的。
JWT的組成
- JWT token的格式:header.payload.signature
- header中用于存放簽名的生成算法
{"alg": "HS512"}Copy to clipboardErrorCopied
- payload中用于存放使用者名、token的生成時間和過期時間
{"sub":"admin","created":1489079981393,"exp":1489684781}Copy to clipboardErrorCopied
- signature為以header和payload生成的簽名,一旦header和payload被篡改,驗證将失敗
//secret為加密算法的密鑰String signature = HMACSHA512(base64UrlEncode(header) + "." +base64UrlEncode(payload),s
JWT實作認證和授權的原理
- 使用者調用登入接口,登入成功後擷取到JWT的token;
- 之後使用者每次調用接口都在http的header中添加一個叫Authorization的頭,值為JWT的token;
- 背景程式通過對Authorization頭中資訊的解碼及數字簽名校驗來擷取其中的使用者資訊,進而實作認證和授權。
僅需四步,整合SpringSecurity+JWT實作登入認證!
學習過我的mall項目的應該知道,子產品是使用SpringSecurity+JWT來實作登入認證的,而
mall-admin
子產品是使用的SpringSecurity基于Session的預設機制來實作登陸認證的。很多小夥伴都找不到
mall-portal
的登入接口,最近我把這兩個子產品的登入認證給統一了,都使用SpringSecurity+JWT的形式實作。 主要是通過把登入認證的通用邏輯抽取到了
mall-portal
子產品來實作的,下面我們講講如何使用
mall-security
子產品來實作登入認證,僅需四步即可。
mall-security
整合步驟
這裡我們以 mall-portal
改造為例來說說如何實作。
- 第一步,給需要登入認證的子產品添加
依賴:mall-security
<dependency> <groupId>com.macro.mall</groupId> <artifactId>mall-security</artifactId></dependency>Copy to clipboardErrorCopied
- 第二步,添加MallSecurityConfig配置類,繼承
中的SecurityConfig配置,并且配置一個UserDetailsService接口的實作類,用于擷取登入使用者詳情:mall-security
/** * mall-security子產品相關配置 * Created by macro on 2019/11/5. */@Configuration@EnableWebSecurity@EnableGlobalMethodSecurity(prePostEnabled=true)public class MallSecurityConfig extends SecurityConfig { @Autowired private UmsMemberService memberService; @Bean public UserDetailsService userDetailsService() { //擷取登入使用者資訊 return username -> memberService.loadUserByUsername(username); }}Copy to clipboardErrorCopied
- 第三步,在application.yml中配置下不需要安全保護的資源路徑:
secure: ignored: urls: #安全路徑白名單 - /swagger-ui.html - /swagger-resources/** - /swagger/** - /**/v2/api-docs - /**/*.js - /**/*.css - /**/*.png - /**/*.ico - /webjars/springfox-swagger-ui/** - /druid/** - /actuator/** - /sso/** - /home/**Copy to clipboardErrorCopied
- 第四步,在UmsMemberController中實作登入和重新整理token的接口:
/** * 會員登入注冊管理Controller * Created by macro on 2018/8/3. */@Controller@Api(tags = "UmsMemberController", description = "會員登入注冊管理")@RequestMapping("/sso")public class UmsMemberController { @Value("${jwt.tokenHeader}") private String tokenHeader; @Value("${jwt.tokenHead}") private String tokenHead; @Autowired private UmsMemberService memberService; @ApiOperation("會員登入") @RequestMapping(value = "/login", method = RequestMethod.POST) @ResponseBody public CommonResult login(@RequestParam String username, @RequestParam String password) { String token = memberService.login(username, password); if (token == null) { return CommonResult.validateFailed("使用者名或密碼錯誤"); } Map<String, String> tokenMap = new HashMap<>(); tokenMap.put("token", token); tokenMap.put("tokenHead", tokenHead); return CommonResult.success(tokenMap); } @ApiOperation(value = "重新整理token") @RequestMapping(value = "/refreshToken", method = RequestMethod.GET) @ResponseBody public CommonResult refreshToken(HttpServletRequest request) { String token = request.getHeader(tokenHeader); String refreshToken = memberService.refreshToken(token); if (refreshToken == null) { return CommonResult.failed("token已經過期!"); } Map<String, String> tokenMap = new HashMap<>(); tokenMap.put("token", refreshToken); tokenMap.put("tokenHead", tokenHead); return CommonResult.success(tokenMap); }}Copy to clipboardErrorCopied
實作原理
将SpringSecurity+JWT的代碼封裝成通用子產品後,就可以友善其他需要登入認證的子產品來使用,下面我們來看看它是如何實作的,首先我們看下 mall-security
的目錄結構。
目錄結構
mall-security├── component| ├── JwtAuthenticationTokenFilter -- JWT登入授權過濾器| ├── RestAuthenticationEntryPoint -- 自定義傳回結果:未登入或登入過期| └── RestfulAccessDeniedHandler -- 自定義傳回結果:沒有權限通路時├── config| ├── IgnoreUrlsConfig -- 用于配置不需要安全保護的資源路徑| └── SecurityConfig -- SpringSecurity通用配置└── util └── JwtTokenUtil -- JWT的token處理工具類Copy to clipboardErrorCopied
做了哪些變化
其實我也就添加了兩個類,一個IgnoreUrlsConfig,用于從application.yml中擷取不需要安全保護的資源路徑。一個SecurityConfig提取了一些SpringSecurity的通用配置。
- IgnoreUrlsConfig中的代碼:
/** * 用于配置不需要保護的資源路徑 * Created by macro on 2018/11/5. */@Getter@Setter@ConfigurationProperties(prefix = "secure.ignored")public class IgnoreUrlsConfig { private List<String> urls = new ArrayList<>();}Copy to clipboardErrorCopied
- SecurityConfig中的代碼:
/** * 對SpringSecurity的配置的擴充,支援自定義白名單資源路徑和查詢使用者邏輯 * Created by macro on 2019/11/5. */public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity httpSecurity) throws Exception { ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = httpSecurity .authorizeRequests(); //不需要保護的資源路徑允許通路 for (String url : ignoreUrlsConfig().getUrls()) { registry.antMatchers(url).permitAll(); } //允許跨域請求的OPTIONS請求 registry.antMatchers(HttpMethod.OPTIONS) .permitAll(); // 任何請求需要身份認證 registry.and() .authorizeRequests() .anyRequest() .authenticated() // 關閉跨站請求防護及不使用session .and() .csrf() .disable() .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 自定義權限拒絕處理類 .and() .exceptionHandling() .accessDeniedHandler(restfulAccessDeniedHandler()) .authenticationEntryPoint(restAuthenticationEntryPoint()) // 自定義權限攔截器JWT過濾器 .and() .addFilterBefore(jwtAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService()) .passwordEncoder(passwordEncoder()); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean public JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter() { return new JwtAuthenticationTokenFilter(); } @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Bean public RestfulAccessDeniedHandler restfulAccessDeniedHandler() { return new RestfulAccessDeniedHandler(); } @Bean public RestAuthenticationEntryPoint restAuthenticationEntryPoint() { return new RestAuthenticationEntryPoint(); } @Bean public IgnoreUrlsConfig ignoreUrlsConfig() { return new IgnoreUrlsConfig(); } @Bean public JwtTokenUtil jwtTokenUtil() { return new JwtTokenUtil(); }}