Spark Streaming textFileStream не поддерживает подстановочные знаки

Я установил простой тест для потоковой передачи текстовых файлов с S3 и заставил его работать, когда я попробовал что-то вроде

val input = ssc.textFileStream("s3n://mybucket/2015/04/03/")

и в bucket я бы запустил файлы журналов, и все будет нормально работать.

Но если они были вложенной папкой, они не нашли бы файлы, которые были помещены во вложенную папку (и да, я знаю, что hdfs фактически не использует структуру папок)

val input = ssc.textFileStream("s3n://mybucket/2015/04/")

Итак, я попытался просто сделать подстановочные знаки, как это было раньше, при использовании стандартного искрового приложения

val input = ssc.textFileStream("s3n://mybucket/2015/04/*")

Но когда я пытаюсь это сделать, он выдает ошибку

java.io.FileNotFoundException: File s3n://mybucket/2015/04/* does not exist.
at org.apache.hadoop.fs.s3native.NativeS3FileSystem.listStatus(NativeS3FileSystem.java:506)
at org.apache.hadoop.fs.FileSystem.listStatus(FileSystem.java:1483)
at org.apache.hadoop.fs.FileSystem.listStatus(FileSystem.java:1523)
at org.apache.spark.streaming.dstream.FileInputDStream.findNewFiles(FileInputDStream.scala:176)
at org.apache.spark.streaming.dstream.FileInputDStream.compute(FileInputDStream.scala:134)
at org.apache.spark.streaming.dstream.DStream$$anonfun$getOrCompute$1$$anonfun$1.apply(DStream.scala:300)
at org.apache.spark.streaming.dstream.DStream$$anonfun$getOrCompute$1$$anonfun$1.apply(DStream.scala:300)
at scala.util.DynamicVariable.withValue(DynamicVariable.scala:57)
at org.apache.spark.streaming.dstream.DStream$$anonfun$getOrCompute$1.apply(DStream.scala:299)
at org.apache.spark.streaming.dstream.DStream$$anonfun$getOrCompute$1.apply(DStream.scala:287)
at scala.Option.orElse(Option.scala:257)
.....

Я знаю, что вы можете использовать подстановочные знаки при чтении fileInput для стандартных искровых приложений, но кажется, что при выполнении потокового ввода он не делает этого и не выполняет автоматическую обработку файлов в подпапках. Есть ли что-то, что я здесь отсутствует?

В конечном итоге мне нужна работа с потоками, работающая 24/7, которая будет контролировать ведро S3 с логами, помещенными в него по дате

Так что-то вроде

s3n://mybucket/<YEAR>/<MONTH>/<DAY>/<LogfileName>

Есть ли способ передать его самой большой папке и автоматически читать файлы, которые отображаются в любой папке (потому что, очевидно, дата будет увеличиваться каждый день)?

ИЗМЕНИТЬ

Итак, перейдя в документацию на http://spark.apache.org/docs/latest/streaming-programming-guide.html#basic-sources, говорится, что вложенные каталоги не поддерживаются.

Может кто-нибудь пролить свет на то, почему это так?

Кроме того, поскольку мои файлы будут вложенными в зависимости от их даты, что было бы хорошим способом решить эту проблему в моем потоковом приложении? Это немного сложно, так как журналы занимают несколько минут, чтобы записать их на S3, поэтому последний файл, написанный за день, может быть записан в папке предыдущего дня, даже если мы находимся несколько минут в новый день.

Ответ 1

Некоторое "уродливое, но рабочее решение" может быть создано путем расширения FileInputDStream. Запись sc.textFileStream(d) эквивалентна

new FileInputDStream[LongWritable, Text, TextInputFormat](streamingContext, d).map(_._2.toString)

Вы можете создать CustomFileInputDStream, который расширит FileInputDStream. Пользовательский класс скопирует метод вычисления из класса FileInputDStream и настроит метод findNewFiles в соответствии с вашими потребностями.

