總結Java開發面試常問的問題,持續更新中~

GitHub地址:github.com/zaiyunduan1…,若是對你有幫助歡迎Starcss

數據庫

mysql

爲何用自增列做爲主鍵

  1. 若是咱們定義了主鍵(PRIMARY KEY),那麼InnoDB會選擇主鍵做爲彙集索引、若是沒有顯式定義主鍵,則InnoDB會選擇第一個不包含有NULL值的惟一索引做爲主鍵索引、若是也沒有這樣的惟一索引,則InnoDB會選擇內置6字節長的ROWID做爲隱含的彙集索引(ROWID隨着行記錄的寫入而主鍵遞增,這個ROWID不像ORACLE的ROWID那樣可引用,是隱含的)。html

  2. 數據記錄自己被存於主索引(一顆B+Tree)的葉子節點上。這就要求同一個葉子節點內(大小爲一個內存頁或磁盤頁)的各條數據記錄按主鍵順序存放,所以每當有一條新的記錄插入時,MySQL會根據其主鍵將其插入適當的節點和位置,若是頁面達到裝載因子(InnoDB默認爲15/16),則開闢一個新的頁(節點)前端

  3. 若是表使用自增主鍵,那麼每次插入新的記錄,記錄就會順序添加到當前索引節點的後續位置,當一頁寫滿,就會自動開闢一個新的頁java

  4. 若是使用非自增主鍵(若是身份證號或學號等),因爲每次插入主鍵的值近似於隨機,所以每次新紀錄都要被插到現有索引頁得中間某個位置,此時MySQL不得不爲了將新記錄插到合適位置而移動數據,甚至目標頁面可能已經被回寫到磁盤上而從緩存中清掉,此時又要從磁盤上讀回來,這增長了不少開銷,同時頻繁的移動、分頁操做形成了大量的碎片,獲得了不夠緊湊的索引結構,後續不得不經過OPTIMIZE TABLE來重建表並優化填充頁面。node

爲何使用數據索引能提升效率

  1. 數據索引的存儲是有序的
  2. 在有序的狀況下,經過索引查詢一個數據是無需遍歷索引記錄的
  3. 極端狀況下,數據索引的查詢效率爲二分法查詢效率,趨近於 log2(N)

B+樹索引和哈希索引的區別

B+樹是一個平衡的多叉樹,從根節點到每一個葉子節點的高度差值不超過1,並且同層級的節點間有指針相互連接,是有序的 mysql

哈希索引就是採用必定的哈希算法,把鍵值換算成新的哈希值,檢索時不須要相似B+樹那樣從根節點到葉子節點逐級查找,只需一次哈希算法便可,是無序的

哈希索引的優點:linux

  1. 等值查詢。哈希索引具備絕對優點(前提是:沒有大量重複鍵值,若是大量重複鍵值時,哈希索引的效率很低,由於存在所謂的哈希碰撞問題。)

哈希索引不適用的場景:git

  1. 不支持範圍查詢
  2. 不支持索引完成排序
  3. 不支持聯合索引的最左前綴匹配規則

一般,B+樹索引結構適用於絕大多數場景,像下面這種場景用哈希索引才更有優點:程序員

在HEAP表中,若是存儲的數據重複度很低(也就是說基數很大),對該列數據以等值查詢爲主,沒有範圍查詢、沒有排序的時候,特別適合採用哈希索引,例如這種SQL:github

select id,name from table where name='李明'; — 僅等值查詢
複製代碼

而經常使用的InnoDB引擎中默認使用的是B+樹索引,它會實時監控表上索引的使用狀況,若是認爲創建哈希索引能夠提升查詢效率,則自動在內存中的「自適應哈希索引緩衝區」創建哈希索引(在InnoDB中默認開啓自適應哈希索引),經過觀察搜索模式,MySQL會利用index key的前綴創建哈希索引,若是一個表幾乎大部分都在緩衝池中,那麼創建一個哈希索引可以加快等值查詢。

注意:在某些工做負載下,經過哈希索引查找帶來的性能提高遠大於額外的監控索引搜索狀況和保持這個哈希表結構所帶來的開銷。但某些時候,在負載高的狀況下,自適應哈希索引中添加的read/write鎖也會帶來競爭,好比高併發的join操做。like操做和%的通配符操做也不適用於自適應哈希索引,可能要關閉自適應哈希索引。

B樹和B+樹的區別

  1. B樹,每一個節點都存儲key和data,全部節點組成這棵樹,而且葉子節點指針爲nul,葉子結點不包含任何關鍵字信息。
    這裏寫圖片描述
  2. B+樹,全部的葉子結點中包含了所有關鍵字的信息,及指向含有這些關鍵字記錄的指針,且葉子結點自己依關鍵字的大小自小而大的順序連接,全部的非終端結點能夠當作是索引部分,結點中僅含有其子樹根結點中最大(或最小)關鍵字。 (而B 樹的非終節點也包含須要查找的有效信息)

這裏寫圖片描述

爲何說B+比B樹更適合實際應用中操做系統的文件索引和數據庫索引?

  1. B+的磁盤讀寫代價更低 B+的內部結點並無指向關鍵字具體信息的指針。所以其內部結點相對B樹更小。若是把全部同一內部結點的關鍵字存放在同一盤塊中,那麼盤塊所能容納的關鍵字數量也越多。一次性讀入內存中的須要查找的關鍵字也就越多。相對來講IO讀寫次數也就下降了。

  2. B+-tree的查詢效率更加穩定 因爲非終結點並非最終指向文件內容的結點,而只是葉子結點中關鍵字的索引。因此任何關鍵字的查找必須走一條從根結點到葉子結點的路。全部關鍵字查詢的路徑長度相同,致使每個數據的查詢效率至關。

mysql聯合索引

  1. 聯合索引是兩個或更多個列上的索引。對於聯合索引:Mysql從左到右的使用索引中的字段,一個查詢能夠只使用索引中的一部份,但只能是最左側部分。例如索引是key index (a,b,c). 能夠支持a 、 a,b 、 a,b,c 3種組合進行查找,但不支持 b,c進行查找 .當最左側字段是常量引用時,索引就十分有效。
  2. 利用索引中的附加列,您能夠縮小搜索的範圍,但使用一個具備兩列的索引 不一樣於使用兩個單獨的索引。複合索引的結構與電話簿相似,人名由姓和名構成,電話簿首先按姓氏對進行排序,而後按名字對有相同姓氏的人進行排序。若是您知 道姓,電話簿將很是有用;若是您知道姓和名,電話簿則更爲有用,但若是您只知道名不姓,電話簿將沒有用處。

什麼狀況下應不建或少建索引

  1. 表記錄太少
  2. 常常插入、刪除、修改的表
  3. 數據重複且分佈平均的表字段,假如一個表有10萬行記錄,有一個字段A只有T和F兩種值,且每一個值的分佈機率大約爲50%,那麼對這種表A字段建索引通常不會提升數據庫的查詢速度。
  4. 常常和主字段一塊查詢但主字段索引值比較多的表字段

MySQL分區

什麼是表分區?

表分區,是指根據必定規則,將數據庫中的一張表分解成多個更小的,容易管理的部分。從邏輯上看,只有一張表,可是底層倒是由多個物理分區組成。

表分區與分表的區別

分表:指的是經過必定規則,將一張表分解成多張不一樣的表。好比將用戶訂單記錄根據時間成多個表。

分表與分區的區別在於:分區從邏輯上來說只有一張表,而分表則是將一張表分解成多張表。

表分區有什麼好處?

  1. 分區表的數據能夠分佈在不一樣的物理設備上,從而高效地利用多個硬件設備。 2. 和單個磁盤或者文件系統相比,能夠存儲更多數據
  2. 優化查詢。在where語句中包含分區條件時,能夠只掃描一個或多個分區表來提升查詢效率;涉及sum和count語句時,也能夠在多個分區上並行處理,最後彙總結果。
  3. 分區表更容易維護。例如:想批量刪除大量數據能夠清除整個分區。
  4. 能夠使用分區表來避免某些特殊的瓶頸,例如InnoDB的單個索引的互斥訪問,ext3問價你係統的inode鎖競爭等。

分區表的限制因素

  1. 一個表最多隻能有1024個分區
  2. MySQL5.1中,分區表達式必須是整數,或者返回整數的表達式。在MySQL5.5中提供了非整數表達式分區的支持。
  3. 若是分區字段中有主鍵或者惟一索引的列,那麼多有主鍵列和惟一索引列都必須包含進來。即:分區字段要麼不包含主鍵或者索引列,要麼包含所有主鍵和索引列。
  4. 分區表中沒法使用外鍵約束
  5. MySQL的分區適用於一個表的全部數據和索引,不能只對表數據分區而不對索引分區,也不能只對索引分區而不對錶分區,也不能只對表的一部分數據分區。

如何判斷當前MySQL是否支持分區?

命令:show variables like '%partition%' 運行結果:

mysql> show variables like '%partition%'; +-------------------+-------+ | Variable_name | Value | +-------------------+-------+ | have_partitioning | YES | +-------------------+-------+ 1 row in set (0.00 sec) have_partintioning 的值爲YES,表示支持分區。

MySQL支持的分區類型有哪些?

  1. RANGE分區: 這種模式容許將數據劃分不一樣範圍。例如能夠將一個表經過年份劃分紅若干個分區
  2. LIST分區: 這種模式容許系統經過預約義的列表的值來對數據進行分割。按照List中的值分區,與RANGE的區別是,range分區的區間範圍值是連續的。
  3. HASH分區 :這中模式容許經過對錶的一個或多個列的Hash Key進行計算,最後經過這個Hash碼不一樣數值對應的數據區域進行分區。例如能夠創建一個對錶主鍵進行分區的表。
  4. KEY分區 :上面Hash模式的一種延伸,這裏的Hash Key是MySQL系統產生的。

四種隔離級別

  1. Serializable (串行化):可避免髒讀、不可重複讀、幻讀的發生。
  2. Repeatable read (可重複讀):可避免髒讀、不可重複讀的發生。
  3. Read committed (讀已提交):可避免髒讀的發生。
  4. Read uncommitted (讀未提交):最低級別,任何狀況都沒法保證。

關於MVVC

MySQL InnoDB存儲引擎,實現的是基於多版本的併發控制協議——MVCC (Multi-Version Concurrency Control) (注:與MVCC相對的,是基於鎖的併發控制,Lock-Based Concurrency Control)。MVCC最大的好處:讀不加鎖,讀寫不衝突。在讀多寫少的OLTP應用中,讀寫不衝突是很是重要的,極大的增長了系統的併發性能,現階段幾乎全部的RDBMS,都支持了MVCC。

  1. LBCC:Lock-Based Concurrency Control,基於鎖的併發控制。
  2. MVCC:Multi-Version Concurrency Control,基於多版本的併發控制協議。純粹基於鎖的併發機制併發量低,MVCC是在基於鎖的併發控制上的改進,主要是在讀操做上提升了併發量。

在MVCC併發控制中,讀操做能夠分紅兩類:

  1. 快照讀 (snapshot read):讀取的是記錄的可見版本 (有多是歷史版本),不用加鎖(共享讀鎖s鎖也不加,因此不會阻塞其餘事務的寫)。
  2. 當前讀 (current read):讀取的是記錄的最新版本,而且,當前讀返回的記錄,都會加上鎖,保證其餘事務不會再併發修改這條記錄。

行級鎖定的優勢:

  1. 當在許多線程中訪問不一樣的行時只存在少許鎖定衝突。
  2. 回滾時只有少許的更改
  3. 能夠長時間鎖定單一的行。

行級鎖定的缺點:

  1. 比頁級或表級鎖定佔用更多的內存。
  2. 當在表的大部分中使用時,比頁級或表級鎖定速度慢,由於你必須獲取更多的鎖。
  3. 若是你在大部分數據上常常進行GROUP BY操做或者必須常常掃描整個表,比其它鎖定明顯慢不少。
  4. 用高級別鎖定,經過支持不一樣的類型鎖定,你也能夠很容易地調節應用程序,由於其鎖成本小於行級鎖定。

MySQL 觸發器簡單實例

  1. CREATE TRIGGER <觸發器名稱> --觸發器必須有名字,最多64個字符,可能後面會附有分隔符.它和MySQL中其餘對象的命名方式基本相象.
  2. { BEFORE | AFTER } --觸發器有執行的時間設置:能夠設置爲事件發生前或後。
  3. { INSERT | UPDATE | DELETE } --一樣也能設定觸發的事件:它們能夠在執行insert、update或delete的過程當中觸發。
  4. ON <表名稱> --觸發器是屬於某一個表的:當在這個表上執行插入、 更新或刪除操做的時候就致使觸發器的激活. 咱們不能給同一張表的同一個事件安排兩個觸發器。
  5. FOR EACH ROW --觸發器的執行間隔:FOR EACH ROW子句通知觸發器 每隔一行執行一次動做,而不是對整個表執行一次。
  6. <觸發器SQL語句> --觸發器包含所要觸發的SQL語句:這裏的語句能夠是任何合法的語句, 包括複合語句,可是這裏的語句受的限制和函數的同樣。

什麼是存儲過程

簡單的說,就是一組SQL語句集,功能強大,能夠實現一些比較複雜的邏輯功能,相似於JAVA語言中的方法;

ps:存儲過程跟觸發器有點相似,都是一組SQL集,可是存儲過程是主動調用的,且功能比觸發器更增強大,觸發器是某件事觸發後自動調用;

有哪些特性

  1. 有輸入輸出參數,能夠聲明變量,有if/else, case,while等控制語句,經過編寫存儲過程,能夠實現複雜的邏輯功能;
  2. 函數的廣泛特性:模塊化,封裝,代碼複用;
  3. 速度快,只有首次執行需通過編譯和優化步驟,後續被調用能夠直接執行,省去以上步驟;
DROP PROCEDURE IF EXISTS `proc_adder`;
DELIMITER ;;
CREATE DEFINER=`root`@`localhost` PROCEDURE `proc_adder`(IN a int, IN b int, OUT sum int)
BEGIN
    #Routine body goes here...

    DECLARE c int;
    if a is null then set a = 0; 
    end if;
  
    if b is null then set b = 0;
    end if;

    set sum  = a + b;
END
;;
DELIMITER ;

set @b=5;
call proc_adder(0,@b,@s);
SELECT @s as sum;



create table tab2(
   tab2_id varchar(11)
);

DROP TRIGGER if EXISTS t_ai_on_tab1;
create TRAILING t_ai_on_tab1
AFTER INSERT ON tab1
for EACH ROW
BEGIN
   INSERT INTO tab2(tab2_id) values(new.tab1_id);
