Java概念題
拆箱裝箱的原理
自動裝箱時編譯器調用valueOf將原始類型值轉換成對象,同時自動拆箱時,編譯器通過調用類似intValue(),doubleValue()這類的方法將對象轉換成原始類型值。
只有double和float的自動裝箱代碼沒有使用緩存,每次都是new 新的對象,其它的6種基本類型都使用了緩存策略。
使用緩存策略是因為,緩存的這些對象都是經常使用到的(如字符、-128至127之間的數字),防止每次自動裝箱都創建一次對象的實例。
而double、float是浮點型的,沒有特別的熱的(經常使用到的)數據的,緩存效果沒有其它幾種類型使用效率高。
反射的原理,反射創建類實例
程序在運行時能夠動態的獲取到一個類的類型信息
創建Class類對象的方式有三種:
對象.getClass()
方式類名.Class
方式Class.forName( 類的包名 )
方式
反射性能慢的原因
- 反射需要按名字來檢索類和方法,有一定的時間開銷。
- 反射涉及類型的動態處理,某些虛擬機無法執行性能優化。
深拷貝和淺拷貝
如果在拷貝這個對象的時候,只對基本數據類型進行了拷貝,而對引用數據類型只是進行了引用的傳遞,而沒有真實的創建一個新的對象,則認為是淺拷貝。反之,在對引用數據類型進行拷貝的時候,創建了一個新的對象,並且復制其內的成員變量,則認為是深拷貝。
實現Cloneable
接口,復寫clone()
方法,實現的是淺拷貝
Spring概念題
Spring AOP與IOC的實現原理
IOC:
IOC(控制翻轉)是一種編程范式,可以在一定程度上解決復雜系統對象耦合度太高的問題,並不是Spring的專利。IOC最常見的方式是DI(依賴注入),可以通過一個容器,將Bean維護起來,方便在其他地方直接使用,而不是重新new。
為什么DI可以起到解耦的作用?
一個軟件系統包含了大量的對象,每個對象之間都有依賴關系,在普通的軟件編寫過程中,對象的定義分布在各個文件之中,對象之間的依賴,只能通過類的構造器傳參,方法傳參的形式來完成。當工程變大之后,復雜的邏輯會讓對象之間的依賴梳理變得異常困難。
在Spring IOC中,一般情況,我們可以在XML文件之中,統一的編寫bean的定義,bean與bean之間的依賴關系,從而增加邏輯的清晰度。而且,bean的創建是由Spring來完成的,不需要編程人員關心,編程人員只需要將精力放到業務的邏輯上面,減輕了思維的負擔。
Spring 啟動時讀取應用程序提供的Bean配置信息,並在Spring容器中生成一份相應的Bean配置注冊表,然后根據這張注冊表實例化Bean,裝配好Bean之間的依賴關系,為上層應用提供准備就緒的運行環境。
Bean緩存池:HashMap實現
依賴注入的思想是通過反射機制實現的,在實例化一個類時,它通過反射調用類中set方法將事先保存在Bean緩存池(HashMap)中的類屬性注入到類中。
AOP:
aop實現原理其實是java動態代理,但是jdk的動態代理必須實現接口,所以spring的aop是用cglib這個庫實現的,cglib使用了asm這個直接操縱字節碼的框架,所以可以做到不實現接口的情況下完成動態代理。
Spring的beanFactory和factoryBean的區別
BeanFactory
BeanFactory是IOC最基本的容器,負責生產和管理bean,它為其他具體的IOC容器實現提供了最基本的規范,例如DefaultListableBeanFactory, XmlBeanFactory, ApplicationContext 等具體的容器都是實現了BeanFactory,再在其基礎之上附加了其他的功能。
FactoryBean
FactoryBean是一個接口,當在IOC容器中的Bean實現了FactoryBean接口后,通過getBean(String BeanName)獲取到的Bean對象並不是FactoryBean的實現類對象,而是這個實現類中的getObject()方法返回的對象。要想獲取FactoryBean的實現類,就要getBean(&BeanName),在BeanName之前加上&。
public interface FactoryBean<T> {
T getObject() throws Exception;
Class<?> getObjectType();
boolean isSingleton();
}
- getObject('name')返回工廠中的實例
- getObject('&name')返回工廠本身的實例
FactoryBean在Spring中最為典型的一個應用就是用來創建AOP的代理對象。
我們知道AOP實際上是Spring在運行時創建了一個代理對象,也就是說這個對象,是我們在運行時創建的,而不是一開始就定義好的,這很符合工廠方法模式。更形象地說,AOP代理對象通過Java的反射機制,在運行時創建了一個代理對象,在代理對象的目標方法中根據業務要求織入了相應的方法。這個對象在Spring中就是——ProxyFactoryBean
。
所以,FactoryBean為我們實例化Bean提供了一個更為靈活的方式,我們可以通過FactoryBean創建出更為復雜的Bean實例
區別
BeanFactory和FactoryBean其實沒有什么比較性的,只是兩者的名稱特別接近,所以有時候會拿出來比較一番。
BeanFactory是個Factory,也就是 IOC 容器或對象工廠,所有的 Bean 都是由 BeanFactory( 也就是 IOC 容器 ) 來進行管理。
FactoryBean是一個能生產或者修飾生成對象的工廠Bean(本質上也是一個bean),可以在BeanFactory(IOC容器)中被管理,所以它並不是一個簡單的Bean。當使用容器中factory bean的時候,該容器不會返回factory bean本身,而是返回其生成的對象。要想獲取FactoryBean的實現類本身,得在getBean(String BeanName)中的BeanName之前加上&,寫成getBean(String &BeanName)。
Spring事務的原理
Mybatis的底層實現原理
spring的controller是單例還是多例,怎么保證並發的安全
controller默認是單例的,不要使用非靜態的成員變量,否則會發生數據邏輯混亂。
正因為單例所以不是線程安全的。
補充說明
spring bean作用域有以下5個:
-
singleton:單例模式,當spring創建applicationContext容器的時候,spring會欲初始化所有的該作用域實例,加上lazy-init就可以避免預處理;
-
prototype:原型模式,每次通過getBean獲取該bean就會新產生一個實例,創建后spring將不再對其管理;
(下面是在web項目下才用到的)
-
request:搞web的大家都應該明白request的域了吧,就是每次請求都新產生一個實例,和prototype不同就是創建后,接下來的管理,spring依然在監聽;
-
session:每次會話,同上;
-
global session:全局的web域,類似於servlet中的application。
網絡概念題
TIME_WAIT和CLOSE_WAIT狀態區別
常用的三個狀態是:ESTABLISHED 表示正在通信,TIME_WAIT 表示主動關閉,CLOSE_WAIT 表示被動關閉。
TCP協議規定,對於已經建立的連接,網絡雙方要進行四次握手才能成功斷開連接,如果缺少了其中某個步驟,將會使連接處於假死狀態,連接本身占用的資源不會被釋放。網絡服務器程序要同時管理大量連接,所以很有必要保證無用連接完全斷開,否則大量僵死的連接會浪費許多服務器資源。在眾多TCP狀態中,最值得注意的狀態有兩個:CLOSE_WAIT和TIME_WAIT。
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 這么長的時間?
RFC 793 [Postel 1981c] 指出MSL為2分鍾。然而,實現中的常用值是30秒,1分鍾,或2分鍾。
2MSL即兩倍的MSL,TCP的TIME_WAIT狀態也稱為2MSL等待狀態,當TCP的一端發起主動關閉,在發出最后一個ACK包后,即第3次握手完成后發送了第四次握手的ACK包后就進入了TIME_WAIT狀態,必須在此狀態上停留兩倍的MSL時間。
等待2MSL時間主要目的是怕最后一個ACK包對方沒收到,那么對方在超時后將重發第三次握手的FIN包,主動關閉端接到重發的FIN包后可以再發一個ACK應答包。
在TIME_WAIT狀態時兩端的端口不能使用,要等到2MSL時間結束才可繼續使用。
當連接處於2MSL等待階段時任何遲到的報文段都將被丟棄。不過在實際應用中可以通過設置SO_REUSEADDR選項達到不必等待2MSL時間結束再使用此端口。
TIME_WAIT 和*CLOSE_WAIT*狀態socket過多
如果服務器出了異常,百分之八九十都是下面兩種情況:
1.服務器保持了大量TIME_WAIT狀態
2.服務器保持了大量CLOSE_WAIT狀態,簡單來說CLOSE_WAIT數目過大是由於被動關閉連接處理不當導致的。
因為linux分配給一個用戶的文件句柄是有限的,而TIME_WAIT和CLOSE_WAIT兩種狀態如果一直被保持,那么意味着對應數目的通道就一直被占着,而且是“占着茅坑不使勁”,一旦達到句柄數上限,新的請求就無法被處理了,接着就是大量Too Many Open Files異常,Tomcat崩潰。
redis概念題
redis 的並發競爭問題是什么?如何解決這個問題?了解 redis 事務的 CAS 方案嗎?
這個也是線上非常常見的一個問題,就是多客戶端同時並發寫一個 key,可能本來應該先到的數據后到了,導致數據版本錯了;或者是多客戶端同時獲取一個 key,修改值之后再寫回去,只要順序錯了,數據就錯了。
解決辦法
- zookeeper 實現分布式鎖
- 你要寫入緩存的數據,都是從 mysql 里查出來的,都得寫入 mysql 中,寫入 mysql 中的時候必須保存一個時間戳,從 mysql 查出來的時候,時間戳也查出來。
- 每次要寫之前,先判斷一下當前這個 value 的時間戳是否比緩存里的 value 的時間戳要新。如果是的話,那么可以寫,否則,就不能用舊的數據覆蓋新的數據。
Mysql概念題
Mysql 中 MyISAM 和 InnoDB 的區別
-
InnoDB 支持事務,MyISAM 不支持事務。這是 MySQL 將默認存儲引擎從 MyISAM 變成 InnoDB 的重要原因之一;
-
InnoDB 支持外鍵,而 MyISAM 不支持。對一個包含外鍵的 InnoDB 表轉為 MYISAM 會失敗;
-
InnoDB 是聚集索引,MyISAM 是非聚集索引。聚簇索引的文件存放在主鍵索引的葉子節點上,因此 InnoDB 必須要有主鍵,通過主鍵索引效率很高。但是輔助索引需要兩次查詢,先查詢到主鍵,然后再通過主鍵查詢到數據。因此,主鍵不應該過大,因為主鍵太大,其他索引也都會很大。而 MyISAM 是非聚集索引,數據文件是分離的,索引保存的是數據文件的指針。主鍵索引和輔助索引是獨立的。
-
InnoDB 不保存表的具體行數,執行 select count(*) from table 時需要全表掃描。而MyISAM 用一個變量保存了整個表的行數,執行上述語句時只需要讀出該變量即可,速度很快;
-
InnoDB 最小的鎖粒度是行鎖,MyISAM 最小的鎖粒度是表鎖。一個更新語句會鎖住整張表,導致其他查詢和更新都會被阻塞,因此並發訪問受限。這也是 MySQL 將默認存儲引擎從 MyISAM 變成 InnoDB 的重要原因之一;
MySql中索引數據結構
Mysql中索引為B+樹結構
聚集索引
其中,葉子節點存儲了整個表的數據,而不是只有索引列,每個葉子節點包含了主鍵值、事務ID
非葉子節點存儲的不是數據指針,只存儲了主鍵值,並以此作為指向行的“指針”。這樣的策略減少了當出現行移動或者數據頁分裂時二級索引的維護工作。使用主鍵當做指針會讓二級索引占更多空間
非聚集索引
葉子節點存儲了主鍵值
然后根據主鍵值再次查詢數據
總結
一個表數據空間中的索引數據區域中有很多索引,每一個索引都是一顆B+Tree,在非聚集索引的B+Tree中索引的值作為B+Tree的節點的Key,數據主鍵作為節點的Value。 在InnoDB中,表數據文件本身就是按B+Tree組織的一個索引結構,這棵樹的葉節點數據域保存了完整的數據記錄。這個索引的key是數據表的主鍵,因此InnoDB表數據文件本身就是主鍵索引。這種索引也叫做聚集索引。 聚集索引查詢速度比非聚集索引快,是因為聚集索引只查詢一次,查詢到的元素的key就是主鍵,value就是數據記錄。 非聚集索引查詢要查詢兩次,第一次查詢到的元素的value為數據記錄的主鍵,再根據主鍵查詢匹配的數據記錄。
Mysql隔離級別怎么產生的(怎么使用鎖形成不同的隔離級別)
策略題
mySQL里有2000w數據,redis中只存20w的數據,如何保證redis中的數據都是熱點數據?
提供一種簡單實現緩存失效的思路: LRU(最近少用的淘汰)
即redis的緩存每命中一次,就給命中的緩存增加一定ttl(過期時間)(根據具體情況來設定, 比如10分鍾).
一段時間后, 熱數據的ttl都會較大, 不會自動失效, 而冷數據基本上過了設定的ttl就馬上失效了.
redis 提供 6種數據淘汰策略:
redis 內存數據集大小上升到一定大小的時候,就會施行數據淘汰策略。redis 提供 6種數據淘汰策略:
volatile-lru:從已設置過期時間的數據集(server.db[i].expires)中挑選最近最少使用的數據淘汰
volatile-ttl:從已設置過期時間的數據集(server.db[i].expires)中挑選將要過期的數據淘汰
volatile-random:從已設置過期時間的數據集(server.db[i].expires)中任意選擇數據淘汰
allkeys-lru:從數據集(server.db[i].dict)中挑選最近最少使用的數據淘汰
allkeys-random:從數據集(server.db[i].dict)中任意選擇數據淘汰
no-enviction(驅逐):禁止驅逐數據
分布式唯一id
- UUID
算法的核心思想是結合機器的網卡、當地時間、一個隨記數來生成UUID。
- 優點:本地生成,生成簡單,性能好,沒有高可用風險
- 缺點:長度過長,存儲冗余,且無序不可讀,查詢效率低
- 數據庫自增ID
使用數據庫的id自增策略,如 MySQL 的 auto_increment。並且可以使用兩台數據庫分別設置不同步長,生成不重復ID的策略來實現高可用。
- 優點:數據庫生成的ID絕對有序,高可用實現方式簡單
- 缺點:需要獨立部署數據庫實例,成本高,有性能瓶頸
- 批量生成ID
一次按需批量生成多個ID,每次生成都需要訪問數據庫,將數據庫修改為最大的ID值,並在內存中記錄當前值及最大值。
- 優點:避免了每次生成ID都要訪問數據庫並帶來壓力,提高性能
- 缺點:屬於本地生成策略,存在單點故障,服務重啟造成ID不連續
- Redis生成ID
Redis的所有命令操作都是單線程的,本身提供像 incr 和 increby 這樣的自增原子命令,所以能保證生成的 ID 肯定是唯一有序的。
- 優點:不依賴於數據庫,靈活方便,且性能優於數據庫;數字ID天然排序,對分頁或者需要排序的結果很有幫助。
- 缺點:如果系統中沒有Redis,還需要引入新的組件,增加系統復雜度;需要編碼和配置的工作量比較大。
考慮到單節點的性能瓶頸,可以使用 Redis 集群來獲取更高的吞吐量。假如一個集群中有5台 Redis。可以初始化每台 Redis 的值分別是1, 2, 3, 4, 5,然后步長都是 5。各個 Redis 生成的 ID 為:
A:1, 6, 11, 16, 21
B:2, 7, 12, 17, 22
C:3, 8, 13, 18, 23
D:4, 9, 14, 19, 24
E:5, 10, 15, 20, 25
復制代碼
隨便負載到哪個機確定好,未來很難做修改。步長和初始值一定需要事先確定。使用 Redis 集群也可以方式單點故障的問題。
另外,比較適合使用 Redis 來生成每天從0開始的流水號。比如訂單號 = 日期 + 當日自增長號。可以每天在 Redis 中生成一個 Key ,使用 INCR 進行累加。
- Twitter的snowflake算法
Twitter一個全局ID生成的服務 Snowflake:github.com/twitter/sno…
如上圖的所示,Twitter 的 Snowflake 算法由下面幾部分組成:
- 1位符號位:
由於 long 類型在 java 中帶符號的,最高位為符號位,正數為 0,負數為 1,且實際系統中所使用的ID一般都是正數,所以最高位為 0。
- 41位時間戳(毫秒級):
需要注意的是此處的 41 位時間戳並非存儲當前時間的時間戳,而是存儲時間戳的差值(當前時間戳 - 起始時間戳),這里的起始時間戳一般是ID生成器開始使用的時間戳,由程序來指定,所以41位毫秒時間戳最多可以使用 (1 << 41) / (1000x60x60x24x365) = 69年
。
- 10位數據機器位:
包括5位數據標識位和5位機器標識位,這10位決定了分布式系統中最多可以部署 1 << 10 = 1024
s個節點。超過這個數量,生成的ID就有可能會沖突。
- 12位毫秒內的序列:
這 12 位計數支持每個節點每毫秒(同一台機器,同一時刻)最多生成 1 << 12 = 4096個ID
加起來剛好64位,為一個Long型。
優點:
1)不依賴於數據庫,靈活方便,且性能優於數據庫。
2)ID按照時間在單機上是遞增的。
缺點:
1)在單機上是遞增的,但是由於涉及到分布式環境,每台機器上的時鍾不可能完全同步,也許有時候也會出現不是全局遞增的情況。
數據庫生成
以MySQL舉例,利用給字段設置auto_increment_increment
和auto_increment_offset
來保證ID自增,每次業務使用下列SQL讀寫MySQL得到ID號。
begin;
REPLACE INTO Tickets64 (stub) VALUES ('a');
SELECT LAST_INSERT_ID();
commit;
這種方案的優缺點如下:
優點:
- 非常簡單,利用現有數據庫系統的功能實現,成本小,有DBA專業維護。
- ID號單調自增,可以實現一些對ID有特殊要求的業務。
缺點:
- 強依賴DB,當DB異常時整個系統不可用,屬於致命問題。配置主從復制可以盡可能的增加可用性,但是數據一致性在特殊情況下難以保證。主從切換時的不一致可能會導致重復發號。
- ID發號性能瓶頸限制在單台MySQL的讀寫性能。
對於MySQL性能問題,可用如下方案解決:在分布式系統中我們可以多部署幾台機器,每台機器設置不同的初始值,且步長和機器數相等。比如有兩台機器。設置步長step為2,TicketServer1的初始值為1(1,3,5,7,9,11…)、TicketServer2的初始值為2(2,4,6,8,10…)。
美團Leaf
Leaf 是美團開源的分布式ID生成器,能保證全局唯一性、趨勢遞增、單調遞增、信息安全,里面也提到了幾種分布式方案的對比,但也需要依賴關系數據庫、Zookeeper等中間件。
具體可以參考官網說明:tech.meituan.com/MT_Leaf.htm…
MQ重復消費
上半場
MQ-client生成inner-msg-id,保證上半場冪等。
這個ID全局唯一,業務無關,由MQ保證。
利用一張日志表來記錄已經處理成功的消息的 ID,如果新到的消息 ID 已經在日志表中,那么就不再處理這條消息,或者放置在redis緩存中也可以
下半場
業務發送方帶入biz-id,業務接收方去重保證冪等。
這個ID對單業務唯一,業務相關,對MQ透明。
結論:冪等性,不僅對MQ有要求,對業務上下游也有要求。