Pomoc - Szukaj - Użytkownicy - Kalendarz
Pełna wersja: dlugi czas wykonywania skryptu + przekierowanie
Forum PHP.pl > Forum > PHP
andycole
Witam,

Mam skrypt do wysylki mailingu.
Baza maili moich uzytkownikow liczy ponad 22 tysiace, wiec radze sobie tak:

set_time_limit(300);
pobierz 80 maili sposrod tych do ktorych jeszcze nie wyslano
jezeli sa maile to
w petli
wyslij i zapisz, ze wyslano
sleep(3);
header(odswiezenie strony)
jezeli nie ma juz maili
header(strona glowna wysylki mailingu)

I problem tkwi w tym, ze zawsze po ok 3 minutach (+/- 200 sekund) wywala mi w przegladarce:

Kod
Nieprawidłowe przekierowanie
Firefox wykrył, że serwer przekierowuje żądanie tego zasobu w sposób uniemożliwiający jego ukończenie.


Czy po przekierowaniu header czas wykonania skryptu nie liczony jest na nowo?
darko
Pokaż kod, zgaduję, że robisz przekierowanie do tego samego skryptu z tymi samymi warunkami, tym samym masz nieskończoną pętlę, a właściwie "zapętloną" logikę.
andycole
Skrypt jest przydlugawy,
ale jest jak mowisz, do tej samej strony z tymi samymi parametrami przekierowuje...
200 sekund dziala, ale jak zrobic zeby dzialalo tak dlugo dopoki nie wysle wszystkiego?
jak zwiekszyc ten czas?
darko
set_time_limit ($nowy_czas_w_sekundach)
lub
ini_set("max_execution_time", $nowy_czas_w_sekundach);
andycole
wczesniej mialem
set_time_limit(600); ale nie dzialalo

ustawilem
set_time_limit(600);
ini_set("max_execution_time", 600);

ale pech chcial, ze zostalo na tyle malo maili do wysylki, ze zmiescily sie w ok 100 sekund...

Przetestuje przy nastepnej wysylce, mam nadzieje ze pomoze smile.gif

Dzieki

EDIT:

niestety nadal to samo,
ma ktos jakis inny pomysl?
thek
Ja robię to w sposób "oflagowany". Wybieram sobie pewną zmienną, którą traktuję jako flagę. Przykładowo może to być data ostatniego wysłania newslettera, ale może to być inny typ flagi. Z datą jest chyba jednak najbezpieczniej.
Zapytanie wyciąga paczkę rekordów, które mają ją niższą niż aktualna (ważne data w postaci rok-miesiąc-dzień, a nie timestamp) i ustawia im ją na aktualną oraz przetwarza, po czym wywołuje skrypt główny (oczywiście robię sleepa, by nie zajechać serwera i bazy). Gdy skrypt wykryje brak rekordów do przetworzenia - kończy działanie. Najważniejsze jest zapytanie. To ono decyduje, czy Twój skrypt będzie nieskończoną pętlą, czy nie. Dlatego musi za każdym razem modyfikować datę wysyłki ostatniego newslettera, nawet jeśli nic nie posłał bo brak było danych dla tego usera do posłania. Może i maila nie posłało, ale zmiana nastąpić musi, bo inaczej w puli zawsze Ci będą zostawały osoby ze starszą datą niż aktualna i skrypt się zapętli.
Pilsener
A po co jakiś header, sleep itp. Nie prościej użyć crona? Prawie każdy hosting daje choćby protezę do uruchamiania skryptów PHP wg harmonogramu. Ustawiasz sobie, żeby skrypt się odpalał np. co 15 minut, wysyłał określoną ilość maili a do bazy trafia raport z wysyłki i tyle. Robienie tego w ten sposób to dla mnie amatorska prowizorka i strata czasu (w dodatku na większości hostingów są limity długości wykonywania się skryptów, a tutaj nawet przeglądarka się buntuje), w PA powinieneś tylko ustawić treść maila, do kogo ma być wysłany i np. o której godzinie rozpoczynasz wysyłkę. Potem przeglądasz logi i sprawdzasz, czy wysyłka postępuje bez przeszkód, ja tak np. wysyłam SMSy i nie wyobrażam sobie robić tego inaczej.
andycole
Dzieki za konkretne odpowiedzi.

