1. 程式人生 > >[Kafka設計解析]--(八)Exactly Once語義與事務機制原理

[Kafka設計解析]--(八)Exactly Once語義與事務機制原理

寫在前面的話

本文所有Kafka原理性的描述除特殊說明外均基於Kafka 1.0.0版本。

為什麼要提供事務機制

Kafka事務機制的實現主要是為了支援

  • Exactly Once即正好一次語義
  • 操作的原子性
  • 有狀態操作的可恢復性

Exactly Once

Kafka背景及架構介紹》一文中有說明Kafka在0.11.0.0之前的版本中只支援At Least OnceAt Most Once語義,尚不支援Exactly Once語義。

但是在很多要求嚴格的場景下,如使用Kafka處理交易資料,Exactly Once語義是必須的。我們可以通過讓下游系統具有冪等性來配合Kafka的At Least Once語義來間接實現Exactly Once

。但是:

  • 該方案要求下游系統支援冪等操作,限制了Kafka的適用場景
  • 實現門檻相對較高,需要使用者對Kafka的工作機制非常瞭解
  • 對於Kafka Stream而言,Kafka本身即是自己的下游系統,但Kafka在0.11.0.0版本之前不具有冪等傳送能力

因此,Kafka本身對Exactly Once語義的支援就非常必要。

操作原子性

操作的原子性是指,多個操作要麼全部成功要麼全部失敗,不存在部分成功部分失敗的可能。

實現原子性操作的意義在於:

  • 操作結果更可控,有助於提升資料一致性
  • 便於故障恢復。因為操作是原子的,從故障中恢復時只需要重試該操作(如果原操作失敗)或者直接跳過該操作(如果原操作成功),而不需要記錄中間狀態,更不需要針對中間狀態作特殊處理

實現事務機制的幾個階段

冪等性發送

上文提到,實現Exactly Once的一種方法是讓下游系統具有冪等處理特性,而在Kafka Stream中,Kafka Producer本身就是“下游”系統,因此如果能讓Producer具有冪等處理特性,那就可以讓Kafka Stream在一定程度上支援Exactly once語義。

為了實現Producer的冪等語義,Kafka引入了Producer ID(即PID)和Sequence Number。每個新的Producer在初始化的時候會被分配一個唯一的PID,該PID對使用者完全透明而不會暴露給使用者。

對於每個PID,該Producer傳送資料的每個<Topic, Partition>

都對應一個從0開始單調遞增的Sequence Number

類似地,Broker端也會為每個<PID, Topic, Partition>維護一個序號,並且每次Commit一條訊息時將其對應序號遞增。對於接收的每條訊息,如果其序號比Broker維護的序號(即最後一次Commit的訊息的序號)大一,則Broker會接受它,否則將其丟棄:

  • 如果訊息序號比Broker維護的序號大一以上,說明中間有資料尚未寫入,也即亂序,此時Broker拒絕該訊息,Producer丟擲InvalidSequenceNumber
  • 如果訊息序號小於等於Broker維護的序號,說明該訊息已被儲存,即為重複訊息,Broker直接丟棄該訊息,Producer丟擲DuplicateSequenceNumber

上述設計解決了0.11.0.0之前版本中的兩個問題:

  • Broker儲存訊息後,傳送ACK前宕機,Producer認為訊息未傳送成功並重試,造成資料重複
  • 前一條訊息傳送失敗,後一條訊息傳送成功,前一條訊息重試後成功,造成資料亂序

事務性保證

上述冪等設計只能保證單個Producer對於同一個<Topic, Partition>Exactly Once語義。

另外,它並不能保證寫操作的原子性——即多個寫操作,要麼全部被Commit要麼全部不被Commit。

更不能保證多個讀寫操作的的原子性。尤其對於Kafka Stream應用而言,典型的操作即是從某個Topic消費資料,經過一系列轉換後寫回另一個Topic,保證從源Topic的讀取與向目標Topic的寫入的原子性有助於從故障中恢復。

事務保證可使得應用程式將生產資料和消費資料當作一個原子單元來處理,要麼全部成功,要麼全部失敗,即使該生產或消費跨多個<Topic, Partition>

另外,有狀態的應用也可以保證重啟後從斷點處繼續處理,也即事務恢復。

為了實現這種效果,應用程式必須提供一個穩定的(重啟後不變)唯一的ID,也即Transaction IDTransactin IDPID可能一一對應。區別在於Transaction ID由使用者提供,而PID是內部的實現對使用者透明。