изменение метода findNewFiles из:

 private def findNewFiles(currentTime: Long): Array[String] = {
    try {
      lastNewFileFindingTime = clock.getTimeMillis()

  // Calculate ignore threshold
  val modTimeIgnoreThreshold = math.max(
    initialModTimeIgnoreThreshold,   // initial threshold based on newFilesOnly setting
    currentTime - durationToRemember.milliseconds  // trailing end of the remember window
  )
  logDebug(s"Getting new files for time $currentTime, " +
    s"ignoring files older than $modTimeIgnoreThreshold")
  val filter = new PathFilter {
    def accept(path: Path): Boolean = isNewFile(path, currentTime, modTimeIgnoreThreshold)
  }
  val newFiles = fs.listStatus(directoryPath, filter).map(_.getPath.toString)
  val timeTaken = clock.getTimeMillis() - lastNewFileFindingTime
  logInfo("Finding new files took " + timeTaken + " ms")
  logDebug("# cached file times = " + fileToModTime.size)
  if (timeTaken > slideDuration.milliseconds) {
    logWarning(
      "Time taken to find new files exceeds the batch size. " +
        "Consider increasing the batch size or reducing the number of " +
        "files in the monitored directory."
    )
  }
  newFiles
} catch {
  case e: Exception =>
    logWarning("Error finding new files", e)
    reset()
    Array.empty
}

}

в

  private def findNewFiles(currentTime: Long): Array[String] = {
    try {
      lastNewFileFindingTime = clock.getTimeMillis()

      // Calculate ignore threshold
      val modTimeIgnoreThreshold = math.max(
        initialModTimeIgnoreThreshold,   // initial threshold based on newFilesOnly setting
        currentTime - durationToRemember.milliseconds  // trailing end of the remember window
      )
      logDebug(s"Getting new files for time $currentTime, " +
        s"ignoring files older than $modTimeIgnoreThreshold")
      val filter = new PathFilter {
        def accept(path: Path): Boolean = isNewFile(path, currentTime, modTimeIgnoreThreshold)
      }
      val directories = fs.listStatus(directoryPath).filter(_.isDirectory)
      val newFiles = ArrayBuffer[FileStatus]()

      directories.foreach(directory => newFiles.append(fs.listStatus(directory.getPath, filter) : _*))

      val timeTaken = clock.getTimeMillis() - lastNewFileFindingTime
      logInfo("Finding new files took " + timeTaken + " ms")
      logDebug("# cached file times = " + fileToModTime.size)
      if (timeTaken > slideDuration.milliseconds) {
        logWarning(
          "Time taken to find new files exceeds the batch size. " +
            "Consider increasing the batch size or reducing the number of " +
            "files in the monitored directory."
        )
      }
      newFiles.map(_.getPath.toString).toArray
    } catch {
      case e: Exception =>
        logWarning("Error finding new files", e)
        reset()
        Array.empty
    }
  }

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

Я создал CustomFileInputDStream, как я упоминал, и активировал его, позвонив:

new CustomFileInputDStream[LongWritable, Text, TextInputFormat](streamingContext, d).map(_._2.toString)

Кажется, мы себя ожидаем.

Когда я пишу такое решение, я должен добавить некоторые моменты для рассмотрения:

  • Вы нарушаете инкапсуляцию Spark и создаете пользовательский класс, который вам нужно будет поддерживать только в качестве прохода времени.

  • Я считаю, что решение, подобное этому, - последнее средство. Если ваш вариант использования может быть реализован по-разному, обычно лучше избегать такого решения.

  • Если у вас будет много "подкаталогов" на S3 и будет проверять каждый из них, это будет стоить вам.

  • Будет очень интересно понять, не поддерживает ли Databricks вложенные файлы только из-за возможного штрафа за производительность или нет, возможно, есть более глубокая причина, о которой я не думал.

Ответ 2

у нас была та же проблема. мы присоединились к именам подпапок с запятой.

List<String> paths = new ArrayList<>();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");

try {        
    Date start = sdf.parse("2015/02/01");
    Date end = sdf.parse("2015/04/01");

    Calendar calendar = Calendar.getInstance();
    calendar.setTime(start);        

    while (calendar.getTime().before(end)) {
        paths.add("s3n://mybucket/" + sdf.format(calendar.getTime()));
        calendar.add(Calendar.DATE, 1);
    }                
} catch (ParseException e) {
    e.printStackTrace();
}

String joinedPaths = StringUtils.join(",", paths.toArray(new String[paths.size()]));
val input = ssc.textFileStream(joinedPaths);

Я надеюсь, что таким образом ваша проблема будет решена.