Меня зовут Илья, я отвечаю за инфраструктуру пользовательских продуктов в Яндекс Go. Мы строим цикл заказа такси — процессы, происходящие под капотом после того, как пользователь нажимает «Заказать». Поиск машины, назначение водителя, изменение адреса, оплата поездки — всё это части цикла. Ещё мы делаем инфраструктуру создания циклов, которая используется в Еде, Лавке, Доставке и других направлениях внутри Яндекса.

Но обо всём по порядку. Давайте расскажу историю того, как мы развивали механизм обработки заказа, адаптировали его под нужды пользователей и старались исключить ошибки, а главное, почему мы используем такую архитектуру сейчас. Если у вас на бэкенде тоже выполняются последовательности зависящих друг от друга действий, и не оставляет желание оптимизировать логику — то тем более добро пожаловать под кат.

Как всё начиналось


В 2011 году мы запустили сервис заказа такси. В первой реализации в бэкенде был endpoint /create-order, внутри которого мы создавали заказ, искали ближайшую машину и отправляли заказ водителю (кстати, если вы не знали о том, как устроен поиск водителя, то обязательно почитайте).



Получается, в момент поиска бэкенд должен сделать несколько действий подряд, каждое из которых может сломаться с небольшой вероятностью. В этом случае нужно, чтобы кто-то повторил действия, продолжив с нужного места. Проще всего попросить это делать пользователя. Но у такого решения есть проблемы: например, пользователь может не отправить запрос повторно, поскольку у него пропал интернет, разрядился телефон или потому что человек просто закрыл приложение. Тогда (в случае ошибки) бэкенд выполнит только часть действий. Скажем, назначит на заказ водителя, но не сообщит об этом. Мы не хотим такого допускать — это может привести к тому, что пассажир и водитель не смогут встретиться и начать поездку. Для решения проблемы мы вынесли последовательность действий, которую нужно совершить для заказа, в отдельную асинхронную задачу на бэкенде — она перезапускается сама в случае ошибки. Теперь клиент получает ответ о том, что заказ принят к обработке, а сами действия по назначению водителя выполняются асинхронно. Даже если пользователь закроет приложение, мы будем уверены, что заказ пройдёт все нужные стадии обработки.



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

Как у нас появилась своя очередь задач


Чтобы заводить асинхронные задачи, поначалу взяли самое простое решение — запускать на серверах крон-таски, выполняющиеся раз в минуту. Внутри крона мы сканировали базу данных на наличие новых заказов и запускали их в обработку. К сожалению, и тут не всё было гладко. Во-первых, заказы, добавленные сразу после запуска крона, могли пролежать без дела целую минуту, а мы не хотим заставлять пользователя ждать. Во-вторых, все решения, которые мы разрабатываем в Яндексе, должны уметь переживать отказы единичных серверов. (Даже более того: все наши сервисы сейчас представлены в двух-трёх зонах доступности и умеют переживать отключение как минимум одной из них.) Решение на кронах плохо горизонтально масштабируется — нужно, чтобы один заказ не обрабатывался одновременно на нескольких серверах. Значит, нужно придумывать распределенные блокировки. Архитектура такого решения становится избыточной, её сложно понимать и поддерживать.

Поэтому следующим нашим шагом стал переход цикла заказа на Сelery. Это широко используемый проект, с помощью которого можно организовать фоновую обработку задач. Celery написана на Python, поддерживает несколько брокеров сообщений и баз данных для хранения результатов задач. В нашей конфигурации мы использовали MongoDB в качестве брокера, потому что у Такси уже был богатый опыт работы с этой базой. Бэкенд ставил задачу в очередь, Celery-worker получал задачу и выполнял нужную последовательность действий — поиск водителя и отправку предложения о заказе ему на устройство. Если какая-то операция приводила к ошибке, задача перезапускалась.

Некоторое время всё работало успешно, но однажды Celery перестала справляться с нагрузкой. Существенным фактором было то, что мы не хотели поддерживать зоопарк баз данных и использовали только MongoDB, а Celery с ней работает не так хорошо, как хотелось бы.

Мы прикинули за и против и решили разрабатывать собственную платформу для запуска асинхронных задач. Начали с небольшого проекта на Python и MongoDB. Решение оказалось удачным и продолжило развиваться.

