用PHP构建一个简易监视引擎(2)
http://www.sina.com.cn 2006年10月27日 08:40
天极yesky
四. 构建监视服务
在这一节中,我们将使用PHP来编写一个基本的监视引擎。因为你不会事先知道怎样改变,所以你应该使它的实现既灵活又具可能性。
该记录程序应该能够支持任意的服务检查(例如,HTTP和FTP服务)并且能够以任意方式(通过电子邮件,输出到一个日志文件,等等)记录事件。你当然想让它以一个守护程序方式运行;所以,你应该请求它输出其完整的当前状态。
一个服务需要实现下列抽象类:
abstract class ServiceCheck { const FAILURE = 0; const SUCCESS = 1; protected $timeout = 30; protected $next_attempt; protected $current_status = ServiceCheck::SUCCESS; protected $previous_status = ServiceCheck::SUCCESS; protected $frequency = 30; protected $description; protected $consecutive_failures = 0; protected $status_time; protected $failure_time; protected $loggers = array(); abstract public function __construct($params); public function __call($name, $args) { if(isset($this->$name)) { return $this->$name; } } public function set_next_attempt() { $this->next_attempt = time() + $this->frequency; } public abstract function run(); public function post_run($status) { if($status !== $this->current_status) { $this->previous_status = $this->current_status; } if($status === self::FAILURE) { if( $this->current_status === self::FAILURE ) { $this->consecutive_failures++; } else { $this->failure_time = time(); } } else { $this->consecutive_failures = 0; } $this->status_time = time(); $this->current_status = $status; $this->log_service_event(); } public function log_current_status() { foreach($this->loggers as $logger) { $logger->log_current_status($this); } } private function log_service_event() { foreach($this->loggers as $logger) { $logger->log_service_event($this); } } public function register_logger(ServiceLogger $logger) { $this->loggers[] = $logger; } } |
上面的__call()重载方法提供对一个ServiceCheck对象的参数的只读存取操作:
· timeout-在引擎终止检查之前,这一检查能够挂起多长时间。
· next_attempt-下次尝试连接到服务器的时间。
· current_status-服务的当前状态:SUCCESS或FAILURE。
· previous_status-当前状态之前的状态。
· frequency-每隔多长时间检查一次服务。
· description-服务描述。
· consecutive_failures-自从上次成功以来,服务检查连续失败的次数。
· status_time-服务被检查的最后时间。
· failure_time-如果状态为FAILED,则它代表发生失败的时间。
这个类还实现了观察者模式,允许ServiceLogger类型的对象注册自身,然后当调用log_current_status()或log_service_event()时调用它。
这里实现的关键函数是run(),它负责定义应该怎样执行检查。如果检查成功,它应该返回SUCCESS;否则返回FAILURE。
当定义在run()中的服务检查返回后,post_run()方法被调用。它负责设置对象的状态并实现记入日志。
ServiceLogger接口:指定一个日志类仅需要实现两个方法:log_service_event()和log_current_status(),它们分别在当一个run()检查返回时和当实现一个普通状态请求时被调用。
该接口如下所示:
interface ServiceLogger { public function log_service_event(ServiceCheck$service); public function log_current_status(ServiceCheck$service); } |
最后,你需要编写引擎本身。该想法类似于在前一节编写简单程序时使用的思想:服务器应该创建一个新的进程来处理每一次检查并使用一个SIGCHLD处理器来检测当检查完成时的返回值。可以同时检查的最大数目应该是可配置的,从而可以防止对系统资源的过渡使用。所有的服务和日志都将在一个XML文件中定义。
下面是定义该引擎的ServiceCheckRunner类:
class ServiceCheckRunner { private $num_children; private $services = array(); private $children = array(); public function _ _construct($conf, $num_children) { $loggers = array(); $this->num_children = $num_children; $conf = simplexml_load_file($conf); foreach($conf->loggers->logger as $logger) { $class = new Reflection_Class("$logger->class"); if($class->isInstantiable()) { $loggers["$logger->id"] = $class->newInstance(); } else { fputs(STDERR, "{$logger->class} cannot be instantiated.\n"); exit; } } foreach($conf->services->service as $service) { $class = new Reflection_Class("$service->class"); if($class->isInstantiable()) { $item = $class->newInstance($service->params); foreach($service->loggers->logger as $logger) { $item->register_logger($loggers["$logger"]); } $this->services[] = $item; } else { fputs(STDERR, "{$service->class} is not instantiable.\n"); exit; } } } private function next_attempt_sort($a, $b){ if($a->next_attempt() == $b->next_attempt()) { return 0; } return ($a->next_attempt() < $b->next_attempt())? -1 : 1; } private function next(){ usort($this->services,array($this,'next_attempt_sort')); return $this->services[0]; } public function loop(){ declare(ticks=1); pcntl_signal(SIGCHLD, array($this, "sig_child")); pcntl_signal(SIGUSR1, array($this, "sig_usr1")); while(1) { $now = time(); if(count($this->children)< $this->num_children) { $service = $this->next(); if($now < $service->next_attempt()) { sleep(1); continue; } $service->set_next_attempt(); if($pid = pcntl_fork()) { $this->children[$pid] = $service; } else { pcntl_alarm($service->timeout()); exit($service->run()); } } } } public function log_current_status(){ foreach($this->services as $service) { $service->log_current_status(); } } private function sig_child($signal){ $status = ServiceCheck::FAILURE; pcntl_signal(SIGCHLD, array($this, "sig_child")); while(($pid = pcntl_wait($status, WNOHANG)) > 0){ $service = $this->children[$pid]; unset($this->children[$pid]); if(pcntl_wifexited($status) && pcntl_wexitstatus($status) ==ServiceCheck::SUCCESS) { $status = ServiceCheck::SUCCESS; } $service->post_run($status); } } private function sig_usr1($signal){ pcntl_signal(SIGUSR1, array($this, "sig_usr1")); $this->log_current_status(); } } |
这是一个很复杂的类。其构造器读取并分析一个XML文件,创建所有的将被监视的服务,并创建记录它们的日志程序。
loop()方法是该类中的主要方法。它设置请求的信号处理器并检查是否能够创建一个新的子进程。现在,如果下一个事件(以next_attempt时间CHUO排序)运行良好,那么一个新的进程将被创建。在这个新的子进程内,发出一个警告以防止测试持续时间超出它的时限,然后执行由run()定义的测试。
还存在两个信号处理器:SIGCHLD处理器sig_child(),负责收集已终止的子进程并执行它们的服务的post_run()方法;SIGUSR1处理器sig_usr1(),简单地调用所有已注册的日志程序的log_current_status()方法,这可以用于得到整个系统的当前状态。
当然,这个监视架构并不没有做任何实际的事情。但是首先,你需要检查一个服务。下列这个类检查是否你从一个HTTP服务器取回一个"200 Server OK"响应:
class HTTP_ServiceCheck extends ServiceCheck{ public $url; public function _ _construct($params){ foreach($params as $k => $v) { $k = "$k"; $this->$k = "$v"; } } public function run(){ if(is_resource(@fopen($this->url, "r"))) { return ServiceCheck::SUCCESS; } else { return ServiceCheck::FAILURE; } } } |
与你以前构建的框架相比,这个服务极其简单,在此恕不多描述。