Философия Dataflow заключается в том, что PTransform
является основной единицей абстракции и компоновки, т.е. любая автономная задача обработки данных должна быть инкапсулирована как PTransform
. Сюда входит задача подключения к сторонней системе хранения: получение данных откуда-то или их экспорт куда-то.
Возьмем, к примеру, Google Cloud Datastore. Во фрагменте кода:
PCollection<Entity> entities =
p.apply(DatastoreIO.readFrom(dataset, query));
...
p.apply(some processing)
.apply(DatastoreIO.writeTo(dataset));
тип возвращаемого значения DatastoreIO.readFrom(dataset, query)
является подклассом PTransform<PBegin, PCollection<Entity>>
, а тип DatastoreIO.writeTo(dataset)
является подклассом PTransform<PCollection<Entity>, PDone>
.
Верно, что эти функции реализуются под капотом с использованием классов Source
и Sink
, но для пользователя, который просто хочет прочитать или записать что-то в Datastore, это деталь реализации, которая обычно не имеет значения (однако см. обратите внимание в конце этого ответа о раскрытии класса Source
или Sink
). Любой коннектор или любая другая задача обработки данных - это PTransform
.
Примечание. В настоящее время коннекторы, которые читают откуда-то, обычно PTransform<PBegin, PCollection<T>>
, а коннекторы, которые пишут куда-то, как правило, PTransform<PCollection<T>, PDone>
, но мы рассматриваем варианты, которые упростят использование коннекторов более гибкими способами (например, чтение из PCollection
имен файлов).
Однако, конечно, эта деталь важна для тех, кто хочет реализовать новый соединитель. В частности, вы можете спросить:
В: Зачем мне вообще нужны классы Source
и Sink
, если я могу просто реализовать свой коннектор как PTransform?
О: Если вы можете реализовать свой коннектор, просто используя встроенные преобразования (например, ParDo
, GroupByKey
и т. д.), это вполне допустимый способ разработки коннектора. Однако классы Source
и Sink
предоставить некоторые низкоуровневые возможности, которые в случае необходимости были бы громоздкими или невозможными для самостоятельной разработки.
Например, BoundedSource
и UnboundedSource
предоставляют хуки для управления тем, как происходит распараллеливание (как начальная, так и динамическая перебалансировка работы - BoundedSource.splitIntoBundles
, BoundedReader.splitAtFraction
), в то время как эти хуки в настоящее время не доступны для произвольных DoFn
s.
Вы можете технически реализовать синтаксический анализатор для формата файла, написав DoFn<FilePath, SomeRecord>
, который принимает имя файла в качестве входных, читает файл и выдает SomeRecord
, но этот DoFn
не сможет динамически распараллелить чтение частей файла на нескольких рабочих, если файл оказался очень большим во время выполнения. С другой стороны, FileBasedSource
имеет встроенную возможность, а также обработку шаблонов файлов glob и тому подобного.
Точно так же вы можете попробовать реализовать соединитель для потоковой системы, реализовав DoFn
, который принимает фиктивный элемент в качестве входных данных, устанавливает соединение и передает все элементы в ProcessingContext.output()
, но DoFn
s в настоящее время не поддерживают запись неограниченного количества выходных данных из одного пакета. , а также они явно не поддерживают механизм контрольных точек и дедупликации, необходимый для обеспечения строгой согласованности, которую Dataflow предоставляет потоковым конвейерам. UnboundedSource
, напротив, все это поддерживает.
Sink
(точнее, Write.to()
PTransform
) также интересен: это просто составное преобразование, которое вы могли бы написать самостоятельно, если захотите (т.е. оно не имеет жестко запрограммированной поддержки в средстве выполнения или бэкэнде потока данных), но оно было разработано с помощью рассмотрение типичных проблем распределенной отказоустойчивости, которые возникают при параллельной записи данных в систему хранения, и предоставляет ловушки, которые заставляют вас помнить об этих проблемах: например, потому что пакеты данных записываются параллельно , и некоторые пакеты могут быть повторены или дублированы для обеспечения отказоустойчивости, есть ловушка для «фиксации» только результатов успешно завершенных пакетов (WriteOperation.finalize
).
Подводя итог: использование Source
или Sink
API для разработки коннектора помогает структурировать код таким образом, чтобы он хорошо работал в настройках распределенной обработки, а исходные API-интерфейсы предоставляют вам доступ к расширенным возможностям платформы. . Но если ваш коннектор очень простой и не требует ни того, ни другого, то вы можете просто собрать коннектор из других встроенных преобразований.
В: Предположим, я решил использовать Source
и Sink
. Тогда как мне упаковать коннектор в виде библиотеки: нужно ли просто предоставить класс Source
или Sink
или обернуть его в PTransform
?
A: Коннектор в конечном итоге должен быть упакован как PTransform
, чтобы пользователь мог просто p.apply()
в своем конвейере. Однако внутри ваше преобразование может использовать классы Source
и Sink
.
Распространенным шаблоном является предоставление классов Source
и Sink
, используя шаблон Fluent Builder и позволяя пользователю обернуть их в Read.from()
или Write.to()
преобразование, но это не является строгим требованием.
person
jkff
schedule
11.01.2016