Книга: Простой Python. Современный стиль программирования. 2-е изд.
Назад: Глава 16. Данные в коробке: надежные хранилища
Дальше: Глава 18. Распутываем Всемирную паутину

Глава 17. Данные в пространстве: сети

Время — то, что удерживает все от того, чтобы случаться сразу. Пространство — то, что удерживает все от того, чтобы случаться со мной.

Цитаты о времени

Из главы 15 вы узнали о конкурентности: способе выполнять больше одного дела за раз. Теперь мы попробуем решать задачи более чем в одном месте. В этом нам помогут распределенныевычисления и сети. Существует несколько хороших причин бросить вызов пространству и времени.

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

• Устойчивость. Один в поле не воин, поэтому стоит дублировать задачи для того, чтобы обойти проблемы, связанные с отказом аппаратного и программного обеспечения.

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

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

В этой главе мы сначала рассмотрим примитивные концепции работы с сетями, а затем поднимемся на самый высокий уровень. Начнем с модели TCP/IP и сокетов.

TCP/IP

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

Самый нижний слой управляет такими аспектами, как электрические сигналы. Каждый более высокий слой базируется на нижних. Примерно в середине находится IP (Internet Protocol — интернет-протокол), на котором определяется, как адресуются локации сети и перемещаются пакеты (фрагменты) данных. На слое, расположенном выше IP, два протокола описывают, как перемещать байты между локациями.

UDP (User Datagram Protocol протокол датаграмм пользователя). Служит для обмена небольшим объемом данных. Датаграмма — это небольшое сообщение, которое отправляется целиком; оно похоже на открытку.

TCP (Transmission Control Protocol протокол управления передачей). Используется для более длинных соединений. С его помощью отправляются потоки байтов, он гарантирует, что они придут по порядку и без дупликаций.

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

Вот шутка про UDP. Дошло?

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

Ты хочешь услышать шутку про TCP?

Да, я хочу услышать шутку про TCP.

О'кей, я расскажу тебе шутку про TCP.

О'кей, я выслушаю шутку про TCP.

О'кей, теперь я отправлю тебе шутку про TCP.

О'кей, теперь я приму шутку про TCP.

... (и т.д.)

Ваша локальная машина всегда будет иметь IP-адрес 127.0.0.1 и имя localhost. Вы можете встретить название для этого процесса — интерфейс обратной петли. При подключении к Интернету у вашей машины также появится публичный IP. Если же вы используете домашний компьютер, то он будет скрыт за оборудованием, таким как кабельный модем или роутер. Вы можете запускать интернет-протоколы даже между процессами на одной машине.

Большая часть Интернета, с которой мы будем взаимодействовать, — Всемирная паутина, серверы баз данных и т.д. — основана на протоколе TCP, функцио­нирующем поверх протокола IP, сокращенно — TCP/IP. Сначала рассмотрим самые простые интернет-сервисы. Затем перейдем к общим шаблонам для работы с сетями.

Сокеты

Если вы хотите узнать, как все работает, то этот подраздел для вас.

На самом низком уровне сетевого программирования используется сокет, позаимствованный из языка программирования C и операционной системы Unix. Кодирование на уровне сокетов может быть утомительным. Вам будет гораздо интереснее работать с чем-то наподобие ZeroMQ, однако не помешает узнать, что лежит под этим уровнем. Например, сообщения о сокетах часто появляются при возникновении ошибок в работе с сетями.

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

В следующем коде клиента и сервера элемент address — это кортеж вида (адрес, порт). Данный элемент является строкой, которая может быть именем или IP-адресом. Когда ваши программы просто беседуют друг с другом на одной машине, вы можете использовать имя 'localhost' или эквивалентный адрес '127.0.0.1'.

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

В примере 17.1 показана первая программа, udp_server.py.

Пример 17.1. udp_server.py

from datetime import datetime

import socket

 

server_address = ('localhost', 6789)

max_size = 4096

 

print('Starting the server at', datetime.now())

print('Waiting for a client to call.')

server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

server.bind(server_address)

 

data, client = server.recvfrom(max_size)

 

print('At', datetime.now(), client, 'said', data)

server.sendto(b'Are you talking to me?', client)

server.close()

Сервер должен установить сетевое соединение с помощью двух методов, импортированных из пакета socket. Первый метод, socket.socket, создает сокет, а второй, bind, привязывается к нему (слушает любые данные, приходящие на этот IP-адрес и порт). Параметр AF_INET означает, что мы создаем интернет-сокет (IP). (Существует и другой тип для Unix domain sockets, но он будет работать только на локальной машине.) Параметр SOCK_DGRAM означает, что мы будем отправлять и получать датаграммы — другими словами, станем использовать UDP.

Теперь сервер просто ждет прихода датаграммы (recvfrom). Когда она появляется, сервер просыпается и получает данные и информацию о клиенте. Переменная client содержит комбинацию адреса и порта, необходимую для получения доступа к клиенту. Сервер завершает работу, отправляя ответ и закрывая соединение.

Взглянем на файл udp_client.py (пример 17.2).

Пример 17.2. udp_client.py

import socket

from datetime import datetime

 

server_address = ('localhost', 6789)

max_size = 4096

 

print('Starting the client at', datetime.now())

client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

client.sendto(b'Hey!', server_address)

data, server = client.recvfrom(max_size)

print('At', datetime.now(), server, 'said', data)

client.close()

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

Запустим сервер в отдельном окне. Он выведет приветственное сообщение и будет спокойно ждать до тех пор, пока клиент не отправит ему данные:

$ python udp_server.py

Starting the server at 2014-02-05 21:17:41.945649

Waiting for a client to call.

Далее запустим клиент в отдельном окне. Он выведет приветственное сообщение, отправит данные (слово Hey в байтовом формате), покажет ответ сервера и завершит работу:

$ python udp_client.py

Starting the client at 2014-02-05 21:24:56.509682

At 2014-02-05 21:24:56.518670 ('127.0.0.1', 6789) said b'Are you talking to me?'

Наконец, сервер выведет что-то наподобие этого и завершит работу:

At 2014-02-05 21:24:56.518473 ('127.0.0.1', 56267) said b'Hey!'

Клиенту нужно было знать адрес и номер порта сервера, однако он не указал свой номер порта, поэтому тот был автоматически присвоен системой — в данном случае он был равен 56267.

109914.png

По протоколу UDP данные отправляются небольшими фрагментами. Этот протокол не гарантирует доставки. Если вы отправите несколько сообщений с помощью UDP, то они могут прийти в неправильном порядке или вообще не появиться. Этот протокол быстр, легок, не создает соединений, однако ненадежен. Он полезен в том случае, когда нужно отправить пакеты быстро и не страшны их потери, которые происходят время от времени — например, при использовании технологии VoIP (voice over IP).

