|
大家好,我是庫森。
8 y$ g; }% f3 s?低曌鳛橐患殷w面廠,我們來看看今年海康威視的校招薪資開了多少?
1 M h8 C, i8 |$ X我根據(jù)一些同學(xué)的反饋,整理了?低曑浖_發(fā)崗位的校招薪資,在目前的就業(yè)背景下,?低暤男U行劫Y還是很體面的。
% \4 Q7 U6 V/ N* E" a# G; R7 i( H3 n14k x 15 = 21w(本科 985,武漢)15k x 15 = 22.5w(碩士雙一流,杭州)16 x 15 = 24w (本碩 211,杭州)19 x 15 = 28.5w (本碩 211,杭州)那海康威視的面試難度如何呢?
7 F/ G3 P5 V4 N我也找了一位今年秋招面海康威視同學(xué)的面經(jīng),給大家做做參考參考,總共 1 輪技術(shù)面 + 1 輪 HR 面,3-5 個工作日出結(jié)果。
k7 S, r0 S) \4 E) M3 @一面是技術(shù)面,問的問題不算多,主要拷打了 Java、MySQL、Redis 方面的八股文,都屬于經(jīng)典的面試問題,不算難。
& u1 X0 S7 j6 v5 s1 T
fmvmujcputf64078576854.jpg (110.84 KB, 下載次數(shù): 0)
下載附件
保存到相冊
fmvmujcputf64078576854.jpg
2 小時前 上傳
+ k* q& t- ^+ n" d z/ e& L
Java 介紹一下 Spring Boot 整體的啟動流程?首先從main找到run()方法,在執(zhí)行run()方法之前new一個SpringApplication對象進(jìn)入run()方法,創(chuàng)建應(yīng)用監(jiān)聽器SpringApplicationRunListeners開始監(jiān)聽然后加載SpringBoot配置環(huán)境(ConfigurableEnvironment),然后把配置環(huán)境(Environment)加入監(jiān)聽對象中然后加載應(yīng)用上下文(ConfigurableApplicationContext),當(dāng)做run方法的返回對象最后創(chuàng)建Spring容器,refreshContext(context),實現(xiàn)starter自動化配置和bean的實例化等工作。[/ol]說一說 Spring MVC 整體的執(zhí)行流程?
" k, a8 i1 G& p4 b. }! d# L- D3 F
cxzzckxp1pl64078576954.jpg (128.48 KB, 下載次數(shù): 0)
下載附件
保存到相冊
cxzzckxp1pl64078576954.jpg
2 小時前 上傳
0 k6 E6 I' j T" r/ l9 h( b流程圖步驟詳解:發(fā)送請求:用戶發(fā)送的所有請求都會到前端控制器DispatcherServlet請求查找Handler:DispatcherServlet收到請求會調(diào)用HandlerMapping(處理器映射器)查找Handler返回Handler:處理器映射器根據(jù)url返回具體的處理器,生成HandlerExecutionChain對象,其中包含了目標(biāo)Handler和若干攔截器(可能沒有)請求調(diào)用Handler:DispatcherServlet通過Handler尋找匹配到HandlerAdapter執(zhí)行Handler:HandlerAdapter調(diào)用Handler返回結(jié)果:Handler執(zhí)行完成,返回一個ModelAndView對象返回結(jié)果給DispatcherServlet:HandlerAdapter將Handler執(zhí)行結(jié)果ModelAndView返回給DispatcherServlet如果Handler返回的View是邏輯視圖名稱而不是真正的View對象,DispatcherServlet調(diào)用resolveViewName方法在配置的所有視圖解析器(ViewResolver)中,尋找合適的,最終通過ViewResolver將邏輯視圖名解析成真正的View對象ViewResolver通過調(diào)用createView方法嘗試將視圖名解析成View,如果無法解析會返回Null(注: 如果ViewResolver是派生自AbstractCachingViewResolver則在調(diào)用createView方法前會先嘗試根據(jù)viewName和Iocale從緩存中查找對應(yīng)的視圖對象)DispatcherServlet調(diào)用View的render方法進(jìn)行渲染視圖 (即將模型數(shù)據(jù)填充至request域)DispatcherServlet響應(yīng)用戶[/ol]MySQL MySQL 索引的機制,類型有哪些?MySQL可以按照四個角度來分類索引。* z" L1 U% R$ f# [$ l
按「數(shù)據(jù)結(jié)構(gòu)」分類:B+tree索引、Hash索引、Full-text索引。按「物理存儲」分類:聚簇索引(主鍵索引)、二級索引(輔助索引)。按「字段特性」分類:主鍵索引、唯一索引、普通索引、前綴索引。按「字段個數(shù)」分類:單列索引、聯(lián)合索引。接下來,按照這些角度來說說各類索引的特點。5 Z5 A& |4 h/ b. K s( ?. v0 i' n& a
按數(shù)據(jù)結(jié)構(gòu)分類
5 H, _7 m8 ?0 K4 [0 v/ ~從數(shù)據(jù)結(jié)構(gòu)的角度來看,MySQL 常見索引有 B+Tree 索引、HASH 索引、Full-Text 索引。
7 B. l+ X+ c- J( \% z: S每一種存儲引擎支持的索引類型不一定相同,我在表中總結(jié)了 MySQL 常見的存儲引擎 InnoDB、MyISAM 和 Memory 分別支持的索引類型。! v1 Q0 i' [2 R8 ]* W4 {
zjro0uixxki64078577054.jpg (95.02 KB, 下載次數(shù): 0)
下載附件
保存到相冊
zjro0uixxki64078577054.jpg
2 小時前 上傳
' I- H$ t: X$ ?7 T: G" @* Z# X. a WInnoDB 是在 MySQL 5.5 之后成為默認(rèn)的 MySQL 存儲引擎,B+Tree 索引類型也是 MySQL 存儲引擎采用最多的索引類型。2 z A. s; H2 u" D! q9 c4 A
在創(chuàng)建表時,InnoDB 存儲引擎會根據(jù)不同的場景選擇不同的列作為索引: g! t8 K- F5 Z, [+ w
如果有主鍵,默認(rèn)會使用主鍵作為聚簇索引的索引鍵(key);如果沒有主鍵,就選擇第一個不包含 NULL 值的唯一列作為聚簇索引的索引鍵(key);在上面兩個都沒有的情況下,InnoDB 將自動生成一個隱式自增 id 列作為聚簇索引的索引鍵(key);其它索引都屬于輔助索引(Secondary Index),也被稱為二級索引或非聚簇索引。創(chuàng)建的主鍵索引和二級索引默認(rèn)使用的是 B+Tree 索引。. D7 {+ }- S$ ^
按物理存儲分類' c! g2 y1 d J* n1 J: C* \
從物理存儲的角度來看,索引分為聚簇索引(主鍵索引)、二級索引(輔助索引)。
5 F" \8 }3 T& {& E4 N3 Q這兩個區(qū)別在前面也提到了:
" N2 O- H6 r: Q7 Z4 V/ O2 v8 _1 e主鍵索引的 B+Tree 的葉子節(jié)點存放的是實際數(shù)據(jù),所有完整的用戶記錄都存放在主鍵索引的 B+Tree 的葉子節(jié)點里;二級索引的 B+Tree 的葉子節(jié)點存放的是主鍵值,而不是實際數(shù)據(jù)。所以,在查詢時使用了二級索引,如果查詢的數(shù)據(jù)能在二級索引里查詢的到,那么就不需要回表,這個過程就是覆蓋索引。如果查詢的數(shù)據(jù)不在二級索引里,就會先檢索二級索引,找到對應(yīng)的葉子節(jié)點,獲取到主鍵值后,然后再檢索主鍵索引,就能查詢到數(shù)據(jù)了,這個過程就是回表。
6 w4 m1 J8 n' e- B按字段特性分類
! \6 i2 W# m* q3 g從字段特性的角度來看,索引分為主鍵索引、唯一索引、普通索引、前綴索引。
) g* ?$ r S$ n% [% S `7 C' j主鍵索引主鍵索引就是建立在主鍵字段上的索引,通常在創(chuàng)建表的時候一起創(chuàng)建,一張表最多只有一個主鍵索引,索引列的值不允許有空值。& e; x) }; v. @% q# H# B, `3 P
在創(chuàng)建表時,創(chuàng)建主鍵索引的方式如下:
6 r2 a! X% P- b0 bCREATE TABLE table_name (
6 a1 }( K. D5 [) K) l ....
5 X& F r1 ^; \4 H; ~ PRIMARY KEY (index_column_1) USING BTREE# Z% g! m( ?1 r4 P% q1 L
);' `0 ^0 U- [ a; @0 s- @6 [1 }: e
唯一索引唯一索引建立在 UNIQUE 字段上的索引,一張表可以有多個唯一索引,索引列的值必須唯一,但是允許有空值。
: {7 A" V5 i, X在創(chuàng)建表時,創(chuàng)建唯一索引的方式如下:
3 D7 I9 u* m# k) R4 ~& W$ e# sCREATE TABLE table_name (9 ?! \9 X; l5 }4 N- E5 Q
....1 Q" D2 r- w& Q: Y
UNIQUE KEY(index_column_1,index_column_2,...) ( A, W1 d) ~0 F
);
3 T$ S. z0 t/ h/ j. [; \建表后,如果要創(chuàng)建唯一索引,可以使用這面這條命令:9 M4 y% `/ ~0 b; c, V' S1 \& T
CREATE UNIQUE INDEX index_name3 a" e$ n0 g8 K u
ON table_name(index_column_1,index_column_2,...);* ]2 T8 W" ?) @, G" c
普通索引普通索引就是建立在普通字段上的索引,既不要求字段為主鍵,也不要求字段為 UNIQUE。& _: v1 `) K6 ?5 l2 G
在創(chuàng)建表時,創(chuàng)建普通索引的方式如下:
+ J& } j1 |* |CREATE TABLE table_name (8 r3 l1 I! C+ c- v# h* O+ l
....9 T, g2 M5 t; x* R0 h, |. c
INDEX(index_column_1,index_column_2,...)
( b p* E8 T' W- G% ?);
! {* U. l; _, X- p( @建表后,如果要創(chuàng)建普通索引,可以使用這面這條命令:
! B" M Y( _, ]CREATE INDEX index_name! ]! K! q# Q2 u' x! p7 v& g
ON table_name(index_column_1,index_column_2,...);
# g+ }& @3 K+ ]. @3 D前綴索引前綴索引是指對字符類型字段的前幾個字符建立的索引,而不是在整個字段上建立的索引,前綴索引可以建立在字段類型為 char、 varchar、binary、varbinary 的列上。6 Y- g$ A0 q" w: q/ b9 n* L
使用前綴索引的目的是為了減少索引占用的存儲空間,提升查詢效率。
: W! E( ^/ \7 C/ K在創(chuàng)建表時,創(chuàng)建前綴索引的方式如下:3 d- n" H& G3 H7 h+ k" N6 r
CREATE TABLE table_name(
, e8 C" ~8 n/ @: F column_list,
5 O* w `7 J# j; r0 q8 e INDEX(column_name(length))
) Y' u; G% L+ V% Z5 f N. V);& u9 G, P4 K. t B* B' j0 S7 u$ k
建表后,如果要創(chuàng)建前綴索引,可以使用這面這條命令:
x/ j9 n9 v/ s4 v9 ICREATE INDEX index_name: |6 W. j+ g" Z6 V8 W& L5 k+ l
ON table_name(column_name(length));
* C2 S# T% y/ f" g8 C按字段個數(shù)分類" t5 Y3 Q1 L2 Q$ y& q" q
從字段個數(shù)的角度來看,索引分為單列索引、聯(lián)合索引(復(fù)合索引)。$ ?, u$ P4 u$ s0 X* _
建立在單列上的索引稱為單列索引,比如主鍵索引;建立在多列上的索引稱為聯(lián)合索引;通過將多個字段組合成一個索引,該索引就被稱為聯(lián)合索引。
' `0 y8 t; [9 p5 y3 l/ `1 E C- ~& ~比如,將商品表中的 product_no 和 name 字段組合成聯(lián)合索引(product_no, name),創(chuàng)建聯(lián)合索引的方式如下:7 }+ B4 z- s0 E- L
CREATE INDEX index_product_no_name ON product(product_no, name);
7 ^+ V: ]5 J' v( M聯(lián)合索引(product_no, name) 的 B+Tree 示意圖如下(圖中葉子節(jié)點之間我畫了單向鏈表,但是實際上是雙向鏈表,原圖我找不到了,修改不了,偷個懶我不重畫了,大家腦補成雙向鏈表就行)。8 ]5 l4 m- ?% j" c- H; s, @
ibea4h5cthi64078577154.jpg (125.28 KB, 下載次數(shù): 0)
下載附件
保存到相冊
ibea4h5cthi64078577154.jpg
2 小時前 上傳
. Y+ l/ p- W% F) s- u) F3 F
可以看到,聯(lián)合索引的非葉子節(jié)點用兩個字段的值作為 B+Tree 的 key 值。當(dāng)在聯(lián)合索引查詢數(shù)據(jù)時,先按 product_no 字段比較,在 product_no 相同的情況下再按 name 字段比較。. u. Q3 m4 ], A7 p- O% u; |
也就是說,聯(lián)合索引查詢的 B+Tree 是先按 product_no 進(jìn)行排序,然后再 product_no 相同的情況再按 name 字段排序。; ~& ] M! s3 o) ?9 T: `, J
因此,使用聯(lián)合索引時,存在最左匹配原則,也就是按照最左優(yōu)先的方式進(jìn)行索引的匹配。在使用聯(lián)合索引進(jìn)行查詢的時候,如果不遵循「最左匹配原則」,聯(lián)合索引會失效,這樣就無法利用到索引快速查詢的特性了。
3 A9 [+ d# {: n6 u6 F- r比如,如果創(chuàng)建了一個 (a, b, c) 聯(lián)合索引,如果查詢條件是以下這幾種,就可以匹配上聯(lián)合索引:, b1 ^- t- U; \2 [6 w
where a=1;where a=1 and b=2 and c=3;where a=1 and b=2;需要注意的是,因為有查詢優(yōu)化器,所以 a 字段在 where 子句的順序并不重要。! j; p+ t$ P; V) `( w7 e( x2 ^. F# O
但是,如果查詢條件是以下這幾種,因為不符合最左匹配原則,所以就無法匹配上聯(lián)合索引,聯(lián)合索引就會失效:) n! q2 ?" r9 h/ e) z* I
where b=2;where c=3;where b=2 and c=3;上面這些查詢條件之所以會失效,是因為(a, b, c) 聯(lián)合索引,是先按 a 排序,在 a 相同的情況再按 b 排序,在 b 相同的情況再按 c 排序。所以,b 和 c 是全局無序,局部相對有序的,這樣在沒有遵循最左匹配原則的情況下,是無法利用到索引的。& b' [1 G& C2 z3 i* \
聯(lián)合索引有一些特殊情況,并不是查詢過程使用了聯(lián)合索引查詢,就代表聯(lián)合索引中的所有字段都用到了聯(lián)合索引進(jìn)行索引查詢,也就是可能存在部分字段用到聯(lián)合索引的 B+Tree,部分字段沒有用到聯(lián)合索引的 B+Tree 的情況。
# z4 _" C. `9 W. e% C這種特殊情況就發(fā)生在范圍查詢。聯(lián)合索引的最左匹配原則會一直向右匹配直到遇到「范圍查詢」就會停止匹配。也就是范圍查詢的字段可以用到聯(lián)合索引,但是在范圍查詢字段的后面的字段無法用到聯(lián)合索引。
) g2 `3 c1 C' C. v; f- c; e有無排查索引失效的經(jīng)驗,展開講講?可以使用 EXPLAIN 來查看 SQL 的執(zhí)行計劃,判斷SQL是否走了索引,如果沒有走索引,就代表索引發(fā)生失效了。
9 l2 G8 O4 K) m+ W& H, R0 [如下圖,就是一個沒有使用索引,并且是一個全表掃描的查詢語句。
, u) A$ h A. _* E4 e1 A
53a05fgp4c264078577254.jpg (61.98 KB, 下載次數(shù): 0)
下載附件
保存到相冊
53a05fgp4c264078577254.jpg
2 小時前 上傳
! g% K5 n. b, j& ]- s3 |對于執(zhí)行計劃,參數(shù)有:
6 ?# N0 T# {& g; W6 dpossible_keys 字段表示可能用到的索引;key 字段表示實際用的索引,如果這一項為 NULL,說明沒有使用索引;key_len 表示索引的長度;rows 表示掃描的數(shù)據(jù)行數(shù)。type 表示數(shù)據(jù)掃描類型,我們需要重點看這個。type 字段就是描述了找到所需數(shù)據(jù)時使用的掃描方式是什么,常見掃描類型的執(zhí)行效率從低到高的順序為:6 ~& d* D: d3 b7 g3 s
All(全表掃描):在這些情況里,all 是最壞的情況,因為采用了全表掃描的方式。index(全索引掃描):index 和 all 差不多,只不過 index 對索引表進(jìn)行全掃描,這樣做的好處是不再需要對數(shù)據(jù)進(jìn)行排序,但是開銷依然很大。所以,要盡量避免全表掃描和全索引掃描。range(索引范圍掃描):range 表示采用了索引范圍掃描,一般在 where 子句中使用 、in、between 等關(guān)鍵詞,只檢索給定范圍的行,屬于范圍查找。從這一級別開始,索引的作用會越來越明顯,因此我們需要盡量讓 SQL 查詢可以使用到 range 這一級別及以上的 type 訪問方式。ref(非唯一索引掃描):ref 類型表示采用了非唯一索引,或者是唯一索引的非唯一性前綴,返回數(shù)據(jù)返回可能是多條。因為雖然使用了索引,但該索引列的值并不唯一,有重復(fù)。這樣即使使用索引快速查找到了第一條數(shù)據(jù),仍然不能停止,要進(jìn)行目標(biāo)值附近的小范圍掃描。但它的好處是它并不需要掃全表,因為索引是有序的,即便有重復(fù)值,也是在一個非常小的范圍內(nèi)掃描。eq_ref(唯一索引掃描):eq_ref 類型是使用主鍵或唯一索引時產(chǎn)生的訪問方式,通常使用在多表聯(lián)查中。比如,對兩張表進(jìn)行聯(lián)查,關(guān)聯(lián)條件是兩張表的 user_id 相等,且 user_id 是唯一索引,那么使用 EXPLAIN 進(jìn)行執(zhí)行計劃查看的時候,type 就會顯示 eq_ref。const(結(jié)果只有一條的主鍵或唯一索引掃描):const 類型表示使用了主鍵或者唯一索引與常量值進(jìn)行比較,比如 select name from product where id=1。需要說明的是 const 類型和 eq_ref 都使用了主鍵或唯一索引,不過這兩個類型有所區(qū)別,const 是與常量進(jìn)行比較,查詢效率會更快,而 eq_ref 通常用于多表聯(lián)查中。extra 顯示的結(jié)果,這里說幾個重要的參考指標(biāo):( a$ ^' \/ W& ^
Using filesort :當(dāng)查詢語句中包含 group by 操作,而且無法利用索引完成排序操作的時候, 這時不得不選擇相應(yīng)的排序算法進(jìn)行,甚至可能會通過文件排序,效率是很低的,所以要避免這種問題的出現(xiàn)。Using temporary:使了用臨時表保存中間結(jié)果,MySQL 在對查詢結(jié)果排序時使用臨時表,常見于排序 order by 和分組查詢 group by。效率低,要避免這種問題的出現(xiàn)。Using index:所需數(shù)據(jù)只需在索引即可全部獲得,不須要再到表中取數(shù)據(jù),也就是使用了覆蓋索引,避免了回表操作,效率不錯。索引失效的場景有哪些?會發(fā)生索引失效的情況:
6 m( w" j" g1 ?當(dāng)我們使用左或者左右模糊匹配的時候,也就是 like %xx 或者 like %xx%這兩種方式都會造成索引失效;當(dāng)我們在查詢條件中對索引列使用函數(shù),就會導(dǎo)致索引失效。當(dāng)我們在查詢條件中對索引列進(jìn)行表達(dá)式計算,也是無法走索引的。MySQL 在遇到字符串和數(shù)字比較的時候,會自動把字符串轉(zhuǎn)為數(shù)字,然后再進(jìn)行比較。如果字符串是索引列,而條件語句中的輸入?yún)?shù)是數(shù)字的話,那么索引列會發(fā)生隱式類型轉(zhuǎn)換,由于隱式類型轉(zhuǎn)換是通過 CAST 函數(shù)實現(xiàn)的,等同于對索引列使用了函數(shù),所以就會導(dǎo)致索引失效。聯(lián)合索引要能正確使用需要遵循最左匹配原則,也就是按照最左優(yōu)先的方式進(jìn)行索引的匹配,否則就會導(dǎo)致索引失效。在 WHERE 子句中,如果在 OR 前的條件列是索引列,而在 OR 后的條件列不是索引列,那么索引會失效。Redis Redis 為什么這么快?官方使用基準(zhǔn)測試的結(jié)果是,單線程的 Redis 吞吐量可以達(dá)到 10W/每秒,如下圖所示:# Y5 ^- m" G L; i' A
h1twxfhtg3w64078577354.jpg (86.12 KB, 下載次數(shù): 0)
下載附件
保存到相冊
h1twxfhtg3w64078577354.jpg
2 小時前 上傳
9 ]+ `/ t; P2 c) h* f2 p7 a, s之所以 Redis 采用單線程(網(wǎng)絡(luò) I/O 和執(zhí)行命令)那么快,有如下幾個原因:
5 a9 Q0 ~3 ^0 c: O" S6 _$ J" ~Redis 的大部分操作都在內(nèi)存中完成,并且采用了高效的數(shù)據(jù)結(jié)構(gòu),因此 Redis 瓶頸可能是機器的內(nèi)存或者網(wǎng)絡(luò)帶寬,而并非 CPU,既然 CPU 不是瓶頸,那么自然就采用單線程的解決方案了;Redis 采用單線程模型可以避免了多線程之間的競爭,省去了多線程切換帶來的時間和性能上的開銷,而且也不會導(dǎo)致死鎖問題。Redis 采用了 I/O 多路復(fù)用機制處理大量的客戶端 Socket 請求,IO 多路復(fù)用機制是指一個線程處理多個 IO 流,就是我們經(jīng)常聽到的 select/epoll 機制。簡單來說,在 Redis 只運行單線程的情況下,該機制允許內(nèi)核中,同時存在多個監(jiān)聽 Socket 和已連接 Socket。內(nèi)核會一直監(jiān)聽這些 Socket 上的連接請求或數(shù)據(jù)請求。一旦有請求到達(dá),就會交給 Redis 線程處理,這就實現(xiàn)了一個 Redis 線程處理多個 IO 流的效果。Redis 6.0 之后為什么引入了多線程?Redis 單線程指的是「接收客戶端請求->解析請求 ->進(jìn)行數(shù)據(jù)讀寫等操作->發(fā)送數(shù)據(jù)給客戶端」這個過程是由一個線程(主線程)來完成的,這也是我們常說 Redis 是單線程的原因。
9 h1 x- {" I- B但是,Redis 程序并不是單線程的,Redis 在啟動的時候,是會啟動后臺線程(BIO)的:( K9 s/ R6 i- z
Redis 在 2.6 版本,會啟動 2 個后臺線程,分別處理關(guān)閉文件、AOF 刷盤這兩個任務(wù);Redis 在 4.0 版本之后,新增了一個新的后臺線程,用來異步釋放 Redis 內(nèi)存,也就是 lazyfree 線程。例如執(zhí)行 unlink key / flushdb async / flushall async 等命令,會把這些刪除操作交給后臺線程來執(zhí)行,好處是不會導(dǎo)致 Redis 主線程卡頓。因此,當(dāng)我們要刪除一個大 key 的時候,不要使用 del 命令刪除,因為 del 是在主線程處理的,這樣會導(dǎo)致 Redis 主線程卡頓,因此我們應(yīng)該使用 unlink 命令來異步刪除大key。之所以 Redis 為「關(guān)閉文件、AOF 刷盤、釋放內(nèi)存」這些任務(wù)創(chuàng)建單獨的線程來處理,是因為這些任務(wù)的操作都是很耗時的,如果把這些任務(wù)都放在主線程來處理,那么 Redis 主線程就很容易發(fā)生阻塞,這樣就無法處理后續(xù)的請求了。
5 c0 Q/ X) D& r% w/ {5 e8 `( O后臺線程相當(dāng)于一個消費者,生產(chǎn)者把耗時任務(wù)丟到任務(wù)隊列中,消費者(BIO)不停輪詢這個隊列,拿出任務(wù)就去執(zhí)行對應(yīng)的方法即可。' E# f# _! }1 C/ R( a
znfflvyj2bj64078577454.jpg (142.24 KB, 下載次數(shù): 0)
下載附件
保存到相冊
znfflvyj2bj64078577454.jpg
2 小時前 上傳
" S0 |4 p4 O: B, y0 q雖然 Redis 的主要工作(網(wǎng)絡(luò) I/O 和執(zhí)行命令)一直是單線程模型,但是在 Redis 6.0 版本之后,也采用了多個 I/O 線程來處理網(wǎng)絡(luò)請求,這是因為隨著網(wǎng)絡(luò)硬件的性能提升,Redis 的性能瓶頸有時會出現(xiàn)在網(wǎng)絡(luò) I/O 的處理上。
: b+ o5 D- s4 F' S; d0 O所以為了提高網(wǎng)絡(luò) I/O 的并行度,Redis 6.0 對于網(wǎng)絡(luò) I/O 采用多線程來處理。但是對于命令的執(zhí)行,Redis 仍然使用單線程來處理,所以大家不要誤解Redis 有多線程同時執(zhí)行命令。
- W& [2 F0 q# q9 iRedis 官方表示,Redis 6.0 版本引入的多線程 I/O 特性對性能提升至少是一倍以上。
! N) R. s* i0 w; z; l0 IRedis 6.0 版本支持的 I/O 多線程特性,默認(rèn)情況下 I/O 多線程只針對發(fā)送響應(yīng)數(shù)據(jù)(write client socket),并不會以多線程的方式處理讀請求(read client socket)。要想開啟多線程處理客戶端讀請求,就需要把 Redis.conf 配置文件中的 io-threads-do-reads 配置項設(shè)為 yes。
1 @- p: ]$ ]- W* q+ D" t, S//讀請求也使用io多線程
8 l1 |* p( {2 Pio-threads-do-reads yes
F# B( k6 r, E! Y. x9 S: B4 M, S( Q同時, Redis.conf 配置文件中提供了 IO 多線程個數(shù)的配置項。
) Z+ o* ~3 Q6 H0 S// io-threads N,表示啟用 N-1 個 I/O 多線程(主線程也算一個 I/O 線程)
& S/ K( q, }6 m5 o9 xio-threads 4
% G0 \6 X8 X0 ?6 M s" d c$ w關(guān)于線程數(shù)的設(shè)置,官方的建議是如果為 4 核的 CPU,建議線程數(shù)設(shè)置為 2 或 3,如果為 8 核 CPU 建議線程數(shù)設(shè)置為 6,線程數(shù)一定要小于機器核數(shù),線程數(shù)并不是越大越好。
G0 P: f& l6 f7 l9 G( ~2 ~1 Q因此, Redis 6.0 版本之后,Redis 在啟動的時候,默認(rèn)情況下會額外創(chuàng)建 6 個線程(這里的線程數(shù)不包括主線程):
* V" T. c) f2 ?8 d9 d# ?1 i: j7 SRedis-server :Redis的主線程,主要負(fù)責(zé)執(zhí)行命令;bio_close_file、bio_aof_fsync、bio_lazy_free:三個后臺線程,分別異步處理關(guān)閉文件任務(wù)、AOF刷盤任務(wù)、釋放內(nèi)存任務(wù);io_thd_1、io_thd_2、io_thd_3:三個 I/O 線程,io-threads 默認(rèn)是 4 ,所以會啟動 3(4-1)個 I/O 多線程,用來分擔(dān) Redis 網(wǎng)絡(luò) I/O 的壓力。Redis 分布式鎖怎么解決超賣問題的?同一個鎖key,同一時間只能有一個客戶端拿到鎖,其他客戶端會陷入無限的等待來嘗試獲取那個鎖,只有獲取到鎖的客戶端才能執(zhí)行下面的業(yè)務(wù)邏輯。- l3 t3 f6 [9 W8 ~1 ~
比如說,用戶要一次性買 10 臺手機,那么避免超賣的流程如下:% ]8 D' x" f J9 \! Q' L
只有一個訂單系統(tǒng)實例可以成功加分布式鎖,然后只有他一個實例可以查庫存、判斷庫存是否充足、下單扣減庫存,接著釋放鎖。釋放鎖之后,另外一個訂單系統(tǒng)實例才能加鎖,接著查庫存,一下發(fā)現(xiàn)庫存只有 2 個了,庫存不足,無法購買,下單失敗,不會將庫存扣減為-8的,就避免超賣的問題。這種方案的缺點是同一個商品在多用戶同時下單的情況下,會基于分布式鎖串行化處理,導(dǎo)致沒法同時處理同一個商品的大量下單的請求。 |
|