• 正文
    • TCL(一面)
  • 相關(guān)推薦
申請入駐 產(chǎn)業(yè)圖譜

Java后端面試解析:TCL線下面試速通了,已成功 oc!

04/09 10:55
825
加入交流群
掃碼加入
獲取工程師必備禮包
參與熱點資訊討論

大家好,我是小林。我在圖解網(wǎng)站整理過通信硬件廠的面經(jīng),比如華為、聯(lián)想、深信服等等,這次咱們來補一個 TCL 公司的面經(jīng)!

圖解學(xué)習(xí)網(wǎng)站:https://xiaolincoding.com

TCL 是智能硬件制造為主的的公司,最知名了就是 TCL 電視了,雖然說是電子硬件制造廠,但是也一樣會有軟件開發(fā)的崗位招聘。

我從網(wǎng)上搜羅了 25 屆 TCL 開發(fā)崗的校招薪資情況,年薪有 30w+,可以算是大廠的薪酬級別了。

Java后端,24k x 16,深圳大數(shù)據(jù)開發(fā),24k x 16,深圳安卓開發(fā),22k x 16,深圳

看到不少 TCL 同學(xué)反饋,年終獎平均是 2-3 個月,大部分同學(xué)是拿 2.5 個月,當(dāng)然要想拿到 4 個月及以上年終獎的話,那肯定得是績效比較優(yōu)秀的同學(xué)了。

話說回來,TCL 面試難度如何呢?

TCL 面試的流程不長,去年有訓(xùn)練營同學(xué)秋招的時候,線下面試 TCL,直接就一輪技術(shù)面試+一輪 hr 面就速通拿到 offer 了,效率極高,不到 1 小時就走完線下面試的所有流程了。

而且 TCL 面試強度不算大,一輪技術(shù)面大概 20-30 分鐘就完事了,一場面試技術(shù)問題大概 10 個左右,難度也是中規(guī)中矩,不會有太多刁難的問題,算法手撕出現(xiàn)的概率比較低。

這次就來看看 TCL 的 Java 開發(fā)崗位的校招一面面經(jīng),問的范圍還是蠻廣,Java 方面的知識沒有怎么拷打,主要拷打了后端組件的內(nèi)容,比如Spring、微服務(wù)、Kafka、Redis、MySQL、Netty 都分別問了 1-2 個問題。

TCL(一面)

1. Spring 初始化Bean前要做什么?有幾種方式

在 Spring 容器調(diào)用 Bean 的初始化方法(如?init-method@PostConstruct?等)之前,會按順序完成以下關(guān)鍵步驟:實例化 → 屬性注入 → Aware 接口回調(diào) →?BeanPostProcessor?前置處理。

實例化:通過構(gòu)造函數(shù)或工廠方法創(chuàng)建 Bean 的實例。

屬性注入:通過?setter 方法、字段注入(如?@Autowired)或構(gòu)造器注入,完成 Bean 的依賴注入。

Aware 接口回調(diào):如果 Bean 實現(xiàn)了 Spring 的?Aware 接口,Spring 會調(diào)用對應(yīng)的回調(diào)方法,使 Bean 能感知容器信息。

BeanPostProcessor 的前置處理:調(diào)用所有注冊的?BeanPostProcessor 的?postProcessBeforeInitialization() 方法,允許對 Bean 進行自定義修改(如代理增強)。

在初始化階段(即上述步驟完成后),Spring 提供了以下三種主要方式來定義 Bean 的初始化邏輯:

使用?@PostConstruct

注解:在方法上添加?@PostConstruct 注解,Spring 會在依賴注入完成后調(diào)用該方法。

@Component
public?class?MyBean?{
? ??@PostConstruct
? ??public?void?init()?{
? ? ? ??// 初始化邏輯
? ? }
}

實現(xiàn)

InitializingBean

接口:實現(xiàn)?InitializingBean 接口,并重寫?afterPropertiesSet() 方法。

@Component
public?class?MyBean?implements?InitializingBean?{
? ??@Override
? ??public?void?afterPropertiesSet()?{
? ? ? ??// 初始化邏輯
? ? }
}

配置

init-method

    在 XML 或 Java 配置中指定自定義的初始化方法。XML 示例:
<bean id="myBean"?class="com.example.MyBean"?init-method="customInit"/>

