RabbitMQ wysyłka email

RabbitMQ wysyłka email
LU
  • Rejestracja:około 11 lat
  • Ostatnio:około miesiąc
  • Lokalizacja:Gdańsk
0

W pewnym projekcie powstał problem na produkcji. Emaile do klientów z fakturami się nie wysyłają. Architektura jest taka, że job wstaje codziennie o określonej godzinie i wysyła wystawione faktury klientom. Aby to zrobić wrzuca na kolejkę to co trzeba wysłać a inna aplikacja odpowiedzialna za wysyłkę to odbiera i wysyła (komunikuje się z serwerem pocztowym itd.). Gdy wysyłka się nie powiedzie aplikacja do wysyłki wrzuca na topic failure to co trzeba było wysłać a dalej już z tego topicu nic nie odbiera. Aktualnie na topicu failure wisi około 70 rzeczy do wysyłki, nie udało się wysłać z różnych powodów np. Request timeout.
Czy jest lepsze rozwiązanie?
Może job powinien wrzucać na kolejke wszystko to co jest do wysyłki uwzględniając to co nie udało się wysłać? Wtedy wysyłka tych nieudanych byłaby ponawiana codziennie. Tylko nie ma on informacji o tym czy się udało czy nie, wysyłka odbywa się w innym serwisie. Może ten serwis powinien wrzucać na jakiś topic np. success jako response czy się udało czy nie?


grski
  • Rejestracja:ponad 9 lat
  • Ostatnio:8 miesięcy
  • Postów:245
4

Miałem podobną zagwozdkę kiedyś w jednej z firmy. Opiszę ci trochę, jak my to rozwiązaliśmy.

Otóż był sobie task - w twoim przypadku jest to wysyłka mejla akurat.

Był sobie jakiś tam obiekt. W twoim przypadku jest to faktura. W db mieliśmy tam rekord tak dla spokoju typu Faktura() { created_at, updated_at, invoice, sent=False }

Był wykonywany jakiś task. Task się mógł udać, lub nie. Jeśli się udał, to spoko, oprócz wykonania taska i zrobienia danej czynności, na sam koniec musiał też polecieć aptejd do bazy, że dana Faktura sent=True.

Jeśli się nie udał task, to to, co się działo dalej, zależało od sytuacji.

Były czasami taski, które chcemy retrajować w nieskończoność, muszą się udać albo być powtarzane ciągle.
Tutaj logika była prosta, failure worker zjadał sobie taskiz kolejki, jak się nie udało, odkładał na górę, jak się udało oznaczał rekord w bazie i commitował taska. I tak w kółko. Tutaj potencjalne zagrożenie: kolejka która rośnie w nieskończoność.

Były taski, które miały się retrajować maksymalnie X razy. Wtedy mieliśmy dwa różne podejścia.
Pierwszym jest podobny przypadek co wyżej, ale z modyfikacją. To znaczy robimy maksymalnie X retrajów, oddalonych od siebie w jakiś plus minus stałych odstępach czasowych albo podobnych i tyle. Jeśli się uda retrajing - super. Jeśli po X razach task dalej się nie uda, wrzucaj go na kolejke failed_retrying. I tutaj masz dwa podejscia rozbicia tego problemu: ze schedulingiem tzn każdy retraj co X minut, albo po prostu FIFO na kolejce i w ten sposób taski będą konsumowane.

Drugim rozwiązaniem był exponential backoff. Robiliśmy X retrajów, każdy oddalony od drugiego wykładniczo dłużej w czasie. Po X nieudanym retraju tak samo - na failed_retrying topic/kolejke.
Tutaj też pamiętam, że rozważaliśmy dwa rozwiązania odnośnie implementacji - jeden topic ze wszystkimi failurami i do danych taska pchać kiedy ma się wykonać vs failury per backoff czyli topic_5m_retry, topic_15m_retry, topic_60m_retry i tak dalej. Z racji wydajnościowych wybraliśmy opcję numer dwa by miec jak najwięcej topiców i workerów, większe rozłożenie.

Podsumowując, biorąc pod uwagę to, co opisałeś, ja to bym zrobił w sposób następujący:

  1. Worker zjada taski wysyłki z danego topica.
  2. Te taski, które się udały - super, świetnie, oznaczmy je w db, że się udały.
  3. Te, które się nie udały, wrzucam na pierwszą kolejkę exponential backoff retry czyli np. retry_1m, które po 1 minucie spróbuje ponownie.
  4. Powtarzam kroki 2-3, przy czym przerzucam taski nieudane na kolejki o wykładniczo większym backoffie, aż do momentu kiedy przekroczę maksymalną liczbę retrajów. Liczbę retrajów możesz śledzić w bazie albo eg. redisie jak baza za wolna lub poprzez obserwacje który task znajduje się na jakiej kolejce - jak jest na 3, to wiadomo, że 1 i 2 się nie udały.
  5. Gdy przekroczę maksymalną liczbę retrajów do jakiegoś taska, wrzucam go na kolejkę retry_failed skąd zjada go ostatni worker, podejmujący jakąś akcję - eg. informacja admina czy coś, że wysyłka tego się nie udała mimo retrajów i raczej się nie uda w przyszłości -> nie ma sensu próbować dalej.