Вышесказанное приводит нас к протоколу TCP (Transmission Control Protocol — протокол управления передачей). Он используется для более продолжительных соединений, таких как соединения с Интернетом. TCP доставляет данные в том порядке, в котором те были отправлены. Если возникают какие-то проблемы, то он пытается отправить данные снова. Из-за этого протокол TCP работает немного медленнее, чем UDP, но, как правило, он предпочтительнее, если вам нужно получить все пакеты в правильном порядке.

109920.png

Первые две версии веб-протокола HTTP были основаны на TCP, но HTTP/3 основан на протоколе QUIC (), который использует UDP. Поэтому при выборе между UDP и TCP следует учитывать множество факторов.

Обменяемся пакетами между клиентом и сервером с помощью TCP.

Файл tcp_client.py действует так же, как и предыдущий клиент, работающий с UDP, отправляя только одну строку на сервер. Однако существуют небольшие различия в вызовах сокетов, показанные в примере 17.3.

Пример 17.3. tcp_client.py:

import socket

from datetime import datetime

 

address = ('localhost', 6789)

max_size = 1000

 

print('Starting the client at', datetime.now())

client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

client.connect(address)

client.sendall(b'Hey!')

data = client.recv(max_size)

print('At', datetime.now(), 'someone replied', data)

client.close()

Мы заменили параметр SOCK_DGRAM на SOCK_STREAM, чтобы получить потоковый протокол, TCP. Мы также добавили вызов connect() с целью установить поток. Нам не нужно было делать это для UDP, поскольку каждая датаграмма после отправки предоставлялась сама себе.

Как показано в примере 17.4, файл tcp_server.py также отличается от своего собрата, работающего с UDP.

Пример 17.4. tcp_server.py

from datetime import datetime

import socket

 

address = ('localhost', 6789)

max_size = 1000

 

print('Starting the server at', datetime.now())

print('Waiting for a client to call.')

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

server.bind(address)

server.listen(5)

 

client, addr = server.accept()

data = client.recv(max_size)

 

print('At', datetime.now(), client, 'said', data)

client.sendall(b'Are you talking to me?')

client.close()

server.close()

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

Вызов client.recv(1000) устанавливает максимальную длину входящего сообщения равной 1000 байт.

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

$ python tcp_server.py

Starting the server at 2014-02-06 22:45:13.306971

Waiting for a client to call.

At 2014-02-06 22:45:16.048865 <socket.socket object, fd=6, family=2, type=1,

    proto=0> said b'Hey!'

Теперь запустим клиент. Он отправит сообщение серверу, получит ответ и завершит работу:

$ python tcp_client.py

Starting the client at 2014-02-06 22:45:16.038642

At 2014-02-06 22:45:16.049078 someone replied b'Are you talking to me?'

Сервер получит сообщение, выведет его на экран, ответит и завершит работу:

At 2014-02-06 22:45:16.048865 <socket.socket object, fd=6, family=2, type=1,

    proto=0> said b'Hey!'

Обратите внимание: чтобы ответить, сервер TCP вызвал метод client.sendall(), а в предыдущем примере — client.sendto(). TCP поддерживает клиент-серверное соединение с помощью нескольких вызовов сокетов и запоминает IP-адрес клиента.

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

UDP отправляет сообщения, но их размер ограничен и нет гарантии, что они достигнут места назначения.

• TCP вместо сообщений отправляет потоки байтов. Вы не знаете, сколько байтов отправит или получит система с каждым вызовом.

• Для обмена сообщениями с помощью TCP вам нужна дополнительная информация, чтобы собрать полное сообщение из сегментов: фиксированный размер сообщения (в байтах), или размер всего сообщения, или какой-либо разделитель.

Поскольку сообщения являются байтами, а не текстовыми строками Unicode, вам придется использовать тип bytes. Более подробную информацию об этом типе см. в главе 12.

Если после всего этого вас все еще восхищает программирование сокетов, то вам стоит посетить ресурс Python socket programming HOWTO (), чтобы получить более подробную информацию.

scapy

Иногда вам нужно погрузиться в поток данных, путешествующих по сети. Возможно, вы хотите отладить веб-API или отследить некую проблему безопасности. Библиотека scapy и программа предоставляют предметно-ориентированный язык для создания и анализа пакетов в Python. Работать с ней гораздо проще, чем писать и отлаживать программы, написанные на языке С.

Стандартная команда для установки выглядит так: pipinstallscapy. Документация (/) этой библиотеки довольно обширна. Если вы используете такие инструменты, как tcpdump или wireshark, для исследования проблем, связанных с протоколом TCP, то вам следует обратить внимание на scapy. Наконец, не следует путать библиотеки scapy и scrapy; последняя рассмотрена в разделе «Поиск и выборка данных» главы 18.

Netcat

Еще один инструмент для проверки сетей и портов — Netcat (), название которого зачастую сокращают до nc. Рассмотрим пример соединения HTTP с сайтом Google, где запросим базовую информацию о главной странице:

$ $ nc 80

HEAD / HTTP/1.1

 

HTTP/1.1 200 OK

Date: Sat, 27 Jul 2019 21:04:02 GMT

...

В следующей главе, в подразделе «Тестируем с помощью telnet» раздела «Веб-клиенты», вы увидите пример, в котором делается то же самое.

Паттерны для работы с сетями

Сетевые приложения можно создать на основе некоторых простых паттернов.

Самым распространенным является «Запрос — ответ», также известный как «Клиент — сервер». Работает синхронно: клиент ожидает ответа сервера. Вы видели множество примеров его использования в этой книге. Ваш браузер — это также клиент, делающий HTTP-запрос веб-серверу, который возвращает ответ.

• Еще один распространенный паттерн — «Разветвление на выходе»: вы отправляете данные любому доступному работнику из пула процессов. Примером служит веб-сервер, расположенный за балансировщиком нагрузки.

• Противоположностью этого паттерна является «Разветвление на входе»: вы принимаете данные из одного или нескольких источников. В качестве примера можно привести регистратор, который принимает текстовые сообщения от нескольких процессов и записывает их в единый файл журнала.

Еще один паттерн похож на радио- или телепередачи, он называется «Публикация — подписка» или pub — sub. В рамках этого паттерна публикатор рассылает данные. В простой системе их получат все подписчики. Однако зачастую они могут указать, что заинтересованы только в определенных типах данных (подобные типы часто называются темами), и публикатор будет отправлять лишь эту информацию. Поэтому, в отличие от паттерна «Разветвление на входе», заданный фрагмент данных может получить более чем один подписчик. Если на тему никто не подписался, то данные будут проигнорированы.