另外,為了保證新的Producer啟動後,舊的具有相同Transaction ID的Producer即失效,每次Producer通過Transaction ID拿到PID的同時,還會獲取一個單調遞增的epoch。由於舊的Producer的epoch比新Producer的epoch小,Kafka可以很容易識別出該Producer是老的Producer並拒絕其請求。

有了Transaction ID後,Kafka可保證:

  • 跨Session的資料冪等傳送。當具有相同Transaction ID的新的Producer例項被建立且工作時,舊的且擁有相同Transaction ID的Producer將不再工作。
  • 跨Session的事務恢復。如果某個應用例項宕機,新的例項可以保證任何未完成的舊的事務要麼Commit要麼Abort,使得新例項從一個正常狀態開始工作。

需要注意的是,上述的事務保證是從Producer的角度去考慮的。從Consumer的角度來看,該保證會相對弱一些。尤其是不能保證所有被某事務Commit過的所有訊息都被一起消費,因為:

  • 對於壓縮的Topic而言,同一事務的某些訊息可能被其它版本覆蓋
  • 事務包含的訊息可能分佈在多個Segment中(即使在同一個Partition內),當老的Segment被刪除時,該事務的部分資料可能會丟失
  • Consumer在一個事務內可能通過seek方法訪問任意Offset的訊息,從而可能丟失部分訊息
  • Consumer可能並不需要消費某一事務內的所有Partition,因此它將永遠不會讀取組成該事務的所有訊息

事務機制原理

事務性訊息傳遞

這一節所說的事務主要指原子性,也即Producer將多條訊息作為一個事務批量傳送,要麼全部成功要麼全部失敗。

為了實現這一點,Kafka 0.11.0.0引入了一個伺服器端的模組,名為Transaction Coordinator,用於管理Producer傳送的訊息的事務性。

Transaction Coordinator維護Transaction Log,該log存於一個內部的Topic內。由於Topic資料具有永續性,因此事務的狀態也具有永續性。

Producer並不直接讀寫Transaction Log,它與Transaction Coordinator通訊,然後由Transaction Coordinator將該事務的狀態插入相應的Transaction Log

Transaction Log的設計與Offset Log用於儲存Consumer的Offset類似。

事務中Offset的提交

許多基於Kafka的應用,尤其是Kafka Stream應用中同時包含Consumer和Producer,前者負責從Kafka中獲取訊息,後者負責將處理完的資料寫回Kafka的其它Topic中。

為了實現該場景下的事務的原子性,Kafka需要保證對Consumer Offset的Commit與Producer對傳送訊息的Commit包含在同一個事務中。否則,如果在二者Commit中間發生異常,根據二者Commit的順序可能會造成資料丟失和資料重複:

  • 如果先Commit Producer傳送資料的事務再Commit Consumer的Offset,即At Least Once語義,可能造成資料重複。
  • 如果先Commit Consumer的Offset,再Commit Producer資料傳送事務,即At Most Once語義,可能造成資料丟失。

用於事務特性的控制型訊息

為了區分寫入Partition的訊息被Commit還是Abort,Kafka引入了一種特殊型別的訊息,即Control Message。該類訊息的Value內不包含任何應用相關的資料,並且不會暴露給應用程式。它只用於Broker與Client間的內部通訊。

對於Producer端事務,Kafka以Control Message的形式引入一系列的Transaction Marker。Consumer即可通過該標記判定對應的訊息被Commit了還是Abort了,然後結合該Consumer配置的隔離級別決定是否應該將該訊息返回給應用程式。

事務處理樣例程式碼

Producer<String, String> producer = new KafkaProducer<String, String>(props);
    
// 初始化事務,包括結束該Transaction ID對應的未完成的事務(如果有)
// 保證新的事務在一個正確的狀態下啟動
producer.initTransactions();
// 開始事務
producer.beginTransaction();
// 消費資料
ConsumerRecords<String, String> records = consumer.poll(100);
try{
    // 傳送資料
    producer.send(new ProducerRecord<String, String>("Topic", "Key", "Value"));
    
    // 傳送消費資料的Offset,將上述資料消費與資料傳送納入同一個Transaction內
    producer.sendOffsetsToTransaction(offsets, "group1");
    // 資料傳送及Offset傳送均成功的情況下,提交事務
    producer.commitTransaction();
} catch (ProducerFencedException | OutOfOrderSequenceException | AuthorizationException e) {
    // 資料傳送或者Offset傳送出現異常時,終止事務
    producer.abortTransaction();
} finally {
    // 關閉Producer和Consumer
    producer.close();
    consumer.close();
}

