我目前正在尝试还原旧应用程序的功能,以及部分无法使用(消耗部分MQ消息)逻辑的逻辑部分(诚实的是,我什至不确定它之前是否能正常工作)。这个程序有自定义的逻辑来运行处理兔子消息的工作者。问题是,当工作人员在运行时总是对数据库执行任何操作(获取一些数据,关闭连接等)失败,而错误为send of 291 bytes failed with errno=32 Broken pipe
。使用Propel ORM进行应用。首先,我将分享propel配置文件:
config.propel.php:
<?php
use Propel\Runtime\Propel;
use Propel\Runtime\Connection\ConnectionManagerSingle;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
$loggerConfig = $config->get('logger')['sql'];
$db = $config->get('db');
$env = $config->get('app')['env'];
$defaultLogger = new Logger('defaultLogger');
$defaultLogger->pushHandler(new StreamHandler($loggerConfig['filename'], $loggerConfig['level']));
Propel::getServiceContainer()->setLogger('defaultLogger', $defaultLogger);
$queryLogger = new Logger('main');
$queryLogger->pushHandler(new StreamHandler($loggerConfig['filename'], $loggerConfig['level']));
Propel::getServiceContainer()->setLogger('main', $queryLogger);
$serviceContainer = Propel::getServiceContainer();
$serviceContainer->setAdapterClass('main', 'mysql');
$manager = new ConnectionManagerSingle();
$dsn = 'mysql:host='.$db['host'].';';
if (isset($db['port'])) {
$dsn .= 'port=' . $db['port'] . ";";
} else {
$dsn .= 'port=3306;';
}
if (isset($db['charset'])) {
$dsn .= 'charset='.$db['charset'].";";
}
$dsn .= 'dbname='.$db['dbname'];
$manager->setConfiguration([
'dsn' => $dsn,
'user' => $db['user'],
'password' => $db['password'],
]);
$serviceContainer->setConnectionManager('main', $manager);
if ($env == 'development') {
$serviceContainer->getWriteConnection('main')->useDebug(true);
}
bootstrap.daemon.php
实际上用于引导该工作人员的应用程序:
<?php
require 'lib/vendor/autoload.php';
require 'app/autoload.php';
// app config is just set of configurations like database credentials etc.
require 'app/config.php';
// provided above
require_once 'app/config.propel.php';
和start
脚本本身(启动工作程序的守护程序):
#!/usr/bin/php -q
<?php
set_time_limit(0);
require __DIR__.'/../../bootstrap.daemon.php';
// it's a routing (not sure why it here)
require __DIR__.'/../../app/config.public.php';
use App;
use Luncher;
App::start($config);
/*
Here is some magic:)
Do not change
*/
{
ini_set('mysql.connect_timeout', 0);
ini_set('default_socket_timeout', 0);
}
$l = new Luncher;
$l->startDeamon();
Launcher
:
<?php
namespace App\Daemons\Core;
use App\App;
use App\Daemons\Core\ServiceWrapper;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
class Luncher
{
private static $daemonsNamespace = "App\Daemons\\";
public function __construct()
{
set_error_handler(function($errno, $errstr, $errfile, $errline) {
$this->out("[ERROR] ". $errstr." at line ".$errline." in file ".$errfile);
die();
});
}
public function startDeamon($daemonName = 'Deamon)
{
$className = self::$daemonsNamespace.$daemonName;
if (class_exists($className)) {
$class = new \ReflectionClass($className);
$logger = new Logger('daemon');
$logger->pushHandler(new StreamHandler($daemonConfig['log']));
$serives = new ServiceWrapper($logger);
// start 5 workers
for ($i=0; $i<5; $i++) {
echo date("d.m.Y H:i")." Starting daemon $daemonName\n";
$className::start($serives);
}
} else {
echo date("d.m.Y H:i")." Class $className not found!\n";
}
}
private function stopDaemon($daemonName = 'Daemon')
{
$daemonPid = $this->getDaemonPids(daemonName);
if (strlen($daemonPid)) {
$this->out("Killing daemon $daemonName");
exec("kill ".$daemonPid);
}
}
private function getDaemonPids($daemonName)
{
$result = [];
$tmp = [];
exec("pidof daemons.$daemonName", $tmp);
if (isset($tmp[0])) {
$result = explode(' ', $tmp[0]);
}
return $result;
}
private function out($str)
{
echo date("d.m.Y H:i")." $str \n";
}
}
Deamon
本身:
<?php
namespace App\Daemons\Core\Queue;
use App\App;
use Propel\Runtime\Propel;
use App\Daemons\Core\ServiceWrapper;
class Daemon
{
protected $isParent = true;
protected $AMQPChannel;
protected $services;
protected $currentMsg;
protected $reinitializeAttempts = 0;
const MAX_REINITIALIZE_ATTEMPTS = 2;
public static function start(ServiceWrapper $services)
{
$d = new static;
$d->services = $services;
$d->fork();
if ($d->isChild()) {
$d->services->setPid(getmypid());
$d->run();
}
}
protected function fork()
{
$this->isParent = pcntl_fork();
}
protected function isChild()
{
if (!$this->isParent) {
return true;
} else {
return false;
}
}
final protected function run()
{
try {
$fErrorHandler = function($errno, $errstr, $errfile, $errline) {
$this->error('[ERROR] '.$errstr." at line ".$errline." in file ".$errfile);
die();
};
set_error_handler(function($errno, $errstr, $errfile, $errline) use ($fErrorHandler) {
$fErrorHandler($errno, $errstr, $errfile, $errline);
});
register_shutdown_function(function() use ($fErrorHandler) {
$error = error_get_last();
if ($error !== null) {
$errno = $error["type"];
$errfile = $error["file"];
$errline = $error["line"];
$errstr = $error["message"];
$fErrorHandler($errno, $errstr, $errfile, $errline);
}
});
$this->_init();
$callback = function($msg) {
if (!is_null($msg) && is_array($msg)) {
$this->output("Got a new message from queue");
$this->output(json_encode($msg));
$this->currentMsg = $msg;
$this->onNewMessage($msg);
$this->currentMsg = null;
$this->reinitializeAttempts = 0;
}
};
if ($this->currentMsg) {
$callback($this->currentMsg);
}
$this->consume('long_jobs', $callback);
$this->runHandling();
} catch (\Exception $e) {
$this->error('[EXCEPTION] '.$e->getMessage()." ".$e->getFile()." : ". $e->getLine());
/**
* we can't get error code for PDOException :(
*/
if ($e instanceof \PDOException || $e instanceof \Doctrine\DBAL\DBALException) {
if ($this->reinitializeAttempts < self::MAX_REINITIALIZE_ATTEMPTS) {
$this->output('Trying to reinitialize daemon');
$this->reinitializeAttempts++;
$this->run();
return;
} else {
$this->error('Max reinitialize attempts were achivied');
}
}
die();
}
}
protected function output($msg)
{
$this->services->output($msg);
}
protected function error($msg)
{
$this->services->error($msg);
}
protected function onNewMessage($data)
{
$this->output("Got a new job");
$job = new LongJob();
$this->output("Lunching job ".$data['jobName']);
$job->setServices($this->services);
$t1 = microtime(true);
$job->run($data['data']);
Propel::closeConnections();
$this->output("Job is finished. time: ".(microtime(true) - $t1)." seconds");
}
private function _init()
{
$config = App::getConfig();
$connection= new AMQPConnection($config->host, $config->port, $config->user, $config->password);
$this->AMQPChannel = $connection->channel();
}
public function consume($queueName, $userCallback)
{
$callback = function($msg) use ($userCallback) {
$userCallback(json_decode($msg->body, true));
$msg->delivery_info['channel']->basic_ack($msg->delivery_info['delivery_tag']);
};
$this->AMQPChannel->basic_qos(null, 1, null);
$this->AMQPChannel->queue_declare($queueName, false, true, false, false);
$this->AMQPChannel->basic_consume($queueName, '', false, false, false, false, $callback);
}
public function runHandling()
{
while (count($this->AMQPChannel->callbacks)) {
$this->channel->wait();
}
$this->AMQPChannel->close();
}
}
还有LongJob
:
namespace App\Daemons\LongJobs;
use App\Daemons\Core\LongJob;
use App\Models\User;
use App\App;
use App\EventsServer\Core\Event;
use App\EventsServer\Core\EventsManager;
use App\Models\AR\Payments\Payments;
class LongJob {
protected $services;
public function setServices(IServices $services)
{
$this->services = $services;
}
public function run($data)
{
if (!isset($data['userId'])) {
$this->services->error("There is no user id!");
return;
}
$compatProductId = ProductQuery::create()
->filterBySku(Product::SKU_COMPAT)
->findOne()
->getId();
$oUser = User::find($data['userId']);
if (!$oUser) {
$this->services->error("Can't find user with id ". $data['userId']);
return;
}
$arPurchase = PurchaseQuery::create()
->filterByProductId($compatProductId)
->find();
foreach ($arPurchase as $purchase) {
if ($data['userId'] != $purchase->getUserId()) {
$oPurchasedUser = User::find($purchase->getUserId());
$oPayments = new Payments($oPurchasedUser);
$oPayments->calcAndSaveCompat([$oUser]);
}
}
}
当LongJob
正在运行并且首次使用数据库时,它将引发该错误。很抱歉,没有代码,但是我不知道该如何共享该问题。老实说,几年前代码还不是很整洁。根据我的经验,我总是将Rabbit与一些现代框架一起使用,因此我对自己创建这样的守护进程并不十分熟悉,而且我也不知道如何解决它:(