Рассмотрим некоторые примеры использования паттерна «Запрос — ответ», а затем и паттерна «Публикация — подписка».

Паттерн «Запрос — ответ»

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

Я только что показал, как выполнять простые запросы с помощью протоколов UDP и TCP, но создать приложение для работы с сетями на уровне сокетов довольно сложно. Посмотрим, сможет ли нам помочь ZeroMQ.

ZeroMQ

ZeroMQ — библиотека, а не сервер. Иногда называемые сокетами на стероидах, сокеты ZeroMQ делают то, чего вы вроде бы ожидаете от обычных сокетов:

происходит обмен сообщениями целиком;

• выполняются повторные соединения при обрыве;

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

Онлайн-руководство (/) написано хорошим языком, в нем представлено лучшее из виденных мной описаний сетевых паттернов. Питер Хинтдженс создал печатную версию (ZeroMQ: Messaging for Many Applications, O’Reilly), внутри которой хороший код, а на обложке — большая рыба (хорошо, что не наоборот). Все примеры в этом печатном руководстве написаны на языке С, но онлайн-версия позволяет выбирать один из нескольких языков для каждого примера. Можно даже выбрать примеры для Python (). В этой главе я покажу базовые приемы работы с ZeroMQ в Python.

ZeroMQ похож на конструктор Lego, и все мы знаем, что даже из небольшого количества деталей можно построить удивительное множество вещей. В этом случае вы будете создавать сети из сокетов нескольких типов и паттернов. Основные «детальки Lego», представленные в следующем списке, являются типами сокетов ZeroMQ, которые из-за превратностей судьбы выглядят как паттерны работы в сети, рассмотренные нами ранее:

REQ (синхронный запрос);

• REP (синхронный ответ);

• DEALER (асинхронный запрос);

• ROUTER (асинхронный ответ);

• PUB (публикация);

• SUB (подписка);

• PUSH (разветвление на выходе);

PULL (разветвление на входе).

Чтобы попробовать поработать с ними самостоятельно, вам нужно установить библиотеку ZeroMQ, введя следующую команду:

$ pip install pyzmq

Простейший паттерн — это одна пара «Запрос — ответ». Он является синхронным: один сокет создает запрос, а затем другой сокет отвечает на него. Сначала рассмотрим код ответа (сервера) zmq_server.py, показанный в примере 17.5.

Пример 17.5. zmq_server.py

import zmq

 

host = '127.0.0.1'

port = 6789

context = zmq.Context()

server = context.socket(zmq.REP)

server.bind("tcp://%s:%s" % (host, port))

while True:

    # ожидаем следующего запроса клиента

    request_bytes = server.recv()

    request_str = request_bytes.decode('utf-8')

    print("That voice in my head says: %s" % request_str)

    reply_str = "Stop saying: %s" % request_str

    reply_bytes = bytes(reply_str, 'utf-8')

    server.send(reply_bytes)

Мы создаем объект типа Context — это объект ZeroMQ, который сопровождает состояние. Далее создаем сокет ZeroMQ, имеющий тип REP (получено от REPly — ответ). Вызываем метод bind(), чтобы заставить его слушать определенный IP-адрес и порт. Обратите внимание: они указаны в строке, 'tcp://localhost:6789', а не в кортеже, как это было в случае с простыми сокетами.

Код в данном примере продолжает получать запросы от отправителя и посылать ответы. Эти сообщения могут быть очень длинными — ZeroMQ обработает детали.

В примере 17.6 показан код клиента, zmq_client.py. Он имеет тип REQ (получено от REQuest — запрос), в нем вызывается метод connect(), а не bind():

Пример 17.6. zmq_client.py

import zmq

 

host = '127.0.0.1'

port = 6789

context = zmq.Context()

client = context.socket(zmq.REQ)

client.connect("tcp://%s:%s" % (host, port))

for num in range(1, 6):

    request_str = "message #%s" % num

    request_bytes = request_str.encode('utf-8')

    client.send(request_bytes)

    reply_bytes = client.recv()

    reply_str = reply_bytes.decode('utf-8')

    print("Sent %s, received %s" % (request_str, reply_str))

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

$ python zmq_server.py &

Запустите клиент в том же окне:

$ python zmq_client.py

Вы увидите чередующиеся строки от сервера и клиента:

That voice in my head says 'message #1'

Sent 'message #1', received 'Stop saying message #1'

That voice in my head says 'message #2'

Sent 'message #2', received 'Stop saying message #2'

That voice in my head says 'message #3'

Sent 'message #3', received 'Stop saying message #3'

That voice in my head says 'message #4'

Sent 'message #4', received 'Stop saying message #4'

That voice in my head says 'message #5'

Sent 'message #5', received 'Stop saying message #5'

Наш клиент завершает работу после отправки пятого сообщения, но мы не давали такого указания серверу, поэтому он будет ожидать новых сообщений. При повторном запуске клиент выведет те же пять строк, сервер тоже выведет их. Если вы не завершите процесс zmq_server.py и попробуете запустить еще один, то Python пожалуется на то, что адрес уже используется:

$ python zmq_server.py

 

[2] 356

Traceback (most recent call last):

  File "zmq_server.py", line 7, in <module>

    server.bind("tcp://%s:%s" % (host, port))

  File "socket.pyx", line 444, in zmq.backend.cython.socket.Socket.bind

      (zmq/backend/cython/socket.c:4076)

  File "checkrc.pxd", line 21, in zmq.backend.cython.checkrc._check_rc

      (zmq/backend/cython/socket.c:6032)

zmq.error.ZMQError: Address already in use

Сообщения нужно отправлять как байтовые строки, поэтому в нашем примере мы закодировали строки в формате UTF-8. Вы можете отправить любое количество сообщений, если будете преобразовывать их в тип bytes. Мы использовали простые текстовые строки как источник сообщений, поэтому методов encode() и decode() будет достаточно для трансформации их в байтовые строки и обратно. Если ваши сообщения имеют другие типы данных, то можете использовать библиотеку, такую как MessagePack (/).

Даже этот простой паттерн REQ — REP позволяет реализовать некоторые шаблоны коммуникации, поскольку любое количество клиентов REQ может использовать метод connect(), чтобы соединиться с единственным сервером REP. Сервер обрабатывает запросы синхронно по одному за раз, но не сбрасывает другие запросы, ожида­ющие его внимания. ZeroMQ буферизует сообщения до определенного лимита; как раз из-за этого в его имени есть буква Q. Здесь она расшифровывается как Queue — «очередь», M — Message («сообщение»), а Zero («ноль») означает, что ему не нужны посредники.

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