Java 注解配置示例:

@Bean(initMethod =?"customInit")
public?MyBean?myBean()?{
? ??return?new?MyBean();
}

2. 微服務(wù)和單體的區(qū)別與優(yōu)勢是什么?

    所有功能模塊(如用戶管理、訂單處理、支付等)集中在一個單一的代碼庫中,編譯為一個可執(zhí)行文件或服務(wù),共享同一個數(shù)據(jù)庫和資源。單體架構(gòu)適合業(yè)務(wù)簡單、團隊小的場景,優(yōu)勢是開發(fā)效率高、性能好,不過隨著隨著業(yè)務(wù)增長,代碼庫臃腫,維護會變的更困難,而且任何修改都可能影響全局,回歸測試成本高。將應(yīng)用拆分為多個獨立的、松耦合的小型服務(wù),每個服務(wù)專注于單一業(yè)務(wù)功能,擁有獨立的代碼庫、數(shù)據(jù)庫和進程,通過API(如REST或gRPC)通信。微服務(wù)通過解耦服務(wù)實現(xiàn)靈活擴展和獨立部署,但復(fù)雜度高,適合大型系統(tǒng)(如電商平臺、社交網(wǎng)絡(luò))。

3. Spring Cloud組件有哪些?

我項目用的是Spring Cloud Alibaba,我簡單說一下Spring Cloud Alibaba組件:

Nacos:其服務(wù)注冊和發(fā)現(xiàn)功能能讓服務(wù)提供者將自身服務(wù)信息注冊到 Nacos 服務(wù)器,服務(wù)消費者從服務(wù)器獲取服務(wù)列表,此外,Nacos 還支持配置的動態(tài)更新,可實現(xiàn)服務(wù)的快速上下線和配置的實時生效。

Sentinel:負(fù)責(zé)流量控制與熔斷降級,Sentinel 可以通過配置規(guī)則對服務(wù)的流量進行精準(zhǔn)控制,防止服務(wù)因流量過大而崩潰。同時,當(dāng)服務(wù)出現(xiàn)異常時,它能快速進行熔斷降級,保障系統(tǒng)的穩(wěn)定性。

Seata;分布式事務(wù),支持AT模式(自動補償)、TCC、Saga等, 可以幫助開發(fā)者在微服務(wù)架構(gòu)中處理分布式事務(wù)問題,確保數(shù)據(jù)的一致性。通過 Seata,開發(fā)者可以像處理本地事務(wù)一樣處理分布式事務(wù),降低了開發(fā)的復(fù)雜度。

RocketMQ:分布式消息隊列,支持順序消息、事務(wù)消息、延遲消息,開發(fā)者可以方便地在微服務(wù)之間進行異步通信,實現(xiàn)解耦和流量削峰。例如,在電商系統(tǒng)中,訂單服務(wù)可以通過 RocketMQ 向庫存服務(wù)發(fā)送消息,實現(xiàn)庫存的扣減。

Dubbo:提供了高效的遠(yuǎn)程服務(wù)調(diào)用能力,支持多種協(xié)議和負(fù)載均衡策略。在微服務(wù)架構(gòu)中,使用 Dubbo 可以方便地實現(xiàn)服務(wù)之間的遠(yuǎn)程調(diào)用,提高系統(tǒng)的性能和可擴展性。

Arthas開源的Java動態(tài)追蹤工具,基于字節(jié)碼增強技術(shù),功能非常強大。

4. Kafka 分區(qū)和消費者的關(guān)系是怎樣的?

Kafka 的主題(Topic)可以被劃分為多個分區(qū)(Partition),分區(qū)是物理上的概念,每個分區(qū)是一個有序的、不可變的消息序列。分區(qū)分布在不同的 broker 上,以此實現(xiàn)數(shù)據(jù)的分布式存儲。

消費者從 Kafka 的主題中讀取消息,消費者可以組成消費者組(Consumer Group),每個消費者組內(nèi)有多個消費者實例。

在一個消費者組內(nèi),一個分區(qū)只能被一個消費者實例消費。這種設(shè)計保證了分區(qū)內(nèi)消息消費的順序性,因為一個分區(qū)的消息只能由一個消費者按順序處理。比如,一個主題有 3 個分區(qū),一個消費者組中有 3 個消費者,那么這 3 個消費者會分別對應(yīng)一個分區(qū)進行消息消費。