Если разработчик хочет обрабатывать свои задачи, то ему нужно объявить под это функцию у себя в сервисе, и она будет вызываться в нужные моменты. После этого надо зарегистрировать очередь в системе — и можно отправлять задачи в брокер. Тот проследит, чтобы задача была выполнена как минимум один раз, при этом не допустит ситуации, когда задача выполняется несколькими обработчиками одновременно. Разработчик может контролировать, какие сервисы и с какой частотой могут добавлять задачу в очередь, не копятся ли в очереди необработанные задачи, и устанавливать максимальную пропускную способность «разгребания» задач.

Внутри система состоит из stateless-брокера задач, хранилища данных и обвязки на стороне обработчика. Рядом с кодом обработчика стоит side-car-демон, то есть агент, который с одной стороны взаимодействует с брокером задач, а с другой — с библиотекой интеграции внутри обработчика. Агент следит за состоянием обработчика, собирает метрики и группирует задачи, уменьшая нагрузку на брокер. Такая архитектура позволяет быстро добавлять поддержку новых языков: зависящая от языка часть системы совсем маленькая и пишется опытным разработчиком за пару дней. Например, недавно мы добавили поддержку Go.



Платформа сейчас обрабатывает десятки тысяч задач в секунду. Время от постановки задачи до начала выполнения в среднем составляет всего пару десятков миллисекунд.

Почему нужна сериалиазция событий


Иногда пользователи отменяют заказы. В 2015 году случалось, что на такие отменённые заказы всё же приезжали водители. Проблема возникало достаточно редко, всего несколько раз в неделю, но пользовательский опыт от этого сильно страдал. Оказалось, что возникал race condition между отправкой предложения водителю и отменой заказа пользователем.



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

[
 {
    "timestamp": 1634156405029,
    "key": "create",
  },
  {
    "timestamp": 1634156433765,
    "key": "new-driver-found",
    "driver-id": "xxAAA"
  },
  {
    "timestamp": 1634156433765,
    "key": "cancel"
  }
  // ...
]

У нас появилась точка сериализации, по логу мы всегда можем определить порядок событий — для любых двух из них можно понять, какое произошло раньше. Пусть при создании заказа генерируется событие create, когда мы находим подходящего водителя — new_driver_found, в при отмене — cancel. Тогда при конкурирующих поиске и отмене может возникнуть две последовательности событий.


В первом сценарии нужно отправить водителю отмену, а во втором — вообще не отправлять ему предложение. Получается, что действия, которые необходимо производить при обработке каждого события, зависят от текущего состояния заказа. Так у нас появился конечный автомат заказа и цикл его обработки. К этой архитектуре мы пришли примерно к 2016 году.

class OrderStateMachine:
  def __init__(self):
    self.status = 'pending'
    self.driver_id = None

  def process_event(self, event):
    # ...
    if (self.status == 'pending' and event.key == 'new-driver-found'):
      self.status = 'assigned'
      self.driver_id = event.driver_id
    elif (self.status == 'assigned' and event.key == 'cancel'):
      self.status = 'canceled'
      send_cancel_to_driver(self.driver_id)
    # ...

def process_order(event_log):
  processor = OrderStateMachine()
  for event in event_log:
    processor.process_event(event_log)

К чему мы пришли в 2021 году


С развитием проекта росло и количество действий, необходимых для обработки каждого события. Появлялась поддержка новых способов оплаты, добавлялись тарифы и сценарии использования, например доставка. Запустился Яндекс Плюс — то есть добавилась механика начисления кешбэка и списания баллов. К 2019 году перед нами встала задача управления сложностью нашей конструкции. Мы постарались максимально структурировать схему. Сделали собственный DSL — специализированный под задачу язык описания действий, которые нужно выполнять при обработке заказа. Взяли фреймворк userver и написали рантайм, который выполнял код на нашем DSL.

Действия могут быть такими: сходить в сервис по HTTP или отправить сообщение в очередь. Последнее, если нужно выполнить действие безусловно, работает отлично. Например, можно отправить сообщение «начать поиск машины». Когда надо получить результат от сервиса, удобнее написать HTTP-запрос. В том числе, назначая водителя на заказ, мы отправляем запрос в сервис driver-app-api. Тот отвечает, удалось ли зарезервировать машину или по какой-то причине не удалось. От ответа зависят наши дальнейшие шаги — ждать, пока водитель доедет до пассажира, или начать новый поиск.

