Run PHP Görev uyumsuz

10 Cevap php

Ben biraz büyük bir web uygulaması üzerinde çalışmak ve arka uç PHP çoğunlukla. Orada bazı görevi tamamlamak için gereken kod birkaç yer vardır, ama ben kullanıcı sonucu bekleyin yapmak istemiyorum. Yeni bir hesap oluştururken Örneğin, ben onlara hoş bir e-posta göndermek gerekiyor. Onlar 'Finish' düğmesine vurduğunda Ama, ben onları e-posta aslında gönderilene kadar ben sadece süreci başlatmak istiyoruz, bekleyin ve hemen kullanıcıya bir mesaj döndürür yapmak istemiyorum.

Şimdiye kadar, bazı yerlerde I) (exec ile kesmek gibi hissediyor kullanarak ne oldum. Temelde gibi şeyler yapıyor:

exec("doTask.php $arg1 $arg2 $arg3 >/dev/null 2>&1 &");

Hangi çalışmak için görünür, ancak daha iyi bir yolu olup olmadığını merak ediyorum. Ben bir MySQL tablosu görevleri sıraya dizer bir sistem, ve bir kez ikinci bir o tabloyu sorgular, ve bulduğu herhangi yeni görevleri yürütür ayrı uzun süren bir PHP komut dosyası yazıyorum düşünüyorum. Bu da ben gerekirse bana gelecekte birçok işçi makineleri arasında görevleri bölünmüş izin avantaj olurdu.

Ben tekerleği yeniden icat muyum? Exec daha iyi bir çözüm var mı () kesmek veya MySQL kuyruğa?

10 Cevap

Ben kuyruk yaklaşım kullandım, ve bu işleme erteleme gibi sunucu yükü boşta kadar kolayca "acil olmayan görevleri" kapalı bölümlemek eğer oldukça etkili bir yük yönetmenize izin vererek, iyi çalışıyor.

Kendi çalışırken çok zor, burada kontrol etmek için birkaç seçenek var değil:

  • ActiveMQ tam gaz açık kaynak ileti sırası istiyorsanız.
  • ZeroMQ - Bu soket programlama kendisi hakkında çok fazla endişelenmenize gerek kalmadan dağıtılacak kod yazmak kolaylaştıran oldukça serin bir yuva kütüphanesidir. Tek bir ana bilgisayarda mesaj kuyruk için kullanabilirsiniz - sadece sizin webapp sürekli çalışan bir konsol uygulaması bir sonraki uygun fırsatta tüketmek istiyorsunuz bir kuyruğa şey itmek gerekir
  • beanstalkd - Sadece bu cevabı yazarken bir tane buldum, ama ilginç görünüyor
  • dropr bir PHP tabanlı ileti kuyruğu proje, ama aktif olarak Eylül 2010 yılından bu yana tutulan değil
  • Son olarak, kullanımı hakkında bir blog yazısı memcached for message queuing

Eğer kullanıcıya sayfayı gönderdiğiniz bir kez bu uzatmak için görünen bir etkisi var ama, sen erken fesih korkusu olmadan son işlem yapabilirsiniz - bir başka, belki basit, yaklaşım kullanmak için ignore_user_abort olduğunu kullanıcı açısından sayfa yükleme.

Süreçleri çatal başka bir yolu kıvrılma yoluyla olmaktadır. Sen bir webcoder olarak iç görevler kurabilirsiniz. Örneğin:

Sonra kullanıcı erişilen komut hizmeti çağrıları yapmak:

$service->addTask('t1', $data); // post data to URL via curl

Servis mysql veya ne olursa olsun sizin gibi nokta ile görevler kuyruğunun takip edebilirsiniz: tüm hizmet içinde sarılmış oluyor ve komut sadece URL'leri alıcıdır. Bu (yani kolayca ölçeklenebilir), gerekirse başka bir makine / sunucu hizmeti taşımak için boşaltır.

(Amazon web servisleri gibi) http yetkilendirme veya özel bir yetkilendirme düzeni ekleme Başkalarının / hizmetlerin (isterseniz) tarafından tüketilmesi gereken görevleri açmak ve daha da ileriye götürmek ve takip etmek için üstüne bir izleme hizmeti ekleyebilirsiniz sağlar Sıraya ve görev durumu.

Bu set-up iş biraz sürer ama pek çok yarar vardır.

I Beanstalkd bir proje için kullanılan ve yine planlanan ettik. Ben uyumsuz süreçlerini çalıştırmak için bir mükemmel bir yol buldum.

Onunla yaptığım şeylerin bir çift vardır:

  • Resim boyutlandırma - ve hafif yüklü sıra büyük (2mb +) görüntüleri sadece iyi çalıştı, yeniden boyutlandırma, CLI-tabanlı PHP komut dosyası kapalı geçen, ancak düzenli olarak (bellek alanı sorunları içine çalışan bir mod_php örneği içinde aynı görüntüleri yeniden boyutlandırmak için çalışıyor I ) 32MB PHP süreç sınırlıdır ve boyutlandırma o daha aldı
  • yakın gelecekte kontrolleri - beanstalkd (sadece X saniye sonra çalıştırmak için bu işi kullanılabilir hale) buna müsait gecikmeler var - bu yüzden ben biraz sonra zaman içinde, bir olay için 5 veya 10 çek kapalı atabileceği

Ben bir 'güzel' url çözmek için, yani örneğin, QueueTask('/image/resize/filename/example.jpg') diyebileceğim bir görüntüyü yeniden boyutlandırmak için bir Zend Framework-tabanlı sistem yazdı. URL ilk olarak bir dizi (modül, kontrol, işlem, parametreler) çözülür ve daha sonra kendisi sıra enjeksiyon için JSON dönüştürüldü.

Uzun süren bir cli komut dosyası daha sonra (Zend_Router_Simple yoluyla) koştu, kuyruğundan işi aldı, ve gerekirse, gerekli gibi yapıldığı zaman almak için web sitesi PHP memcached içine bilgi koymak.

Ben de koydun bir kırışıklık cli-script sadece başlatmadan önce 50 döngüler için koştu, ama planlandığı gibi yeniden başlatmak istiyorsanız eğer, o (bir bash-komut dosyası aracılığıyla yürütülüyor) hemen öyle yapardı. Orada bir sorun vardı ve ben ilk birkaç saniye için duraklama olur exit(0) (exit; veya die(); için varsayılan değer) yaptım.

Eğer sadece cevap için beklemek zorunda kalmadan bir veya birkaç HTTP istekleri yürütmek istiyorsanız, basit bir PHP çözüm de vardır.

Çağıran komut dosyası:

$socketcon = fsockopen($host, 80, $errno, $errstr, 10);
if($socketcon) {   
   $socketdata = "GET $remote_house/script.php?parameters=... HTTP 1.1\r\nHost: $host\r\nConnection: Close\r\n\r\n";      
   fwrite($socketcon, $socketdata); 
   fclose($socketcon);
}
// repeat this with different parameters as often as you like

Denilen script.php üzerinde, ilk satırları bu PHP işlevleri çağırabilirsiniz:

ignore_user_abort(true);
set_time_limit(0);

Bu komut dosyası HTTP bağlantısı kapatıldığında zaman sınırı olmadan çalışmaya devam etmesine neden olur.

Bu Ben şimdi birkaç yıl için kullanıyorum ve ben daha iyi bir şey görmedim ya bulamadı aynı yöntemi. Insanlar söylediler, PHP tek dişli olduğunu, bu yüzden yapabileceğiniz çok fazla bir şey yok.

Ben aslında bu ekstra bir düzeyi eklemiş ve alma ve proses id depoladığını. Bu bana başka bir sayfaya yönlendirme ve kullanıcı işlemi (işlem kimliği artık yok) tam olup olmadığını kontrol için AJAX kullanarak, bu sayfada oturmak olmasını sağlar. Bu senaryonun uzunluğu tarayıcı zaman aşımı neden olur durumlar için yararlıdır, ancak kullanıcı bir sonraki adımdan önce tamamlamak için komut dosyası için beklemek gerekiyor. (Benim durumumda bu kullanıcı bazı bilgileri doğrulamak için gereken sonra veritabanına 30 000 kayıtlarına ekleyebilirsiniz dosyaları gibi CSV ile büyük ZIP dosyalarını işleme edilmiştir.)

Ben de rapor üretimi için benzer bir işlem kullandık. Yavaş bir SMTP ile gerçek bir sorun olmadığı sürece, ben böyle bir şey için e-posta gibi "arka plan işleme" kullanmak istiyorum emin değilim. Bunun yerine bir sıra olarak bir tablo kullanabilir ve sonra kuyruktaki e-postalar göndermek için her dakika çalışan bir süreç var. Siz iki ya da benzeri sorunlar e-posta gönderme warry olması gerekir. Ben de diğer görevler için benzer bir kuyruk süreci düşünün.

İşte benim web uygulaması için kodlanmış basit bir sınıftır. PHP betikleri ve diğer komut çatallamak sağlar. UNIX ve Windows üzerinde çalışır.

class BackgroundProcess {
    static function open($exec, $cwd = null) {
    	if (!is_string($cwd)) {
    		$cwd = @getcwd();
    	}

    	@chdir($cwd);

    	if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN') {
    		$WshShell = new COM("WScript.Shell");
    		$WshShell->CurrentDirectory = str_replace('/', '\\', $cwd);
    		$WshShell->Run($exec, 0, false);
    	} else {
    		exec($exec . " > /dev/null 2>&1 &");
    	}
    }

    static function fork($phpScript, $phpExec = null) {
    	$cwd = dirname($phpScript);

    	@putenv("PHP_FORCECLI=true");

    	if (!is_string($phpExec) || !file_exists($phpExec)) {
    		if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN') {
    			$phpExec = str_replace('/', '\\', dirname(ini_get('extension_dir'))) . '\php.exe';

    			if (@file_exists($phpExec)) {
    				BackgroundProcess::open(escapeshellarg($phpExec) . " " . escapeshellarg($phpScript), $cwd);
    			}
    		} else {
    			$phpExec = exec("which php-cli");

    			if ($phpExec[0] != '/') {
    				$phpExec = exec("which php");
    			}

    			if ($phpExec[0] == '/') {
    				BackgroundProcess::open(escapeshellarg($phpExec) . " " . escapeshellarg($phpScript), $cwd);
    			}
    		}
    	} else {
    		if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN') {
    			$phpExec = str_replace('/', '\\', $phpExec);
    		}

    		BackgroundProcess::open(escapeshellarg($phpExec) . " " . escapeshellarg($phpScript), $cwd);
    	}
    }
}

