Як ви відтворюєте умови помилок і переглядаєте, що відбувається під час виконання програми?
Як ви візуалізуєте взаємодію між різними сумісними частинами програми?
На основі мого досвіду відповідь на ці два аспекти виглядає наступним чином:
Розподілений трасування
Розподілене трасування - це технологія, яка фіксує дані про терміни для кожного окремого компонента вашої системи та представляє їх у графічному форматі. Представлення одночасних страт завжди переплітаються, що дозволяє вам бачити, що працює паралельно, а що ні.
Розподілене трасування завдячує своїм джерелом (звичайно) розподіленим системам, які за визначенням є асинхронними та сильно сумісними. Розподілена система з розподіленим трасуванням дозволяє людям:
а) визначити важливі вузькі місця, б) отримати наочне уявлення про ідеальні «пробіги» вашої програми та в) забезпечити наочність того, яка паралельна поведінка виконується; г) отримати дані про терміни, які можна використовувати для оцінки відмінностей між змінами система (вкрай важливо, якщо у вас є сильні угоди про домовленість).
Наслідками розподіленого трасування є:
Він додає накладні витрати на всі ваші паралельні процеси, оскільки він перетворюється на більше коду для виконання та надсилання потенційно через мережу. У деяких випадках ця накладні витрати дуже важливі - навіть Google використовує лише їх систему трасування Dapper лише для невеликого підмножини всіх запитів, щоб не зруйнувати досвід користувачів.
Існує багато різних інструментів, не всі з яких є взаємодіючими між собою. Це дещо покращено такими стандартами, як OpenTracing, але не повністю вирішеними.
Це нічого не говорить про спільні ресурси та їх поточний статус. Можливо, ви зможете здогадатися на основі коду програми та того, що вам показує графік, але це не корисний інструмент у цьому плані.
Поточні інструменти передбачають, що у вас є запасний обсяг пам’яті та місця. Розміщення сервера таймсері може бути недешевим, залежно від ваших обмежень.
Помилка відстеження програмного забезпечення
Я посилаюсь на Sentry вище перш за все тому, що це найпоширеніший інструмент там, і з поважної причини - програмне забезпечення для відстеження помилок, наприклад виконання програми Hijack Hent, щоб одночасно пересилати стек стека помилок, що виникають на центральний сервер.
Чиста перевага такого виділеного програмного забезпечення у паралельному коді:
- Повторні помилки не дублюються . Іншими словами, якщо одна чи кілька одночасних систем стикаються з тим самим винятком, Sentry збільшить звіт про інцидент, але не подасть дві копії інциденту.
Це означає, що ви можете зрозуміти, яка паралельна система зазнає помилки, не проходячи незліченну кількість одночасних повідомлень про помилки. Якщо ви коли-небудь страждали спамом електронної пошти від розповсюдженої системи, ви знаєте, що таке пекло.
Ви навіть можете «позначати» різні аспекти вашої одночасної системи (хоча це передбачає, що у вас немає роботи, переплетеної саме через один потік, який технічно не є одночасно, оскільки нитка просто ефективно переходить між завданнями, але все одно повинна обробляти обробники подій до завершення) і побачити розбиття помилок за тегом.
- Ви можете модифікувати це програмне забезпечення для обробки помилок, щоб забезпечити додаткові деталі з винятками під час виконання. Які відкриті ресурси мав цей процес? Чи є спільний ресурс, який тримав цей процес? Який користувач відчув цю проблему?
Це, окрім ретельних слідів стека (та вихідних карт, якщо вам потрібно надати мінімізовану версію файлів), дозволяє легко визначити, що відбувається не так велику частину часу.
- (Для певного типу) Ви можете мати окрему інформаційну панель звітності про тестові запуски системи, що дозволяє вловлювати помилки під час тестування.
До недоліків такого програмного забезпечення можна віднести:
Як і все, вони додають об'ємність. Наприклад, ви не хочете такої системи на вбудованому обладнання. Я настійно рекомендую пробний запуск такого програмного забезпечення, порівнюючи просте виконання з і без нього вибірки протягом декількох сотень запусків на простої машини.
Не всі мови однаково підтримуються, оскільки багато з цих систем покладаються на неявне вилучення винятку, і не всі мови мають надійні винятки. При цьому, є багато клієнтів для багатьох систем.
Вони можуть бути підвищені як ризик для безпеки, оскільки багато хто з цих систем по суті є закритим джерелом. У таких випадках докладіть належних зусиль у їх дослідженні або, якщо бажаєте, прокатуйте свої власні.
Вони не завжди можуть дати тобі потрібну інформацію. Це ризик при всіх спробах додати видимість.
Більшість цих сервісів були розроблені для дуже одночасних веб-додатків, тому не кожен інструмент може бути ідеальним для вашого випадку використання.
Підсумовуючи : видимість є найважливішою частиною будь-якої супутньої системи. Два способи, які я описав вище, у поєднанні з виділеними інформаційними панелями щодо обладнання та даних, щоб отримати цілісну картину системи в будь-який момент часу, широко використовуються у всій галузі саме для вирішення цього аспекту.
Деякі додаткові пропозиції
Я витратив більше часу, ніж я дбаю про виправлення коду людьми, які намагалися вирішити паралельні проблеми жахливими способами. Кожного разу я виявляв випадки, коли такі речі можуть значно покращити досвід розробника (що так само важливо, як і досвід користувачів):
Покладайтеся на типи . Введення тексту існує для підтвердження вашого коду і може використовуватися під час виконання в якості додаткового захисту. Якщо введення тексту не існує, покладайтеся на твердження та відповідний обробник помилок, щоб виявити помилки. Паралельний код вимагає оборонного коду, а типи служать найкращим наявним видом перевірки.
- Перевірте зв’язки між компонентами коду , а не лише самим компонентом. Не плутайте це з повномасштабним тестом на інтеграцію - який перевіряє кожну зв'язок між кожним компонентом, і навіть тоді він лише шукає глобальну перевірку кінцевого стану. Це жахливий спосіб ловити помилки.
Хороший тест на посилання перевіряє, чи, коли один компонент розмовляє з іншим компонентом ізольовано , отримане повідомлення та надіслане повідомлення є тим самим, що й очікується. Якщо у вас є два або більше компонентів, які покладаються на спільну послугу для спілкування, спінізуйте їх усіх, попросіть їх обмінюватися повідомленнями через центральну службу та дізнайтеся, чи всі вони отримують те, що ви очікуєте в підсумку.
Розбиття тестів, що включають багато компонентів, у тест самих компонентів і тест того, як кожен з компонентів також спілкується, дає вам підвищену впевненість у дійсності вашого коду. Наявність такого строгого тестування дозволяє виконувати контракти між службами, а також фіксувати несподівані помилки, які виникають під час їх запуску відразу.
- Використовуйте правильні алгоритми для перевірки стану вашої програми. Я говорю про прості речі, наприклад, коли у вас є головний процес, який чекає, коли всі його працівники закінчать завдання, і хочете перейти до наступного кроку, якщо всі працівники повністю виконані - це приклад виявлення глобальної припинення, для якого існують відомі методології, такі як алгоритм Сафра.
Деякі з цих інструментів постачаються в комплекті з мовами - наприклад, Rust гарантує, що ваш код не матиме гоночних умов під час компіляції, тоді як Go має вбудований детектор тупикового зв'язку, який також працює під час компіляції. Якщо ви зможете наздогнати проблеми, перш ніж потрапити на виробництво, це завжди виграш.
Загальне правило: проектування для відмов у паралельних системах . Передбачайте, що загальні служби вийдуть з ладу або зламаються. Це стосується навіть коду, який не поширюється на машинах - паралельний код на одній машині може покладатися на зовнішні залежності (наприклад, загальнодоступний файл журналу, сервер Redis, проклятий сервер MySQL), який може зникнути або видалитись у будь-який час .
Найкращий спосіб зробити це - час від часу перевіряти стан заявки - провести перевірку стану здоров’я для кожної послуги та переконатись, що споживачі цієї служби повідомляються про погане самопочуття. Сучасні інструменти для контейнерів, такі як Docker, роблять це досить добре, і їх слід використовувати для роботи з пісочницею.
Як ви зрозумієте, що можна зробити одночасно, а що можна зробити послідовним?
Один з найбільших уроків, який я навчився, працюючи над системою, що суттєво поєднується, це такий: ви ніколи не можете мати достатню кількість показників . Метрики повинні керувати абсолютно всім у вашій програмі - ви не інженер, якщо ви все не вимірюєте.
Без показників ви не можете зробити кілька дуже важливих речей:
Оцініть різницю, внесену змінами в систему. Якщо ви не знаєте, чи ручка налаштування A, зроблений показник B, знижується, а показник C знизиться, ви не знаєте, як виправити вашу систему, коли люди несподівано натиснуть злоякісний код на вашу систему (і вони підштовхнуть код до вашої системи) .
Зрозумійте, що вам потрібно зробити далі, щоб покращити речі. Поки ви не знаєте, що у додатків не вистачає пам'яті, ви не можете зрозуміти, чи потрібно вам більше пам’яті чи купувати більше диска для своїх серверів.
Метрики настільки важливі і важливі, що я доклав зусиль, щоб спланувати те, що я хочу виміряти, перш ніж я навіть подумаю про те, що потребуватиме система. Насправді метрики є настільки важливими, що я вважаю, що вони є правильною відповіддю на це запитання: ви знаєте, що можна зробити послідовним або паралельним, коли ви вимірюєте, які біти у вашій програмі виконуються. Правильний дизайн використовує цифри, а не здогадки.
Але, безумовно, є кілька основних правил:
Послідовна передбачає залежність. Два процеси повинні бути послідовними, якщо один певним чином залежить від іншого. Процеси без залежностей повинні бути одночасними. Однак сплануйте спосіб впоратися з відмовою в потоці, що не перешкоджає процесам нижче за течією чекати нескінченно.
Ніколи не змішуйте завдання, пов'язані з входом / виводом, із завданням, пов'язаним з процесором, на одному ядрі. Не пишіть (наприклад) веб-сканер, який запускає десять одночасних запитів в одну нитку, не записує їх, як тільки вони надходять, і не очікуйте масштабування до п'яти сотень - запити вводу / виводу переходять паралельно до черги, але процесор все одно пройде через них послідовно. (Ця однопоточна модель, керована подіями, є популярною, але вона обмежена через цей аспект - замість того, щоб зрозуміти це, люди просто затискають руки і кажуть, що Node не масштабує, щоб навести приклад).
Один потік може зробити багато роботи вводу / виводу. Але для повного використання одночасності вашого обладнання використовуйте потокові пули, які разом займають усі ядра. У наведеному вище прикладі запуск п'яти процесів Python (кожен з яких може використовувати ядро на шестиядерній машині) тільки для роботи з процесором, а шостий потік Python тільки для роботи вводу / виводу буде масштабуватися набагато швидше, ніж ви думаєте.
Єдиний спосіб скористатися паралельністю CPU - це спеціалізована нитка. Один потік часто досить хороший для великої роботи, пов'язаної з входом / виводом. Ось чому веб-сервери, керовані подіями на зразок Nginx, краще (вони виконують суто роботу, пов'язану з входом / виводом), ніж Apache (яка пов'язує роботу, пов'язану введення / виводу з чимось, що вимагає процесора та запускає процес за запитом), але чому використовувати Node для виконання десятки тисяч розрахунків GPU, отриманих паралельно, - жахлива ідея.