Несколько сокетов REQ подключаются к одному сокету ROUTER, который передает каждый запрос сокету DEALER, а тот, в свою очередь, связывается с подключенным к нему сокетом REP (рис. 17.1). Процесс похож на то, как несколько браузеров связываются с прокси-сервером, расположенным перед фермой веб-серверов. Это позволяет при необходимости добавить несколько клиентов и серверов.

Сокеты REQ соединяются только с сокетом ROUTER, сокет DEALER соединяется с несколькими сокетами REP, лежащими позади него. ZeroMQ заботится о деталях, гарантируя, что нагрузка, создаваемая запросами, сбалансирована и ответы будут возвращаться по правильному адресу.

114442.png 

Рис. 17.1. Использование посредника для соединения с несколькими клиентами и серверами

Еще один сетевой паттерн называется «Вентилятор», в его рамках используются сокеты PUSH — для перепоручения асинхронных задач, и сокеты PULL — для сбора результатов.

Последняя значимая особенность ZeroMQ — возможность масштабироваться, просто изменив тип соединения с сокетом при его создании:

tcp выполняет соединение между процессами на одной или нескольких машинах;

• ipc выполняет соединение между процессами на одной машине;

inproc выполняет соединение между потоками одного процесса.

Последнее соединение, inproc, — способ передать данные между потоками, избежав блокировок; является альтернативой примеру работы с потоками, показанному в подразделе «Потоки» на с. 311.

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

Другие инструменты обмена сообщениями

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

проект Apache, чей веб-сервер вы увидите в подразделе «Apache» на с. 411, также поддерживает проект ActiveMQ (/), который включает несколько интерфейсов Python, использующих простой текстовый протокол STOMP ();

• популярна также библиотека RabbitMQ (/), вы можете прочесть онлайн-руководство для нее ();

NATS (/) — это быстрая система обмена сообщениями, написанная на Go.

Паттерн «Публикация — подписка»

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

Redis

Вы уже видели Redis в главе 16, он использовался как сервер структур данных. Однако он также содержит систему публикации — подписки. Публикатор создает сообщения, имеющие тему и значение, а подписчики указывают, какие темы хотят получать.

В примере 17.7 показан публикатор redis_pub.py.

Пример 17.7. redis_pub.py

import redis

import random

 

conn = redis.Redis()

cats = ['siamese', 'persian', 'maine coon', 'norwegian forest']

hats = ['stovepipe', 'bowler', 'tam-o-shanter', 'fedora']

for msg in range(10):

    cat = random.choice(cats)

    hat = random.choice(hats)

    print('Publish: %s wears a %s' % (cat, hat))

    conn.publish(cat, hat)

Каждая тема представляет собой породу кота, а сопутствующее значение — вид шляпы.

В примере 17.8 показан подписчик redis_sub.py.

Пример 17.8. redis_sub.py

import redis

conn = redis.Redis()

 

topics = ['maine coon', 'persian']

sub = conn.pubsub()

sub.subscribe(topics)

for msg in sub.listen():

    if msg['type'] == 'message':

        cat = msg['channel']

        hat = msg['data']

        print('Subscribe: %s wears a %s' % (cat, hat))

Подписчик показывает, что он хочет принимать сообщения о котах породы 'mainecoon' и 'persian' и никаких других. Метод listen() возвращает словарь. Если он имеет тип 'message', то это значит, что он был отправлен публикатору и совпадает с нашими критериями. Ключ 'channel' — тема (порода кота), а ключ 'data' содержит сообщение (шляпа).

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

$ python redis_sub.py

Далее запустим публикатор. Он отправит десять сообщений, а затем завершит работу:

$ python redis_pub.py

Publish: maine coon wears a stovepipe

Publish: norwegian forest wears a stovepipe

Publish: norwegian forest wears a tam-o-shanter

Publish: maine coon wears a bowler

Publish: siamese wears a stovepipe

Publish: norwegian forest wears a tam-o-shanter

Publish: maine coon wears a bowler

Publish: persian wears a bowler

Publish: norwegian forest wears a bowler

Publish: maine coon wears a stovepipe

Подписчик интересуют только две породы котов:

$ python redis_sub.py

Subscribe: maine coon wears a stovepipe

Subscribe: maine coon wears a bowler

Subscribe: maine coon wears a bowler

Subscribe: persian wears a bowler

Subscribe: maine coon wears a stovepipe

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

Вы можете создать любое количество подписчиков и публикаторов. Если для какого-то сообщения подписчика не найдется, то оно пропадет с сервера Redis. Но при наличии подписчиков сообщение останется на сервере, пока все они не получат его.

ZeroMQ

У ZeroMQ нет центрального сервера, поэтому каждый публикатор пишет всем подписчикам. Перепишем наш пример для ZeroMQ. Публикатор zmq_pub.py выглядит так, как показано в примере 17.9.

Пример 17.9. zmq_pub.py

import zmq

import random

import time

host = '*'

port = 6789

ctx = zmq.Context()

pub = ctx.socket(zmq.PUB)

pub.bind('tcp://%s:%s' % (host, port))

cats = ['siamese', 'persian', 'maine coon', 'norwegian forest']

hats = ['stovepipe', 'bowler', 'tam-o-shanter', 'fedora']

time.sleep(1)

for msg in range(10):

    cat = random.choice(cats)

    cat_bytes = cat.encode('utf-8')

    hat = random.choice(hats)

    hat_bytes = hat.encode('utf-8')

    print('Publish: %s wears a %s' % (cat, hat))

    pub.send_multipart([cat_bytes, hat_bytes])

Обратите внимание на то, как в этом коде используется кодировка UTF-8 для темы и строки значения.

Файл подписчика называется zmq_sub.py (пример 17.10).

Пример 17.10. zmq_sub.py

import zmq

host = '127.0.0.1'

port = 6789

ctx = zmq.Context()

sub = ctx.socket(zmq.SUB)

sub.connect('tcp://%s:%s' % (host, port))

topics = ['maine coon', 'persian']

for topic in topics:

    sub.setsockopt(zmq.SUBSCRIBE, topic.encode('utf-8'))

while True:

    cat_bytes, hat_bytes = sub.recv_multipart()

    cat = cat_bytes.decode('utf-8')

    hat = hat_bytes.decode('utf-8')

    print('Subscribe: %s wears a %s' % (cat, hat))

В этом коде мы подписываемся на два разных байтовых значения: две строки из topics, закодированные с помощью UTF-8.

109926.png

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

