Разница между map и mapAsync

может ли кто-нибудь объяснить мне разницу между map и mapAsync w.r.t AKKA поток? в документации говорят, что

преобразования потока и побочные эффекты включая внешнее non-stream услуги могут быть выполнены с mapAsync или mapAsyncUnordered

Почему мы не можем просто нас карте? Я предполагаю, что поток, источник, потоп все будет Монадическим по своей природе и, следовательно, карта должна работать нормально w.r.T задержка в природе из этих ?

1 ответов


подпись

разница выделен в подписи: Flow.map принимает функцию, которая возвращает тип T пока Flow.mapAsync принимает функцию, которая возвращает тип Future[T].

Практический Пример

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

type UserID = String
type FullName = String

val databaseLookup : UserID => FullName = ???  //implementation unimportant

дали Source of UserID значения, мы могли бы просто использовать Flow.map в потоке, чтобы запросить базу данных и распечатать полные имена на консоли:

val userIDSource : Source[UserID, _] = ???

val stream = 
  userIDSource.via(Flow[UserID].map(databaseLookup))
              .to(Sink.foreach[FullName](println))
              .run()

одним из ограничений этого подхода является то, что этот поток будет только 1 запрос к БД одновременно. Последовательный запрос будет "узким местом" и, вероятно, предотвратит максимальную пропускную способность в нашем потоке.

мы могли бы попытаться улучшить производительность с помощью параллельных запросов, используя Future:

def concurrentDBLookup(userID : UserID) : Future[FullName] = 
  Future { databaseLookup(userID) }

val concurrentStream = 
  userIDSource.via(Flow[UserID].map(concurrentDBLookup))
              .to(Sink.foreach[Future[FullName]](_ foreach println))
              .run()

проблема с этим упрощенное добавление заключается в том, что мы эффективно устранили противодавление.

раковина просто тянет в будущем и добавляет foreach println, что относительно быстро по сравнению с запросами базы данных. Поток будет непрерывно распространять спрос на источник и порождать больше фьючерсов внутри Flow.map. Таким образом, нет ограничений на количество databaseLookup работает по совместительству. Неограниченный параллельный запрос может в конечном итоге перегрузить база данных.

Flow.mapAsync на помощь; мы можем иметь параллельный доступ к БД, в то же время ограничивая количество одновременных поисков:

val maxLookupCount = 10

val maxLookupConcurrentStream = 
  userIDSource.via(Flow[UserID].mapAsync(maxLookupCount)(concurrentDBLookup))
              .to(Sink.foreach[FullName](println))
              .run()

Также обратите внимание, что Sink.foreach стало проще, он больше не принимает Future[FullName], а просто FullName вместо.

Неупорядоченная Асинхронная Карта

если поддержание последовательного порядка UserIDs в FullNames не требуется, вы можете использовать Flow.mapAsyncUnordered. Например: вам как раз нужно чтобы напечатать все имена на консоли, но не заботился о порядке, они были напечатаны.