На нашем DSL описание этой логики выглядит примерно так:

stages:
 - id: book-driver
   handlers:
     - /drivers-app-module/send-offer
   result: 
       book-result: /handlers/send-offer/response/body/result
 - id: restart-search
   handlers:
     - /lookup-module/start-search
   enabled-if-equal:
    - /shared-state/book-result
    - "success"

Быстро оказалось, что наш подход к конструированию цикла заказа можно переиспользовать и для других проектов, например для обработки платежей или заказов в Лавке. Вот только формат заказов у этих сервисов сильно отличается от Такси, да и сами переходы конечного автомата не имеют с Такси ничего общего. Мы сделали ещё одну итерацию разработки, чтобы полностью отвязать наш процессинг от цикла заказа такси. Команда разработала хранилище событий, разделив его на горячую и холодную части. Так мы можем запоминать всё, что происходит с заказами (это очень помогает поддержке), и при этом не проседать в производительности. Добавили в наш DSL возможность декларативного описания переходов конечного автомата, чтобы разработчики Еды или Лавки могли описать свои циклы обработки. В результате получили инфраструктуру для создания циклов обработки чего угодно, processing as a service.

Продуктовые требования к нашей инфраструктуре получаются очень противоречивыми. С одной стороны, нужно обрабатывать заказы как можно быстрее. Нельзя допускать, чтобы время поиска водителя увеличилось на десятки секунд из-за проблем с каким-нибудь компонентом: пользователям вряд ли такое понравится.



Посмотрим на пример выше. Мы хотим приступить к обработке события new_driver_found как можно раньше — в идеале, сразу после того, как оно произошло. Но мы действуем последовательно — значит, сначала должны обработать событие created и только потом перейти к new_driver_found. А обработка каждого события состоит из десятков действий, каждое из которых может потенциально сломаться — и затормозить весь конечный автомат.


Мы придумали несколько стратегий, которые помогают сочетать быструю обработку событий с сохранением их последовательности.

Чтобы переживать отказы единичных обработчиков, мы развиваем систему стратегий фолбэков. Нам нужно уметь обнаруживать отказы и справляться с ними. Задача определения отказов на первый взгляд выглядит простой: если мы сходили в сервис по HTTP и получили 503, значит, сервис не работает. Правда, бывают вр‎é‎менные флапы, если, скажем, переключился мастер одного из десяти шардов базы данных. Вряд ли можно считать, что сервис при этом сломался: процесс завершится за несколько секунд и пользователи ничего не заметят. Поэтому не стоит отключать сервис сразу, когда на нём появляются ошибки. Для более надёжной детекции мы подсчитываем статистику в некотором окне. Подробнее о том, как всё устроено, можно почитать в этом конспекте.

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

Мы научились определять отказы сервисов, но что можно сделать, чтобы пережить отказ? Мы рассматриваем несколько возможных политик фолбэков:

  1. Отключение микросервиса. Это самое простое, что может быть. Отлично работает для функций, которые не критичны для продолжения заказа. Например, лучше не показать пассажиру имя водителя, чем не дать уехать совсем. Можно не показать плашку «10 лет сервису поиска такси» или новогоднее промо — главное, чтобы отображалась цена поездки. Важно только создать систему метрик и мониторинга, чтобы отключение было вовремя замечено и владельцы как можно скорее вернули микросервис к жизни. Наши мониторинги доставляют алерты команде разработки в течение пары минут, и проблемы, которые как-то задевают пользователей, обычно чинятся в течение получаса.
  2. Использование запасного варианта. Если сервис назначения через буферный подход даст сбой, можно вернуться к более простому жадному алгоритму (вот подробности на Хабре про алгоритмы поиска).
  3. Выполнить действие в сервисе после того, как он починится. Самая распространённая стратегия, в простом сценарии она решается тем, что мы передаём события сервису через очередь задач или сообщений. Всё сильно усложняется, если у следующих шагов обработки есть зависимости по данным от сервиса, выполнение которого мы хотим отложить. Например, статистику по заказам можно пересчитать офлайн с задержкой в 10 минут, если сервис онлайн-подсчёта будет недоступен. В такси вынесение в офлайн тех расчётов, задержка которых не так важна, позволяет уменьшить время выполнения основных функций — ускорить поиск водителя и уменьшить время подачи машины.