end;

INSERT INTO tab1(tab1_id) values('0001');

SELECT * FROM tab2;
複製代碼

MySQL優化

  1. 開啓查詢緩存,優化查詢
  2. explain你的select查詢,這能夠幫你分析你的查詢語句或是表結構的性能瓶頸。EXPLAIN 的查詢結果還會告訴你你的索引主鍵被如何利用的,你的數據表是如何被搜索和排序的
  3. 當只要一行數據時使用limit 1,MySQL數據庫引擎會在找到一條數據後中止搜索,而不是繼續日後查少下一條符合記錄的數據
  4. 爲搜索字段建索引
  5. 使用 ENUM 而不是 VARCHAR,若是你有一個字段,好比「性別」,「國家」,「民族」,「狀態」或「部門」,你知道這些字段的取值是有限並且固定的,那麼,你應該使用 ENUM 而不是VARCHAR。
  6. Prepared Statements Prepared Statements很像存儲過程,是一種運行在後臺的SQL語句集合,咱們能夠從使用 prepared statements 得到不少好處,不管是性能問題仍是安全問題。Prepared Statements 能夠檢查一些你綁定好的變量,這樣能夠保護你的程序不會受到「SQL注入式」攻擊
  7. 垂直分表
  8. 選擇正確的存儲引擎

key和index的區別

  1. key 是數據庫的物理結構,它包含兩層意義和做用,一是約束(偏重於約束和規範數據庫的結構完整性),二是索引(輔助查詢用的)。包括primary key, unique key, foreign key 等
  2. index是數據庫的物理結構,它只是輔助查詢的,它建立時會在另外的表空間(mysql中的innodb表空間)以一個相似目錄的結構存儲。索引要分類的話,分爲前綴索引、全文本索引等;

Mysql 中 MyISAM 和 InnoDB 的區別有哪些?

區別:

  1. InnoDB支持事務,MyISAM不支持,對於InnoDB每一條SQL語言都默認封裝成事務,自動提交,這樣會影響速度,因此最好把多條SQL語言放在begin和commit之間,組成一個事務;

  2. InnoDB支持外鍵,而MyISAM不支持。對一個包含外鍵的InnoDB錶轉爲MYISAM會失敗;

  3. InnoDB是彙集索引,數據文件是和索引綁在一塊兒的,必需要有主鍵,經過主鍵索引效率很高。可是輔助索引須要兩次查詢,先查詢到主鍵,而後再經過主鍵查詢到數據。所以,主鍵不該該過大,由於主鍵太大,其餘索引也都會很大。而MyISAM是非彙集索引,數據文件是分離的,索引保存的是數據文件的指針。主鍵索引和輔助索引是獨立的。

  4. InnoDB不保存表的具體行數,執行select count(*) from table時須要全表掃描。而MyISAM用一個變量保存了整個表的行數,執行上述語句時只須要讀出該變量便可,速度很快;

  5. Innodb不支持全文索引,而MyISAM支持全文索引,查詢效率上MyISAM要高;

如何選擇:

  1. 是否要支持事務,若是要請選擇innodb,若是不須要能夠考慮MyISAM;

  2. 若是表中絕大多數都只是讀查詢,能夠考慮MyISAM,若是既有讀寫也挺頻繁,請使用InnoDB。

  3. 系統奔潰後,MyISAM恢復起來更困難,可否接受;

  4. MySQL5.5版本開始Innodb已經成爲Mysql的默認引擎(以前是MyISAM),說明其優點是有目共睹的,若是你不知道用什麼,那就用InnoDB,至少不會差。

數據庫表建立注意事項

1、字段名及字段配製合理性

  1. 剔除關係不密切的字段

  2. 字段命名要有規則及相對應的含義(不要一部分英文,一部分拼音,還有相似a.b.c這樣不明含義的字段)

  3. 字段命名儘可能不要使用縮寫(大多數縮寫都不能明確字段含義)

  4. 字段不要大小寫混用(想要具備可讀性,多個英文單詞可以使用下劃線形式鏈接)

  5. 字段名不要使用保留字或者關鍵字

  6. 保持字段名和類型的一致性

  7. 慎重選擇數字類型

  8. 給文本字段留足餘量

2、系統特殊字段處理及建成後建議

  1. 添加刪除標記(例如操做人、刪除時間)

  2. 創建版本機制

3、表結構合理性配置

  1. 多型字段的處理,就是表中是否存在字段可以分解成更小獨立的幾部分(例如:人能夠分爲男人和女人)

  2. 多值字段的處理,能夠將表分爲三張表,這樣使得檢索和排序更加有調理,且保證數據的完整性!

4、其它建議

  1. 對於大數據字段,獨立表進行存儲,以便影響性能(例如:簡介字段)

  2. 使用varchar類型代替char,由於varchar會動態分配長度,char指定長度是固定的。

  3. 給表建立主鍵,對於沒有主鍵的表,在查詢和索引定義上有必定的影響。

  4. 避免表字段運行爲null,建議設置默認值(例如:int類型設置默認值爲0)在索引查詢上,效率立顯!

  5. 創建索引,最好創建在惟一和非空的字段上,創建太多的索引對後期插入、更新都存在必定的影響(考慮實際狀況來建立)。

redis

redis單線程問題

單線程指的是網絡請求模塊使用了一個線程(因此不需考慮併發安全性),即一個線程處理全部網絡請求,其餘模塊仍用了多個線程。

爲何說redis可以快速執行

  1. 絕大部分請求是純粹的內存操做(很是快速)
  2. 採用單線程,避免了沒必要要的上下文切換和競爭條件
  3. 非阻塞IO - IO多路複用

redis的內部實現

內部實現採用epoll,採用了epoll+本身實現的簡單的事件框架。epoll中的讀、寫、關閉、鏈接都轉化成了事件,而後利用epoll的多路複用特性,不在io上浪費一點時間 這3個條件不是相互獨立的,特別是第一條,若是請求都是耗時的,採用單線程吞吐量及性能不好。redis爲特殊的場景選擇了合適的技術方案。

Redis關於線程安全問題

redis其實是採用了線程封閉的觀念,把任務封閉在一個線程,天然避免了線程安全問題,不過對於須要依賴多個redis操做的複合操做來講,依然須要鎖,並且有多是分佈式鎖。

使用redis有哪些好處?

  1. 速度快,由於數據存在內存中,相似於HashMap,HashMap的優點就是查找和操做的時間複雜度都是O(1)
  2. 支持豐富數據類型,支持string,list,set,sorted set,hash
  3. 支持事務,操做都是原子性,所謂的原子性就是對數據的更改要麼所有執行,要麼所有不執行
  4. 豐富的特性:可用於緩存,消息,按key設置過時時間,過時後將會自動刪除

redis相比memcached有哪些優點?

  1. memcached全部的值均是簡單的字符串,redis做爲其替代者,支持更爲豐富的數據類型
  2. redis的速度比memcached快不少
  3. redis能夠持久化其數據
  4. Redis支持數據的備份,即master-slave模式的數據備份。
  5. 使用底層模型不一樣,它們之間底層實現方式 以及與客戶端之間通訊的應用協議不同。Redis直接本身構建了VM 機制 ,由於通常的系統調用系統函數的話,會浪費必定的時間去移動和請求。
  6. value大小:redis最大能夠達到1GB,而memcache只有1MB

Redis主從複製

過程原理:

  1. 當從庫和主庫創建MS關係後,會向主數據庫發送SYNC命令
  2. 主庫接收到SYNC命令後會開始在後臺保存快照(RDB持久化過程),並將期間接收到的寫命令緩存起來
  3. 當快照完成後,主Redis會將快照文件和全部緩存的寫命令發送給從Redis
  4. 從Redis接收到後,會載入快照文件而且執行收到的緩存的命令
  5. 以後,主Redis每當接收到寫命令時就會將命令發送從Redis,從而保證數據的一致

缺點:全部的slave節點數據的複製和同步都由master節點來處理,會照成master節點壓力太大,使用主從從結構來解決

redis兩種持久化方式的優缺點

  1. RDB 持久化能夠在指定的時間間隔內生成數據集的時間點快照(point-in-time snapshot)
  2. AOF 持久化記錄服務器執行的全部寫操做命令,並在服務器啓動時,經過從新執行這些命令來還原數據集。
  3. Redis 還能夠同時使用 AOF 持久化和 RDB 持久化。當redis重啓時,它會有限使用AOF文件來還原數據集,由於AOF文件保存的數據集一般比RDB文件所保存的數據集更加完整

RDB的優勢:

  1. RDB 是一個很是緊湊(compact)的文件,它保存了 Redis 在某個時間點上的數據集。 這種文件很是適合用於進行備份: 好比說,你能夠在最近的 24 小時內,每小時備份一次 RDB 文件,而且在每月的每一天,也備份一個 RDB 文件。 這樣的話,即便趕上問題,也能夠隨時將數據集還原到不一樣的版本。

  2. RDB 很是適用於災難恢復(disaster recovery):它只有一個文件,而且內容都很是緊湊,能夠(在加密後)將它傳送到別的數據中心,或者亞馬遜 S3 中。

  3. RDB 能夠最大化 Redis 的性能:父進程在保存 RDB 文件時惟一要作的就是 fork 出一個子進程,而後這個子進程就會處理接下來的全部保存工做,父進程無須執行任何磁盤 I/O 操做。

  4. RDB 在恢復大數據集時的速度比 AOF 的恢復速度要快

Redis 常見的性能問題都有哪些?如何解決?

  1. Master寫內存快照,save命令調度rdbSave函數,會阻塞主線程的工做,當快照比較大時對性能影響是很是大的,會間斷性暫停服務,因此Master最好不要寫內存快照。
  2. Master AOF持久化,若是不重寫AOF文件,這個持久化方式對性能的影響是最小的,可是AOF文件會不斷增大,AOF文件過大會影響Master重啓的恢復速度。Master最好不要作任何持久化工做,包括內存快照和AOF日誌文件,特別是不要啓用內存快照作持久化,若是數據比較關鍵,某個Slave開啓AOF備份數據,策略爲每秒同步一次。
  3. Master調用BGREWRITEAOF重寫AOF文件,AOF在重寫的時候會佔大量的CPU和內存資源,致使服務load太高,出現短暫服務暫停現象。
  4. Redis主從複製的性能問題,爲了主從複製的速度和鏈接的穩定性,Slave和Master最好在同一個局域網內

redis 提供 6種數據淘汰策略

  1. volatile-lru:從已設置過時時間的數據集(server.db[i].expires)中挑選最近最少使用的數據淘汰
  2. volatile-ttl:從已設置過時時間的數據集(server.db[i].expires)中挑選將要過時的數據淘汰
  3. volatile-random:從已設置過時時間的數據集(server.db[i].expires)中任意選擇數據淘汰
  4. allkeys-lru:從數據集(server.db[i].dict)中挑選最近最少使用的數據淘汰
  5. allkeys-random:從數據集(server.db[i].dict)中任意選擇數據淘汰
  6. no-enviction(驅逐):禁止驅逐數據

java

java虛擬機

這裏寫圖片描述
圖片來源(Hollis微信公衆號)

何時會觸發full gc

  1. System.gc()方法的調用
  2. 老年代空間不足
  3. 永生區空間不足(JVM規範中運行時數據區域中的方法區,在HotSpot虛擬機中又被習慣稱爲永生代或者永生區,Permanet Generation中存放的爲一些class的信息、常量、靜態變量等數據)
  4. GC時出現promotion failed和concurrent mode failure
  5. 統計獲得的Minor GC晉升到舊生代平均大小大於老年代剩餘空間
  6. 堆中分配很大的對象

能夠做爲root的對象:

  1. 類中的靜態變量,當它持有一個指向一個對象的引用時,它就做爲root
  2. 活動着的線程,能夠做爲root
  3. 一個Java方法的參數或者該方法中的局部變量,這兩種對象能夠做爲root
  4. JNI方法中的局部變量或者參數,這兩種對象能夠做爲root

例子:下述的Something和Apple均可以做爲root對象。

public AClass{
 
  public static Something;
  public static final Apple;
   ''''''
}
複製代碼

Java方法的參數和方法中的局部變量,能夠做爲root.

public Aclass{

public void doSomething(Object A){
    ObjectB b = new ObjectB; 
    }
 }
複製代碼

新生代轉移到老年代的觸發條件

  1. 長期存活的對象
  2. 大對象直接進入老年代
  3. minor gc後,survivor仍然放不下
  4. 動態年齡判斷 ,大於等於某個年齡的對象超過了survivor空間一半 ,大於等於某個年齡的對象直接進入老年代

G1和CMS的區別

  1. G1同時回收老年代和年輕代,而CMS只能回收老年代,須要配合一個年輕代收集器。另外G1的分代更可能是邏輯上的概念,G1將內存分紅多個等大小的region,Eden/ Survivor/Old分別是一部分region的邏輯集合,物理上內存地址並不連續。
    這裏寫圖片描述
  2. CMS在old gc的時候會回收整個Old區,對G1來講沒有old gc的概念,而是區分Fully young gc和Mixed gc,前者對應年輕代的垃圾回收,後者混合了年輕代和部分老年代的收集,所以每次收集確定會回收年輕代,老年代根據內存狀況能夠不回收或者回收部分或者所有(這種狀況應該是可能出現)。

雙親委派模型中有哪些方法。用戶如何自定義類加載器 。怎麼打破雙親委託機制

  1. 雙親委派模型中用到的方法:
  • findLoadedClass(),
  • loadClass()
  • findBootstrapClassOrNull()
  • findClass()
  • defineClass():把二進制數據轉換成字節碼。
  • resolveClass()

自定義類加載器的方法:繼承 ClassLoader 類,重寫 findClass()方法 。

  1. 繼承ClassLoader覆蓋loadClass方法 原順序
  2. findLoadedClass
  3. 委託parent加載器加載(這裏注意bootstrap加載器的parent爲null)
  4. 自行加載 打破委派機制要作的就是打亂2和3的順序,經過類名篩選本身要加載的類,其餘的委託給parent加載器。

即時編譯器的優化方法

字節碼能夠經過如下兩種方式轉換成合適的語言:

  1. 解釋器
  2. 即時編譯器 即時編譯器把整段字節碼編譯成本地代碼,執行本地代碼比一條一條進行解釋執行的速度快不少,由於本地代碼是保存在緩存裏的

編譯過程的五個階段

  1. 第一階段:詞法分析
  2. 第二階段:語法分析
  3. 第三階段:詞義分析與中間代碼產生
  4. 第四階段:優化
  5. 第五階段:目標代碼生成

java應用系統運行速度慢的解決方法