Co do flag, zamiast daty ja po prostu mam tabele w ktorej zapisuje numer_listu | numer_wysylki i pozniej wybierajac maile przy nastepnej iteracji wybieram te, ktorych nie ma w ww tabeli.

Co do crona...
Pilsner, jak z poziomu PHP wrzucic skrypt do crona zeby wykonywal sie co X minut?
Pilsener
Wszystko zależy od tego, czy Twój hosting daje Ci taką możliwość a potem oczywiście od tego, w jaki sposób zapewnia obsługę tego. Jeśli Twój hosting nie daje takiej możliwości możesz przygotować protezę i wywoływać co określony czas skrypt zdalnie przez protokół http. Możesz do tego użyć darmowego hostingu z obsługą crona, ale pewniej oczywiście walczyć o to na swoim hostingu. Więcej o cronie poczytasz w necie, jest masa artykułów, choćby tutaj:
http://pl.wikipedia.org/wiki/Crontab
andycole
Mam VPS'a,
thek
Pilsener... Pogadaj sobie o Cronie w sytuacji gdy masz limit skryptu 30 sekund + możliwość odpalania co 5 minut + setki tysięcy maili. Jeśli w ciągu 24h wyślesz wszystkim generowany indywidualnie mail to gratuluję biggrin.gif Weź pod uwagę, że nieraz w paczce możesz posłać tylko około 100-200 maili zanim skończy się limit, co daje:

200*12(razy w ciągu godziny)*24(godziny)~=57.000 maili na dobę biggrin.gif

Jak Ty chcesz ominąć ten problem przy wysyłce choćby 60.000 maili? winksmiley.jpg Cron Ci tutaj może poskakać, bo 3.000 userów maila nawet nie wyślesz. Pomyślałeś o tym przypadku? Wtedy jesteś zmuszony używać wywoływania skryptu w postaci nie-cronowej i sam wymyślić metodę. Tak więc wyskakiwanie z "amatorszczyzną" w chwili gdy nie ma ktoś innej możliwości jest delikatnie mówiąc bezpodstawnym obrażaniem. Skrypt można wywołać cronem, ale musi się on wykonywać w tle i być odporny na limity czasu wykonania. Jeśli dobrze go napiszesz to pozostaje Ci jedynie odpalanie go z crona automatycznie o którejś tam godzinie. Poza tym dla Twojej wiedzy... Skrypt obsługujący tego typu jakoś codziennie o 2-3 w nocy odpala cron na serwisie jednym nad jakim sprawuje opiekę i jakoś nie miał ani razu problemu, maile zawsze do wszystkich dochodzą, a ja sobie spokojnie śpię w tym czasie i mam w nosie czy trwa to 5 minut czy 5 godzin. Może dla iluś tam tysięcy cron dałby radę, ale:
1. Uruchamiasz skrypt co 5 minut niezależnie czy są zadania do zrobienia czy nie, mój odpala się raz i leci do upadłego.
2. Nie masz pewności czy posłało wszystkie maile, a w moim wypadku nawet jeśli serwer padł w trakcie wysyłki, to następnej nocy skrypt i tak "nadrobi" zaległości automatycznie. bez ingerencji z mojej strony. Dodatkowo mogę go odpalać ręcznie z przeglądarki i zamknąć ją bez obawy o zatrzymanie skryptu ( poczytaj nieco to znajdziesz o ignore user abort )
3. Nie muszę ingerować w nic. A nawet jeśli obciążenie serwera będzie na tyle ogromne i skrypt zakończy się z powodu limitu czasu to śmiało wznawiam go kiedy chcę. Jest tak napisany, że podejmie pracę od miejsca przerwania. Czy zrobię to ręcznie czy napiszę w cronie by "na wszelki wypadek" robił to co X godzin jest nieistotne, bo nie wykona się jeśli nie ma nic do zrobienia. Potęga algorytmu nad prostym wklepaniem do harmonogramu: "Wykonuj co 5 minut". Bo wykonywanie skryptu z odstępem 20 sekund przez Y minut na serwerze z limitem czasu skrypty 30 sekund i cronem co 5 minut to zauważalny skok wydajności. Ty zrobisz 200 maili w cronie, a ja około 3000 w skrypcie. Widzisz różnicę czy nadal uważasz rozwiązanie za "prowizoryczną amatorszczyznę"? Ja znam tylko jeden serwis z cronem co minutę. Znasz jakiś, który posłałby kilkaset tysięcy generowanych indywidualnie maili (treść jest generowana na podstawie konfiguracji ustawianych przez usera z poziomu jego panelu newslettera) z cronem co 5 minut i limitem skryptu 30 sekund? To jest matematycznie niemożliwe do wykonania co Ci udowodniłem ciut wyżej w podliczeniu. Które podejście nadal uważasz za sensowniejsze?