Обратите внимание: в публикаторе мы вызываем метод send_multipart(), а в подписчике — recv_multipart(). Это позволяет отправлять многокомпонентные сообщения и использовать первую часть как тему. Мы также можем отправить тему и сообщение как простую строку или строку байтов, но подход, где коты и шляпы разделены, кажется более чистым.

Запустите подписчик:

$ python zmq_sub.py

Запустите публикатор. Он немедленно отправит десять сообщений, а затем завершит работу:

$ python zmq_pub.py

Publish: norwegian forest wears a stovepipe

Publish: siamese wears a bowler

Publish: persian wears a stovepipe

Publish: norwegian forest wears a fedora

Publish: maine coon wears a tam-o-shanter

Publish: maine coon wears a stovepipe

Publish: persian wears a stovepipe

Publish: norwegian forest wears a fedora

Publish: norwegian forest wears a bowler

Publish: maine coon wears a bowler

Подписчик выведет на экран все, что запросил и получил:

Subscribe: persian wears a stovepipe

Subscribe: maine coon wears a tam-o-shanter

Subscribe: maine coon wears a stovepipe

Subscribe: persian wears a stovepipe

Subscribe: maine coon wears a bowler

Другие инструменты «Публикации — подписки»

Вам могут пригодиться следующие ссылки Python.

RabbitMQ — широко известный посредник сообщений, Python API для него называется pika. Обратитесь к тематической документации (/) и руководству по «Публикации — подписке» ().

• Перейдите в окно поиска PyPi (/) и введите pubsub, чтобы найти пакеты для Python, такие как pypubsub (/).

• Протокол PubSubHubbub () позволяет подписчикам зарегистрировать функции обратного вызова для публикаторов.

NATS (/) — это быстрая система обмена сообщениями с открытым исходным кодом, которая поддерживает паттерны «Публикация — подписка», «Запрос — ответ» и очереди.

Интернет-сервисы

Python имеет широкий набор инструментов для работы с сетями. В следующих подразделах мы рассмотрим способы автоматизации наиболее популярных интернет-сервисов. В сети доступна полная официальная документация ().

Доменная система имен

Компьютеры имеют числовые IP-адреса, например 85.2.101.94, однако имена мы запоминаем лучше, чем числа. Доменная система имен (Domain Name System, DNS) — критически важный интернет-сервис, который преобразует IP-адреса в имена и обратно с помощью распределенной базы данных. Когда вы используете браузер и внезапно видите сообщение наподобие looking up host, вы, возможно, потеряли соединение с Интернетом, и первым предположением должен стать сбой DNS.

Некоторые функции DNS можно найти в низкоуровневом модуле socket. Функция gethostbuname() возвращает IP-адрес доменного имени, а ее расширенная версия gethostbyname_ex() возвращает имя, список альтернативных имен и список адресов:

>>> import socket

>>> socket.gethostbyname('')

'66.6.44.4'

>>> socket.gethostbyname_ex('')

('crappytaxidermy.com', [''], ['66.6.44.4'])

Метод getaddrinfo() ищет IP-адрес, но также возвращает достаточное количество информации, чтобы создать сокет, который с ним соединится:

>>> socket.getaddrinfo('', 80)

[(2, 2, 17, '', ('66.6.44.4', 80)),

(2, 1, 6, '', ('66.6.44.4', 80))]

Предыдущий вызов вернул два кортежа: первый — для UDP, а второй — для TCP (6 в строке 2, 1, 6 — это значение для TCP).

Вы можете запросить информацию лишь для TCP или только для UDP:

>>> socket.getaddrinfo('', 80, socket.AF_INET,

socket.SOCK_STREAM)

[(2, 1, 6, '', ('66.6.44.4', 80))]

Некоторые номера портов для TCP и UDP () зарезервированы определенными сервисами IANA и связаны с именами сервисов. Например, HTTP имеет имя , ему присвоен номер порта TCP 80.

Эти функции преобразуют имена сервисов в номера портов и наоборот:

>>> import socket

>>> socket.getservbyname('')

80

>>> socket.getservbyport(80)

''

Модули Python для работы с электронной почтой

Стандартная библиотека Python содержит следующие модули для работы с электронной почтой:

smtplib () — для отправки сообщений по электронной почте с помощью простого протокола передачи почты (Simple Mail Transfer Protocol, SMTP);

• email () — для создания и анализа сообщений электронной почты;

• poplib () — для чтения электронной почты с помощью протокола почтового отделения, версия 3 (Post Office Protocol 3, POP3);

imaplib () — для чтения электронной почты с помощью протокола доступа к электронной почте (Internet Message Access Protocol, IMAP).

Если вы хотите написать собственный SMTP-сервер на Python, то попробуйте smtpd () или его новую асинхронную версию — aiosmtpd (/).

Другие протоколы

Используя стандартный модуль ftplib (), вы можете перемещать байты с помощью протокола передачи файлов (File Transfer Protocol, FTP). Несмотря на свой возраст, он все еще хорошо работает.

Вы видели, как эти модули применяются повсеместно в разных местах данной книги. Взгляните также на документацию, касающуюся поддержки интернет-протоколов в стандартной библиотеке ().

Веб-сервисы и API

Поставщики информации всегда имеют сайт, однако он предназначен для человеческих глаз, а не для машин. Если данные опубликованы только на нем, то любой, кто хочет получить к ним доступ, должен писать краулер (это показано в разделе «Поиск и выборка данных» на с. 424) и переписывать их после каждого изменения формата. Обычно данная процедура утомительна. В противоположность ей если сайт предлагает API для своих данных, то эти данные становятся доступными для клиентских программ. API меняются реже, чем макеты веб-страниц, поэтому и изменения в клиентах распространены меньше. Быстрый чистый конвейер также позволяет упростить создание гибридных приложений — комбинаций, которые не предвиделись, но могут быть полезны и даже прибыльны.

Простейший API — веб-интерфейс, который предоставляет данные в структурированном формате, таком как JSON или XML (однако не в текстовом и не в формате HTML). API может быть минимальным или полнофункциональным RESTful API (данное понятие рассматривается в разделе «REST API» на с. 424), это позволит найти еще один выход для байтов, работающих без устали.

В самом начале книги вы видели веб-API — этот интерфейс запрашивал старую копию сайта из Internet Archive.

API особенно полезны для получения данных с популярных сайтов социальных медиа, таких как Twitter, Facebook и LinkedIn. Все эти сайты предоставляют бесплатные API, но требуют от вас регистрации и получения ключа (долго генерируемой текстовой строки, ее часто называют токеном), который будет использоваться при соединении. Ключ помогает сайту определить, кто получает доступ к данным. Он также может служить для ограничения трафика запросов к серверам.