完整事務過程

Kafka Transaction

找到Transaction Coordinator

由於Transaction Coordinator是分配PID和管理事務的核心,因此Producer要做的第一件事情就是通過向任意一個Broker傳送FindCoordinator請求找到Transaction Coordinator的位置。

注意:只有應用程式為Producer配置了Transaction ID時才可使用事務特性,也才需要這一步。另外,由於事務性要求Producer開啟冪等特性,因此通過將transactional.id設定為非空從而開啟事務特性的同時也需要通過將enable.idempotence設定為true來開啟冪等特性。

獲取PID

找到Transaction Coordinator後,具有冪等特性的Producer必須發起InitPidRequest請求以獲取PID。

注意:只要開啟了冪等特性即必須執行該操作,而無須考慮該Producer是否開啟了事務特性。

如果事務特性被開啟 
InitPidRequest會發送給Transaction Coordinator。如果Transaction Coordinator是第一次收到包含有該Transaction ID的InitPidRequest請求,它將會把該<TransactionID, PID>存入Transaction Log,如上圖中步驟2.1所示。這樣可保證該對應關係被持久化,從而保證即使Transaction Coordinator宕機該對應關係也不會丟失。

除了返回PID外,InitPidRequest還會執行如下任務:

  • 增加該PID對應的epoch。具有相同PID但epoch小於該epoch的其它Producer(如果有)新開啟的事務將被拒絕。
  • 恢復(Commit或Abort)之前的Producer未完成的事務(如果有)。

注意:InitPidRequest的處理過程是同步阻塞的。一旦該呼叫正確返回,Producer即可開始新的事務。

另外,如果事務特性未開啟,InitPidRequest可傳送至任意Broker,並且會得到一個全新的唯一的PID。該Producer將只能使用冪等特性以及單一Session內的事務特性,而不能使用跨Session的事務特性。

開啟事務

Kafka從0.11.0.0版本開始,提供beginTransaction()方法用於開啟一個事務。呼叫該方法後,Producer本地會記錄已經開啟了事務,但Transaction Coordinator只有在Producer傳送第一條訊息後才認為事務已經開啟。

Consume-Transform-Produce

這一階段,包含了整個事務的資料處理過程,並且包含了多種請求。

AddPartitionsToTxnRequest
一個Producer可能會給多個<Topic, Partition>傳送資料,給一個新的<Topic, Partition>傳送資料前,它需要先向Transaction Coordinator傳送AddPartitionsToTxnRequest

Transaction Coordinator會將該<Transaction, Topic, Partition>存於Transaction Log內,並將其狀態置為BEGIN,如上圖中步驟4.1所示。有了該資訊後,我們才可以在後續步驟中為每個Topic, Partition>設定COMMIT或者ABORT標記(如上圖中步驟5.2所示)。

另外,如果該<Topic, Partition>為該事務中第一個<Topic, Partition>Transaction Coordinator還會啟動對該事務的計時(每個事務都有自己的超時時間)。

ProduceRequest
Producer通過一個或多個ProduceRequest傳送一系列訊息。除了應用資料外,該請求還包含了PID,epoch,和Sequence Number。該過程如上圖中步驟4.2所示。

AddOffsetsToTxnRequest
為了提供事務性,Producer新增了sendOffsetsToTransaction方法,該方法將多組訊息的傳送和消費放入同一批處理內。

該方法先判斷在當前事務中該方法是否已經被呼叫並傳入了相同的Group ID。若是,直接跳到下一步;若不是,則向Transaction Coordinator傳送AddOffsetsToTxnRequests請求,Transaction Coordinator將對應的所有<Topic, Partition>存於Transaction Log中,並將其狀態記為BEGIN,如上圖中步驟4.3所示。該方法會阻塞直到收到響應。

TxnOffsetCommitRequest
作為sendOffsetsToTransaction方法的一部分,在處理完AddOffsetsToTxnRequest後,Producer也會發送TxnOffsetCommit請求給Consumer Coordinator從而將本事務包含的與讀操作相關的各<Topic, Partition>的Offset持久化到內部的__consumer_offsets中,如上圖步驟4.4所示。

在此過程中,Consumer Coordinator會通過PID和對應的epoch來驗證是否應該允許該Producer的該請求。

