Праектаванне каналу паведамленняў без дублікацый на SQS, SES і Redis
Як дыяграма SQS з двумя каналамі, альтэрнатыўны спосаб перадачы з SES на SendGrid і атамарны блакіт Redis дапамагаюць прыняцоўцам отрымваць дакументы бізнесу роўна адной раз, нават пад высокай нагрузкай.
Крыніцы: How I Built a Notification Engine Handling 5M+ Events
Двое працавіка атрымляюць той самы захад у той жа момент. Кожны пераглядае лог электронных пісьмаў, не знаходзіць ніякіх запісаў і апышвае проблему. Апродавець атрымляе той самы замовленне два разы, і каманда даведваеся пра гэта з пісьма падтрымкі, а не праз монітарынг. У этай статыце расказваеца пра механізм паведамленняў для платформы B2B у ланцугу саплывоў, якая змогла обрабатваць паўторы больш чамуз мільёна захадоў ў месяц, а таксама пра тры проблемы ў працы, якія на яе вплылі: загубленыя электронныя пісьмы, пісьмы, якія блакуецца пад час стварэння PDF, і дуплікаты.
Што павінен даставляць механізм
У SaaS-распрацоўкі для ланца снабжэння продуктамі падпрыемствая працэсы, такія як стварэнне чыстая рэдагаванне замовлення або адканаўанне операцый з інвентаром, павінны ствараць дакументы для правільных аб’ектаў: замовленні на пакупку, замовленні на вывоз, наказы на доставку, акты прыёмкі товароў і рахункі-фактуры. Таму адна працэса можа складацца з калькі крокаў: стварэнне PDF, яго зберагчыце і адправка па электроннай поштазі калькам аб’ектаў. Той жа механізм таксама адправляе паведамленні праз SMS і WhatsApp для калькі іншых сцэнарыяў выкарыстоўвання.
Наследкі ёсць рэальныя. Паведамленне, якое так і не прыбывае, — гэта не толькі косметычны баг; відсутнае замовленні на пакупку можа завадзіць рэальным товарам дастацца клёянту.
Две очэрэдзі: адна прымярае, адная дастаёт
Дызайн базуецца на двух очэрэдзях Amazon SQS з чыста раздэльнымі заданнямі.
Галоўныя чэргі — гэта там, дзе прымуўцяюцься рашэнні. Адзін спожымач чытае кожную прыходзячую падзею і выявляе, што яна значыць: канфігурацыйную дакумент, канал для адправкі і таго, хто павінен яго прымець.
Падчэргі толькі адправляюць. Рабочыя з адправкай не застосоўваюць жадных бізнес-правіл; яны пераводзяць паведамленні да прадаўцаў.
Адна паведамленне на аднаго прымэчніка
Найважлівейшым рашэннем у такой структуры ёсць тое, што кожны прымэчнік атрымлея сваё сабскай паведамленне ў падчэрзі. Заказ, які павінен дасягнуць дзесяці чалавек, стае дзесяцью незалежнымі паведамленнямі. Калі адправка на одну адрэсу не выйшла, іншыя дзев’яць все равно будуць адправлены, а пракатка будзе спрамована толькі да таго прымэчніка, у якога была проблема, а не да всіх.
Такая структура таксама ізольвае змяны. Замена прадавальніка або канала вплывае толькі на аспект доставкі; код, які вяршыць выбор таго, што надаць, ніколі не патрэбуе ведаць, як перавозіцца звесць.
Чаму не адна очаковаец за каналом?
Адзінаковыя очаковаецы для электронных паштоў, SMS і WhatsApp далі б кожнаму каналу незалежную працэздатнась і механізмы раскрычвання адказоў. Працэсу з такім об’ёмам гэта не было неабходна, а стварэнне очаковаеца тады, калі гэта патрэбна, дэшавае, чым керуванне чатырма з самага пачатку.
Адказ адзін: электронныя пашты, якія так і не прыбылі
Прадавальнікі электронных паштоў злучваюцца, іноды явна з адказам пра абэрасцію, а іноды таямна з адказам, у якім не ўсё неабходнае. Amazon SES ёст тут галоўным прадавальнікам, і адказ без ID пашты спрацоўваецца як адказ пра абэрасцію, так сама як і выключэнне. У будзь-ям случае тая ж пашта надаецца праз SendGrid:
// simplified
try {
const res = await ses.send(email);
if (!res.MessageId) throw new Error("No MessageId from SES");
} catch (err) {
await sendgrid.send(email); // fallback
}
Зверніце увагу на явна выкліканне аберрацыі, калі не хватает MessageId: гэта перадае неясны кейс у той жа пат што і серьязная аберрацыя. Цэй выклік варта разумець. Калі SES прыняў электронны ліст, але адпаведна адпаведзь згубілася, механізм альтэрнатыўнага рашэння можа выдасці яго ўтрох, таму гэты слой праграмавана прывяжвае значэнне „выдана прынямна адной раз“ да значэння „выдана хоць раз“. Для бізнес-дакументаў гэта зазвычай правільны падход, але гэта ўжо адна з прычын, чаму важлівы крок адзначэння дублікатаў, опісаны ніжэй.
Калі аба прадастальнікі не функцыонуюць, ліст патрапляе у канэц-ліст для нерасылаемых паведамленняў і працоўваецца занова пасля павялічэння часу. Кроме таго, аддзельны механізм перасылкі бера ў працу электронныя лісты, якія занесены як нерасылаемыя, таму існуе шлях для восьбі, якая працяглася.
Другая аберрацыя: PDF-файлы, якія не функцыонуюць пад высокай навантажэнням
У документах-электранках дзеяе строгая правіла: нічага не адправляецца, пакуль не будзе створаны PDF. Прымоўцы патрабуюць сам факт замовлення, а не лінк, які вяртае памылку. Корантакты выклікае функцыю Lambda для стварэння PDF, зберагае файл у S3 і толькі пасля чаго адправляе электранку з лінкам да яго.
Правіла правыя, але яны створылі скрытую залежнасць. Калі сістэма сповольвалася і стварэнне PDF не выходзіла, электранка не падводзілася явна; ёй проста ніколі не выходзіла адправіцца, таму што яна чакала на файл, які ніколі не будзе створаны. Рашэнням было надаць працэсу стварэння PDF саваста спроб, так ўбачліва памылка пад час апрацоўкі больш не будзе таямніча абрываць пазначэнне для прымоўца.
Трэцяя памылка: тая ж электранка, два разы
Дуплікаты з’являліся з трох працоўнасупрацоўных джэрэл, і кожна з іх патрабавала разныя адпаведзі:
- звычныя багі логікі, якія былі вылечаны як багі;
Чаму перагляд журналу недастатні
З самага пачатку канвей пераглядаў журнал электронных паведамленняў пры адправцы: калі ў запісе было паказана, што паведамленне ужо было адправлена, павтапрыятые былі прыпінены. Гэта добра работае для серыйяваных павтапрыятыяў. Аднак гэта не можа зупініць конкурэнцію, адтолькі ўжыванне журналу і збір у яму — гэта два разныя заходы. Два працоўнікі могу абодва пераглянуць статус «не адправлена» пры тым, як ніхто з іх не запішае змяны, і абодва продаваць працу. Стандартныя канеўы SQS аддаюць паведамлення прынеймна раз, таму тое, што адно і то ж паведамленне падае да двух прыемнікаў, ёсць стандартной сітуацыяй, а не рэдкім збойным станом.
Атамарны блакат у Redis
Рашэнь складалася з выкарыстоўванняя атамічнага блаката ў Redis пры перапрацоўкі лог-даных. Ключ складаецца з ідэнтыфікатора замовлення, адрэсы электроннай пошты абяцанага прыёмніка і тыпу дакумента, і ён выгоршваецца прыблізна за пяць хвілін:
// simplified
const key = `notif:${orderId}:${email}:${docType}`;
const locked = await redis.set(key, "1", "NX", "EX", 300);
if (!locked) return; // someone else already has it
SET з опцыяй NX запісвае ключ толькі калі ён яшчо не існуе, а EX 300 у той жа командзе задае тэрмін выгоршвання у 300 секунд. Пакалі перапрацоўка наявнасці ключа і яго запіс выкананы як адна операцыя на серверэ Redis, толькі аднам працоўніку даходзіць правдападобны рэзультат; усе іншы працоўнікі бачаць null і завершаюць работу раней. Чыгунам такім чынам закрываецца прасвет, які засталі праз перапрацоўку лог-даных.
Ёсць калькі асоблівасцяў гэтага падходу, якія варта запамятаць:
- Важліва гранулярнасць ключа. Уключэнне тыпу дакумента значыць, што замовлення на праймут і рахунок-фактура для таго ж замовлення і прыёмніка не будуць спачатку вважаныя дуплікатамі.
Тая ж ідэя выкарыстоўваецца ў HTTP API-х як ключы ідэмпатэнтнасці; артыкул на ключы ідэмпатэнтнасці ў Node.js POST-канцах распаводзіцься пра гэты аспект.
Куды дасягла сістэма
Пасля гэтых змян двыжак обрабоцвае больш чамуз пяці мільйонаў запускаў на месцы ў месяц за дапамогою трыгераў замовленняў, запасоў і дакументаў, з 98,9% успешнай доставкі і часам задзейснення па p95 меншым за 1,2 секунды.
Што робіць інакш следзеўчы раз
- Адкрывайце проблемы ранейш, чым гэта зрабяць кліенты. Занадто многія проблемы спачатку практычна выказваліся як працоўныя запытанні, а толькі пасля — у журналах. Апавешчэнні пра неудачы доставкі і пра глыбіню календара з непрацэйнымі паведамленнямі павінны быць ў першай версіі, а не ў пазнейшай.
- Запобегайце знікненню запісаў пры здарэннях. Дзеяныя запісы знікалі з прычын, якія так і не былі цэлкам адказаны. Транзакцыйны календар, які фіксуе здарэнне разам з бізнес-запісам і пасля таго яго перадае, робіць такія зніклыя запісы значна рэдкімі. Статыя на тэблы календара выходу і прыймання пояснюе гэты механізм.
- Залічвайце аднаго падкалендара, пакуль гэта не стане проблемай. Спяльны календар доставкі па-ранейшам работае; яго трэба роздзеліць толькі тады, калі адны канал пачне перакрываць рэшты.
Спіс пераконтрацыяў для стварэння савама
- Зберагаюце рашынкі пра маршрутызацыю ў адным месца, а процэс доставкі — у іншым.
- Адправляйце по адной паведамленні на кожага прыёмніка, ўпэўніваючыся, што адна неудача не заблокуе рэшту.
- Не павяржыцеся ў адного-едзінага прадастальніка, і спрыймайце відсутнасць ID паведамлення як неудачу.
- Надаюце паведамленням, якія не былі доставлены, пункт прызначэння: очакальную чэргу для недаступных паведамленняў з можлівасцю перапрыбутку.
- Ніколі не адправляйце документ у фармаце электроннага пісьма ранейш, чым документ існуе, і перапрыбуйце крок, які яго стварае.
- Зрабіце процэс унікалізацыі атамарным, таму што практыка спачатку пераканалізацыі, а потым запісу залеўляе вакуум для можлівых нештатнасцей.
- Спавешчайце пра неудачы, каб кліянты не сталі вашай сістэмай манітарынгу.
Заключанне
Ніякі з эых захоўнікаў не былі спраектаваны з самага пачатку; кожны з іх быў даданы пасля таго, як у працэсе выдання ўстало ўсё, і самэ гэта являецца спосабам, пры яком большасць сістэм выдання фактычна розвіваецца. Урок заключаецца не столькі ў конкрэтным рашэнні, сколькі у парадку мышлення: прыпускайце, што выданне адбудзецца хаця б раз, прыпускайце, што прадастальнікі і залежнасці будуць выкананы без проблем, і робіце кожны крок або ідэмпатным, або захаваным чымось атамічным.