У следующих брендов имеются интересные сервисы API:

New York Times (/);

• Twitter (/);

• Facebook ();

• Weather Underground ();

Marvel Comics (/).

Примеры API для карт вы можете увидеть в главе 21, примеры других API — в главе 22.

Сериализация данных

Как вы видели в главе 16, такие форматы данных, как XML, JSON и YAML, представляют собой способы хранения структурированных текстовых данных. Сетевым приложениям необходимо обмениваться данными с другими программами. Преобразование между данными в памяти и последовательностями байтов (в частности, перед их отправкой другому клиенту) называется сериализацией или маршаллингом. JSON — это популярный формат сериализации, особенно часто используемый в RESTful-системах, но с его помощью нельзя выразить все типы данных, используемые в Python. Кроме того, как текстовый формат он выглядит более многословным в отличие от ряда бинарных методов сериализации. Рассмотрим несколько подходов, с которыми вы можете столкнуться.

Сериализация с помощью pickle

Python предоставляет модуль pickle, позволяющий сохранить и восстановить любой объект в специальном бинарном формате.

Помните, как JSON сошел с ума, когда встретил объект datetime? Для pickle это не проблема:

>>> import pickle

>>> import datetime

>>> now1 = datetime.datetime.utcnow()

>>> pickled = pickle.dumps(now1)

>>> now2 = pickle.loads(pickled)

>>> now1

datetime.datetime(2014, 6, 22, 23, 24, 19, 195722)

>>> now2

datetime.datetime(2014, 6, 22, 23, 24, 19, 195722)

Модуль pickle работает и с вашими классами и объектами. Мы определим небольшой класс, который называется Tiny и возвращает слово 'tiny', когда используется как строка:

>>> import pickle

>>> class Tiny():

...     def __str__(self):

...        return 'tiny'

...

>>> obj1 = Tiny()

>>> obj1

<__main__.Tiny object at 0x10076ed10>

>>> str(obj1)

'tiny'

>>> pickled = pickle.dumps(obj1)

>>> pickled

b'\x80\x03c__main__\nTiny\nq\x00)\x81q\x01.'

>>> obj2 = pickle.loads(pickled)

>>> obj2

<__main__.Tiny object at 0x10076e550>

>>> str(obj2)

'tiny'

Строка pickled — это обработанная pickle бинарная строка, созданная из объекта obj1. Мы преобразовали ее в объект obj2 с целью сделать копию объекта obj1. Используйте функцию dump(), чтобы pickle сохранил данные в файл, и функцию load(), чтобы pickle загрузил данные из файла.

Модуль multiprocessing использует pickle для обмена данными между процессами.

Если модуль pickle не может сериализовать ваш формат данных, то с ним, возможно, справится более новый сторонний пакет dill ().

109934.png

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

Другие форматы сериализации

Есть и другие бинарные форматы обмена данными, они обычно компактнее и быстрее, чем XML или JSON:

MsgPack (/);

• Protocol Buffers ();

• Avro ();

• Thrift (/);

• Lima (/);

• Serialize () — это фронтенд Python для других форматов, включающих JSON, YAML, pickle и MsgPack;

Бенчмарк () различных пакетов сериализации для Python.

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

Некоторые сторонние пакеты выполняют двухстороннюю конвертацию объектов и простых типов данных Python (что позволяет преобразовывать их в такие форматы, как JSON), а также могут выполнять валидацию таких категорий:

типы данных;

• диапазоны значений;

обязательные и необязательные данные.

Ниже представлены некоторые из этих пакетов:

Marshmallow ();

• Pydantic (/) — использует подсказки для типов, поэтому требует версии Python не ниже 3.6;

TypeSystem ().

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

Удаленные вызовы процедур

Удаленные вызовы процедур (Remote Procedure Call, RPC) выглядят как обычные функции, но выполняются на удаленных машинах по всей сети. Вместо того чтобы вызывать RESTful API и передавать туда аргументы, закодированные в URL или теле запросов, вы можете вызвать функцию RPC на собственной машине. При этом в RPC-клиенте произойдет следующее.

1. Он преобразует аргументы вашей функции в байты.

2. Он отправляет закодированные байты удаленной машине.

И вот что происходит на удаленной машине.

1. Она получает закодированные байты запроса.

2. После получения байтов RPC-клиент декодирует их в оригинальные структуры данных.

3. Затем клиент находит и вызывает локальную функцию с помощью раскодированных данных.

4. Далее он кодирует результат работы функции.

5. Наконец, отправляет закодированные байты вызывающей стороне.

Затем машина, запустившая процесс, декодирует полученные байты в возвращенные значения.

RPC — это популярный прием, и люди реализовали его множеством способов. На стороне сервера вы запускаете серверную программу, создаете механизм для ее связывания с помощью какого-либо способа транспортировки байтов и метода кодирования/декодирования, определяете функции сервиса и подаете сигнал «RPC готов к работе». Клиенты соединяются с сервером и вызывают одну из его функций с помощью RPC.

XML RPC

Стандартная библиотека содержит только одну реализацию RPC, которая использует в качестве формата обмена данными XML, — xmlrpc. Вы определяете и регистрируете функции на сервере, а клиент вызывает их так, будто они были импортированы. Сначала рассмотрим файл xmlrpc_server.py (пример 17.11).

Пример 17.11. xmlrpc_server.py

from xmlrpc.server import SimpleXMLRPCServer

 

def double(num):

    return num * 2

 

server = SimpleXMLRPCServer(("localhost", 6789))

server.register_function(double, "double")

server.serve_forever()

Функция, которую мы предоставляем на сервере, называется double(). В качестве аргумента она ожидает число, а возвращает его же, умноженное на два. Сервер начинает работу на определенных адресе и порте. Нам нужно зарегистрировать функцию, чтобы сделать ее доступной клиентам с помощью RPC. Наконец, можно запустить ее.

Теперь, как вы догадались, рассмотрим пример 17.12, в нем показывается файл xmlrpc_client.py.

Пример 17.12. xmlrpc_client.py

import xmlrpc.client

 

proxy = xmlrpc.client.ServerProxy("/")

num = 7

result = proxy.double(num)

print("Double %s is %s" % (num, result))

Клиент соединяется с сервером с помощью функции ServerProxy(). Далее он вызывает функцию proxy.double(). Откуда она появилась? Она была создана динамически с помощью сервера. Механизм RPC волшебным образом прикрепляет имя функции к вызову удаленного сервера.

Попробуйте сами — запустите сервер и клиент:

$ python xmlrpc_server.py

Далее запустите клиент:

$ python xmlrpc_client.py

