原文鏈接:https://blog.csdn.net/shengqianfeng/article/details/102572691
memcache的分布式原理
memcached 雖然稱為 “ 分布式 ” 緩存服務器,但服務器端並沒有 “ 分布式 ” 功能。每個服務器都是完全獨立和隔離的服務。 memcached 的分布式,則是完全由客戶端程序庫實現的。 這種分布式是 memcached 的最大特點。
memcache的內存分配機制
如何存放數據到memcached緩存中?(memcache內存分配機制)
Slab Allocator內存分配機制:
預先將內存分配成數個slab倉庫,每個倉庫再切出不同大小的chunk,去適配收到的數據。多余的只能造成浪費,不可避免。
增長因子(Grace factor):一般而言觀察數據大小的變化規律設置合理的增長因子,默認1.25倍.
太大容易造成浪費。memcached.exe -m 64 -p 11211 -f 1.25
如果有100byte的內容要存儲,但122大小的倉庫的chunk用滿了怎么辦?
答:是並不會尋找更大倉庫的chunk來存儲,而是把122倉庫中的舊數據踢掉!
memcache的惰性失效機制
1 當某個值過期后並不會從內存刪除。(因此status統計時的curr_items有其信息)
2 如果之前沒有get過,將不會自動刪除。
如果(過期失效,沒get過一次)又沒有一個新值去占用他的位置時,當做空的chunk占用。
3 當取其值(get)時,判斷是否過期:如果過期返回空,且清空。(所以curr_items就減少了)
即這個過期只是讓用戶看不到這個數據而已,並沒有在過期的瞬間立即從內存刪除,這個過程
稱為lazy expirtion,屬性失效,好處是節約了cpu和檢測的成本,稱為“惰性失效機制”
memcache緩存的無底洞現象
緩存的無底洞現象:
facebook的工作人員反應,他們在2010年左右,memcacahed節點就已經達到3000個,大約數千G的緩存,他們發現一個問題,memchache連接頻率太高導致效率下降,於是加memcache節點,添加后發現連接頻率導致的問題仍然沒有好轉,稱之為“無底洞現象”。
問題分析:
以用戶為例:user-133-age,user-133_name,user-133-height…N個key
當服務器增多,133號用戶的信息也被散落在更多的服務器,
所以同樣是訪問個人主頁,得到相同的個人信息,節點越多,要連接節點越多,對於memcache的連接數並沒有隨着節點的增多而降低,問題出現。
事實上:
nosql和傳統的rdbms並不是水火不容,兩者在某些設計上是可以相互參考的。
對於nosql的key-value這種存儲,key的設計可以參考mysql中表和列的設計。
比如user表下有age、name、height列,對應的key可以用user:133:age=23,user:133:name=ls,user:133:height=168;
問題的解決方案:
把某一組key按其共同前綴來分布,比如:user:133:age=23,user:133:name=ls,user:133:height=168;
在用分布式算法求其節點時,應該以user:133來計算,而不是以user:133:age來計算,這樣這三個關於個人信息的key都落在同一個節點上。
再次訪問只需要連接一個節點。問題解決。
一致性Hash算法的實現原理
Hash環
我們把232次方想成一個環,比如鍾表上有60個分針點組成一個圓,那么hash環就是由232個點組成的圓。第一個點是0,最后一個點是232-1,我們把這232個點組成的環稱之為HASH環。
一致性Hash算法
將memcached物理機節點通過Hash算法虛擬到一個虛擬閉環上(由0到232構成),key請求的時候通過Hash算法計算出Hash值然后對232取模,定位到環上順時針方向最接近的虛擬物理節點就是要找到的緩存服務器。
假設有ABC三台緩存服務器:
我們使用這三台服務器各自的IP進行hash計算然后對2~32取模即:
Hash(服務器IP)%2~32
計算出來的結果是0到2~32-1的一個整數,那么Hash環上必有一個點與之對應。比如:
現在緩存服務器已經落到了Hash環上,接下來我們就看我們的數據是怎么放到緩存服務器的?
我們可以同樣對Object取Hash值然后對2~32取模,比如落到了接近A的一個點上:
那么這個數據理應存到A這個緩存服務器節點上
所以,在緩存服務器節點數量不變的情況下,緩存的落點是不會變的。
但是如果B掛掉了呢?
按照hash且取模的算法,圖中3這個Object理應就分配到了C這個節點上去了,所以就會到C上找緩存數據,結果當然是找不到,進而從DB讀取數據重新放到了C上。
但是對於編號為1,2的Object還是落到A,編號為4的Object還是落到C,B宕機所影響的僅僅是3這個Object。這就是一致性Hash算法的優點。
Hash環的傾斜
前面我們理想化的把三台memcache機器均勻分到了Hash環上:
但是現實情況可能是:
如果Hash環傾斜,即緩存服務器過於集中將會導致大量緩存數據被分配到了同一個服務器上。比如編號1,2,3,4,6的Object都被存到了A,5被存到B,而C上竟然一個數據都沒有,這將造成內存空間的浪費。
為了解決這個問題,一致性Hash算法中使用“虛擬節點”解決。
虛擬節點解決Hash環傾斜
“虛擬節點”是“實際節點”在hash環上的復制品,一個實際節點可能對應多個虛擬節點。這樣就可以將ABC三台服務器相對均勻分配到Hash環上,以減少Hash環傾斜的影響,使得緩存被均勻分配到hash環上。
hash算法平衡性
平衡性指的是hash的結果盡可能分布到所有的緩存中去,這樣可以使得所有的緩存空間都可以得到利用。但是hash算法不保證絕對的平衡性,為了解決這個問題一致性hash引入了“虛擬節點”的概念。虛擬節點”( virtual node )是實際節點在 hash 空間的復制品( replica ),一實際個節點對應了若干個“虛擬節點”,這個對應個數也成為“復制個數”,“虛擬節點”在 hash 空間中以 hash 值排列。“虛擬節點”的hash計算可以采用對應節點的IP地址加數字后綴的方式。
例如假設 cache A 的 IP 地址為202.168.14.241 。
引入“虛擬節點”前,計算 cache A 的 hash 值: Hash(“202.168.14.241”);
引入“虛擬節點”后,計算“虛擬節”點 cache A1 和 cache A2 的 hash 值:
Hash(“202.168.14.241#1”); // cache A1
Hash(“202.168.14.241#2”); // cache A2
這樣只要是命中cacheA1和cacheA2節點,就相當於命中了cacheA的緩存。這樣平衡性就得到了提高。
參考:https://www.cnblogs.com/yixiwenwen/p/3580646.html
memcached與redis的區別
1 redis做存儲,可以持久化,memcache做緩存,數據易丟失。
2 redis支持多數據類型,memcache存放字符串。
3 redis服務端僅支持單進程、單線程訪問,也就是先來后到的串行模式,避免線程上下文切換,自然也就保證數據操作的原子性。Memcache服務端是支持多線程訪問的。
4 redis雖然是單進程單線程模式,但是redis使用了IO多路復用技術做到一個線程可以處理很多個請求來保證高性能。
Redis的主從復制
1 在Slave啟動並連接到Master之后,它將主動發送一個SYNC命令給Master。
2 Master在收到SYNC命令之后,將執行BGSAVE命令執行后台存盤進程(rdb快照), 同時收集所有接收到的修改數據集的命令即寫命令到緩沖區,在后台存盤進程執行完畢后,Master將傳送整個數據庫文件到Slave。
3 Slave在接收到數據庫文件數據之后,將自身內存清空,加載rdb文件到內存中完成一次完全同步。
4 接着,Master繼續將所有已經收集到緩沖區的修改命令,和新的修改命令依次傳送給Slaves
5 Slave將在本地執行這些數據修改命令,從而達到最終的數據同步
6 之后Master和Slave之間會不斷通過異步方式進行命令的同步,從而保證數據的實時同步
7 如果Master和Slave之間的鏈接出現斷連現象,Slave可以自動重連Master,但是在
重新連接成功之后:
2.8之前的redis將進行一次完全同步
2.8之后進行部分同步,使用的是PSYNC命令
如下:
Redis的部分復制過程
部分同步工作原理如下:
1):Master為被發送的復制流創建一個內存緩沖區(in-memory backlog),記錄最近發送的復制流命令
2):Master和Slave之間都記錄一個復制偏移量(replication offset)和當前Master ID(Master run id)
3):當出現網絡斷開,Slave會重新連接,並且向Master請求繼續執行原來的復制進程
4):如果Slave中斷網前的MasterID和當前要連的MasterID相同,並且從斷開時到當前時刻Slave記錄的偏移量所指定的數據仍然保存在Master的復制流緩沖區里面,則Master會向Slave發送缺失的那部分數據,Slave執行后復制工作可以繼續執行。
5):否則Slave就執行完整重同步操作
Redis的主從復制阻塞模式
1 同一個Master服務可以同步n多個Slave服務
2 Slave節點同樣可以接受其它Slave節點的連接和同步服務請求,分擔Master節點的同步壓力
3 Master是以非阻塞方式為Slave提供同步服務,所以主從復制期間Master一樣可以提供讀寫請求。
4 Slave同樣是以非阻塞的方式完成數據同步,在同步期間,如果有客戶端提交查詢請求,Redis則返回同步之前的數據
Redis的數據持久化方式
Rdb快照和aof
RDB快照:可以配置在n秒內有m個key修改就做自動化快照方式
AOF:每一個收到的寫命令都通過write函數追加到文件中。更安全。
Redis的高可用部署方式
哨兵模式
redis3.0之前的Sentinel哨兵機制,redis3.0之前只能使用一致性hash方式做分布式緩存。哨兵的出現主要是解決了主從復制出現故障時需要人為干預的問題。
Redis哨兵主要功能
(1)集群監控:負責監控Redis master和slave進程是否正常工作
(2)消息通知:如果某個Redis實例有故障,那么哨兵負責發送消息作為報警通知給管理員
(3)故障轉移:如果master node掛掉了,會自動轉移到slave node上
(4)配置中心:如果故障轉移發生了,通知client客戶端新的master地址
Redis哨兵的高可用
原理:當主節點出現故障時,由Redis Sentinel自動完成故障發現和轉移,並通知應用方,實現高可用性
哨兵機制建立了多哨兵節點,共同監控數據節點的運行狀況。
同時哨兵節點之間也互相通信,交換對主從節點的監控狀況。
每隔1秒每個哨兵會向整個集群:Master主服務器+Slave從服務器+其他Sentinel(哨兵)進程,發送一次ping命令做一次心跳檢測。
哨兵如何判斷redis主從節點是否正常?
涉及兩個新的概念:主觀下線和客觀下線。
- 主觀下線:一個哨兵節點判定主節點down掉是主觀下線。
- 客觀下線:只有半數哨兵節點都主觀判定主節點down掉,此時多個哨兵節點交換主觀判定結果,才會判定主節點客觀下線。
原理:基本上哪個哨兵節點最先判斷出這個主節點客觀下線,就會在各個哨兵節點中發起投票機制Raft算法(選舉算法),最終被投為領導者的哨兵節點完成主從自動化切換的過程。
集群模式
redis3.0之后的容錯集群方式,無中心結構,每個節點保存數據和整個集群狀態,每個節點都和其他所有節點連接,需要至少三個master提供寫的功能。
因此集群中至少應該有奇數個節點,因此至少有三個節點,每個節點至少有一個備份節點,所以redis集群應該至少6個節點。
每個Master有一個范圍的slot槽位用於寫數據。
Redis可以在線擴容嗎?zk呢
Reids的在線擴容,不需要重啟服務器,動態的在原始集群中添加新的節點,並分配slot槽。
但是zk不能在線擴容,需要重啟,但是我們可以選擇一個一個重啟。
Redis高並發和快速的原因
1.redis是基於內存的,內存的讀寫速度非常快;
2.redis是單線程的,省去了很多上下文切換線程的時間;
3.redis使用多路復用技術,可以處理並發的連接。
缺點:無法發揮多核CPU性能
瀏覽器本地緩存的了解和使用
資源在瀏覽器端的本地緩存可以通過Expires和Last-Modified返回頭信息進行有效控制。
1)Expires告訴瀏覽器在該指定過期時間前再次訪問同一URL時,直接從本地緩存讀取,無需再向服務器發起http請求;
優點是:瀏覽器直接讀取緩存信息無需發起http請求。
缺點是:當用戶按F5或Ctl+F5刷新頁面時瀏覽器會再次發起http請求。
2)當服務器返回設置了Last-Modified頭,下次發起同一URL的請求時,請求頭會自動包含If-Modified-Since頭信息,服務器對靜態內容會根據該信息跟文件的最后修改時間做比較,如果最后修改時間不大於If-Modified-Since頭信息,則返回304:告訴瀏覽器請求內容未更新可直接使用本地緩存。
(注意:只對靜態內容有效,如js/css/image/html等,不包括動態內容,如JSP)
優點:無論用戶行為如何都有效;
缺點:仍需向服務器發起一次http請求;
緩存雪崩
如果緩存集中在一段時間內失效,發生大量的緩存穿透,所有的查詢都落在數據庫上,造成了緩存雪崩。
解決辦法:
沒有完美的解決方案,可以通過隨機算法讓失效時間隨機分布,避免同一時刻失效。
緩存穿透
訪問一個不存在的key,緩存不起作用,請求會穿透到DB,可能DB也沒查到,流量大時DB會掛掉。
解決辦法:
1.采用布隆過濾器,使用一個足夠大的bitmap,用於存儲可能訪問的key,不存在的key直接被過濾;
2訪問key未在DB查詢到值,也將空值寫進緩存,但可以設置較短過期時間。
HashMap的Hash碰撞
Hash值沖突問題是Hash表存儲模型需要解決的一個問題。通常有兩種方法:
將相同Hash值的Entry對象組織成一個鏈表放置在hash值對應的槽位。HashMap采用的是鏈表法,且是單向鏈表(通過head元素就可以操作后續所有元素,對鏈表而言,新加入的節點會從頭節點加入。)
核心源碼:
1 private void addEntry(int hash, K key, V value, int bucketIndex) { 2 Entry<K,V> e = table[bucketIndex]; 3 table[bucketIndex] = new Entry<K,V>(hash, key, value, e); 4 if (size++ >= threshold) 5 resize(2 * table.length); 6 }
以上代碼說明:
系統總是將新添加的 Entry 對象放入 table 數組的 bucketIndex 索引處。
1 如果 bucketIndex 索引處已經有了一個 Entry 對象,那新添加的 Entry 對象指向原有的 Entry 對象
(產生一個 Entry 鏈)
2 如果 bucketIndex 索引處沒有 Entry 對象,也就是上面程序代碼的 e 變量是 null,也就是新放入的
Entry 對象指向 null,也就是沒有產生 Entry 鏈。
HashMap里面沒有出現hash沖突時,沒有形成單鏈表時,hashmap查找元素很快,get()方法能夠直接定位到元素,
但是出現單鏈表后,單個bucket 里存儲的不是一個 Entry,而是一個 Entry 鏈,系統只能必須按順序遍歷每個
Entry,直到找到想搜索的 Entry 為止——如果恰好要搜索的 Entry 位於該 Entry 鏈的最末端(該 Entry 是最早
放入該 bucket 中),那系統必須循環到最后才能找到該元素。
HashMap的get和put原理
PUT原理:
當調用HashMap的put方法傳遞key和value時,先調用key的hashcode方法。
通過key的Hash值來找到Bucket----‘桶’的位置,然后迭代這個位置的Entry列表
判斷是否存在key的hashcode和equals完全相同的key,如果完全相同則覆蓋value,
否則插入到entry鏈的頭部。
- HashMap在put時的Entry鏈形成的場景?
當程序試圖將一個key-value對放入HashMap中時,程序首先根據該 key 的 hashCode() 返回值決定該 Entry 的存儲位置:
如果這兩個 Entry 的 key 的 hashCode() 返回值相同,那它們的存儲位置相同。
如果這兩個 Entry 的 key 通過 equals 比較返回 true,新添加 Entry 的 value 將覆蓋集合中原有 Entry 的 value,但key不會覆蓋。
如果這兩個 Entry 的 key 通過 equals 比較返回 false,新添加的 Entry 將與集合中原有 Entry 形成 Entry 鏈,而且新添加的 Entry 位於 Entry 鏈的頭部
GET原理:
根據該 key 的 hashCode 值計算它的 hash 碼,遍歷並循環取出 Entry 數組中指定索引處的Entry值,如果該 Entry 的 key 與被搜索 key 相同 ,且Enrty的hash值跟key的hash碼相同,然后看是否是Entry鏈,如果是則迭代這個位置的Entry列表,判斷是否存在key的hashcode和equals完全相同的key,如果完全相同則獲取value。
HashMap的rehash
HashMap初始容量大小為16,一般來說,當有數據要插入時,都會檢查容量有沒有超過設定的thredhold,如果超過,需要增大Hash表的尺寸,但是這樣一來,整個Hash表里的元素都需要被重算一遍。這叫rehash,這個成本相當的大
HashMap的線程不安全問題
比如put操作時,有兩個線程A和B,首先A希望插入一個key-value對到HashMap中,首先計算記錄所要落到的桶的索引BucketIndex坐標,然后獲取到該桶里面的Entry鏈表header頭結點,此時線程A的時間片用完了,而此時線程B被調度得以執行,和線程A一樣執行,只不過線程B成功將記錄插到了桶里面,假設線程A插入的記錄計算出來的桶索引和線程B要插入的記錄計算出來的桶索引是一樣的,那么當線程B成功插入之后,線程A再次被調度運行時,它依然持有過期的鏈表頭但是它對此一無所知,以至於它認為它應該這樣做,如此一來就覆蓋了線程B插入的記錄,這樣線程B插入的記錄就憑空消失了,造成了數據不一致的行為。另一個不安全的體現是是get操作可能由於resize而死循環。
參考:https://www.cnblogs.com/qiumingcheng/p/5259892.html
HashMap和Hashtable的區別
相同點:
1 都實現了Map接口
2 Hashtable和HashMap采用的hash/rehash算法都大概一樣,所以性能不會有很大的差異
不同點:
1 hashMap允許NULL作為key和value,而hashtable不允許
2 hashMap線程不安全,Hashtable線程安全
3 hashMap速度快於hashtable
4 HashMap 把 Hashtable的contains方法去掉了,改成containsvalue和containsKey,避免引起誤會
5 Hashtable是基於陳舊的Dictionary類的,HashMap是Java 1.2引進的Map接口的一個實現
為什么collection沒有實現clonable接口
Collection接口有很多不同的集合實現形式,而clonable只對具體的對象有意義。
為什map沒有實現collection接口
Set 和List 都繼承了Conllection,Map沒有繼承於Collection接口,Map提供的是key-Value的映射,而Collection代表一組對象。
Map接口的實現有哪些,區別是什么
HashMap,LinkedHashMap,Hashtable,TreeMap。
LinkedHashMap 是HashMap的一個子類,保存了記錄的插入順序
Hashtable和HashMap類似,它繼承自Dictionary類,不同的是它不允許鍵或值為空。
TreeMap實現SortMap接口,能夠把它保存的記錄根據鍵排序,默認是按鍵值的升序排序,也可以指定排序的比較器
Executors框架的四種線程池及拒絕策略
四種線程池
ExecutorService executorService =
固定大小線程池
Executors.newFixedThreadPool(60);
設置固定值會造成高並發線程排隊等待空閑線程,尤其是當讀取大數據量時線程處理時間長而不釋放線程,導致無法創建新線程。
可緩存線程池
Executors.newCachedThreadPool();
線程池無限大,而系統資源(內存等)有限,會導致機器內存溢出OOM。
定長且可定時、周期線程池
Executors.newScheduledThreadPool(5);
單線程線程池
Executors.newSingledThreadPool();
1 /* 自定義線程池。 2 * 構造參數: 3 * public ThreadPoolExecutor( 4 * int corePoolSize,--當前線程池核心線程數 5 * int maximumPoolSize,--當前線程池最大線程數 6 * long keepAliveTime,--保持活着的空間時間 7 * TimeUnit unit,--時間單位 8 * BlockingQueue<Runnable> workQueue,--排隊等待的自定義隊列 9 * ThreadFactoty threadFactory, 10 * RejectedExecutionHandler handler--隊列滿以后,其他任務被拒絕執行的方法 11 * ){.........}
在使用有界隊列時,若有新的任務需要執行,
- 若線程池實際線程數小於corePoolSize,則優先創建線程,
- 若大於corePoolSize,則會將任務加入隊列,
- 若隊列已滿,則在總線程數不大於maximumPoolSize的前提下,創建新的線程,
- 若線程數大於maximumPoolSize,則執行拒絕策略。或其他自定義方式。
JDK拒絕策略
- AbortPolicy:默認,直接拋出異常,系統正常工作。
- DiscardOldestPolicy:丟棄最老的一個請求,嘗試再次提交當前任務。
- CallerRunsPolicy:只要線程池未關閉,該策略直接在調用者線程中,運行當前被丟棄的任務。用線程池中的線程執行,而是交給調用方來執行, 如果添加到線程池失敗,那么主線程會自己去執行該任務,不會等待線程池中的線程去執行
1 new ThreadPoolExecutor( 2 2, 3, 30, TimeUnit.SECONDS, 3 new SynchronousQueue<Runnable>(), 4 new RecorderThreadFactory("CookieRecorderPool"), 5 new ThreadPoolExecutor.CallerRunsPolicy());
- DiscardPolicy:丟棄無法處理的任務,不給予任何處理。
- 自定義拒絕策略
如果需要自定義策略,可以實現RejectedExecutionHandler接口。
參考:https://www.cnblogs.com/duanxz/p/3696849.html
Reactor單線程模型
一個Acceptor線程,監聽Accept事件,負責接收客戶端的連接SocketChannel,SocketChannel注冊到Selector上並關心可讀可寫事件。
一個Reactor線程,負責輪訓selector,將selector注冊的就緒事件的key讀取出來,拿出attach任務Handler根據事件類型分別去執行讀寫等。
單線程模型的瓶頸:
比如:拿一個客戶端來說,進行多次請求,如果Handler中數據讀出來后處理的速度比較慢(非IO操作:解碼-計算-編碼-返回)會造成客戶端的請求被積壓,導致響應變慢!
所以引入Reactor多線程模型!
Reactor多線程模型
Reactor多線程就是把Handler中的IO操作,非IO操作分開。
操作IO的線程稱為IO線程,操作非IO的線程叫做工作線程。
客戶端的請求(IO操作:讀取出來的數據)可以直接放進工作線程池(非IO操作:解碼-計算-編碼-返回)中,這樣異步處理,客戶端發送的請求就得到返回了不會一直阻塞在Handler中。
但是當用戶進一步增加的時候,Reactor線程又會出現瓶頸,因為Reactor中既有IO操作,又要響應連接請求。為了分擔Reactor的負擔,所以引入了主從Reactor模型!
主從Reactor模型
主Reactor用於響應連接請求,從Reactor用於處理IO操作請求!
特點是:服務端用於接收客戶端連接的不再是1個單獨的NIO線程(Acceptor線程),而是一個獨立的NIO線程池。
Acceptor線程池接收到客戶端TCP連接請求處理完成后(可能包含接入認證等),將新創建的SocketChannel注冊到I/O線程池(sub reactor線程池)的某個I/O線程上,由它負責SocketChannel的讀寫和編解碼工作。
Acceptor線程池只用於客戶端的登錄、握手和安全認證,一旦鏈路建立成功,就將鏈路注冊到后端subReactor線程池的I/O線程上,有I/O線程負責后續的I/O操作。
第三種模型比起第二種模型,是將Reactor分成兩部分,mainReactor負責監聽server socket,accept新連接,並將建立的socket分派給subReactor。subReactor負責多路分離已連接的socket,讀寫網 絡數據,對業務處理功能,其扔給worker線程池完成。通常,subReactor個數上可與CPU個數等同。
Object的內存布局
方法區卸載Class的條件
1 該類所有的實例已經被回收
2 加載該類的ClassLoader已經被回收
4該類對應的java.lang.Class對象沒有任何地方被引用
Ps:方法區除了回收無用class,也回收廢棄常量,即沒有被引用常量
可以作為GC Roots的對象包括哪些
虛擬機棧(棧幀中的局部變量表)中引用的變量
方法區中類靜態屬性引用的對象
方法區中常量引用的對象
本地方法棧中JNI引用的變量
JVM運行時內存模型
方法區、堆、虛擬機棧、本地方法棧、程序計數器
Netty的ByteBuffer的引用計數器機制
從netty的4.x版本開始,netty使用引用計數機制進行部分對象的管理,通過該機制netty可以很好的實現自己的共享資源池。
如果應用需要一個資源,可以從netty自己的共享資源池中獲取,新獲取的資源對象的引用計數被初始化為1,可以通過資源對象的retain方法增加引用計數,當引用計數為0的時候該資源對象擁有的資源將會被回收。
判斷對象是否存活的兩種方法
1 引用計數法:缺點是對循環引用的對象無法回收
2 可達性分析
Java對象的初始化過程
類加載雙親委派模型
從上到下分三個類加載器:
BootStrap classloader:啟動類加載器,負責將Java_HOME/lib下的類庫加載到虛擬機內存中,比如rt.jar
Extension classloader:擴展類加載器,負責將JAVA_HOME/lib/ext下的類庫加載到虛擬機內存中。
Application classloader:應用程序類加載器,負責加載classpath環境變量下指定的類庫。如果程序中沒有自定義過類加載器,那么這個就是程序中默認的類加載器。
雙親委派模型:
如果一個類加載器收到類加載的請求,它首先不會自己去嘗試加載這個類,而是把這個請求委派給父類加載器完成。每個類加載器都是如此,只有當父加載器在自己的搜索范圍內找不到指定的類時(即ClassNotFoundException),子加載器才會嘗試自己去加載。
防止自定義的一些跟jdk標准庫中沖突的全限定名的類被加載,導致標准庫函數不可用。
Zookeeper的常用應用場景有哪些
- 分布式鎖:獲取父節點下的最小節點作為獲得鎖的一方
- 命名服務:通過在zookeeper節點下創建全局唯一的一個path
- 配置管理:配置放在zk上,所有應用監聽節點改變。
- 集群管理:GroupMembers集群管理,是否有機器退出和加入
Zookeeper的分布式數據一致性算法
ZAB原子消息廣播協議。
一種是基於basic paxos實現的,另外一種是基於fast paxos算法實現的。
參考:
http://www.360doc.com/content/16/0823/11/14513665_585293946.shtml
ZAB協議定義了選舉(election)、發現(discovery)、同步(sync)、廣播(Broadcast)四個階段;
Zk啟動過程的Leader選舉分析及數據同步
參考:http://www.cnblogs.com/leesf456/p/6107600.html
Zookeeper數據同步的簡單描述
在ZooKeeper中所有的客戶端事務請求都由一個主服務器也就是Leader來處理,其他服務器為Follower,Leader將客戶端的事務請求轉換為事務Proposal,並且將Proposal分發給集群中其他所有的Follower,然后Leader等待Follwer反饋,當有過半數(>=N/2+1)的Follower反饋信息后,Leader將再次向集群內Follower廣播Commit信息,Commit為將之前的Proposal提交;
ZK集群最少需要幾台機器?
三台,2N+1,保證奇數,主要是為了leader選舉算法中的“超過半數有效(>=N/2+1)”
Zookeeper和Eureka的區別
答:ZK保證Cp,即一致性,分區容錯性,比如當master節點因為網絡故障和其他節點失去聯系的時候,剩余節點會重新進行Master選舉。問題在於Master選舉的時間太長30~210s,選舉期間整個zk集群是不可用的,這就導致選舉期間的注冊服務癱瘓。
Eureka保證Ap,高可用性,它沒有所謂主從節點概念,各節點平等。某節點掛掉不影響其他節點功能,其他節點照樣提供查詢和注冊功能。Eureka客戶端發現Eureka節點掛掉直接切換到其他正常的節點上去。只不過可能查到的數據不是最新的,也就是Eureka不保證數據的強一致性。
作為注冊中心,推薦Eureka,因為注冊服務更重要的是可用性。
InnoDB和MyISAM存儲引擎的區別
Starting from MySQL 5.5.5, the default storage engine for new tables isInnoDB rather than MyISAM.
Btree索引和Hash索引的區別
Btree索引適合范圍查找,Hash索引適合精確查找
數據庫的ACID特性
數據庫事務必須具備ACID特性
原子性:Atomic,所有的操作執行成功,才算整個事務成功
一致性:Consistency,不管事務success或fail,不能破壞關系數據的完整性以及業務邏輯上的一致性
隔離性:Isolation,每個事務擁有獨立數據空間,多個事務的數據修改相互隔離。事務查看數據更新時,數據要么是另一個事務修改前的狀態,要么是修改后狀態,不應該查看到中間狀態數據。
持久性:Durability,事務執行成功,數據必須永久保存。重啟DB,數據需要恢復到事務執行成功后的狀態。
原子性、一致性、持久性DBMS通過日志來實現。
隔離性DBMS通過鎖來實現
Mysql數據庫的隔離級別
M
Select For Update使用場景
select for update 的使用場景,為了避免自己看到的數據並不是數據庫存儲的最新數據並且看到的數據只能由自己修改,需要用 for update 來限制。
分布式事務模型之XA和TCC的區別和聯系?
XA-DTP模型
最早的分布式事務模型是 X/Open 國際聯盟提出的 X/Open Distributed Transaction Processing(DTP)模型,也就是大家常說的 X/Open XA 協議,簡稱XA 協議。
DTP 模型中包含一個全局事務管理器(TM,Transaction Manager)和多個資源管理器(RM,Resource Manager)。全局事務管理器負責管理全局事務狀態與參與的資源,協同資源一起提交或回滾;資源管理器則負責具體的資源操作。
TCC模型
TCC(Try-Confirm-Cancel)分布式事務模型相對於 XA 等傳統模型,其特征在於它不依賴資源管理器(RM)對分布式事務的支持,而是通過對業務邏輯的分解來實現分布式事務。
Try-Confirm-Cancel
Try 操作對應2PC 的一階段准備(Prepare);Confirm 對應 2PC 的二階段提交(Commit),Cancel 對應 2PC 的二階段回滾(Rollback),可以說 TCC 就是應用層的 2PC。
參考:
https://mp.weixin.qq.com/s?__biz=MzUzMzU5Mjc1Nw==&mid=2247483681&idx=1&sn=05845495c5ef33683addd98fffc72106&chksm=faa0eefbcdd767edbf46cea6f223b426e276dd4d9b19cce64f59387590818f5e4eb96c7d2533&mpshare=1&scene=2&srcid=0118GSYShGZaOyCndUoAqsae&from=timeline#rd
Mysql-binlog日志復制方式
①基於段的復制
記錄的是執行的語句
②基於行的復制
記錄是表中每一行的操作
③混合復制
mysql主從復制原理
從服務器的IO線程讀取主服務器的二進制日志變更,寫入到中繼日志relaylog中,如果IO線程追趕上了主服務器的日志,則進入sleep狀態,直到主服務器發送喚醒信號,從服務器上的SQL線程重放relaylog中的日志。
基於日志點的復制和GTID的復制有何區別?
基於日志點的復制:從主服務器的哪個二進制日志的偏移量進行增量同步,如果指定錯誤會造成遺漏或重復。
基於GTID的復制:從服務器會告訴主服務器,已經在從服務器上已經執行完了哪些gtid值,然后主庫會把從庫未執行的事務gtid值發送給從庫執行。同一個事務只在指定的從庫上執行一次。
Mysql性能診斷和優化
聚簇索引和非聚簇索引的區別
聚簇索引,就是指主索引文件和數據文件為同一份文件,聚簇索引主要用在Innodb存儲引擎中。如主鍵。B+Tree的葉子節點上的data就是數據本身。
非聚簇索引就是指B+Tree的葉子節點上的data,並不是數據本身,而是數據存放的地址
消費者宕機:怎么保證消息隊列消息不丟失?
比如activemq或者rabbitmq生產者消息投遞到消息隊列后,消費者拿到消息后,默認是自動簽收機制,消息隊列將刪除這條消息,但是如果僅僅是拿到但是沒有來得及處理業務邏輯時,消費者就宕機,那么此消息將會丟失,以后也不會再收到。
解決辦法:
消費端要設置簽收機制為手動簽收,只有當消息最終被處理,才告訴消息隊列已經消費,此時消息隊列再刪除這條消息。
MQ集群宕機:怎么保證消息不丟失?
生產者投遞消息到mq服務器,如果不保證消息和隊列的持久化,那么當mq宕機時消息將徹底丟失,所以需要對消息做持久化存儲,可以存儲到磁盤或者數據庫中,當mq服務器恢復時,消費端可以繼續消費mq服務器中的消息。
但是,比如RabbitMQ的消息持久化,是不承諾100%的消息不丟失的!
&emsp**;原因**:因為有可能RabbitMQ接收到了消息,但是還沒來得及持久化到磁盤,他自己就宕機了,這個時候消息還是會丟失的。如果要完全100%保證寫入RabbitMQ的數據必須落地磁盤,不會丟失,需要依靠其他的機制。
參考:
https://mp.weixin.qq.com/s/ZAWPRToPQFcwzHBf47jZ-A
https://mp.weixin.qq.com/s/HwAc6o8jdIHQTnE3ghXhIA
https://mp.weixin.qq.com/s/AEn3j2lVJOHZx9yegwTsvw
https://mp.weixin.qq.com/s/uqWIf0MAet_StszuOrZCwQ
https://mp.weixin.qq.com/s/9SFrwaCCLnNyuCqP_KQ0zw
https://mp.weixin.qq.com/s/vZ4KVC2eGmssnQUyIKgzfw
https://mp.weixin.qq.com/s/r2_o5wa6Gn94NY4ViRnjpA
springmvc如何解決循環依賴的問題
當使用構造器方式初始化一個bean,而且此時多個Bean之間有循環依賴的情況,spring容器就會拋出異常!
解決辦法:初始化bean的時候(注意此時的bean必須是單例,否則不能提前暴露一個創建中的bean)使用set方法進行注入屬性,此時bean對象會先執行構造器實例化,接着將實例化后的bean放入一個map中,並提供引用。當需要通過set方式設置bean的屬性的時候,spring容器就會從map中取出被實例化的bean。比如A對象需要set注入B對象,那么從Map中取出B對象即可。以此類推,不會出現循環依賴的異常。
spring事務的傳播行為和隔離級別
spring事務七個事務傳播行為
在TransactionDefinition接口中定義了七個事務傳播行為:
- PROPAGATION_REQUIRED 如果存在一個事務,則支持當前事務。如果沒有事務則開啟一個新的事務。
- PROPAGATION_SUPPORTS 如果存在一個事務,支持當前事務。如果沒有事務,則非事務的執行。但是對於事務同步的事務管理器,PROPAGATION_SUPPORTS與不使用事務有少許不同。
- PROPAGATION_MANDATORY 如果已經存在一個事務,支持當前事務。如果沒有一個活動的事務,則拋出異常。
- PROPAGATION_REQUIRES_NEW 總是開啟一個新的事務。如果一個事務已經存在,則將這個存在的事務掛起。
- PROPAGATION_NOT_SUPPORTED 總是非事務地執行,並掛起任何存在的事務。
- PROPAGATION_NEVER 總是非事務地執行,如果存在一個活動事務,則拋出異常
- PROPAGATION_NESTED如果一個活動的事務存在,則運行在一個嵌套的事務中. 如果沒有活動事務, 則按TransactionDefinition.PROPAGATION_REQUIRED 屬性執行
Spring事務的五種隔離級別
在TransactionDefinition接口中定義了五個不同的事務隔離級別
- ISOLATION_DEFAULT 這是一個PlatfromTransactionManager默認的隔離級別,使用數據庫默認的事務隔離級別.
另外四個與JDBC的隔離級別相對應 - ISOLATION_READ_UNCOMMITTED 這是事務最低的隔離級別,它充許別外一個事務可以看到這個事務未提交的數據。這種隔離級別會產生臟讀,不可重復讀和幻像讀
- ISOLATION_READ_COMMITTED 保證一個事務修改的數據提交后才能被另外一個事務讀取。另外一個事務不能讀取該事務未提交的數據。這種事務隔離級別可以避免臟讀出現,但是可能會出現不可重復讀和幻像讀。
- ISOLATION_REPEATABLE_READ 這種事務隔離級別可以防止臟讀,不可重復讀。但是可能出現幻像讀。它除了保證一個事務不能讀取另一個事務未提交的數據外,還保證了避免下面的情況產生(不可重復讀)。
- ISOLATION_SERIALIZABLE 這是花費最高代價但是最可靠的事務隔離級別。事務被處理為順序執行。除了防止臟讀,不可重復讀外,還避免了幻像讀。
設計模式
單例模式
1懶漢模式-非安全
懶漢模式(線程不安全,可能出現多個Singleton 實例)
1 public class Singleton { 2 private static Singleton instance; 3 private Singleton (){} 4 5 public static Singleton getInstance() { 6 if (instance == null) { 7 instance = new Singleton(); 8 } 9 return instance; 10 } 11 }
2懶漢模式-安全
懶漢模式 (線程安全)
1 public class Singleton { 2 private static Singleton instance; 3 private Singleton (){} 4 5 public static synchronized Singleton getInstance() { 6 if (instance == null) { 7 instance = new Singleton(); 8 } 9 return instance; 10 } 11 }
3餓漢模式
1 public class Singleton { 2 private static Singleton instance = new Singleton(); 3 private Singleton (){} 4 public static Singleton getInstance() { 5 return instance; 6 } 7 }
4餓漢模式(變種)
餓漢(變種,跟第三種差不多,都是在類初始化即實例化instance)
1 public class Singleton { 2 private Singleton instance = null; 3 static { 4 instance = new Singleton(); 5 } 6 private Singleton (){} 7 public static Singleton getInstance() { 8 return this.instance; 9 } 10 }
5靜態內部類
靜態內部類,跟三四有細微差別:
Singleton類被裝載instance不一定被初始化,因為內部類SingletonHolder沒有被主動使用,只有顯示調用getInstance才會顯示裝載SingletonHolder 類,從而實例化instance
1 public class Singleton { 2 private static class SingletonHolder { 3 private static final Singleton INSTANCE = new Singleton(); 4 } 5 private Singleton (){} 6 public static final Singleton getInstance() { 7 return SingletonHolder.INSTANCE; 8 } 9 }
6枚舉
枚舉(既可以避免多線程同步問題,還可以防止被反序列化重建對象)
1 public enum Singleton { 2 INSTANCE; 3 public void whateverMethod() { 4 5 } 6 7 public static void main(String[] args) { 8 Singleton s = Singleton.INSTANCE; 9 Singleton s2 = Singleton.INSTANCE; 10 System.out.println(s==s2); 11 } 12 13 }
**輸出結果:**true
說明這種方式創建的對象是同一個,因為枚舉類中的INSTANCE是static final類型的,只能被實例化一次。對於Enum中每一個枚舉實例,都是相當於一個單獨的Singleton實例。所以借用 《Effective Java》一書中的話,
單元素的枚舉類型已經成為實現Singleton的最佳方法
7懶漢升級版
1 public class Singleton { 2 private volatile static Singleton singleton; 3 private Singleton (){} 4 public static Singleton getSingleton() { 5 if (singleton == null) { 6 synchronized (Singleton.class) { 7 if (singleton == null) { 8 singleton = new Singleton(); 9 } 10 } 11 } 12 return singleton; 13 } 14 }
策略模式
ThreadLocal的實現原理
ThreadLocal的實現原理,有什么缺點?跟線程池結合使用要注意什么
原理:Current Thread當前線程中有一個ThreadLocalMap對象,它的key是ThreadLocal的弱引用,Value是ThreadLocal調用set方法設置的對象值。每一個線程維護一個各自的ThreadLocalMap,所以多個線程之間變量相互隔離,互不干擾。
缺點:存在內存泄漏問題,因為當ThreadLocal設置為null后,ThreadLocalMap的key的弱引用指向了null,又沒有任何的強引用指向threadlocal,所以threadlocal會被GC回收掉。但是,ThreadLocalMap的Value不會被回收,CurrentThread當前線程的強引用指向了ThreadLocalMap,進而指向了這個Entry<key,value>,所以只有當currentThread結束強引用斷開后,currentThread、ThreadLocalMap、Entry將全部被GC回收。
所以結論是:
只要currentThread被GC回收,就不會出現內存泄漏。
但是在currentThread被GC回收之前,threadlocal設置為null之后的這段時間里,Value不會被回收,比如當使用線程池的時候,線程結束不會被GC回收,會被繼續復用,那這個Value肯定還會繼續存在。如果這個Value很大的情況下,可能就會內存泄漏。
雖然threadlocal的set和get方法執行時會清除key為null的value,但是如果當前線程在使用中沒有調用threadlocal的set或者get方法一樣可能會內存泄漏。
跟線程池結合使用的注意事項:
因為線程池中線程復用的情況,本次的threadlocal中可能已經存在數據,所以上一次使用完threadlocal的變量后,要調用threadlocal的remove方法清除value。而且要注意調用完remove后應該保證不會再調用get方法。
AQS實現公平鎖和非公平鎖
基於AQS的鎖(比如ReentrantLock)原理大體是這樣:
- 有一個state變量,初始值為0,假設當前線程為A,每當A獲取一次鎖,status++. 釋放一次,status–.鎖會記錄當前持有的線程。
- 當A線程擁有鎖的時候,status>0. B線程嘗試獲取鎖的時候會對這個status有一個CAS(0,1)的操作,嘗試幾次失敗后就掛起線程,進入一個等待隊列。
- 如果A線程恰好釋放,–status==0, A線程會去喚醒等待隊列中第一個線程,即剛剛進入等待隊列的B線程,B線程被喚醒之后回去檢查這個status的值,嘗試CAS(0,1),而如果這時恰好C線程也嘗試去爭搶這把鎖。
非公平鎖實現:
C直接嘗試對這個status CAS(0,1)操作,並成功改變了status的值,B線程獲取鎖失敗,再次掛起,這就是非公平鎖,B在C之前嘗試獲取鎖,而最終是C搶到了鎖。
公平鎖:
C發現有線程在等待隊列,直接將自己進入等待隊列並掛起,B獲取鎖
RPC的序列化方式有哪些
Thrift—facebook
ProtoBuf—google
Hessian
JAVA原生的序列化接口
Json/xml
服務熔斷與服務降級概念
服務熔斷:
一般指某個服務的下游服務出現問題時采用的手段,而服務降級一般是從整體層面考慮的。下游服務出現問題時可以進行服務熔斷。
對於目標服務的請求和調用大量超時或失敗,這時應該熔斷該服務的所有調用,並且對於后續調用應直接返回,從而快速釋放資源,確保在目標服務不可用的這段時間內,所有對它的調用都是立即返回,不會阻塞的。再等到目標服務好轉后進行接口恢復。
服務降級:
當服務器壓力劇增的情況下,根據當前業務情況及流量對一些服務和頁面有策略的降級,以此釋放服務器資源以保證核心任務的正常運行。
ThreadLocalMap的線性探測法、HashMap的拉鏈法。兩種解決hash碰撞的方式有何不同?
Netty的RPC如何實現
Netty中源碼inbound和outbound有啥區別?
怎么分庫分表可以做到多維度查找
Fork/Join框架
JAVA線程執行中怎么kill掉
1 通過設置全局變量標志來控制線程的任務執行完成.進而銷毀線程
2 如果線程處於長久的阻塞狀態,可以interrupt脫離線程阻塞狀態跳出程序體
HA主備怎么預防腦裂
一般采用2個方法
- 仲裁
當兩個節點出現分歧時,由第3方的仲裁者決定聽誰的。這個仲裁者,可能是一個鎖服務,一個共享盤或者其它什么東西。 - fencing
當不能確定某個節點的狀態時,通過fencing把對方干掉,確保共享資源被完全釋放,前提是必須要有可靠的fence設備。
性別字段是否需要加索引
1.聚集索引,葉子節點存儲行記錄,InnoDB索引和記錄是存儲在一起的。
2.普通索引,葉子節點存儲了主鍵的值。
在InnoDB引擎中每個表都會有一個聚集索引,如果表定義了主鍵,那主鍵就是聚集索引.一個表只有一個聚集索引,其余為普通索引.如果性別sex字段定義為普通的索引,那么在使用普通索引查詢時,會先加載普通索引,通過普通索引查詢到實際行的主鍵,用主鍵通過聚集索引去查詢最終的行.
如果不對sex性別字段加索引,那么查找過程就是直接全表掃描查詢到聚集索引定位到行,而不需要普通索引和聚集索引的切換,所以效率可能更高一點.
Https的SSL握手過程
Https協議由兩部分組成:http+ssl,即在http協議的基礎上增加了一層ssl的握手過程.
- 瀏覽器作為發起方,向網站發送一套瀏覽器自身支持的加密規則,比如客戶端支持的加密算法,Hash算法,ssl版本,以及一個28字節的隨機數client_random
- .網站選出一套加密算法和hash算法,生成一個服務端隨機數server_random並以證書的形式返回給客戶端瀏覽器,這個證書還包含網站地址、公鑰public_key、證書的頒發機構CA以及證書過期時間。
- .瀏覽器解析證書是否有效,如果無效則瀏覽器彈出提示框告警。如果證書有效,則根據server_random生成一個preMaster_secret和Master_secret[會話密鑰], master_secret 的生成需要 preMaster_key ,並需要 client_random 和 server_random 作為種子。瀏覽器向服務器發送經過public_key加密的preMaster_secret,以及對握手消息取hash值並使用master_secret進行加密發送給網站.[客戶端握手結束通知,表示客戶端的握手階段已經結束。這一項同時也是前面發送的所有內容的hash值,用來供服務器校驗]
- .服務器使用private_key 解密后得到preMaster_secret,再根據client_random 和 server_random 作為種子得到master_secret.然后使用master_secret解密握手消息並計算hash值,跟瀏覽器發送的hash值對比是否一致.
然后把握手消息通過master_secret進行對稱加密后返回給瀏覽器.以及把握手消息進行hash且master_secret加密后發給瀏覽器.[服務器握手結束通知,表示服務器的握手階段已經結束。這一項同時也是前面發送的所有內容的hash值,用來供客戶端校驗。] - .客戶端同樣可以使用master_secret進行解密得到握手消息.校驗握手消息的hash值是否跟服務器發送過來的hash值一致,一致則握手結束.通信開始
- .以后的通信都是通過master_secret+對稱加密算法的方式進行. 客戶端與服務器進入加密通信,就完全是使用普通的HTTP協議,只不過用"會話密鑰"加密內容。SSL握手過程中如果有任何錯誤,都會使加密連接斷開,從而阻止了隱私信息的傳輸
非對稱加密算法:RSA,DSA/DSS
對稱加密算法:AES,RC4,3DES
HASH算法:MD5,SHA1,SHA256
參考: http://www.ruanyifeng.com/blog/2014/02/ssl_tls.html
select和epoll的區別
1 select有最大並發數限制,默認最大文件句柄數1024,可修改。
epoll沒有最大文件句柄數限制,僅受系統中進程能打開的最大文件句柄限制。
2 select效率低,每次都要線性掃描其維護的fd_set集合。
epoll只在集合不為空才輪訓
3select存在內核空間和用戶空間的內存拷貝問題。
epoll中減少內存拷貝,mmap將用戶空間的一塊地址和內核空間的一塊地址同時映射到相同的一塊物理內存地址
NIO使用的多路復用器是epoll
Epoll導致的selector空輪詢
Java NIO Epoll 會導致 Selector 空輪詢,最終導致 CPU 100%
官方聲稱在 JDK 1.6 版本的 update18 修復了該問題,但是直到 JDK 1.7 版本該問題仍舊存在,只不過該 BUG 發生概率降低了一些而已,它並沒有得到根本性解決
Netty的解決方案:
對 Selector 的 select 操作周期進行統計,每完成一次空的 select 操作進行一次計數,若在某個周期內連續發生 N 次空輪詢,則判斷觸發了 Epoll 死循環 Bug。
然后,Netty 重建 Selector 來解決。判斷是否是其他線程發起的重建請求,若不是則將原 SocketChannel 從舊的 Selector 上取消注冊,然后重新注冊到新的 Selector 上,最后將原來的 Selector 關閉。
正排索引和倒排索引
正排索引
也叫正向索引,正排表是以document文檔的ID為關鍵字,記錄了document文檔中所有的關鍵字keyword的信息,所以在查找某個keyword的時候,會掃描正排表中每個document文檔,直到查出所有包含keyword的文檔。
圖示:
倒排索引
也叫反向索引,倒排表示以keyword關鍵字建立索引,關鍵詞keyword所對應的的表項記錄了出現這個keyword的所有文檔。表項包含了該文檔的ID和在該文檔中出現的位置情況。
倒排表一次可以查出keyword關鍵字對應的所有文檔,效率高於正排表。
正排索引是從文檔到關鍵字的映射(已知文檔求關鍵字)
倒排索引是從關鍵字到文檔的映射(已知關鍵字求文檔)
本人到目前為止接觸java有七年多的時間,越來越覺得語言的工具化,以工具化的角度去看待自己的技術,上邊這些面試題目雖然在實際中都基本上會被問到,但是不應該投機取巧,還是少一些功利化的東西,功利化作為內驅沒錯,但是學習技術還是得靜下心,沉住氣,保持耐心.真正的實力絕對不是靠這些面試題達成的,絕對是日復一日的堅持和忍耐。