如果兩個消費者負(fù)責(zé)同一個分區(qū),那么就意味著兩個消費者同時讀取分區(qū)的消息,由于消費者自己可以控制讀取消息的偏移量,就有可能C1才讀到2,而C1讀到1,C1還沒處理完,C2已經(jīng)讀到3了,則會造成很多浪費,因為這就相當(dāng)于多線程讀取同一個消息,會造成消息處理的重復(fù),且不能保證消息的順序。

如果消費者數(shù)量 > 分區(qū)數(shù),多余消費者閑置,比如主題?orders?有 3 個分區(qū)(P0、P1、P2),消費者組?group-1?有 3 個消費者(C1、C2、C3):

P0 → C1 ?
P1 → C2 ?
P2 → C3 ?

如果消費者組擴容到 4 個消費者,第 4 個消費者(C4)將無法分配到分區(qū),處于空閑狀態(tài)。

5. Kafka 如何批量拉取消息?

可以通過調(diào)整配置參數(shù)來實現(xiàn) ?Kafka 批量拉取消息:

設(shè)置max.poll.records:該參數(shù)用于設(shè)定一次poll操作最多拉取的消息數(shù)量。例如,在 Java 中創(chuàng)建KafkaConsumer實例時,通過props.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, 100);將其設(shè)置為 100,意味著每次poll最多拉取 100 條消息。

調(diào)整fetch.max.bytes:此參數(shù)用于設(shè)置一次fetch請求能夠拉取的最大數(shù)據(jù)量。如果消息體較大,可適當(dāng)調(diào)大該參數(shù),以確保能拉取到足夠數(shù)量的消息。在 Java 中可通過props.put(ConsumerConfig.FETCH_MAX_BYTES_CONFIG, 1024 * 1024);將其設(shè)置為 1MB。

6. Redis分布式鎖實現(xiàn)原理?

分布式鎖是用于分布式環(huán)境下并發(fā)控制的一種機制,用于控制某個資源在同一時刻只能被一個應(yīng)用所使用。如下圖所示:

Redis 本身可以被多個客戶端共享訪問,正好就是一個共享存儲系統(tǒng),可以用來保存分布式鎖,而且 Redis 的讀寫性能高,可以應(yīng)對高并發(fā)的鎖操作場景。Redis 的 SET 命令有個 NX 參數(shù)可以實現(xiàn)「key不存在才插入」,所以可以用它來實現(xiàn)分布式鎖:

    如果 key 不存在,則顯示插入成功,可以用來表示加鎖成功;如果 key 存在,則會顯示插入失敗,可以用來表示加鎖失敗。

基于 Redis 節(jié)點實現(xiàn)分布式鎖時,對于加鎖操作,我們需要滿足三個條件。

    加鎖包括了讀取鎖變量、檢查鎖變量值和設(shè)置鎖變量值三個操作,但需要以原子操作的方式完成,所以,我們使用 SET 命令帶上 NX 選項來實現(xiàn)加鎖;鎖變量需要設(shè)置過期時間,以免客戶端拿到鎖后發(fā)生異常,導(dǎo)致鎖一直無法釋放,所以,我們在 SET 命令執(zhí)行時加上 EX/PX 選項,設(shè)置其過期時間;鎖變量的值需要能區(qū)分來自不同客戶端的加鎖操作,以免在釋放鎖時,出現(xiàn)誤釋放操作,所以,我們使用 SET 命令設(shè)置鎖變量值時,每個客戶端設(shè)置的值是一個唯一值,用于標(biāo)識客戶端;

滿足這三個條件的分布式命令如下:

SET lock_key unique_value NX PX 10000
    lock_key 就是 key 鍵;unique_value 是客戶端生成的唯一的標(biāo)識,區(qū)分來自不同客戶端的鎖操作;NX 代表只在 lock_key 不存在時,才對 lock_key 進行設(shè)置操作;PX 10000 表示設(shè)置 lock_key 的過期時間為 10s,這是為了避免客戶端發(fā)生異常而無法釋放鎖。

而解鎖的過程就是將 lock_key 鍵刪除(del lock_key),但不能亂刪,要保證執(zhí)行操作的客戶端就是加鎖的客戶端。所以,解鎖的時候,我們要先判斷鎖的 unique_value 是否為加鎖客戶端,是的話,才將 lock_key 鍵刪除。