這裡需要注意:

  • 寫入__consumer_offsets的Offset資訊在當前事務Commit前對外是不可見的。也即在當前事務被Commit前,可認為該Offset尚未Commit,也即對應的訊息尚未被完成處理。
  • Consumer Coordinator並不會立即更新快取中相應<Topic, Partition>的Offset,因為此時這些更新操作尚未被COMMIT或ABORT。

Commit或Abort事務

一旦上述資料寫入操作完成,應用程式必須呼叫KafkaProducercommitTransaction方法或者abortTransaction方法以結束當前事務。

EndTxnRequest
commitTransaction方法使得Producer寫入的資料對下游Consumer可見。abortTransaction方法通過Transaction Marker將Producer寫入的資料標記為Aborted狀態。下游的Consumer如果將isolation.level設定為READ_COMMITTED,則它讀到被Abort的訊息後直接將其丟棄而不會返回給客戶程式,也即被Abort的訊息對應用程式不可見。

無論是Commit還是Abort,Producer都會發送EndTxnRequest請求給Transaction Coordinator,並通過標誌位標識是應該Commit還是Abort。

收到該請求後,Transaction Coordinator會進行如下操作

  1. PREPARE_COMMITPREPARE_ABORT訊息寫入Transaction Log,如上圖中步驟5.1所示
  2. 通過WriteTxnMarker請求以Transaction Marker的形式將COMMITABORT資訊寫入使用者資料日誌以及Offset Log中,如上圖中步驟5.2所示
  3. 最後將COMPLETE_COMMITCOMPLETE_ABORT資訊寫入Transaction Log中,如上圖中步驟5.3所示

補充說明:對於commitTransaction方法,它會在傳送EndTxnRequest之前先呼叫flush方法以確保所有傳送出去的資料都得到相應的ACK。對於abortTransaction方法,在傳送EndTxnRequest之前直接將當前Buffer中的事務性訊息(如果有)全部丟棄,但必須等待所有被髮送但尚未收到ACK的訊息傳送完成。

上述第二步是實現將一組讀操作與寫操作作為一個事務處理的關鍵。因為Producer寫入的資料Topic以及記錄Comsumer Offset的Topic會被寫入相同的Transactin Marker,所以這一組讀操作與寫操作要麼全部COMMIT要麼全部ABORT。

WriteTxnMarkerRequest
上面提到的WriteTxnMarkerRequestTransaction Coordinator傳送給當前事務涉及到的每個<Topic, Partition>的Leader。收到該請求後,對應的Leader會將對應的COMMIT(PID)或者ABORT(PID)控制資訊寫入日誌,如上圖中步驟5.2所示。

該控制訊息向Broker以及Consumer表明對應PID的訊息被Commit了還是被Abort了。

這裡要注意,如果事務也涉及到__consumer_offsets,即該事務中有消費資料的操作且將該消費的Offset存於__consumer_offsets中,Transaction Coordinator也需要向該內部Topic的各Partition的Leader傳送WriteTxnMarkerRequest從而寫入COMMIT(PID)COMMIT(PID)控制資訊。

寫入最終的COMPLETE_COMMITCOMPLETE_ABORT訊息
寫完所有的Transaction Marker後,Transaction Coordinator會將最終的COMPLETE_COMMITCOMPLETE_ABORT訊息寫入Transaction Log中以標明該事務結束,如上圖中步驟5.3所示。

此時,Transaction Log中所有關於該事務的訊息全部可以移除。當然,由於Kafka內資料是Append Only的,不可直接更新和刪除,這裡說的移除只是將其標記為null從而在Log Compact時不再保留。

另外,COMPLETE_COMMITCOMPLETE_ABORT的寫入並不需要得到所有Rreplica的ACK,因為如果該訊息丟失,可以根據事務協議重發。

補充說明,如果參與該事務的某些<Topic, Partition>在被寫入Transaction Marker前不可用,它對READ_COMMITTED的Consumer不可見,但不影響其它可用<Topic, Partition>的COMMIT或ABORT。在該<Topic, Partition>恢復可用後,Transaction Coordinator會重新根據PREPARE_COMMITPREPARE_ABORT向該<Topic, Partition>傳送Transaction Marker

總結

  • PIDSequence Number的引入實現了寫操作的冪等性
  • 寫操作的冪等性結合At Least Once語義實現了單一Session內的Exactly Once語義
  • Transaction MarkerPID提供了識別訊息是否應該被讀取的能力,從而實現了事務的隔離性
  • Offset的更新標記了訊息是否被讀取,從而將對讀操作的事務處理轉換成了對寫(Offset)操作的事務處理
  • Kafka事務的本質是,將一組寫操作(如果有)對應的訊息與一組讀操作(如果有)對應的Offset的更新進行同樣的標記(即Transaction Marker)來實現事務中涉及的所有讀寫操作同時對外可見或同時對外不可見
  • Kafka只提供對Kafka本身的讀寫操作的事務性,不提供包含外部系統的事務性