Double 7 is 14

После этого сервер выведет на экран следующее:

127.0.0.1 - - [13/Feb/2014 20:16:23] "POST / HTTP/1.1" 200 -

Популярные методы передачи данных — HTTP и ZeroMQ.

JSON RPC

JSON-RPC (версии 1.0 () и 2.0 ()) аналогичны XML-RPC, но в них используется JSON. Существует множество библиотек для работы с JSON-RPC, самая простая из них состоит из двух частей: клиента () и сервера ().

Они устанавливаются с помощью команд pipinstalljsonrpcserver и pipinstalljsonrpclient.

Эти библиотеки предоставляют множество альтернативных способов написать клиент () и сервер (). В примерах 17.13 и 17.14 я применяю встроенный в данную библиотеку сервер, который использует порт 5000 и является простейшим.

Сначала рассмотрим сервер.

Пример 17.13. jsonrpc_server.py

from jsonrpcserver import method, serve

 

@method

def double(num):

    return num * 2

 

if __name__ == "__main__":

    serve()

Затем рассмотрим клиент.

Пример 17.14. jsonrpc_client.py

from jsonrpcclient import request

 

num = 7

response = request("", "double", num=num)

print("Double", num, "is", response.data.result)

Как и в случае с большинством примеров архитектуры «Клиент — сервер», рассмотренных в данной главе, сначала запустите сервер (сделайте это в отдельном окне терминала или добавьте после его имени символ &, чтобы запустить его в фоновом режиме), а затем запустите клиент:

$ python jsonrpc_server.py &

[1] 10621

$ python jsonrpc_client.py

127.0.0.1 - - [23/Jun/2019 15:39:24] "POST / HTTP/1.1" 200 -

Double 7 is 14

Если вы запустили сервер в фоновом режиме, то убейте процесс после того, как закончите работу.

MessagePack RPC

Библиотека кодирования MessagePack имеет собственную реализацию RPC (). Она устанавливается следующим образом:

$ pip install msgpack-rpc-python

Эта команда также установит tornado, веб-сервер, основанный на событиях, который данная библиотека использует в качестве транспорта. Как обычно, сначала рассмотрим сервер (msgpack_server.py, пример 17.15).

Пример 17.15. msgpack_server.py

from msgpackrpc import Server, Address

 

class Services():

    def double(self, num):

        return num * 2

 

server = Server(Services())

server.listen(Address("localhost", 6789))

server.start()

Класс Services предоставляет свои методы как сервисы RPC. Теперь запустим клиент, msgpack_client.py (пример 17.16).

Пример 17.16. msgpack_client.py

from msgpackrpc import Client, Address

 

client = Client(Address("localhost", 6789))

num = 8

result =  client.call('double', num)

print("Double %s is %s" % (num, result))

Чтобы увидеть код в действии, выполним привычный порядок действий: запустим сервер и клиент в отдельных окнах терминала, а затем пронаблюдаем результат:

$ python msgpack_server.py

 

$ python msgpack_client.py

Double 8 is 16

Zerorpc

Пакет zerorpc (/) написан разработчиками Docker (когда они еще назывались dotCloud). Он использует ZeroMQ и MsgPack для соединения клиентов и серверов и магическим образом предоставляет функции как конечные точки RPC.

Введите команду pipinstallzerorpc, чтобы установить его. В примерах 17.17 и 17.18 показаны клиент и сервер, работающие по принципу «запрос — ответ».

Пример 17.17. zerorpc_server.py

import zerorpc

 

class RPC():

    def double(self, num):

        return 2 * num

 

server = zerorpc.Server(RPC())

server.bind("tcp://0.0.0.0:4242")

server.run()

Пример 17.18. zerorpc_client.py

import zerorpc

 

client = zerorpc.Client()

client.connect("tcp://127.0.0.1:4242")

num = 7

result = client.double(num)

print("Double", num, "is", result)

Обратите внимание: клиент вызывает метод client.double() даже несмотря на то, что его определение отсутствует:

$ python zerorpc_server &

[1] 55172

$ python zerorpc_client.py

Double 7 is 14

На сайте вы сможете найти еще больше примеров.

gRPC

Компания Google создала gRPC (/) как портативный и быстрый способ определять и соединять сервисы. Этот пакет кодирует данные как буферы протоколов ().

Установите те части, которые касаются Python:

$ pip install grpcio

$ pip install grpcio-tools

Документация к клиенту Python () очень по­дробная, так что здесь я приведу лишь краткий обзор его возможностей. Кроме того, вам может понравиться это отдельное руководство ().

Чтобы использовать gRPC, нужно создать файл с расширением .proto для определения сервиса и его методов rpc.

Метод rpc похож на определение функции (в нем описываются его аргументы и возвращаемые типы) и может указывать на один из следующих паттернов работы с сетью:

запрос — ответ (синхронно или асинхронно);

• запрос — потоковый ответ;

• потоковый запрос — ответ (синхронно или асинхронно);

потоковый запрос — потоковый ответ.

Отдельные запросы могут быть блокирующими или асинхронными. По потоковым запросам можно итерировать.

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

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

Twirp

Twirp () похож на gRPC, однако его создатели заявляют, что он проще. Вы определяете файл с расширением .proto, как и в случае с gRPC, и twirp может сгенерировать код Python, который обрабатывает клиентскую и серверную части.

Инструменты удаленного управления

• Salt (/) написан на Python. Он создавался как способ реализовать удаленное выполнение программ, но позже вырос в полноценную платформу управления системами. Основанный на ZeroMQ вместо SSH, он может работать с тысячами серверов.

• Альтернативными продуктами являются Puppet (/) и Chef (), тесно связанные с Ruby.

Пакет Ansible (), который, как и Salt, написан с помощью Python, вполне сопоставим с ними. Вы можете скачать и применять его бесплатно, но поддержка и отдельные пакеты с надстройками требуют коммерческой лицензии. По умолчанию он использует SSH и не требует установки особого программного обеспечения на тех компьютерах, которыми будет управлять.

Пакеты Salt и Ansible функционально являются супермножествами пакета Fabric, поскольку обрабатывают исходную конфигурацию, развертывание и удаленное выполнение.

Работаем с большими объемами данных

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

Из-за объемов дискового пространства для баз данных и файлов поиск требовал множества механических движений дисковой головки. (Подумайте о виниловой пластинке и времени, необходимом для перемещения иголки с одной дорожки на другую вручную. А также подумайте о скрипящем звуке, который она издаст, если вы надавите слишком сильно, не говоря уже о звуках, которые издаст хозяин пластинки.) Но передавать потоком последовательные фрагменты диска вы можете быстрее.

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

