Всем большое спасибо, кто
проголосовал. Большинству интересны вопросы архитектуры — это понятно. Но также здорово, что половине интересен AI, а трети — софт-скилы и менеджмент.
Короче, есть такая идея: сделать серию постов с “глоссарием” понятий, паттернов и анти-паттернов на эти темы. Читаете, что-то новое узнаете, всем профит.
Хочу начать с пачки терминов, которые называют
паттернами отказоусточивости. Вообще говоря, мне всегда казалось, что самое сложное в распределенных системах это распределенные базы, а вопросы устойчивости распределенной базы (диапазоны ключей и рапределение по нодам, лидер/реплики, кворум/перевыборы, сплитбрейн) вообще про другое, но не будем сюда копать (кто знает термины удачнее — велкам в каменты).
Итак, условно можно разделить паттерны отказоусточивости на две группы, и они про разные цели
- как защититься от отказа чужого сервиса, взгляд со стороны “клиента”
- как ограничить внешние запросы, чтобы не вызвать собственный отказ
Это разделение условно, вот например, паттерн
exponential backoff (with jitter конечно, у вашего exponential backoff ведь есть jitter? ладно, шучу) — exponential backoff короче, одновременно и заботится о клиенте, и о том, чтобы не перегружать вызываемый сервис.
Конечно начать надо с простых понятий, например, с
timeout:
• таймаут это ”жесткая” верхняя граница: если она превышена, операция на клиенте прерывается
• таймауты ставятся на разные операции: например, есть connect, read, write timeout
• connect timeout может быть до установления TCP-соединения, а может быть до установления TLS-хендшейка в каких-то продвинутых прикладных библиотеках, и это еще не такой большой сюрпиз как то что
• connect timeout может вдруг не сработать потому что внутри connect есть резолвинг днс, а он скорее всего блокирующий со своим таймаутом, поэтому фиговый DNS/конфигурация может неслабо налить в ваш latency
• read-timeout надо смотреть внимательно api что конкретно он ограничивает, т.к. это может быть общий таймаут, или просто таймаут между последовательной вычиткой данных, или даже таймаут на чтение заголовков (в Go например есть Transport.ResponseHeaderTimeout)
• если операция на клиенте прерывается, но на самом деле статус что было на сервере “мы хз”, и это источник неприятных редких ошибок, поэтому операции, которые меняют состояние, должны иметь ключ идемпотентности (если вы не можете никак выучить это слово, запомните так: “идём, потент!”)
• если вы из бигтеха, и у вас цепочка вызовов между вашими четырьмя тысячами микросервисов, то встает вопрос на общий бюджет цепочки и начинается полная веселуха, спасает разве что сквозная передача остатка бюджета типа grpc-timeout.
Как видите, термин очень простой. Но можно опуститься до глубин, обсуждая эти сраные таймауты. Вот например у меня есть такое историческое сообщение от Андрея Нигматулина, и я думаю что ему лет двадцать, где Андрей “поясняет похапешникам за ядро”:
Это значит, до ядра дошёл ACK (третий пакет в цепочке SYN, SYN+ACK, ACK),
которым клиент хочет сообщить серверу о том, что производит соединение. Если
backlog в этот момент не полный (см somaxconn и параметр в listen(2)), то
соединение на стороне сервер помещается в backlog, и дальше оно доступно для
accept(2).
Если же backlog полный (сервис подвис или по какой-то другой причине не делает
accept() так быстро, чтобы backlog не переполнялся), то пакет дропается и
увеличивается счотчик ListenOverflows.
У такого соединения, однако, всё же есть шанс быть установленным в дальнейшем,
т.к. tcp поддерживает re-transmits
Вот, таймауты, короче, кхм, очень простая штука. Дальше будет в таком же духе про retry, exponential backoff (с jitter, конечно), circuit breaker, bulkhead, fallback, hedged requests, rate limiting, throttling, backpressure, load shedding и steady state. Но помни %username% это всё кобол, в новом мире вайбкодинга это всё зашьют сначала в системный промт, а потом в претрейн, так что никому не понадобится даже “если отвалился, дёрни снова с экспоненциальной задержкой, но не более трех раз”.
🔥спасибо, подучил