今天和大家聊聊软件的架构风格。所谓的软件架构风格,就是一种可以重复利用的软件结构模式,其最大的作用是用相同的结构解决某一特殊领域的问题。如著名的三层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文件
<?php require ‘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订阅系统等。希望此文能帮助大家对消息队列与事件驱动架构风格有一个基础的认识。当然,这也只是我个人的一些见解,如果有不对的地方,也请大家不吝指正。