問題解決思路:

  1. 查看部署應用系統的系統資源使用狀況,CPU,內存,IO這幾個方面去看。找到對就的進程。
  2. 使用jstack,jmap等命令查看是JVM是在在什麼類型的內存空間中作GC(內存回收),和查看GC日誌查看是那段代碼在佔用內存。 首先,調節內存的參數設置,若是仍是同樣的問題,就要定位到相應的代碼。
  3. 定位代碼,修改代碼(通常是代碼的邏輯問題,或者代碼獲取的數據量過大。)

內存溢出是什麼,什麼緣由致使的

內存溢出是指應用系統中存在沒法回收的內存或使用的內存過多,最終使得程序運行要用到的內存大於虛擬機能提供的最大內存。爲了解決Java中內存溢出問題,咱們首先必須瞭解Java是如何管理內存的。Java的內存管理就是對象的分配和釋放問題。在Java中,內存的分配是由程序完成的,而內存的釋放是由垃圾收集器(Garbage Collection,GC)完成的,程序員不須要經過調用GC函數來釋放內存,由於不一樣的JVM實現者可能使用不一樣的算法管理GC,有的是內存使用到達必定程度時,GC纔開始工做,也有定時執行的,有的是中斷式執行GC。但GC只能回收無用而且再也不被其它對象引用的那些對象所佔用的空間。Java的內存垃圾回收機制是從程序的主要運行對象開始檢查引用鏈,當遍歷一遍後發現沒有被引用的孤立對象就做爲垃圾回收。

引發內存溢出的緣由有不少種,常見的有如下幾種:

  1. 內存中加載的數據量過於龐大,如一次從數據庫取出過多數據;

  2. 集合類中有對對象的引用,使用完後未清空,使得JVM不能回收;

  3. 代碼中存在死循環或循環產生過多重複的對象實體;

  4. 使用的第三方軟件中的BUG;

  5. 啓動參數內存值設定的太小;

內存溢出的解決

內存溢出雖然很棘手,但也有相應的解決辦法,能夠按照從易到難,一步步的解決。

第一步,就是修改JVM啓動參數,直接增長內存。這一點看上去彷佛很簡單,但很容易被忽略。JVM默承認以使用的內存爲64M,Tomcat默承認以使用的內存爲128MB,對於稍複雜一點的系統就會不夠用。在某項目中,就由於啓動參數使用的默認值,常常報「OutOfMemory」錯誤。所以,-Xms,-Xmx參數必定不要忘記加。

第二步,檢查錯誤日誌,查看「OutOfMemory」錯誤前是否有其它異常或錯誤。在一個項目中,使用兩個數據庫鏈接,其中專用於發送短信的數據庫鏈接使用DBCP鏈接池管理,用戶爲不將短信發出,有意將數據庫鏈接用戶名改錯,使得日誌中有許多數據庫鏈接異常的日誌,一段時間後,就出現「OutOfMemory」錯誤。經分析,這是因爲DBCP鏈接池BUG引發的,數據庫鏈接不上後,沒有將鏈接釋放,最終使得DBCP報「OutOfMemory」錯誤。通過修改正確數據庫鏈接參數後,就沒有再出現內存溢出的錯誤。

查看日誌對於分析內存溢出是很是重要的,經過仔細查看日誌,分析內存溢出前作過哪些操做,能夠大體定位有問題的模塊。

第三步,找出可能發生內存溢出的位置。重點排查如下幾點:

  1. 檢查代碼中是否有死循環或遞歸調用。

  2. 檢查是否有大循環重複產生新對象實體。

  3. 檢查對數據庫查詢中,是否有一次得到所有數據的查詢。通常來講,若是一次取十萬條記錄到內存,就可能引發內存溢出。這個問題比較隱蔽,在上線前,數據庫中數據較少,不容易出問題,上線後,數據庫中數據多了,一次查詢就有可能引發內存溢出。所以對於數據庫查詢儘可能採用分頁的方式查詢。

  4. 檢查List、MAP等集合對象是否有使用完後,未清除的問題。List、MAP等集合對象會始終存有對對象的引用,使得這些對象不能被GC回收。

第四步,使用內存查看工具動態查看內存使用狀況。某個項目上線後,每次系統啓動兩天後,就會出現內存溢出的錯誤。這種狀況通常是代碼中出現了緩慢的內存泄漏,用上面三個步驟解決不了,這就須要使用內存查看工具了。

內存查看工具備許多,比較有名的有:Optimizeit Profiler、JProbe Profiler、JinSight和Java1.5的Jconsole等。它們的基本工做原理大同小異,都是監測Java程序運行時全部對象的申請、釋放等動做,將內存管理的全部信息進行統計、分析、可視化。開發人員能夠根據這些信息判斷程序是否有內存泄漏問題。通常來講,一個正常的系統在其啓動完成後其內存的佔用量是基本穩定的,而不該該是無限制的增加的。持續地觀察系統運行時使用的內存的大小,能夠看到在內存使用監控窗口中是基本規則的鋸齒形的圖線,若是內存的大小持續地增加,則說明系統存在內存泄漏問題。經過間隔一段時間取一次內存快照,而後對內存快照中對象的使用與引用等信息進行比對與分析,能夠找出是哪一個類的對象在泄漏。

經過以上四個步驟的分析與處理,基本能處理內存溢出的問題。固然,在這些過程當中也須要至關的經驗與敏感度,須要在實際的開發與調試過程當中不斷積累。

整體上來講,產生內存溢出是因爲代碼寫的很差形成的,所以提升代碼的質量是最根本的解決辦法。有的人認爲先把功能實現,有BUG時再在測試階段進行修正,這種想法是錯誤的。正如一件產品的質量是在生產製造的過程當中決定的,而不是質量檢測時決定的,軟件的質量在設計與編碼階段就已經決定了,測試只是對軟件質量的一個驗證,由於測試不可能找出軟件中全部的BUG。

java併發

JAVA 線程狀態轉換圖示

這裏寫圖片描述

synchronized 的底層怎麼實現

  1. 同步代碼塊(Synchronization)基於進入和退出管程(Monitor)對象實現。每一個對象有一個監視器鎖(monitor)。當monitor被佔用時就會處於鎖定狀態,線程執行monitorenter指令時嘗試獲取monitor的全部權,過程以下:
  • 若是monitor的進入數爲0,則該線程進入monitor,而後將進入數設置爲1,該線程即爲monitor的全部者。

  • 若是線程已經佔有該monitor,只是從新進入,則進入monitor的進入數加1.

  • 若是其餘線程已經佔用了monitor,則該線程進入阻塞狀態,直到monitor的進入數爲0,再從新嘗試獲取monitor的全部權。

  1. 被 synchronized 修飾的同步方法並無經過指令monitorenter和monitorexit來完成(理論上其實也能夠經過這兩條指令來實現),不過相對於普通方法,其常量池中多了ACC_SYNCHRONIZED標示符。JVM就是根據該標示符來實現方法的同步的:當方法調用時,調用指令將會檢查方法的 ACC_SYNCHRONIZED 訪問標誌是否被設置,若是設置了,執行線程將先獲取monitor,獲取成功以後才能執行方法體,方法執行完後再釋放monitor。在方法執行期間,其餘任何線程都沒法再得到同一個monitor對象。 其實本質上沒有區別,只是方法的同步是一種隱式的方式來實現,無需經過字節碼來完成

講一下CAS

CAS,compare and swap的縮寫,中文翻譯成比較並交換。樂觀鎖用到的機制就是CAS,每次不加鎖而是假設沒有衝突而去完成某項操做,若是由於衝突失敗就重試。

原理:

  1. CAS有3個操做數,內存值V,舊的預期值A,要修改的新值B。當且僅當預期值A和內存值V相同時,將內存值V修改成B,不然什麼都不作。

JDK文檔說cas同時具備volatile讀和volatile寫的內存語義。

缺點:

  1. ABA問題。 由於CAS須要在操做值的時候檢查下值有沒有發生變化,若是沒有發生變化則更新,可是若是一個值原來是A,變成了B,又變成了A,那麼使用CAS進行檢查時會發現它的值沒有發生變化

  2. 循環時間長開銷大。 自旋CAS若是長時間不成功,會給CPU帶來很是大的執行開銷。

  3. 只能保證一個共享變量的原子操做。 對多個共享變量操做時,循環CAS就沒法保證操做的原子性,這個時候就能夠用鎖,或者有一個取巧的辦法,就是把多個共享變量合併成一個共享變量來操做。好比有兩個共享變量i=2,j=a,合併一下ij=2a,而後用CAS來操做ij。從Java1.5開始JDK提供了AtomicReference類來保證引用對象之間的原子性,你能夠把多個變量放在一個對象裏來進行CAS操做。

線程池

Executor線程池框架是一個根據一組執行策略調用,調度,執行和控制的異步任務的框架。

ThreadPoolExecutor執行的策略

  1. 線程數量未達到corePoolSize,則新建一個線程(核心線程)執行任務
  2. 線程數量達到了corePools,則將任務移入隊列等待
  3. 隊列已滿,新建線程(非核心線程)執行任務
  4. 隊列已滿,總線程數又達到了maximumPoolSize,就會由(RejectedExecutionHandler)拋出異常

新建線程 -> 達到核心數 -> 加入隊列 -> 新建線程(非核心) -> 達到最大數 -> 觸發拒絕策略

常見四種線程池

  1. CachedThreadPool():可緩存線程池。
  • 線程數無限制
  • 有空閒線程則複用空閒線程,若無空閒線程則新建線程
  • 必定程序減小頻繁建立/銷燬線程,減小系統開銷
  1. FixedThreadPool():定長線程池。
  • 可控制線程最大併發數(同時執行的線程數)
  • 超出的線程會在隊列中等待
  1. ScheduledThreadPool():定時線程池。
  • 支持定時及週期性任務執行。
  1. SingleThreadExecutor():單線程化的線程池。
  • 有且僅有一個工做線程執行任務
  • 全部任務按照指定順序執行,即遵循隊列的入隊出隊規則

四種拒絕策略

  1. AbortPolicy:拒絕任務,且還拋出RejectedExecutionException異常,線程池默認策略
  2. CallerRunPolicy:拒絕新任務進入,若是該線程池尚未被關閉,那麼這個新的任務在執行線程中被調用
  3. DiscardOldestPolicy: 若是執行程序還沒有關閉,則位於頭部的任務將會被移除,而後重試執行任務(再次失敗,則重複該過程),這樣將會致使新的任務將會被執行,而先前的任務將會被移除。
  4. DiscardPolicy:沒有添加進去的任務將會被拋棄,也不拋出異常。基本上爲靜默模式。

爲何要用線程池

  1. 減小了建立和銷燬線程的次數,每一個工做線程均可以被重複利用,可執行多個任務。
  2. 運用線程池能有效的控制線程最大併發數,能夠根據系統的承受能力,調整線程池中工做線線程的數目,防止由於消耗過多的內存,而把服務器累趴下(每一個線程須要大約1MB內存,線程開的越多,消耗的內存也就越大,最後死機)。
  3. 對線程進行一些簡單的管理,好比:延時執行、定時循環執行的策略等,運用線程池都能進行很好的實現

對象鎖和靜態鎖之間的區別

  1. 對象鎖用於對象實例方法,
  2. 類鎖用於類的靜態方法或一個類的class對象。
  3. 類的對象實例能夠有不少,不一樣對象實例的對象鎖互不干擾,而每一個類只有一個類鎖

簡述volatile字