EDIT: Brak crona w przypadku serwisu też nie jest problemem. Ty proponujesz zdalne uruchamianie skryptu co ileś tam. Jest sens tego? Nie lepiej odpalić skrypt "raz a porządnie"?
Pilsener
Ale przecież sam napisałeś
Cytat
masz limit skryptu 30 sekund
, więc jak odpalisz skrypt "raz a dobrze"? Przecież z pierwszego postu jasno wynika, że kolega stosuje ten sam mechanizm, lecz w inny sposób: porcjuje i odświeża bawiąc się w sleep i header, czym się to różni od użycia do tego celu crona? A jak połączenie z przeglądarką zostanie zawieszone czy zresetowane albo któryś etap wysyłki nie powiedzie się? Jak ponownie wywołasz skrypt? A jak jakiś błąd wyskoczy? Chyba, że czegoś nie rozumiem tutaj? Mówisz, że wyślę nie za dużo tych maili a czy ja muszę się ograniczać do uruchomienia tylko jednego skryptu? Mogę ich przecież odpalić równolegle nawet 100, wszystko zależy od moich potrzeb i możliwości serwera.

Co do pozostałych uwag to na to jest także lekarstwo, bo wysyłkę łatwo zaprojektować tak, że maile będą wysyłane aż do skutku lub komunikatu błędu, nie widzę także przeszkód by zmodyfikować plik crontaba po zakończeniu wysyłki tak, aby już nie uruchamiał się co 5 minut. Samo 5 minut także jest przykładowe i zależy od naszych potrzeb/możliwości, dzielimy pracę na etapy by ją usprawnić a nie utrudnić.

Za bardzo zbaczamy w stronę możliwości/konfiguracji serwera i szczegółów algorytmu, to, w jaki sposób skrypt realizuję samo wysyłanie ma tutaj imo znaczenie drugorzędne. Chodzi o sam fakt uruchomienia wysyłki w tle a tutaj moim zdaniem cron jest lepszym rozwiązaniem niż bawienie się w sleep + header - to rozwiązanie zastępcze i prowizoryczne, nie wyobrażam sobie za bardzo praktycznego podpięcia w PA tego i uruchomienia sprzężenia zwrotnego, jednak nie chcę brnąć dalej w dyskusję na temat wad/zalet takiej protezy, kiedyś eksperymentowałem ze sleep a nawet autorefreshem w JS, jednak gdy zacząłem korzystać z crona nie wyobrażam sobie inaczej i tak mi zawsze radzili bardziej doświadczeni koledzy.
andycole
Dajcie juz spokoj smile.gif
thek, powiedz mi lepiej jak przekierowanie dokladnie robisz, bo w tym lezy u mnie problem :/
darko
Zawsze możesz zrobić jeszcze tak:
1. sprawdzić ile maili "poleciało" do przekroczenia czasu wykonania skryptu
2. wyciągać rekordy limit $start, $limit_ile_polcecialo - 10 (dla pewności można odjąć jeszcze więcej)

