أفضل طريقة لإدارة البرنامج النصي PHP طويل الجري؟

StackOverflow https://stackoverflow.com/questions/2212635

  •  19-09-2019
  •  | 
  •  

سؤال

لدي برنامج نصي PHP يستغرق وقتا طويلا (5-30 دقيقة) لإكماله. فقط في حالة تهمه، يستخدم البرنامج النصي حليقة إلى كشط البيانات من خادم آخر. هذا هو السبب في أن الأمر يستغرق وقتا طويلا؛ يجب أن تنتظر كل صفحة من كل صفحة قبل معالجةها والانتقال إلى التالي.

أريد أن أكون قادرا على بدء البرنامج النصي واتركها حتى يتم ذلك، مما سيعرض علامة في جدول قاعدة البيانات.

ما أحتاج إليه هو كيفية أن أكون قادرا على إنهاء طلب HTTP قبل الانتهاء من تشغيل البرنامج النصي. أيضا، هو البرنامج النصي php أفضل طريقة للقيام بذلك؟

هل كانت مفيدة؟

المحلول

بالتأكيد يمكن القيام به مع PHP، ومع ذلك، يجب أن لا تفعل ذلك كملقظة خلفية - يجب الانفصال العملية الجديدة من مجموعة العملية حيث يتم البدء فيها.

نظرا لأن الأشخاص يحتفظون بإعطاء نفس الإجابة الخاطئة في هذه الأسئلة الشائعة، فقد كتبت إجابة أكمل هنا:

http://symcbean.blogspot.com/0/02/php-and-long-running-processes.html.

من التعليقات:

النسخة القصيرة هي shell_exec('echo /usr/bin/php -q longThing.php | at now'); لكن الأسباب التي تجعلها طويلة بعض الشيء للإدماج هنا.

نصائح أخرى

الطريقة السريعة والقذرة هي استخدام ignore_user_abort وظيفة في php. يقول هذا أساسا: لا يهمني ما يفعله المستخدم، قم بتشغيل هذا البرنامج النصي حتى يتم الانتهاء منه. هذا أمر خطير إلى حد ما إذا كان موقع مواجه عام (لأنه من الممكن، أنه ينتهي بك الأمر إلى إصدارات 20 ++ من البرنامج النصي الذي يعمل في نفس الوقت إذا بدأ 20 مرة).

تتمثل الطريقة "النظيفة" (على الأقل IMHO) في تعيين علامة (في DB على سبيل المثال) عندما تريد بدء العملية وتشغيل Cronjob كل ساعة (أو نحو ذلك) للتحقق مما إذا كانت هذه العلامة محددة. إذا تم تعيينه، يبدأ البرنامج النصي التشغيلي الطويل، إذا لم يتم تعيينه، يحدث Nothin.

يمكنك استخدام exec. أو النظام لبدء مهمة خلفية، ثم القيام بالعمل في ذلك.

أيضا، هناك أساليب أفضل لإخراج الويب الذي تستخدمه الشخص الذي تستخدمه. يمكنك استخدام نهج متجدد (مؤشرات الترابط متعددة في وقت واحد في المرة الواحدة)، أو واحدة باستخدام EventLoop (موضوع واحد يقوم بصفحات متعددة في الوقت المناسب). سيكون نهجي الشخصي باستخدام بيرل يستخدم Anyevent :: http..

إيتا: symcbean. أوضح كيفية فصل عملية الخلفية بشكل صحيح هنا.

لا، PHP ليس هو الحل الأفضل.

لست متأكدا من Ruby أو Perl، ولكن مع Python، يمكنك إعادة كتابة مكشطة صفحتك لتكون متعددة الخيوط وربما تشغيل 20x على الأقل. يمكن أن يكون كتابة تطبيقات متعددة الخيوط إلى حد ما من التحدي إلى حد ما، لكن تطبيق Python الأول الذي كتبته هو مكشطة صفحة Mutlti الخيوط. ويمكنك ببساطة استدعاء البرنامج النصي Python من داخل صفحة PHP الخاصة بك باستخدام أحد وظائف تنفيذ SHELT.

