Pull to refresh
25
0
Илья Сидоров @Lol4t0

User

Send message

Для простенького сервиса, наверное, пойдёт, но для production, скорее, не стоит, это же никто не поддерживает, в том числе в плане совместимости с новыми стандартами и библиотеками

Для IO нагрузки подойдёт userver, для числодробилки не могу дать совета, но и вряд ли fastcgi-daemon2 на C++03

Эта штука умерла 6 лет назад, лучше не надо повторять)

Вот вы смеётесь, но, кажется, так и бывает. Программист использует tolower для проверки аргумента (обычно пустая строка), tolwer загружает локаль с диска. У какого-то странного человека настройки локали загружаются с сетевой шары с пингом 2с. Новая статья на хабр о криворуких программистах готова!

Остается задача, как сделать так, чтобы каждый новый входной триггер провоцировал срабатывание правил строго на одном инстансе ЭС, но это уже дело техники.

Ох, что-то мне это не кажется таким простым делом, может быть вы знаете какой-то секрет?:-))

Проблемы, с которыми мы сталкивались - инстанс может внезапно вылететь, нужно уметь это обрабатыавть, или может зависнуть на середине операции, а потом вернуться через полчаса; а ещё бывает brain split

В основном плюсы и питон

У нас в любой момент времени был какой-то говтовый продукт, который мы улучшали с учётом накопленного опыта путем последовтельных минимльных изменений. То есть у нас сначала появлился какой-то автомат цикла заказа, а потом мы его формализовали. Взять готовый фреймворк и переписать архитектуру вокруг него - довольно сложный шаг при таком подходе, потому что сразу большую часть логики придется переписать, а значит и провалидировать, что она праивльно работает; нужно будет познакомить разработчиков с новой библиотекой, набрать опыт эксплуатации. Мы посчитали, что ценность готового фреймворка не оправдывает таких резких изменений.

Для сериализации используем postgres, хотим ещё попробовать YandexDatabase, потому что pg сложно масштабрировать горизонально

Пока что мы придерживались концепции, что в процессинге простые обработчики, которые могут ходить во внешние сервисы - по http или через очередь сообщений - в процессе обработки событий. А уже внутри сервисов какая-то сложная бизенс-логика. Такая модель помогает лучше прогнозировать необходимые ресурсы для заданного уровня нагрузки, упрощает devops самого процессинга. Но есть и проблемы - нужно заводить отдельный микросервис для каждой задачи, даже если она уровня - положи что-то в базу. Ну и хождений по сети больше становится. Поэтому мы в целом думаем, как бы нам сделать поддержку сложных обработчиков, и не сломать изоляцию и эксплуатацию)

У нас вся система строится от лога событий. Основной инвариант - сколько бы раз и в какой бы момент вы не пробовали прогнать лог событий через правила вычисления состояния - для одного и того же лога событий должно получиться одно и то же состояние. Поэтому сходить во внешний источник во время вычисления состояний нельзя, всё состояние должно вычисляться по тем данным, которое содержатся в событиях и, собственно, правилам вычисления состояний.

Но при этом сами правила могут быть гибкими, можно делать конструкции вида

if state.status == 'pending' and current_event.key == 'new_driver_found':
  state.status = 'assigning'
  state.current_driver_id = event.driver_id

Где state - текущее состояние автомата, current_event - событие в логе, которые мы обрабатываем

В этом примере видно, что можно в состояние добавить новый контекст из события, этот контекст будет виден при обработке следующих событий

if state.status == 'assigning' and current_event.key == 'driver_accepted_offer' and state.current_driver_id == current_event.driver_id:
  state.status = 'assigned'

Вот внутри обработчика каждого события можно ходить во внешние ручки, у нас есть возможность разделить обработчик на независимые стадии, внутри обработчика таким образом получается свое небольшое состояние, которое полностью динамическое (и не персистентное). Это состояние соответственно живет до конца обработки события и в следующем событии уже не будет видно

Безусловно проблема изменения схемы обработки существует. Мы разработали несколько практик решения этой задачи, одни попроще и дают меньше гарантий, другие - посложнее, но дают строгие гарантии:

  1. Можно просто делать совместимые изменения

  2. Включение фичи по рубильнику - сначала выкатываем код на весь кластер, потом включаем рубильник, и новая функция начинает работать. Собственно, хорошо работает для новых функций :-) Даёт самые слабые гарантии, всякие переключения получаются неатомарнеыми, потому что доставка конфигурации может занять несколько секунд. Зато проще всего реализовать

  3. Включение в отдельной стадии. Например, у нас при обработке события handle_new_driver_found можно выполнить обработчик send_offer_to_driver_v1 и send_offer_to_driver_v2. Тогда мы отдельной стадией вычисляем (используя нашу систему доставки конфигурации), нужно ли для заказа выполнять поведение версии v1 или v2, а потом - вызываем соответствующий обработчик. Поскольку все вычисления происходят локально, то можно гарантировать, что вызовется или поведение версии v1 или поведение версии v2, и не будет случая, когда вызовутся сразу оба или ни одно из них сразу. Но вот если у нас будет, например, 2 события handle_new_driver_found, потому что первый водитель откажется, то для второго события может быть вызвано поведение версии, например, v2, в то время как для первого - версии v1. Поэтому получается промежуточный вариант по гарантиям и по затратам на разработку

  4. Включение на уровне автомата. Можно, например, записать флаг используемой версии поведения в payload события, откуда дописать его в текущее состояние. Поскольку состояние вычисляется всегда одинаково - то для одного заказа поведение получается зафиксировано. Такой подход даёт самые строгие гарантии, но требует больше всего усилий в реализации

