Введение в масштабирование распределенных приложений Python

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

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

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

Это руководство с первого взгляда:

  • Что такое масштабирование?
  • Масштабирование процессора в Python
  • Демон-процессы в Python
  • Циклы событий и Asyncio в Python
  • Следующие шаги для вашего обучения

Что такое масштабирование?

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

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

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

Прежде чем мы рассмотрим методы построения масштабируемых систем на Python, давайте пройдемся по фундаментальным свойствам распределенных систем.

Однопоточное приложение

Это тип системы, который не подразумевает никакого распределения. Это самый простой вид приложений. Однако они ограничены возможностями использования одного процессора.

Многопоточное приложение

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

Сетевое распределенное приложение

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

Многопоточность

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

Масштабирование процессора в Python

Использование нескольких процессоров — один из лучших вариантов масштабирования в Python. Для этого мы должны использовать параллелизм и параллельность, что может быть непросто реализовать должным образом. Python предлагает два варианта распределения рабочей нагрузки между несколькими локальными процессорами: потоки и процессы.

Потоки в Python

Потоки — это хороший способ одновременного выполнения функции. Если доступно несколько CPU, потоки могут быть запланированы на несколько вычислительных блоков. Планирование определяется операционной системой.

По умолчанию существует только один поток, главный. Это поток, в котором выполняется ваше приложение Python. Чтобы запустить другой поток, Python предлагает модуль потоков.

import threading

def print_something(something):
    print(something)

t = threading.Thread(target=print_something, args=("hello",))
t.start()
print("thread started")
t.join()
Вход в полноэкранный режим Выход из полноэкранного режима

После запуска главный поток ожидает завершения второго потока, вызывая его метод join. Но если вы не объедините все потоки, то возможно, что основной поток завершится раньше, чем к нему смогут присоединиться другие потоки, и ваша программа окажется заблокированной.

Чтобы предотвратить это, вы можете настроить потоки как демоны. Когда поток является демоном, он похож на фоновый поток и будет завершен, как только завершится основной поток. Обратите внимание, что нам не нужно использовать метод join.

import threading

def print_something(something):
    print(something)

t = threading.Thread(target=print_something, args=("hello",))
t.daemon = True
t.start()
print("thread started")

-->
hello
thread started
Вход в полноэкранный режим Выход из полноэкранного режима

Процессы в Python

Многопоточность не идеальна для масштабируемости из-за глобальной блокировки интерпретатора (GIL). В качестве альтернативы мы можем использовать процессы вместо потоков. Пакет multiprocessing является хорошим, высокоуровневым вариантом для процессов. Он предоставляет интерфейс, который запускает новые процессы. Каждый процесс — это новый, независимый экземпляр, поэтому каждый процесс имеет свое собственное независимое глобальное состояние.

import random
import multiprocessing

def compute(results):
    results.append(sum(
        [random.randint(1, 100) for i in range(1000000)]))

if __name__ == "__main__":
    with multiprocessing.Manager() as manager:
        results = manager.list()
        workers = [multiprocessing.Process(target=compute, args=(results,))
                   for x in range(8)]
        for worker in workers:
            worker.start()
        for worker in workers:
            worker.join()
        print("Results: %s" % results)

Вход в полноэкранный режим Выход из полноэкранного режима

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

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

import multiprocessing
import random

def compute(n):
    return sum(
        [random.randint(1, 100) for i in range(1000000)])

if __name__ == "__main__":
    # Start 8 workers
    pool = multiprocessing.Pool(processes=8)
    print("Results: %s" % pool.map(compute, range(8)))

-->
Results: [50482130, 50512647, 50450070, 50505847, 50513886, 50539079, 50489976, 50518142]
Вход в полноэкранный режим Выход из полноэкранного режима

Демон-процессы в Python

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

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

Ниже мы создадим класс PrinterService для реализации метода cotyledon.Service: run. Он содержит главный цикл и terminate. Эта библиотека выполняет большую часть своей работы за кулисами, например, вызовы os.fork и установка режимов для демонов.

