標籤:軟體架構 架構風格 訊息佇列 事件驅動 事件觸發
今天和大家聊聊軟體的架構風格。所謂的軟體架構風格,就是一種可以重複利用的軟體結構模式,其最大的作用是用相同的結構解決某一特殊領域的問題。如著名的三層B/S架構設計,其主要目的就是為瞭解決Web系統服務端與用戶端的高耦合與維護成本高的問題。使用B/S三層架構模式,實現了服務端與用戶端的分離,真正的實現了零用戶端 ,使使用者在軟體升級時更方便,提高了軟體的可修改性。而伺服器端的三層結構設計,對邏輯層、表現層與資料層進行了分離,不僅方便系統的維護,而且提高了系統的可擴充性。由於有了這麼多的優點,當今幾乎所有的Web系統都是用了MVC的概念進行設計。所以MVC就是一種軟體架構風格,它解決了Web系統這一領域的問題。
當然,無論是B/S系統還是C/S系統。只用一種架構風格是解決不了所有問題的。每種架構風格只能解決一些特定的問題,同時也存在一些不足之處。還是以三層B/S架構為例,雖然有以上提到的一些優點,但也存在動態互動能力不強(B/S系統以頁面為單位提交資料)、安全性差(容易被注入,如果使用非SSL連結,則傳輸過程中資料容易被竊取)、資料查詢速度低(由於採用B/S三層架構模式串連資料庫只能使用TCP的方式串連,查詢速度會受到網路傳輸速度影響)等缺點。所以在設計系統時,應採用多種架構風格結合的方式,取長補短,按需配置。
除了我們非常熟悉的三層B/S架構、三層C/S架構外。還有一些架構風格也經常會被用到,如訊息佇列架構風格、過濾器架構風格、倉庫風格、事件驅動風格等。三層架構大家應該已經非常熟悉了,對應的各種語言的架構也有很多,在此就不在贅述。倉庫風格與過濾器風格在Web系統上用的不多,在下也不是很熟悉,所以也不在此討論。下面主要介紹下訊息佇列架構與事件驅動架構的實現。當然,以下是我自己的理解,如有不對,還請指正。
訊息佇列可以解決很多問題,比如12306的網上購票業務。在過年過節的時候會有大量使用者高並發的訪問網站,如果每個連結都開啟一個處理進程,恐怕再多的伺服器也承受不了。因為在一個連結開啟時,伺服器就需要開始處理邏輯,處理邏輯就會消耗伺服器資源,同時的高並發處理會瞬間使伺服器掛掉。這時,如果一個使用者串連到來,系統只是將使用者的請求放到一個隊列裡就關閉串連或進行其它邏輯的處理。而放到隊列裡的請求,則交給佇列服務器依次處理,處理完畢後,可以將處理結果直接通知給使用者,也可等使用者自己來擷取。這樣伺服器消耗的資源就會降低很多。而由於是非同步處理,用戶端也不必等待伺服器執行完相應的邏輯就可以執行其它事務,增加了效率。所以,訊息佇列主要解決了2類問題,一類是排隊問題,有先後次序處理要求的問題。第二類是非同步處理問題,需要非同步處理以節省伺服器資源及增加效率的問題。以下是我寫的一個訊息佇列的代碼範例,由於訊息佇列風格與事件驅動風格都涉及到處理序間通訊,為了方便,我將需要互動的變數寫在memcache裡了。
首先建立項目目錄,目錄名為queue。以下所有檔案均在queue目錄下。
建立檔案index.php
<?php set_time_limit(0); session_start(); $memcache = new Memcache(); $memcache->addServer(‘127.0.0.1‘,11211); $position = $_GET[‘r‘]; if(!isset($_SESSION[‘uuid‘])){//判斷用戶端是否執行完該任務 $uuid = time()+rand(0,9); $_SESSION[‘uuid‘]=$uuid; $arr = array($position,$uuid); $queue = $memcache->get(‘queue‘); $queue[] = $arr; $memcache->set(‘queue‘,$queue); //將請求排入佇列 } $now_queue = $memcache->get(‘queue‘); //取得目前隊列中所有的請求 if(!empty($now_queue)){ foreach($now_queue as $k=>$q){ if($q[1]==$_SESSION[‘uuid‘]){//根據uuid session判斷前面還需等待幾個任務執行。 if(!isset($_SESSION[‘js‘]) OR $_SESSION[‘js‘]!=$k){ $_SESSION[‘js‘] = $k; $flag = true; echo ‘前面有‘.($k+1).‘個任務在執行,請等待!!‘."\n"; } break; } } } $rs = $memcache->get($_SESSION[‘uuid‘]);//取任務執行的結果 if(!empty($rs)){//如果取得到結果,則登出這個任務 unset($_SESSION[‘js‘]); unset($_SESSION[‘uuid‘]); print_r($rs); }else if(!isset($flag) OR !$flag){ echo ‘前面有‘.($_SESSION[‘js‘]+1).‘個任務在執行,請等待!!‘."\n"; } ?>
建立檔案service.php
<?php $memcache = new Memcache(); $memcache->addServer(‘127.0.0.1‘,11211); for(;;){//處理隊列請求 $queue = $memcache->get(‘queue‘); if(!empty($queue)){ $handle = array_shift($queue); $memcache->set(‘queue‘,$queue); if(function_exists($handle[0])){ $rs = $handle[0](); $memcache->set($handle[1],$rs); } } } //具體請求1 function fun1(){ sleep(3); return array(‘fun1‘); } //具體請求2 function fun2(){ sleep(3); return array(‘fun2‘); } ?>
首先在控制台執行service.php。/yourpath/php /yourpath/queue/service.php。
然後在瀏覽器執行http://localhost/queue/index.php?r=fun1或http://localhost/queue/index.php?r=fun2,r=fun1時,將執行fun1的任務排入佇列,r=fun2時,將執行fun2的任務排入佇列。排入佇列的任務不會並發完成,而是依次完成,並且是與用戶端非同步。
事件驅動模式主要解決觸發式需求。比如當滑鼠點擊某一按鈕時會觸發一系列的處理過程。事件驅動系統風格是用戶端不直接調用方法,而是觸發或廣播一個或多個事件。系統中的方法在一個或多個事件中註冊。當一個事件被觸發,系統自動調用在這個事件中註冊的所有方法。“滑鼠點擊按鈕”稱為事件,相應的處理過程則是被調用的方法。那麼,為什麼要這麼設計呢?個人覺得還是解耦的需要。根據迪米特法則,“如果兩個類不必彼此直接通訊,那麼這兩個類就不應當發生直接的相互作用。如果其中一個類需要調用另一個類的某一個方法的話,可以通過第三者轉寄這個調用。”。在滑鼠點擊按鈕執行處理過程這一情境下,用戶端可以不直接和觸發的方法產生互動,而是只用設定“滑鼠點擊按鈕”這一事件即可。在實際應用中,我們可以將用戶端事件設定,事件觸發方法這兩部進行物理分離,用兩個進程來非同步處理,進一步解耦。以下是我寫的一個事件驅動模式的代碼範例:
首先建立項目目錄,目錄名為event。以下所有檔案均在event目錄下。
建立client.php檔案
<?phprequire ‘event.php‘;$event = new event();$event->setEvent3(true); //觸發event事件?>
建立event.php檔案
<?php //定義事件 class event{ private $memcache; public function __construct(){ $this->memcache = new Memcache(); $this->memcache->addServer(‘127.0.0.1‘,11211); } public function setEvent1($open){ $this->memcache->set(‘event1‘,$open); } public function setEvent2($open){ $this->memcache->set(‘event2‘,$open); } public function setEvent3($open){ $this->memcache->set(‘event3‘,$open); } public function getEvent1(){ return $this->memcache->get(‘event1‘); } public function getEvent2(){ return $this->memcache->get(‘event2‘); } public function getEvent3(){ return $this->memcache->get(‘event3‘); } }?>
建立function.php檔案
<?php //定義事件觸發執行的方法 class process{ public function fun1(){ echo "process1 starting !!!...\n"; } public function fun2(){ echo "process2 starting !!!...\n"; } public function fun3(){ echo "process3 starting !!!...\n"; } public function fun4(){ echo "process4 starting !!!...\n"; } public function fun5(){ echo "process5 starting !!!...\n"; } public function fun6(){ echo "process6 starting !!!...\n"; } }?>
建立listener.php檔案
<?php //事件監聽伺服器 require ‘event.php‘; require ‘function.php‘; class listener{ private $event; private $register; private $process; private $memcache; public function __construct(){ $this->process = new process(); $this->event = new event(); $this->register = require ‘register.php‘; $this->memcache = new Memcache(); $this->memcache->addServer(‘127.0.0.1‘,11211); } public function listen(){ for(;;){ $event_keys = array_keys($this->register); foreach($event_keys as $ek){ $getstatusfunc = ‘get‘.ucfirst($ek); if($this->event->$getstatusfunc()){ foreach($this->register[$ek] as $r){ $this->process->$r(); } $this->memcache->set($ek,false); } } } } } $listener = new listener(); $listener->listen(); ?>
建立register.php檔案
<?php //事件與方法註冊綁定 return array( ‘event1‘=>array( ‘fun1‘,‘fun2‘,‘fun3‘, //當event1事件觸發時,執行fun1, fun2, fun3方法 ), ‘event2‘=>array( ‘fun2‘,‘fun3‘,‘fun4‘, ), ‘event3‘=>array( ‘fun4‘,‘fun5‘,‘fun6‘, ), );?>
首先在控制台執行listener.php。/yourpath/php /yourpath/event/listener.php。
然後在瀏覽器執行http://localhost/event/client.php,此時控制台會顯示具體的方法執行的資訊。這裡client.php只與event類互動,然後通過event類觸發process類方法。並且觸發的方法與用戶端之間也是非同步執行的。
以上我們介紹了訊息佇列架構風格與事件驅動架構風格。它們與MVC結合在一起為Web系統提供了較為完善的解決方案,能夠滿足大多數的Web需求。另外,訊息架構風格除了隊列外,還有一種發布形式的風格,這種風格主要解決的是滿足多用戶端同時擷取訊息的需求。就像多個IP地址加入到同一個組播地址一樣,當不同的用戶端同時加入到一個“主題”(這裡的主題就相當於組播地址),伺服器發送給主題的任何訊息,所有加入同一主題的用戶端都能同時收到。在實際應用中,也經常用到。例如RSS訂閱系統等。希望此文能協助大家對訊息佇列與事件驅動架構風格有一個基礎的認識。當然,這也只是我個人的一些見解,如果有不對的地方,也請大家不吝指正。
本文出自 “架構師之路” 部落格,轉載請與作者聯絡!
聊聊常用的軟體架構風格