Проблема в том, что разработчик функции, содержащей receive loop, не может контролировать то, как её будут использовать. Тогда нужно говорить скорее о том, что в принципе использование receive loop в потоке, который не контролируешь - плохая идея. А уж если по какой-то причине используешь, то лови только те сообщения, которые нужны

Да вроде нет

1> self() ! '4', receive M when is_integer(M)-> M+1 after 100 -> receive N -> {common, N} end end.     
{common,'4'}
2> self() ! 4, receive M when is_integer(M)-> M+1 after 100 -> receive N -> {common, N} end end.  
5

По-моему тут проблема не с gen_server, а с recieve ... msg

Вот в доке даже пример есть, как надо

receive do

message when Mint.HTTP.is_connection_message(conn, message) -> ...

def cache(func): return lambda *a, **k: func.__dict__.setdefault(f'{a}:{k}', f'{a}:{k}' in func.__dict__ or func(*a, **k)) or func.__dict__[f'{a}:{k}']

3 действия


  1. вызываем функцию только если элемента нет в кеше
    f'{a}:{k}' in func.__dict__ or func(*a, **k))
  2. записываем новое значение, если его нет в кеше
    func.__dict__.setdefault(f'{a}:{k}', ...
  3. возвращаем значение из кеша
    or func.__dict__[f'{a}:{k}']

Да. Это просто позволяет сильно разгрузить настоящие тестовые окружения, которые становится проще использовать для задач, где без них сложно обойтись


На настоящих окружениях мы умеем мержить ПРы разных разработчиков и запускать всё вместе, то есть прям острой конкуренции за общие окружения нет. Но понятно, что когда на них начинают выкатывать код все разработчики сразу, то в итоге окружение просто перестает работать, и никто не может понять почему. А если там только те задачи, которым окружения необходимо для тестирования интеграции, то в целом оно работает.

Для тэстовых окружений не встречались с проблемой многих желающих на одно окружение?

Встречались, поэтому и сделали карманное окружение, где такой проблемы нет


Расскажите про дальнейшую судьбу для отключаемой функциональности

Её бывает два вида
1) та, которую включают, и потом больше никогда не выключает. Такую стараемся чистить, конечно.
2) та, которую можно отключить для контролируемой деградации, она так и остается.
Если пытаться не вклинивать переключатели непосредственно в бизнес логику, а переключать стратегии, то особо страшной лапши не получается.


Также интересует подход к обеспечению обратной совместимости

Клиентские протоколы не ломаем почти никогда. Это довольно больно, зато позволяет поддерживать старые клиенты на системах, для которых уже не выпускается обновлений приложений. При этом конечно следим за статистикой обращений к АПИ, если видим, что обращений нет, то оно становится кандидатом на выпиливание. Прямо сейчас например мы поддерживаем одно из старых АПИ, по которому к нам поступает 1 заказ в час)


Как накатывается миграция на prestable и stable

Там нет миграции. Это один и тот же контур по данным. prestable — просто часть stable. Базы общие, потоки данных общие.

разве это повод игнорировать счетчик цикла?

Если предположить, что UB нет, то это означает, что внутри printf есть условно exit, который завершает программу раньше, чем счётчик достигает границы, а значит счётчик и не нужен.

На самом деле идея действительно простая. Но по опыту выходит, что никто о таких вещах просто не задумывается.


Технически же вся сложность в том, чтобы вырубить так, чтобы не сломать для пользователя поведение, потому что не всегда можно просто взять и вырубить. И об этом еще меньше задумываются.

А зачем они вообще нужны в стандартной библиотеке?


Вот чего действительно нет, так это нормальной системы управления зависимостями

Так говорить некорректно. Wine не виртуальная машина, и не дает никакого overhead-а на код приложения. Он выполняется на реальном железе в обычном режиме. Wine просто предоставляет свой стек API-вызовов. Точно так же как и ROS или Windows. То, что API ROS работает быстрее, чем API Wine/Linux, мне, если честно, не очевидно. Более того, вполне возможно, что на разных задачах выигрывать будут разные реализации.

Information

Rating
Does not participate
Location
Москва, Москва и Московская обл., Россия
Works in
Date of birth
Registered
Activity