異常處理

Exception處理

InvalidProducerEpoch
這是一種Fatal Error,它說明當前Producer是一個過期的例項,有Transaction ID相同但epoch更新的Producer例項被建立並使用。此時Producer會停止並丟擲Exception。

InvalidPidMapping
Transaction Coordinator沒有與該Transaction ID對應的PID。此時Producer會通過包含有Transaction IDInitPidRequest請求建立一個新的PID。

NotCorrdinatorForGTransactionalId
Transaction Coordinator不負責該當前事務。Producer會通過FindCoordinatorRequest請求重新尋找對應的Transaction Coordinator

InvalidTxnRequest
違反了事務協議。正確的Client實現不應該出現這種Exception。如果該異常發生了,使用者需要檢查自己的客戶端實現是否有問題。

CoordinatorNotAvailable
Transaction Coordinator仍在初始化中。Producer只需要重試即可。

DuplicateSequenceNumber
傳送的訊息的序號低於Broker預期。該異常說明該訊息已經被成功處理過,Producer可以直接忽略該異常並處理下一條訊息

InvalidSequenceNumber
這是一個Fatal Error,它說明發送的訊息中的序號大於Broker預期。此時有兩種可能

  • 資料亂序。比如前面的訊息傳送失敗後重試期間,新的訊息被接收。正常情況下不應該出現該問題,因為當冪等傳送啟用時,max.inflight.requests.per.connection被強制設定為1,而acks被強制設定為all。故前面訊息重試期間,後續訊息不會被髮送,也即不會發生亂序。並且只有ISR中所有Replica都ACK,Producer才會認為訊息已經被髮送,也即不存在Broker端資料丟失問題。
  • 伺服器由於日誌被Truncate而造成資料丟失。此時應該停止Producer並將此Fatal Error報告給使用者。

InvalidTransactionTimeout
InitPidRequest調用出現的Fatal Error。它表明Producer傳入的timeout時間不在可接受範圍內,應該停止Producer並報告給使用者。

處理Transaction Coordinator失敗

PREPARE_COMMIT/PREPARE_ABORT前失敗

Producer通過FindCoordinatorRequest找到新的

相關推薦

Kafka設計解析- Exactly Once語義事務機制原理

1 寫在前面的話 本文所有Kafka原理性的描述除特殊說明外均基於Kafka 1.0.0版本。 2 為什麼要提供事務機制 Kafka事務機制的實現主要是為了支援 Exactly Once即正好一次語義操作的原子性有狀態操作的可恢復性2.1 Exactly Once 《Kafka背景及架構介紹》一文

[Kafka設計解析]--Exactly Once語義事務機制原理

寫在前面的話本文所有Kafka原理性的描述除特殊說明外均基於Kafka 1.0.0版本。為什麼要提供事務機制Kafka事務機制的實現主要是為了支援Exactly Once即正好一次語義操作的原子性有狀態操作的可恢復性Exactly Once《Kafka背景及架構介紹》一文中有

Kafka設計解析Kafka高性能架構之道

持久化 edt 可見 enc linu 定義 通信 index posit 轉載自 技術世界,原文鏈接 Kafka設計解析(六)- Kafka高性能架構之道 本文從宏觀架構層面和微觀實現層面分析了Kafka如何實現高性能。包含Kafka如何利用Partition實現

Kafka設計解析十三Kafka消費組(consumer group)

信息 格式 eve 引擎 區分 展開 rebalance 4.5 內容 轉載自 huxihx,原文鏈接 Kafka消費組(consumer group) 一直以來都想寫一點關於kafka consumer的東西,特別是關於新版consumer的中文資料很少。最近Ka

Kafka設計解析 Kafka背景及架構介紹

摘要   Kafka是由LinkedIn開發並開源的分散式訊息系統,因其分散式及高吞吐率而被廣泛使用,現已與Cloudera Hadoop,Apache Storm,Apache Spark整合。本文介紹了Kafka的建立背景,設計目標,使用訊息系統的優勢以及目前流行的訊息系統對比。並介紹了Ka

kafka--Kafka設計解析Kafka High Availability