Hadoop

После того как компания Google опубликовала () полученные результаты, компания Yahoo! вслед за ней создала пакет с открытым исходным кодом, написанный на Java, который называется Hadoop (в честь игрушечного плюшевого слона, принадлежавшего сыну главного разработчика).

Здесь вступают в действие слова «большие данные». Зачастую они просто означают следующее: «данных слишком много, чтобы они поместились на мою машину» — данные, объем которых превышает дисковое пространство, память, время работы процессора или все перечисленное. Для некоторых организаций решением вопроса больших данных является Hadoop. Этот пакет копирует данные среди машин, пропускает их через программы масштабирования и сжатия и сохраняет на диск результаты после каждого шага.

Этот процесс может быть медленным. Более быстрый метод — отправкапотокомс помощью Hadoop, который работает как каналы Unix, посылая данные между программами и не требуя записи на диск после выполнения каждого шага. Вы можете писать программы, использующие отправку потоком с помощью Hadoop, на любом языке, включая Python.

Множество модулей Python были написаны для Hadoop, некоторые из них рассматриваются в статье блога A Guide to Python Frameworks for Hadoop (). Компания Spotify, известная передачей потоковой музыки, открыла исходный код своего компонента для отправки потоком с помощью Hadoop — написанного на Python Luigi ().

Spark

Конкурент по имени Spark () был разработан для того, чтобы превысить скорость работы Hadoop в 10–100 раз. Он может читать и обрабатывать любой источник данных и формат Hadoop. Spark включает в себя API для Python и других языков. Вы можете найти документацию по установке онлайн ().

Disco

Еще одна альтернатива Hadoop — Disco (/), который использует Python для обработки MapReduce и язык программирования Erlang для коммуникации. К сожалению, вы не можете установить его с помощью pip — см. документацию ().

Dask

Dask (/) похож на Spark, однако написан на Python и широко используется в научных пакетах, таких как NumPy, Pandas и scikit-learn. Он может распространять задачи в кластерах, содержащих тысячи машин.

Для получения Dask и всех вспомогательных модулей выполните следующую команду:

$ pip install dask[complete]

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

Работаем в облаках

Я действительно совсем не знаю облаков.

Джони Митчелл

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

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

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

Сеть надежна.

• Латентность равна нулю.

• Полоса пропускания бесконечна.

• Сеть безопасна.

• Топология не меняется.

• Существует всего один администратор.

• Стоимость транспортировки равна нулю.

Сеть гомогенна.

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

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

Самые крупные поставщики облачных сервисов:

Amazon (AWS);

• Google;

Microsoft Azure.

Amazon Web Services

По мере роста компании Amazon от сотен до тысяч и миллионов серверов разработчики столкнулись со всеми проблемами распределенных систем. Однажды в 2002 году (или около того) СЕО компании Джефф Безос объявил работникам Amazon, что с этого момента все данные и функционал должны быть доступны только через интерфейсы сетевых сервисов — не через файлы, базы данных или локальные вызовы функций. Программистам пришлось разрабатывать эти интерфейсы так, как если бы их код стал общедоступным. Письмо заканчивалось мотивирующей фразой: «Тот, кто этого не сделает, будет уволен».

Неудивительно, что разработчики взялись за дело и с течением времени создали очень крупную архитектуру, ориентированную на сервисы. Они позаимствовали или придумали сами множество решений, включая Amazon Web Services (AWS) (/), которое сейчас доминирует на рынке. Официальной библиотекой Python для работы с AWS является boto3:

документация ();

страницы SDK ().

Установите ее следующим образом:

$ pip install boto3

Вы можете использовать boto3 как альтернативу веб-страницам для управления AWS.

Google

Google часто использует Python для внутренних нужд и нанимает именитых разработчиков Python (у них какое-то время работал сам Гвидо ван Россум). На главной странице Google (/) и странице, посвященной Python (), вы можете найти подробную информацию о его сервисах.

Microsoft Azure

Microsoft наряду с Amazon и Google предлагает облачный сервис под названием Azure (/). В статье Python on Azure () можно узнать, как разрабатывать и размещать приложения Python в этом сервисе.

OpenStack

OpenStack (/) — это бесплатное решение с открытым исходным кодом, содержащее сервисы Python и Rest API. Многие из них аналогичны сервисам, предлагаемым коммерческими облаками.

Docker

Простой стандартизированный контейнер отправки ПО произвел революцию в международной торговле. Всего несколько лет назад Docker применил название «контейнер» и аналогию к методу виртуализации с помощью малоизвестных особенностей Linux. Контейнеры гораздо легче, чем виртуальные машины, и немного тяжелее, чем virtualenvs в Python. Они позволяют упаковать приложение отдельно от других программ на одной и той же машине, общим для них будет только ядро ОС.

Чтобы установить клиентскую библиотеку Docker (), выполните следующую команду:

$ pip install docker

Kubernetes

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

обход отказов;

• балансировка нагрузки;

масштабирование.

Похоже, что лидером на рынке управления контейнерами становится Kubernetes (/).

Чтобы установить его клиентскую библиотеку (), выполните следующую команду:

$ pip install kubernetes

Читайте далее

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

Упражнения

17.1. Используйте объект класса socket, чтобы реализовать сервис, сообщающий текущее время. Когда клиент отправляет на сервер строку 'time', верните текущие дату и время как строку ISO.

17.2. Задействуйте сокеты ZeroMQ REQ и REP, чтобы сделать то же самое.

17.3. Попробуйте сделать то же самое с помощью XMLRPC.

17.4. Возможно, вы видели эпизод телесериала I Love Lucy, в котором Люси и Этель работают на шоколадной фабрике. Парочка стала отставать, когда линия конвейера, направлявшая к ним на обработку конфеты, еще более ускорилась. Напишите симуляцию, которая отправляет разные типы конфет в список Redis, и клиент Lucy, делающий блокирующие выталкивания из списка. Ей нужно 0,5 секунды, чтобы обработать одну конфету. Выведите на экран время и тип каждой конфеты, которую получит Lucy, а также количество необработанных конфет.

17.5. Используйте ZeroMQ, чтобы публиковать стихотворение из упражнения 12.4 (пример 12.1) по одному слову за раз. Напишите потребитель ZeroMQ, который будет выводить на экран каждое слово, начинающееся с гласной. Напишите другой потребитель, который станет выводить все слова, состоящие из пяти букв. Знаки препинания игнорируйте.

.

Или поместим его в фоновый режим с помощью символа &.

Назад: Глава 16. Данные в коробке: надежные хранилища
Дальше: Глава 18. Распутываем Всемирную паутину

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