Napisałem książkę - Programowanie z Górskim: Junior Python Developer
Pora na następny krok na drodze po pierwszą pracę w it i WCALE-NIE-MITYCZNE #programista40k? Zapraszam.
edytowany 1x, ostatnio: grski
Ktos
Ja rozumiem anglicyzmy i kalki językowe w programowaniu, ale retraje i przede wszystkim retrajing mnie lekko załamały :)
TS
Nie dałem rady tego przeczytać
KA
A mi się podoba :) brzmi jak jakiś pastisz korporacyjnej mowy: zretrajuj taska asap.
LU
  • Rejestracja:około 11 lat
  • Ostatnio:około miesiąc
  • Lokalizacja:Gdańsk
0

topic_5m_retry, topic_15m_retry, topic_60m_retry

Korzystałeś może z czegoś ala https://github.com/rabbitmq/rabbitmq-delayed-message-exchange
czy konsumer gdy otrzymał message sam opóźniał jej przetwarzanie?

Gdy przekroczę maksymalną liczbę retrajów do jakiegoś taska, wrzucam go na kolejkę retry_failed skąd zjada go ostatni worker, podejmujący jakąś akcję - eg. informacja admina czy coś, że wysyłka tego się nie udała mimo retrajów i raczej się nie uda w przyszłości -> nie ma sensu próbować dalej.

Co jeśli przez 2 dni nie było sieci? Czy wysyłka po 2 dniach zostanie ponowiona tego co się nie udało?


danek
  • Rejestracja:ponad 10 lat
  • Ostatnio:6 miesięcy
  • Lokalizacja:Poznań
  • Postów:797
0

A znacie powody dla których nie udaje się ich wysłać? Czy są to sytuacje losowe? Co z takimi wiadomościami powinno się stać z punktu widzenia biznesu?


Spring? Ja tam wole mieć kontrole nad kodem ᕙ(ꔢ)ᕗ
Haste - mała biblioteka do testów z czasem.
grski
  • Rejestracja:ponad 9 lat
  • Ostatnio:8 miesięcy
  • Postów:245
0

Korzystałeś może z czegoś ala https://github.com/rabbitmq/rabbitmq-delayed-message-exchange
czy konsumer gdy otrzymał message sam opóźniał jej przetwarzanie?

  1. Nie, do tej pory raczej pracowałem z kafką, przynajmniej jeśli chodzi o takie masowe, troszkę bardziej skomplikowane zastosowania. Prostych appek z celerką gdzie rabbit był jedynie jako najprostszy broker, nie liczę.
  2. Wygląda na to, że ten plugin zrobi ci robotę i w tym wypadku tak - będzie to zachowane by default. nie jestem jednak pewien, nigdy z niego nie korzystałe. Kkiedyś widziałem za to Leniwą implementacje. Można to zrobić w ten sposób, że w message doszywasz czas kiedy task powinien być wykonany, consumer sobie przelatuje przez wszystkie taski, patrzy na datę i jeśli jest za wcześnie, to ich nie commituje jako wykonanych/jeśli commituje to znowu wysyła na kolejke.

Co jeśli przez 2 dni nie było sieci? Czy wysyłka po 2 dniach zostanie ponowiona tego co się nie udało?

Jeśli często sytuacje gdzie nie macie sieci przez 2 dni to myślę, że to nie nad retrajem powinno się tu popracować a zmianą seweroni :D

A czy powtórzy? To zależy którą strategię zaimplementujecie - w przypadku powtarzania z pętlą to wiadomo - będzie powtarzać i po 2 dniach i po 10 dniach - dopóki nie każesz mu przestać.

W przpyadku ograniczenia z maksymalną liczbą powtórzeń, to zależy ile będzie tych powtórzeń w jakim odstępie czasowym - jeśli np. ostatni retraj wypada za 3 dni to tak, ale w innym wypadku pewnie nie.

Oczywiście jest też opcja, że dwa dni bez sieci to jakiś kluczowy requirement, którego się nie da zmienić i specjalnie isę przed tym zabezpieczysz i dopiszesz w consumerze kod który sprawdza czy jest sieć, jak nie, to sobie wchodzi w stan uśpienia i sprawdza znowu eg za minutę czy coś.

Poza tym dość specyficzna sytuacja - 2 dni bez sieci, dzieje się to u was?
Macie jakieś logi które by wam powiedziały dlaczego te niektóre faktury nie są wysyłane? Może warto by to zbadać?

Z emailem generalnie to sprawa jest prosta i o ile jest sieć a smtp działa, to wysyłka się nie powiedzie raczej bardzo rzadko: emaila nie ma(czyli powtórka nie ma sensu bo i tak się nie uda), serwer klienta/twój chwilowo nie działa (tu warto powtarzać) albo właśnie nie ma sieci. 1 pewnie dość częste, 2 i 3 raczej dość rzadkie.