可以看到,解鎖是有兩個操作,這時就需要 Lua 腳本來保證解鎖的原子性,因為 Redis 在執(zhí)行 Lua 腳本時,可以以原子性的方式執(zhí)行,保證了鎖釋放操作的原子性。

// 釋放鎖時,先比較 unique_value 是否相等,避免鎖的誤釋放
if?redis.call("get",KEYS[1]) == ARGV[1]?then
??return?redis.call("del",KEYS[1])
else
??return?0
end

這樣一來,就通過使用 SET 命令和 Lua 腳本在 Redis 單節(jié)點上完成了分布式鎖的加鎖和解鎖。

7. Redision使用方式是什么?

Redisson 是一個基于 Redis 的 Java 客戶端,提供了豐富的分布式數(shù)據(jù)結(jié)構(gòu)和服務(wù),其中?分布式鎖是其核心功能之一,以下是 Redisson 實現(xiàn)分布式鎖的完整方式。

引入依賴:如果你使用 Maven 項目,可在?pom.xml 里添加以下依賴:

<dependency>
? ? <groupId>org.redisson</groupId>
? ? <artifactId>redisson</artifactId>
? ? <version>3.16.2</version> <!-- 選擇合適的版本 -->
</dependency>
    • 配置客戶端:要使用 Redisson,得先創(chuàng)建一個

RedissonClient

    ?實例。以下是一個簡單的單機 Redis 配置示例:
import?org.redisson.Redisson;
import?org.redisson.api.RedissonClient;
import?org.redisson.config.Config;

public?class?RedissonConfigExample?{
? ??public?static?RedissonClient?getRedissonClient()?{
? ? ? ? Config config =?new?Config();
? ? ? ??// 單機模式
? ? ? ? config.useSingleServer().setAddress("redis://127.0.0.1:6379");
? ? ? ??return?Redisson.create(config);
? ? }
}
    實現(xiàn)分布式鎖代碼:Redisson 提供了多種分布式數(shù)據(jù)結(jié)構(gòu),例如分布式鎖、分布式集合等。下面是使用分布式鎖的示例:
import?org.redisson.api.RLock;
import?org.redisson.api.RedissonClient;

