<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          20+互聯(lián)網(wǎng)公司面試總結(jié)

          共 27662字,需瀏覽 56分鐘

           ·

          2020-03-06 23:23

          本文來源:https://zhuanlan.zhihu.com/p/106997736作者:袁廣鑫
          不多BB,全都是干貨算法相關(guān)以及作者的背景可以到上面的鏈接查看
          面試問題整理

          ZooKeeper

          CAP定理:

          一個分布式系統(tǒng)不可能同時滿足以下三種,一致性(C:Consistency),可用性(A:Available),分區(qū)容錯性(P:Partition Tolerance).在此ZooKeeper保證的是CP,ZooKeeper不能保證每次服務(wù)請求的可用性,在極端環(huán)境下,ZooKeeper可能會丟棄一些請求,消費者程序需要重新請求才能獲得結(jié)果。另外在進行l(wèi)eader選舉時集群都是不可用,所以說,ZooKeeper不能保證服務(wù)可用性。(Base理論CA強一致性和最終一致性)

          ZAB協(xié)議:

          ZAB協(xié)議包括兩種基本的模式:崩潰恢復(fù)和消息廣播。當整個 Zookeeper 集群剛剛啟動或者Leader服務(wù)器宕機、重啟或者網(wǎng)絡(luò)故障導(dǎo)致不存在過半的服務(wù)器與 Leader 服務(wù)器保持正常通信時,所有服務(wù)器進入崩潰恢復(fù)模式,首先選舉產(chǎn)生新的 Leader 服務(wù)器,然后集群中 Follower 服務(wù)器開始與新的 Leader 服務(wù)器進行數(shù)據(jù)同步。當集群中超過半數(shù)機器與該 Leader 服務(wù)器完成數(shù)據(jù)同步之后,退出恢復(fù)模式進入消息廣播模式,Leader 服務(wù)器開始接收客戶端的事務(wù)請求生成事物提案來進行事務(wù)請求處理。

          選舉算法和流程:FastLeaderElection(默認提供的選舉算法)

          目前有5臺服務(wù)器,每臺服務(wù)器均沒有數(shù)據(jù),它們的編號分別是1,2,3,4,5,按編號依次啟動,它們的選擇舉過程如下:

          1. 服務(wù)器1啟動,給自己投票,然后發(fā)投票信息,由于其它機器還沒有啟動所以它收不到反饋信息,服務(wù)器1的狀態(tài)一直屬于Looking。

          2. 服務(wù)器2啟動,給自己投票,同時與之前啟動的服務(wù)器1交換結(jié)果,由于服務(wù)器2的編號大所以服務(wù)器2勝出,但此時投票數(shù)沒有大于半數(shù),所以兩個服務(wù)器的狀態(tài)依然是LOOKING。

          3. 服務(wù)器3啟動,給自己投票,同時與之前啟動的服務(wù)器1,2交換信息,由于服務(wù)器3的編號最大所以服務(wù)器3勝出,此時投票數(shù)正好大于半數(shù),所以服務(wù)器3成為leader,服務(wù)器1,2成為follower。

          4. 服務(wù)器4啟動,給自己投票,同時與之前啟動的服務(wù)器1,2,3交換信息,盡管服務(wù)器4的編號大,但之前服務(wù)器3已經(jīng)勝出,所以服務(wù)器4只能成為follower。

          5. 服務(wù)器5啟動,后面的邏輯同服務(wù)器4成為follower。

          Redis

          應(yīng)用場景

          1. 緩存

          2. 共享Session

          3. 消息隊列系統(tǒng)

          4. 分布式鎖

          單線程的Redis為什么快

          1. 純內(nèi)存操作

          2. 單線程操作,避免了頻繁的上下文切換

          3. 合理高效的數(shù)據(jù)結(jié)構(gòu)

          4. 采用了非阻塞I/O多路復(fù)用機制

          Redis 的數(shù)據(jù)結(jié)構(gòu)及使用場景

          1. String字符串:字符串類型是 Redis 最基礎(chǔ)的數(shù)據(jù)結(jié)構(gòu),首先鍵都是字符串類型,而且 其他幾種數(shù)據(jù)結(jié)構(gòu)都是在字符串類型基礎(chǔ)上構(gòu)建的,我們常使用的 set key value 命令就是字符串。常用在緩存、計數(shù)、共享Session、限速等。

          2. Hash哈希:在Redis中,哈希類型是指鍵值本身又是一個鍵值對結(jié)構(gòu),哈希可以用來存放用戶信息,比如實現(xiàn)購物車。

          3. List列表(雙向鏈表):列表(list)類型是用來存儲多個有序的字符串。可以做簡單的消息隊列的功能。

          4. Set集合:集合(set)類型也是用來保存多個的字符串元素,但和列表類型不一 樣的是,集合中不允許有重復(fù)元素,并且集合中的元素是無序的,不能通過索引下標獲取元素。利用 Set 的交集、并集、差集等操作,可以計算共同喜好,全部的喜好,自己獨有的喜好等功能。

          5. Sorted Set有序集合(跳表實現(xiàn)):Sorted Set 多了一個權(quán)重參數(shù) Score,集合中的元素能夠按 Score 進行排列。可以做排行榜應(yīng)用,取 TOP N 操作。

          Redis 的數(shù)據(jù)過期策略

          Redis 中數(shù)據(jù)過期策略采用定期刪除+惰性刪除策略

          • 定期刪除策略:Redis 啟用一個定時器定時監(jiān)視所有的 key,判斷key是否過期,過期的話就刪除。這種策略可以保證過期的 key 最終都會被刪除,但是也存在嚴重的缺點:每次都遍歷內(nèi)存中所有的數(shù)據(jù),非常消耗 CPU 資源,并且當 key 已過期,但是定時器還處于未喚起狀態(tài),這段時間內(nèi) key 仍然可以用。

          • 惰性刪除策略:在獲取 key 時,先判斷 key 是否過期,如果過期則刪除。這種方式存在一個缺點:如果這個 key 一直未被使用,那么它一直在內(nèi)存中,其實它已經(jīng)過期了,會浪費大量的空間。

          • 這兩種策略天然的互補,結(jié)合起來之后,定時刪除策略就發(fā)生了一些改變,不在是每次掃描全部的 key 了,而是隨機抽取一部分 key 進行檢查,這樣就降低了對 CPU 資源的損耗,惰性刪除策略互補了為檢查到的key,基本上滿足了所有要求。但是有時候就是那么的巧,既沒有被定時器抽取到,又沒有被使用,這些數(shù)據(jù)又如何從內(nèi)存中消失?沒關(guān)系,還有內(nèi)存淘汰機制,當內(nèi)存不夠用時,內(nèi)存淘汰機制就會上場。淘汰策略分為:

          1. 當內(nèi)存不足以容納新寫入數(shù)據(jù)時,新寫入操作會報錯。(Redis 默認策略)

          2. 當內(nèi)存不足以容納新寫入數(shù)據(jù)時,在鍵空間中,移除最近最少使用的 Key。(LRU推薦使用)

          3. 當內(nèi)存不足以容納新寫入數(shù)據(jù)時,在鍵空間中,隨機移除某個 Key。

          4. 當內(nèi)存不足以容納新寫入數(shù)據(jù)時,在設(shè)置了過期時間的鍵空間中,移除最近最少使用的 Key。這種情況一般是把 Redis 既當緩存,又做持久化存儲的時候才用。

          5. 當內(nèi)存不足以容納新寫入數(shù)據(jù)時,在設(shè)置了過期時間的鍵空間中,隨機移除某個 Key。

          6. 當內(nèi)存不足以容納新寫入數(shù)據(jù)時,在設(shè)置了過期時間的鍵空間中,有更早過期時間的 Key 優(yōu)先移除。

          Redis的LRU具體實現(xiàn):

          傳統(tǒng)的LRU是使用棧的形式,每次都將最新使用的移入棧頂,但是用棧的形式會導(dǎo)致執(zhí)行select *的時候大量非熱點數(shù)據(jù)占領(lǐng)頭部數(shù)據(jù),所以需要改進。Redis每次按key獲取一個值的時候,都會更新value中的lru字段為當前秒級別的時間戳。Redis初始的實現(xiàn)算法很簡單,隨機從dict中取出五個key,淘汰一個lru字段值最小的。在3.0的時候,又改進了一版算法,首先第一次隨機選取的key都會放入一個pool中(pool的大小為16),pool中的key是按lru大小順序排列的。接下來每次隨機選取的keylru值必須小于pool中最小的lru才會繼續(xù)放入,直到將pool放滿。放滿之后,每次如果有新的key需要放入,需要將pool中l(wèi)ru最大的一個key取出。淘汰的時候,直接從pool中選取一個lru最小的值然后將其淘汰。

          如何解決 Redis 緩存雪崩問題

          1. 使用 Redis 高可用架構(gòu):使用 Redis 集群來保證 Redis 服務(wù)不會掛掉

          2. 緩存時間不一致,給緩存的失效時間,加上一個隨機值,避免集體失效

          3. 限流降級策略:有一定的備案,比如個性推薦服務(wù)不可用了,換成熱點數(shù)據(jù)推薦服務(wù)

          如何解決 Redis 緩存穿透問題

          1. 在接口做校驗

          2. 存null值(緩存擊穿加鎖)

          3. 布隆過濾器攔截:將所有可能的查詢key 先映射到布隆過濾器中,查詢時先判斷key是否存在布隆過濾器中,存在才繼續(xù)向下執(zhí)行,如果不存在,則直接返回。布隆過濾器將值進行多次哈希bit存儲,布隆過濾器說某個元素在,可能會被誤判。布隆過濾器說某個元素不在,那么一定不在。

          Redis的持久化機制

          Redis為了保證效率,數(shù)據(jù)緩存在了內(nèi)存中,但是會周期性的把更新的數(shù)據(jù)寫入磁盤或者把修改操作寫入追加的記錄文件中,以保證數(shù)據(jù)的持久化。Redis的持久化策略有兩種:
          ? ?1. RDB:快照形式是直接把內(nèi)存中的數(shù)據(jù)保存到一個dump的文件中,定時保存,保存策略。
          當Redis需要做持久化時,Redis會fork一個子進程,子進程將數(shù)據(jù)寫到磁盤上一個臨時RDB文件中。當子進程完成寫臨時文件后,將原來的RDB替換掉。
          ? ?1. AOF:把所有的對Redis的服務(wù)器進行修改的命令都存到一個文件里,命令的集合。
          使用AOF做持久化,每一個寫命令都通過write函數(shù)追加到appendonly.aof中。aof的默認策略是每秒鐘fsync一次,在這種配置下,就算發(fā)生故障停機,也最多丟失一秒鐘的數(shù)據(jù)。
          缺點是對于相同的數(shù)據(jù)集來說,AOF的文件體積通常要大于RDB文件的體積。根據(jù)所使用的fsync策略,AOF的速度可能會慢于RDB。
          Redis默認是快照RDB的持久化方式。對于主從同步來說,主從剛剛連接的時候,進行全量同步(RDB);全同步結(jié)束后,進行增量同步(AOF)。

          Redis和memcached的區(qū)別

          1. 存儲方式上:memcache會把數(shù)據(jù)全部存在內(nèi)存之中,斷電后會掛掉,數(shù)據(jù)不能超過內(nèi)存大小。redis有部分數(shù)據(jù)存在硬盤上,這樣能保證數(shù)據(jù)的持久性。

          2. 數(shù)據(jù)支持類型上:memcache對數(shù)據(jù)類型的支持簡單,只支持簡單的key-value,,而redis支持五種數(shù)據(jù)類型。

          3. 用底層模型不同:它們之間底層實現(xiàn)方式以及與客戶端之間通信的應(yīng)用協(xié)議不一樣。redis直接自己構(gòu)建了VM機制,因為一般的系統(tǒng)調(diào)用系統(tǒng)函數(shù)的話,會浪費一定的時間去移動和請求。

          4. value的大小:redis可以達到1GB,而memcache只有1MB。

          Redis并發(fā)競爭key的解決方案

          1. 分布式鎖+時間戳

          2. 利用消息隊列

          Redis與Mysql雙寫一致性方案

          先更新數(shù)據(jù)庫,再刪緩存。數(shù)據(jù)庫的讀操作的速度遠快于寫操作的,所以臟數(shù)據(jù)很難出現(xiàn)。可以對異步延時刪除策略,保證讀請求完成以后,再進行刪除操作。

          Redis的管道pipeline

          對于單線程阻塞式的Redis,Pipeline可以滿足批量的操作,把多個命令連續(xù)的發(fā)送給Redis Server,然后一一解析響應(yīng)結(jié)果。Pipelining可以提高批量處理性能,提升的原因主要是TCP連接中減少了“交互往返”的時間。pipeline 底層是通過把所有的操作封裝成流,redis有定義自己的出入輸出流。在 sync() 方法執(zhí)行操作,每次請求放在隊列里面,解析響應(yīng)包。

          Mysql

          事務(wù)的基本要素

          1. 原子性:事務(wù)是一個原子操作單元,其對數(shù)據(jù)的修改,要么全都執(zhí)行,要么全都不執(zhí)行

          2. 一致性:事務(wù)開始前和結(jié)束后,數(shù)據(jù)庫的完整性約束沒有被破壞。

          3. 隔離性:同一時間,只允許一個事務(wù)請求同一數(shù)據(jù),不同的事務(wù)之間彼此沒有任何干擾。

          4. 持久性:事務(wù)完成后,事務(wù)對數(shù)據(jù)庫的所有更新將被保存到數(shù)據(jù)庫,不能回滾。

          事務(wù)的并發(fā)問題

          1. 臟讀:事務(wù)A讀取了事務(wù)B更新的數(shù)據(jù),然后B回滾操作,那么A讀取到的數(shù)據(jù)是臟數(shù)據(jù)

          2. 不可重復(fù)讀:事務(wù)A多次讀取同一數(shù)據(jù),事務(wù)B在事務(wù)A多次讀取的過程中,對數(shù)據(jù)作了更新并提交,導(dǎo)致事務(wù)A多次讀取同一數(shù)據(jù)時,結(jié)果不一致。

          3. 幻讀:A事務(wù)讀取了B事務(wù)已經(jīng)提交的新增數(shù)據(jù)。注意和不可重復(fù)讀的區(qū)別,這里是新增,不可重復(fù)讀是更改(或刪除)。select某記錄是否存在,不存在,準備插入此記錄,但執(zhí)行 insert 時發(fā)現(xiàn)此記錄已存在,無法插入,此時就發(fā)生了幻讀。

          MySQL事務(wù)隔離級別

          事務(wù)隔離級別臟讀不可重復(fù)讀幻讀
          讀未提交
          不可重復(fù)讀
          可重復(fù)讀
          串行化

          在MySQL可重復(fù)讀的隔離級別中并不是完全解決了幻讀的問題,而是解決了讀數(shù)據(jù)情況下的幻讀問題。而對于修改的操作依舊存在幻讀問題,就是說MVCC對于幻讀的解決時不徹底的。
          通過索引加鎖,間隙鎖,next key lock可以解決幻讀的問題。

          Mysql的邏輯結(jié)構(gòu)

          • 最上層的服務(wù)類似其他CS結(jié)構(gòu),比如連接處理,授權(quán)處理。

          • 第二層是Mysql的服務(wù)層,包括SQL的解析分析優(yōu)化,存儲過程觸發(fā)器視圖等也在這一層實現(xiàn)。

          • 最后一層是存儲引擎的實現(xiàn),類似于Java接口的實現(xiàn),Mysql的執(zhí)行器在執(zhí)行SQL的時候只會關(guān)注API的調(diào)用,完全屏蔽了不同引擎實現(xiàn)間的差異。比如Select語句,先會判斷當前用戶是否擁有權(quán)限,其次到緩存(內(nèi)存)查詢是否有相應(yīng)的結(jié)果集,如果沒有再執(zhí)行解析sql,優(yōu)化生成執(zhí)行計劃,調(diào)用API執(zhí)行。

          SQL執(zhí)行順序

          SQL的執(zhí)行順序:from---where--group by---having---select---order by

          MVCC,redolog,undolog,binlog

          • undoLog 也就是我們常說的回滾日志文件 主要用于事務(wù)中執(zhí)行失敗,進行回滾,以及MVCC中對于數(shù)據(jù)歷史版本的查看。由引擎層的InnoDB引擎實現(xiàn),是邏輯日志,記錄數(shù)據(jù)修改被修改前的值,比如"把id='B' 修改為id = 'B2' ,那么undo日志就會用來存放id ='B'的記錄”。當一條數(shù)據(jù)需要更新前,會先把修改前的記錄存儲在undolog中,如果這個修改出現(xiàn)異常,,則會使用undo日志來實現(xiàn)回滾操作,保證事務(wù)的一致性。當事務(wù)提交之后,undo log并不能立馬被刪除,而是會被放到待清理鏈表中,待判斷沒有事物用到該版本的信息時才可以清理相應(yīng)undolog。它保存了事務(wù)發(fā)生之前的數(shù)據(jù)的一個版本,用于回滾,同時可以提供多版本并發(fā)控制下的讀(MVCC),也即非鎖定讀。

          • redoLog 是重做日志文件是記錄數(shù)據(jù)修改之后的值,用于持久化到磁盤中。redo log包括兩部分:一是內(nèi)存中的日志緩沖(redo log buffer),該部分日志是易失性的;二是磁盤上的重做日志文件(redo log file),該部分日志是持久的。由引擎層的InnoDB引擎實現(xiàn),是物理日志,記錄的是物理數(shù)據(jù)頁修改的信息,比如“某個數(shù)據(jù)頁上內(nèi)容發(fā)生了哪些改動”。當一條數(shù)據(jù)需要更新時,InnoDB會先將數(shù)據(jù)更新,然后記錄redoLog 在內(nèi)存中,然后找個時間將redoLog的操作執(zhí)行到磁盤上的文件上。不管是否提交成功我都記錄,你要是回滾了,那我連回滾的修改也記錄。它確保了事務(wù)的持久性。

          • MVCC多版本并發(fā)控制是MySQL中基于樂觀鎖理論實現(xiàn)隔離級別的方式,用于讀已提交和可重復(fù)讀取隔離級別的實現(xiàn)。在MySQL中,會在表中每一條數(shù)據(jù)后面添加兩個字段:最近修改該行數(shù)據(jù)的事務(wù)ID,指向該行(undolog表中)回滾段的指針。Read View判斷行的可見性,創(chuàng)建一個新事務(wù)時,copy一份當前系統(tǒng)中的活躍事務(wù)列表。意思是,當前不應(yīng)該被本事務(wù)看到的其他事務(wù)id列表。

          • binlog由Mysql的Server層實現(xiàn),是邏輯日志,記錄的是sql語句的原始邏輯,比如"把id='B' 修改為id = ‘B2’。binlog會寫入指定大小的物理文件中,是追加寫入的,當前文件寫滿則會創(chuàng)建新的文件寫入。產(chǎn)生:事務(wù)提交的時候,一次性將事務(wù)中的sql語句,按照一定的格式記錄到binlog中。用于復(fù)制和恢復(fù)在主從復(fù)制中,從庫利用主庫上的binlog進行重播(執(zhí)行日志中記錄的修改邏輯),實現(xiàn)主從同步。業(yè)務(wù)數(shù)據(jù)不一致或者錯了,用binlog恢復(fù)。

          binlog和redolog的區(qū)別

          1. redolog是在InnoDB存儲引擎層產(chǎn)生,而binlog是MySQL數(shù)據(jù)庫的上層服務(wù)層產(chǎn)生的。

          2. 兩種日志記錄的內(nèi)容形式不同。MySQL的binlog是邏輯日志,其記錄是對應(yīng)的SQL語句。而innodb存儲引擎層面的重做日志是物理日志。

          3. 兩種日志與記錄寫入磁盤的時間點不同,binlog日志只在事務(wù)提交完成后進行一次寫入。而innodb存儲引擎的重做日志在事務(wù)進行中不斷地被寫入,并日志不是隨事務(wù)提交的順序進行寫入的。

          4. binlog不是循環(huán)使用,在寫滿或者重啟之后,會生成新的binlog文件,redolog是循環(huán)使用。

          5. binlog可以作為恢復(fù)數(shù)據(jù)使用,主從復(fù)制搭建,redolog作為異常宕機或者介質(zhì)故障后的數(shù)據(jù)恢復(fù)使用。

          Mysql如何保證一致性和持久性

          MySQL為了保證ACID中的一致性和持久性,使用了WAL(Write-Ahead Logging,先寫日志再寫磁盤)。Redo log就是一種WAL的應(yīng)用。當數(shù)據(jù)庫忽然掉電,再重新啟動時,MySQL可以通過Redo log還原數(shù)據(jù)。也就是說,每次事務(wù)提交時,不用同步刷新磁盤數(shù)據(jù)文件,只需要同步刷新Redo log就足夠了。

          InnoDB的行鎖模式

          • 共享鎖(S):用法lock in share mode,又稱讀鎖,允許一個事務(wù)去讀一行,阻止其他事務(wù)獲得相同數(shù)據(jù)集的排他鎖。若事務(wù)T對數(shù)據(jù)對象A加上S鎖,則事務(wù)T可以讀A但不能修改A,其他事務(wù)只能再對A加S鎖,而不能加X鎖,直到T釋放A上的S鎖。這保證了其他事務(wù)可以讀A,但在T釋放A上的S鎖之前不能對A做任何修改。

          • 排他鎖(X):用法for update,又稱寫鎖,允許獲取排他鎖的事務(wù)更新數(shù)據(jù),阻止其他事務(wù)取得相同的數(shù)據(jù)集共享讀鎖和排他寫鎖。若事務(wù)T對數(shù)據(jù)對象A加上X鎖,事務(wù)T可以讀A也可以修改A,其他事務(wù)不能再對A加任何鎖,直到T釋放A上的鎖。在沒有索引的情況下,InnoDB只能使用表鎖。

          為什么選擇B+樹作為索引結(jié)構(gòu)

          • Hash索引:Hash索引底層是哈希表,哈希表是一種以key-value存儲數(shù)據(jù)的結(jié)構(gòu),所以多個數(shù)據(jù)在存儲關(guān)系上是完全沒有任何順序關(guān)系的,所以,對于區(qū)間查詢是無法直接通過索引查詢的,就需要全表掃描。所以,哈希索引只適用于等值查詢的場景。而B+ 樹是一種多路平衡查詢樹,所以他的節(jié)點是天然有序的(左子節(jié)點小于父節(jié)點、父節(jié)點小于右子節(jié)點),所以對于范圍查詢的時候不需要做全表掃描

          • 二叉查找樹:解決了排序的基本問題,但是由于無法保證平衡,可能退化為鏈表。

          • 平衡二叉樹:通過旋轉(zhuǎn)解決了平衡的問題,但是旋轉(zhuǎn)操作效率太低。

          • 紅黑樹:通過舍棄嚴格的平衡和引入紅黑節(jié)點,解決了 ? ?AVL旋轉(zhuǎn)效率過低的問題,但是在磁盤等場景下,樹仍然太高,IO次數(shù)太多。

          • B+樹:在B樹的基礎(chǔ)上,將非葉節(jié)點改造為不存儲數(shù)據(jù)純索引節(jié)點,進一步降低了樹的高度;此外將葉節(jié)點使用指針連接成鏈表,范圍查詢更加高效。

          B+樹的葉子節(jié)點都可以存哪些東西

          可能存儲的是整行數(shù)據(jù),也有可能是主鍵的值。B+樹的葉子節(jié)點存儲了整行數(shù)據(jù)的是主鍵索引,也被稱之為聚簇索引。而索引B+ Tree的葉子節(jié)點存儲了主鍵的值的是非主鍵索引,也被稱之為非聚簇索引

          覆蓋索引

          指一個查詢語句的執(zhí)行只用從索引中就能夠取得,不必從數(shù)據(jù)表中讀取。也可以稱之為實現(xiàn)了索引覆蓋。

          查詢在什么時候不走(預(yù)期中的)索引

          1. 模糊查詢 %like

          2. 索引列參與計算,使用了函數(shù)

          3. 非最左前綴順序

          4. where對null判斷

          5. where不等于

          6. or操作有至少一個字段沒有索引

          7. 需要回表的查詢結(jié)果集過大(超過配置的范圍)

          數(shù)據(jù)庫優(yōu)化指南

          1. 創(chuàng)建并使用正確的索引

          2. 只返回需要的字段

          3. 減少交互次數(shù)(批量提交)

          4. 設(shè)置合理的Fetch Size(數(shù)據(jù)每次返回給客戶端的條數(shù))

          JVM

          運行時數(shù)據(jù)區(qū)域

          1. 程序計數(shù)器:程序計數(shù)器是一塊較小的內(nèi)存空間,它可以看作是當前線程所執(zhí)行的字節(jié)碼的行號指示器。在虛擬機的概念模型里,字節(jié)碼解釋器工作時就是通過改變這個計數(shù)器的值來選取下一條需要執(zhí)行的字節(jié)碼指令,分支、循環(huán)、跳轉(zhuǎn)、異常處理、線程恢復(fù)等基礎(chǔ)功能都需要依賴這個計數(shù)器來完成。是線程私有”的內(nèi)存。

          2. Java虛擬機棧:與程序計數(shù)器一樣,Java虛擬機棧(Java Virtual Machine Stacks)也是線程私有的,它的生命周期與線程相同。虛擬機棧描述的是Java方法執(zhí)行的內(nèi)存模型:每個方法在執(zhí)行的同時都會創(chuàng)建一個棧幀 ,用于存儲局部變量表、操作數(shù)棧、動態(tài)鏈接、方法出口等信息。每一個方法從調(diào)用直至執(zhí)行完成的過程,就對應(yīng)著一個棧幀在虛擬機棧中入棧到出棧的過程。

          3. 本地方法棧:本地方法棧(Native Method Stack)與虛擬機棧所發(fā)揮的作用是非常相似的,它們之間的區(qū)別不過是虛擬機棧為虛擬機執(zhí)行Java方法(也就是字節(jié)碼)服務(wù),而本地方法棧則為虛擬機使用到的Native方法服務(wù)。

          4. Java堆:對于大多數(shù)應(yīng)用來說,Java堆是Java虛擬機所管理的內(nèi)存中最大的一塊。Java堆是被所有線程共享的一塊內(nèi)存區(qū)域,在虛擬機啟動時創(chuàng)建。此內(nèi)存區(qū)域的唯一目的就是存放對象實例,幾乎所有的對象實例都在這里分配內(nèi)存。

          分代回收

          HotSpot JVM把年輕代分為了三部分:1個Eden區(qū)和2個Survivor區(qū)(分別叫from和to)。一般情況下,新創(chuàng)建的對象都會被分配到Eden區(qū)(一些大對象特殊處理),這些對象經(jīng)過第一次Minor GC后,如果仍然存活,將會被移到Survivor區(qū)。對象在Survivor區(qū)中每熬過一次Minor GC,年齡就會增加1歲,當它的年齡增加到一定程度時,就會被移動到年老代中。

          因為年輕代中的對象基本都是朝生夕死的,所以在年輕代的垃圾回收算法使用的是復(fù)制算法,復(fù)制算法的基本思想就是將內(nèi)存分為兩塊,每次只用其中一塊,當這一塊內(nèi)存用完,就將還活著的對象復(fù)制到另外一塊上面。復(fù)制算法不會產(chǎn)生內(nèi)存碎片。

          在GC開始的時候,對象只會存在于Eden區(qū)和名為“From”的Survivor區(qū),Survivor區(qū)“To”是空的。緊接著進行GC,Eden區(qū)中所有存活的對象都會被復(fù)制到“To”,而在“From”區(qū)中,仍存活的對象會根據(jù)他們的年齡值來決定去向。年齡達到一定值(年齡閾值,可以通過-XX:MaxTenuringThreshold來設(shè)置)的對象會被移動到年老代中,沒有達到閾值的對象會被復(fù)制到“To”區(qū)域。經(jīng)過這次GC后,Eden區(qū)和From區(qū)已經(jīng)被清空。這個時候,“From”和“To”會交換他們的角色,也就是新的“To”就是上次GC前的“From”,新的“From”就是上次GC前的“To”。不管怎樣,都會保證名為To的Survivor區(qū)域是空的。Minor GC會一直重復(fù)這樣的過程,直到“To”區(qū)被填滿,“To”區(qū)被填滿之后,會將所有對象移動到年老代中。

          常見的垃圾回收機制

          1. 引用計數(shù)法:引用計數(shù)法是一種簡單但速度很慢的垃圾回收技術(shù)。每個對象都含有一個引用計數(shù)器,當有引用連接至對象時,引用計數(shù)加1。當引用離開作用域或被置為null時,引用計數(shù)減1。雖然管理引用計數(shù)的開銷不大,但這項開銷在整個程序生命周期中將持續(xù)發(fā)生。垃圾回收器會在含有全部對象的列表上遍歷,當發(fā)現(xiàn)某個對象引用計數(shù)為0時,就釋放其占用的空間。

          2. 可達性分析算法:這個算法的基本思路就是通過一系列的稱為“GC Roots”的對象作為起始點,從這些節(jié)點開始向下搜索,搜索所走過的路徑稱為引用鏈,當一個對象到GC Roots沒有任何引用鏈相連(用圖論的話來說,就是從GC Roots到這個對象不可達)時,則證明此對象是不可用的。

          G1和CMS的比較

          1. CMS收集器是獲取最短回收停頓時間為目標的收集器,因為CMS工作時,GC工作線程與用戶線程可以并發(fā)執(zhí)行,以此來達到降低手機停頓時間的目的(只有初始標記和重新標記會STW)。但是CMS收集器對CPU資源非常敏感。在并發(fā)階段,雖然不會導(dǎo)致用戶線程停頓,但是會占用CPU資源而導(dǎo)致引用程序變慢,總吞吐量下降。

          2. CMS僅作用于老年代,是基于標記清除算法,所以清理的過程中會有大量的空間碎片。

          3. CMS收集器無法處理浮動垃圾,由于CMS并發(fā)清理階段用戶線程還在運行,伴隨程序的運行自熱會有新的垃圾不斷產(chǎn)生,這一部分垃圾出現(xiàn)在標記過程之后,CMS無法在本次收集中處理它們,只好留待下一次GC時將其清理掉。

          4. G1是一款面向服務(wù)端應(yīng)用的垃圾收集器,適用于多核處理器、大內(nèi)存容量的服務(wù)端系統(tǒng)。G1能充分利用CPU、多核環(huán)境下的硬件優(yōu)勢,使用多個CPU(CPU或者CPU核心)來縮短STW的停頓時間,它滿足短時間停頓的同時達到一個高的吞吐量。

          5. 從JDK 9開始,G1成為默認的垃圾回收器。當應(yīng)用有以下任何一種特性時非常適合用G1:Full GC持續(xù)時間太長或者太頻繁;對象的創(chuàng)建速率和存活率變動很大;應(yīng)用不希望停頓時間長(長于0.5s甚至1s)。

          6. G1將空間劃分成很多塊(Region),然后他們各自進行回收。堆比較大的時候可以采用,采用復(fù)制算法,碎片化問題不嚴重。整體上看屬于標記整理算法,局部(region之間)屬于復(fù)制算法。

          7. G1 需要記憶集 (具體來說是卡表)來記錄新生代和老年代之間的引用關(guān)系,這種數(shù)據(jù)結(jié)構(gòu)在 G1 中需要占用大量的內(nèi)存,可能達到整個堆內(nèi)存容量的 20% 甚至更多。而且 G1 中維護記憶集的成本較高,帶來了更高的執(zhí)行負載,影響效率。所以 CMS 在小內(nèi)存應(yīng)用上的表現(xiàn)要優(yōu)于 G1,而大內(nèi)存應(yīng)用上 G1 更有優(yōu)勢,大小內(nèi)存的界限是6GB到8GB。

          哪些對象可以作為GC Roots

          1. 虛擬機棧(棧幀中的本地變量表)中引用的對象。

          2. 方法區(qū)中類靜態(tài)屬性引用的對象。

          3. 方法區(qū)中常量引用的對象。

          4. 本地方法棧中JNI(即一般說的Native方法)引用的對象。

          GC中Stop the world(STW)

          在執(zhí)行垃圾收集算法時,Java應(yīng)用程序的其他所有除了垃圾收集收集器線程之外的線程都被掛起。此時,系統(tǒng)只能允許GC線程進行運行,其他線程則會全部暫停,等待GC線程執(zhí)行完畢后才能再次運行。這些工作都是由虛擬機在后臺自動發(fā)起和自動完成的,是在用戶不可見的情況下把用戶正常工作的線程全部停下來,這對于很多的應(yīng)用程序,尤其是那些對于實時性要求很高的程序來說是難以接受的。

          但不是說GC必須STW,你也可以選擇降低運行速度但是可以并發(fā)執(zhí)行的收集算法,這取決于你的業(yè)務(wù)。

          垃圾回收算法

          1. 停止-復(fù)制:先暫停程序的運行,然后將所有存活的對象從當前堆復(fù)制到另一個堆,沒有被復(fù)制的對象全部都是垃圾。當對象被復(fù)制到新堆時,它們是一個挨著一個的,所以新堆保持緊湊排列,然后就可以按前述方法簡單,直接的分配了。缺點是一浪費空間,兩個堆之間要來回倒騰,二是當程序進入穩(wěn)定態(tài)時,可能只會產(chǎn)生極少的垃圾,甚至不產(chǎn)生垃圾,盡管如此,復(fù)制式回收器仍會將所有內(nèi)存自一處復(fù)制到另一處。

          2. 標記-清除:同樣是從堆棧和靜態(tài)存儲區(qū)出發(fā),遍歷所有的引用,進而找出所有存活的對象。每當它找到一個存活的對象,就會給對象一個標記,這個過程中不會回收任何對象。只有全部標記工作完成的時候,清理動作才會開始。在清理過程中,沒有標記的對象會被釋放,不會發(fā)生任何復(fù)制動作。所以剩下的堆空間是不連續(xù)的,垃圾回收器如果要希望得到連續(xù)空間的話,就得重新整理剩下的對象。

          3. 標記-整理:它的第一個階段與標記/清除算法是一模一樣的,均是遍歷GC Roots,然后將存活的對象標記。移動所有存活的對象,且按照內(nèi)存地址次序依次排列,然后將末端內(nèi)存地址以后的內(nèi)存全部回收。因此,第二階段才稱為整理階段。

          4. 分代收集算法:把Java堆分為新生代和老年代,然后根據(jù)各個年代的特點采用最合適的收集算法。新生代中,對象的存活率比較低,所以選用復(fù)制算法,老年代中對象存活率高且沒有額外空間對它進行分配擔(dān)保,所以使用“標記-清除”或“標記-整理”算法進行回收。

          Minor GC和Full GC觸發(fā)條件

          • Minor GC觸發(fā)條件:當Eden區(qū)滿時,觸發(fā)Minor GC。

          • Full GC觸發(fā)條件:

          1. 調(diào)用System.gc時,系統(tǒng)建議執(zhí)行Full GC,但是不必然執(zhí)行

          2. 老年代空間不足

          3. 方法區(qū)空間不足

          4. 通過Minor GC后進入老年代的平均大小大于老年代的可用內(nèi)存

          5. 由Eden區(qū)、From Space區(qū)向To Space區(qū)復(fù)制時,對象大小大于To Space可用內(nèi)存,則把該對象轉(zhuǎn)存到老年代,且老年代的可用內(nèi)存小于該對象大小

          JVM類加載過程

          類從被加載到虛擬機內(nèi)存中開始,到卸載出內(nèi)存為止,它的整個生命周期包括:加載、驗證、準備、解析、初始化、使用和卸載7個階段。

          1. 加載:通過一個類的全限定名來獲取定義此類的二進制字節(jié)流,將這個字節(jié)流所代表的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運行時數(shù)據(jù)結(jié)構(gòu),在內(nèi)存中生成一個代表這個類的Class對象,作為方法去這個類的各種數(shù)據(jù)的訪問入口

          2. 驗證:驗證是連接階段的第一步,這一階段的目的是確保Class文件的字節(jié)流中包含的信息符合當前虛擬機的要求,并且不會危害虛擬自身的安全。

          3. 準備:準備階段是正式為類變量分配內(nèi)存并設(shè)置類變量初始值的階段,這些變量所使用的內(nèi)存都將在方法去中進行分配。這時候進行內(nèi)存分配的僅包括類變量(static),而不包括實例變量,實例變量將會在對象實例化時隨著對象一起分配在Java堆中。

          4. 解析:解析階段是虛擬機將常量池內(nèi)的符號(Class文件內(nèi)的符號)引用替換為直接引用(指針)的過程。

          5. 初始化:初始化階段是類加載過程的最后一步,開始執(zhí)行類中定義的Java程序代碼(字節(jié)碼)。

          雙親委派模型

          雙親委派的意思是如果一個類加載器需要加載類,那么首先它會把這個類請求委派給父類加載器去完成,每一層都是如此。一直遞歸到頂層,當父加載器無法完成這個請求時,子類才會嘗試去加載。

          JVM鎖優(yōu)化和膨脹過程

          1. 自旋鎖:自旋鎖其實就是在拿鎖時發(fā)現(xiàn)已經(jīng)有線程拿了鎖,自己如果去拿會阻塞自己,這個時候會選擇進行一次忙循環(huán)嘗試。也就是不停循環(huán)看是否能等到上個線程自己釋放鎖。自適應(yīng)自旋鎖指的是例如第一次設(shè)置最多自旋10次,結(jié)果在自旋的過程中成功獲得了鎖,那么下一次就可以設(shè)置成最多自旋20次。

          2. 鎖粗化:虛擬機通過適當擴大加鎖的范圍以避免頻繁的拿鎖釋放鎖的過程。

          3. 鎖消除:通過逃逸分析發(fā)現(xiàn)其實根本就沒有別的線程產(chǎn)生競爭的可能(別的線程沒有臨界量的引用),或者同步塊內(nèi)進行的是原子操作,而“自作多情”地給自己加上了鎖。有可能虛擬機會直接去掉這個鎖。

          4. 偏向鎖:在大多數(shù)的情況下,鎖不僅不存在多線程的競爭,而且總是由同一個線程獲得。因此為了讓線程獲得鎖的代價更低引入了偏向鎖的概念。偏向鎖的意思是如果一個線程獲得了一個偏向鎖,如果在接下來的一段時間中沒有其他線程來競爭鎖,那么持有偏向鎖的線程再次進入或者退出同一個同步代碼塊,不需要再次進行搶占鎖和釋放鎖的操作。

          5. 輕量級鎖:當存在超過一個線程在競爭同一個同步代碼塊時,會發(fā)生偏向鎖的撤銷。當前線程會嘗試使用CAS來獲取鎖,當自旋超過指定次數(shù)(可以自定義)時仍然無法獲得鎖,此時鎖會膨脹升級為重量級鎖。

          6. 重量級鎖:重量級鎖依賴對象內(nèi)部的monitor鎖來實現(xiàn),而monitor又依賴操作系統(tǒng)的MutexLock(互斥鎖)。當系統(tǒng)檢查到是重量級鎖之后,會把等待想要獲取鎖的線程阻塞,被阻塞的線程不會消耗CPU,但是阻塞或者喚醒一個線程,都需要通過操作系統(tǒng)來實現(xiàn)。

          什么情況下需要開始類加載過程的第一個階段加載

          1. 遇到new、getstatic、putstatic或invokestatic這4條字節(jié)碼指令時,如果類沒有進行過初始化,則需要先觸發(fā)其初始化。生成這4條指令的最常見的Java代碼場景是:使用new關(guān)鍵字實例化對象的時候、讀取或設(shè)置一個類的靜態(tài)字段(被final修飾、已在編譯期把結(jié)果放入常量池的靜態(tài)字段除外)的時候,以及調(diào)用一個類的靜態(tài)方法的時候。

          2. 使用java.lang.reflect包的方法對類進行反射調(diào)用的時候,如果類沒有進行過初始化,則需要先觸發(fā)其初始化。

          3. 當初始化一個類的時候,如果發(fā)現(xiàn)其父類還沒有進行過初始化,則需要先觸發(fā)其父類的初始化。

          4. 當虛擬機啟動時,用戶需要指定一個要執(zhí)行的主類(包含main()方法的那個類),虛擬機會先初始化這個主類。

          i++操作的字節(jié)碼指令

          1. 將int類型常量加載到操作數(shù)棧頂

          2. 將int類型數(shù)值從操作數(shù)棧頂取出,并存儲到到局部變量表的第1個Slot中

          3. 將int類型變量從局部變量表的第1個Slot中取出,并放到操作數(shù)棧頂

          4. 將局部變量表的第1個Slot中的int類型變量加1

          5. 表示將int類型數(shù)值從操作數(shù)棧頂取出,并存儲到到局部變量表的第1個Slot中,即i中

          Java基礎(chǔ)

          HashMap和ConcurrentHashMap

          由于HashMap是線程不同步的,雖然處理數(shù)據(jù)的效率高,但是在多線程的情況下存在著安全問題,因此設(shè)計了CurrentHashMap來解決多線程安全問題。

          HashMap在put的時候,插入的元素超過了容量(由負載因子決定)的范圍就會觸發(fā)擴容操作,就是rehash,這個會重新將原數(shù)組的內(nèi)容重新hash到新的擴容數(shù)組中,在多線程的環(huán)境下,存在同時其他的元素也在進行put操作,如果hash值相同,可能出現(xiàn)同時在同一數(shù)組下用鏈表表示,造成閉環(huán),導(dǎo)致在get時會出現(xiàn)死循環(huán),所以HashMap是線程不安全的。

          HashMap的環(huán):若當前線程此時獲得ertry節(jié)點,但是被線程中斷無法繼續(xù)執(zhí)行,此時線程二進入transfer函數(shù),并把函數(shù)順利執(zhí)行,此時新表中的某個位置有了節(jié)點,之后線程一獲得執(zhí)行權(quán)繼續(xù)執(zhí)行,因為并發(fā)transfer,所以兩者都是擴容的同一個鏈表,當線程一執(zhí)行到e.next = new table[i] 的時候,由于線程二之前數(shù)據(jù)遷移的原因?qū)е麓藭rnew table[i] 上就有ertry存在,所以線程一執(zhí)行的時候,會將next節(jié)點,設(shè)置為自己,導(dǎo)致自己互相使用next引用對方,因此產(chǎn)生鏈表,導(dǎo)致死循環(huán)。

          在JDK1.7版本中,ConcurrentHashMap維護了一個Segment數(shù)組,Segment這個類繼承了重入鎖ReentrantLock,并且該類里面維護了一個 HashEntry[] table數(shù)組,在寫操作put,remove,擴容的時候,會對Segment加鎖,所以僅僅影響這個Segment,不同的Segment還是可以并發(fā)的,所以解決了線程的安全問題,同時又采用了分段鎖也提升了并發(fā)的效率。在JDK1.8版本中,ConcurrentHashMap摒棄了Segment的概念,而是直接用Node數(shù)組+鏈表+紅黑樹的數(shù)據(jù)結(jié)構(gòu)來實現(xiàn),并發(fā)控制使用Synchronized和CAS來操作,整個看起來就像是優(yōu)化過且線程安全的HashMap。,v>

          HashMap如果我想要讓自己的Object作為K應(yīng)該怎么辦

          1. 重寫hashCode()是因為需要計算存儲數(shù)據(jù)的存儲位置,需要注意不要試圖從散列碼計算中排除掉一個對象的關(guān)鍵部分來提高性能,這樣雖然能更快但可能會導(dǎo)致更多的Hash碰撞;

          2. 重寫equals()方法,需要遵守自反性、對稱性、傳遞性、一致性以及對于任何非null的引用值x,x.equals(null)必須返回false的這幾個特性,目的是為了保證key在哈希表中的唯一性;

          volatile

          volatile在多處理器開發(fā)中保證了共享變量的“ 可見性”。可見性的意思是當一個線程修改一個共享變量時,另外一個線程能讀到這個修改的值。(共享內(nèi)存,私有內(nèi)存)

          Atomic類的CAS操作

          CAS是英文單詞CompareAndSwap的縮寫,中文意思是:比較并替換。CAS需要有3個操作數(shù):內(nèi)存地址V,舊的預(yù)期值A(chǔ),即將要更新的目標值B。CAS指令執(zhí)行時,當且僅當內(nèi)存地址V的值與預(yù)期值A(chǔ)相等時,將內(nèi)存地址V的值修改為B,否則就什么都不做。整個比較并替換的操作是一個原子操作。如 Intel 處理器,比較并交換通過指令的 cmpxchg 系列實現(xiàn)。

          CAS操作ABA問題:

          如果在這段期間它的值曾經(jīng)被改成了B,后來又被改回為A,那CAS操作就會誤認為它從來沒有被改變過。Java并發(fā)包為了解決這個問題,提供了一個帶有標記的原子引用類“AtomicStampedReference”,它可以通過控制變量值的版本來保證CAS的正確性。

          Synchronized和Lock的區(qū)別

          1. 首先synchronized是java內(nèi)置關(guān)鍵字在jvm層面,Lock是個java類。

          2. synchronized無法判斷是否獲取鎖的狀態(tài),Lock可以判斷是否獲取到鎖,并且可以主動嘗試去獲取鎖。

          3. synchronized會自動釋放鎖(a 線程執(zhí)行完同步代碼會釋放鎖 ;b 線程執(zhí)行過程中發(fā)生異常會釋放鎖),Lock需在finally中手工釋放鎖(unlock()方法釋放鎖),否則容易造成線程死鎖。

          4. 用synchronized關(guān)鍵字的兩個線程1和線程2,如果當前線程1獲得鎖,線程2線程等待。如果線程1阻塞,線程2則會一直等待下去,而Lock鎖就不一定會等待下去,如果嘗試獲取不到鎖,線程可以不用一直等待就結(jié)束了。

          5. synchronized的鎖可重入、不可中斷、非公平,而Lock鎖可重入、可判斷、可公平(兩者皆可)

          6. Lock鎖適合大量同步的代碼的同步問題,synchronized鎖適合代碼少量的同步問題。

          AQS理論的數(shù)據(jù)結(jié)構(gòu)

          AQS內(nèi)部有3個對象,一個是state(用于計數(shù)器,類似gc的回收計數(shù)器),一個是線程標記(當前線程是誰加鎖的),一個是阻塞隊列。

          如何指定多個線程的執(zhí)行順序

          1. 設(shè)定一個 orderNum,每個線程執(zhí)行結(jié)束之后,更新 orderNum,指明下一個要執(zhí)行的線程。并且喚醒所有的等待線程。

          2. 在每一個線程的開始,要 while 判斷 orderNum 是否等于自己的要求值,不是,則 wait,是則執(zhí)行本線程。

          為什么要使用線程池

          1. 減少創(chuàng)建和銷毀線程的次數(shù),每個工作線程都可以被重復(fù)利用,可執(zhí)行多個任務(wù)。

          2. 可以根據(jù)系統(tǒng)的承受能力,調(diào)整線程池中工作線程的數(shù)目,放置因為消耗過多的內(nèi)存,而把服務(wù)器累趴下

          核心線程池ThreadPoolExecutor內(nèi)部參數(shù)

          1. corePoolSize:指定了線程池中的線程數(shù)量

          2. maximumPoolSize:指定了線程池中的最大線程數(shù)量

          3. keepAliveTime:線程池維護線程所允許的空閑時間

          4. unit: keepAliveTime 的單位。

          5. workQueue:任務(wù)隊列,被提交但尚未被執(zhí)行的任務(wù)。

          6. threadFactory:線程工廠,用于創(chuàng)建線程,一般用默認的即可。

          7. handler:拒絕策略。當任務(wù)太多來不及處理,如何拒絕任務(wù)。

          線程池的拒絕策略

          1. ThreadPoolExecutor.AbortPolicy:丟棄任務(wù)并拋出RejectedExecutionException異常。

          2. ThreadPoolExecutor.DiscardPolicy:丟棄任務(wù),但是不拋出異常。

          3. ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊列最前面的任務(wù),然后重新提交被拒絕的任務(wù)

          4. ThreadPoolExecutor.CallerRunsPolicy:由調(diào)用線程(提交任務(wù)的線程)處理該任務(wù)

          線程池的線程數(shù)量怎么確定

          1. 一般來說,如果是CPU密集型應(yīng)用,則線程池大小設(shè)置為N+1。

          2. 一般來說,如果是IO密集型應(yīng)用,則線程池大小設(shè)置為2N+1。

          3. 在IO優(yōu)化中,線程等待時間所占比例越高,需要越多線程,線程CPU時間所占比例越高,需要越少線程。這樣的估算公式可能更適合:最佳線程數(shù)目 = ((線程等待時間+線程CPU時間)/線程CPU時間 )* CPU數(shù)目

          ThreadLocal的原理和實現(xiàn)

          ThreadLoal 變量,線程局部變量,同一個 ThreadLocal 所包含的對象,在不同的 Thread 中有不同的副本。ThreadLocal 變量通常被private static修飾。當一個線程結(jié)束時,它所使用的所有 ThreadLocal 相對的實例副本都可被回收。

          一個線程內(nèi)可以存在多個 ThreadLocal 對象,所以其實是 ThreadLocal 內(nèi)部維護了一個 Map ,這個 Map 不是直接使用的 HashMap ,而是 ThreadLocal 實現(xiàn)的一個叫做 ThreadLocalMap 的靜態(tài)內(nèi)部類。而我們使用的 get()、set() 方法其實都是調(diào)用了這個ThreadLocalMap類對應(yīng)的 get()、set() 方法。

          HashSet和HashMap

          HashSet的value存的是一個static finial PRESENT = newObject()。而HashSet的remove是使用HashMap實現(xiàn),則是map.remove而map的移除會返回value,如果底層value都是存null,顯然將無法分辨是否移除成功。

          Boolean占幾個字節(jié)

          未精確定義字節(jié)。Java語言表達式所操作的boolean值,在編譯之后都使用Java虛擬機中的int數(shù)據(jù)類型來代替,而boolean數(shù)組將會被編碼成Java虛擬機的byte數(shù)組,每個元素boolean元素占8位。

          Spring

          什么是三級緩存

          1. 第一級緩存:單例緩存池singletonObjects。

          2. 第二級緩存:早期提前暴露的對象緩存earlySingletonObjects。(屬性還沒有值對象也沒有被初始化)

          3. 第三級緩存:singletonFactories單例對象工廠緩存。

          創(chuàng)建Bean的整個過程

          1. getBean方法肯定不陌生,必經(jīng)之路,然后調(diào)用doGetBean,進來以后首先會執(zhí)行transformedBeanName找別名,看你的Bean上面是否起了別名。然后進行很重要的一步,getSingleton,這段代碼就是從你的單例緩存池中獲取Bean的實例。那么你第一次進來肯定是沒有的,緩存里肯定是拿不到的。也就是一級緩存里是沒有的。那么它怎么辦呢?他會嘗試去二級緩存中去拿,但是去二級緩存中拿并不是無條件的,首先要判斷isSingletonCurrentlyInCreation(beanName)他要看你這個對象是否正在創(chuàng)建當中,如果不是直接就退出該方法,如果是的話,他就會去二級緩存earlySingletonObjects里面取,如果沒拿到,它還接著判斷allowEarlyReference這個東西是否為true。它的意思是說,是否允許讓你從單例工廠對象緩存中去拿對象。默認為true。好了,此時如果進來那么就會通過singletonFactory.getObject()去單例工廠緩存中去拿。然后將緩存級別提升至二級緩存也就早期暴露的緩存。

          2. getSingleton執(zhí)行完以后會走dependsOn方法,判斷是否有dependsOn標記的循環(huán)引用,有的話直接卡死,拋出異常。比如說A依賴于B,B依賴于A 通過dependsOn注解去指定。此時執(zhí)行到這里就會拋出異常。這里所指并非是構(gòu)造函數(shù)的循環(huán)依賴。

          3. beforeSingletonCreation在這里方法里。就把你的對象標記為了早期暴露的對象。提前暴露對象用于創(chuàng)建Bean的實例。

          4. 緊接著就走創(chuàng)建Bean的流程開始。在創(chuàng)建Bean之前執(zhí)行了一下resolveBeforeInstantiation。它的意思是說,代理AOPBean定義注冊信息但是這里并不是實際去代理你的對象,因為對象還沒有被創(chuàng)建。只是代理了Bean定義信息,還沒有被實例化。把Bean定義信息放進緩存,以便我想代理真正的目標對象的時候,直接去緩存里去拿。

          5. 接下來就真正的走創(chuàng)建Bean流程,首先走進真正做事兒的方法doCreateBean然后找到createBeanInstance這個方法,在這里面它將為你創(chuàng)建你的Bean實例信息(Bean的實例)。如果說創(chuàng)建成功了,那么就把你的對象放入緩存中去(將創(chuàng)建好的提前曝光的對象放入singletonFactories三級緩存中)將對象從二級緩存中移除因為它已經(jīng)不是提前暴露的對象了。但是。如果說在createBeanInstance這個方法中在創(chuàng)建Bean的時候它會去檢測你的依賴關(guān)系,會去檢測你的構(gòu)造器。然后,如果說它在創(chuàng)建A對象的時候,發(fā)現(xiàn)了構(gòu)造器里依賴了B,然后它又會重新走getBean的這個流程,當在走到這里的時候,又發(fā)現(xiàn)依賴了A此時就會拋出異常。為什么會拋出異常,因為,走getBean的時候他會去從你的單例緩存池中去拿,因為你這里的Bean還沒有被創(chuàng)建好。自然不會被放進緩存中,所以它是在緩存中拿不到B對象的。反過來也是拿不到A對象的。造成了死循環(huán)故此直接拋異常。這就是為什么Spring IOC不能解決構(gòu)造器循環(huán)依賴的原因。因為你還沒來的急放入緩存你的對象是不存在的。所以不能創(chuàng)建。同理@Bean標注的循環(huán)依賴方法也是不能解決的,跟這個同理。那么多例就更不能解決了。為什么?因為在走createBeanInstance的時候,會判斷是否是單例的Bean定義信息mbd.isSingleton();如果是才會進來。所以多例的Bean壓根就不會走進來,而是走了另一段邏輯,這里不做介紹。至此,構(gòu)造器循環(huán)依賴和@Bean的循環(huán)依賴還有多例Bean的循環(huán)依賴為什么不能解決已經(jīng)解釋清楚。然后如果說,Bean創(chuàng)建成功了。那么會走后面的邏輯。

          6. 將創(chuàng)建好的Bean放入緩存,addSingletonFactory方法就是將你創(chuàng)建好的Bean放入三級緩存中。并且移除早期暴露的對象。

          7. 通過populateBean給屬性賦值,我們知道,創(chuàng)建好的對象,并不是一個完整的對象,里面的屬性還沒有被賦值。所以這個方法就是為創(chuàng)建好的Bean為它的屬性賦值。并且調(diào)用了我們實現(xiàn)的的XXXAware接口進行回調(diào)初始化,。然后調(diào)用我們實現(xiàn)的Bean的后置處理器,給我們最后一次機會去修改Bean的屬性。

          Spring如何解決循環(huán)依賴問題

          Spring使用了三級緩存解決了循環(huán)依賴的問題。在populateBean()給屬性賦值階段里面Spring會解析你的屬性,并且賦值,當發(fā)現(xiàn),A對象里面依賴了B,此時又會走getBean方法,但這個時候,你去緩存中是可以拿的到的。因為我們在對createBeanInstance對象創(chuàng)建完成以后已經(jīng)放入了緩存當中,所以創(chuàng)建B的時候發(fā)現(xiàn)依賴A,直接就從緩存中去拿,此時B創(chuàng)建完,A也創(chuàng)建完,一共執(zhí)行了4次。至此Bean的創(chuàng)建完成,最后將創(chuàng)建好的Bean放入單例緩存池中。

          BeanFactory和ApplicationContext的區(qū)別

          1. BeanFactory是Spring里面最低層的接口,提供了最簡單的容器的功能,只提供了實例化對象和拿對象的功能。

          2. ApplicationContext應(yīng)用上下文,繼承BeanFactory接口,它是Spring的一各更高級的容器,提供了更多的有用的功能。如國際化,訪問資源,載入多個(有繼承關(guān)系)上下文 ,使得每一個上下文都專注于一個特定的層次,消息發(fā)送、響應(yīng)機制,AOP等。

          3. BeanFactory在啟動的時候不會去實例化Bean,中有從容器中拿Bean的時候才會去實例化。ApplicationContext在啟動的時候就把所有的Bean全部實例化了。它還可以為Bean配置lazy-init=true來讓Bean延遲實例化

          動態(tài)代理的實現(xiàn)方式,AOP的實現(xiàn)方式

          1. JDK動態(tài)代理:利用反射機制生成一個實現(xiàn)代理接口的匿名類,在調(diào)用具體方法前調(diào)用InvokeHandler來處理。

          2. CGlib動態(tài)代理:利用ASM(開源的Java字節(jié)碼編輯庫,操作字節(jié)碼)開源包,將代理對象類的class文件加載進來,通過修改其字節(jié)碼生成子類來處理。

          3. 區(qū)別:JDK代理只能對實現(xiàn)接口的類生成代理;CGlib是針對類實現(xiàn)代理,對指定的類生成一個子類,并覆蓋其中的方法,這種通過繼承類的實現(xiàn)方式,不能代理final修飾的類。

          Spring的的事務(wù)傳播機制

          1. REQUIRED(默認):支持使用當前事務(wù),如果當前事務(wù)不存在,創(chuàng)建一個新事務(wù)。

          2. SUPPORTS:支持使用當前事務(wù),如果當前事務(wù)不存在,則不使用事務(wù)。

          3. MANDATORY:強制,支持使用當前事務(wù),如果當前事務(wù)不存在,則拋出Exception。

          4. REQUIRES_NEW:創(chuàng)建一個新事務(wù),如果當前事務(wù)存在,把當前事務(wù)掛起。

          5. NOT_SUPPORTED:無事務(wù)執(zhí)行,如果當前事務(wù)存在,把當前事務(wù)掛起。

          6. NEVER:無事務(wù)執(zhí)行,如果當前有事務(wù)則拋出Exception。

          7. NESTED:嵌套事務(wù),如果當前事務(wù)存在,那么在嵌套的事務(wù)中執(zhí)行。如果當前事務(wù)不存在,則表現(xiàn)跟REQUIRED一樣。

          Spring的后置處理器

          1. BeanPostProcessor:Bean的后置處理器,主要在bean初始化前后工作。

          2. InstantiationAwareBeanPostProcessor:繼承于BeanPostProcessor,主要在實例化bean前后工作;AOP創(chuàng)建代理對象就是通過該接口實現(xiàn)。

          3. BeanFactoryPostProcessor:Bean工廠的后置處理器,在bean定義(bean definitions)加載完成后,bean尚未初始化前執(zhí)行。

          4. BeanDefinitionRegistryPostProcessor:繼承于BeanFactoryPostProcessor。其自定義的方法postProcessBeanDefinitionRegistry會在bean定義(bean definitions)將要加載,bean尚未初始化前真執(zhí)行,即在BeanFactoryPostProcessor的postProcessBeanFactory方法前被調(diào)用。

          消息隊列

          為什么需要消息隊列

          解耦,異步處理,削峰/限流

          Kafka的文件存儲機制

          Kafka中消息是以topic進行分類的,生產(chǎn)者通過topic向Kafka broker發(fā)送消息,消費者通過topic讀取數(shù)據(jù)。然而topic在物理層面又能以partition為分組,一個topic可以分成若干個partition。partition還可以細分為segment,一個partition物理上由多個segment組成,segment文件由兩部分組成,分別為“.index”文件和“.log”文件,分別表示為segment索引文件和數(shù)據(jù)文件。這兩個文件的命令規(guī)則為:partition全局的第一個segment從0開始,后續(xù)每個segment文件名為上一個segment文件最后一條消息的offset值。

          Kafka 如何保證可靠性

          如果我們要往 Kafka 對應(yīng)的主題發(fā)送消息,我們需要通過 Producer 完成。前面我們講過 Kafka 主題對應(yīng)了多個分區(qū),每個分區(qū)下面又對應(yīng)了多個副本;為了讓用戶設(shè)置數(shù)據(jù)可靠性, Kafka 在 Producer 里面提供了消息確認機制。也就是說我們可以通過配置來決定消息發(fā)送到對應(yīng)分區(qū)的幾個副本才算消息發(fā)送成功。可以在定義 Producer 時通過 acks 參數(shù)指定。這個參數(shù)支持以下三種值:

          • acks = 0:意味著如果生產(chǎn)者能夠通過網(wǎng)絡(luò)把消息發(fā)送出去,那么就認為消息已成功寫入 Kafka 。在這種情況下還是有可能發(fā)生錯誤,比如發(fā)送的對象無能被序列化或者網(wǎng)卡發(fā)生故障,但如果是分區(qū)離線或整個集群長時間不可用,那就不會收到任何錯誤。在 acks=0 模式下的運行速度是非常快的(這就是為什么很多基準測試都是基于這個模式),你可以得到驚人的吞吐量和帶寬利用率,不過如果選擇了這種模式, 一定會丟失一些消息。

          • acks = 1:意味若 Leader 在收到消息并把它寫入到分區(qū)數(shù)據(jù)文件(不一定同步到磁盤上)時會返回確認或錯誤響應(yīng)。在這個模式下,如果發(fā)生正常的 Leader 選舉,生產(chǎn)者會在選舉時收到一個 LeaderNotAvailableException 異常,如果生產(chǎn)者能恰當?shù)靥幚磉@個錯誤,它會重試發(fā)送悄息,最終消息會安全到達新的 Leader 那里。不過在這個模式下仍然有可能丟失數(shù)據(jù),比如消息已經(jīng)成功寫入 Leader,但在消息被復(fù)制到 follower 副本之前 Leader發(fā)生崩潰。

          • acks = all(這個和 request.required.acks = -1 含義一樣):意味著 Leader 在返回確認或錯誤響應(yīng)之前,會等待所有同步副本都收到悄息。如果和min.insync.replicas 參數(shù)結(jié)合起來,就可以決定在返回確認前至少有多少個副本能夠收到悄息,生產(chǎn)者會一直重試直到消息被成功提交。不過這也是最慢的做法,因為生產(chǎn)者在繼續(xù)發(fā)送其他消息之前需要等待所有副本都收到當前的消息。

          Kafka消息是采用Pull模式,還是Push模式

          Kafka最初考慮的問題是,customer應(yīng)該從brokes拉取消息還是brokers將消息推送到consumer,也就是pull還push。在這方面,Kafka遵循了一種大部分消息系統(tǒng)共同的傳統(tǒng)的設(shè)計:producer將消息推送到broker,consumer從broker拉取消息。push模式下,當broker推送的速率遠大于consumer消費的速率時,consumer恐怕就要崩潰了。最終Kafka還是選取了傳統(tǒng)的pull模式。Pull模式的另外一個好處是consumer可以自主決定是否批量的從broker拉取數(shù)據(jù)。Pull有個缺點是,如果broker沒有可供消費的消息,將導(dǎo)致consumer不斷在循環(huán)中輪詢,直到新消息到t達。為了避免這點,Kafka有個參數(shù)可以讓consumer阻塞知道新消息到達。

          Kafka是如何實現(xiàn)高吞吐率的

          1. 順序讀寫:kafka的消息是不斷追加到文件中的,這個特性使kafka可以充分利用磁盤的順序讀寫性能

          2. 零拷貝:跳過“用戶緩沖區(qū)”的拷貝,建立一個磁盤空間和內(nèi)存的直接映射,數(shù)據(jù)不再復(fù)制到“用戶態(tài)緩沖區(qū)”

          3. 文件分段:kafka的隊列topic被分為了多個區(qū)partition,每個partition又分為多個段segment,所以一個隊列中的消息實際上是保存在N多個片段文件中

          4. 批量發(fā)送:Kafka允許進行批量發(fā)送消息,先將消息緩存在內(nèi)存中,然后一次請求批量發(fā)送出去

          5. 數(shù)據(jù)壓縮:Kafka還支持對消息集合進行壓縮,Producer可以通過GZIP或Snappy格式對消息集合進行壓縮

          Kafka判斷一個節(jié)點還活著的兩個條件

          1. 節(jié)點必須可以維護和 ZooKeeper 的連接,Zookeeper 通過心跳機制檢查每個節(jié)點的連接

          2. 如果節(jié)點是個 follower,他必須能及時的同步 leader 的寫操作,延時不能太久

          Dubbo

          Dubbo的容錯機制

          1. 失敗自動切換,當出現(xiàn)失敗,重試其它服務(wù)器。通常用于讀操作,但重試會帶來更長延遲。可通過 retries="2" 來設(shè)置重試次數(shù)

          2. 快速失敗,只發(fā)起一次調(diào)用,失敗立即報錯。通常用于非冪等性的寫操作,比如新增記錄。

          3. 失敗安全,出現(xiàn)異常時,直接忽略。通常用于寫入審計日志等操作。

          4. 失敗自動恢復(fù),后臺記錄失敗請求,定時重發(fā)。通常用于消息通知操作。

          5. 并行調(diào)用多個服務(wù)器,只要一個成功即返回。通常用于實時性要求較高的讀操作,但需要浪費更多服務(wù)資源。可通過 forks="2" 來設(shè)置最大并行數(shù)。

          6. 廣播調(diào)用所有提供者,逐個調(diào)用,任意一臺報錯則報錯。通常用于通知所有提供者更新緩存或日志等本地資源信息

          Dubbo注冊中心掛了還可以繼續(xù)通信么

          可以,因為剛開始初始化的時候,消費者會將提供者的地址等信息拉取到本地緩存,所以注冊中心掛了可以繼續(xù)通信。

          Dubbo框架設(shè)計結(jié)構(gòu)

          1. 服務(wù)接口層:該層是與實際業(yè)務(wù)邏輯相關(guān)的,根據(jù)服務(wù)提供方和服務(wù)消費方的業(yè)務(wù)設(shè)計對應(yīng)的接口和實現(xiàn)。

          2. 配置層:對外配置接口,以ServiceConfig和ReferenceConfig為中心,可以直接new配置類,也可以通過spring解析配置生成配置類。

          3. 服務(wù)代理層:服務(wù)接口透明代理,生成服務(wù)的客戶端Stub和服務(wù)器端Skeleton,以ServiceProxy為中心,擴展接口為ProxyFactory。

          4. 服務(wù)注冊層:封裝服務(wù)地址的注冊與發(fā)現(xiàn),以服務(wù)URL為中心,擴展接口為RegistryFactory、Registry和RegistryService。可能沒有服務(wù)注冊中心,此時服務(wù)提供方直接暴露服務(wù)。

          5. 集群層:封裝多個提供者的路由及負載均衡,并橋接注冊中心,以Invoker為中心,擴展接口為Cluster、Directory、Router和LoadBalance。將多個服務(wù)提供方組合為一個服務(wù)提供方,實現(xiàn)對服務(wù)消費方來透明,只需要與一個服務(wù)提供方進行交互。

          6. 監(jiān)控層:RPC調(diào)用次數(shù)和調(diào)用時間監(jiān)控,以Statistics為中心,擴展接口為MonitorFactory、Monitor和MonitorService。

          7. 遠程調(diào)用層:封將RPC調(diào)用,以Invocation和Result為中心,擴展接口為Protocol、Invoker和Exporter。Protocol是服務(wù)域,它是Invoker暴露和引用的主功能入口,它負責(zé)Invoker的生命周期管理。Invoker是實體域,它是Dubbo的核心模型,其它模型都向它靠擾,或轉(zhuǎn)換成它,它代表一個可執(zhí)行體,可向它發(fā)起invoke調(diào)用,它有可能是一個本地的實現(xiàn),也可能是一個遠程的實現(xiàn),也可能一個集群實現(xiàn)。

          8. 信息交換層:封裝請求響應(yīng)模式,同步轉(zhuǎn)異步,以Request和Response為中心,擴展接口為Exchanger、ExchangeChannel、ExchangeClient和ExchangeServer。

          9. 網(wǎng)絡(luò)傳輸層:抽象mina和netty為統(tǒng)一接口,以Message為中心,擴展接口為Channel、Transporter、Client、Server和Codec。

          10. 數(shù)據(jù)序列化層:可復(fù)用的一些工具,擴展接口為Serialization、 ObjectInput、ObjectOutput和ThreadPool。

          操作系統(tǒng)

          進程和線程

          1. 進程是操作系統(tǒng)資源分配的最小單位,線程是CPU任務(wù)調(diào)度的最小單位。一個進程可以包含多個線程,所以進程和線程都是一個時間段的描述,是CPU工作時間段的描述,不過是顆粒大小不同。

          2. 不同進程間數(shù)據(jù)很難共享,同一進程下不同線程間數(shù)據(jù)很易共享。

          3. 每個進程都有獨立的代碼和數(shù)據(jù)空間,進程要比線程消耗更多的計算機資源。線程可以看做輕量級的進程,同一類線程共享代碼和數(shù)據(jù)空間,每個線程都有自己獨立的運行棧和程序計數(shù)器,線程之間切換的開銷小。

          4. 進程間不會相互影響,一個線程掛掉將導(dǎo)致整個進程掛掉。

          5. 系統(tǒng)在運行的時候會為每個進程分配不同的內(nèi)存空間;而對線程而言,除了CPU外,系統(tǒng)不會為線程分配內(nèi)存(線程所使用的資源來自其所屬進程的資源),線程組之間只能共享資源。

          進程的組成部分

          進程由進程控制塊(PCB)、程序段、數(shù)據(jù)段三部分組成。

          進程的通信方式

          1. 無名管道:半雙工的,即數(shù)據(jù)只能在一個方向上流動,只能用于具有親緣關(guān)系的進程之間的通信,可以看成是一種特殊的文件,對于它的讀寫也可以使用普通的read、write 等函數(shù)。但是它不是普通的文件,并不屬于其他任何文件系統(tǒng),并且只存在于內(nèi)存中。

          2. FIFO命名管道:FIFO是一種文件類型,可以在無關(guān)的進程之間交換數(shù)據(jù),與無名管道不同,F(xiàn)IFO有路徑名與之相關(guān)聯(lián),它以一種特殊設(shè)備文件形式存在于文件系統(tǒng)中。

          3. 消息隊列:消息隊列,是消息的鏈接表,存放在內(nèi)核中。一個消息隊列由一個標識符(即隊列ID)來標識。

          4. 信號量:信號量是一個計數(shù)器,信號量用于實現(xiàn)進程間的互斥與同步,而不是用于存儲進程間通信數(shù)據(jù)。

          5. 共享內(nèi)存:共享內(nèi)存指兩個或多個進程共享一個給定的存儲區(qū),一般配合信號量使用。

          進程間五種通信方式的比較

          1. 管道:速度慢,容量有限,只有父子進程能通訊。

          2. FIFO:任何進程間都能通訊,但速度慢。

          3. 消息隊列:容量受到系統(tǒng)限制,且要注意第一次讀的時候,要考慮上一次沒有讀完數(shù)據(jù)的問題。

          4. 信號量:不能傳遞復(fù)雜消息,只能用來同步。

          5. 共享內(nèi)存區(qū):能夠很容易控制容量,速度快,但要保持同步,比如一個進程在寫的時候,另一個進程要注意讀寫的問題,相當于線程中的線程安全,當然,共享內(nèi)存區(qū)同樣可以用作線程間通訊,不過沒這個必要,線程間本來就已經(jīng)共享了同一進程內(nèi)的一塊內(nèi)存。

          死鎖的4個必要條件

          1. 互斥條件:一個資源每次只能被一個線程使用;

          2. 請求與保持條件:一個線程因請求資源而阻塞時,對已獲得的資源保持不放;

          3. 不剝奪條件:進程已經(jīng)獲得的資源,在未使用完之前,不能強行剝奪;

          4. 循環(huán)等待條件:若干線程之間形成一種頭尾相接的循環(huán)等待資源關(guān)系。?

          如何避免(預(yù)防)死鎖

          1. 破壞“請求和保持”條件:讓進程在申請資源時,一次性申請所有需要用到的資源,不要一次一次來申請,當申請的資源有一些沒空,那就讓線程等待。不過這個方法比較浪費資源,進程可能經(jīng)常處于饑餓狀態(tài)。還有一種方法是,要求進程在申請資源前,要釋放自己擁有的資源。

          2. 破壞“不可搶占”條件:允許進程進行搶占,方法一:如果去搶資源,被拒絕,就釋放自己的資源。方法二:操作系統(tǒng)允許搶,只要你優(yōu)先級大,可以搶到。

          3. 破壞“循環(huán)等待”條件:將系統(tǒng)中的所有資源統(tǒng)一編號,進程可在任何時刻提出資源申請,但所有申請必須按照資源的編號順序提出(指定獲取鎖的順序,順序加鎖)。

          計算機網(wǎng)路

          Get和Post區(qū)別

          1. Get是不安全的,因為在傳輸過程,數(shù)據(jù)被放在請求的URL中;Post的所有操作對用戶來說都是不可見的。

          2. Get傳送的數(shù)據(jù)量較小,這主要是因為受URL長度限制;Post傳送的數(shù)據(jù)量較大,一般被默認為不受限制。

          3. Get限制Form表單的數(shù)據(jù)集的值必須為ASCII字符;而Post支持整個ISO10646字符集。

          4. Get執(zhí)行效率卻比Post方法好。Get是form提交的默認方法。

          5. GET產(chǎn)生一個TCP數(shù)據(jù)包;POST產(chǎn)生兩個TCP數(shù)據(jù)包。(非必然,客戶端可靈活決定)

          Http請求的完全過程

          1. 瀏覽器根據(jù)域名解析IP地址(DNS),并查DNS緩存

          2. 瀏覽器與WEB服務(wù)器建立一個TCP連接

          3. 瀏覽器給WEB服務(wù)器發(fā)送一個HTTP請求(GET/POST):一個HTTP請求報文由請求行(request line)、請求頭部(headers)、空行(blank line)和請求數(shù)據(jù)(request body)4個部分組成。

          4. 服務(wù)端響應(yīng)HTTP響應(yīng)報文,報文由狀態(tài)行(status line)、相應(yīng)頭部(headers)、空行(blank line)和響應(yīng)數(shù)據(jù)(response body)4個部分組成。

          5. 瀏覽器解析渲染

          tcp和udp區(qū)別

          1. TCP面向連接,UDP是無連接的,即發(fā)送數(shù)據(jù)之前不需要建立連接。

          2. TCP提供可靠的服務(wù)。也就是說,通過TCP連接傳送的數(shù)據(jù),無差錯,不丟失,不重復(fù),且按序到達;UDP盡最大努力交付,即不保證可靠交付。

          3. TCP面向字節(jié)流,實際上是TCP把數(shù)據(jù)看成一連串無結(jié)構(gòu)的字節(jié)流,UDP是面向報文的,UDP沒有擁塞控制,因此網(wǎng)絡(luò)出現(xiàn)擁塞不會使源主機的發(fā)送速率降低(對實時應(yīng)用很有用,如IP電話,實時視頻會議等)

          4. 每一條TCP連接只能是點到點的,UDP支持一對一,一對多,多對一和多對多的交互通信。

          5. TCP首部開銷20字節(jié),UDP的首部開銷小,只有8個字節(jié)。

          6. TCP的邏輯通信信道是全雙工的可靠信道,UDP則是不可靠信道。

          tcp和udp的優(yōu)點

          • TCP的優(yōu)點:可靠,穩(wěn)定 TCP的可靠體現(xiàn)在TCP在傳遞數(shù)據(jù)之前,會有三次握手來建立連接,而且在數(shù)據(jù)傳遞時,有確認、窗口、重傳、擁塞控制機制,在數(shù)據(jù)傳完后,還會斷開連接用來節(jié)約系統(tǒng)資源。TCP的缺點:慢,效率低,占用系統(tǒng)資源高,易被攻擊 TCP在傳遞數(shù)據(jù)之前,要先建連接,這會消耗時間,而且在數(shù)據(jù)傳遞時,確認機制、重傳機制、擁塞控制機制等都會消耗大量的時間,而且要在每臺設(shè)備上維護所有的傳輸連接,事實上,每個連接都會占用系統(tǒng)的CPU、內(nèi)存等硬件資源。而且,因為TCP有確認機制、三次握手機制,這些也導(dǎo)致TCP容易被人利用,實現(xiàn)DOS、DDOS、CC等攻擊。

          • UDP的優(yōu)點:快,比TCP稍安全 UDP沒有TCP的握手、確認、窗口、重傳、擁塞控制等機制,UDP是一個無狀態(tài)的傳輸協(xié)議,所以它在傳遞數(shù)據(jù)時非常快。沒有TCP的這些機制,UDP較TCP被攻擊者利用的漏洞就要少一些。但UDP也是無法避免攻擊的,比如:UDP Flood攻擊…… UDP的缺點:不可靠,不穩(wěn)定 因為UDP沒有TCP那些可靠的機制,在數(shù)據(jù)傳遞時,如果網(wǎng)絡(luò)質(zhì)量不好,就會很容易丟包。基于上面的優(yōu)缺點,那么:什么時候應(yīng)該使用TCP:當對網(wǎng)絡(luò)通訊質(zhì)量有要求的時候,比如:整個數(shù)據(jù)要準確無誤的傳遞給對方,這往往用于一些要求可靠的應(yīng)用,比如HTTP、HTTPS、FTP等傳輸文件的協(xié)議,POP、SMTP等郵件傳輸?shù)膮f(xié)議。在日常生活中,常見使用TCP協(xié)議的應(yīng)用如下:瀏覽器,用的HTTP FlashFXP,用的FTP Outlook,用的POP、SMTP Putty,用的Telnet、SSH QQ文件傳輸。什么時候應(yīng)該使用UDP:當對網(wǎng)絡(luò)通訊質(zhì)量要求不高的時候,要求網(wǎng)絡(luò)通訊速度能盡量的快,這時就可以使用UDP。比如,日常生活中,常見使用UDP協(xié)議的應(yīng)用如下:QQ語音 QQ視頻 TFTP。

          三次握手

          • 第一次握手:建立連接時,客戶端發(fā)送syn包(syn=x)到服務(wù)器,并進入SYN_SENT狀態(tài),等待服務(wù)器確認;SYN:同步序列編號(Synchronize Sequence Numbers)。

          • 第二次握手:服務(wù)器收到syn包,必須確認客戶的SYN(ack=x+1),同時自己也發(fā)送一個SYN包(syn=y),即SYN+ACK包,此時服務(wù)器進入SYN_RECV狀態(tài);

          • 第三次握手:客戶端收到服務(wù)器的SYN+ACK包,向服務(wù)器發(fā)送確認包ACK(ack=y+1),此包發(fā)送完畢,客戶端和服務(wù)器進入ESTABLISHED(TCP連接成功)狀態(tài),完成三次握手。

          為什么不能兩次握手

          TCP是一個雙向通信協(xié)議,通信雙方都有能力發(fā)送信息,并接收響應(yīng)。如果只是兩次握手, 至多只有連接發(fā)起方的起始序列號能被確認, 另一方選擇的序列號則得不到確認

          四次揮手

          1. 客戶端進程發(fā)出連接釋放報文,并且停止發(fā)送數(shù)據(jù)。釋放數(shù)據(jù)報文首部,F(xiàn)IN=1,其序列號為seq=u(等于前面已經(jīng)傳送過來的數(shù)據(jù)的最后一個字節(jié)的序號加1),此時,客戶端進入FIN-WAIT-1(終止等待1)狀態(tài)。TCP規(guī)定,F(xiàn)IN報文段即使不攜帶數(shù)據(jù),也要消耗一個序號。

          2. 服務(wù)器收到連接釋放報文,發(fā)出確認報文,ACK=1,ack=u+1,并且?guī)献约旱男蛄刑杝eq=v,此時,服務(wù)端就進入了CLOSE-WAIT(關(guān)閉等待)狀態(tài)。TCP服務(wù)器通知高層的應(yīng)用進程,客戶端向服務(wù)器的方向就釋放了,這時候處于半關(guān)閉狀態(tài),即客戶端已經(jīng)沒有數(shù)據(jù)要發(fā)送了,但是服務(wù)器若發(fā)送數(shù)據(jù),客戶端依然要接受。這個狀態(tài)還要持續(xù)一段時間,也就是整個CLOSE-WAIT狀態(tài)持續(xù)的時間。

          3. 客戶端收到服務(wù)器的確認請求后,此時,客戶端就進入FIN-WAIT-2(終止等待2)狀態(tài),等待服務(wù)器發(fā)送連接釋放報文(在這之前還需要接受服務(wù)器發(fā)送的最后的數(shù)據(jù))。

          4. 服務(wù)器將最后的數(shù)據(jù)發(fā)送完畢后,就向客戶端發(fā)送連接釋放報文,F(xiàn)IN=1,ack=u+1,由于在半關(guān)閉狀態(tài),服務(wù)器很可能又發(fā)送了一些數(shù)據(jù),假定此時的序列號為seq=w,此時,服務(wù)器就進入了LAST-ACK(最后確認)狀態(tài),等待客戶端的確認。

          5. 客戶端收到服務(wù)器的連接釋放報文后,必須發(fā)出確認,ACK=1,ack=w+1,而自己的序列號是seq=u+1,此時,客戶端就進入了TIME-WAIT(時間等待)狀態(tài)。注意此時TCP連接還沒有釋放,必須經(jīng)過2??MSL(最長報文段壽命)的時間后,當客戶端撤銷相應(yīng)的TCB后,才進入CLOSED狀態(tài)。

          6. 服務(wù)器只要收到了客戶端發(fā)出的確認,立即進入CLOSED狀態(tài)。同樣,撤銷TCB后,就結(jié)束了這次的TCP連接。可以看到,服務(wù)器結(jié)束TCP連接的時間要比客戶端早一些

          為什么連接的時候是三次握手,關(guān)閉的時候卻是四次握手

          因為當Server端收到Client端的SYN連接請求報文后,可以直接發(fā)送SYN+ACK報文。其中ACK報文是用來應(yīng)答的,SYN報文是用來同步的。但是關(guān)閉連接時,當Server端收到FIN報文時,很可能并不會立即關(guān)閉SOCKET,所以只能先回復(fù)一個ACK報文,告訴Client端,"你發(fā)的FIN報文我收到了"。只有等到我Server端所有的報文都發(fā)送完了,我才能發(fā)送FIN報文,因此不能一起發(fā)送。故需要四步握手。

          數(shù)據(jù)結(jié)構(gòu)與算法

          排序算法

          1. 冒泡排序

          2. 選擇排序:選擇排序與冒泡排序有點像,只不過選擇排序每次都是在確定了最小數(shù)的下標之后再進行交換,大大減少了交換的次數(shù)

          3. 插入排序:將一個記錄插入到已排序的有序表中,從而得到一個新的,記錄數(shù)增1的有序表

          4. 快速排序:通過一趟排序?qū)⑿蛄蟹殖勺笥覂刹糠郑渲凶蟀氩糠值牡闹稻扔野氩糠值闹敌。缓笤俜謩e對左右部分的記錄進行排序,直到整個序列有序。

          int?partition(int?a[],??int?low,?int?high){
          ????int?key?=?a[low];
          ????while(?low?????????while(low?=?key)?high--;
          ????????a[low]?=?a[high];
          ????????while(low?????????a[high]?=?a[low];
          ????}
          ????a[low]?=?key;
          ????return?low;
          }
          void?quick_sort(int?a[],?int?low,?int?high){
          ????if(low?>=?high)?return;
          ????int?keypos?=?partition(a,?low,?high);
          ????quick_sort(a,?low,?keypos-1);
          ????quick_sort(a,?keypos+1,?high);
          }
          1. 堆排序:假設(shè)序列有n個元素,先將這n建成大頂堆,然后取堆頂元素,與序列第n個元素交換,然后調(diào)整前n-1元素,使其重新成為堆,然后再取堆頂元素,與第n-1個元素交換,再調(diào)整前n-2個元素…直至整個序列有序。

          2. 希爾排序:先將整個待排記錄序列分割成為若干子序列分別進行直接插入排序,待整個序列中的記錄基本有序時再對全體記錄進行一次直接插入排序。

          3. 歸并排序:把有序表劃分成元素個數(shù)盡量相等的兩半,把兩半元素分別排序,兩個有序表合并成一個

          實際問題

          高并發(fā)系統(tǒng)的設(shè)計與實現(xiàn)

          在開發(fā)高并發(fā)系統(tǒng)時有三把利器用來保護系統(tǒng):緩存、降級和限流。

          • 緩存:緩存比較好理解,在大型高并發(fā)系統(tǒng)中,如果沒有緩存數(shù)據(jù)庫將分分鐘被爆,系統(tǒng)也會瞬間癱瘓。使用緩存不單單能夠提升系統(tǒng)訪問速度、提高并發(fā)訪問量,也是保護數(shù)據(jù)庫、保護系統(tǒng)的有效方式。大型網(wǎng)站一般主要是“讀”,緩存的使用很容易被想到。在大型“寫”系統(tǒng)中,緩存也常常扮演者非常重要的角色。比如累積一些數(shù)據(jù)批量寫入,內(nèi)存里面的緩存隊列(生產(chǎn)消費),以及HBase寫數(shù)據(jù)的機制等等也都是通過緩存提升系統(tǒng)的吞吐量或者實現(xiàn)系統(tǒng)的保護措施。甚至消息中間件,你也可以認為是一種分布式的數(shù)據(jù)緩存。

          • 降級:服務(wù)降級是當服務(wù)器壓力劇增的情況下,根據(jù)當前業(yè)務(wù)情況及流量對一些服務(wù)和頁面有策略的降級,以此釋放服務(wù)器資源以保證核心任務(wù)的正常運行。降級往往會指定不同的級別,面臨不同的異常等級執(zhí)行不同的處理。根據(jù)服務(wù)方式:可以拒接服務(wù),可以延遲服務(wù),也有時候可以隨機服務(wù)。根據(jù)服務(wù)范圍:可以砍掉某個功能,也可以砍掉某些模塊。總之服務(wù)降級需要根據(jù)不同的業(yè)務(wù)需求采用不同的降級策略。主要的目的就是服務(wù)雖然有損但是總比沒有好。

          • 限流:限流可以認為服務(wù)降級的一種,限流就是限制系統(tǒng)的輸入和輸出流量已達到保護系統(tǒng)的目的。一般來說系統(tǒng)的吞吐量是可以被測算的,為了保證系統(tǒng)的穩(wěn)定運行,一旦達到的需要限制的閾值,就需要限制流量并采取一些措施以完成限制流量的目的。比如:延遲處理,拒絕處理,或者部分拒絕處理等等。

          常見的限流算法:

          常見的限流算法有計數(shù)器、漏桶和令牌桶算法。漏桶算法在分布式環(huán)境中消息中間件或者Redis都是可選的方案。發(fā)放令牌的頻率增加可以提升整體數(shù)據(jù)處理的速度,而通過每次獲取令牌的個數(shù)增加或者放慢令牌的發(fā)放速度和降低整體數(shù)據(jù)處理速度。而漏桶不行,因為它的流出速率是固定的,程序處理速度也是固定的。

          秒殺并發(fā)情況下庫存為負數(shù)問題

          1. for update顯示加鎖

          2. 把udpate語句寫在前邊,先把數(shù)量-1,之后select出庫存如果>-1就commit,否則rollback。

          update?products?set?quantity?=?quantity-1?WHERE?id=3;
          select?quantity?from?products?WHERE?id=3?for?update;
          1. update語句在更新的同時加上一個條件

          quantity?=?select?quantity?from?products?WHERE?id=3;
          update?products?set?quantity?=?($quantity-1)?WHERE?id=3?and?queantity?=?$quantity;


          戳:百萬字長文帶你學(xué)習(xí)「Java」


          如果大家想要實時關(guān)注我更新的文章以及分享的干貨的話,可以關(guān)注我的公眾號Java3y

          • 獲取Java精美腦圖2e33ca0f8c3868c730ff69e5df36b2b1.webp

          • ?獲取Java學(xué)習(xí)路線2e33ca0f8c3868c730ff69e5df36b2b1.webp

          • 獲取開發(fā)常用工具2e33ca0f8c3868c730ff69e5df36b2b1.webp

          • ?加入技術(shù)交流群2e33ca0f8c3868c730ff69e5df36b2b1.webp

          在公眾號下回復(fù)「888」即可獲取!!

          04150dd3cdfdc0d7c79b2f73c90b5ab7.webp

          點個在看090fca3df6a18a9ae31a42dffa0c425d.webp,分享到朋友圈6a843e92be8eed203c9bc0a267202826.webp,對我真的很重要!!


          瀏覽 50
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧美三级电影网站 | 色黄片免费 | 日日色综合 | 国产精品国产三级国产专播品爱网 | 亚洲激情视频在线 |