W ten sposób etapowo, "na raty" można to wykonać. Rozwiązanie bardzo dalekie od ideału, ale przynajmniej nie musisz kombinować z cron
andycole
Cytat(darko @ 19.12.2009, 03:05:41 ) *
1. sprawdzić ile maili "poleciało" do przekroczenia czasu wykonania skryptu


Moglbys to szerzej wyjasnic bo niebardzo rozumiem?

Jak pobierac rekordy to ja wiem, ale chodzi mi bardziej o sposob przekierowania, zeby bylo tak, ze po 1 kliknieciu maile wysylaja sie do skutku. Tak zebym nie musial co jakis czas znowu odswiezac strony smile.gif

A czy ten problem z czasem moze byc spowodowany przez firefoxa? smile.gif ktory po prostu nie moze wczytywac strony niz X sekund?
Thorang Hoog
Cytat(andycole @ 19.12.2009, 10:46:29 ) *
A czy ten problem z czasem moze byc spowodowany przez firefoxa? smile.gif ktory po prostu nie moze wczytywac strony niz X sekund?


Firefox wykrywa kiedy przekierowujesz stronę w nieskończoność (Przynajmniej w jej mniemaniu winksmiley.jpg )
Nie wiem jak zadziała dodanie zmiennej typu get która by się zmieniała.

Kiedy czytałem wasze wypowiedzi na temat crona czy przekierowań tkneła mnie myśl żeby wykorzystać technologię AJAX.
Najpierw użytkownik przesyła dane formularzem do skryptu. Ten zapisuje treść maila do bazy danych i osoby do których należy wysłać.
Wczytana strona zawiera w JavaScripcie informacje o ilości maili do wysłania i asynchronicznie wywołuje skrypt na serwerze który wysyła maile przez czas trochę krótszy od maksymalnego i zwraca liczbę wysłanych maili. Oczywiście osoby do których już wysłano zostały by usunięte z bazy danych.

Oprawił bym to w ładny pasek postępu i elegancki przycisk anuluj ;P
kacka
Pamiętaj tylko że mailing takiej ilości wiadomości może zostać potraktowany przez serwery pocztowe jako SPAM, a wtedy cała praca jest bezcelowa.
andycole
Cytat(Thorang Hoog @ 20.12.2009, 15:11:49 ) *
Firefox wykrywa kiedy przekierowujesz stronę w nieskończoność (Przynajmniej w jej mniemaniu winksmiley.jpg )
Nie wiem jak zadziała dodanie zmiennej typu get która by się zmieniała.


To moze byc dobry pomysl, dzieki, przetestuje i dam odpowiedz.
Thorang Hoog
Cytat(andycole @ 20.12.2009, 15:36:51 ) *
To moze byc dobry pomysl, dzieki, przetestuje i dam odpowiedz.


Nie zadziała dry.gif nawet odwołanie do różnych plików
Chrome, Firefox: 20 wywołań,
Internet Explorer 6: 100 wywołań

innych przeglądarek nie sprawdzałem.
za017
Przeglądarki blokują powtarzające się polecenia przekierowania, po pierwsze aby uchronić klienta przed "wędrowaniem" po wielu stronach i zbieraniem różnego rodzaju śmieci (cookiesów), a także przed wpadnięciem w nieskończoną pętlę. Zamiast funkcji header po stronie serwera może użyj meta tagu w kodzie odpowiedzi:
  1. <meta http-equiv="refresh" content="1;url=http://adres_skryptu" />