兩個特性

  1. 保證了不一樣線程對這個變量進行 讀取 時的可見性,即一個線程修改 了某個變量的值 , 這新值對其餘線程來講是當即可見的 。(volatile 解決了 線程間 共享變量
  2. 禁止進行指令重排序 ,阻止編譯器對代碼的優化

要想併發程序正確地執行,必需要保證原子性、可見性以及有序性,鎖保證了原子性,而volatile保證可見性和有序性

happens-before 原則(先行發生原則):

  1. 程序次序規則:一個線程內,按照代碼順序,書寫在前面的操做先行發生於書寫在 後面的操做
  2. 鎖定規則:一個 unLock 操做先行發生於後面對同一個鎖的 lock 操做
  3. volatile 變量規則:對一個變量的寫操做先行發生於後面對這個變量的讀操做
  4. 傳遞規則:若是操做 A 先行發生於操做 B,而操做 B 又先行發生於操做 C,則能夠 得出操做 A 先行發生於操做 C
  5. 線程啓動規則:Thread 對象的 start()方法先行發生於此線程的每一個一個動做
  6. 線程中斷規則:對線程 interrupt()方法的調用先行發生於被中斷線程的代碼檢測 到中斷事件的發生
  7. 線程終結規則:線程中全部的操做都先行發生於線程的終止檢測,咱們能夠經過 T hread.join()方法結束、Thread.isAlive()的返回值手段檢測到線程已經終止執行
  8. 對象終結規則:一個對象的初始化完成先行發生於他的 finalize()方法的開始

Lock 和synchronized 的區別

  1. Lock 是一個 接口,而 synchronized 是 Java 中的 關鍵字, synchronized 是 內置的語言實現;

  2. synchronized 在 發生異常時,會 自動釋放線程佔有的鎖,所以 不會導 致死鎖現象發生;而 Lock 在發生異常時,若是沒有主動經過 unLock()去釋放 鎖,則很 可能形成死鎖現象,所以用 使用 Lock 時須要在 finally 塊中釋放鎖;

  3. Lock 可讓 等待鎖的線程響應中斷 (可中斷鎖),而 synchronized 卻不行,使用 synchronized 時,等待的線程會一直等待下去, 不可以響應中 斷 (不可中斷鎖);

  4. 經過 Lock 能夠知道 有沒有成功獲取鎖 (tryLock ( ) 方法 : 若是獲取 了鎖 ,回 則返回 true ;回 不然返回 false e, , 也就說這個方法不管如何都會當即返回 。 在拿不到鎖時不會一直在那等待。 ),而 synchronized 卻沒法辦到。

  5. Lock 能夠提升 多個線程進行讀操做的效率( 讀寫鎖)。

  6. Lock 能夠實現 公平鎖,synchronized 不保證公平性。 在性能上來講,若是線程競爭資源不激烈時,二者的性能是差很少的,而 當競爭資源很是激烈時(即有大量線程同時競爭),此時 Lock 的性能要遠遠優 於 synchronized。因此說,在具體使用時要根據適當狀況選擇。

ThreadLocal(線程變量副本)

Synchronized實現內存共享,ThreadLocal爲每一個線程維護一個本地變量。 採用空間換時間,它用於線程間的數據隔離,爲每個使用該變量的線程提供一個副本,每一個線程均可以獨立地改變本身的副本,而不會和其餘線程的副本衝突。 ThreadLocal類中維護一個Map,用於存儲每個線程的變量副本,Map中元素的鍵爲線程對象,而值爲對應線程的變量副本。 ThreadLocal在Spring中發揮着巨大的做用,在管理Request做用域中的Bean、事務管理、任務調度、AOP等模塊都出現了它的身影。 Spring中絕大部分Bean均可以聲明成Singleton做用域,採用ThreadLocal進行封裝,所以有狀態的Bean就可以以singleton的方式在多線程中正常工做了。

經過Callable和Future建立線程

Java 5在concurrency包中引入了java.util.concurrent.Callable 接口,它和Runnable接口很類似,但它能夠返回一個對象或者拋出一個異常。

Callable接口使用泛型去定義它的返回類型。Executors類提供了一些有用的方法去在線程池中執行Callable內的任務。因爲Callable任務是並行的,咱們必須等待它返回的結果。java.util.concurrent.Future對象爲咱們解決了這個問題。在線程池提交Callable任務返回了一個Future對象,使用它咱們能夠知道Callable任務的狀態和獲得Callable返回的執行結果。Future提供了get()方法讓咱們能夠等待Callable結束並獲取它的執行結果

  1. 建立Callable接口的實現類,並實現call()方法,該call()方法將做爲線程執行體,而且有返回值。
  2. 建立Callable實現類的實例,使用FutureTask類來包裝Callable對象,該FutureTask對象封裝了該Callable對象的call()方法的返回值。
  3. 使用FutureTask對象做爲Thread對象的target建立並啓動新線程
  4. 調用FutureTask對象的get()方法來得到子線程執行結束後的返回值

什麼叫守護線程,用什麼方法實現守護線程(Thread.setDeamon()的含義)

在Java中有兩類線程:User Thread(用戶線程)、Daemon Thread(守護線程) 用個比較通俗的好比,任何一個守護線程都是整個JVM中全部非守護線程的保姆: 只要當前JVM實例中尚存在任何一個非守護線程沒有結束,守護線程就;只有當最後一個非守護線程結束時,守護線程隨着JVM一同結束工做。 JVM內部的實現是若是運行的程序只剩下守護線程的話,程序將終止運行,直接結束。因此守護線程是做爲輔助線程存在的,主要的做用是提供計數等等輔助的功能。

如何中止一個線程?

終止線程的三種方法:

  1. 使用退出標誌,使線程正常退出,也就是當run方法完成後線程終止。 在定義退出標誌exit時,使用了一個Java關鍵字volatile,這個關鍵字的目的是使exit同步,也就是說在同一時刻只能由一個線程來修改exit的值,
thread.exit = true;  // 終止線程thread 
複製代碼
  1. 使用stop方法強行終止線程(這個方法不推薦使用,由於stop和suspend、resume同樣,也可能發生不可預料的結果)。 使用stop方法能夠強行終止正在運行或掛起的線程。咱們能夠使用以下的代碼來終止線程: thread.stop(); 雖然使用上面的代碼能夠終止線程,但使用stop方法是很危險的,就象忽然關閉計算機電源,而不是按正常程序關機同樣,可能會產生不可預料的結果,所以,並不推薦使用stop方法來終止線程。

  2. 使用interrupt方法中斷線程,使用interrupt方法來終端線程可分爲兩種狀況:

  • 線程處於阻塞狀態,如使用了sleep方法。
  • 使用while(!isInterrupted()){……}來判斷線程是否被中斷。 在第一種狀況下使用interrupt方法,sleep方法將拋出一個InterruptedException例外,而在第二種狀況下線程將直接退出。

注意:在Thread類中有兩個方法能夠判斷線程是否經過interrupt方法被終止。一個是靜態的方法interrupted(),一個是非靜態的方法isInterrupted(),這兩個方法的區別是interrupted用來判斷當前線是否被中斷,而isInterrupted能夠用來判斷其餘線程是否被中斷。所以,while (!isInterrupted())也能夠換成while (!Thread.interrupted())。

什麼是線程安全?什麼是線程不安全?

  1. 線程安全就是多線程訪問時,採用了加鎖機制,當一個線程訪問該類的某個數據時,進行保護,其餘線程不能進行訪問直到該線程讀取完,其餘線程纔可以使用。不會出現數據不一致或者數據污染。
  2. 線程不安全就是不提供數據訪問保護,有可能出現多個線程前後更改數據形成所獲得的數據是髒數據 在多線程的狀況下,因爲同一進程的多個線程共享同一片存儲空間,在帶來方便的同時,也帶來了訪問衝突這個嚴重的問題。Java語言提供了專門機制以解決這種衝突,有效避免了同一個數據對象被多個線程同時訪問。

java容器

HashSet和TreeSet區別

HashSet

  1. 不能保證元素的排列順序,順序有可能發生變化
  2. 不是同步的
  3. 集合元素能夠是null,但只能放入一個null 當向HashSet結合中存入一個元素時,HashSet會調用該對象的hashCode()方法來獲得該對象的hashCode值,而後根據 hashCode值來決定該對象在HashSet中存儲位置。

TreeSet

  1. TreeSet是SortedSet接口的惟一實現類
  2. TreeSet能夠確保集合元素處於排序狀態。TreeSet支持兩種排序方式,天然排序 和定製排序,其中天然排序爲默認的排序方式。向TreeSet中加入的應該是同一個類的對象

講一下LinkedHashMap

LinkedHashMap的實現就是HashMap+LinkedList的實現方式,以HashMap維護數據結構,以LinkList的方式維護數據插入順序

LinkedHashMap保存了記錄的插入順序,在用Iterator遍歷LinkedHashMap時,先獲得的記錄確定是先插入的。 在遍歷的時候會比HashMap慢TreeMap可以把它保存的記錄根據鍵排序,默認是按升序排序,也能夠指定排序的比較器

利用LinkedHashMap實現LRU算法緩存(

  1. LinkedList首先它是一個Map,Map是基於K-V的,和緩存一致
  2. LinkedList提供了一個boolean值可讓用戶指定是否實現LRU)

Java8 中HashMap的優化(引入紅黑樹的數據結構和擴容的優化)

  1. if (binCount >= TREEIFY_THRESHOLD - 1) 當符合這個條件的時候,把鏈表變成treemap紅黑樹,這樣查找效率從o(n)變成了o(log n) ,在JDK1.8的實現中,優化了高位運算的算法,經過hashCode()的高16位異或低16位實現的:
  2. 咱們使用的是2次冪的擴展(指長度擴爲原來2倍),因此,元素的位置要麼是在原位置,要麼是在原位置再移動2次冪的位置

這裏的Hash算法本質上就是三步:取key的hashCode值、高位運算、取模運算。

元素在從新計算hash以後,由於n變爲2倍,那麼n-1的mask範圍在高位多1bit(紅色),所以新的index就會發生這樣的變化: hashMap 1.8 哈希算法例圖2

這裏寫圖片描述
所以,咱們在擴充HashMap的時候,不須要像JDK1.7的實現那樣從新計算hash,只須要看看原來的hash值新增的那個bit是1仍是0就行了,是0的話索引沒變,是1的話索引變成「原索引+oldCap」

Map遍歷的keySet()和entrySet()性能差別緣由

Set<Entry<String, String>> entrySet = map.entrySet();
Set<String> set = map.keySet();` 
複製代碼
  1. keySet()循環中經過key獲取對應的value的時候又會調用getEntry()進行循環。循環兩次
  2. entrySet()直接使用getEntry()方法獲取結果,循環一次
  3. 因此 keySet()的性能會比entrySet()差點。因此遍歷map的話仍是用entrySet()來遍歷
public V get(Object key) {
        if (key == null)
            return getForNullKey();
        Entry<K,V> entry = getEntry(key);

        return null == entry ? null : entry.getValue();
    }    
複製代碼
final Entry<K,V> getEntry(Object key) {
        if (size == 0) {
            return null;
        }

        int hash = (key == null) ? 0 : hash(key);
        for (Entry<K,V> e = table[indexFor(hash, table.length)];
             e != null;
             e = e.next) {
            Object k;
            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k))))
                return e;
        }
        return null;
}
複製代碼

java基礎

抽象類和接口的對比

參數 抽象類 接口
默認的方法實現 它能夠有默認的方法實現 接口徹底是抽象的。它根本不存在方法的實現
實現 子類使用extends關鍵字來繼承抽象類。若是子類不是抽象類的話,它須要提供抽象類中全部聲明的方法的實現。 子類使用關鍵字implements來實現接口。它須要提供接口中全部聲明的方法的實現
構造器 抽象類能夠有構造器 接口不能有構造器
與正常Java類的區別 除了你不能實例化抽象類以外,它和普通Java類沒有任何區別 接口是徹底不一樣的類型
訪問修飾符 抽象方法能夠有public、protected和default這些修飾符 接口方法默認修飾符是public。你不能夠使用其它修飾符。
main方法 抽象方法能夠有main方法而且咱們能夠運行它 接口沒有main方法,所以咱們不能運行它。
多繼承 抽象方法能夠繼承一個類和實現多個接口 接口只能夠繼承一個或多個其它接口
速度 它比接口速度要快 接口是稍微有點慢的,由於它須要時間去尋找在類中實現的方法。
添加新方法 若是你往抽象類中添加新的方法,你能夠給它提供默認的實現。所以你不須要改變你如今的代碼。 若是你往接口中添加方法,那麼你必須改變實現該接口的類。

建立一個類的幾種方法?

  1. 使用new關鍵字 → 調用了構造函數
  2. 使用Class類的newInstance方法 → 調用了構造函數
Employee emp2 = (Employee)Class.forName("org.programming.mitra.exercises.Employee").newInstance();
複製代碼
  1. 使用Constructor類的newInstance方法 → 調用了構造函數
Constructor<Employee> constructor = Employee.class.getConstructor();
Employee emp3 = constructor.newInstance();
複製代碼
  1. 使用clone方法 → 沒有調用構造函數
  2. 使用反序列化 }→ 沒有調用構造函數
ObjectInputStream in = new ObjectInputStream(new FileInputStream("data.obj"));
Employee emp5 = (Employee) in.readObject();
複製代碼

Redirect和forward

  1. 上圖所示的間接轉發請求的過程以下: 瀏覽器向Servlet1發出訪問請求; Servlet1調用sendRedirect()方法,將瀏覽器重定向到Servlet2; 瀏覽器向servlet2發出請求; 最終由Servlet2作出響應。

  2. 上圖所示的直接轉發請求的過程以下: 瀏覽器向Servlet1發出訪問請求; Servlet1調用forward()方法,在服務器端將請求轉發給Servlet2; 最終由Servlet2作出響應。

什麼是泛型,爲何要使用以及類型擦除。

  1. 泛型的本質就是「參數化類型」,也就是說所操做的數據類型被指定爲一個參數。 建立集合時就指定集合元素的數據類型,該集合只能保存其指定類型的元素, 避免使用強制類型轉換。
  2. Java 編譯器生成的字節碼是不包含泛型信息的,泛型類型信息將在 編譯處理 時 被擦除,這個過程即 類型擦除。類型擦除能夠簡單的理解爲將泛型 java 代碼轉 換爲普通 java 代碼,只不過編譯器更直接點,將泛型 java 代碼直接轉換成普通 java 字節碼。

類型擦除的主要過程以下:

  1. 將全部的泛型參數用其最左邊界(最頂級的父類型)類型替換。
  2. 移除全部的類型參數。

Object跟這些標記符表明的java類型有啥區別呢?

Object是全部類的根類,任何類的對象均可以設置給該Object引用變量,使用的時候可能須要類型強制轉換,可是用使用了泛型T、E等這些標識符後,在實際用以前類型就已經肯定了,不須要再進行類型強制轉換。

Error類和Exception類區別

  1. Error類和Exception類的父類都是throwable類,他們的區別是: Error類通常是指與虛擬機相關的問題,如系統崩潰,虛擬機錯誤,內存空間不足,方法調用棧溢等。對於這類錯誤的致使的應用程序中斷,僅靠程序自己沒法恢復和和預防,遇到這樣的錯誤,建議讓程序終止。 Exception類表示程序能夠處理的異常,能夠捕獲且可能恢復。遇到這類異常,應該儘量處理異常,使程序恢復運行, 而不該該隨意終止異常。
  2. Exception類又分爲運行時異常(Runtime Exception)和受檢查的異常(Checked Exception ),運行時異常;ArithmaticException,IllegalArgumentException,編譯能經過,可是一運行就終止了,程序不會處理運行時異常,出現這類異常,程序會終止。而受檢查的異常,要麼用try。。。catch捕獲,要麼用throws字句聲明拋出,交給它的父類處理,不然編譯不會經過。

throw和throws區別

throw:(針對對象的作法) 拋出一個異常,能夠是系統定義的,也能夠是本身定義的

public void yichang(){
    NumberFormatException e = new NumberFormatException();
    throw e;
}
複製代碼

throws:(針對一個方法拋出的異常) 拋出一個異常,能夠是系統定義的,也能夠是本身定義的。

public void yichang() throws NumberFormatException{
    int a = Integer.parseInt("10L");
}
複製代碼
  1. throws出如今方法函數頭;而throw出如今函數體。
  2. throws表示出現異常的一種可能性,並不必定會發生這些異常;throw則是拋出了異常,執行throw則必定拋出了某種異常。
  3. 二者都是消極處理異常的方式(這裏的消極並非說這種方式很差),只是拋出或者可能拋出異常,可是不會由函數去處理異常,真正的處理異常由函數的上層調用處理。

.class 文件是什麼類型文件

class文件是一種8位字節的二進制流文件

java中序列化之子類繼承父類序列化

父類實現了Serializable,子類不須要實現Serializable

相關注意事項 1. 序列化時,只對對象的狀態進行保存,而無論對象的方法; 2. 當一個父類實現序列化,子類自動實現序列化,不須要顯式實現Serializable接口; c)當一個對象的實例變量引用其餘對象,序列化該對象時也把引用對象進行序列化; 3. 並不是全部的對象均可以序列化,至於爲何不能夠,有不少緣由了,好比: 1.安全方面的緣由,好比一個對象擁有private,public等field,對於一個要傳輸的對象,好比寫到文件,或者進行rmi傳輸等等,在序列化進行傳輸的過程當中,這個對象的private等域是不受保護的。 2. 資源分配方面的緣由,好比socket,thread類,若是能夠序列化,進行傳輸或者保存,也沒法對他們進行從新的資源分配,並且,也是沒有必要這樣實現。

2,反過來父類未實現Serializable,子類實現了,序列化子類實例的時候,父類的屬性是直接被跳過不保存,仍是能保存但不能還原?(答案:值不保存)

解:父類實現接口後,全部派生類的屬性都會被序列化。子類實現接口的話,父類的屬性值丟失。

java中序列化之子類繼承父類序列化

標識符

標識符能夠包括這4種字符:字母、下劃線、$、數字;開頭不能是數字;不能是關鍵字

Integer i=new Integer(127);和Integer i=127;的區別

Integer i = 127的時候,使用Java常量池技術,是爲了方便快捷地建立某些對象,當你須要一個對象時候,就去這個池子裏面找,找不到就在池子裏面建立一個。可是必須注意 若是對象是用new 建立的。那麼無論是什麼對像,它是不會放到池子裏的,而是向堆申請新的空間存儲。Byte,Short,Integer,Long,Character這5種整型的包裝類也只是在對應值在-128到127之間的數時纔可以使用對象池。超過了就要申請空間建立對象了

int i1=128;
    Integer i2=128;
    Integer i3=new Integer(128);//自動拆箱
    
    System.out.println(i1==i2);//true
    System.out.println(i1==i3);//true
    
    Integer i5=127;
    Integer i6=127;
    System.out.println(i5==i6);//true
    
    
    Integer i5=127;
    Integer ii5=new Integer(127);
    System.out.println(i5==ii5);//false
    
    Integer i7=new Integer(127);
    Integer i8=new Integer(127);
    System.out.println(i7==i8);//false
複製代碼

手寫單例模式

最好的單例模式是靜態內部類,不要寫雙重檢驗

private static class LazySomethingHolder {
  public static Something something = new Something();
}

public static Something getInstance() {
  return LazySomethingHolder.something;
}
複製代碼

爲何線程通訊的方法wait(), notify()和notifyAll()被定義在Object類裏?

Java的每一個對象中都有一個鎖(monitor,也能夠成爲監視器) 而且wait(),notify()等方法用於等待對象的鎖或者通知其餘線程對象的監視器可用。在Java的線程中並無可供任何對象使用的鎖和同步器。這就是爲何這些方法是Object類的一部分,這樣Java的每個類都有用於線程間通訊的基本方法

Java中wait 和sleep 方法比較

  1. 這兩個方法來自不一樣的類分別是Thread和Object

  2. 最主要是sleep方法沒有釋放鎖,而wait方法釋放了鎖,使得其餘線程能夠使用同步控制塊或者方法。

  3. wait,notify和notifyAll只能在同步控制方法或者同步控制塊裏面使用,而sleep能夠在任何地方使用(使用範圍)

  4. sleep必須捕獲異常,而wait,notify和notifyAll不須要捕獲異常

  5. sleep方法屬於Thread類中方法,表示讓一個線程進入睡眠狀態,等待必定的時間以後,自動醒來進入到可運行狀態,不會立刻進入運行狀態,由於線程調度機制恢復線程的運行也須要時間,一個線程對象調用了sleep方法以後,並不會釋放他所持有的全部對象鎖,因此也就不會影響其餘進程對象的運行。但在sleep的過程當中過程當中有可能被其餘對象調用它的interrupt(),產生InterruptedException異常,若是你的程序不捕獲這個異常,線程就會異常終止,進入TERMINATED狀態,若是你的程序捕獲了這個異常,那麼程序就會繼續執行catch語句塊(可能還有finally語句塊)以及之後的代碼。

  • 注意sleep()方法是一個靜態方法,也就是說他只對當前對象有效,經過t.sleep()讓t對象進入sleep,這樣的作法是錯誤的,它只會是使當前線程被sleep 而不是t線程
  1. wait屬於Object的成員方法,一旦一個對象調用了wait方法,必需要採用notify()和notifyAll()方法喚醒該進程;若是線程擁有某個或某些對象的同步鎖,那麼在調用了wait()後,這個線程就會釋放它持有的全部同步資源,而不限於這個被調用了wait()方法的對象。wait()方法也一樣會在wait的過程當中有可能被其餘對象調用interrupt()方法而產生

hashCode和equals方法的關係

在有些狀況下,程序設計者在設計一個類的時候爲須要重寫equals方法,好比String類,可是千萬要注意,在重寫equals方法的同時,必須重寫hashCode方法。 也就是說對於兩個對象,若是調用equals方法獲得的結果爲true,則兩個對象的hashcode值一定相等; 若是equals方法獲得的結果爲false,則兩個對象的hashcode值不必定不一樣; 若是兩個對象的hashcode值不等,則equals方法獲得的結果一定爲false; 若是兩個對象的hashcode值相等,則equals方法獲得的結果未知。

Object類中有哪些方法,列舉3個以上(能夠引導)

Object方法:equals()、toString()、finalize()、hashCode()、getClass()、clone()、wait()、notify()、notifyAll()

String s=new String("xyz")究竟建立String Object分爲兩種狀況:

  1. 若是String常理池中,已經建立"xyz",則不會繼續建立,此時只建立了一個對象new String("xyz");
  2. 若是String常理池中,沒有建立"xyz",則會建立兩個對象,一個對象的值是"xyz",一個對象new String("xyz")。

什麼是值傳遞和引用傳遞

值傳遞

public class TempTest {

  private void test1(int a) {
    a = 5;
    System.out.println("test1方法中的a=" + a);
  }

  public static void main(String[] args) {
    TempTest t = new TempTest();
    int a = 3;
    t.test1(11);
    System.out.println("main方法中a=" + a);
  }

}
複製代碼

test1方法中的a=5 main方法中a=3 值傳遞:傳遞的是值的拷貝,傳遞後就互不相關了 引用傳遞:傳遞的是變量所對應的內存空間的地址

public class TempTest {
  private void test1(A a) {
    a.age = 20;
    System.out.println("test1方法中a=" + a.age);
  }

  public static void main(String[] args) {
    TempTest t = new TempTest();
    A a = new A();
    a.age = 10;
    t.test1(a);
    System.out.println("main方法中a=" + a.age);
  }
}

class A {
  public int age = 0;
}
複製代碼

test1方法中a=20 main方法中a=20 傳遞前和傳遞後都指向同一個引用(同一個內存空間) 若是不互相影響,方法是在test1方法裏面新new一個實例就能夠了

講一下netty

netty經過Reactor模型基於多路複用器接收並處理用戶請求,內部實現了兩個線程池,boss線程和work線程池,其中boss線程池的線程負責處理請求的accept事件,當接收到accept事件的請求,把對應的socket封裝到一個NioSocketChannel中,並交給work線程池,其中work線程池負責請求的read和write事件

Nio的原理(同步非阻塞)

服務端和客戶端各自維護一個管理通道的對象,咱們稱之爲 selector,該對 象能檢測一個或多個通道(channel)上的事件。咱們以服務端爲例,若是服務 端的 selector 上註冊了讀事件,某時刻客戶端給服務端送了一些數據,阻塞 I/O 這時會調用 read()方法阻塞地讀取數據,而 NIO 的服務端會在 selector 中添加 一個讀事件。服務端的處理線程會輪詢地訪問 selector,若是訪問 selector 時發 現有感興趣的事件到達,則處理這些事件,若是沒有感興趣的事件到達,則處 理線程會一直阻塞直到感興趣的事件到達爲止。

這裏寫圖片描述

緩衝區Buffer、通道Channel、選擇器Selector

緩衝區Buffer

  • 緩衝區其實是一個容器對象,更直接的說,其實就是一個數組,在NIO庫中,全部數據都是用緩衝區處理的。在讀取數據時,它是直接讀到緩衝區中的; 在寫入數據時,它也是寫入到緩衝區中的;任什麼時候候訪問 NIO 中的數據,都是將它放到緩衝區中。而在面向流I/O系統中,全部數據都是直接寫入或者直接將數據讀取到Stream對象中。

通道Channel

  • 通道是一個對象,經過它能夠讀取和寫入數據,固然了全部數據都經過Buffer對象來處理。咱們永遠不會將字節直接寫入通道中,相反是將數據寫入包含一個或者多個字節的緩衝區。一樣不會直接從通道中讀取字節,而是將數據從通道讀入緩衝區,再從緩衝區獲取這個字節。通道與流的不一樣之處在於 通道是雙向 的。而流只是在一個方向上移動(一個流必須是 InputStream 或者 OutputStream 的子類,好比 InputStream 只能進行讀取操做,OutputStream 只能進行寫操做),而通道是雙向的,能夠用於讀、寫或者同時用於讀寫。

選擇器(Selector )

  • NIO 有一個主要的類 Selector,這個相似一個觀察者,只要咱們把須要探知 的 socketchannel 告訴 Selector,咱們接着作別的事情, 當有事件發生時,他會 通知咱們,傳回一組 SelectionKey, 咱們讀取這些 Key, 就會得到咱們剛剛註冊 過的 socketchannel, 而後,咱們從這個 Channel 中讀取數據,放心,包準能 夠讀到,接着咱們能夠處理這些數據。
  • Selector 內部原理實際是在作一個 對所註冊的 channel 的輪詢訪問,不斷 地輪詢,一旦輪詢到一個 channel 有所註冊的事情發生,好比數據來了,他就 會站起來報告, 交出一把鑰匙,讓咱們 經過這把鑰匙來讀取這個 channel 的內 容。

BIO和NIO的區別

  1. BIO:同步阻塞式IO,服務器實現模式爲一個鏈接一個線程,即客戶端有鏈接請求時服務器端就須要啓動一個線程進行處理,若是這個鏈接不作任何事情會形成沒必要要的線程開銷,固然能夠經過線程池機制改善。
  2. NIO:同步非阻塞式IO,服務器實現模式爲一個請求一個線程,即客戶端發送的鏈接請求都會註冊到多路複用器上,多路複用器輪詢到鏈接有I/O請求時才啓動一個線程進行處理。

NIO的selector做用

Selector(選擇器)是Java NIO中可以檢測一到多個NIO通道,並可以知曉通道是否爲諸如讀寫事件作好準備的組件。這樣,一個單獨的線程能夠管理多個channel,從而管理多個網絡鏈接。

爲了實現Selector管理多個SocketChannel,必須將具體的SocketChannel對象註冊到Selector,並聲明須要監聽的事件(這樣Selector才知道須要記錄什麼數據),一共有4種事件:

  1. connect:客戶端鏈接服務端事件,對應值爲SelectionKey.OP_CONNECT(8)
  2. accept:服務端接收客戶端鏈接事件,對應值爲SelectionKey.OP_ACCEPT(16)
  3. read:讀事件,對應值爲SelectionKey.OP_READ(1)
  4. write:寫事件,對應值爲SelectionKey.OP_WRITE(4)

每次請求到達服務器,都是從connect開始,connect成功後,服務端開始準備accept,準備就緒,開始讀數據,並處理,最後寫回數據返回。

因此,當SocketChannel有對應的事件發生時,Selector均可以觀察到,並進行相應的處理。

計算機網絡

GET 和 POST 區別

(GET) 請注意,查詢字符串(名稱/值對)是在 GET 請求的 URL 中發送的: /test/demo_form.asp?name1=value1&name2=value2

  1. GET 請求可被緩存

  2. GET 請求保留在瀏覽器歷史記錄中

  3. GET 請求可被收藏爲書籤

  4. GET 請求不該在處理敏感數據時使用

  5. GET 請求有長度限制

  6. GET 請求只應當用於取回數據 POST 方法 (POST) 請注意,查詢字符串(名稱/值對)是在 POST 請求的 HTTP 消息主體中發送的: POST /test/demo_form.asp HTTP/1.1 Host: w3schools.com name1=value1&name2=value2

  7. POST 請求不會被緩存

  8. POST 請求不會保留在瀏覽器歷史記錄中

  9. POST 不能被收藏爲書籤

  10. POST 請求對數據長度沒有要求

dns使用的協議

既使用TCP又使用UDP

  • 首先了解一下TCP與UDP傳送字節的長度限制:
  1. UDP報文的最大長度爲512字節,而TCP則容許報文長度超過512字節。當DNS查詢超過512字節時,協議的TC標誌出現刪除標誌,這時則使用TCP發送。一般傳統的UDP報文通常不會大於512字節。
  • 區域傳送時使用TCP,主要有一下兩點考慮:
  1. 輔域名服務器會定時(通常時3小時)向主域名服務器進行查詢以便了解數據是否有變更。若有變更,則會執行一次區域傳送,進行數據同步。區域傳送將使用TCP而不是UDP,由於數據同步傳送的數據量比一個請求和應答的數據量要多得多。
  2. TCP是一種可靠的鏈接,保證了數據的準確性。
  • 域名解析時使用UDP協議:
  1. 客戶端向DNS服務器查詢域名,通常返回的內容都不超過512字節,用UDP傳輸便可。不用通過TCP三次握手,這樣DNS服務器負載更低,響應更快。雖然從理論上說,客戶端也能夠指定向DNS服務器查詢的時候使用TCP,但事實上,不少DNS服務器進行配置的時候,僅支持UDP查詢包。

冪等

一個冪等操做的特色是其任意屢次執行所產生的影響均與一次執行的影響相同。冪等函數,或冪等方法,是指能夠使用相同參數重複執行,並能得到相同結果的函數。這些函數不會影響系統狀態,也不用擔憂重複執行會對系統形成改變。例如,「getUsername()和setTrue()」函數就是一個冪等函數.

Cookies和session區別

  1. Cookies是一種可以讓網站服務器把少許數據儲存到客戶端的硬盤或內存,或是從客戶端的硬盤讀取數據的一種技術。Cookies是當你瀏覽某網站時,由Web服務器置於你硬盤上的一個很是小的文本文件,它能夠記錄你的用戶ID、密碼、瀏覽過的網頁、停留的時間等信息。 session: 當用戶請求來自應用程序的 Web 頁時,若是該用戶尚未會話,則 Web 服務器將自動建立一個 Session 對象。當會話過時或被放棄後,服務器將終止該會話。 cookie機制:採用的是在客戶端保持狀態的方案,而session機制採用的是在服務端保持狀態的方案。同時咱們看到因爲服務器端保持狀態的方案在客戶端也須要保存一個標識,因此session機制可能須要藉助cookie機制來達到保存標識的目的。

  2. Session是服務器用來跟蹤用戶的一種手段,每一個Session都有一個惟一標識:session ID。當服務器建立了Session時,給客戶端發送的響應報文包含了Set-cookie字段,其中有一個名爲sid的鍵值對,這個鍵值Session ID。客戶端收到後就把Cookie保存瀏覽器,而且以後發送的請求報表都包含SessionID。HTTP就是經過Session和Cookie這兩個發送一塊兒合做來實現跟蹤用戶狀態,Session用於服務端,Cookie用於客戶端

TCP粘包和拆包產生的緣由

  1. 應用程序寫入數據的字節大小大於套接字發送緩衝區的大小
  2. 進行MSS大小的TCP分段。MSS是最大報文段長度的縮寫。MSS是TCP報文段中的數據字段的最大長度。數據字段加上TCP首部纔等於整個的TCP報文段。因此MSS並非TCP報文段的最大長度,而是:MSS=TCP報文段長度-TCP首部長度
  3. 以太網的payload大於MTU進行IP分片。MTU指:一種通訊協議的某一層上面所能經過的最大數據包大小。若是IP層有一個數據包要傳,並且數據的長度比鏈路層的MTU大,那麼IP層就會進行分片,把數據包分紅託乾片,讓每一片都不超過MTU。注意,IP分片能夠發生在原始發送端主機上,也能夠發生在中間路由器上。

TCP粘包和拆包的解決策略

  1. 消息定長。例如100字節。
  2. 在包尾部增長回車或者空格符等特殊字符進行分割,典型的如FTP協議
  3. 將消息分爲消息頭和消息尾。
  4. 其它複雜的協議,如RTMP協議等。

三次握手

第一次握手:創建鏈接時,客戶端發送syn包(syn=j)到服務器,並進入SYN_SEND狀態,等待服務器確認;

第二次握手:服務器收到syn包,必須確認客戶的SYN(ack=j+1),同時本身也發送一個SYN包(syn=k),即SYN+ACK包,此時服務器進入SYN_RECV狀態;

第三次握手:客戶端收到服務器的SYN+ACK包,向服務器發送確認包ACK(ack=k+1),此包發送完畢,客戶端和服務器進入ESTABLISHED狀態,完成三次握手。

完成三次握手,客戶端與服務器開始傳送數據

四次揮手

  1. 客戶端先發送FIN,進入FIN_WAIT1狀態
  2. 服務端收到FIN,發送ACK,進入CLOSE_WAIT狀態,客戶端收到這個ACK,進入FIN_WAIT2狀態
  3. 服務端發送FIN,進入LAST_ACK狀態
  4. 客戶端收到FIN,發送ACK,進入TIME_WAIT狀態,服務端收到ACK,進入CLOSE狀態

TIME_WAIT的狀態就是主動斷開的一方(這裏是客戶端),發送完最後一次ACK以後進入的狀態。而且持續時間還挺長的。客戶端TIME_WAIT持續2倍MSL時長,在linux體系中大概是60s,轉換成CLOSE狀態

TIME_WAIT

TIME_WAIT 是主動關閉連接時造成的,等待2MSL時間,約4分鐘。主要是防止最後一個ACK丟失。 因爲TIME_WAIT 的時間會很是長,所以server端應儘可能減小主動關閉鏈接

CLOSE_WAIT

CLOSE_WAIT是被動關閉鏈接是造成的。根據TCP狀態機,服務器端收到客戶端發送的FIN,則按照TCP實現發送ACK,所以進入CLOSE_WAIT狀態。但若是服務器端不執行close(),就不能由CLOSE_WAIT遷移到LAST_ACK,則系統中會存在不少CLOSE_WAIT狀態的鏈接。此時,多是系統忙於處理讀、寫操做,而未將已收到FIN的鏈接,進行close。此時,recv/read已收到FIN的鏈接socket,會返回0。

爲何須要 TIME_WAIT 狀態?

假設最終的ACK丟失,server將重發FIN,client必須維護TCP狀態信息以即可以重發最終的ACK,不然會發送RST,結果server認爲發生錯誤。TCP實現必須可靠地終止鏈接的兩個方向(全雙工關閉),client必須進入 TIME_WAIT 狀態,由於client可能面 臨重發最終ACK的情形。

爲何 TIME_WAIT 狀態須要保持 2MSL 這麼長的時間?

若是 TIME_WAIT 狀態保持時間不足夠長(好比小於2MSL),第一個鏈接就正常終止了。第二個擁有相同相關五元組的鏈接出現,而第一個鏈接的重複報文到達,干擾了第二個鏈接。TCP實現必須防止某個鏈接的重複報文在鏈接終止後出現,因此讓TIME_WAIT狀態保持時間足夠長(2MSL),鏈接相應方向上的TCP報文要麼徹底響應完畢,要麼被 丟棄。創建第二個鏈接的時候,不會混淆。

TIME_WAIT 和CLOSE_WAIT狀態socket過多

若是服務器出了異常,百分之八九十都是下面兩種狀況:

1.服務器保持了大量TIME_WAIT狀態

2.服務器保持了大量CLOSE_WAIT狀態,簡單來講CLOSE_WAIT數目過大是因爲被動關閉鏈接處理不當致使的。

一次完整的HTTP請求過程

域名解析 --> 發起TCP的3次握手 --> 創建TCP鏈接後發起http請求 --> 服務器響應http請求,瀏覽器獲得html代碼 --> 瀏覽器解析html代碼,並請求html代碼中的資源(如js、css、圖片等) --> 瀏覽器對頁面進行渲染呈現給用戶

講一下長鏈接

  • 1、基於http協議的長鏈接
  1. 在HTTP1.0和HTTP1.1協議中都有對長鏈接的支持。其中HTTP1.0須要在request中增長」Connection: keep-alive「 header纔可以支持,而HTTP1.1默認支持.
  • http1.0請求與服務端的交互過程:
  1. 客戶端發出帶有包含一個header:」Connection: keep-alive「的請求
  2. 服務端接收到這個請求後,根據http1.0和」Connection: keep-alive「判斷出這是一個長鏈接,就會在response的header中也增長」Connection: keep-alive「,同是不會關閉已創建的tcp鏈接.
  3. 客戶端收到服務端的response後,發現其中包含」Connection: keep-alive「,就認爲是一個長鏈接,不關閉這個鏈接。並用該鏈接再發送request.轉到a)
  • 2、發心跳包。每隔幾秒就發一個數據包過去

TCP如何保證可靠傳輸?

  1. 三次握手。
  2. 將數據截斷爲合理的長度。應用數據被分割成 TCP 認爲最適合發送的數據塊(按字節編號,合理分片)
  3. 超時重發。當 TCP 發出一個段後,它啓動一個定時器,若是不 能及時收到一個確認就重發
  4. 對於收到的請求,給出確認響應
  5. 校驗出包有錯,丟棄報文段,不給出響應
  6. 對失序數據進行從新排序,而後才交給應用層
  7. 對於重複數據 , 可以丟棄重複數據
  8. 流量控制。TCP 鏈接的每一方都有固定大小的緩衝空間。TCP 的接收端 只容許另外一端發送接收端緩衝區所能接納的數據。這將防止較快主機導致較慢主機的緩衝 區溢出。
  9. 擁塞控制。當網絡擁塞時,減小數據的發送。

詳細介紹http

HTTP協議是Hyper Text Transfer Protocol(超文本傳輸協議)的縮寫,是用於從萬維網(WWW:World Wide Web )服務器傳輸超文本到本地瀏覽器的傳送協議。

特色

  1. 簡單快速:客戶向服務器請求服務時,只需傳送請求方法和路徑。請求方法經常使用的有GET、HEAD、POST。每種方法規定了客戶與服務器聯繫的類型不一樣。因爲HTTP協議簡單,使得HTTP服務器的程序規模小,於是通訊速度很快。

  2. 靈活:HTTP容許傳輸任意類型的數據對象。正在傳輸的類型由Content-Type加以標記。

  3. 無鏈接:無鏈接的含義是限制每次鏈接只處理一個請求。服務器處理完客戶的請求,並收到客戶的應答後,即斷開鏈接。採用這種方式能夠節省傳輸時間。

  4. 無狀態:HTTP協議是無狀態協議。無狀態是指協議對於事務處理沒有記憶能力。缺乏狀態意味着若是後續處理須要前面的信息,則它必須重傳,這樣可能致使每次鏈接傳送的數據量增大。另外一方面,在服務器不須要先前信息時它的應答就較快。

  5. 支持B/S及C/S模式。

請求消息Request

  1. 請求行,用來講明請求類型,要訪問的資源以及所使用的HTTP版本.
  2. 請求頭部,緊接着請求行(即第一行)以後的部分,用來講明服務器要使用的附加信息 從第二行起爲請求頭部,HOST將指出請求的目的地.User-Agent,服務器端和客戶端腳本都能訪問它,它是瀏覽器類型檢測邏輯的重要基礎.該信息由你的瀏覽器來定義,而且在每一個請求中自動發送等等
  3. 空行,請求頭部後面的空行是必須的
  4. 請求數據也叫主體,能夠添加任意的其餘數據。

響應消息Response

  1. 狀態行,由HTTP協議版本號, 狀態碼, 狀態消息 三部分組成。
  2. 消息報頭,用來講明客戶端要使用的一些附加信息
  3. 空行,消息報頭後面的空行是必須的
  4. 響應正文,服務器返回給客戶端的文本信息。

狀態碼

  • 200 OK //客戶端請求成功
  • 301 Moved Permanently //永久重定向,使用域名跳轉
  • 302 Found // 臨時重定向,未登錄的用戶訪問用戶中心重定向到登陸頁面
  • 400 Bad Request //客戶端請求有語法錯誤,不能被服務器所理解
  • 401 Unauthorized //請求未經受權,這個狀態代碼必須和WWW-Authenticate報頭域一塊兒使用
  • 403 Forbidden //服務器收到請求,可是拒絕提供服務
  • 404 Not Found //請求資源不存在,eg:輸入了錯誤的URL
  • 500 Internal Server Error //服務器發生不可預期的錯誤
  • 503 Server Unavailable //服務器當前不能處理客戶端的請求,一段時間後可能恢復正常

http的方法

  1. get:客戶端向服務端發起請求,得到資源。請求得到URL處所在的資源。
  2. post:向服務端提交新的請求字段。請求URL的資源後添加新的數據。
  3. head:請求獲取URL資源的響應報告,即得到URL資源的頭部
  4. patch:請求局部修改URL所在資源的數據項
  5. put:請求修改URL所在資源的數據元素。
  6. delete:請求刪除url資源的數據

URI和URL的區別

URI,是uniform resource identifier,統一資源標識符,用來惟一的標識一個資源。 Web上可用的每種資源如HTML文檔、圖像、視頻片斷、程序等都是一個來URI來定位的

URI通常由三部組成:

  1. 訪問資源的命名機制
  2. 存放資源的主機名
  3. 資源自身的名稱,由路徑表示,着重強調於資源。

URL是uniform resource locator,統一資源定位器,它是一種具體的URI,即URL能夠用來標識一個資源,並且還指明瞭如何locate這個資源。 URL是Internet上用來描述信息資源的字符串,主要用在各類WWW客戶程序和服務器程序上,特別是著名的Mosaic。 採用URL能夠用一種統一的格式來描述各類信息資源,包括文件、服務器的地址和目錄等。

URL通常由三部組成:

  1. 協議(或稱爲服務方式)
  2. 存有該資源的主機IP地址(有時也包括端口號)
  3. 主機資源的具體地址。如目錄和文件名等

HTTPS和HTTP的區別

  1. https協議須要到CA申請證書,通常免費證書不多,須要交費。
  2. http是超文本傳輸協議,信息是明文傳輸;https 則是具備安全性的ssl加密傳輸協 議。
  3. http和https使用的是徹底不一樣的鏈接方式,用的端口也不同,前者是80,後者是443。
  4. http的鏈接很簡單,是無狀態的;HTTPS協議是由SSL+HTTP協議構建的可進行加密傳輸、身份認證的網絡協議,比http協議安全。
  5. http默認使用80端口,https默認使用443端口

https是如何保證數據傳輸的安全

https實際就是在TCP層與http層之間加入了SSL/TLS來爲上層的安全保駕護航,主要用到對稱加密、非對稱加密、證書,等技術進行客戶端與服務器的數據加密傳輸,最終達到保證整個通訊的安全性。

  • SSL/TLS協議做用:
  1. 認證用戶和服務器,確保數據發送到正確的客戶機和服務器;
  2. 加密數據以防止數據中途被竊取;
  3. 維護數據的完整性,確保數據在傳輸過程當中不被改變。
    這裏寫圖片描述

數據結構與算法

動態規劃的思想

動態規劃過程是:每次決策依賴於當前狀態,又隨即引發狀態的轉移。一個決策序列就是在變化的狀態中產生出來的,因此,這種多階段最優化決策解決問題的過程就稱爲動態規劃。

快速排序的思想

在數組中找到一個基準數(pivot) 分區,將數組中比基準數大的放到它的右邊,比基準數小的放到它的左邊 繼續對左右區間重複第二步,直到各個區間只有一個數,這時候,數組也就有序了。

快速排序算法是不穩定的算法

27 23 27 3 以第一個27做爲pivot中心點,則27與後面那個3交換,造成 3 23 27 27,排序通過一次結束,但最後那個27在排序之初先於初始位置3那個27,因此不穩定。

堆排序的思想

利用大頂堆(小頂堆)堆頂記錄的是最大關鍵字(最小關鍵字)這一特性,使得每次從無序中選擇最大記錄(最小記錄)變得簡單。 其基本思想爲(大頂堆): 1)將初始待排序關鍵字序列(R1,R2....Rn)構建成大頂堆,此堆爲初始的無須區; 2)將堆頂元素R[1]與最後一個元素R[n]交換,此時獲得新的無序區(R1,R2,......Rn-1)和新的有序區(Rn),且知足R[1,2...n-1]<=R[n]; 3)因爲交換後新的堆頂R[1]可能違反堆的性質,所以須要對當前無序區(R1,R2,......Rn-1)調整爲新堆,而後再次將R[1]與無序區最後一個元素交換,獲得新的無序區(R1,R2....Rn-2)和新的有序區(Rn-1,Rn)。不斷重複此過程直到有序區的元素個數爲n-1,則整個排序過程完成。

字典樹

字典樹主要有以下三點性質:

  1. 根節點不包含字符,除根節點意外每一個節點只包含一個字符。
  2. 從根節點到某一個節點,路徑上通過的字符鏈接起來,爲該節點對應的字符串。
  3. 每一個節點的全部子節點包含的字符串不相同。

鏈表反轉

  1. 首先讓頭節點與第一個元素節點斷開,可是要注意在斷開以前須要用p指針指向第一個元素節點來保存第一個元素節點的位置,而後再斷開。在這裏有一個指針q指向一個指針域爲空的節點,這個節點用來作爲鏈表反轉後的最後一個節點。
  2. 讓第二個元素節點的指針從指向第三個元素節點變爲指向第一個元素節點,以此類推,直至指針p指向原鏈表最後一個元素。
  3. p指針指向NULL時,讓原頭節點的指針域指向原來最後一個元素節點。此時鏈表倒置已完成。
linkList reverse(linkList head){
  linkList p,q,pr;
  p = head->next;
  q = NULL;
  head->next = NULL;
  while(p){
    pr = p->next;
    p->next = q;
    q = p;
    p = pr;
  }
  head->next = q;
  return head;
}
複製代碼

操做系統/Linux

進程有哪些狀態

通常來講,進程有三個狀態,即就緒狀態,運行狀態,阻塞狀態。

  1. 運行態:進程佔用CPU,並在CPU上運行
  2. 就緒態:進程已經具有運行條件,可是CPU尚未分配過來
  3. 阻塞態:進程因等待某件事發生而暫時不能運行
    這裏寫圖片描述

固然理論上上述三種狀態之間轉換分爲六種狀況;

  1. 運行——>就緒:1,主要是進程佔用CPU的時間過長,而系統分配給該進程佔用CPU的時間是有限的;2,在採用搶先式優先級調度算法的系統中,當有更高優先級的進程要運行時,該進程就被迫讓出CPU,該進程便由執行狀態轉變爲就緒狀態。
  2. 就緒——>運行:運行的進程的時間片用完,調度就轉到就緒隊列中選擇合適的進程分配CPU
  3. 運行——>阻塞:正在執行的進程因發生某等待事件而沒法執行,則進程由執行狀態變爲阻塞狀態,如發生了I/O請求
  4. 阻塞——>就緒:進程所等待的事件已經發生,就進入就緒隊列

如下兩種狀態是不可能發生的:

  1. 阻塞——>運行:即便給阻塞進程分配CPU,也沒法執行,操做系統在進行調度時不會從阻塞隊列進行挑選,而是從就緒隊列中選取

  2. 就緒——>阻塞:就緒態根本就沒有執行,談不上進入阻塞態。

進程間通訊方式

  1. 管道pipe:管道是一種半雙工的通訊方式,數據只能單向流動,並且只能在具備親緣關係的進程間使用。進程的親緣關係一般是指父子進程關係。
  2. 命名管道FIFO:有名管道也是半雙工的通訊方式,可是它容許無親緣關係進程間的通訊。
  3. 消息隊列MessageQueue:消息隊列是由消息的鏈表,存放在內核中並由消息隊列標識符標識。消息隊列克服了信號傳遞信息少、管道只能承載無格式字節流以及緩衝區大小受限等缺點。
  4. 共享存儲SharedMemory:共享內存就是映射一段能被其餘進程所訪問的內存,這段共享內存由一個進程建立,但多個進程均可以訪問。共享內存是最快的 IPC 方式,它是針對其餘進程間通訊方式運行效率低而專門設計的。它每每與其餘通訊機制,如信號兩,配合使用,來實現進程間的同步和通訊。
  5. 信號量Semaphore:信號量是一個計數器,能夠用來控制多個進程對共享資源的訪問。它常做爲一種鎖機制,防止某進程正在訪問共享資源時,其餘進程也訪問該資源。所以,主要做爲進程間以及同一進程內不一樣線程之間的同步手段。
  6. 套接字Socket:套解口也是一種進程間通訊機制,與其餘通訊機制不一樣的是,它可用於不一樣及其間的進程通訊。
  7. 信號 ( sinal ) : 信號是一種比較複雜的通訊方式,用於通知接收進程某個事件已經發生。

Linux中軟連接和硬連接的區別

ln -s source dist # 創建軟鏈接 ln source dist # 創建硬鏈接 創建硬連接時,連接文件和被連接文件必須位於同一個文件系統中,而且不能創建指向目錄的硬連接

  1. 硬鏈接就像一個文件有多個文件名,
  2. 軟鏈接就是產生一個新文件(這個文件內容,實際上就是記當要連接原文件路徑的信息),這個文件指向另外一個文件的位置

I/O多路複用

單個線程,經過記錄跟蹤每一個I/O流(sock)的狀態,來同時管理多個I/O流 。儘可能多的提升服務器的吞吐能力

select, poll, epoll 都是I/O多路複用的具體的實現

設計模式

設計模式主要分三個類型:建立型、結構型和行爲型。

  1. 建立型有:
  • Singleton,單例模式:保證一個類只有一個實例,並提供一個訪問它的全局訪問點

  • Abstract Factory,抽象工廠:提供一個建立一系列相關或相互依賴對象的接口,而無須指定它們的具體類。

  • Factory Method,工廠方法:定義一個用於建立對象的接口,讓子類決定實例化哪個類,Factory Method使一個類的實例化延遲到了子類。

  • Builder,建造模式:將一個複雜對象的構建與他的表示相分離,使得一樣的構建過程能夠建立不一樣的表示。

  • Prototype,原型模式:用原型實例指定建立對象的種類,而且經過拷貝這些原型來建立新的對象。

  1. 行爲型有:
  • Iterator,迭代器模式:提供一個方法順序訪問一個聚合對象的各個元素,而又不須要暴露該對象的內部表示。
  • Observer,觀察者模式:定義對象間一對多的依賴關係,當一個對象的狀態發生改變時,全部依賴於它的對象都獲得通知自動更新。
  • Template Method,模板方法:定義一個操做中的算法的骨架,而將一些步驟延遲到子類中,TemplateMethod使得子類能夠不改變一個算法的結構便可以重定義該算法得某些特定步驟。
  • Command,命令模式:將一個請求封裝爲一個對象,從而使你能夠用不一樣的請求對客戶進行參數化,對請求排隊和記錄請求日誌,以及支持可撤銷的操做。
  • State,狀態模式:容許對象在其內部狀態改變時改變他的行爲。對象看起來彷佛改變了他的類。
  • Strategy,策略模式:定義一系列的算法,把他們一個個封裝起來,並使他們能夠互相替換,本模式使得算法能夠獨立於使用它們的客戶。
  • China of Responsibility,職責鏈模式:使多個對象都有機會處理請求,從而避免請求的送發者和接收者之間的耦合關係
  • Mediator,中介者模式:用一箇中介對象封裝一些列的對象交互。
  • Visitor,訪問者模式:表示一個做用於某對象結構中的各元素的操做,它使你能夠在不改變各元素類的前提下定義做用於這個元素的新操做。
  • Interpreter,解釋器模式:給定一個語言,定義他的文法的一個表示,並定義一個解釋器,這個解釋器使用該表示來解釋語言中的句子
  • Memento,備忘錄模式:在不破壞對象的前提下,捕獲一個對象的內部狀態,並在該對象以外保存這個狀態。
  1. 結構型有:
  • Composite,組合模式:將對象組合成樹形結構以表示部分總體的關係,Composite使得用戶對單個對象和組合對象的使用具備一致性。
  • Facade,外觀模式:爲子系統中的一組接口提供一致的界面,fa?ade提供了一高層接口,這個接口使得子系統更容易使用。
  • Proxy,代理模式:爲其餘對象提供一種代理以控制對這個對象的訪問
  • Adapter,適配器模式:將一類的接口轉換成客戶但願的另一個接口,Adapter模式使得本來因爲接口不兼容而不能一塊兒工做那些類能夠一塊兒工做。
  • Decrator,裝飾模式:動態地給一個對象增長一些額外的職責,就增長的功能來講,Decorator模式相比生成子類更加靈活。
  • Bridge,橋模式:將抽象部分與它的實現部分相分離,使他們能夠獨立的變化。
  • Flyweight,享元模式

動態代理和靜態代理有什麼區別

靜態代理

這種代理方式須要代理對象和目標對象實現同樣的接口。在程序運行前,代理類的.class文件就已經存在了。

優勢:

  1. 能夠在不修改目標對象的前提下擴展目標對象的功能。

缺點:

  1. 冗餘。因爲代理對象要實現與目標對象一致的接口,會產生過多的代理類。
  2. 不易維護。一旦接口增長方法,目標對象與代理對象都要進行修改。

動態代理

動態代理利用了JDK API,運用反射機制動態地在內存中構建代理對象,從而實現對目標對象的代理功能。動態代理又被稱爲JDK代理或接口代理。

動態代理是實現JDK裏的InvocationHandler接口的invoke方法,但注意的是代理的是接口,也就是你的業務類必需要實現接口,經過Proxy裏的newProxyInstance獲得代理對象

優勢:

  1. 動態代理對象不須要實現接口,可是要求目標對象必須實現接口,不然不能使用動態代理。

靜態代理與動態代理的區別主要

  1. 靜態代理在編譯時就已經實現,編譯完成後代理類是一個實際的class文件
  2. 動態代理是在運行時動態生成的,即編譯完成後沒有實際的class文件,而是在運行時動態生成類字節碼,並加載到JVM中
  3. 靜態代理一般只代理一個類,動態代理是代理一個接口下的多個實現類。
  4. 靜態代理事先知道要代理的是什麼,而動態代理不知道要代理什麼東西,只有在運行時才知道。

JDK中的動態代理和CGLIB

動態代理

  1. JDK中的動態代理: 經過反射類Proxy以及InvocationHandler回調接口實現的,

  2. 動態代理缺點: JDK中所要進行動態代理的類必需要實現一個接口,也就是說只能對該類所實現接口中定義的方法進行代理,這在實際編程中具備必定的侷限性,並且使用反射的效率也並非很高。

CGLIB

  1. CGLIB原理:動態生成一個要代理類的子類,子類重寫要代理的類的全部不是final的方法。在子類中採用方法攔截的技術攔截全部父類方法的調用,順勢織入橫切邏輯。它比使用java反射的JDK動態代理要快。

  2. CGLIB底層:使用字節碼處理框架ASM,來轉換字節碼並生成新的類。不鼓勵直接使用ASM,由於它要求你必須對JVM內部結構包括class文件的格式和指令集都很熟悉。

  3. CGLIB優勢:它爲沒有實現接口的類提供代理,爲JDK的動態代理提供了很好的補充。一般能夠使用Java的動態代理建立代理,但當要代理的類沒有實現接口或者爲了更好的性能,CGLIB是一個好的選擇。

  4. CGLIB缺點:對於final方法,沒法進行代理

場景題和設計題

場景題:設計判斷論文抄襲的系統

  1. 一類是基於字符串比較的方法;另外一類是基於詞頻統計的方法。
  2. 基於字符串比較的方法也稱爲數字指紋法,這類方法經過某種選取策略在文檔中取一些字符串做爲「指紋」,把指紋映射到Hash 表中,最後統計Hash
  3. 表中相同的指紋數目或者比率,做爲文本類似度依據。
  4. 基於詞頻統計的方法也稱爲基於語義的方法。詞頻統計法源於信息檢索技術中的向量空間模型,該類方法首先都要統計每篇文檔中各個單詞的出現次數,而後根據單詞頻度構成文檔特徵向量,最後採用點積、餘弦或者相似方式度量兩篇文檔的特徵向量,以此做爲文檔類似度的依據。

設計一個即時聊天的系統

  1. 用戶經過客戶端進入系統,向服務器發出消息,請求登錄。
  2. 服務器收到請求後,向客戶端返回應答消息,表示贊成接受該用戶加入,並順帶將本身服務線程所在的監聽端口號告訴用戶。
  3. 客戶端按照服務器應答中給出的端口號與服務器創建穩定的鏈接。
  4. 服務器經過該鏈接將當前在線用戶的列表信息傳給新加入的客戶端。
  5. 客戶端得到了在線用戶列表,就能夠獨立自主地與在線的其餘用戶通訊了。
  6. 當用戶退出系統時要及時地通知服務器。

分佈式系統事務一致性解決方案

分佈式系統事務一致性解決方案 MQ(事務消息)

舉個例子,Bob向Smith轉帳,那咱們究竟是先發送消息,仍是先執行扣款操做?

好像均可能會出問題。若是先發消息,扣款操做失敗,那麼Smith的帳戶裏面會多出一筆錢。反過來,若是先執行扣款操做,後發送消息,那有可能扣款成功了可是消息沒發出去,Smith收不到錢。除了上面介紹的經過異常捕獲和回滾的方式外,還有沒有其餘的思路呢?

下面以阿里巴巴的RocketMQ中間件爲例,分析下其設計和實現思路。

RocketMQ第一階段發送Prepared消息時,會拿到消息的地址,第二階段執行本地事物,第三階段經過第一階段拿到的地址去訪問消息,並修改狀態。細心的讀者可能又發現問題了,若是確認消息發送失敗了怎麼辦?RocketMQ會按期掃描消息集羣中的事物消息,這時候發現了Prepared消息,它會向消息發送者確認,Bob的錢究竟是減了仍是沒減呢?若是減了是回滾仍是繼續發送確認消息呢?RocketMQ會根據發送端設置的策略來決定是回滾仍是繼續發送確認消息。這樣就保證了消息發送與本地事務同時成功或同時失敗。以下圖:

這裏寫圖片描述

設計高併發的系統?

  1. HTML 頁面靜態化 訪問頻率較高但內容變更較小,使用網站 HTML 靜態化方案來優化訪問速度。將社區 內的帖子、文章進行實時的靜態化,有更新的時候再從新靜態化也是大量使用的策略。 優點: 1、減輕服務器負擔。 2、加快頁面打開速度, 靜態頁面無需 訪問 數據庫,打開速度較動態頁面有明顯提升; 3、不少搜索引擎都會優先收錄靜態頁面,不只被收錄的快,還收錄的全,容易被搜 索引擎找到; 4、HTML 靜態頁面不會受程序相關漏洞的影響,減小攻擊 ,提升安全性。
  2. 圖片服務器和應用服務器相分離 如今不少的網站上都會用到大量的圖片,而圖片是網頁傳輸中佔主要的數據量,也是影 響網站性能的主要因素。所以不少網站都會將圖片存儲從網站中分離出來,另外架構一個 或多個服務器來存儲圖片,將圖片放到一個虛擬目錄中,而網頁上的圖片都用一個 URL 地 址來指向這些服務器上的圖片的地址,這樣的話網站的性能就明顯提升了。 優點: 1、分擔 Web 服務器的 I/O 負載-將耗費資源的圖片服務分離出來,提升服務器的性能 和穩定性。 2、 可以專門對圖片服務器進行優化-爲圖片服務設置有針對性的 緩存方案,減小帶寬 成本,提升訪問速度。 3、 提升網站的可擴展性-經過增長圖片服務器,提升圖片吞吐能力。
  3. 數據庫 見「數據庫部分的---若是有一個特別大的訪問量到數據庫上,怎麼作優化?」。
  4. 緩存 儘可能使用緩存,包括用戶緩存,信息緩存等,多花點內存來作緩存,能夠大量減小與 數據庫的交互,提升性能。 假如咱們能減小數據庫頻繁的訪問,那對系統確定大大有利的。好比一個電子商務系 統的商品搜索,若是某個關鍵字的商品常常被搜,那就能夠考慮這部分商品列表存放到緩 存(內存中去),這樣不用每次訪問數據庫,性能大大增長。
  5. 鏡像 鏡像是冗餘的一種類型,一個磁盤上的數據在另外一個磁盤上存在一個徹底相同的副本 即爲鏡像。
  6. 負載均衡 在網站高併發訪問的場景下,使用負載均衡技術(負載均衡服務器)爲一個應用構建 一個由多臺服務器組成的服務器集羣,將併發訪問請求分發到多臺服務器上處理,避免單 一服務器因負載壓力過大而響應緩慢,使用戶請求具備更好的響應延遲特性。
  7. 併發控制 加鎖,如樂觀鎖和悲觀鎖。
  8. 消息隊列 經過 mq 一個一個排隊方式,跟 12306 同樣。

設計高負載的系統

  1. 應用無狀態
  2. 有效使用緩存
  3. 應用拆分
  4. 數據庫拆分
  5. 異步通訊
  6. 非結構化數據存儲 ( TFS,NOSQL)
  7. 監控、預警系統
  8. 配置統一管理

訂票系統,某車次只有一張火車票,假定有 1w 我的同時打開 12306 網站來訂票,如何解決併發問題?(可擴展到任何高併發網站要考慮的 併發讀寫問題

使用樂觀鎖,樂觀鎖意思是不鎖定表的狀況下,利用業務的控制來解決併發問題,這樣既保證數據的併發 可讀性 ,又保證保存數據的 排他性,保證性能的同時解決了併發帶來 的髒數據問題。hibernate 中實現樂觀鎖。(樂觀鎖,使用版本標識來肯定讀到的數據與提交時的數據是否一致。提交後修改版本標識,不一致時能夠採起丟棄和再次嘗試的策略。)

分佈式與集羣的區別是什麼

分佈式:一個業務分拆多個子業務,部署在不一樣的服務器上 集羣:同一個業務,部署在多個服務器上

實時展示熱門文章,好比近8小時點擊量最大的文章前100名

  1. 數據接收
  • 客戶端會爲了減輕服務器的壓力而選擇延遲合併點擊請求進行批量發送
  • 服務器確定會有多臺機器多進程部署來接受點擊請求,接收到的請求在進行參數解析後,被髮送到存儲單元。爲了減輕存儲的壓力,每一個進程可能會使用小窗口聚合數據,每隔一小段時間將窗口內的數據聚合起來一塊兒發給存儲單元。
  1. 數據存儲
  • 使用kafka存,ZeroCopy機制併發量很高,數據持久化在磁盤裏成本低。不過kafka的數據通常是有過時時間的,若是想徹底記住用戶的點擊以便作長期的數據分析,須要要使用hdfs了
  1. 分佈式TopN算法
  • 用戶太多,用戶表按用戶ID哈希分紅了1024張子表。用戶表裏有一個字段score,表示這個用戶的積分數。如今咱們要計算前100名積分最多的用戶以及積分數,該怎麼查詢?
  • 若是是單個表,一個SQL也就搞定了,
  • 若是是多個子表,你得在每一個子表上都進行一次TopN查詢,而後聚合結果再作一次TopN查詢。子表查詢能夠多線程並行,提升聚合效率。
  1. 滑動窗口
  • 8小時的滑動窗口,意味着新的數據源源不斷的進來,舊的數據時時刻刻在淘汰,在業務能夠差幾分鐘。
  • 咱們對時間片進行了切分,一分鐘一個槽來進行計數,過時了8小時,移掉第一個,計算topn的帖子,維護窗口,移除過時的槽,而後統計topn,30s~60s調用一次
  1. 定時任務
  • 每一個子節點都會有一個定時任務去負責維持統計窗口,過時失效的統計數據,計算局部的topn熱帖。
  • 如今每一個子節點都有了各自的局部topn熱帖,那麼還須要一個主節點去彙總這些局部熱點,而後計算去全局熱帖。
  1. 點擊去重
  • 首先要從客戶端下手,客戶端自己能夠過濾一部分無效點擊。同一篇文章在過短的時間內被當前用戶反覆點擊,這個模式仍是很好發現的。若是間隔時間比較長,那就是讀者的回味點擊,屬於文章的正向反饋,應該記錄下來
  • 服務器還須要防止用戶的防刷行爲。若是缺失防刷控制,能夠經過這種漏洞來使得本身的文章非法得到大量點擊,進入熱門文章列表,打上熱門標籤,被海量的用戶看到,就會得到較大的經濟效益,即便這篇文章內容自己吸引力並不足夠。

如何解決電商網站超賣現象

超賣是什麼

  • 由於數據庫底層的寫操做和讀操做能夠同時進行,雖然寫操做默認帶有隱式鎖(即對同一數據不能同時進行寫操做)可是讀操做默認是不帶鎖的,因此當用戶1去修改庫存的時候,用戶2依然能夠讀到庫存爲1,致使兩個用戶同時減一次庫存,因此出現了超賣現象。

解決方案

  1. 使用redis預減庫存
  2. 當庫存大於0,才能更新庫存update sk_goods_seckill set stock_count = stock_count - 1 where goods_id = #{goodsId} and stock_count > 0
  3. 添加惟一索引,UNIQUE KEY u_uid_gid (user_id,goods_id) USING BTREE,防止同一用戶同一商品下兩次訂單
  4. 樂觀鎖,就是在數據庫設計一個版本號的字段,每次修改都使其+1,這樣在提交時比對提交前的版本號就知道是否是併發提交了,可是有個缺點就是隻能是應用中控制,若是有跨應用修改同一條數據樂觀鎖就沒辦法了,這個時候能夠考慮悲觀鎖。
  5. 悲觀鎖,就是直接在數據庫層面將數據鎖死,相似於oralce中使用select xxxxx from xxxx where xx=xx for update,這樣其餘線程將沒法提交數據。
  6. 使用消息隊列異步下單

mq異步調用失敗,如何保證數據一致性?

  1. 按你的使用場景,推送數據必須得在數據建立事務成功以後執行,這裏必須有個前後。你能夠將推送這個操做異步執行,消息隊列有一搬有ack機制,確保消息沒丟失。這時候監聽消息隊列的程序會執行推送,若是推送成功作標記。若是推送失敗也標記記錄時間,也能夠推到另外一個消息隊列約定多少分鐘重試。實在不行就完全標記失敗,或者回滾以前建立的數據。這個纔是最終一致性。
  2. 若是是並行的操做,就得使用消息隊列的confirm機制了。

分佈式鎖的幾種實現方式

  1. 基於數據庫表
  • 要實現分佈式鎖,最簡單的方式可能就是直接建立一張鎖表,而後經過操做該表中的數據來實現了。

  • 咱們對method_name(方法名)作了惟一性約束,這裏若是有多個請求同時提交到數據庫的話,數據庫會保證只有一個操做能夠成功,那麼咱們就能夠認爲操做成功的那個線程得到了該方法的鎖,能夠執行方法體內容。當方法執行完畢以後,想要釋放鎖的話就刪除該記錄

  1. 基於數據庫排他鎖
  • 能夠經過數據庫的排他鎖來實現分佈式鎖
  • 在查詢語句後面增長for update,數據庫會在查詢過程當中給數據庫表增長排他鎖(這裏再多提一句,InnoDB引擎在加鎖的時候,只有經過索引進行檢索的時候纔會使用行級鎖,不然會使用表級鎖。這裏咱們但願使用行級鎖,就要給method_name添加索引
  • 值得注意的是,這個索引必定要建立成惟一索引,不然會出現多個重載方法之間沒法同時被訪問的問題。重載方法的話建議把參數類型也加上。)。當某條記錄被加上排他鎖以後,其餘線程沒法再在該行記錄上增長排他鎖。
  1. 基於緩存
  • 好比Tair的put方法,redis的setnx方法等。而且,這些緩存服務也都提供了對數據的過時自動刪除的支持,能夠直接設置超時時間來控制鎖的釋放。
  1. 基於Zookeeper
  • 每一個客戶端對某個方法加鎖時,在zookeeper上的與該方法對應的指定節點的目錄下,生成一個惟一的瞬時有序節點。 判斷是否獲取鎖的方式很簡單,只須要判斷有序節點中序號最小的一個。 當釋放鎖的時候,只需將這個瞬時節點刪除便可。同時,其能夠避免服務宕機致使的鎖沒法釋放,而產生的死鎖問題。

消息隊列

四種MQ區別

這裏寫圖片描述

如何保證消息隊列是高可用的

  1. 集羣,以rcoketMQ爲例,有多master 模式、多master多slave異步複製模式、多 master多slave同步雙寫模式。

這裏寫圖片描述
Producer 與 NameServer集羣中的其中一個節點(隨機選擇)創建長鏈接,按期從 NameServer 獲取 Topic 路由信息,並向提供 Topic 服務的 Broker Master 創建長鏈接,且定時向 Broker 發送心跳。Producer 只能將消息發送到 Broker master,可是 Consumer 則不同,它同時和提供 Topic 服務的 Master 和 Slave創建長鏈接,既能夠從 Broker Master 訂閱消息,也能夠從 Broker Slave 訂閱消息。

如何保證消息不被重複消費

緣由:

  • 消費者在消費後,會發送一個確認信息給消息隊列,消息隊列收到後會將該消息從消息隊列中刪除,例如RabbitMQ是發送一個ACK確認消息,RocketMQ是返回一個CONSUME_SUCCESS成功標誌,kafka每個消息都有一個offset,kafka消費過消息後,須要提交offset,讓消息隊列知道本身已經消費過了。由於網絡傳輸等等故障,確認信息沒有傳送到消息隊列,致使消息隊列不知道本身已經消費過該消息了,再次將該消息分發給其餘的消費者。

解決方法:

  1. 消息能夠使用惟一id標識
  2. 若是拿到這個消息作數據庫的insert操做。給這個消息作一個惟一主鍵,那麼就算出現重複消費的狀況,就會致使主鍵衝突,避免數據庫出現髒數據。
  3. 若是拿到這個消息作redis的set的操做,不管set幾回結果都是同樣的,set操做是算冪等操做。
  4. 使用第三方介質作消費記錄。以redis爲例,給消息分配一個全局id,只要消費過該消息,將< id,message>以K-V形式寫入redis。那消費者開始消費前,先去redis中查詢有沒消費記錄便可。

流行框架

SpringMVC的工做原理

這裏寫圖片描述
SpringMVC流程

  1. 用戶發送請求至前端控制器DispatcherServlet。
  2. DispatcherServlet收到請求調用HandlerMapping處理器映射器。
  3. 處理器映射器找到具體的處理器(能夠根據xml配置、註解進行查找),生成處理器對象及處理器攔截器(若是有則生成)一併返回給DispatcherServlet。
  4. DispatcherServlet調用HandlerAdapter處理器適配器。
  5. HandlerAdapter通過適配調用具體的處理器(Controller,也叫後端控制器)。
  6. Controller執行完成返回ModelAndView。
  7. HandlerAdapter將controller執行結果ModelAndView返回給DispatcherServlet。
  8. DispatcherServlet將ModelAndView傳給ViewReslover視圖解析器。
  9. ViewReslover解析後返回具體View。
  10. DispatcherServlet根據View進行渲染視圖(即將模型數據填充至視圖中)。
  11. DispatcherServlet響應用戶。

請求 ---> DispatcherServlet(前端控制器)---> 調用HandlerMapping(處理器映射器)---> DispatcherServlet調用 HandlerAdapter(處理器適配器)---> 適配調用具體的Controller ---> 返回ModelAndView ---> 傳給ViewReslover視圖解析器 ---> 解析後返回具體View ---> 根據View進行渲染視圖響應用戶

MyBatis原理

MyBatis完成2件事情

  1. 封裝JDBC操做
  2. 利用反射打通Java類與SQL語句之間的相互轉換

MyBatis的主要成員

  1. Configuration MyBatis全部的配置信息都保存在Configuration對象之中,配置文件中的大部分配置都會存儲到該類中
  2. SqlSession 做爲MyBatis工做的主要頂層API,表示和數據庫交互時的會話,完成必要數據庫增刪改查功能
  3. Executor MyBatis執行器,是MyBatis 調度的核心,負責SQL語句的生成和查詢緩存的維護
  4. StatementHandler 封裝了JDBC Statement操做,負責對JDBC statement 的操做,如設置參數等
  5. ParameterHandler 負責對用戶傳遞的參數轉換成JDBC Statement 所對應的數據類型
  6. ResultSetHandler 負責將JDBC返回的ResultSet結果集對象轉換成List類型的集合
  7. TypeHandler 負責java數據類型和jdbc數據類型(也能夠說是數據表列類型)之間的映射和轉換
  8. MappedStatement MappedStatement維護一條select|update|delete|insert節點的封裝
  9. SqlSource 負責根據用戶傳遞的parameterObject,動態地生成SQL語句,將信息封裝到BoundSql對象中,並返回
  10. BoundSql 表示動態生成的SQL語句以及相應的參數信息

這裏寫圖片描述

MyBatis緩存

MyBatis提供查詢緩存,用於減輕數據庫壓力,提升性能。MyBatis提供了一級緩存和二級緩存。

這裏寫圖片描述

  1. 一級緩存是SqlSession級別的緩存,每一個SqlSession對象都有一個哈希表用於緩存數據,不一樣SqlSession對象之間緩存不共享。同一個SqlSession對象對象執行2遍相同的SQL查詢,在第一次查詢執行完畢後將結果緩存起來,這樣第二遍查詢就不用向數據庫查詢了,直接返回緩存結果便可。一級緩存是MyBatis內部實現的一個特性,用戶不能配置,默認狀況下自動支持的緩存,用戶沒有定製它的權利

  2. 二級緩存是Application應用級別的緩存,它的是生命週期很長,跟Application的聲明週期同樣,也就是說它的做用範圍是整個Application應用。MyBatis默認是不開啓二級緩存的,能夠在配置文件中使用以下配置來開啓二級緩存

<settings>
    <setting name="cacheEnabled" value="true"/>
</settings>
複製代碼

後記

以上對面試相關的知識點作了系統的整理,但願對你們有所幫助。想要支持樓主的話,在 Github 上點個 Star,還有就是有些知識點是從網上優秀博客摘抄下來的,若是做者不但願文章部份內容被轉載,能夠通知樓主,會及時處理,感謝。

相關文章
相關標籤/搜索