Cotyledon использует несколько потоков внутри. Поэтому объект threading.Event используется для синхронизации наших методов run и terminate.

import threading
import time
import cotyledon

class PrinterService(cotyledon.Service):
    name = "printer"

    def __init__(self, worker_id):
        super(PrinterService, self).__init__(worker_id)
        self._shutdown = threading.Event()

    def run(self):
        while not self._shutdown.is_set():
            print("Doing stuff")
            time.sleep(1)

    def terminate(self):
        self._shutdown.set()

# Create a manager
manager = cotyledon.ServiceManager()
# Add 2 PrinterService to run
manager.add(PrinterService, 2)
# Run all of that
manager.run()
Вход в полноэкранный режим Выход из полноэкранного режима

Cotyledon запускает главный процесс, который отвечает за обработку всех своих дочерних процессов. Затем он запускает два экземпляра PrinterService и дает новым процессам имена, чтобы их было легко отследить. При использовании Cotyledon, если один из процессов завершается, он автоматически перезапускается.

Примечание: Cotyledon также предлагает функции для перезагрузки конфигурации программы или динамического изменения количества рабочих для класса.

Циклы событий и Asyncio в Python

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

В Python очень простой цикл событий может выглядеть следующим образом:

while True: message = get_message() if message == quit: break
  process_message(message)
Войти в полноэкранный режим Выход из полноэкранного режима

Asyncio — это новый, самый современный цикл событий, появившийся в Python 3. Asyncio расшифровывается как асинхронный ввод-вывод. Это парадигма программирования, которая позволяет достичь высокого параллелизма с помощью одного потока или цикла событий. Это хорошая альтернатива многопоточности по следующим причинам:

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

Asyncio основан на концепции циклов событий. Когда asyncio создает цикл событий, приложение регистрирует функции для обратного вызова при наступлении определенного события. Это тип функции, называемый coroutine. Она работает аналогично генератору, поскольку возвращает управление вызывающей стороне с помощью оператора yield.

import asyncio

async def hello_world():
    print("hello world!")
    return 42

hello_world_coroutine = hello_world()
print(hello_world_coroutine)

event_loop = asyncio.get_event_loop()
try:
    print("entering event loop")
    result = event_loop.run_until_complete(hello_world_coroutine)
    print(result)
finally:
    event_loop.close()

-->
<coroutine object hello_world at 0x7f36a2c4da98>
entering event loop
hello world!
42
Вход в полноэкранный режим Выход из полноэкранного режима

Выше, корутина hello_world определена как функция, но ключевое слово, используемое для начала ее определения — async def. Эта корутина печатает сообщение и возвращает результат. Цикл событий запускает корутину и завершается, когда корутина возвращается.

Следующие шаги для вашего обучения

Поздравляем вас с тем, что вы дошли до конца! Теперь у вас должно быть хорошее представление об инструментах, которые мы можем использовать для масштабирования в Python. Мы можем использовать эти инструменты для эффективного построения распределенных систем. Но нам еще многое предстоит узнать. Далее вы захотите узнать о:

  • Совместно выполнять coroutine.
  • Распределение на основе очередей
  • Управление блокировками
  • Развертывание на PaaS

Чтобы начать работу с этими концепциями, ознакомьтесь с полным курсом Educative «Руководство хакера по масштабированию Python». Вы узнаете обо всем, начиная от параллелизма и заканчивая распределением на основе очередей, управлением блокировками и членством в группах. В конце вы получите практические навыки создания REST API на Python и развертывания приложения на PaaS.

К концу курса вы будете более продуктивно работать с Python и сможете писать распределенные приложения.

Счастливого обучения!

Продолжить чтение о Python на Образовательном

  • Python Concurrency: Осмысление asyncio
  • Обновления Python 3.9: топографическая сортировка и работа со строками
  • Использование Python Script Automation для получения изображений из NASA

Начните обсуждение

Что вы хотите узнать о Python в следующий раз? Была ли эта статья полезной? Сообщите нам об этом в комментариях ниже!

Оцените статью
devanswers.ru
Добавить комментарий