Skrypt może wstawiać ten tag do nagłówka html'a tak długo, aż zostaną wysłane wszystkie maile. Można też użyć ajax, który będzie w pętli wywoływał skrypt tak długo, jak długo zwraca pewną ustaloną wartość: po wysłaniu wszystkich maili skrypt zwróci pusty tekst jako odpowiedź i pętla zostanie przerwana.
Zaletą ajaksowego rozwiązania jest brak migotania strony w oknie przeglądarki i możliwość zrobienia ładnego, w pełni funkcjonalnego paska postępu (wykorzystaj jQuery).

Załączam gotowca (efekt prokrastynacji, czyli poniedziałkowej niechęci do zaplanowanej pracy smile.gif )

Aby sprawdzić działanie wystarczy:
1. umieścić na serwerze dwa pliki: sendmail.php i mailing.html (źródła poniżej),
2. do podkatalogu js wrzucić bibliotekę jQuery http://docs.jquery.com/Downloading_jQuery
3. utworzyć w bazie danych przykładową tabelę:
  1. CREATE TABLE `tbl_mailing` (`id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, `mail` VARCHAR(255), `status` ENUM ('waiting','sent'));

i wypełnić ją danymi, lub dodać pole `status` ENUM ('waiting','sent') do swojej tabeli.

Skrypt działający "w tle", realizujący wysyłanie maili partiami, zwracający do klianta informacje o procentowym postępie, lub zakończeniu wysyłki, nazwa skryptu sendmail.php

  1. <?php
  2. define(HOST,'host');
  3. define(USER,'user');
  4. define(PASSWORD,'password');
  5. define(DATABASE,'database');
  6. define(TABLE_MAILING, '`tbl_mailing`');
  7. define(HOW_MANY_EMAILS_AT_ONCE,25);
  8.  
  9. $db = new mysqli(HOST,USER,PASSWORD,DATABASE);
  10.  
  11. // pole `status` jest zdefiniowane jako: `status` ENUM ('waiting','sent')
  12.  
  13. // Ilość wszystkich maili:
  14. $total = $db->query('SELECT COUNT(id) FROM '.TABLE_MAILING)->fetch_row();
  15.  
  16. // pobieramy maile oczekujące na wysłanie
  17. $ans = $db->query('SELECT * FROM '.TABLE_MAILING.' WHERE `status`=\'waiting\' ORDER BY `id` ASC LIMIT 0,'.HOW_MANY_EMAILS_AT_ONCE);
  18. $confirm = array();
  19. while($row = $ans->fetch_row()) {
  20. echo $row[0].':'.$row[1].'<br />';
  21.  
  22. /* -- TUTAJ REALIZUJEMY LOGIKĘ ZWIĄZANĄ Z WYSYŁANIEM MAILI -- */
  23.  
  24. // budujemy listę maili, których wysłanie zakończyło się sukcesem
  25. array_push($confirm, $row[0]);
  26. }
  27.  
  28. if(count($confirm))
  29. $db->query('UPDATE '.TABLE_MAILING.' SET `status`=\'sent\' WHERE `id` IN ('.implode(',',$confirm).')');
  30.  
  31. // Ilość pozostałych do wysłania
  32. $waiting = $db->query('SELECT COUNT(id) FROM '.TABLE_MAILING.' WHERE `status`=\'waiting\'')->fetch_row();
  33.  
  34. // Zwracamy procentowy postęp wysyłania maili lub potwierdzenie zakończenia
  35. if($waiting[0]) {
  36. $sent = $total[0] - $waiting[0];
  37. echo '
  38. <input type="text" id="percent" value="'.(round(100*$sent/$total[0], 2)).'" />
  39. ';
  40. } else
  41. echo '<input type="text" id="finished" value="yes" />';


Frontend dla klienta: nazwa pliku np. mailing.html (oczywiście może być wygenerowany przez php)
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
  3. "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
  4. <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="pl">
  5. <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
  6. <meta http-equiv="Content-language" content="pl" />
  7. <script type="text/javascript" src="js/jquery.js" ></script>
  8. <script type="text/javascript">
  9. function loader() {
  10. $.ajax({
  11. type: "GET",
  12. url: "sendmail.php",
  13. cache: "false",
  14. data: "",
  15. success: function(msg) {
  16. $("#data").empty().append(msg);
  17. if($("#finished").val()=="yes") {
  18. // unikamy błędów zaokrągleń i błędu przy odświeżeniu strony:
  19. $("#progressBar").css("width","800px");
  20. alert("Gotowe !");
  21. return;
  22. }
  23. $("#progressBar").css("width", (Math.round($("#percent").val()*8)) + "px" );
  24. setTimeout("loader()",1); // unikamy wywołania rekurencyjnego!
  25. },
  26. handleError: function() { alert("hm..."); }
  27. });
  28. }
  29.  
  30. onload = function() {
  31. loader();
  32. }
  33.  
  34. <style type="text/css">
  35. body { text-align:center; }
  36. div#body { width:900px;margin:0px auto 0px auto; border:1px solid #eee;padding:25px;}
  37. div#container { margin:0px auto 0px auto;width:800px;border:1px solid #000; }
  38. div#progressBar { height:20px;width:0px;background-color:#000066;margin:0px;padding:0px;font:8pt/12pt arial,tahoma,verdana,helvetica; }
  39. </head>
  40. <div id="body">
  41. <div id="container">
  42. <div id="progressBar">
  43. </div>
  44. </div>
  45. </div>
  46. <div id="data" style="display:none"></div>
  47. </body>
  48. </html>
jajcarzd1
Cytat(Thorang Hoog @ 20.12.2009, 16:11:49 ) *
Kiedy czytałem wasze wypowiedzi na temat crona czy przekierowań tkneła mnie myśl żeby wykorzystać technologię AJAX.
Najpierw użytkownik przesyła dane formularzem do skryptu. Ten zapisuje treść maila do bazy danych i osoby do których należy wysłać.
Wczytana strona zawiera w JavaScripcie informacje o ilości maili do wysłania i asynchronicznie wywołuje skrypt na serwerze który wysyła maile przez czas trochę krótszy od maksymalnego i zwraca liczbę wysłanych maili. Oczywiście osoby do których już wysłano zostały by usunięte z bazy danych.

Oprawił bym to w ładny pasek postępu i elegancki przycisk anuluj ;P


Ja stosuję właśnie takie rozwiązanie przy pomocy xajaxa-a. W pierwszym kroku klient wypełnia pola tematu i treści i klika wyslij. Przy pierwszym odpaleniu funkcji dane z formularza są zapisywane do sesji a następnie jest pobierany jeden email z bazy lub jakaś porcja adresów, dokonywana jest wysyłka po czym ponownie jest wywoływany skrypt z parametrami od kóre maila ma zacząc, ile już się wysłało, ile nie, a jako że działa to asynchronicznie to na bieżąco te wszystkei dane mam wyświetlane i aktualizowane. Może nie jest to najlepsze rozwiązanie ale działa.

Pozdro.
andycole
za017, "pomógł".
Twój pomysł i skrypt pomogly mi przerobić mojego newslettera, teraz wysyłam za pomocą jQuery.
Fakt, że wysłałem paczkę jedynie do 1200 maili, a nie 20 000 jak wtedy, ale myślę, że ta metoda ma sens smile.gif

Informacja dla ludzi szukających recepty na to jak ominąć zabezpieczenia antyspamowe np onetu:
Wysyłam 150 maili, 3 sekundy przerwy.... I wszędzie dochodzi (wp, o2, interia, gmail, ONET smile.gif )
za017
Dzięki za plusik. To duża satysfakcja, że coś zrobionie dla przyjemności okazuje się przydatne dla kogoś.
Pozdrawiam.
To jest wersja lo-fi głównej zawartości. Aby zobaczyć pełną wersję z większą zawartością, obrazkami i formatowaniem proszę kliknij tutaj.
Invision Power Board © 2001-2025 Invision Power Services, Inc.