Java的面向?qū)ο缶幊?/a>">Java的面向?qū)ο缶幊?/a>
769
2025-04-01
多線程并發(fā)拓展
(1)死鎖
public class DeadLockTest implements Runnable {
private int flag ;
private static Object o1=new Object();
private static Object o2=new Object();
public DeadLockTest(int flag) {
this.flag = flag;
}
@Override
public void run() {
if(flag == 0){
synchronized (o2){
try {
Thread.sleep(500);
System.out.println("Thread1開(kāi)始");
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o1){
System.out.println("Thread1結(jié)束!");
}
}
}
if(flag ==1){
synchronized (o1){
try {
Thread.sleep(500);
System.out.println("Thread2開(kāi)始");
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o2){
System.out.println("Thread2結(jié)束!");
}
}
}
}
public static void main(String[] args){
new Thread(new DeadLockTest(0)).start();
new Thread(new DeadLockTest(1)).start();
}
}
死鎖問(wèn)題是多線程特有的問(wèn)題,它可以被認(rèn)為是線程間切換消耗系統(tǒng)性能的一種極端情況。在死鎖時(shí),線程間相互等待資源,而又不釋放自身的資源,導(dǎo)致無(wú)窮無(wú)盡的等待,其結(jié)果是系統(tǒng)任務(wù)永遠(yuǎn)無(wú)法執(zhí)行完成。死鎖問(wèn)題是在多線程開(kāi)發(fā)中應(yīng)該堅(jiān)決避免和杜絕的問(wèn)題。
一般來(lái)說(shuō),要出現(xiàn)死鎖問(wèn)題需要滿足以下條件:
1. 互斥條件:一個(gè)資源每次只能被一個(gè)線程使用。
2. 請(qǐng)求與保持條件:一個(gè)進(jìn)程因請(qǐng)求資源而阻塞時(shí),對(duì)已獲得的資源保持不放。
3. 不剝奪條件:進(jìn)程已獲得的資源,在未使用完之前,不能強(qiáng)行剝奪。
4. 循環(huán)等待條件:若干進(jìn)程之間形成一種頭尾相接的循環(huán)等待資源關(guān)系。
只要破壞死鎖 4 個(gè)必要條件之一中的任何一個(gè),死鎖問(wèn)題就能被解決。
死鎖解決方案
死鎖是由四個(gè)必要條件導(dǎo)致的,所以一般來(lái)說(shuō),只要破壞這四個(gè)必要條件中的一個(gè)條件,死鎖情況就應(yīng)該不會(huì)發(fā)生。
如果想要打破互斥條件,我們需要允許進(jìn)程同時(shí)訪問(wèn)某些資源,這種方法受制于實(shí)際場(chǎng)景,不太容易實(shí)現(xiàn)條件;
打破不可搶占條件,這樣需要允許進(jìn)程強(qiáng)行從占有者那里奪取某些資源,或者簡(jiǎn)單一點(diǎn)理解,占有資源的進(jìn)程不能再申請(qǐng)占有其他資源,必須釋放手上的資源之后才能發(fā)起申請(qǐng),這個(gè)其實(shí)也很難找到適用場(chǎng)景;
進(jìn)程在運(yùn)行前申請(qǐng)得到所有的資源,否則該進(jìn)程不能進(jìn)入準(zhǔn)備執(zhí)行狀態(tài)。這個(gè)方法看似有點(diǎn)用處,但是它的缺點(diǎn)是可能導(dǎo)致資源利用率和進(jìn)程并發(fā)性降低;
避免出現(xiàn)資源申請(qǐng)環(huán)路,即對(duì)資源事先分類編號(hào),按號(hào)分配。這種方式可以有效提高資源的利用率和系統(tǒng)吞吐量,但是增加了系統(tǒng)開(kāi)銷,增大了進(jìn)程對(duì)資源的占用時(shí)間。
如果我們?cè)谒梨i檢查時(shí)發(fā)現(xiàn)了死鎖情況,那么就要努力消除死鎖,使系統(tǒng)從死鎖狀態(tài)中恢復(fù)過(guò)來(lái)。消除死鎖的幾種方式:
1. 最簡(jiǎn)單、最常用的方法就是進(jìn)行系統(tǒng)的重新啟動(dòng),不過(guò)這種方法代價(jià)很大,它意味著在這之前所有的進(jìn)程已經(jīng)完成的計(jì)算工作都將付之東流,包括參與死鎖的那些進(jìn)程,以及未參與死鎖的進(jìn)程;
2. 撤消進(jìn)程,剝奪資源。終止參與死鎖的進(jìn)程,收回它們占有的資源,從而解除死鎖。這時(shí)又分兩種情況:一次性撤消參與死鎖的全部進(jìn)程,剝奪全部資源;或者逐步撤消參與死鎖的進(jìn)程,逐步收回死鎖進(jìn)程占有的資源。一般來(lái)說(shuō),選擇逐步撤消的進(jìn)程時(shí)要按照一定的原則進(jìn)行,目的是撤消那些代價(jià)最小的進(jìn)程,比如按進(jìn)程的優(yōu)先級(jí)確定進(jìn)程的代價(jià);考慮進(jìn)程運(yùn)行時(shí)的代價(jià)和與此進(jìn)程相關(guān)的外部作業(yè)的代價(jià)等因素;
3. 進(jìn)程回退策略,即讓參與死鎖的進(jìn)程回退到?jīng)]有發(fā)生死鎖前某一點(diǎn)處,并由此點(diǎn)處繼續(xù)執(zhí)行,以求再次執(zhí)行時(shí)不再發(fā)生死鎖。雖然這是個(gè)較理想的辦法,但是操作起來(lái)系統(tǒng)開(kāi)銷極大,要有堆棧這樣的機(jī)構(gòu)記錄進(jìn)程的每一步變化,以便今后的回退,有時(shí)這是無(wú)法做到的。
其實(shí)即便是商業(yè)產(chǎn)品,依然會(huì)有很多死鎖情況的發(fā)生,例如 MySQL 數(shù)據(jù)庫(kù),它也經(jīng)常容易出現(xiàn)死鎖案例。
(2)多線程并發(fā)最佳實(shí)踐
使用本地變量
使用不可變類
最小化鎖的作用域范圍s=1/(1-a+a/n)? (a 并行計(jì)算部分所占比例,n 并行處理節(jié)點(diǎn)個(gè)數(shù),S 加速比)
使用線程池Executor ,而不是直接new Thread執(zhí)行
寧可使用同步也不要使用線程的wait和notiyf
使用BlockingQueue 實(shí)現(xiàn)生產(chǎn)-消費(fèi)模式
使用并發(fā)集合而不是加了鎖的同步集合
使用Semaphore創(chuàng)建有界的訪問(wèn)
寧可使用同步代碼塊,也不適用同步的方法
避免使用靜態(tài)變量
(3)高并發(fā)處理思路
擴(kuò)容
垂直 擴(kuò)容 :(縱向擴(kuò)展)提高系統(tǒng)的 部件能力
水平擴(kuò)容:(橫向擴(kuò)容):增加更多系統(tǒng)成員來(lái)實(shí)現(xiàn)
數(shù)據(jù)庫(kù)擴(kuò)容:
讀操作擴(kuò)展:memcache,redis,cdn等緩存
寫(xiě)操作擴(kuò)展:Cassandra,Hbase等
緩存:
緩存命中率的影響因素: 業(yè)務(wù)場(chǎng)景合業(yè)務(wù)需求,緩存的設(shè)計(jì)(粒度和策略),緩存容量和基礎(chǔ)設(shè)施
本地緩存:編程實(shí)現(xiàn)(成員變量、局部變量、靜態(tài)變量)Guava Cache
分布式緩存:? Memcache 、Redis
高并發(fā)場(chǎng)景下緩存常見(jiàn)問(wèn)題:
1、緩存一致性問(wèn)題
當(dāng)數(shù)據(jù)時(shí)效性要求很高時(shí),需要保證緩存中的數(shù)據(jù)與數(shù)據(jù)庫(kù)中的保持一致,而且需要保證緩存節(jié)點(diǎn)和副本中的數(shù)據(jù)也保持一致,不能出現(xiàn)差異現(xiàn)象。這就比較依賴緩存的過(guò)期和更新策略。一般會(huì)在數(shù)據(jù)發(fā)生更改的時(shí),主動(dòng)更新緩存中的數(shù)據(jù)或者移除對(duì)應(yīng)的緩存。
2、緩存并發(fā)問(wèn)題
緩存過(guò)期后將嘗試從后端數(shù)據(jù)庫(kù)獲取數(shù)據(jù),這是一個(gè)看似合理的流程。但是,在高并發(fā)場(chǎng)景下,有可能多個(gè)請(qǐng)求并發(fā)的去從數(shù)據(jù)庫(kù)獲取數(shù)據(jù),對(duì)后端數(shù)據(jù)庫(kù)造成極大的沖擊,甚至導(dǎo)致 “雪崩”現(xiàn)象。此外,當(dāng)某個(gè)緩存key在被更新時(shí),同時(shí)也可能被大量請(qǐng)求在獲取,這也會(huì)導(dǎo)致一致性的問(wèn)題。那如何避免類似問(wèn)題呢?我們會(huì)想到類似“鎖”的機(jī)制,在緩存更新或者過(guò)期的情況下,先嘗試獲取到鎖,當(dāng)更新或者從數(shù)據(jù)庫(kù)獲取完成后再釋放鎖,其他的請(qǐng)求只需要犧牲一定的等待時(shí)間,即可直接從緩存中繼續(xù)獲取數(shù)據(jù)。
3、緩存穿透問(wèn)題
緩存穿透在有些地方也稱為“擊穿”。很多朋友對(duì)緩存穿透的理解是:由于緩存故障或者緩存過(guò)期導(dǎo)致大量請(qǐng)求穿透到后端數(shù)據(jù)庫(kù)服務(wù)器,從而對(duì)數(shù)據(jù)庫(kù)造成巨大沖擊。
這其實(shí)是一種誤解。真正的緩存穿透應(yīng)該是這樣的:
在高并發(fā)場(chǎng)景下,如果某一個(gè)key被高并發(fā)訪問(wèn),沒(méi)有被命中,出于對(duì)容錯(cuò)性考慮,會(huì)嘗試去從后端數(shù)據(jù)庫(kù)中獲取,從而導(dǎo)致了大量請(qǐng)求達(dá)到數(shù)據(jù)庫(kù),而當(dāng)該key對(duì)應(yīng)的數(shù)據(jù)本身就是空的情況下,這就導(dǎo)致數(shù)據(jù)庫(kù)中并發(fā)的去執(zhí)行了很多不必要的查詢操作,從而導(dǎo)致巨大沖擊和壓力。
可以通過(guò)下面的幾種常用方式來(lái)避免緩存?zhèn)鹘y(tǒng)問(wèn)題:
緩存空對(duì)象
對(duì)查詢結(jié)果為空的對(duì)象也進(jìn)行緩存,如果是集合,可以緩存一個(gè)空的集合(非null),如果是緩存單個(gè)對(duì)象,可以通過(guò)字段標(biāo)識(shí)來(lái)區(qū)分。這樣避免請(qǐng)求穿透到后端數(shù)據(jù)庫(kù)。同時(shí),也需要保證緩存數(shù)據(jù)的時(shí)效性。這種方式實(shí)現(xiàn)起來(lái)成本較低,比較適合命中不高,但可能被頻繁更新的數(shù)據(jù)。
單獨(dú)過(guò)濾處理
對(duì)所有可能對(duì)應(yīng)數(shù)據(jù)為空的key進(jìn)行統(tǒng)一的存放,并在請(qǐng)求前做攔截,這樣避免請(qǐng)求穿透到后端數(shù)據(jù)庫(kù)。這種方式實(shí)現(xiàn)起來(lái)相對(duì)復(fù)雜,比較適合命中不高,但是更新不頻繁的數(shù)據(jù)。
3、緩存顛簸問(wèn)題
緩存的顛簸問(wèn)題,有些地方可能被成為“緩存抖動(dòng)”,可以看做是一種比“雪崩”更輕微的故障,但是也會(huì)在一段時(shí)間內(nèi)對(duì)系統(tǒng)造成沖擊和性能影響。一般是由于緩存節(jié)點(diǎn)故障導(dǎo)致。業(yè)內(nèi)推薦的做法是通過(guò)一致性Hash算法來(lái)解決。
4、緩存的雪崩現(xiàn)象
緩存雪崩就是指由于緩存的原因,導(dǎo)致大量請(qǐng)求到達(dá)后端數(shù)據(jù)庫(kù),從而導(dǎo)致數(shù)據(jù)庫(kù)崩潰,整個(gè)系統(tǒng)崩潰,發(fā)生災(zāi)難。導(dǎo)致這種現(xiàn)象的原因有很多種,上面提到的“緩存并發(fā)”,“緩存穿透”,“緩存顛簸”等問(wèn)題,其實(shí)都可能會(huì)導(dǎo)致緩存雪崩現(xiàn)象發(fā)生。這些問(wèn)題也可能會(huì)被惡意攻擊者所利用。還有一種情況,例如某個(gè)時(shí)間點(diǎn)內(nèi),系統(tǒng)預(yù)加載的緩存周期性集中失效了,也可能會(huì)導(dǎo)致雪崩。為了避免這種周期性失效,可以通過(guò)設(shè)置不同的過(guò)期時(shí)間,來(lái)錯(cuò)開(kāi)緩存過(guò)期,從而避免緩存集中失效。
從應(yīng)用架構(gòu)角度,我們可以通過(guò)限流、降級(jí)、熔斷等手段來(lái)降低影響,也可以通過(guò)多級(jí)緩存來(lái)避免這種災(zāi)難。
此外,從整個(gè)研發(fā)體系流程的角度,應(yīng)該加強(qiáng)壓力測(cè)試,盡量模擬真實(shí)場(chǎng)景,盡早的暴露問(wèn)題從而防范。
5、緩存無(wú)底洞現(xiàn)象
該問(wèn)題由 facebook 的工作人員提出的, facebook 在 2010 年左右,memcached 節(jié)點(diǎn)就已經(jīng)達(dá)3000 個(gè),緩存數(shù)千 G 內(nèi)容。
他們發(fā)現(xiàn)了一個(gè)問(wèn)題---memcached 連接頻率,效率下降了,于是加 memcached 節(jié)點(diǎn),
添加了后,發(fā)現(xiàn)因?yàn)檫B接頻率導(dǎo)致的問(wèn)題,仍然存在,并沒(méi)有好轉(zhuǎn),稱之為”無(wú)底洞現(xiàn)象”。
目前主流的數(shù)據(jù)庫(kù)、緩存、Nosql、搜索中間件等技術(shù)棧中,都支持“分片”技術(shù),來(lái)滿足“高性能、高并發(fā)、高可用、可擴(kuò)展”等要求。有些是在client端通過(guò)Hash取模(或一致性Hash)將值映射到不同的實(shí)例上,有些是在client端通過(guò)范圍取值的方式映射的。當(dāng)然,也有些是在服務(wù)端進(jìn)行的。但是,每一次操作都可能需要和不同節(jié)點(diǎn)進(jìn)行網(wǎng)絡(luò)通信來(lái)完成,實(shí)例節(jié)點(diǎn)越多,則開(kāi)銷會(huì)越大,對(duì)性能影響就越大。
主要可以從如下幾個(gè)方面避免和優(yōu)化:
數(shù)據(jù)分布方式
有些業(yè)務(wù)數(shù)據(jù)可能適合Hash分布,而有些業(yè)務(wù)適合采用范圍分布,這樣能夠從一定程度避免網(wǎng)絡(luò)IO的開(kāi)銷。
IO優(yōu)化
可以充分利用連接池,NIO等技術(shù)來(lái)盡可能降低連接開(kāi)銷,增強(qiáng)并發(fā)連接能力。
數(shù)據(jù)訪問(wèn)方式
一次性獲取大的數(shù)據(jù)集,會(huì)比分多次去獲取小數(shù)據(jù)集的網(wǎng)絡(luò)IO開(kāi)銷更小。
當(dāng)然,緩存無(wú)底洞現(xiàn)象并不常見(jiàn)。在絕大多數(shù)的公司里可能根本不會(huì)遇到。
消息隊(duì)列
流程A在處理時(shí)沒(méi)有在當(dāng)前線程同步的處理完而是直接發(fā)送了一條消息A1到隊(duì)列里,然后消息隊(duì)列過(guò)了一段時(shí)間(可能是幾毫秒 幾秒 幾分鐘)這個(gè)消息開(kāi)始被處理,消息處理的過(guò)程就相當(dāng)于流程A被處理;當(dāng)然這只是一個(gè)簡(jiǎn)單的模型下面我們套用實(shí)際的場(chǎng)景來(lái)看一下,比如下單成功后發(fā)送短信提醒;如果沒(méi)有消息隊(duì)列我們會(huì)選擇同步調(diào)用發(fā)短信的接口并等待短信發(fā)送成功,正常情況下這么做是沒(méi)有問(wèn)題的但是如果發(fā)短信的時(shí)候短信接口出問(wèn)題了或者說(shuō)調(diào)用超時(shí)了等意外情況,這個(gè)時(shí)候我們就需要設(shè)計(jì)對(duì)應(yīng)的方案來(lái)解決前提是這些方案的設(shè)計(jì)會(huì)比較復(fù)雜;
但是當(dāng)我們使用消息隊(duì)列以后這個(gè)事情就會(huì)變得非常簡(jiǎn)單,使用消息隊(duì)列以后有如下好處:
① 實(shí)現(xiàn)了異步解耦
② 設(shè)計(jì)變的更加簡(jiǎn)單了
③ 保證了數(shù)據(jù)的最終一致性;
④ 提高效率;
消息隊(duì)列特性:
1 與業(yè)務(wù)無(wú)關(guān) : 只做消息分發(fā)
2 FIFO : 先投遞先到達(dá)
3 容災(zāi) : 節(jié)點(diǎn)的動(dòng)態(tài)增刪和消息的持久化
4 性能 : 吞吐量提升,系統(tǒng)內(nèi)部通信效率提高
為什么需要消息隊(duì)列?
生產(chǎn)和消費(fèi)的速度或穩(wěn)定性等因素不一致;
消息隊(duì)列的好處
1 業(yè)務(wù)解耦
2 最終一致性 : 用記錄和補(bǔ)償?shù)姆绞絹?lái)處理,在做所有的不確定事情之前先記錄然后再去做,它的結(jié)果通常分為三種成功失敗或者不確定(比如說(shuō)超時(shí)等);如果是成功我們就可以清楚掉記錄,如果是失敗或者不確定我們可以通過(guò)定時(shí)任務(wù)將所有事情重新做一遍直到成功為止;
3 廣播 : 如果沒(méi)有消息隊(duì)列每一個(gè)新的業(yè)務(wù)方介入都需要聯(lián)調(diào)一次接口,使用消息隊(duì)列只需要關(guān)心消息是否送達(dá)到消息隊(duì)列,新接入的接口訂閱相關(guān)的消息自己做處理就可以了;
4 錯(cuò)峰與流控 : 上下游對(duì)于事情的處理是不同的,比如WEB前端每秒承受上千萬(wàn)的請(qǐng)求都是可以的但是數(shù)據(jù)庫(kù)的處理卻非常有限;迫于成本的壓力我們不能要求數(shù)據(jù)庫(kù)的機(jī)器數(shù)量與前端資源一樣;這樣的問(wèn)題同樣存在于系統(tǒng)與系統(tǒng)之間,比如短信系統(tǒng)的速度卡在網(wǎng)關(guān)上邊它與前端的并發(fā)量不是一個(gè)數(shù)量級(jí)的,用戶玩幾秒種收到短信也是可以的;針對(duì)于這樣的場(chǎng)景如果沒(méi)有消息隊(duì)列也能實(shí)現(xiàn)但是系統(tǒng)的復(fù)雜度非常的高;
常用的消息隊(duì)列介紹
Kafka
Kafka是Apache下的一個(gè)子項(xiàng)目,是一個(gè)高性能 跨語(yǔ)言 分布式發(fā)布訂閱消息隊(duì)列系統(tǒng);
1 Kafka的特性
① 快速持久化 : 它可以在o1的系統(tǒng)開(kāi)銷下實(shí)現(xiàn)消息的持久化;
② 高吞吐 : 在一臺(tái)普通的服務(wù)器上就可以達(dá)到10w/秒的吞吐速率;
③ 天生的分布式 : 所有組件天生支持分布式且自動(dòng)實(shí)現(xiàn)負(fù)載均衡;
2 Kafka基礎(chǔ)定義
① Broker : Kafka集群包含一個(gè)或多個(gè)服務(wù)器,這個(gè)服務(wù)器就被稱為Producer;
② Topic : 指每條發(fā)布到Kafka的消息都有一個(gè)類別,這個(gè)類別叫做Topic;物理上不同Topic的消息是分開(kāi)存儲(chǔ)的,邏輯上一個(gè)Topic消息雖然保存在一個(gè)或者多Producer上但是用戶只需指定消息的Topic就可以生產(chǎn)或者消費(fèi)數(shù)據(jù)而不用關(guān)心數(shù)據(jù)存在哪里;
③ Partition : 是物理上的概念,每個(gè)Topic包含一個(gè)或者多個(gè)Partition;
④ Producer : 負(fù)責(zé)發(fā)布消息到Kafka的Broker里邊;
⑤ consumer : 消息消費(fèi)者,向Kafka broker讀取消息的客戶端;
⑥ Consumer Group : 每個(gè)Consumer屬于一個(gè)特定的Consumer Group(可為每個(gè)Consumer指定group name,若不指定group name則屬于默認(rèn)的group);
RabbitMQ
RabbitMQ是流行的開(kāi)源消息隊(duì)列系統(tǒng),用erlang語(yǔ)言開(kāi)發(fā)。RabbitMQ是AMQP(高級(jí)消息隊(duì)列協(xié)議)的標(biāo)準(zhǔn)實(shí)現(xiàn);
1 RabbitMQ的使用過(guò)程
(1)客戶端連接到消息隊(duì)列服務(wù)器,打開(kāi)一個(gè)channel。
(2)客戶端聲明一個(gè)exchange,并設(shè)置相關(guān)屬性。
(3)客戶端聲明一個(gè)queue,并設(shè)置相關(guān)屬性。
(4)客戶端使用routing key,在exchange和queue之間建立好綁定關(guān)系。
(5)客戶端投遞消息到exchange。
2 RabbitMQ的概念
Broker:簡(jiǎn)單來(lái)說(shuō)就是消息隊(duì)列服務(wù)器實(shí)體。
Exchange:消息交換機(jī),它指定消息按什么規(guī)則,路由到哪個(gè)隊(duì)列。
Queue:消息隊(duì)列載體,每個(gè)消息都會(huì)被投入到一個(gè)或多個(gè)隊(duì)列。
Binding:綁定,它的作用就是把exchange和queue按照路由規(guī)則綁定起來(lái)。
Routing Key:路由關(guān)鍵字,exchange根據(jù)這個(gè)關(guān)鍵字進(jìn)行消息投遞。
vhost:虛擬主機(jī),一個(gè)broker里可以開(kāi)設(shè)多個(gè)vhost,用作不同用戶的權(quán)限分離。
producer:消息生產(chǎn)者,就是投遞消息的程序。
consumer:消息消費(fèi)者,就是接受消息的程序。
channel:消息通道,在客戶端的每個(gè)連接里,可建立多個(gè)channel,每個(gè)channel代表一個(gè)會(huì)話任務(wù)。
(4)高并發(fā)應(yīng)用拆分
比如一個(gè)股票系統(tǒng)有用戶信息、開(kāi)戶、股票行情、交易、訂單等,拆分后如下圖所示:
原則
業(yè)務(wù)優(yōu)先
每個(gè)系統(tǒng)都會(huì)有多個(gè)模塊,每個(gè)模塊又有多個(gè)業(yè)務(wù)功能;按照業(yè)務(wù)邊界進(jìn)行切割,再對(duì)模塊進(jìn)行拆分。
循序漸進(jìn)
邊拆分邊測(cè)試,保證系統(tǒng)的正常運(yùn)行。
兼顧技術(shù):重構(gòu)、分層
不能為了分布式而分布式,拆分過(guò)程不僅是業(yè)務(wù)梳理也是代碼重構(gòu)的過(guò)程,根據(jù)技術(shù)進(jìn)行分層來(lái)分配工作,ui對(duì)用戶體驗(yàn),熟悉C和C++對(duì)服務(wù)器,熟悉數(shù)據(jù)庫(kù)的對(duì)數(shù)據(jù)庫(kù),做到術(shù)業(yè)有專攻,合適的人去做合適的事情。
可靠測(cè)試
測(cè)試完畢后,才可進(jìn)行下一步,每一步都要有足夠的測(cè)試才可進(jìn)行下一步,避免小錯(cuò)誤引起蝴蝶效應(yīng)。
思考
應(yīng)用之間通信: RPC ( dubbo等)、消息隊(duì)列
消息隊(duì)列通常用于傳輸數(shù)據(jù)包小但是數(shù)據(jù)量大,對(duì)實(shí)時(shí)性要求低的場(chǎng)景,比如下單后短信通知客戶。而采用RPC要求實(shí)時(shí)性高,這里通常不會(huì)http或者service服務(wù),原因是使用PRC調(diào)用service方法無(wú)感知,在配置好后和本地方法很像。
應(yīng)用之間數(shù)據(jù)庫(kù)設(shè)計(jì):每個(gè)應(yīng)用都有獨(dú)立的數(shù)據(jù)庫(kù)
通常情況下,每個(gè)應(yīng)用都有自己獨(dú)立的數(shù)據(jù)庫(kù),如果共同使用的信息,可以考慮放在common中使用。
避免事務(wù)操作跨應(yīng)用
分布式事務(wù)是一個(gè)很消耗資源的問(wèn)題,應(yīng)用之間服務(wù)分開(kāi)開(kāi)發(fā),能夠保持相互獨(dú)立。
框架
服務(wù)化 Dubbo
微服務(wù)Spring Cloud
微服務(wù):獨(dú)立的服務(wù)共同組成一個(gè)系統(tǒng)
要實(shí)踐微服務(wù)要解決4個(gè)問(wèn)題:
客戶端如何訪問(wèn)這些服務(wù)
API Gateway提供統(tǒng)一的服務(wù)入口,對(duì)前臺(tái)透明,同時(shí)可以聚合后臺(tái)的服務(wù),提供安全過(guò)濾流控等api的管理功能
服務(wù)之間是如何通信的
異步的話使用消息隊(duì)列,同步調(diào)用使用REST或者是RPC,Rest可以使用springboot,RPC通常使用Dubbo
同步調(diào)用一致性強(qiáng)但是出現(xiàn)調(diào)用問(wèn)題,REST一般基于http實(shí)現(xiàn),能夠跨客戶端,同時(shí)對(duì)客戶端沒(méi)有更多的要求。
RPC的傳輸協(xié)議更高效,安全也更加可控。特別是在一個(gè)公司內(nèi)部如果有統(tǒng)一的開(kāi)發(fā)規(guī)范和統(tǒng)一的框架,它的開(kāi)發(fā)效率會(huì)更加明顯。
而異步消息在分布式系統(tǒng)中有特別廣泛的應(yīng)用,它既能減少調(diào)用服務(wù)之間的耦合,又能成為調(diào)用之間的緩沖,確保消息積壓不會(huì)沖垮被調(diào)用方。
同時(shí)保證調(diào)用方的用戶的體驗(yàn),繼續(xù)干自己的活。付出的代價(jià)是一致性的減慢,需要接受數(shù)據(jù)的最終一致性
如何實(shí)現(xiàn)如此多服務(wù)
在微服務(wù)架構(gòu)中一般每一服務(wù)都會(huì)拷貝進(jìn)行負(fù)載均衡,服務(wù)如何相互感知,如何相互管理,這就是服務(wù)發(fā)現(xiàn)的問(wèn)題了,一般都是進(jìn)行服務(wù)注冊(cè)信息的分布式管理。
服務(wù)掛了該如何解決,有什么備份方案和應(yīng)急處理機(jī)制
分布式最大的特性就是網(wǎng)絡(luò)是不可靠的,當(dāng)系統(tǒng)是由一系列的調(diào)用鏈組成的時(shí)候,其中任何一個(gè)出問(wèn)題都不至于影響到整個(gè)鏈路。
相應(yīng)的手段有:重試機(jī)制、應(yīng)用的限流、熔斷機(jī)制、負(fù)載均衡、系統(tǒng)降級(jí)
(5) 應(yīng)用限流
限流就是通過(guò)對(duì)并發(fā)訪問(wèn)/請(qǐng)求進(jìn)行限速或一個(gè)時(shí)間窗口內(nèi)的請(qǐng)求進(jìn)行限速,從而達(dá)到保護(hù)系統(tǒng)的目的。一般系統(tǒng)可以通過(guò)壓測(cè)來(lái)預(yù)估能處理的峰值,一旦達(dá)到設(shè)定的峰值閥值,則可以拒絕服務(wù)(定向錯(cuò)誤頁(yè)或告知資源沒(méi)有了)、排隊(duì)或等待(例如:秒殺、評(píng)論、下單)、降級(jí)(返回默認(rèn)數(shù)據(jù))
限流不能亂用,否則正常流量會(huì)出現(xiàn)一些奇怪的問(wèn)題,從而導(dǎo)致用戶抱怨。
假設(shè)有130W到140W的數(shù)據(jù)插入到數(shù)據(jù)庫(kù)中,如果沒(méi)有做限流,數(shù)據(jù)庫(kù)的主庫(kù)會(huì)突然接收到130w的插入操作
首先是網(wǎng)絡(luò)上的開(kāi)銷,很可能直接把帶寬占滿,導(dǎo)致其他請(qǐng)求無(wú)法正常傳輸和處理,其次會(huì)是數(shù)據(jù)庫(kù)的負(fù)載突然增高,導(dǎo)致無(wú)法處理某些數(shù)據(jù)庫(kù)的操作,也有可能數(shù)據(jù)庫(kù)沒(méi)有足夠的連接導(dǎo)致某些數(shù)據(jù)庫(kù)插入查詢失??;
還有一點(diǎn)就是現(xiàn)在數(shù)據(jù)庫(kù)都做了主從設(shè)計(jì),主數(shù)據(jù)庫(kù)的數(shù)據(jù)還要同步給從庫(kù),這時(shí)瞬間插入了大量的數(shù)據(jù),會(huì)帶來(lái)從庫(kù)和主庫(kù)的延遲特別大,這時(shí)從庫(kù)查詢不準(zhǔn)確的概率也會(huì)跟著提升。
如果我們放慢插入數(shù)據(jù)庫(kù)的速度,這時(shí)插入數(shù)據(jù)庫(kù)主庫(kù)的速率會(huì)很正常,同步到從庫(kù)也很正常。網(wǎng)絡(luò)消耗也可以接收不會(huì)影響其他服務(wù)。
1.計(jì)數(shù)器法
有時(shí)我們還會(huì)使用計(jì)數(shù)器來(lái)進(jìn)行限流,主要用來(lái)限制一定時(shí)間內(nèi)的總并發(fā)數(shù),比如數(shù)據(jù)庫(kù)連接池、線程池、秒殺的并發(fā)數(shù);計(jì)數(shù)器限流只要一定時(shí)間內(nèi)的總請(qǐng)求數(shù)超過(guò)設(shè)定的閥值則進(jìn)行限流,是一種簡(jiǎn)單粗暴的總數(shù)量限流,而不是平均速率限流。
這個(gè)方法有一個(gè)致命問(wèn)題:臨界問(wèn)題——當(dāng)遇到惡意請(qǐng)求,在0:59時(shí),瞬間請(qǐng)求100次,并且在1:00請(qǐng)求100次,那么這個(gè)用戶在1秒內(nèi)請(qǐng)求了200次,用戶可以在重置節(jié)點(diǎn)突發(fā)請(qǐng)求,而瞬間超過(guò)我們?cè)O(shè)置的速率限制,用戶可能通過(guò)算法漏洞擊垮我們的應(yīng)用。
2.滑動(dòng)窗口算法
在上圖中,整個(gè)紅色矩形框是一個(gè)時(shí)間窗口,在我們的例子中,一個(gè)時(shí)間窗口就是1分鐘,然后我們將時(shí)間窗口進(jìn)行劃分,如上圖我們把滑動(dòng)窗口
劃分為6格,所以每一格代表10秒,每超過(guò)10秒,我們的時(shí)間窗口就會(huì)向右滑動(dòng)一格,每一格都有自己獨(dú)立的計(jì)數(shù)器,例如:一個(gè)請(qǐng)求在0:35到達(dá),
那么0:30到0:39的計(jì)數(shù)器會(huì)+1,那么滑動(dòng)窗口是怎么解決臨界點(diǎn)的問(wèn)題呢?如上圖,0:59到達(dá)的100個(gè)請(qǐng)求會(huì)在灰色區(qū)域格子中,而1:00到達(dá)的請(qǐng)求
會(huì)在紅色格子中,窗口會(huì)向右滑動(dòng)一格,那么此時(shí)間窗口內(nèi)的總請(qǐng)求數(shù)共200個(gè),超過(guò)了限定的100,所以此時(shí)能夠檢測(cè)出來(lái)觸發(fā)了限流。
回頭看看計(jì)數(shù)器算法,會(huì)發(fā)現(xiàn),其實(shí)計(jì)數(shù)器算法就是窗口滑動(dòng)算法,只不過(guò)計(jì)數(shù)器算法沒(méi)有對(duì)時(shí)間窗口進(jìn)行劃分,所以是一格。
由此可見(jiàn),當(dāng)滑動(dòng)窗口的格子劃分越多,限流的統(tǒng)計(jì)就會(huì)越精確。
3.漏銅算法
這個(gè)算法很簡(jiǎn)單。首先,我們有一個(gè)固定容量的桶,有水進(jìn)來(lái),也有水出去。對(duì)于流進(jìn)來(lái)的水,我們無(wú)法預(yù)計(jì)共有多少水流進(jìn)來(lái),也無(wú)法預(yù)計(jì)流水速度,但
對(duì)于流出去的水來(lái)說(shuō),這個(gè)桶可以固定水流的速率,而且當(dāng)桶滿的時(shí)候,多余的水會(huì)溢出來(lái)。
4.令牌桶算法
從上圖中可以看出,令牌算法有點(diǎn)復(fù)雜,桶里存放著令牌token。桶一開(kāi)始是空的,token以固定的速率r往桶里面填充,直到達(dá)到桶的容量,多余的token會(huì)
被丟棄。每當(dāng)一個(gè)請(qǐng)求過(guò)來(lái)時(shí),就會(huì)嘗試著移除一個(gè)token,如果沒(méi)有token,請(qǐng)求無(wú)法通過(guò)。
(6)服務(wù)降級(jí)與服務(wù)熔斷
服務(wù)降級(jí):
服務(wù)壓力劇增的時(shí)候根據(jù)當(dāng)前的業(yè)務(wù)情況及流量對(duì)一些服務(wù)和頁(yè)面有策略的降級(jí),以此環(huán)節(jié)服務(wù)器的壓力,以保證核心任務(wù)的進(jìn)行。
同時(shí)保證部分甚至大部分任務(wù)客戶能得到正確的相應(yīng)。也就是當(dāng)前的請(qǐng)求處理不了了或者出錯(cuò)了,給一個(gè)默認(rèn)的返回。
服務(wù)熔斷:
在股票市場(chǎng),熔斷這個(gè)詞大家都不陌生,是指當(dāng)股指波幅達(dá)到某個(gè)點(diǎn)后,交易所為控制風(fēng)險(xiǎn)采取的暫停交易措施。相應(yīng)的,服務(wù)熔斷一般是指軟件系統(tǒng)中,由于某些原因使得服務(wù)出現(xiàn)了過(guò)載現(xiàn)象,為防止造成整個(gè)系統(tǒng)故障,從而采用的一種保護(hù)措施,所以很多地方把熔斷亦稱為過(guò)載保護(hù)。
降級(jí)分類
降級(jí)按照是否自動(dòng)化可分為:自動(dòng)開(kāi)關(guān)降級(jí)和人工開(kāi)關(guān)降級(jí)。
降級(jí)按照功能可分為:讀服務(wù)降級(jí)、寫(xiě)服務(wù)降級(jí)。
降級(jí)按照處于的系統(tǒng)層次可分為:多級(jí)降級(jí)。
自動(dòng)降級(jí)分類
(1)、超時(shí)降級(jí):主要配置好超時(shí)時(shí)間和超時(shí)重試次數(shù)和機(jī)制,并使用異步機(jī)制探測(cè)回復(fù)情況
(2)、失敗次數(shù)降級(jí):主要是一些不穩(wěn)定的api,當(dāng)失敗調(diào)用次數(shù)達(dá)到一定閥值自動(dòng)降級(jí),同樣要使用異步機(jī)制探測(cè)回復(fù)情況
(3)、故障降級(jí):比如要調(diào)用的遠(yuǎn)程服務(wù)掛掉了(網(wǎng)絡(luò)故障、DNS故障、http服務(wù)返回錯(cuò)誤的狀態(tài)碼、rpc服務(wù)拋出異常),則可以直接降級(jí)。降級(jí)后的處理方案有:默認(rèn)值(比如庫(kù)存服務(wù)掛了,返回默認(rèn)現(xiàn)貨)、兜底數(shù)據(jù)(比如廣告掛了,返回提前準(zhǔn)備好的一些靜態(tài)頁(yè)面)、緩存(之前暫存的一些緩存數(shù)據(jù))
(4)、限流降級(jí)
當(dāng)我們?nèi)ッ霘⒒蛘邠屬?gòu)一些限購(gòu)商品時(shí),此時(shí)可能會(huì)因?yàn)樵L問(wèn)量太大而導(dǎo)致系統(tǒng)崩潰,此時(shí)開(kāi)發(fā)者會(huì)使用限流來(lái)進(jìn)行限制訪問(wèn)量,當(dāng)達(dá)到限流閥值,后續(xù)請(qǐng)求會(huì)被降級(jí);降級(jí)后的處理方案可以是:排隊(duì)頁(yè)面(將用戶導(dǎo)流到排隊(duì)頁(yè)面等一會(huì)重試)、無(wú)貨(直接告知用戶沒(méi)貨了)、錯(cuò)誤頁(yè)(如活動(dòng)太火爆了,稍后重試)。
服務(wù)熔斷和服務(wù)降級(jí)比較:
兩者其實(shí)從有些角度看是有一定的類似性的:
目的很一致,都是從可用性可靠性著想,為防止系統(tǒng)的整體緩慢甚至崩潰,采用的技術(shù)手段;
最終表現(xiàn)類似,對(duì)于兩者來(lái)說(shuō),最終讓用戶體驗(yàn)到的是某些功能暫時(shí)不可達(dá)或不可用;
粒度一般都是服務(wù)級(jí)別,當(dāng)然,業(yè)界也有不少更細(xì)粒度的做法,比如做到數(shù)據(jù)持久層(允許查詢,不允許增刪改);
自治性要求很高,熔斷模式一般都是服務(wù)基于策略的自動(dòng)觸發(fā),降級(jí)雖說(shuō)可人工干預(yù),但在微服務(wù)架構(gòu)下,完全靠人顯然不可能,開(kāi)關(guān)預(yù)置、配置中心都是必要手段;
而兩者的區(qū)別也是明顯的:
觸發(fā)原因不太一樣,服務(wù)熔斷一般是某個(gè)服務(wù)(下游服務(wù))故障引起,而服務(wù)降級(jí)一般是從整體負(fù)荷考慮;
管理目標(biāo)的層次不太一樣,熔斷其實(shí)是一個(gè)框架級(jí)的處理,每個(gè)微服務(wù)都需要(無(wú)層級(jí)之分),而降級(jí)一般需要對(duì)業(yè)務(wù)有層級(jí)之分(比如降級(jí)一般是從最外圍服務(wù)開(kāi)始)
實(shí)現(xiàn)方式不太一樣
服務(wù)降級(jí)要考慮的問(wèn)題:
1.核心和非核心服務(wù)
2.是否支持降級(jí),降級(jí)策略
3.業(yè)務(wù)放通的場(chǎng)景,策略
Hystrix,該庫(kù)旨在通過(guò)控制那些訪問(wèn)遠(yuǎn)程系統(tǒng)、服務(wù)和第三方庫(kù)的節(jié)點(diǎn),從而對(duì)延遲和故障提供更強(qiáng)大的容錯(cuò)能力。Hystrix具備擁有回退機(jī)制和斷路器功能的線程和信號(hào)隔離,請(qǐng)求緩存和請(qǐng)求打包(request collapsing,即自動(dòng)批處理,譯者注),以及監(jiān)控和配置等功能。
(7)數(shù)據(jù)庫(kù)分庫(kù)分表
數(shù)據(jù)庫(kù)的瓶頸
單個(gè)庫(kù)數(shù)據(jù)量太大;考慮多個(gè)庫(kù)解決問(wèn)題。
單個(gè)數(shù)據(jù)庫(kù)服務(wù)器壓力過(guò)大、讀寫(xiě)瓶頸;考慮多個(gè)庫(kù)、讀寫(xiě)分離解決問(wèn)題。
單個(gè)表數(shù)據(jù)量過(guò)大;考慮分表解決問(wèn)題。
數(shù)據(jù)庫(kù)切庫(kù)與分庫(kù)
對(duì)數(shù)據(jù)庫(kù)的操作中讀多寫(xiě)少,且讀操作占用系統(tǒng)資源多,耗時(shí)長(zhǎng),適用多個(gè)分庫(kù)進(jìn)行負(fù)載均衡。詳情查看: 自定義注解完成數(shù)據(jù)庫(kù)切庫(kù)
切庫(kù)的基礎(chǔ)及實(shí)際應(yīng)用中,隨著業(yè)務(wù)增加,并發(fā)增加,需做到讀寫(xiě)分離;
自定義注解完成數(shù)據(jù)庫(kù)切庫(kù)-代碼實(shí)現(xiàn);另一種方式是在業(yè)務(wù)中直接定義兩個(gè)數(shù)據(jù)庫(kù)鏈接:主庫(kù)連接和從庫(kù)連接;更新數(shù)據(jù)時(shí),讀取主庫(kù)連接,
支持多數(shù)據(jù)源:指一個(gè)項(xiàng)目里,同時(shí)可以訪問(wèn)多個(gè)不同的數(shù)據(jù)庫(kù)。
原理:?jiǎn)蝹€(gè)數(shù)據(jù)源在配置時(shí)會(huì)綁定一套mybatis配置,多個(gè)數(shù)據(jù)源時(shí),不同的數(shù)據(jù)源綁定不同的mybatis配置就可以了,簡(jiǎn)單的思路就是讓不同的數(shù)據(jù)源掃描不同的包,讓不同的包下的mapper對(duì)應(yīng)連接不同的數(shù)據(jù)源去處理邏輯。
數(shù)據(jù)庫(kù)分表
分表的類型
橫向(水平)分表(Horizontal Partitioning)
這種形式分區(qū)是對(duì)表的行進(jìn)行分割,通過(guò)這樣的方式不同分組里面的物理列分割的數(shù)據(jù)集得以組合,從而進(jìn)行個(gè)體分割(單分區(qū))或集體分割(1個(gè)或多個(gè)分區(qū))。所有在表中定義的列在每個(gè)數(shù)據(jù)集中都能找到,所以表的特性依然得以保持。
縱向(垂直)分表(Vertical Partitioning)
這種分割方式一般來(lái)說(shuō)是通過(guò)對(duì)表的垂直劃分來(lái)減少目標(biāo)表的寬度,使某些特定的列被劃分到特定的分區(qū),每個(gè)分區(qū)都包含了其中的列所對(duì)應(yīng)的行。
在數(shù)據(jù)庫(kù)供應(yīng)商開(kāi)始在他們的數(shù)據(jù)庫(kù)引擎中建立分區(qū)(主要是水平分區(qū))時(shí),DBA和建模者必須設(shè)計(jì)好表的物理分區(qū)結(jié)構(gòu),不要保存冗余的數(shù)據(jù)(不同表中同時(shí)都包含父表中的數(shù)據(jù))或相互聯(lián)結(jié)成一個(gè)邏輯父對(duì)象(通常是視圖)。這種做法會(huì)使水平分區(qū)的大部分功能失效,有時(shí)候也會(huì)對(duì)垂直分區(qū)產(chǎn)生影響。
分表的好處
性能的提升(Increased performance)- 在掃描操作中,如果MySQL的優(yōu)化器知道哪個(gè)分區(qū)中才包含特定查詢中需要的數(shù)據(jù),它就能直接去掃描那些分區(qū)的數(shù)據(jù),而不用浪費(fèi)很多時(shí)間掃描不需要的地方了。需要舉個(gè)例子?好啊,百萬(wàn)行的表劃分為10個(gè)分區(qū),每個(gè)分區(qū)就包含十萬(wàn)行數(shù)據(jù),那么查詢分區(qū)需要的時(shí)間僅僅是全表掃描的十分之一了,很明顯的對(duì)比。同時(shí)對(duì)十萬(wàn)行的表建立索引的速度也會(huì)比百萬(wàn)行的快得多得多。如果你能把這些分區(qū)建立在不同的磁盤上,這時(shí)候的I/O讀寫(xiě)速度就“不堪設(shè)想”;
對(duì)數(shù)據(jù)管理的簡(jiǎn)化(Simplified data management)- 分區(qū)技術(shù)可以讓DBA對(duì)數(shù)據(jù)的管理能力提升。通過(guò)優(yōu)良的分區(qū),DBA可以簡(jiǎn)化特定數(shù)據(jù)操作的執(zhí)行方式。例如:DBA在對(duì)某些分區(qū)的內(nèi)容進(jìn)行刪除的同時(shí)能保證余下的分區(qū)的數(shù)據(jù)完整性(這是跟對(duì)表的數(shù)據(jù)刪除這種大動(dòng)作做比較的)。
(8)高可用的一些手段
任務(wù)調(diào)度系統(tǒng)分布式:elastic-job組件 + zookeeper;
主備切換:Apache curator + zookeeper分布式鎖;
監(jiān)控報(bào)警機(jī)制。詳情參看: https://www.imooc.com/article/20891?業(yè)務(wù)相關(guān)監(jiān)控報(bào)警系統(tǒng)
Java 任務(wù)調(diào)度 多線程
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。