Как вызвать PySpark в режиме отладки?

У меня есть IntelliJ IDEA с Apache Spark 1.4.

Я хочу иметь возможность добавлять точки отладки в мои скрипты Spark Python, чтобы я мог легко их отлаживать.

В настоящее время я запускаю этот бит Python для инициализации искрового процесса

proc = subprocess.Popen([SPARK_SUBMIT_PATH, scriptFile, inputFile], shell=SHELL_OUTPUT, stdout=subprocess.PIPE)

if VERBOSE:
    print proc.stdout.read()
    print proc.stderr.read()

Когда spark-submit в конечном итоге вызывает myFirstSparkScript.py, режим отладки не задействован и выполняется как обычно. К сожалению, редактирование исходного кода Apache Spark и запуск персонализированной копии не являются приемлемым решением.

Кто-нибудь знает, возможно ли исправить-отправить вызов Apache Spark script в режиме отладки? Если да, то как?

Ответ 1

Насколько я понимаю ваши намерения, то, что вы хотите, напрямую невозможно, учитывая архитектуру Spark. Даже без вызова subprocess единственная часть вашей программы, которая доступна непосредственно в драйвере, - это SparkContext. От всего остального вы эффективно изолированы различными уровнями связи, включая хотя бы один (в локальном режиме) экземпляр JVM. Чтобы проиллюстрировать это, давайте использовать диаграмму из документации PySpark Internals.

enter image description here

В левом поле находится часть, которая доступна локально и может использоваться для подключения отладчика. Поскольку он больше всего ограничен вызовами JVM, на самом деле там нет ничего, что могло бы вас заинтересовать, если только вы на самом деле не модифицируете сам PySpark.

То, что справа, происходит удаленно, и в зависимости от того, какой менеджер кластера вы используете, с точки зрения пользователя, это почти черный ящик. Более того, во многих случаях код Python справа не делает ничего, кроме вызова JVM API.

Это была плохая часть. Хорошая часть заключается в том, что в большинстве случаев не требуется удаленной отладки. Исключая доступ к объектам, таким как TaskContext, которые могут быть легко смоделированы, каждая часть вашего кода должна легко запускаться/тестироваться локально без использования экземпляра Spark.

Функции, которые вы передаете действиям/преобразованиям, принимают стандартные и предсказуемые объекты Python, а также должны возвращать стандартные объекты Python. Что также важно, это должны быть побочные эффекты бесплатно

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

Другие варианты

Тем не менее, у вас не совсем нет вариантов здесь.

Локальный режим

(пассивно присоединить отладчик к работающему интерпретатору)

Как простой GDB, так и отладчик PySpark могут быть подключены к работающему процессу. Это можно сделать только после запуска демона PySpark и/или рабочих процессов. В локальном режиме вы можете принудительно выполнить его, выполнив фиктивное действие, например:

sc.parallelize([], n).count()

где n - это количество "ядер", доступных в локальном режиме (local[n]). Пример процедуры, шаг за шагом, в Unix-подобных системах:

  • Запустите оболочку PySpark:

    $SPARK_HOME/bin/pyspark 
    
  • Используйте pgrep, чтобы проверить, не запущен ли демон-процесс:

    ➜  spark-2.1.0-bin-hadoop2.7$ pgrep -f pyspark.daemon
    ➜  spark-2.1.0-bin-hadoop2.7$
    
  • То же самое можно определить в PyCharm следующим образом:

    alt + shift + a и выберите "Присоединить к локальному процессу":

    enter image description here

    или Выполнить → Присоединить к локальному процессу.

    На данный момент вы должны увидеть только оболочку PySpark (и, возможно, некоторые не связанные процессы).

    enter image description here

  • Выполнить фиктивное действие:

    sc.parallelize([], 1).count()

  • Теперь вы должны увидеть daemon и worker (здесь только один):

    ➜  spark-2.1.0-bin-hadoop2.7$ pgrep -f pyspark.daemon
    13990
    14046
    ➜  spark-2.1.0-bin-hadoop2.7$
    

    и

    enter image description here

    Процесс с более низким pid является демоном, а процесс с более высоким pid - (возможно) эфемерным рабочим.

  • На этом этапе вы можете подключить отладчик к интересующему процессу:

    • В PyCharm выбираем процесс подключения.
    • С простой GDB по телефону:

      gdb python <pid of running process>
      

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

Распределенный режим

(Использование активного компонента, который подключается к серверу отладчика)

С PyCharm

PyCharm предоставляет сервер отладки Python, который можно использовать с заданиями PySpark.

Прежде всего вы должны добавить конфигурацию для удаленного отладчика:

  • alt + shift + a и выберите "Редактировать конфигурации" или "Выполнить" → "Редактировать конфигурации".
  • Нажмите Добавить новую конфигурацию (зеленый плюс) и выберите Python Remote Debug.
  • Сконфигурируйте хост и порт в соответствии с вашей собственной конфигурацией (убедитесь, что порт и доступ с удаленной машины)

    enter image description here

  • Запустите сервер отладки:

    shift + F9

    Вы должны увидеть консоль отладчика:

    enter image description here

  • Убедитесь, что pyddev доступен на рабочих узлах, либо установив его, либо распространив файл egg.

  • pydevd использует активный компонент, который должен быть включен в ваш код:

    import pydevd
    pydevd.settrace(<host name>, port=<port number>)
    

    Самое сложное - найти правильное место для его включения, и если вы не отлаживаете пакетные операции (например, функции, переданные в mapPartitions), может потребоваться исправление самого источника PySpark, например, pyspark.daemon.worker или RDD методов, таких как RDD.mapPartitions. Допустим, мы заинтересованы в отладке рабочего поведения. Возможный патч может выглядеть так:

    diff --git a/python/pyspark/daemon.py b/python/pyspark/daemon.py
    index 7f06d4288c..6cff353795 100644
    --- a/python/pyspark/daemon.py
    +++ b/python/pyspark/daemon.py
    @@ -44,6 +44,9 @@ def worker(sock):
         """
         Called by a worker process after the fork().
         """
    +    import pydevd
    +    pydevd.settrace('foobar', port=9999, stdoutToServer=True, stderrToServer=True)
    +
         signal.signal(SIGHUP, SIG_DFL)
         signal.signal(SIGCHLD, SIG_DFL)
         signal.signal(SIGTERM, SIG_DFL)
    

    Если вы решили применить исправление к источнику Spark, обязательно используйте исправленный источник, а не упакованную версию, расположенную в $SPARK_HOME/python/lib.

  • Выполнить код PySpark. Вернитесь к консоли отладчика и получайте удовольствие:

    enter image description here

Другие инструменты

Существует ряд инструментов, в том числе python-manhole или pyrasite, которые можно использовать с некоторыми усилиями для работы с PySpark.

Примечание:

Конечно, вы можете использовать "удаленные" (активные) методы в локальном режиме и, в некоторой степени, "локальные" методы в распределенном режиме (вы можете подключиться к рабочему узлу и выполнить те же действия, что и в локальном режиме).