نعم، يمكنك أن تفعل ذلك في PHP. ولكن بالإضافة إلى PHP سيكون من الحكمة استخدام مدير قائمة الانتظار. إليك الاستراتيجية:

  1. تفريق مهمتك الكبيرة في مهام أصغر. في حالتك، يمكن تحميل كل مهمة صفحة واحدة.

  2. أرسل كل مهمة صغيرة إلى قائمة الانتظار.

  3. قم بتشغيل العاملين في قائمة الانتظار الخاصة بك في مكان ما.

استخدام هذه الاستراتيجية له المزايا التالية:

  1. للمهام الجارية الطويلة، لديها القدرة على التعافي في حالة حدوث مشكلة قاتلة في منتصف المدى - لا حاجة للبدء من البداية.

  2. إذا لم يكن من الضروري تشغيل مهامك بالتتابع بالتتابع، فيمكنك تشغيل العديد من العمال لتشغيل المهام في وقت واحد.

لديك مجموعة متنوعة من الخيارات (هذا مجرد عدد قليل):

  1. Rabbitmq (https://www.rabbitmq.com/tutorials/tutorial-one-php.html)
  2. Zeromq (http://zeromq.org/bindings:Ph.)
  3. إذا كنت تستخدم إطار Laravel، فستكون قوائم الانتظار (https://laravel.com/docs/5.4/)، مع برامج تشغيل AWS SES، Redis، Beanstalkd

قد تكون PHP أو لا تكون أفضل أداة، لكنك تعرف كيفية استخدامه، ويتم كتابة بقية التطبيق الخاص بك باستخدامه. هذه الصفات، جنبا إلى جنب مع حقيقة أن PHP هي "جيدة بما فيه الكفاية" تجعل حالة قوية للغاية لاستخدامها، بدلا من بيرل، روبي، أو بيثون.

إذا كان هدفك هو تعلم لغة أخرى، فقم باختيار واحد واستخدامه. أي لغة مذكورة ستفعل الوظيفة، لا مشكلة. يحدث أن أحب بيرل، ولكن ما تريد أن تكون مختلفة.

لدى Symcbean بعض النصائح الجيدة حول كيفية إدارة عمليات الخلفية في رابطه.

باختصار، اكتب النصي CLI PHP للتعامل مع البتات الطويلة. تأكد من أن حالة تقاريرها بطريقة ما. قم بإجراء صفحة PHP للتعامل مع تحديثات الحالة، إما باستخدام AJAX أو الأساليب التقليدية. سيقوم البرنامج النصي الخاص بك في بدء تشغيل العملية قيد التشغيل في جلسة خاصة به، وإرجاع تأكيد أن العملية تسير.

حظ سعيد.

وأنا أتفق مع الإجابات التي أقول أنه يجب تشغيل هذا في عملية خلفية. ولكن من المهم أيضا أن تقريرا عن الحالة حتى يعرف المستخدم أن العمل يجري القيام به.

عند تلقي طلب PHP لإطلاق العملية، يمكنك تخزينها في قاعدة بيانات تمثيل المهمة مع معرف فريد. ثم، ابدأ عملية كشط الشاشة، ويمرها المعرف الفريد. الإبلاغ إلى تطبيق iPhone أن المهمة قد بدأت وأنه يجب أن تحقق من عنوان URL محدد، يحتوي على معرف المهمة الجديد، للحصول على أحدث حالة. يمكن تطبيق iPhone الآن الاستطلاع (أو حتى "استطلاع طويل") هذا عنوان URL. في غضون ذلك، ستقوم عملية الخلفية بتحديث تمثيل قاعدة البيانات للمهمة أثناء عملها مع النسبة المئوية للانتهاء والخطوة الحالية أو أي مؤشرات الحالة الأخرى التي تريدها. وعندما انتهى، سيقوم بتعيين علامة مكتملة.

يمكنك إرسالها كطلب XHR (AJAX). عادة ما يكون للعملاء أي مهلة ل XHRS، على عكس طلبات HTTP العادية.

أدرك أن هذا سؤال قديم للغاية ولكن أود أن أعطيه طلقة. يحاول هذا البرنامج النصي معالجة كل من الدعوة الأولية عند الانتهاء بسرعة وتقطيع الحمل الثقيل إلى قطع أصغر. لم أختبر هذا الحل.

<?php
/**
 * crawler.php located at http://mysite.com/crawler.php
 */

// Make sure this script will keep on runing after we close the connection with
// it.
ignore_user_abort(TRUE);


function get_remote_sources_to_crawl() {
  // Do a database or a log file query here.

  $query_result = array (
    1 => 'http://exemple.com',
    2 => 'http://exemple1.com',
    3 => 'http://exemple2.com',
    4 => 'http://exemple3.com',
    // ... and so on.
  );

  // Returns the first one on the list.
  foreach ($query_result as $id => $url) {
    return $url;
  }
  return FALSE;
}

function update_remote_sources_to_crawl($id) {
  // Update my database or log file list so the $id record wont show up
  // on my next call to get_remote_sources_to_crawl()
}

$crawling_source = get_remote_sources_to_crawl();

if ($crawling_source) {


  // Run your scraping code on $crawling_source here.


  if ($your_scraping_has_finished) {
    // Update you database or log file.
    update_remote_sources_to_crawl($id);

    $ctx = stream_context_create(array(
      'http' => array(
        // I am not quite sure but I reckon the timeout set here actually
        // starts rolling after the connection to the remote server is made
        // limiting only how long the downloading of the remote content should take.
        // So as we are only interested to trigger this script again, 5 seconds 
        // should be plenty of time.
        'timeout' => 5,
      )
    ));

    // Open a new connection to this script and close it after 5 seconds in.
    file_get_contents('http://' . $_SERVER['HTTP_HOST'] . '/crawler.php', FALSE, $ctx);

    print 'The cronjob kick off has been initiated.';
  }
}
else {
  print 'Yay! The whole thing is done.';
}

أود أن أقترح حلا مختلفا قليلا عن Symcbean، وخاصة لأن لدي شرط إضافي يجب تشغيل عملية التشغيل الطويلة كمستخدم آخر، وليس كمستخدم بيانات Apache / WWW.

الحل الأول باستخدام Cron لاستطلاع جدول مهمة خلفية:

  • تدرج صفحة الويب PHP في جدول مهمة خلفية، الحالة "المقدمة"
  • يعمل Cron مرة واحدة كل 3 دقائق، باستخدام مستخدم آخر، تشغيل البرنامج النصي PHP CLI الذي يتحقق من جدول مهام الخلفية للحصول على صفوف "المقدمة"
  • ستقوم PHP CLI بتحديث عمود الحالة في الصف إلى "معالجة" وبدء المعالجة، بعد الانتهاء، سيتم تحديثه إلى "مكتمل"

الحل الثاني باستخدام مرفق Linux Inotify:

  • تقوم صفحة PHP Web بتحديث ملف تحكم مع المعلمات التي تم تعيينها بواسطة المستخدم، وكذلك معرف مهمة
  • سينتظر البرنامج النصي SLES (كمستخدم غير www) InotifyWait كتابة ملف التحكم
  • بعد كتابة ملف التحكم، سيتم رفع حدث Close_Wroite، وسوف يستمر البرنامج النصي Shell
  • برنامج Script Shell ينفذ PHP CLI للقيام بعملية تشغيل طويلة
  • يكتب PHP CLI الإخراج إلى ملف سجل محدد بواسطة معرف المهمة، أو بدلا من ذلك تحديث التقدم في جدول الحالة
  • يمكن ل PHP Web Page استطلاع ملف السجل (بناء على معرف المهمة) لإظهار التقدم المحرز في عملية التشغيل الطويلة، أو يمكن أيضا استعلام جدول الحالة

يمكن العثور على بعض المعلومات الإضافية في مشاركتي: http://inventorsparadox.blogspot.co.ido.201/201/2/long-running-process-in-linux-using-php.html.

لقد فعلت أشياء مماثلة مع بيرل، شوكة مزدوجة () والفصل من العملية الأصلية. يجب أن تتم جميع أعمال جلب HTTP في عملية متشعبة.

استخدم وكيل لتفويض الطلب.

ما أستخدمه دائما هو أحد هذه المتغيرات (لأن النكهات المختلفة لنظام Linux لها قواعد مختلفة حول التعامل مع الناتج / بعض البرامج الناتج بشكل مختلف):

البديل الأول@ exec ('./ myscript.php 1> / dev / null 2> / dev / null &')؛

البديل الثاني@ exec ('php -f myscript.php 1> / dev / null 2> / dev / null &')؛

المتغير الثالث@ exec ('nohup myscript.php 1> / dev / null 2> / dev / null &')؛

قد تقوم بتثبيت "Nohup". ولكن على سبيل المثال، عندما كنت أتمتة محلات FFMPEG Video Convertions، لم يتم التعامل مع واجهة الإخراج بطريقة أو بأخرى بنسبة 100٪ عن طريق إعادة توجيه تدفقات الإخراج 1 و 2، لذلك استخدمت Nohup وإعادة توجيه الإخراج.

إذا كان لديك برنامج نصي طويل، فقم بتقسيم العمل بمساعدة معلمة الإدخال لكل مهمة. (ثم كل صفحة تتصرف مثل الخيط) IE إذا كانت الصفحة لديها 1 LAC Product_Unducts حلقة عملية طويلة بعد ذلك بدلا من حلقة اجعل المنطق منطق لكلمة رئيسية واحدة وتمرير هذه الكلمة من السحر أو cornjobpage.php (في المثال التالي)

وعامل الخلفية أعتقد أنك يجب أن تجرب هذه التقنية، وسوف تساعد في الاتصال بالكثير من الصفحات التي تريد تشغيلها جميع الصفحات بشكل مستقل دون انتظار كل استجابة صفحة غير متزامنة.

cornjobpage.php // Mainpage.

    <?php

post_async("http://localhost/projectname/testpage.php", "Keywordname=testValue");
//post_async("http://localhost/projectname/testpage.php", "Keywordname=testValue2");
//post_async("http://localhost/projectname/otherpage.php", "Keywordname=anyValue");
//call as many as pages you like all pages will run at once independently without waiting for each page response as asynchronous.
            ?>
            <?php

            /*
             * Executes a PHP page asynchronously so the current page does not have to wait for it to     finish running.
             *  
             */
            function post_async($url,$params)
            {

                $post_string = $params;

                $parts=parse_url($url);

                $fp = fsockopen($parts['host'],
                    isset($parts['port'])?$parts['port']:80,
                    $errno, $errstr, 30);

                $out = "GET ".$parts['path']."?$post_string"." HTTP/1.1\r\n";//you can use POST instead of GET if you like
                $out.= "Host: ".$parts['host']."\r\n";
                $out.= "Content-Type: application/x-www-form-urlencoded\r\n";
                $out.= "Content-Length: ".strlen($post_string)."\r\n";
                $out.= "Connection: Close\r\n\r\n";
                fwrite($fp, $out);
                fclose($fp);
            }
            ?>

testpage.ph.

    <?
    echo $_REQUEST["Keywordname"];//case1 Output > testValue
    ?>

ملاحظة: إذا كنت ترغب في إرسال معلمات URL كرقم ثم اتبع هذا الجواب:https://stackoverflow.com/a/41225209/6295712.

ليس أفضل طريقة، كما ذكر الكثيرون هنا، ولكن هذا قد يساعد:

ignore_user_abort(1); // run script in background even if user closes browser
set_time_limit(1800); // run it for 30 minutes

// Long running script here

إذا كان الإخراج المطلوب من البرنامج النصي الخاص بك هو بعض المعالجة، وليس صفحة ويب، فأنا أعتقد أن الحل المطلوب هو تشغيل البرنامج النصي الخاص بك من Shell، ببساطة

php my_script.php

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top