<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    paulwong

    #

    Tengine

    簡介

    Tengine是由淘寶網發起的Web服務器項目。它在Nginx的基礎上,針對大訪問量網站
    的需求,添加了很多高級功能和特性。Tengine的性能和穩定性已經在大型的網站如
    淘寶網天貓商城等得到了很好的檢驗。它的最終目標是打造一個高效、穩定、安全、
    易用的Web平臺。

    從2011年12月開始,Tengine成為一個開源項目,Tengine團隊在積極地開發和維護
    著它。Tengine團隊的核心成員來自于淘寶搜狗等互聯網企業。Tengine是社區合作
    的成果,我們歡迎大家參與其中,貢獻自己的力量。

    特性

    動態

    郵件列表

    posted @ 2015-11-06 12:56 paulwong 閱讀(453) | 評論 (0)編輯 收藏

    SPRING IO

    Spring起初只專注ioc和aop,現在已發展成一個龐大體系。比如security、mvc等。
    如此一來,不同模塊或者與外部進行集成時,依賴處理就需要各自對應版本號。
    比如,較新spring與較老的quartz,它們集成就會遇到問題,給搭建和升級帶來不便。

    因此Spring IO Platform應運而生,只要項目中引入了它,外部集成時依賴關系無需版本號

    <dependency>
     <groupId>org.springframework</groupId>
     <artifactId>spring-core</artifactId> 
    </dependency>

    Spring IO Platform只是一個pom文件,記錄了spring與其他開源項目對應的版本。
    省去了版本號,也就省去了處理依賴時的問題,因為Spring IO Platform中有最優的版本。

    posted @ 2015-10-30 14:05 paulwong 閱讀(483) | 評論 (0)編輯 收藏

    一篇不錯的Docker入門介紹

    看到這標題你可能會想,網上不是已經有很多Docker的入門介紹了么?同一個主題被講過很多次,還有沒有必要必要再講?可是,我體內這混雜的的高傲與固執雖然讓人厭惡,但卻讓我廣受大家的歡迎,這也讓我覺得我應該來一發(篇),哈哈。 

    舉一個我遇到的場景,ELK三劍客,即Elasticsearch、Logstash和Kibana。我可以選擇把它們直接安裝在我的MacBook上,這是我主力開發機,但是在上面已經裝好一個Elasticsearch了。我不想破壞現在已有的環境。要解決這個問題,用2015年時下最熱門的解決方案就是Docker了。如果過去一年關于Docker的各種熱鬧你都錯過了,那么請繼續往下看。 

    Docker做的事情就是將的軟件隔離起來,讓它們即使出了問題也不會互相影響。這并不是什么橫空出世的新思想。你很可能會說內核控制的進程不就這樣玩么?每一個進程都有自己的內存空間,并且在一個進程自身看來,內存空間與所處在的計算機的內存空間是一樣的。然而內核欺騙了進程,在背后將內存地址重新映射到了真實的內存空間中。想想今天高速運轉處理器,任何地方見到的系統都能同時運行多個進程。今天的文明社會比人類歷史任何一個時間點制造的謊言數量級都要很多的量級。 

    扯遠了,Docker將進程的隔離模型的進行了擴展,讓隔離性變得更強。Docker是在Linux內核的基礎上打造的一系列工具。整個文件系統被抽象了,網絡被虛擬化了,其它進程被隱藏了,并且從理論上,不可能逃脫容器去對在一個機器上的其他進程搞破壞。實際中,每個人對于怎么才能逃脫容器,至少去收集一點運行容器的機器的相關信息,持開放的態度。跟虛擬機比較起來,容器的隔離性還是較弱。 

     

    上面箭頭:提升單機性能;下面箭頭:提升隔離性

    但換個角度看,進程比容器性能更好,容器性能比虛擬機性能更佳。原因很簡單,隔離性更高,在每一個上下文中就需要運行更多的東西,從而拖慢速度。選擇一個隔離性的過程,實際就是決定你對要運行進程的信任有多少的過程 - 它會不會去干擾其他的進程?如運行的進程都是自己的親兒子,那你對他們會有一個很高的信任度,對他們用最少的隔離,運行在一個進程中就行了。如果是SAP,那么你很可能需要盡可能高的隔離性:將電腦裝在封存在箱子里,綁在火箭上發射到月球。 

    Docker另一個很好的特性是,容器可以作為一個整體交付。他們不會像虛擬機那么臃腫。這大大的提高了部署的簡易度。在這個微服務流行的世界里,你可以很容易將你的服務捆在一起,用鏡像來發布。你甚至可以將build的結果指定成一個Docker鏡像。 

    Docker將會怎樣改變軟件開發和部署的過程仍然有待觀察。盡管我覺得Docker是一種帶有破壞性的技術,但影響還在幾年之后才會到來。雖然我會認為Docker會讓很多系統管理員丟掉工作,但是實際上Docker卻會改變他們的工作。每個人現在都需要一點變革,趕上時代的腳步。 

    又扯遠了,說說OSX上的Docker。 

    細心的你可能注意到,我之前說Docker是運行在Linux內核之上的。然而OSX沒有Linux內核,那怎么運行Docker呢。為了解決這個問題,我們需要用虛擬機來運行Docker。我們可以用一個叫boot2docker的工具來做這件事情,但是最近被docker-machine取代了。 

    我的機上有一個比較老的Docker,但是我覺得想試試Docker Compose,因為我運行著很多的服務。Docker Compose能讓很多的容器協作起來運行一個整體的環境。為了遵循保證隔離服務的宗旨,每一個服務都運行在單獨的容器中。因而,一個典型的web應用中,可以把web服務器運行在一個容器里面,數據庫運行在另外一個容器里面,然后這些容器可以放在同一個機器上。 

    我從Docker官網上下載了安裝包,并且跟著安裝指南http://docs.docker.com/mac/step_one/安裝。裝好Docker后,我讓docker-machine在Virtual Box上創建了新的虛擬機。 

     

    一起看起來很順利,然后啟動隨處可見的hello-world鏡像。 

    很驚訝這個鏡像的并不完全,完全沒有發現任何一個地方有“hello world”的輸出。然而好在,不是每一個Docker鏡像都實現地這般草率。這個hello world的例子比較無聊,看看能不能找到更加有意思的。我們想從容器中服務一個頁面,我打算使用Nginx,已經有一個現成的Nginx的容器了,因此我創建了個新的Dockerfile。Dockerfile包含了一系列如何指導Docker從一系列鏡像中創建出一個容器的指令。這里提到的容器包含以下內容: 
    Java代碼 
    1. FROM nginx  
    2. COPY *.html /usr/share/nginx/html/  

    第一行設置了我們容器的基礎鏡像。第二行將本地的帶有html后綴的文件拷貝到Nginx容器中WEB服務器的目錄里。為了使用這個Dockerfile文件,我們需要創建一個Docker的鏡像: 
    Java代碼 
    1. /tmp/nginx$ docker build -t nginx_test .  
    2. Sending build context to Docker daemon 3.072 kB  
    3. Step 0 : FROM nginx  
    4. latest: Pulling from library/nginx  
    5. 843e2bded498: Pull complete  
    6. 8c00acfb0175: Pull complete  
    7. 426ac73b867e: Pull complete  
    8. d6c6bbd63f57: Pull complete  
    9. 4ac684e3f295: Pull complete  
    10. 91391bd3c4d3: Pull complete  
    11. b4587525ed53: Pull complete  
    12. 0240288f5187: Pull complete  
    13. 28c109ec1572: Pull complete  
    14. 063d51552dac: Pull complete  
    15. d8a70839d961: Pull complete  
    16. ceab60537ad2: Pull complete  
    17. Digest: sha256:9d0768452fe8f43c23292d24ec0fbd0ce06c98f776a084623d62ee12c4b7d58c  
    18. Status: Downloaded newer image for nginx:latest  
    19. ---> ceab60537ad2  
    20. Step 1 : COPY *.html /usr/share/nginx/html/  
    21. ---> ce25a968717f  
    22. Removing intermediate container c45b9eb73bc7  
    23. Successfully built ce25a968717f   

    Docker build命令開始將拉取已經構建好的Nginx容器。然后將我們的文件拷貝到容器里面,并且顯示容器的hash值,這讓它們很容易辨認。要運行這個容器我們可以運行: 
    Java代碼 
    1. /tmp/nginx$ docker run --name simple_html -d -p 3001:80 -p 3002:443 nginx_test  

    這條命令讓Docker運行nginxtest的容器,并且取名為simple_html。-d選項是為了讓Docker在后臺運行這條命令,并且最終-p選項是為了轉發端口,這里需要將本地的3001端口映射到容器的80端口 - 即正常的web服務器端口。現在我們可以連接到web服務上了。如果我們打開chrome,訪問localhost:3001就會看到: 

     

    居然不行,問題在于Docker沒有意識到自己運行在虛擬機的環境里面,因此我們需要將vm的端口映射到我們本地機器上: 
    Java代碼 
    1. Docker container:80 -> vm host:3001 -> OSX:3001  

    這個從虛擬機管理器里面可以輕松的搞定: 

     

    現在我們可以看到頁面了: 

     

    這就是我們放在容器中的文件。好極了!現在我準備好嘗試更復雜一點的容器了。 

    小貼士: 

    我注意到在虛擬機里面同時并行的運行Docker會整個讓系統hang住。我懷疑同時跑兩個虛擬工具可能讓某個地方卡住產生了沖突的結果。我相信docker-machine的并行的支持正在在積極的解決中,0.5版本可能會看到。直到這之前,你可以參考:http://kb.parallels.com/en/123356并且看看:https://github.com/Parallels/docker-machine中對docker-machine的fork版本。 

    原文鏈接:Yet another intro to docker (翻譯:鐘最龍 校對:宋喻) 

    譯文來自:DockOne.io

    posted @ 2015-10-30 12:49 paulwong 閱讀(470) | 評論 (0)編輯 收藏

    糖果CMS,一個像糖果一樣的CMS 糖果CMS

    一個JAVA的內容管理系統
    http://www.oschina.net/p/tg-cms

    posted @ 2015-10-27 18:24 paulwong 閱讀(502) | 評論 (0)編輯 收藏

    REDIS監控

    http://git.oschina.net/hellovivi/RedisFlag

    posted @ 2015-10-19 12:39 paulwong 閱讀(492) | 評論 (1)編輯 收藏

    深度技術揭秘,支付寶,財付通,到底每天都是怎樣工作的?

    為了可以更好地解釋支付結算系統對賬過程,我們先把業務從頭到尾串起來描述一下場景,幫助大家理解:一個可能得不能再可能的場景,請大家深刻理解里面每個角色做了什么,獲取了哪些信息:  某日陽光燦爛,支付寶用戶小明在淘寶上看中了暖腳器一只,價格100元。猶豫再三后小明使用支付寶網銀完成了支付,支付寶顯示支付成功,淘寶賣家通知他已發貨,最近幾日注意查收。
      小明:持卡人,消費者,淘寶和支付寶的注冊會員,完成了支付動作,自己的銀行賬戶資金減少,交易成功。
      銀行:收單銀行,接受來自支付寶的名為“支付寶BBB”的100元訂單,并引導持卡人小明支付成功,扣除小明銀行卡賬戶余額后返回給支付寶成功通知,并告訴支付寶這筆交易在銀行流水號為“銀行CCC”
      支付寶:支付公司,接收到淘寶發來的訂單號為“淘寶AAA”的商戶訂單號,并形成支付系統唯一流水號:“支付寶BBB”發往銀行系統。然后獲得銀行回復的支付成功信息,順帶銀行流水號“銀行CCC”
      淘寶:我們支付公司稱淘寶這類電商為商戶,是支付系統的客戶。淘寶向支付系統發送了一筆交易收單請求,金額為100,訂單號為:“淘寶AAA”,支付系統最后反饋給淘寶這筆支付流水號為“支付BBB”
      以上流程貌似大家都達到了預期效果,但實際上仍然還有一個問題:
      對支付公司(支付寶)而言,雖然銀行通知了它支付成功,但資金實際還要T+1后結算到它銀行賬戶,所以目前只是一個信息流,資金流還沒過來。
      Tips:插一句話,對支付系統內部賬務來說,由于資金沒有能夠實時到賬,所以此時小明的這筆100元交易資金并沒有直接記入到系統資產類科目下的“銀行存款”科目中,而是掛在“應收賬款”或者“待清算科目”中。大白話就是,這100元雖然答應給我了,我也記下來了,但還沒收到,我掛在那里。
      對商戶(淘寶)而言,雖然支付公司通知了它支付成功,他也發貨了,但資金按照合同也是T+1到賬。如果不對賬確認一下,恐怕也會不安。
      倒是對消費者(小明)而言:反正錢付了,你們也顯示成功了,等暖腳器呀等暖腳器~
      基于支付公司和商戶的困惑,我們的支付結算系統需要進行兩件事情:一是資金渠道對賬,通稱對銀行帳;二是商戶對賬,俗稱對客戶帳。對客戶帳又分為對公客戶和對私客戶,通常對公客戶會對對賬文件格式、對賬周期、系統對接方案有特殊需求,而對私客戶也就是我們一般的消費者只需要可以后臺查詢交易記錄和支付歷史流水就可以了。
      我們先聊銀行資金渠道對賬,由于支付公司的資金真正落地在商業銀行,所以資金渠道的對賬顯得尤為重要。
      在一個銀行會計日結束后,銀行系統會先進行自己內部扎帳,完成無誤后進行數據的清分和資金的結算,將支付公司當日應入賬的資金結算到支付公司賬戶中。于此同時,目前多數銀行已經支持直接系統對接的方式發送對賬文件。于是,在某日臨晨4點,支付寶系統收到來自銀行發來的前一會計日對賬文件。根據數據格式解析正確后和前日支付寶的所有交易數據進行匹配,理想情況是一一匹配成功無誤,然后將這些交易的對賬狀態勾對為“已對賬”。
      Tips:此時,對賬完成的交易,會將該筆資金從“應收賬款”或者“待清算賬款”科目中移動到“銀行存款”科目中,以示該交易真正資金到賬。
      以上太理想了,都那么理想就不要對賬了。所以通常都會出一些差錯,那么我總結了一下常見的差錯情況:
      1.支付時提交到銀行后沒有反饋,但對賬時該交易狀態為支付成功
      這種情況很正常,因為我們在信息傳輸過程中,難免會出現掉包和信息不通暢。消費者在銀行端完成了支付行為,銀行的通知信息卻被堵塞了,如此支付公司也不知道結果,商戶也不知道結果。如果信息一直沒法通知到支付公司這邊,那么這條支付結果就只能在日終對賬文件中體現了。這時支付公司系統需要對這筆交易進行補單操作,將交易置為成功并完成記賬規則,有必要還要通知到商戶。
      此時的小明:估計急的跳起來了……付了錢怎么不給說支付成功呢!坑爹!
      TIPS:通常銀行系統會開放一個支付結果查詢接口。支付公司會對提交到銀行但沒有回復結果的交易進行間隔查詢,以確保支付結果信息的實時傳達。所以以上情況出現的概率已經很少了。
      2.我方支付系統記錄銀行反饋支付成功,金額為100,銀行對賬金額不為100
      這種情況已經不太常見了,差錯不管是長款和短款都不是我們想要的結果。通常雙方系統通訊都是可作為糾紛憑證的,如果銀行在支付結果返回時確認是100元,對賬時金額不一致,可以要求銀行進行協調處理。而這筆賬在支付系統中通常也會做對應的掛賬處理,直到糾紛解決。
      3.我方支付系統記錄銀行反饋支付成功,但對賬文件中壓根不存在
      這種情況也經常見到,因為跨交易會計日的系統時間不同,所以會出現我們認為交易是23點59分完成的,銀行認為是第二天凌晨0點1分完成。對于這種情況我們通常會繼續掛賬,直到再下一日的對賬文件送達后進行對賬匹配。
      如果這筆交易一直沒有找到,那么就和第二種情況一樣,是一種短款,需要和銀行追究。
      以上情況針對的是一家銀行資金渠道所作的流程,實際情況中,支付公司會在不同銀行開立不同銀行賬戶,用以收單結算(成本會降低),所以真實情況極有可能是:
      臨晨1點,工行對賬文件丟過來(支行A)
      臨晨1點01分,工行又丟一個文件過來(支行B)
      臨晨1點15分,農行對賬文件丟過來
      。 。 。
      臨晨5點,興業銀行文件丟過來
      。。。
      不管什么時候,中國銀行都必須通過我方業務員下載對賬文件再上傳的方式進行對賬,所以系統接收到中行文件一般都是早上9點05分……
      對系統來說,每天都要處理大量并發的對賬數據,如果在交易高峰時段進行,會引起客戶交互的延遲和交易的失敗,這是萬萬行不得的所以通常支付公司不會用那么傻的方式處理數據,而是在一個會計日結束后,通常也是臨晨時段,將前一日交易增量備份到專用對賬服務器中,在物理隔絕環境下進行統一的對賬行為,杜絕硬件資源的搶占。
      以上是銀行資金渠道入款的對賬,出款基本原理一致,不過出款渠道在實際業務過程中還有一些特別之處,由于大家不是要建設系統,我就不贅述了。
      談完了資金渠道的對賬,我們再來看看對客戶帳。
      前面提到了,由于資金落在銀行,所以對支付公司來說,對銀行帳非常重要。那么同理,由于資金落在支付公司,所以對商戶來說,對支付公司賬也一樣重要。能否提供高品質甚至定制化的服務,是目前支付公司走差異化路線的一個主要競爭點。
      之前說過,銀行與支付公司之間的通訊都是可以作為糾紛憑證的。原理是對支付報文的關鍵信息進行密鑰加簽+md5處理,以確保往來報文“不可篡改,不可抵賴”。
      同理,支付公司和商戶之間也會有類似機制保證報文的可追溯性,由此我們可以知道,一旦我方支付系統通知商戶支付結果,那么我們就要為此承擔責任。由此我們再推斷一個結論:
      即便某支付訂單銀行方面出錯導致資金未能到賬,一旦我們支付系統通知商戶該筆交易成功,那么根據協議該結算的資金還是要結算給這個商戶。自然,我們回去追究銀行的問題,把賬款追回。
      沒經過排版的小知識點---------------------------------------------------
      一、對支付系統而言,最基本的對賬功能是供商戶在其后臺查詢下載某一時間段內的支付數據文件,以供商戶自己進行對賬。
      這個功能應該是個支付公司就有,如果沒有就別混了。
      二、對大多數支付系統而言,目前已經可以做到對賬文件的主動投送功能。
      這個功能方便了商戶系統和支付系統的對接,商戶的結算人員無須登錄支付平臺后臺下載文件進行對賬,省去了人工操作的麻煩和風險。
      對大型支付系統而言,商戶如果跨時間區域很大,反復查詢該區域內的數據并下載,會對服務器造成比較大的壓力。各位看官別笑,覺得查個數據怎么就有壓力了。實際上為了這個查詢,我最早就職的一家支付公司重新優化了所有SQL語句,并且因為查詢壓力過大服務器癱瘓過好幾次。
      現在比較主流的做法是把商戶短期內查詢過、或者經常要查詢的數據做緩存。實在不行就干脆實時備份,兩分鐘同步一次數據到專用數據庫供商戶查詢,以避免硬件資源占用。甚至……大多數支付公司都會對查詢范圍跨度和歷史事件進行限制,比如最多只能查一個月跨度內,不超過24個月前的數據……以避免服務嗝屁。
      對賬這塊大致就這樣了,再往細的說就說不完了,
      風險控制,在各行各業都尤其重要。
      金融即風險,控制好風險,才有利潤。
      雖然第三方支付嚴格意義上說并非屬于金融行業,但由于涉及資金的清分和結算,業務主體又是資金的收付,所以風險控制一樣非常重要。
      對支付公司來說,風控主要分為合規政策風控以及交易風控兩種。
      前者主要針對特定業務開展,特定產品形態進行法規層面的風險規避,通常由公司法務和風控部門一起合作進行。例如,一家公司要開展第三方支付業務,現在要獲得由人民銀行頒發的“支付業務許可證”。遵守中國對于金融管制的所有條規,幫助人行監控洗錢行為……這些法規合規風險,雖然條條框框,甚至顯得文縐縐,但如果沒人解讀沒人公關,業務都會無法開展。
      當然,這塊也不是本題所關注的重點,提問者關注的,應當是業務進行過程中的交易風控環節。
      現在隨著各支付公司風險控制意識的加強,風控系統漸漸被重視起來。除了上述提到的合規風控相關功能,風控系統最講技術含量,最講業務水平,最考究數據分析的業務就是交易風控環節。
      對一筆支付交易而言,在它發生之前、發生過程中及發生過程后,都會被風控系統嚴密監控,以確保支付及客戶資產安全。而所有的所有秘密,都歸結到一個詞頭上:規則。風控系統是一系列規則的集合,任何再智能的風控方案,都繞不開規則二字。
      我們先看看,哪些情況是交易風控需要監控處理的:
      1.釣魚網站
      什么是釣魚呢?
      用我的說法,就是利用技術手段蒙蔽消費者,當消費者想付款給A的時候,替換掉A的支付頁面,將錢付給B,以達成非法占用資金的目的。
      還有更低級的,直接就是發小廣告,里面帶一個類似http://tiaobao.com的網址,打開后和淘寶頁面一摸一樣,上當客戶直接付款給假冒網站主。
      第一種情況風控系統是可以通過規則進行簡單判定的,雖然有誤殺,但不會多。
      通常使用的規則是判斷提交訂單的IP地址和銀行實際支付完成的IP地址是否一致,如果不一致,則判斷為釣魚網站風險交易,進入待確認訂單。
      但第二種情況,親爹親娘了,支付公司真的沒辦法。當然遇到客戶投訴后可能會事后補救,但交易是無法阻止了。
      2.盜卡組織利用盜卡進行交易
      大家都知道,信用卡信息是不能隨便公布給別人的,國內大多信用卡雖然都設置了密碼,但銀行仍然會開放無磁無密支付接口給到商戶進行快速支付之用。
      所謂無磁無密,就是不需要磁道信息,不需要密碼就可以進行支付的通道。只需要獲取到客戶的CVV,有效期,卡號三個核心信息,而這三個信息是在卡上直接有的,所以大家不要隨便把卡交給別人哦~
      碰到類似的這種交易,風控系統就不會像釣魚網站這樣簡單判斷了。
      過去我們所有的歷史交易,都會存庫,不僅會存支付相關信息,更會利用網頁上的控件(對,惡心的activex或者目前用的比較多的flash控件)抓取支付者的硬件信息,存儲在數據庫中。
      當一筆交易信息帶著能夠搜集到的硬件信息一同提交給風控系統時,風控系統會進行多種規則判定。
      例如:當天該卡是否交易超過3次
      當天該IP是否交易超過3次
      該主機CPU的序列號是否在黑名單之列
      等等等等,一批規則跑完后,風控系統會給該交易進行加權評分,標示其風險系數,然后根據評分給出處理結果。
      通過硬件信息采集以及歷史交易記錄回溯,我們可以建立很多交易風控規則來進行監控。所以規則樣式的好壞,規則系數的調整,就是非常重要的用以區別風控系統檔次高低的標準。
      例如,我聽說著名的風控廠商RED,有一個“神經網絡”機制,灰常牛逼。
       
      其中有一個規則我到現在還記憶猶新:
      某人早上八點在加利福尼亞進行了信用卡支付,到了下午一點,他在東亞某小國家發起了信用卡支付申請。系統判斷兩者距離過長,不是短短5小時內能夠到達的,故判定交易無效,支付請求拒絕。
      規則非常重要,當然,數據也一樣重要。我們不僅需要從自己的歷史記錄中整合數據,同時還要聯合卡組織、銀行、風控機構,購買他們的數據和風控服務用來增加自己的風控實力。
      SO,風控是一個不斷積累數據、分析數據、運營數據、積累數據的循環過程。
      好的風控規則和參數,需要經過無數次的規則修改和調整,是一個漫長的過程。
      不知道大家做互聯網,有沒有利用GA做過AB測試,同樣的,風控系統也需要反復地做類似AB測試的實驗,以保證理論和實際的匹配。
      最后給大家說一個小小的概念:
      所謂風控,是指風險控制,不是風險杜絕。
      風控的目標一定不是把所有風險全部杜絕。
      合理的風控,目標一定是:利潤最大化,而不是風險最小化
      過于嚴格的風控規則,反而會傷害公司利益(看看銷售和風控經常打架就知道了)
      不光是交易的風控,我們日常制定規則,法規,公司流程,也一定要秉著這個出發點進行規劃。

    posted @ 2015-09-09 23:09 paulwong 閱讀(877) | 評論 (0)編輯 收藏

    Reactor模式詳解

    前記

    第一次聽到Reactor模式是三年前的某個晚上,一個室友突然跑過來問我什么是Reactor模式?我上網查了一下,很多人都是給出NIO中的 Selector的例子,而且就是NIO里Selector多路復用模型,只是給它起了一個比較fancy的名字而已,雖然它引入了EventLoop概 念,這對我來說是新的概念,但是代碼實現卻是一樣的,因而我并沒有很在意這個模式。然而最近開始讀Netty源碼,而Reactor模式是很多介紹Netty的文章中被大肆宣傳的模式,因而我再次問自己,什么是Reactor模式?本文就是對這個問題關于我的一些理解和嘗試著來解答。

    什么是Reactor模式

    要回答這個問題,首先當然是求助Google或Wikipedia,其中Wikipedia上說:“The reactor design pattern is an event handling pattern for handling service requests delivered concurrently by one or more inputs. The service handler then demultiplexes the incoming requests and dispatches them synchronously to associated request handlers.”。從這個描述中,我們知道Reactor模式首先是事件驅動的,有一個或多個并發輸入源,有一個Service Handler,有多個Request Handlers;這個Service Handler會同步的將輸入的請求(Event)多路復用的分發給相應的Request Handler。如果用圖來表達:

    從結構上,這有點類似生產者消費者模式,即有一個或多個生產者將事件放入一個Queue中,而一個或多個消費者主動的從這個Queue中Poll事件來處理;而Reactor模式則并沒有Queue來做緩沖,每當一個Event輸入到Service Handler之后,該Service Handler會主動的根據不同的Event類型將其分發給對應的Request Handler來處理。

    更學術的,這篇文章(Reactor An Object Behavioral Pattern for Demultiplexing and Dispatching Handles for Synchronous Events)上說:“The Reactor design pattern handles service requests that are delivered concurrently to an application by one or more clients. Each service in an application may consistent of several methods and is represented by a separate event handler that is responsible for dispatching service-specific requests. Dispatching of event handlers is performed by an initiation dispatcher, which manages the registered event handlers. Demultiplexing of service requests is performed by a synchronous event demultiplexer. Also known as Dispatcher, Notifier”。這段描述和Wikipedia上的描述類似,有多個輸入源,有多個不同的EventHandler(RequestHandler)來處理不同的請求,Initiation Dispatcher用于管理EventHander,EventHandler首先要注冊到Initiation Dispatcher中,然后Initiation Dispatcher根據輸入的Event分發給注冊的EventHandler;然而Initiation Dispatcher并不監聽Event的到來,這個工作交給Synchronous Event Demultiplexer來處理。

    Reactor模式結構

    在解決了什么是Reactor模式后,我們來看看Reactor模式是由什么模塊構成。圖是一種比較簡潔形象的表現方式,因而先上一張圖來表達各個模塊的名稱和他們之間的關系:

    Handle:即操作系統中的句柄,是對資源在操作系統層面上的一種抽象,它可以是打開的文件、一個連接(Socket)、Timer等。由于Reactor模式一般使用在網絡編程中,因而這里一般指Socket Handle,即一個網絡連接(Connection,在Java NIO中的Channel)。這個Channel注冊到Synchronous Event Demultiplexer中,以監聽Handle中發生的事件,對ServerSocketChannnel可以是CONNECT事件,對SocketChannel可以是READ、WRITE、CLOSE事件等。
    Synchronous Event Demultiplexer:阻塞等待一系列的Handle中的事件到來,如果阻塞等待返回,即表示在返回的Handle中可以不阻塞的執行返回的事件類型。這個模塊一般使用操作系統的select來實現。在Java NIO中用Selector來封裝,當Selector.select()返回時,可以調用Selector的selectedKeys()方法獲取Set<SelectionKey>,一個SelectionKey表達一個有事件發生的Channel以及該Channel上的事件類型。上圖的“Synchronous Event Demultiplexer ---notifies--> Handle”的流程如果是對的,那內部實現應該是select()方法在事件到來后會先設置Handle的狀態,然后返回。不了解內部實現機制,因而保留原圖。
    Initiation Dispatcher:用于管理Event Handler,即EventHandler的容器,用以注冊、移除EventHandler等;另外,它還作為Reactor模式的入口調用Synchronous Event Demultiplexer的select方法以阻塞等待事件返回,當阻塞等待返回時,根據事件發生的Handle將其分發給對應的Event Handler處理,即回調EventHandler中的handle_event()方法。
    Event Handler:定義事件處理方法:handle_event(),以供InitiationDispatcher回調使用。
    Concrete Event Handler:事件EventHandler接口,實現特定事件處理邏輯。

    Reactor模式模塊之間的交互

    簡單描述一下Reactor各個模塊之間的交互流程,先從序列圖開始:

    1. 初始化InitiationDispatcher,并初始化一個Handle到EventHandler的Map。
    2. 注冊EventHandler到InitiationDispatcher中,每個EventHandler包含對相應Handle的引用,從而建立Handle到EventHandler的映射(Map)。
    3. 調用InitiationDispatcher的handle_events()方法以啟動Event Loop。在Event Loop中,調用select()方法(Synchronous Event Demultiplexer)阻塞等待Event發生。
    4. 當某個或某些Handle的Event發生后,select()方法返回,InitiationDispatcher根據返回的Handle找到注冊的EventHandler,并回調該EventHandler的handle_events()方法。
    5. 在EventHandler的handle_events()方法中還可以向InitiationDispatcher中注冊新的Eventhandler,比如對AcceptorEventHandler來,當有新的client連接時,它會產生新的EventHandler以處理新的連接,并注冊到InitiationDispatcher中。

    Reactor模式實現

    Reactor An Object Behavioral Pattern for Demultiplexing and Dispatching Handles for Synchronous Events中,一直以Logging Server來分析Reactor模式,這個Logging Server的實現完全遵循這里對Reactor描述,因而放在這里以做參考。Logging Server中的Reactor模式實現分兩個部分:Client連接到Logging Server和Client向Logging Server寫Log。因而對它的描述分成這兩個步驟。
    Client連接到Logging Server

    1. Logging Server注冊LoggingAcceptor到InitiationDispatcher。
    2. Logging Server調用InitiationDispatcher的handle_events()方法啟動。
    3. InitiationDispatcher內部調用select()方法(Synchronous Event Demultiplexer),阻塞等待Client連接。
    4. Client連接到Logging Server。
    5. InitiationDisptcher中的select()方法返回,并通知LoggingAcceptor有新的連接到來。 
    6. LoggingAcceptor調用accept方法accept這個新連接。
    7. LoggingAcceptor創建新的LoggingHandler。
    8. 新的LoggingHandler注冊到InitiationDispatcher中(同時也注冊到Synchonous Event Demultiplexer中),等待Client發起寫log請求。
    Client向Logging Server寫Log

    1. Client發送log到Logging server。
    2. InitiationDispatcher監測到相應的Handle中有事件發生,返回阻塞等待,根據返回的Handle找到LoggingHandler,并回調LoggingHandler中的handle_event()方法。
    3. LoggingHandler中的handle_event()方法中讀取Handle中的log信息。
    4. 將接收到的log寫入到日志文件、數據庫等設備中。
    3.4步驟循環直到當前日志處理完成。
    5. 返回到InitiationDispatcher等待下一次日志寫請求。

    Reactor An Object Behavioral Pattern for Demultiplexing and Dispatching Handles for Synchronous Events有對Reactor模式的C++的實現版本,多年不用C++,因而略過。 

    Java NIO對Reactor的實現

    在Java的NIO中,對Reactor模式有無縫的支持,即使用Selector類封裝了操作系統提供的Synchronous Event Demultiplexer功能。這個Doug Lea已經在Scalable IO In Java中有非常深入的解釋了,因而不再贅述,另外這篇文章對Doug Lea的Scalable IO In Java有一些簡單解釋,至少它的代碼格式比Doug Lea的PPT要整潔一些。

    需要指出的是,不同這里使用InitiationDispatcher來管理EventHandler,在Doug Lea的版本中使用SelectionKey中的Attachment來存儲對應的EventHandler,因而不需要注冊EventHandler這個步驟,或者設置Attachment就是這里的注冊。而且在這篇文章中,Doug Lea從單線程的Reactor、Acceptor、Handler實現這個模式出發;演化為將Handler中的處理邏輯多線程化,實現類似Proactor模式,此時所有的IO操作還是單線程的,因而再演化出一個Main Reactor來處理CONNECT事件(Acceptor),而多個Sub Reactor來處理READ、WRITE等事件(Handler),這些Sub Reactor可以分別再自己的線程中執行,從而IO操作也多線程化。這個最后一個模型正是Netty中使用的模型。并且在Reactor An Object Behavioral Pattern for Demultiplexing and Dispatching Handles for Synchronous Events的9.5 Determine the Number of Initiation Dispatchers in an Application中也有相應的描述。

    EventHandler接口定義

    對EventHandler的定義有兩種設計思路:single-method設計和multi-method設計:
    A single-method interface:它將Event封裝成一個Event Object,EventHandler只定義一個handle_event(Event event)方法。這種設計的好處是有利于擴展,可以后來方便的添加新的Event類型,然而在子類的實現中,需要判斷不同的Event類型而再次擴展成 不同的處理方法,從這個角度上來說,它又不利于擴展。另外在Netty3的使用過程中,由于它不停的創建ChannelEvent類,因而會引起GC的不穩定。
    A multi-method interface:這種設計是將不同的Event類型在 EventHandler中定義相應的方法。這種設計就是Netty4中使用的策略,其中一個目的是避免ChannelEvent創建引起的GC不穩定, 另外一個好處是它可以避免在EventHandler實現時判斷不同的Event類型而有不同的實現,然而這種設計會給擴展新的Event類型時帶來非常 大的麻煩,因為它需要該接口。

    關于Netty4對Netty3的改進可以參考這里
    ChannelHandler with no event objectIn 3.x, every I/O operation created a ChannelEvent object. For each read / write, it additionally created a new ChannelBuffer. It simplified the internals of Netty quite a lot because it delegates resource management and buffer pooling to the JVM. However, it often was the root cause of GC pressure and uncertainty which are sometimes observed in a Netty-based application under high load.

    4.0 removes event object creation almost completely by replacing the event objects with strongly typed method invocations. 3.x had catch-all event handler methods such as handleUpstream() andhandleDownstream(), but this is not the case anymore. Every event type has its own handler method now:

    為什么使用Reactor模式

    歸功與Netty和Java NIO對Reactor的宣傳,本文慕名而學習的Reactor模式,因而已經默認Reactor具有非常優秀的性能,然而慕名歸慕名,到這里,我還是要不得不問自己Reactor模式的好處在哪里?即為什么要使用這個Reactor模式?在Reactor An Object Behavioral Pattern for Demultiplexing and Dispatching Handles for Synchronous Events中是這么說的:
    Reactor Pattern優點

    Separation of concerns: The Reactor pattern decouples application-independent demultiplexing and dispatching mechanisms from application-specific hook method functionality. The application-independent mechanisms become reusable components that know how to demultiplex events and dispatch the appropriate hook methods defined byEvent Handlers. In contrast, the application-specific functionality in a hook method knows how to perform a particular type of service.

    Improve modularity, reusability, and configurability of event-driven applications: The pattern decouples application functionality into separate classes. For instance, there are two separate classes in the logging server: one for establishing connections and another for receiving and processing logging records. This decoupling enables the reuse of the connection establishment class for different types of connection-oriented services (such as file transfer, remote login, and video-on-demand). Therefore, modifying or extending the functionality of the logging server only affects the implementation of the logging handler class.

    Improves application portability: The Initiation Dispatcher’s interface can be reused independently of the OS system calls that perform event demultiplexing. These system calls detect and report the occurrence of one or more events that may occur simultaneously on multiple sources of events. Common sources of events may in- clude I/O handles, timers, and synchronization objects. On UNIX platforms, the event demultiplexing system calls are calledselect and poll [1]. In the Win32 API [16], the WaitForMultipleObjects system call performs event demultiplexing.

    Provides coarse-grained concurrency control: The Reactor pattern serializes the invocation of event handlers at the level of event demultiplexing and dispatching within a process or thread. Serialization at the Initiation Dispatcher level often eliminates the need for more complicated synchronization or locking within an application process.

    這些貌似是很多模式的共性:解耦、提升復用性、模塊化、可移植性、事件驅動、細力度的并發控制等,因而并不能很好的說明什么,特別是它鼓吹的對性能的提升,這里并沒有體現出來。當然在這篇文章的開頭有描述過另一種直觀的實現:Thread-Per-Connection,即傳統的實現,提到了這個傳統實現的以下問題:
    Thread Per Connection缺點

    Efficiency: Threading may lead to poor performance due to context switching, synchronization, and data movement [2];

    Programming simplicity: Threading may require complex concurrency control schemes;

    Portability: Threading is not available on all OS platforms. 對于性能,它其實就是第一點關于Efficiency的描述,即線程的切換、同步、數據的移動會引起性能問題。也就是說從性能的角度上,它最大的提升就是減少了性能的使用,即不需要每個Client對應一個線程。我的理解,其他業務邏輯處理很多時候也會用到相同的線程,IO讀寫操作相對CPU的操作還是要慢很多,即使Reactor機制中每次讀寫已經能保證非阻塞讀寫,這里可以減少一些線程的使用,但是這減少的線程使用對性能有那么大的影響嗎?答案貌似是肯定的,這篇論文(SEDA: Staged Event-Driven Architecture - An Architecture for Well-Conditioned, Scalable Internet Service)對隨著線程的增長帶來性能降低做了一個統計:

    在這個統計中,每個線程從磁盤中讀8KB數據,每個線程讀同一個文件,因而數據本身是緩存在操作系統內部的,即減少IO的影響;所有線程是事先分配的,不會有線程啟動的影響;所有任務在測試內部產生,因而不會有網絡的影響。該統計數據運行環境:Linux 2.2.14,2GB內存,4-way 500MHz Pentium III。從圖中可以看出,隨著線程的增長,吞吐量在線程數為8個左右的時候開始線性下降,并且到64個以后而迅速下降,其相應事件也在線程達到256個后指數上升。即1+1<2,因為線程切換、同步、數據移動會有性能損失,線程數增加到一定數量時,這種性能影響效果會更加明顯。

    對于這點,還可以參考C10K Problem,用以描述同時有10K個Client發起連接的問題,到2010年的時候已經出現10M Problem了。

    當然也有人說:Threads are expensive are no longer valid.在不久的將來可能又會發生不同的變化,或者這個變化正在、已經發生著?沒有做過比較仔細的測試,因而不敢隨便斷言什么,然而本人觀點,即使線程變的影響并沒有以前那么大,使用Reactor模式,甚至時SEDA模式來減少線程的使用,再加上其他解耦、模塊化、提升復用性等優點,還是值得使用的。

    Reactor模式的缺點

    Reactor模式的缺點貌似也是顯而易見的:
    1. 相比傳統的簡單模型,Reactor增加了一定的復雜性,因而有一定的門檻,并且不易于調試。
    2. Reactor模式需要底層的Synchronous Event Demultiplexer支持,比如Java中的Selector支持,操作系統的select系統調用支持,如果要自己實現Synchronous Event Demultiplexer可能不會有那么高效。
    3. Reactor模式在IO讀寫數據時還是在同一個線程中實現的,即使使用多個Reactor機制的情況下,那些共享一個Reactor的Channel如果出現一個長時間的數據讀寫,會影響這個Reactor中其他Channel的相應時間,比如在大文件傳輸時,IO操作就會影響其他Client的相應時間,因而對這種操作,使用傳統的Thread-Per-Connection或許是一個更好的選擇,或則此時使用Proactor模式。

    參考

    Reactor Pattern WikiPedia
    Reactor An Object Behavioral Pattern for Demultiplexing and Dispatching Handles for Synchronous Events
    Scalable IO In Java
    C10K Problem WikiPedia

    posted @ 2015-09-08 12:28 paulwong 閱讀(721) | 評論 (0)編輯 收藏

    Netty3架構解析

    前記

    很早以前就有讀Netty源碼的打算了,然而第一次嘗試的時候從Netty4開始,一直抓不到核心的框架流程,后來因為其他事情忙著就放下了。這次趁著休假重新撿起這個硬骨頭,因為Netty3現在還在被很多項目使用,因而這次決定先從Netty3入手,瞬間發現Netty3的代碼比Netty4中規中矩的多,很多概念在代碼本身中都有清晰的表達,所以半天就把整個框架的骨架搞清楚了。再讀Netty4對Netty3的改進總結,回去讀Netty4的源碼,反而覺得輕松了,一種豁然開朗的感覺。

    記得去年讀Jetty源碼的時候,因為代碼太龐大,并且自己的HTTP Server的了解太少,因而只能自底向上的一個一個模塊的疊加,直到最后把所以的模塊連接在一起而看清它的真正核心骨架。現在讀源碼,開始習慣先把骨架理清,然后延伸到不同的器官、血肉而看清整個人體。

    本文從Reactor模式在Netty3中的應用,引出Netty3的整體架構以及控制流程;然而除了Reactor模式,Netty3還在ChannelPipeline中使用了Intercepting Filter模式,這個模式也在Servlet的Filter中成功使用,因而本文還會從Intercepting Filter模式出發詳細介紹ChannelPipeline的設計理念。本文假設讀者已經對Netty有一定的了解,因而不會包含過多入門介紹,以及幫Netty做宣傳的文字。

    Netty3中的Reactor模式

    Reactor模式在Netty中應用非常成功,因而它也是在Netty中受大肆宣傳的模式,關于Reactor模式可以詳細參考本人的另一篇文章《Reactor模式詳解》,對Reactor模式的實現是Netty3的基本骨架,因而本小節會詳細介紹Reactor模式如何應用Netty3中。

    如果讀《Reactor模式詳解》,我們知道Reactor模式由Handle、Synchronous Event Demultiplexer、Initiation Dispatcher、Event Handler、Concrete Event Handler構成,在Java的實現版本中,Channel對應Handle,Selector對應Synchronous Event Demultiplexer,并且Netty3還使用了兩層Reactor:Main Reactor用于處理Client的連接請求,Sub Reactor用于處理和Client連接后的讀寫請求(關于這個概念還可以參考Doug Lea的這篇PPT:Scalable IO In Java)。所以我們先要解決Netty3中使用什么類實現所有的上述模塊并把他們聯系在一起的,以NIO實現方式為例:

    模式是一種抽象,但是在實現中,經常會因為語言特性、框架和性能需要而做一些改變,因而Netty3對Reactor模式的實現有一套自己的設計:

    1. ChannelEvent:Reactor是基于事件編程的,因而在Netty3中使用ChannelEvent抽象的表達Netty3內部可以產生的各種事件,所有這些事件對象在Channels幫助類中產生,并且由它將事件推入到ChannelPipeline中,ChannelPipeline構建ChannelHandler管道,ChannelEvent流經這個管道實現所有的業務邏輯處理。ChannelEvent對應的事件有:ChannelStateEvent表示Channel狀態的變化事件,而如果當前Channel存在Parent Channel,則該事件還會傳遞到Parent Channel的ChannelPipeline中,如OPEN、BOUND、CONNECTED、INTEREST_OPS等,該事件可以在各種不同實現的Channel、ChannelSink中產生;MessageEvent表示從Socket中讀取數據完成、需要向Socket寫數據或ChannelHandler對當前Message解析(如Decoder、Encoder)后觸發的事件,它由NioWorker、需要對Message做進一步處理的ChannelHandler產生;WriteCompletionEvent表示寫完成而觸發的事件,它由NioWorker產生;ExceptionEvent表示在處理過程中出現的Exception,它可以發生在各個構件中,如Channel、ChannelSink、NioWorker、ChannelHandler中;IdleStateEvent由IdleStateHandler觸發,這也是一個ChannelEvent可以無縫擴展的例子。注:在Netty4后,已經沒有ChannelEvent類,所有不同事件都用對應方法表達,這也意味這ChannelEvent不可擴展,Netty4采用在ChannelInboundHandler中加入userEventTriggered()方法來實現這種擴展,具體可以參考這里

    2. ChannelHandler:在Netty3中,ChannelHandler用于表示Reactor模式中的EventHandler。ChannelHandler只是一個標記接口,它有兩個子接口:ChannelDownstreamHandler和ChannelUpstreamHandler,其中ChannelDownstreamHandler表示從用戶應用程序流向Netty3內部直到向Socket寫數據的管道,在Netty4中改名為ChannelOutboundHandler;ChannelUpstreamHandler表示數據從Socket進入Netty3內部向用戶應用程序做數據處理的管道,在Netty4中改名為ChannelInboundHandler。

    3. ChannelPipeline:用于管理ChannelHandler的管道,每個Channel一個ChannelPipeline實例,可以運行過程中動態的向這個管道中添加、刪除ChannelHandler(由于實現的限制,在最末端的ChannelHandler向后添加或刪除ChannelHandler不一定在當前執行流程中起效,參考這里)。ChannelPipeline內部維護一個ChannelHandler的雙向鏈表,它以Upstream(Inbound)方向為正向,Downstream(Outbound)方向為方向。ChannelPipeline采用Intercepting Filter模式實現,具體可以參考這里,這個模式的實現在后一節中還是詳細介紹。

    4. NioSelector:Netty3使用NioSelector來存放Selector(Synchronous Event Demultiplexer),每個新產生的NIO Channel都向這個Selector注冊自己以讓這個Selector監聽這個NIO Channel中發生的事件,當事件發生時,調用幫助類Channels中的方法生成ChannelEvent實例,將該事件發送到這個Netty Channel對應的ChannelPipeline中,而交給各級ChannelHandler處理。其中在向Selector注冊NIO Channel時,Netty Channel實例以Attachment的形式傳入,該Netty Channel在其內部的NIO Channel事件發生時,會以Attachment的形式存在于SelectionKey中,因而每個事件可以直接從這個Attachment中獲取相關鏈的Netty Channel,并從Netty Channel中獲取與之相關聯的ChannelPipeline,這個實現和Doug Lea的Scalable IO In Java一模一樣。另外Netty3還采用了Scalable IO In Java中相同的Main Reactor和Sub Reactor設計,其中NioSelector的兩個實現:Boss即為Main Reactor,NioWorker為Sub Reactor。Boss用來處理新連接加入的事件,NioWorker用來處理各個連接對Socket的讀寫事件,其中Boss通過NioWorkerPool獲取NioWorker實例,Netty3模式使用RoundRobin方式放回NioWorker實例。更形象一點的,可以通過Scalable IO In Java的這張圖表達:


    若與Ractor模式對應,NioSelector中包含了Synchronous Event Demultiplexer,而ChannelPipeline中管理著所有EventHandler,因而NioSelector和ChannelPipeline共同構成了Initiation Dispatcher。

    5. ChannelSink:在ChannelHandler處理完成所有邏輯需要向客戶端寫響應數據時,一般會調用Netty Channel中的write方法,然而在這個write方法實現中,它不是直接向其內部的Socket寫數據,而是交給Channels幫助類,內部創建DownstreamMessageEvent,反向從ChannelPipeline的管道中流過去,直到第一個ChannelHandler處理完畢,最后交給ChannelSink處理,以避免阻塞寫而影響程序的吞吐量。ChannelSink將這個MessageEvent提交給Netty Channel中的writeBufferQueue,最后NioWorker會等到這個NIO Channel已經可以處理寫事件時無阻塞的向這個NIO Channel寫數據。這就是上圖的send是從SubReactor直接出發的原因。

    6. Channel:Netty有自己的Channel抽象,它是一個資源的容器,包含了所有一個連接涉及到的所有資源的飲用,如封裝NIO Channel、ChannelPipeline、Boss、NioWorkerPool等。另外它還提供了向內部NIO Channel寫響應數據的接口write、連接/綁定到某個地址的connect/bind接口等,個人感覺雖然對Channel本身來說,因為它封裝了NIO Channel,因而這些接口定義在這里是合理的,但是如果考慮到Netty的架構,它的Channel只是一個資源容器,有這個Channel實例就可以得到和它相關的基本所有資源,因而這種write、connect、bind動作不應該再由它負責,而是應該由其他類來負責,比如在Netty4中就在ChannelHandlerContext添加了write方法,雖然netty4并沒有刪除Channel中的write接口。

    Netty3中的Intercepting Filter模式

    如果說Reactor模式是Netty3的骨架,那么Intercepting Filter模式則是Netty的中樞。Reactor模式主要應用在Netty3的內部實現,它是Netty3具有良好性能的基礎,而Intercepting Filter模式則是ChannelHandler組合實現一個應用程序邏輯的基礎,只有很好的理解了這個模式才能使用好Netty,甚至能得心應手。

    關于Intercepting Filter模式的詳細介紹可以參考這里,本節主要介紹Netty3中對Intercepting Filter模式的實現,其實就是DefaultChannelPipeline對Intercepting Filter模式的實現。在上文有提到Netty3的ChannelPipeline是ChannelHandler的容器,用于存儲與管理ChannelHandler,同時它在Netty3中也起到橋梁的作用,即它是連接Netty3內部到所有ChannelHandler的橋梁。作為ChannelPipeline的實現者DefaultChannelPipeline,它使用一個ChannelHandler的雙向鏈表來存儲,以DefaultChannelPipelineContext作為節點:

    public interface ChannelHandlerContext {
        Channel getChannel();
        ChannelPipeline getPipeline();
        String getName();
        ChannelHandler getHandler();
        boolean canHandleUpstream();
        boolean canHandleDownstream();
        void sendUpstream(ChannelEvent e);
        void sendDownstream(ChannelEvent e);
        Object getAttachment();
        void setAttachment(Object attachment);
    }

    private final class DefaultChannelHandlerContext implements ChannelHandlerContext {
        volatile DefaultChannelHandlerContext next;
        volatile DefaultChannelHandlerContext prev;
        private final String name;
        private final ChannelHandler handler;
        private final boolean canHandleUpstream;
        private final boolean canHandleDownstream;
        private volatile Object attachment;
    ..
    }

    在DefaultChannelPipeline中,它存儲了和當前ChannelPipeline相關聯的Channel、ChannelSink以及ChannelHandler鏈表的head、tail,所有ChannelEvent通過sendUpstream、sendDownstream為入口流經整個鏈表:

    public class DefaultChannelPipeline implements ChannelPipeline {
        private volatile Channel channel;
        private volatile ChannelSink sink;
        private volatile DefaultChannelHandlerContext head;
        private volatile DefaultChannelHandlerContext tail;

        public void sendUpstream(ChannelEvent e) {
            DefaultChannelHandlerContext head = getActualUpstreamContext(this.head);
            if (head == null) {
                return;
            }
            sendUpstream(head, e);
        }

        void sendUpstream(DefaultChannelHandlerContext ctx, ChannelEvent e) {
            try {
                ((ChannelUpstreamHandler) ctx.getHandler()).handleUpstream(ctx, e);
            } catch (Throwable t) {
                notifyHandlerException(e, t);
            }
        }

        public void sendDownstream(ChannelEvent e) {
            DefaultChannelHandlerContext tail = getActualDownstreamContext(this.tail);
            if (tail == null) {
                try {
                    getSink().eventSunk(this, e);
                    return;
                } catch (Throwable t) {
                    notifyHandlerException(e, t);
                    return;
                }
            }
            sendDownstream(tail, e);
        }

        void sendDownstream(DefaultChannelHandlerContext ctx, ChannelEvent e) {
            if (e instanceof UpstreamMessageEvent) {
                throw new IllegalArgumentException("cannot send an upstream event to downstream");
            }
            try {
                ((ChannelDownstreamHandler) ctx.getHandler()).handleDownstream(ctx, e);
            } catch (Throwable t) {
                e.getFuture().setFailure(t);
                notifyHandlerException(e, t);
            }
        }

    對Upstream事件,向后找到所有實現了ChannelUpstreamHandler接口的ChannelHandler組成鏈(
    getActualUpstreamContext()),而對Downstream事件,向前找到所有實現了ChannelDownstreamHandler接口的ChannelHandler組成鏈(getActualDownstreamContext()):

        private DefaultChannelHandlerContext getActualUpstreamContext(DefaultChannelHandlerContext ctx) {
            if (ctx == null) {
                return null;
            }
            DefaultChannelHandlerContext realCtx = ctx;
            while (!realCtx.canHandleUpstream()) {
                realCtx = realCtx.next;
                if (realCtx == null) {
                    return null;
                }
            }
            return realCtx;
        }
        private DefaultChannelHandlerContext getActualDownstreamContext(DefaultChannelHandlerContext ctx) {
            if (ctx == null) {
                return null;
            }
            DefaultChannelHandlerContext realCtx = ctx;
            while (!realCtx.canHandleDownstream()) {
                realCtx = realCtx.prev;
                if (realCtx == null) {
                    return null;
                }
            }
            return realCtx;
        }

    在實際實現ChannelUpstreamHandler或ChannelDownstreamHandler時,調用 ChannelHandlerContext中的sendUpstream或sendDownstream方法將控制流程交給下一個 ChannelUpstreamHandler或下一個ChannelDownstreamHandler,或調用Channel中的write方法發送 響應消息。

    public class MyChannelUpstreamHandler implements ChannelUpstreamHandler {
        public void handleUpstream(ChannelHandlerContext ctx, ChannelEvent e) throws Exception {
            // handle current logic, use Channel to write response if needed.
            
    // ctx.getChannel().write(message);
            ctx.sendUpstream(e);
        }
    }

    public class MyChannelDownstreamHandler implements ChannelDownstreamHandler {
        public void handleDownstream(
                ChannelHandlerContext ctx, ChannelEvent e) throws Exception {
            // handle current logic
            ctx.sendDownstream(e);
        }
    }

    當ChannelHandler向ChannelPipelineContext發送事件時,其內部從當前ChannelPipelineContext節點出發找到下一個ChannelUpstreamHandler或ChannelDownstreamHandler實例,并向其發送ChannelEvent,對于Downstream鏈,如果到達鏈尾,則將ChannelEvent發送給ChannelSink:

    public void sendDownstream(ChannelEvent e) {
        DefaultChannelHandlerContext prev = getActualDownstreamContext(this.prev);
        if (prev == null) {
            try {
                getSink().eventSunk(DefaultChannelPipeline.this, e);
            } catch (Throwable t) {
                notifyHandlerException(e, t);
            }
        } else {
            DefaultChannelPipeline.this.sendDownstream(prev, e);
        }
    }

    public void sendUpstream(ChannelEvent e) {
        DefaultChannelHandlerContext next = getActualUpstreamContext(this.next);
        if (next != null) {
            DefaultChannelPipeline.this.sendUpstream(next, e);
        }
    }

    正是因為這個實現,如果在一個末尾的ChannelUpstreamHandler中先移除自己,在向末尾添加一個新的ChannelUpstreamHandler,它是無效的,因為它的next已經在調用前就固定設置為null了。


    ChannelPipeline作為ChannelHandler的容器,它還提供了各種增、刪、改ChannelHandler鏈表中的方法,而且如果某個ChannelHandler還實現了LifeCycleAwareChannelHandler,則該ChannelHandler在被添加進ChannelPipeline或從中刪除時都會得到同志:

    public interface LifeCycleAwareChannelHandler extends ChannelHandler {
        void beforeAdd(ChannelHandlerContext ctx) throws Exception;
        void afterAdd(ChannelHandlerContext ctx) throws Exception;
        void beforeRemove(ChannelHandlerContext ctx) throws Exception;
        void afterRemove(ChannelHandlerContext ctx) throws Exception;
    }

    public interface ChannelPipeline {
        void addFirst(String name, ChannelHandler handler);
        void addLast(String name, ChannelHandler handler);
        void addBefore(String baseName, String name, ChannelHandler handler);
        void addAfter(String baseName, String name, ChannelHandler handler);
        void remove(ChannelHandler handler);
        ChannelHandler remove(String name);
        <T extends ChannelHandler> T remove(Class<T> handlerType);
        ChannelHandler removeFirst();
        ChannelHandler removeLast();
        void replace(ChannelHandler oldHandler, String newName, ChannelHandler newHandler);
        ChannelHandler replace(String oldName, String newName, ChannelHandler newHandler);
        <T extends ChannelHandler> T replace(Class<T> oldHandlerType, String newName, ChannelHandler newHandler);
        ChannelHandler getFirst();
        ChannelHandler getLast();
        ChannelHandler get(String name);
        <T extends ChannelHandler> T get(Class<T> handlerType);
        ChannelHandlerContext getContext(ChannelHandler handler);
        ChannelHandlerContext getContext(String name);
        ChannelHandlerContext getContext(Class<? extends ChannelHandler> handlerType);
        void sendUpstream(ChannelEvent e);
        void sendDownstream(ChannelEvent e);
        ChannelFuture execute(Runnable task);
        Channel getChannel();
        ChannelSink getSink();
        void attach(Channel channel, ChannelSink sink);
        boolean isAttached();
        List<String> getNames();
        Map<String, ChannelHandler> toMap();
    }

    在DefaultChannelPipeline的ChannelHandler鏈條的處理流程為:


    http://www.tkk7.com/DLevin/archive/2015/09/04/427031.html


    參考:

    《Netty主頁》
    《Netty源碼解讀(四)Netty與Reactor模式》
    《Netty代碼分析》
    Scalable IO In Java
    Intercepting Filter Pattern

    posted @ 2015-09-08 11:11 paulwong 閱讀(548) | 評論 (0)編輯 收藏

    最新版BOOTSTRAP 4.0教學


    http://wiki.jikexueyuan.com/project/bootstrap4/

    posted @ 2015-09-02 10:56 paulwong 閱讀(518) | 評論 (0)編輯 收藏

    張左峰的歪理邪說 之 Redmine 1.4.X 史上最全插件方案

    原創作品,允許轉載,轉載時請務必以超鏈接形式標明文章 原始出處 、作者信息和本聲明。否則將追究法律責任。http://salivaxiu.blog.51cto.com/330680/1613510

    前面有一個0.9.X的插件推薦列表,但是太老了,更新一下!

     

    PS.很多插件是我自己漢化,甚至付費的,并且進行小幅修改,所以僅供參考。。。。。

     

    PPS.話說,搞Redmine這幫人,真是一群瘋子,更新太快了。。。。。就不敢更新慢點么。。。。。。

     

    Advanced roadmap & milestones plugin  0.7.0  高級路線圖,里程碑,不解釋
    Author box  0.0.3  這個可以在Wiki側邊欄顯示一個你的頭像和信息,搞個人崇拜用的
    CRM plugin 2.3.3-pro CRM插件,很好用,也很專業,付費的
    Due Date Reminder plugin  0.2.1 超期提醒插件
    Issue Hot Buttons Plugin  0.4.5 超級有用的插件,快速自定義功能按鈕,很方便更新問題,尤其是一兩項的時候
    Redmine Add Subversion Links  0.0.5 為版本庫增加直接的SVN鏈接
    Redmine Assets plugin  0.0.1 軟資產管理,各位親們,如果你有幾萬個問題,里賣包含圖片文檔啥的,你要找一個特痛苦吧?這個插件幫你解決
    Redmine Banner plugin 0.0.6 公告。。。超級實用。。。可以針對全局,也可以針對項目,發出公告
    Redmine Better Gantt Chart plugin 0.6.5 更好的甘特圖。。的確更好
    Redmine CKEditor plugin 0.0.6 超級棒的Textile插件,替換Redmine那個簡單的文本編輯器
    Redmine Code Review plugin 0.4.8 代碼評審,不解釋
    Redmine Default Version plugin 0.0.2 默認版本,給新建問題設置一個默認版本
    Digest plugin 0.2.0 一個后臺發送項目活動信息的插件,做日報,或者什么匯報用的DMSF 1.2.3 網頁版的SVN,文檔管家,支持HTML5,超級好用,但是界面稍顯臃腫
    Redmine Doodles plugin 0.5.1 投票,不解釋
    Redmine Glossary Plugin 0.7.0 名詞解釋插件,知識管理一部分
    Redmine Good Job plugin 0.1.1 Goodjob....耍帥用的
    Issue Importer 1.0 導入插件,超級實用,如果你有多個工作系統,切換來切換去,離不開這玩意,但是不建議新手使用,批量出錯,特痛苦。
    Redmine Information Plugin 0.2.5 這個也挺有用的,把系統的一些信息開放出來,比如宏,讓大家參考
    Redmine Attach Screenshot plugin 0.4.2 附件截圖,好似用了Java
    Redmine Issue Checklist plugin 1.0.3 Checklist,這個還解釋么
    Redmine Issue Extensions plugin 0.1.0 問題擴展插件,官方都用的,必須用啊
    Redmine Issue Templates plugin 0.0.2.1 這是一個比較好用的插件,問題模板,尤其是一些重復性高,但是需要對過程管理的,配合
    ChecklistKnowledgebase 1.0.0 知識庫。。。我覺得也很實用的,不過有個BUG,需要你在數據庫指定關聯一下項目
    List duplicate views plugin 0.0.5 好似可以檢測到插件沖突。。。我就遇到過
    Redmine Local Avatars plugin 0.1.1 本地頭像,哈哈,我們斷網的
    Redmine Logs plugin 0.0.3 可以再后臺直接看日志,不用登陸到服務器了
    Redmine (Monitoring & Controlling | Monitoramento & Controle) 0.1.1 圖形化的數據分析插件,裝B用的(當然也有實際意義,宏觀數據)
    My Roadmaps plugin 0.1.12 我的路線圖。。。。其實沒啥用
    Redmine News Balloon plugin 0.0.1 如果有新聞,會彈出一個氣泡,提醒你
    Niko-niko Calendar plugin 1.1.2 我最最最最喜歡的插件,感覺跟每天微博一樣,挺有意思的
    Redmine plugin views revisions plugin 0.0.1 忘了。。。。。
    Preview attached files and attributes column plugin 0.1.7 附件的那個縮略圖
    Private Wiki 0.1.1 私有維基,Redmine最大的問題,在于權限管理不夠細分,多這么一個插件,就相當增加一項特殊權限
    Reorder links arbitrary 0.0.7 這個對管理員設置有幫助,可以快速設置排序
    Smart issues sort plugin 0.3.1 智能排序,對經常用父任務的人,幫助很大
    My Page Blocks plugin 1.2 (20120610) 自定義我的工作臺,很好使!
    Redmine Wiki Extensions plugin 0.4.1 維基擴展,不解釋,官方都用
    Wiki sidebar toc plugin 0.0.1 維基側邊欄吧?想不起來了
    Redmine Wiki table of contents plugin 0.0.3 維基快速位置排序
    Issues XLS export 0.2.1  問題列表XLS導出

     

    我還沒用的,但是考慮裝上試試的列表,都是1.4.X可以用的

     

    用戶屬性 http://www.redmine.org/plugins/userprofile
    會議室預定 http://www.redmine.org/plugins/mmqb
    費用、分票 http://www.redmine.org/plugins/invoices       需付費,可以考慮
    免費發票 http://www.redmine.org/plugins/haltr
    我的技能 http://www.redmine.org/plugins/redmine_coderwall
    甘特圖日期 http://www.redmine.org/plugins/redmine_gantt_with_date
    登陸后跳轉 http://www.redmine.org/plugins/landing_page
    Wiki加密 http://www.redmine.org/plugins/redmine_wikicipher     可以考慮
    Email過濾 http://www.redmine.org/plugins/redmine_email_notification_content_filter  可以考慮
    另一個截圖 http://www.redmine.org/plugins/javasript_screenshot
    項目側邊欄 http://www.redmine.org/plugins/sidebar       可以考慮
    匿名觀察 http://www.redmine.org/plugins/anonymous-watchers     可以考慮
    你的意思 http://www.redmine.org/plugins/didyoumean      可以考慮
    隱身模式 http://www.redmine.org/plugins/redmine_stealth      可以考慮
    發行說明 http://www.redmine.org/plugins/redmine_release_notes

    論壇主題 http://www.redmine.org/plugins/redmine_boards
    自定義郵件 http://www.redmine.org/plugins/notify_custom_users
    日歷假期 http://www.redmine.org/plugins/redmine_multi_calendar
    多種上傳 http://www.redmine.org/plugins/redmine_multiple_files_upload

    Wiki
    維基按鈕 http://www.redmine.org/plugins/redmine_wiki_files_toolbar

    Time
    時間聯系 http://www.redmine.org/plugins/linked_time_entries

    版本庫
    SCM擴展  http://www.redmine.org/plugins/redmine_scm_extensions
    編輯維基 http://www.redmine.org/plugins/redmine_require_wiki_comment

    posted @ 2015-08-19 10:26 paulwong 閱讀(2236) | 評論 (0)編輯 收藏

    僅列出標題
    共115頁: First 上一頁 33 34 35 36 37 38 39 40 41 下一頁 Last 
    主站蜘蛛池模板: 最新亚洲人成网站在线观看 | 国产精品白浆在线观看免费| 国产成人精品亚洲2020| 亚洲精品无码不卡在线播HE| 四虎影视www四虎免费| 8x成人永久免费视频| 久久免费99精品国产自在现线| 亚洲最大av资源站无码av网址| 久久亚洲精品中文字幕| 亚洲AV无码专区国产乱码4SE| 亚洲第一黄色网址| 国产精品冒白浆免费视频| 在线观看av永久免费| 2021在线观看视频精品免费| 91视频免费网站| 久久久免费观成人影院| 成人免费网站视频www| 亚洲A∨精品一区二区三区下载| 亚洲乱码在线播放| 亚洲色欲www综合网| 亚洲欧洲免费视频| 亚洲阿v天堂在线| 精品久久香蕉国产线看观看亚洲| 免费大黄网站在线观看| 国产午夜影视大全免费观看 | 亚洲欧美日韩中文高清www777 | 亚洲一区免费视频| 亚洲精品免费在线视频| 91在线手机精品免费观看| 久久国产色AV免费看| 99热在线精品免费播放6| 无码日韩精品一区二区三区免费| 免费一区二区无码东京热| 欧洲人免费视频网站在线| 免费国产成人午夜在线观看| 免费精品99久久国产综合精品| 国产午夜无码精品免费看| 久久午夜无码免费| 麻豆国产精品免费视频| 四虎永久在线观看免费网站网址 | 亚洲乱人伦中文字幕无码|