Napisałem książkę - Programowanie z Górskim: Junior Python Developer
Pora na następny krok na drodze po pierwszą pracę w it i WCALE-NIE-MITYCZNE #programista40k? Zapraszam.
edytowany 5x, ostatnio: grski
LU
  • Rejestracja:około 11 lat
  • Ostatnio:około miesiąc
  • Lokalizacja:Gdańsk
0

Co z takimi wiadomościami powinno się stać z punktu widzenia biznesu?

Z punktu widzenia biznesu powinny trafić do klienta :)

Jeśli często sytuacje gdzie nie macie sieci przez 2 dni to myślę, że to nie nad retrajem powinno się tu popracować a zmianą seweroni

Podałem to jako przykład, nwm jak często ale są błędy "Request timeout" co jakiś czas.
Myślę, że niezłym rozwiązaniem byłoby stworzenie tabeli np. shipment, w której odkładałbym rzeczy do wysyłki.
Tabela miałaby kolumnę, flagę sent=true/false. Job codziennie pobierałby z tej tabeli wszystko co niewysłane i wrzucał na kolejke.
Serwis odpowiedzialny za wysyłke odbierałby z kolejki rzeczy do wysłania i po udanej wysyłce robił update do bazy sent=true.
Obyłoby się bez retry ewentualnie jeden po godzinie, byłoby w miarę defensywnie.


catom
  • Rejestracja:około 6 lat
  • Ostatnio:ponad 2 lata
  • Postów:58
4

W jednym z projektów, widziałem takie rozwiązanie:

  • Consumer kolejki jawnie robił ACK lub NACK, w zależności od tego, czy się udało, czy nie.
  • Dla kolejki zdefiniowana była DLQ,na którą trafiały komunikaty po otrzymaniu NACK.
  • Na DLQ ustawiony był TTL i po jego upłynięciu, trafiało to na jej DLQ, które ustawione było na tę pierwszą kolejkę.

Tj. masz 2 kolejki:
emails
emails-dlq

Wiadomość trafia na
1. emails ->
2. Consumer.read()
2. a) Consumer.nack() (wysyłanie się nie powiodło) ->
2. b) Conumer.ack() (wysyłanie się powiodło) END
3. emails-dlq ->
4. upłynął ttl ->
5. emails -> przejdź do punktu 2.

Problem jest taki, że mogą Ci zostać w obiegu komunikaty, przesyłane między kolejkami emails i emails-dlq, które z jakiegoś powodu nigdy nie zostaną wysłane i nie dość, że będą Ci zapychać kolejkę, to jeszcze Consumer je będzie bez sensu przetwarzał.

W tym rozwiązaniu, twórcy rozwiązania dodali pole typu retriesCount w wiadomościach i na podstawie parametru maxRetriesCount zdejmowali wiadomość w Consumerze po osiągnięciu takiego retriesCount.

Można by to nieco zmodyfikować i consumer mógłby wrzucać sam na kolejkę DLQ emails-dlq, zamiast NACK z różnym TTL i dodać do tego exponential back off (jak pisał @grski), gdzie na podstawie konkretnego retriesCount (1,2,3...) dawać inne TTL per message, a nie na poziomie całej kolejki.

edytowany 2x, ostatnio: catom
YA
  • Rejestracja:prawie 10 lat
  • Ostatnio:około 7 godzin
  • Postów:2364
1

Do tego co napisał @catom, to TTL nie zawsze jest dobry, ale też nie zawsze zły ;-)

Dla jednego z systemów opartego o kolejki mieliśmy 4 kolejki na obsługę odrzutów od jednego z konsumentów:

  • R1 - odrzucone przez konsumenta w pierwszym przetwarzaniu
  • R2 - zbiera odrzuty poddawane recyclingowi z R1 (po upływie dnia)
  • R3 - poddawane recyclingowi z R2 (po upływie tygodnia)
  • R4 - zbiera odrzuty, które nie rokują - do analizy przez zespół od utrzymania i podjęcia dalszych akcji (np. naprawienia czegoś i wrzucenia do INPUT albo wytriggerowania jakichś akcji, np. zgłoszenie błędu do softu, konfiguracji etc.)

R1..R4 nie miały podłączonych konsumentów, ale były zdefiniowane joby, które batchowo przenosiły wiadomości do kolejki "wejściowej":

  • R1 --> INPUT
  • R2 --> INPUT
  • R3 --> INPUT

Dla R4 monitorowanie ilości elementów + podnoszenie alarmów -> triggerowanie manualnych akcje.

Takie podejście wynikało z wolumenu przetwarzanych komunikatów (miliony dziennie - połączenia telefoniczne) i chęci odciążenia systemu, tak by "problematyczne wiadomości" nie krążyły non-stop między kolejkami (z drobnymi przerwami). Jedna taka wiadomość często przerzucana między INPUT<-->REJECT potrafi skutecznie zeżreć CPU.

Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)