厲害了!京東要新招 16000 人
共 10342字,需瀏覽 21分鐘
·
2024-08-02 16:26
圖解學習網(wǎng)站:https://xiaolincoding.com
大家好,我是小林。
前幾個星期,我還剛說完美團今年秋招招 6000 人,是校招大戶。
結果,昨天看到京東秋招也開始了,好家伙,沒想到開設的就業(yè)崗位更多!
累計提供 10000+ 就業(yè)崗位,再加上 6000+實習崗位,累計起來就是 1.6w 人招聘需求,今年難得有這么大規(guī)模的招人需求。
截圖來自京東招聘公眾號
更關鍵的是,將并提供極具競爭力的薪酬待遇,之前我看到有同學跟我反饋,他在京東實習,就開了 1.2w 實習薪資,比不少一線大廠的實習薪資都高。
注意,這個是實習薪資,這個實習薪資在小公司,都能招一個 3 年左右的開發(fā)人員了,我只能說,京東有錢!
那京東校招的正式薪資是多少呢?京東去年的校招薪資我去看了下,我給大家整理一下:
京東年總包構成 = 月薪 x 16 + 房補
-
普通 offer:19.5k~23k*16,年包:31w~37w -
sp offer:24k~27k*16,年包:38w~43w -
ssp offer:29k~30k*16,年包:46w~48w
等今年京東校招薪資出來,到時候,我們在做一下對比,看一下是否開的更高了。
既然京東秋招準備開始了,那么給大家分享京東的 Java后端面經(jīng),給準備參加秋招的同學做一個參考。
屬于一面, 問了MySQL、Redis、MQ、Java 方面的八股,問題不算多,整體就問了 10 多個技術問題,但是問的都是比較細節(jié)的,這次面試沒有手撕算法,但是并不是說京東不考算法,算法只要是大廠,90%概率都會考的。
京東面試
MyISAM 和 InnoDB 的區(qū)別?
-
事務:InnoDB 支持事務,MyISAM 不支持事務,這是 MySQL 將默認存儲引擎從 MyISAM 變成 InnoDB 的重要原因之一。 -
索引結構:InnoDB 是聚簇索引,MyISAM 是非聚簇索引。聚簇索引的文件存放在主鍵索引的葉子節(jié)點上,因此 InnoDB 必須要有主鍵,通過主鍵索引效率很高。但是輔助索引需要兩次查詢,先查詢到主鍵,然后再通過主鍵查詢到數(shù)據(jù)。因此,主鍵不應該過大,因為主鍵太大,其他索引也都會很大。而 MyISAM 是非聚簇索引,數(shù)據(jù)文件是分離的,索引保存的是數(shù)據(jù)文件的指針。主鍵索引和輔助索引是獨立的。 -
鎖粒度:InnoDB 最小的鎖粒度是行鎖,MyISAM 最小的鎖粒度是表鎖。一個更新語句會鎖住整張表,導致其他查詢和更新都會被阻塞,因此并發(fā)訪問受限。 -
count 的效率:InnoDB 不保存表的具體行數(shù),執(zhí)行 select count(*) from table 時需要全表掃描。而MyISAM 用一個變量保存了整個表的行數(shù),執(zhí)行上述語句時只需要讀出該變量即可,速度很快。
MySQL 的事務隔離級別有哪些?
-
讀未提交(read uncommitted),指一個事務還沒提交時,它做的變更就能被其他事務看到; -
讀提交(read committed),指一個事務提交之后,它做的變更才能被其他事務看到; -
可重復讀(repeatable read),指一個事務執(zhí)行過程中看到的數(shù)據(jù),一直跟這個事務啟動時看到的數(shù)據(jù)是一致的,MySQL InnoDB 引擎的默認隔離級別; -
串行化(serializable);會對記錄加上讀寫鎖,在多個事務對這條記錄進行讀寫操作時,如果發(fā)生了讀寫沖突的時候,后訪問的事務必須等前一個事務執(zhí)行完成,才能繼續(xù)執(zhí)行;
按隔離水平高低排序如下:針對不同的隔離級別,并發(fā)事務時可能發(fā)生的現(xiàn)象也會不同。
也就是說:
-
在「讀未提交」隔離級別下,可能發(fā)生臟讀、不可重復讀和幻讀現(xiàn)象; -
在「讀提交」隔離級別下,可能發(fā)生不可重復讀和幻讀現(xiàn)象,但是不可能發(fā)生臟讀現(xiàn)象; -
在「可重復讀」隔離級別下,可能發(fā)生幻讀現(xiàn)象,但是不可能臟讀和不可重復讀現(xiàn)象; -
在「串行化」隔離級別下,臟讀、不可重復讀和幻讀現(xiàn)象都不可能會發(fā)生。
接下來,舉個具體的例子來說明這四種隔離級別,有一張賬戶余額表,里面有一條賬戶余額為 100 萬的記錄。然后有兩個并發(fā)的事務,事務 A 只負責查詢余額,事務 B 則會將我的余額改成 200 萬,下面是按照時間順序執(zhí)行兩個事務的行為:
在不同隔離級別下,事務 A 執(zhí)行過程中查詢到的余額可能會不同:
-
在「讀未提交」隔離級別下,事務 B 修改余額后,雖然沒有提交事務,但是此時的余額已經(jīng)可以被事務 A 看見了,于是事務 A 中余額 V1 查詢的值是 200 萬,余額 V2、V3 自然也是 200 萬了; -
在「讀提交」隔離級別下,事務 B 修改余額后,因為沒有提交事務,所以事務 A 中余額 V1 的值還是 100 萬,等事務 B 提交完后,最新的余額數(shù)據(jù)才能被事務 A 看見,因此額 V2、V3 都是 200 萬; -
在「可重復讀」隔離級別下,事務 A 只能看見啟動事務時的數(shù)據(jù),所以余額 V1、余額 V2 的值都是 100 萬,當事務 A 提交事務后,就能看見最新的余額數(shù)據(jù)了,所以余額 V3 的值是 200 萬; -
在「串行化」隔離級別下,事務 B 在執(zhí)行將余額 100 萬修改為 200 萬時,由于此前事務 A 執(zhí)行了讀操作,這樣就發(fā)生了讀寫沖突,于是就會被鎖住,直到事務 A 提交后,事務 B 才可以繼續(xù)執(zhí)行,所以從 A 的角度看,余額 V1、V2 的值是 100 萬,余額 V3 的值是 200萬。
這四種隔離級別分別是怎么實現(xiàn)的?
-
對于「讀未提交」隔離級別的事務來說,因為可以讀到未提交事務修改的數(shù)據(jù),所以直接讀取最新的數(shù)據(jù)就好了; -
對于「串行化」隔離級別的事務來說,通過加讀寫鎖的方式來避免并行訪問; -
對于「讀提交」和「可重復讀」隔離級別的事務來說,它們是通過 MVCC 機制來實現(xiàn)的,它們的區(qū)別在于創(chuàng)建 Read View 的時機不同,「讀提交」隔離級別是在「每個語句執(zhí)行前」都會重新生成一個 Read View,而「可重復讀」隔離級別是「啟動事務時」生成一個 Read View,然后整個事務期間都在用這個 Read View。
MySQL 的 redo log 作用是什么?
Redo log是MySQL中用于保證持久性的重要機制之一。它通過以下方式來保證持久性:
-
Write-ahead logging(WAL):在事務提交之前,將事務所做的修改操作記錄到redo log中,然后再將數(shù)據(jù)寫入磁盤。這樣即使在數(shù)據(jù)寫入磁盤之前發(fā)生了宕機,系統(tǒng)可以通過redo log中的記錄來恢復數(shù)據(jù)。 -
Redo log的順序?qū)懭耄簉edo log采用追加寫入的方式,將redo日志記錄追加到文件末尾,而不是隨機寫入。這樣可以減少磁盤的隨機I/O操作,提高寫入性能。 -
Checkpoint機制:MySQL會定期將內(nèi)存中的數(shù)據(jù)刷新到磁盤,同時將最新的LSN(Log Sequence Number)記錄到磁盤中,這個LSN可以確保redo log中的操作是按順序執(zhí)行的。在恢復數(shù)據(jù)時,系統(tǒng)會根據(jù)LSN來確定從哪個位置開始應用redo log。
為什么 Redis 性能高?
官方使用基準測試的結果是,單線程的 Redis 吞吐量可以達到 10W/每秒,如下圖所示:之所以 Redis 采用單線程(網(wǎng)絡 I/O 和執(zhí)行命令)那么快,有如下幾個原因:
-
Redis 的大部分操作都在內(nèi)存中完成,并且采用了高效的數(shù)據(jù)結構,因此 Redis 瓶頸可能是機器的內(nèi)存或者網(wǎng)絡帶寬,而并非 CPU,既然 CPU 不是瓶頸,那么自然就采用單線程的解決方案了; -
Redis 采用單線程模型可以避免了多線程之間的競爭,省去了多線程切換帶來的時間和性能上的開銷,而且也不會導致死鎖問題。 -
Redis 采用了 I/O 多路復用機制處理大量的客戶端 Socket 請求,IO 多路復用機制是指一個線程處理多個 IO 流,就是我們經(jīng)常聽到的 select/epoll 機制。簡單來說,在 Redis 只運行單線程的情況下,該機制允許內(nèi)核中,同時存在多個監(jiān)聽 Socket 和已連接 Socket。內(nèi)核會一直監(jiān)聽這些 Socket 上的連接請求或數(shù)據(jù)請求。一旦有請求到達,就會交給 Redis 線程處理,這就實現(xiàn)了一個 Redis 線程處理多個 IO 流的效果。
Redis 數(shù)據(jù)結構有哪些?
Redis 提供了豐富的數(shù)據(jù)類型,常見的有五種數(shù)據(jù)類型:String(字符串),Hash(哈希),List(列表),Set(集合)、Zset(有序集合)。隨著 Redis 版本的更新,后面又支持了四種數(shù)據(jù)類型:BitMap(2.2 版新增)、HyperLogLog(2.8 版新增)、GEO(3.2 版新增)、Stream(5.0 版新增)。Redis 五種數(shù)據(jù)類型的應用場景:
-
String 類型的應用場景:緩存對象、常規(guī)計數(shù)、分布式鎖、共享 session 信息等。 -
List 類型的應用場景:消息隊列(但是有兩個問題:1. 生產(chǎn)者需要自行實現(xiàn)全局唯一 ID;2. 不能以消費組形式消費數(shù)據(jù))等。 -
Hash 類型:緩存對象、購物車等。 -
Set 類型:聚合計算(并集、交集、差集)場景,比如點贊、共同關注、抽獎活動等。 -
Zset 類型:排序場景,比如排行榜、電話和姓名排序等。
Redis 后續(xù)版本又支持四種數(shù)據(jù)類型,它們的應用場景如下:
-
BitMap(2.2 版新增):二值狀態(tài)統(tǒng)計的場景,比如簽到、判斷用戶登陸狀態(tài)、連續(xù)簽到用戶總數(shù)等; -
HyperLogLog(2.8 版新增):海量數(shù)據(jù)基數(shù)統(tǒng)計的場景,比如百萬級網(wǎng)頁 UV 計數(shù)等; -
GEO(3.2 版新增):存儲地理位置信息的場景,比如滴滴叫車; -
Stream(5.0 版新增):消息隊列,相比于基于 List 類型實現(xiàn)的消息隊列,有這兩個特有的特性:自動生成全局唯一消息ID,支持以消費組形式消費數(shù)據(jù)。
redis 除了可以緩存,還可以有什么功能?
Redis實現(xiàn)消息隊列
-
使用Pub/Sub模式:Redis的Pub/Sub是一種基于發(fā)布/訂閱的消息模式,任何客戶端都可以訂閱一個或多個頻道,發(fā)布者可以向特定頻道發(fā)送消息,所有訂閱該頻道的客戶端都會收到此消息。該方式實現(xiàn)起來比較簡單,發(fā)布者和訂閱者完全解耦,支持模式匹配訂閱。但是這種方式不支持消息持久化,消息發(fā)布后若無訂閱者在線則會被丟棄;不保證消息的順序和可靠性傳輸。 -
使用List結構:使用List的方式通常是使用 LPUSH命令將消息推入一個列表,消費者使用BLPOP或BRPOP阻塞地從列表中取出消息(先進先出FIFO)。這種方式可以實現(xiàn)簡單的任務隊列。這種方式可以結合Redis的過期時間特性實現(xiàn)消息的TTL;通過Redis事務可以保證操作的原子性。但是需要客戶端自己實現(xiàn)消息確認、重試等機制,相比專門的消息隊列系統(tǒng)功能較弱。
Redis實現(xiàn)分布式鎖
-
set nx方式:Redis提供了幾種方式來實現(xiàn)分布式鎖,最常用的是基于 SET命令的爭搶鎖機制。客戶端可以使用SET resource_name lock_value NX PX milliseconds命令設置鎖,其中NX表示只有當鍵不存在時才設置,PX指定鎖的有效時間(毫秒)。如果設置成功,則認為客戶端獲得鎖。客戶端完成操作后,解鎖的還需要先判斷鎖是不是自己,再進行刪除,這里涉及到 2 個操作,為了保證這兩個操作的原子性,可以用 lua 腳本來實現(xiàn)。 -
RedLock算法:為了提高分布式鎖的可靠性,Redis作者Antirez提出了RedLock算法,它基于多個獨立的Redis實例來實現(xiàn)一個更安全的分布式鎖。它的基本原理是客戶端嘗試在多數(shù)(大于半數(shù))Redis實例上同時加鎖,只有當在大多數(shù)實例上加鎖成功時才認為獲取鎖成功。鎖的超時時間應該遠小于單個實例的超時時間,以避免死鎖。該方式可以通過跨多個節(jié)點減少單點故障的影響,提高了鎖的可用性和安全性。
Java 對象的創(chuàng)建過程說一下?
在Java中創(chuàng)建對象的過程包括以下幾個步驟:
-
類加載檢查:虛擬機遇到一條 new 指令時,首先將去檢查這個指令的參數(shù)是否能在常量池中定位到一個類的符號引用,并且檢查這個符號引用代表的類是否已被加載過、解析和初始化過。如果沒有,那必須先執(zhí)行相應的類加載過程。 -
分配內(nèi)存:在類加載檢查通過后,接下來虛擬機將為新生對象分配內(nèi)存。對象所需的內(nèi)存大小在類加載完成后便可確定,為對象分配空間的任務等同于把一塊確定大小的內(nèi)存從 Java 堆中劃分出來。 -
初始化零值:內(nèi)存分配完成后,虛擬機需要將分配到的內(nèi)存空間都初始化為零值(不包括對象頭),這一步操作保證了對象的實例字段在 Java 代碼中可以不賦初始值就直接使用,程序能訪問到這些字段的數(shù)據(jù)類型所對應的零值。 -
進行必要設置,比如對象頭:初始化零值完成之后,虛擬機要對對象進行必要的設置,例如這個對象是哪個類的實例、如何才能找到類的元數(shù)據(jù)信息、對象的哈希碼、對象的 GC 分代年齡等信息。這些信息存放在對象頭中。另外,根據(jù)虛擬機當前運行狀態(tài)的不同,如是否啟用偏向鎖等,對象頭會有不同的設置方式。 -
執(zhí)行 init 方法:在上面工作都完成之后,從虛擬機的視角來看,一個新的對象已經(jīng)產(chǎn)生了,但從 Java 程序的視角來看,對象創(chuàng)建才剛開始——構造函數(shù),即class文件中的方法還沒有執(zhí)行,所有的字段都還為零,對象需要的其他資源和狀態(tài)信息還沒有按照預定的意圖構造好。所以一般來說,執(zhí)行 new 指令之后會接著執(zhí)行方法,把對象按照程序員的意愿進行初始化,這樣一個真正可用的對象才算完全被構造出來。
synchronized 鎖實現(xiàn)的原理是什么?
synchronized是Java提供的原子性內(nèi)置鎖,這種內(nèi)置的并且使用者看不到的鎖也被稱為監(jiān)視器鎖,使用synchronized之后,會在編譯之后在同步的代碼塊前后加上monitorenter和monitorexit字節(jié)碼指令,他依賴操作系統(tǒng)底層互斥鎖實現(xiàn)。他的作用主要就是實現(xiàn)原子性操作和解決共享變量的內(nèi)存可見性問題。
執(zhí)行monitorenter指令時會嘗試獲取對象鎖,如果對象沒有被鎖定或者已經(jīng)獲得了鎖,鎖的計數(shù)器+1。此時其他競爭鎖的線程則會進入等待隊列中。
執(zhí)行monitorexit指令時則會把計數(shù)器-1,當計數(shù)器值為0時,則鎖釋放,處于等待隊列中的線程再繼續(xù)競爭鎖。
synchronized是排它鎖,當一個線程獲得鎖之后,其他線程必須等待該線程釋放鎖后才能獲得鎖,而且由于Java中的線程和操作系統(tǒng)原生線程是一一對應的,線程被阻塞或者喚醒時時會從用戶態(tài)切換到內(nèi)核態(tài),這種轉(zhuǎn)換非常消耗性能。
從內(nèi)存語義來說,加鎖的過程會清除工作內(nèi)存中的共享變量,再從主內(nèi)存讀取,而釋放鎖的過程則是將工作內(nèi)存中的共享變量寫回主內(nèi)存。
實際上大部分時候我認為說到monitorenter就行了,但是為了更清楚的描述,還是再具體一點。如果再深入到源碼來說,synchronized實際上有兩個隊列waitSet和entryList。
-
當多個線程進入同步代碼塊時,首先進入entryList -
有一個線程獲取到monitor鎖后,就賦值給當前線程,并且計數(shù)器+1 -
如果線程調(diào)用wait方法,將釋放鎖,當前線程置為null,計數(shù)器-1,同時進入waitSet等待被喚醒,調(diào)用notify或者notifyAll之后又會進入entryList競爭鎖 -
如果線程執(zhí)行完畢,同樣釋放鎖,計數(shù)器-1,當前線程置為null
Kafka 和 RocketMQ 的區(qū)別是什么?
-
架構設計: -
Kafka:分布式發(fā)布-訂閱系統(tǒng),由生產(chǎn)者、消費者、Broker、Topic、Partition 和 ZooKeeper 構成。 -
RocketMQ:由 NameServer、Broker、生產(chǎn)者和消費者構成,支持多種消息模式。 -
高可用設計: -
Kafka:通過副本機制、領導者和追隨者模式、控制器和 ZooKeeper 協(xié)調(diào)實現(xiàn)高可用。 -
RocketMQ:采用主從架構,NameServer 無狀態(tài)設計,支持同步和異步復制。 -
延遲消息:Kafka 不支持,RocketMQ 提供多個預定義級別延遲。 -
集群擴展: -
Kafka:通過增加 Broker 擴展,依賴 Zookeeper,分區(qū)重新平衡負載。 -
RocketMQ:區(qū)分 Broker 和 NameServer 角色,擴展時可增加 Broker 和 NameServer。 -
使用場景: -
RocketMQ:適合事務消息、順序消息、廣播消息、定時延遲消息等場景,如電商、金融等對可靠性要求高的行業(yè)。 -
Kafka:適用于日志聚合、流式處理、事件驅(qū)動架構、數(shù)據(jù)集成和分布式系統(tǒng)冗余備份等場景。
Kafka 為什么性能高?
1)頁緩存技術
Kafka是基于操作系統(tǒng)的頁緩存來實現(xiàn)寫入的,操作系統(tǒng)本身有一層緩存,叫做page cache,是在內(nèi)存里的緩存,我們也可以稱之為 os cache,意思就是操作系統(tǒng)自己管理的緩存。
Kafka在寫入磁盤文件的時候,可以直接寫入到這個os cache里,也就是僅僅寫入到內(nèi)存中,接下來由操作系統(tǒng)自己決定什么時候把os cache里的數(shù)據(jù)真的刷入磁盤文件中。這樣可以很大提升寫性能
2)磁盤順序?qū)?/p>
Kafka寫數(shù)據(jù)的時候,Kafka的消息是不斷追加到文件末尾的,而不是在文件的隨機位置寫入數(shù)據(jù),這個特性使Kafka可以充分利用磁盤的順序讀寫性能。順序讀寫不需要磁盤磁頭的尋道時間,避免了隨機磁盤尋址的浪費,只需很少的扇區(qū)旋轉(zhuǎn)時間,所以速度遠快于隨機讀寫。
Kafka中每個分區(qū)是一個有序的,不可變的消息序列,新的消息不斷追加到Partition的末尾,在Kafka中Partition只是一個邏輯概念,Kafka將Partition劃分為多個Segment,每個Segment對應一個物理文件,Kafka對segment文件追加寫,這就是順序讀寫。
2)零拷貝
在消費數(shù)據(jù)的時候,實際上要從kafka的磁盤文件里讀取某條數(shù)據(jù)然后發(fā)送給下游的消費者。
Kafka在讀數(shù)據(jù)的時候為了避免多余的數(shù)據(jù)拷貝,使用了零拷貝技術。也就是說直接讓os cache里的數(shù)據(jù)發(fā)送到網(wǎng)卡后然后傳輸給下游的消費者,跳過中間從os cache拷貝到kafka 進程緩存和再拷貝到socket緩存中的兩次緩存,同時也減少了上下文切換。
在Linux Kernel2.2之后出現(xiàn)了一種叫做“零拷貝(zero-copy)”系統(tǒng)調(diào)用機制,就是跳過“用戶緩沖區(qū)”的拷貝,建立一個磁盤空間和內(nèi)存的直接映射,數(shù)據(jù)不再復制到“用戶緩沖區(qū)”。
Kafka 使用到了 mmap+write(持久化數(shù)據(jù)) 和 sendfile(發(fā)送數(shù)據(jù)) 的方式來實現(xiàn)零拷貝。分別對應 Java 的 MappedByteBuffer 和 FileChannel.transferTo。
3)分區(qū)并發(fā)
kafka中的topic中的內(nèi)容可以分在多個分區(qū)(partition)存儲,每個partition又分為多個段segment,所以每次操作都是針對一小部分做操作,很輕便,并且增加并行操作的能力
4)批量發(fā)送
Kafka允許進行批量發(fā)送消息,Productor發(fā)送消息的時候,可以將消息緩存在本地,等到了固定條件發(fā)送到kafka,可減少IO延遲 (1):等消息條數(shù)到固定條數(shù) (2):一段時間發(fā)送一次
5)數(shù)據(jù)壓縮
Kafka還支持對消息集合進行壓縮,Producer可以通過GZIP或Snappy格式對消息集合進行壓縮,壓縮的好處就是減少傳輸?shù)臄?shù)據(jù)量,減輕對網(wǎng)絡傳輸?shù)膲毫Α?/p>
批量發(fā)送和數(shù)據(jù)壓縮一起使用,單條做數(shù)據(jù)壓縮的話,效果不太明顯。消息發(fā)送時默認不會壓縮,可使用compression.type來指定壓縮方式,可選的值為snappy、gzip和lz4
實習問題
-
實習中有一定復雜度或難度比較大的項目; -
怎么理解實習工作解決的業(yè)務問題; -
實習中意見和別人有沒有不同,怎么處理;