Eğer "Kaydetme için teşekkür ederiz" yanıt Content-Length HTTP başlığını ayarlarsanız bayt belirtilen sayıda alındıktan sonra, ardından tarayıcı bağlantısını kapatmak gerekir. Bu (ignore_user_abort ayarlanmış olduğunu varsayarak) çalışan sunucu tarafı işlemini bırakır böylece son kullanıcı bekleme yapmadan çalışmaya bitirebiliriz.

Tabii ki (dize, render ()) (strlen diyoruz, bir dizeye çıktı yazmak çağrı başlığını) Eğer başlıklarını render önce yanıt içeriğinin boyutunu hesaplamak gerekir, ama bu kısa yanıtlar için oldukça kolay.

Bu yaklaşım not Eğer bir "ön uç" kuyruğunu yönetmek için zorlama ve birbirlerine üzerine çıkmasına yarışı HTTP çocuk süreçleri engellemek için arka uç bazı işler yapmak gerekebilir ancak o avantajı vardır Neyse, zaten yapmam gereken bir.

Bu rojoca tarafından önerilen cURL kullanmak için harika bir fikir.

İşte bir örnek. Betik arkaplanda çalışırken Text.txt izleyebilir:

<?php

function doCurl($begin)
{
    echo "Do curl<br />\n";
    $url = 'http://'.$_SERVER['SERVER_NAME'].$_SERVER['REQUEST_URI'];
    $url = preg_replace('/\?.*/', '', $url);
    $url .= '?begin='.$begin;
    echo 'URL: '.$url.'<br>';
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    $result = curl_exec($ch);
    echo 'Result: '.$result.'<br>';
    curl_close($ch);
}