Kafka在0.8以前的版本中,並不提供High Availablity機制,一旦一個或多個Broker宕機,則宕機期間其上所有Partition都無法繼續提供服務。若該Broker永遠不能再恢復,亦或磁碟故障,則其上資料將丟失。而Kafka的設計目標之一即是提供資

SpringBoot 原始碼解析 ----- Spring Boot 精髓:事務原始碼解析

本篇來講一下SpringBoot是怎麼自動開啟事務的,我們先來回顧一下以前SSM中是如何使用事務的 SSM使用事務 匯入JDBC依賴包 眾所周知,凡是需要跟資料庫打交道的,基本上都要新增jdbc的依賴,在Spring專案中,加入的是spring-jdbc依賴: <dependency>

Kafka設計解析十四Kafka producer介紹

解析 共享 發送 丟失 ash 整體 ket flight memory 轉載自 huxihx,原文鏈接 Kafka producer介紹 Kafka 0.9版本正式使用Java版本的producer替換了原Scala版本的producer。本文著重討論新版本pro

Kafka設計解析二十Apache Flink Kafka consumer

zook 實例 發送 abs 版本 conn 事情 save prope 轉載自 huxihx,原文鏈接 Apache Flink Kafka consumer Flink提供了Kafka connector用於消費/生產Apache Kafka topic的數據。

Kafka設計解析二十一關於Kafka冪等producer的討論

不同 .cn stat ali 就是 額外 ber nec local 轉載自 huxihx,原文鏈接 關於Kafka冪等producer的討論 眾所周知,Kafka 0.11.0.0版本正式支持精確一次處理語義(exactly once semantics,下稱E

Kafka設計解析二十一Kafka水位(high watermark)leader epoch的討論

副本 ade 方式 這樣的 不一致 如果 文件中 rep 經典 轉載自 huxihx,原文鏈接 Kafka水位(high watermark)與leader epoch的討論 本文主要討論0.11版本之前Kafka的副本備份機制的設計問題以及0.11是如何解決的。簡

Kafka設計解析二十二Flink + Kafka 0.11端到端精確一次處理語義的實現

pac 內部 通知 發生 ng- 設計 解析 位移 eas 轉載自 huxihx,原文鏈接 【譯】Flink + Kafka 0.11端到端精確一次處理語義的實現 本文是翻譯作品,作者是Piotr Nowojski和Michael Winters。前者是該方案的實現

Java設計模式Proxy代理模式

com 服務器 exp 技術分享 如果 face pub [] his 一、場景描述 代理在生活中並不少見,租房子需要找中介,打官司需要找律師,很多事情我們需要找專業人士代理我們做,另一方面,中介和律師也代理了房東、法律程序與我們打交道。 當然,設計模式中的代理與廣義的

淺析設計模式——創建型模式之Prototype(原型模式)

ref rri head 創建型模式 obj www str 操作 接口 原型模式Prototype 本文的內容: 一、原型模式的定義 二、原型模式的參與者及其角色 三、原型模式的類圖 四、原型模式的示例 五、參考 一、原型模式的定義   定義:用原型

Java中的設計模式:建造者模式

伸縮 null clas 示例代碼 最簡 裝配 角色扮演 app 但是 介紹 今天我們將研究java中的Builder模式。Builder 設計模式是一種創造性的設計模式,如工廠模式和抽象工廠模式。 當Object包含許多屬性時,引入了Builder模式來解決Factory

Spring原始碼解析——生命週期——BeanPostProcessor在spring底層的使用

一、ApplicationContextAwareProcessor import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import or

設計模式裝飾器模式Decorator結構型

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

設計模式裝飾模式

裝飾模式(Decotator):動態地給一個物件新增一些額外的職責,就增加功能來說,裝飾模式比生成子類更加靈活 這是在網上找的關於裝飾模式的類圖, http://img0.imgtn.bdimg.com/it/u=3834882730,1020120806&fm=214&

設計模式預設介面卡

1、目標 //目標 public interface IDesigner { public void drawPicture(); public void findIdea(); public void thinkUE(); public void showPictur

前端通訊:ajax設計方案--- 設計請求池,複用請求,讓前端通訊快、更快、再快一點

直接進入主題,本篇文章有點長,包括從設計階段,到摸索階段,再到實現階段,最後全面覆蓋測試階段(包括資料蒐集清洗),還有與主流前端通訊框架進行對比PK階段。 首先介紹一下一些概念:   1. 瀏覽器的併發能力:瀏覽器設計當初就定義了瀏覽器開啟頁面,同時傳送http請求的瞬時數量。這樣設計有很多原因,同時保護瀏覽