Выводы


Когда мы делаем систему, в которой взаимодействуют десятки микросервисов, неизбежно возникают проблемы совместимости интерфейсов. У нас интерпретируемый DSL, поэтому мы узнаём о таких проблемах, только когда они случаются. Чтобы легче справляться с этой проблемой, нам пришлось сделать механизм интеграционных тестов для циклов процессинга, которые пишут наши коллеги из Еды и Лавки. В Такси мы используем кодогенерацию интерфейсов на основе схем OpenAPI (Swagger) — в теории можно настроить валидацию потоков с помощью статического анализа. Но это уже планы на будущее.

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

Комментарии (20)


  1. connected201
    21.12.2021 12:21
    -4

    Вы лучше расскажите как работаете неофициально в некоторых странах СНГ, хочу по заимствовать ваш опыт.


  1. connected201
    21.12.2021 13:10
    -2

    Ну почему же минусуем без пруфов?

    Подсказка: страна Молдова.

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


  1. robomakerr
    21.12.2021 13:27

    Верните, пожалуйста, отключение функции «показать водителю, где я». А то такси постоянно приезжает не туда.


    1. dmitryvolkovtaxi
      21.12.2021 14:09
      +3

      Здравствуйте! Я из Яндекс Go. Отключить можно в настройках. Сейчас напишу в личные сообщения, чтобы помочь разобраться.


      1. DonStron
        22.12.2021 12:03
        +4

        отключение функции «показать водителю, где я». А то такси постоянно приезжает не туда

        Дмитрий, а можете прокомментировать тогда как именно работает этот функционал?
        Подтверждаю, что самое простое - это вызов таксиста в ту точку, которую я выбрал на карте визуально меточкой "я". Но таксисты, почему-то этого не видят, у них идёт привязка вызова к каким-то другим местам, во всяком случае они так говорят.
        Что видят таксисты? Просто адрес ближайший к этой точке или прям точку на карте?
        Либо это такая хитрость таксистов, ведь пока мы занимаемся розыском где-же он "ждёт", уже тикает время ожидания и таксисту выгодно "немножко не там ждать"?
        Можете подтвердить корректную работу выбора места точки вызова? Таксисты в самом вызове потом как видят точку, куда нужно приехать? Просто адресом или точной точкой на карте? Тот факт, что они приезжают потом в другое место, например с обратной стороны дома и ждут там (хотя на карте указывал точку возле подъезда) - это хитрость таксистов или недоработка функционала?


        1. dmitryvolkovtaxi
          22.12.2021 20:45

          Здравствуйте! В целом это зависит от GPS-модуля вашего устройства. Если точка определяется неправильно, можно пальцем передвинуть пин на нужное место — водитель должен будет приехать точно туда. И будет очень здорово, если вы уточните ваш номер телефона и время заказа, в котором что-то пошло не так, в ЛС. На конкретном примере будет легче всё проверить и разобраться :)


          1. DonStron
            22.12.2021 22:18
            +3

            Дело не в GPS и не в точности позиционирования. Я именно пальцем передвигаю пин куда мне нужно, чтобы приехала машина.
            Т.е. четко ставлю точку, где я её жду. Но часто такси приезжает либо в другую точку двора, либо с другой стороны дома (вообще за дом) и т.п.

            Т.е. я вижу, что машина приехала и "ожидает". Но находится не там, куда я ставил пин!

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


            1. vikarti
              23.12.2021 20:00

              Не только у вас такое. И ответ что они видят то — тоже интересен.


    1. qyix7z
      21.12.2021 16:55

      Что она есть, что ее нет. У меня ни разу такси не приезжало туда, где я, только на адрес вызова. Может только мне так «везло»…


  1. TemichNosov
    21.12.2021 14:56
    +3

    А как устроенны релизы? Не бывает проблем с заказами, которые начали обрабатываться прошлой версией стейт машины, а после произошел релиза должны продолжить обрабатываться новой? Или, например, появляется новое состояние в стейт-машине, а после релиза понимаете, что надо откатиться на прошлую версию -- не будет проблем с заказами, которые остановились в этом новом состоянии?


    1. Lol4t0 Автор
      21.12.2021 15:53
      +2

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

      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 события, откуда дописать его в текущее состояние. Поскольку состояние вычисляется всегда одинаково - то для одного заказа поведение получается зафиксировано. Такой подход даёт самые строгие гарантии, но требует больше всего усилий в реализации


  1. Pilgrim54
    22.12.2021 09:48
    -4

    А вы в курсе что таксисты мягко говоря не любят Яндекс? Слабо сказать таксисту "вот я из самого сердца Яндекс-Такси, работаю для вас с поте лица за скромные 20% от поездки" ?


    1. DonStron
      22.12.2021 11:49
      +2

      Таксистов насильно держат в Яндексе? Таксисты - взрослые люди, если что-то не нравится - могут вполне уйти в многочисленные конкурирующие организации. А если они продолжают пользоваться именно Яндексом, значит таксистам выгодно и/или есть какие-то преимущества перевешивающие минусы. Тогда какие могут быть претензии?


      1. Pilgrim54
        22.12.2021 15:04

        Ну-ну, знакомая агрументация, тебя никто не заставляет, найдём других за меньшие деньги. И находят, ведь по оценкам 80-90% заказов проходит через Яндекс. Пассажиры считают, что выставленная цена справедливая, к примеру их должны везти за 70-100 рублей по пробкам. Таксист не может просто отказаться, ведь в результате отказа падает рейтинг, везёт, в уме считая убытки, а потом на пустом месте (ожидание 5 минут, не нашли друг друга, непредусмотренный багаж) взрывается. Из-за низкой ставки, из которой таксист должен отчислять агрегатору и парку, ему остаются копейки, вынужден работать сутками. Качество услуг падает, вплоть до аварий.
        А какие это многочисленные конкурирующие организации? Куда пойти человеку, который потерял работу и не может её быстро найти? В курьеры, промоутеры и т.д. ? Человек которому нужно содержать семью, первым делом идёт в таксисты, особенно если есть своя машина. Выбора практически нет.


        1. DonStron
          23.12.2021 20:52

          Я имел ввиду другие сервисы вызова такси, геттакси, ситимобил, уберы и прочие многочисленные сервисы вызова такси. И не только такси, например можно и курьером быть на авто в Достависте, например. Если же по какой-то причине все конкурирующие сервисы такси сдулись и рынок захватил Яндекс, значит на то были какие-то причины, разве нет? Т.е. потребитель в итоге перебежал туда, где ему лучше, комфортнее, где с ним честнее. На самом деле не знаю, у меня нет статистики. Но, полагаю, что выбор у таксистов есть. Если Яндекс такой злой и ужасный, то почему они не идут туда, где комиссии меньше, а заработок больше? У всех всегда есть выбор. Жаловаться на большие комиссии - это какое-то детство. Если же здесь у них самый большой заработок - значит здесь самое лучшее место. Поэтому они продолжают пользоваться Яндексом?

          Просто будем честны - все хотят зарабатывать как можно больше. Таксисты тоже. И почему-то они видят причину своего "недостаточного" заработка в злом Яндексе, хотя несколько таксистов, у которых я спрашивал сколько зарабатывают, говорили, что "сотка получается". По мне так очень неплохо за низкоквалифицированную работу.

          Насчет 70-100 рублей по пробкам. Собственно вот официальные условия:

          Минимальная стоимость (включено 6 мин и 3 км) — 139 ₽

          Далее по городу — не более 8 ₽/км,не более 10 ₽/мин

          Т.е. в пробке включится таймер времени. Так в чём недовольство таксистов?


          1. Pilgrim54
            23.12.2021 22:08

            Спасибо за корректный ответ, за попытку разобраться.
            Не только работники хотят побольше заработать, но и работодатель тоже. Яндекс-такси и прочие агрегаторы, которые ещё остались (про Сити Мобил я забыл, честно говоря, а остальные типа Гетт - мне кажется у них мало заказов и они работают через тот же Яндекс) периодически "крутят ручки", изменяя условия оплаты и вылезают всякие казусы: то невыгодно везти на дальние расстояния, то на ближние, и в любом случае таксисты не любят соваться в пробки или везти куда-нибудь за город, откуда потом не найдёшь заказ обратно (а значит теряешь время на ожидании или деньги на пустом пробеге). Про моральное состояние таксистов в таком случае я уже писал выше. В Москве получают сотку - хорошо, а в провинции по-разному. Например водитель из Владивостока пишет, что без выполнения условий акции у него выходит 2000 в день, а с выполнением - 5000. А чтобы выполнить условия - сделать определённое количество поездок - надо тратить на поездку в среднем 24 минуты (вместе с подачей, ожиданием, погрузкой, пробками и проч), и поэтому водители просто сбрасывают более длительные поездки. Это при условии быть за рулём 16 часов без перерыва, можете представить. И не бывает чтобы каждый день был хороший, это разве что в Мск и Спб.
            Просто эффективные менеджеры, куда бы они не дотянулись, делают хуже. Яндекс был передовой ИТ-компанией, а сейчас пытается монетизировать всё. Вот и такси, если ставить во главу абсолютную прибыль агрегатора, становится вместо приемлемого всем сервиса набором условностей. Водилам не выгодно (читай - убыточно) совершать поездки по определённым направлениям, и они просто скидывают на пассажиров: https://ngs.ru/text/transport/2021/08/16/70074224/
            Я такое свинство осуждаю, но я и не был в ситуации, когда ты например должен заработать за день несколько тысяч только для того, чтобы заплатить за машину, т.е. рискуешь прокататься весь день и остаться в нуле, а то и в минусе.


            1. DonStron
              24.12.2021 10:29

              Яндекс был передовой ИТ-компанией, а сейчас пытается монетизировать всё. Вот и такси, если ставить во главу абсолютную прибыль агрегатора, становится вместо приемлемого всем сервиса набором условностей. Водилам не выгодно (читай - убыточно) совершать поездки по определённым направлениям

              По вашей ссылке таксисты напрямую говорят, что хотят ездить только туда, где они могут хорошо заработать (и ещё желательно в обе стороны). А всякое невыгодное брать не хотят и поэтому хитрят. И смотрите к чему такое может привести: я заказываю такси и хочу ехать на непопулярное направление и... не могу уехать, никто не соглашается. Разве это правильно?

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

              Таксист вообще не должен видеть куда мне нужно ехать, это не его дело. Его дело брать ближайший заказ. Только так будет работать вся система нормально. Только так клиенты будут пользоваться, зная, что машина будет через 5-10 минут после вызова. Так можно прогнозировать своё время и дела.
              А если таксисты будут выбирать везти меня или нет - так сервис стухнет. Клиенту не нужен сервис, где он не может уехать.

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

              Полагаю в Яндексе тоже могут ошибаться в какой-то аналитике и выводах, но аналитику точно делают и тарифы устанавливаются не случайно. И это делается для повышения заработка агрегатора и водителей, и для удобства клиентов. Возможно не всегда правильно и корректно и часто не выгодно для какой-то стороны. Думаете клиенту интересно видеть повышение тарифа в 2 раза из-за "высокого спроса"? Думаете клиенту уезжая из гостей и задержавшись немного с прощанием приятно видеть, как резко повысилась стоимость в полночь, хотя вот ещё 5 минут назад цена другая была?

              Агрегатор берёт процент, значит таксистам достается то же процентное соотношение. Может быть из-за каких-то заказов в кокретные дни у таксиста и не получается выполнить "акцию", но она не сделана, чтобы её выполнять каждый день. Я абсолютно уверен, что если просто брать все заказы подряд, то в среднем за месяц всё будет нормально. А если выбирать заказы, отказываться и прятаться по кустам - то вот потом и получаются жалобы, что мало заработать получается.


        1. donvictorio
          23.12.2021 22:08

          Куда пойти человеку, который потерял работу и не может её быстро найти? В курьеры, промоутеры и т.д. ? Человек которому нужно содержать семью, первым делом идёт в таксисты, особенно если есть своя машина.

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


  1. pomponchik
    24.12.2021 01:56

    А DSL поддерживает динамические стейты?


    1. Lol4t0 Автор
      24.12.2021 10:56

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

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

      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'

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