publicclass?RedissonLockExample?{
? ??public?static?void?main(String[] args)?{
? ? ? ? RedissonClient redisson = RedissonConfigExample.getRedissonClient();
? ? ? ??// 獲取鎖對象
? ? ? ? RLock lock = redisson.getLock("myLock");
? ? ? ??try?{
? ? ? ? ? ??// 嘗試加鎖,最多等待100秒,鎖的持有時間為10秒
? ? ? ? ? ??boolean?isLocked = lock.tryLock(100,?10, java.util.concurrent.TimeUnit.SECONDS);
? ? ? ? ? ??if?(isLocked) {
? ? ? ? ? ? ? ??try?{
? ? ? ? ? ? ? ? ? ??// 模擬業(yè)務(wù)操作
? ? ? ? ? ? ? ? ? ? System.out.println("獲得鎖,開始執(zhí)行任務(wù)");
? ? ? ? ? ? ? ? ? ? Thread.sleep(5000);
? ? ? ? ? ? ? ? }?finally?{
? ? ? ? ? ? ? ? ? ??// 釋放鎖
? ? ? ? ? ? ? ? ? ? lock.unlock();
? ? ? ? ? ? ? ? ? ? System.out.println("釋放鎖");
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? }?catch?(InterruptedException e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? }?finally?{
? ? ? ? ? ??// 關(guān)閉 Redisson 客戶端
? ? ? ? ? ? redisson.shutdown();
? ? ? ? }
? ? }
}

8. MySQL索引底層結(jié)構(gòu)是什么?有什么優(yōu)勢?

MySQL InnoDB 引擎是用了B+樹作為了索引的數(shù)據(jù)結(jié)構(gòu)。

B+Tree 是一種多叉樹,葉子節(jié)點才存放數(shù)據(jù),非葉子節(jié)點只存放索引,而且每個節(jié)點里的數(shù)據(jù)是按主鍵順序存放的。每一層父節(jié)點的索引值都會出現(xiàn)在下層子節(jié)點的索引值中,因此在葉子節(jié)點中,包括了所有的索引值信息,并且每一個葉子節(jié)點都有兩個指針,分別指向下一個葉子節(jié)點和上一個葉子節(jié)點,形成一個雙向鏈表。

主鍵索引的 B+Tree 如圖所示:

比如,我們執(zhí)行了下面這條查詢語句:

select?*?from?product?where?id=?5;

這條語句使用了主鍵索引查詢 id 號為 5 的商品。查詢過程是這樣的,B+Tree 會自頂向下逐層進行查找:

    將 5 與根節(jié)點的索引數(shù)據(jù) (1,10,20) 比較,5 在 1 和 10 之間,所以根據(jù) B+Tree的搜索邏輯,找到第二層的索引數(shù)據(jù) (1,4,7);在第二層的索引數(shù)據(jù) (1,4,7)中進行查找,因為 5 在 4 和 7 之間,所以找到第三層的索引數(shù)據(jù)(4,5,6);在葉子節(jié)點的索引數(shù)據(jù)(4,5,6)中進行查找,然后我們找到了索引值為 5 的行數(shù)據(jù)。

數(shù)據(jù)庫的索引和數(shù)據(jù)都是存儲在硬盤的,我們可以把讀取一個節(jié)點當(dāng)作一次磁盤 I/O 操作。那么上面的整個查詢過程一共經(jīng)歷了 3 個節(jié)點,也就是進行了 3 次 I/O 操作。

B+Tree 存儲千萬級的數(shù)據(jù)只需要 3-4 層高度就可以滿足,這意味著從千萬級的表查詢目標(biāo)數(shù)據(jù)最多需要 3-4 次磁盤 I/O,所以B+Tree 相比于 B 樹和二叉樹來說,最大的優(yōu)勢在于查詢效率很高,因為即使在數(shù)據(jù)量很大的情況,查詢一個數(shù)據(jù)的磁盤 I/O 依然維持在 3-4次。

9. 聯(lián)合索引的失效場景你知道哪些?

6 種會發(fā)生索引失效的情況:

    當(dāng)我們使用左或者左右模糊匹配的時候,也就是 like %xx 或者 like %xx%這兩種方式都會造成索引失效;當(dāng)我們在查詢條件中對索引列使用函數(shù),就會導(dǎo)致索引失效。當(dāng)我們在查詢條件中對索引列進行表達(dá)式計算,也是無法走索引的。MySQL 在遇到字符串和數(shù)字比較的時候,會自動把字符串轉(zhuǎn)為數(shù)字,然后再進行比較。如果字符串是索引列,而條件語句中的輸入?yún)?shù)是數(shù)字的話,那么索引列會發(fā)生隱式類型轉(zhuǎn)換,由于隱式類型轉(zhuǎn)換是通過 CAST 函數(shù)實現(xiàn)的,等同于對索引列使用了函數(shù),所以就會導(dǎo)致索引失效。聯(lián)合索引要能正確使用需要遵循最左匹配原則,也就是按照最左優(yōu)先的方式進行索引的匹配,否則就會導(dǎo)致索引失效。在 WHERE 子句中,如果在 OR 前的條件列是索引列,而在 OR 后的條件列不是索引列,那么索引會失效。

10. mysql ?Explain 執(zhí)行計劃中 key 和possible_key的區(qū)別是什么?

explain 是查看 sql 的執(zhí)行計劃,主要用來分析 sql 語句的執(zhí)行過程,比如有沒有走索引,有沒有外部排序,有沒有索引覆蓋等等。

如下圖,就是一個沒有使用索引,并且是一個全表掃描的查詢語句。

對于執(zhí)行計劃,比較重要的參數(shù)有:

    • possible_keys 字段表示可能用到的索引;key 字段表示實際用的索引,它是從

possible_keys

    ?所列出的索引中挑選出來的,如果這一項為 NULL,說明沒有使用索引;key_len 表示索引的長度;rows 表示掃描的數(shù)據(jù)行數(shù)。type 表示數(shù)據(jù)掃描類型

11. 追問:那 extra中出現(xiàn) Using index condition、Using filesort是為什么?