if (empty($_GET['begin'])) {
    doCurl(1);
}
else {
    while (ob_get_level())
        ob_end_clean();
    header('Connection: close');
    ignore_user_abort();
    ob_start();
    echo 'Connection Closed';
    $size = ob_get_length();
    header("Content-Length: $size");
    ob_end_flush();
    flush();

    $begin = $_GET['begin'];
    $fp = fopen("text.txt", "w");
    fprintf($fp, "begin: %d\n", $begin);
    for ($i = 0; $i < 15; $i++) {
        sleep(1);
        fprintf($fp, "i: %d\n", $i);
    }
    fclose($fp);
    if ($begin < 10)
        doCurl($begin + 1);
}

?>

Ne yazık ki PHP yerli parçacığı yetenekleri her türlü yoktur. Yani bu durumda sen ne yapmak istediğinizi yapmak için özel kod çeşit kullanmak seçim yok ama düşünüyorum.

PHP şeyler diş için net etrafında arama yaparsanız, bazı insanlar PHP konuları taklit etmek için yollar ile geldi.

PHP bir tek dişli dildir, yani exec veya popen kullanmaktan başka onunla uyumsuz bir süreci başlatmak için resmi bir yolu yoktur. O here hakkında bir blog yazısı var. MySQL bir sıra için senin fikrin de iyi bir fikirdir.

Burada özel gereksinimi kullanıcıya bir e-posta göndermek için. Ben gerçekleştirmek için oldukça önemsiz ve hızlı görev bir e-posta gönderme beri uyumsuz bunu için çalışıyoruz neden olarak merak ediyorum. Ben e-posta ton gönderiyor ve ISS spam şüphesiyle sizi engelliyorsa, o sıraya bir nedeni olabilir, ama bunun dışında ben bu şekilde yapmak için herhangi bir nedenle düşünemiyorum herhalde.