Using index condition:表示使用了索引條件下推優(yōu)化,當(dāng)查詢條件中的部分列使用了索引,但還有其他條件無法通過索引直接過濾時,就會出現(xiàn)?Using index condition。數(shù)據(jù)庫會先利用索引來獲取滿足索引條件的記錄,然后再在存儲引擎層根據(jù)剩余的條件對這些記錄進行過濾,而不是像以前那樣先把所有滿足索引條件的記錄都讀取到服務(wù)器層,再進行過濾。例如,有一個復(fù)合索引?idx_country_industry ,查詢語句為?SELECT * FROM c WHERE country = 'Ukraine' AND industry = 'banking',如果只使用?country 列的索引來查詢,那么就會先通過索引找到所有?country 為?Ukraine的記錄,然后在存儲引擎層再根據(jù)?industry = 'banking'

這個條件進一步過濾,此時?Extra 列就會顯示?Using index condition,這樣可以減少存儲引擎和服務(wù)器層之間的數(shù)據(jù)傳輸,以及回表的次數(shù),提高查詢性能。Using filesort :當(dāng)查詢語句中包含 group by 操作,而且無法利用索引完成排序操作的時候, 這時不得不選擇相應(yīng)的排序算法進行,甚至可能會通過文件排序,效率是很低的,所以要避免這種問題的出現(xiàn)。

12. TCP四次揮手中 第三步FIN丟失,會進入什么狀態(tài)?

TCP 四次揮手中,如果第三步的 FIN 丟失,相關(guān)方會進入以下狀態(tài):

服務(wù)端(被動關(guān)閉方):服務(wù)端發(fā)送 FIN 后會進入 LAST_ACK 狀態(tài)。由于 FIN 丟失,服務(wù)端收不到客戶端的 ACK 確認(rèn),會觸發(fā)超時重傳機制,重新發(fā)送 FIN 報文,直到收到客戶端的 ACK 或者達(dá)到最大重傳次數(shù)。如果達(dá)到最大重傳次數(shù)后仍未收到 ACK,服務(wù)端最終會進入 CLOSED 狀態(tài),釋放連接資源。

客戶端(主動關(guān)閉方):客戶端處于 FIN_WAIT_2 狀態(tài),等待服務(wù)端的 FIN 報文。因為未收到服務(wù)端的 FIN,客戶端會一直保持 FIN_WAIT_2 狀態(tài)。如果客戶端的應(yīng)用程序沒有設(shè)置超時時間,那么這個連接可能會一直處于 FIN_WAIT_2 狀態(tài),導(dǎo)致資源無法釋放。不過,在實際應(yīng)用中,通常會設(shè)置超時機制,當(dāng)超過一定時間未收到服務(wù)端的 FIN 報文,客戶端會認(rèn)為連接出現(xiàn)異常,從而主動釋放連接,進入 CLOSED 狀態(tài)。

13. 了解哪些網(wǎng)絡(luò)編程框架?

了解過 netty,它是基于 Java NIO 的高性能、異步事件驅(qū)動的網(wǎng)絡(luò)應(yīng)用框架,netty有以下特點:

高性能:基于 Java NIO 實現(xiàn),單線程可處理萬級并發(fā)連接,吞吐量達(dá)百萬級 QPS。

低延遲:零拷貝技術(shù)減少內(nèi)存復(fù)制,ByteBuf 動態(tài)擴展優(yōu)化內(nèi)存分配。

易用性:通過高度抽象的 API 屏蔽底層 NIO 細(xì)節(jié),降低開發(fā)門檻。

協(xié)議擴展性:內(nèi)置 HTTP、WebSocket、MQTT 等協(xié)議支持,支持自定義私有協(xié)議。

這是一張Netty官網(wǎng)關(guān)于Netty的核心架構(gòu)圖:

可以看到Netty由以下三個核心部分構(gòu)成:

    傳輸服務(wù):傳輸服務(wù)層提供了網(wǎng)絡(luò)傳輸能力的定義和實現(xiàn)方法。它支持 Socket、HTTP 隧道、虛擬機管道等傳輸方式。Netty 對 TCP、UDP 等數(shù)據(jù)傳輸做了抽象和封裝,用戶可以更聚焦在業(yè)務(wù)邏輯實現(xiàn)上,而不必關(guān)系底層數(shù)據(jù)傳輸?shù)募?xì)節(jié)。協(xié)議支持:Netty支持多種常見的數(shù)據(jù)傳輸協(xié)議,包括:HTTP、WebSocket、SSL、zlib/gzip、二進制、文本等,還支持自定義編解碼實現(xiàn)的協(xié)議。Netty豐富的協(xié)議支持降低了開發(fā)成本,基于 Netty 我們可以快速開發(fā) HTTP、WebSocket 等服務(wù)。Core核心:Netty的核心,提供了底層網(wǎng)絡(luò)通信的通用抽象和實現(xiàn),包括:可擴展的事件驅(qū)動模型、通用的通信API、支持零拷貝的Buffer緩沖對象。

介紹完 Netty 的模塊結(jié)構(gòu),我們再來看一下它的處理架構(gòu):

Netty 的架構(gòu)也很清晰,就三層:

    底層 IO 復(fù)用層,負(fù)責(zé)實現(xiàn)多路復(fù)用。通用數(shù)據(jù)處理層,主要對傳輸層的數(shù)據(jù)在進和出兩個方向進行攔截處理,如編/解碼,粘包處理等。應(yīng)用實現(xiàn)層,開發(fā)者在使用 Netty 的時候基本就在這一層上折騰,同時 Netty 本身已經(jīng)在這一層提供了一些常用的實現(xiàn),如 HTTP 協(xié)議,F(xiàn)TP 協(xié)議等。

一般來說,數(shù)據(jù)從網(wǎng)絡(luò)傳遞給 IO 復(fù)用層,IO 復(fù)用層收到數(shù)據(jù)后會將數(shù)據(jù)傳遞給上層進行處理,這一層會通過一系列的處理 Handler 以及應(yīng)用服務(wù)對數(shù)據(jù)進行處理,然后返回給 IO 復(fù)用層,通過它再傳回網(wǎng)絡(luò)。

在 Netty 處理架構(gòu)圖中,可以看到在 IO 復(fù)用層上標(biāo)注了一個「Reactor」:

這個「Reactor」代表的就是其 IO 復(fù)用層具體的實現(xiàn)模式 -- Reactor 模式,Netty 主要采用了?主從 Reactor 多線程模型

在 Reactor 模式中,分為主反應(yīng)組(MainReactor)和子反應(yīng)組(subReactor)以及 ThreadPool,主反應(yīng)組(MainReactor)負(fù)責(zé)處理連接,連接建立完成以后由主線程對應(yīng)的 acceptor 將后續(xù)的數(shù)據(jù)處理(read/write)分發(fā)給子反應(yīng)組(subReactor)進行處理,而 Threadpool 對應(yīng)的是業(yè)務(wù)處理線程池。

服務(wù)端處理請求流程:

    Reactor主線程對象通過seletct監(jiān)聽連接事件,收到事件后,通過Acceptor處理連接事件當(dāng)Acceptor處理連接事件后,主Reactor線程將連接分配給子Reactor線程Reactor子線程將連接加入連接隊列進行監(jiān)聽,并創(chuàng)建handler進行各種事件處理當(dāng)有新事件發(fā)生時,子Reactor線程會調(diào)用對應(yīng)的handler進行處理handler讀取數(shù)據(jù)分發(fā)給worker線程池分配一個獨立的線程進行業(yè)務(wù)處理,并返回結(jié)果給handlerhandler收到響應(yīng)的結(jié)果后,通過send將結(jié)果返回給客戶端

該模型雖然編程復(fù)雜度高,但是其優(yōu)勢比較明顯,體現(xiàn)在:

主從線程職責(zé)分明,主線程只需要接收新請求,子線程完成后續(xù)的業(yè)務(wù)處理主從線程數(shù)據(jù)交互簡單,主線程只需要把新連接傳給子線程

 

更多面經(jīng)分享:https://xiaolincoding.com/backend_interview/

TCL

TCL

TCL實業(yè)聚焦智能終端產(chǎn)品及服務(wù),堅持“科技創(chuàng)造精彩、暢享智慧生活”的使命,以全品類智慧科技產(chǎn)品服務(wù)全球用戶,打造TCL智能科技產(chǎn)業(yè)集團。

TCL實業(yè)聚焦智能終端產(chǎn)品及服務(wù),堅持“科技創(chuàng)造精彩、暢享智慧生活”的使命,以全品類智慧科技產(chǎn)品服務(wù)全球用戶,打造TCL智能科技產(chǎn)業(yè)集團。收起

查看更多

相關(guān)推薦