Простой Python — страница 40 из 66

Когда мы говорили о конкуренции, то рассматривали только вопросы, связанные с временем, — решения для одной машины (процессы, потоки, зеленые потоки). Мы также кратко коснулись некоторых решений, которые могут охватывать всю сеть (Redis, ZeroMQ). Теперь же рассмотрим работу с сетями и распределение вычислений в пространстве.

Шаблоны

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

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

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

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

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

Модель публикации-подписки

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

Redis

Вы можете создать быструю систему pub-sub с использованием Redis. Публикатор создает сообщения, имеющие тему и значение, а подписчики указывают, какие темы они хотят получать.

Так выглядит публикатор 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)

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

Так выглядит подписчик 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))

Подписчик показывает, что он хочет принимать сообщения о котах породы 'maine coon' и '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

Помните сокеты PUB и SUB от ZeroMQ, которые мы видели несколько страниц назад? Они предназначены именно для этого. У ZeroMQ нет центрального сервера, поэтому каждый публикатор пишет всем подписчикам. Перепишем наш пример для ZeroMQ. Публикатор 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:

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.


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


Обратите внимание на то, что в публикаторе мы вызываем метод 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

Другие инструменты для организации подхода публикации-подписки

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

• RabbitMQ. Это широко известный брокер сообщений, Python API для него называется pika. Обратитесь к документации для pika (http://pika.readthedocs.org/) и обучению работе с механизмом публикации — подписки (http://bit.ly/pub-sub-tut).

• pypi.python.org. В правом верхнем углу окна поиска введите pubsub, чтобы найти пакеты для Python вроде pypubsub (http://pubsub.sourceforge.net/).

• Pubsubhubbub. Этот протокол со сладкозвучным именем позволяет подписчикам зарегистрировать функции обратного вызова для публикаторов (https://code.google.com/p/pubsubhubbub/).

TCP/IP

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

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

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

• 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 — это кортеж вида (адрес, порт). address является строкой, которая может быть именем или IP-адресом. Когда ваши программы просто беседуют друг с другом на одной машине, вы можете использовать имя 'localhost' или эквивалентный адрес '127.0.0.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:

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.

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

$ 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.


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


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

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

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, поскольку каждая датаграмма после отправки предоставлялась сама себе.

Файл tcp_server.py также отличается от своего собрата, работающего с UDP:

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 

····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 

····proto=0> said b'Hey!'

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

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

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

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

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

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

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

ZeroMQ

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

$ pip install pyzmq

Простейший шаблон — это одна пара «запрос — ответ». Он является синхронным: один сокет создает запрос, а затем другой сокет на него отвечает. Сначала рассмотрим код ответа (сервера) 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 обработает детали.

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

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 

····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 (http://msgpack.org/).

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

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

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

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

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

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

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

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

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


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


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

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


ZeroMQ определенно не единственная библиотека, отвечающая за передачу сообщений, которая поддерживается Python. Передача сообщений — это одна из самых популярных идей в работе с сетями, и Python старается соответствовать другим языкам программирования. Проект Apache, чей веб-сервер вы видели в пункте «Apache» раздела «Веб-сервер» главы 9, также поддерживает проект ActiveMQ (https://activemq.apache.org/), включающий в себя несколько интерфейсов Python, использующих простой текстовый протокол STOMP (http://stomp.github.io/implementations.html). Популярна также библиотека RabbitMQ (http://www.rabbitmq.com/), вы можете прочесть онлайн-руководство для нее (http://bit.ly/rabbitmq-tut).

Scapy

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

Я планировал включить в эту книгу примеры исходного кода, но передумал по двум причинам.

• Библиотека Scapy еще не была портирована на Python 3. Раньше это нас не останавливало, мы использовали pip2 и python2, но…

• Инструкция по установке библиотеки Scapy (http://bit.ly/scapy-install), по моему мнению, слишком устрашающая для книги, предназначенной начинающим.

Если хотите, то можете взглянуть на примеры кода на основном сайте с документацией (http://bit.ly/scapy-docs). Они могут вдохновить вас на установку этой библиотеки.

Наконец, не путайте библиотеки Scapy и Scrapy, последняя была рассмотрена в подразделе «Поиск и выборка данных» раздела «Веб-сервисы и автоматизация» главы 9.

Интернет-службы

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

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

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

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

>>> import socket

>>> socket.gethostbyname('www.crappytaxidermy.com')

'66.6.44.4'

>>> socket.gethostbyname_ex('www.crappytaxidermy.com')

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

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

>>> socket.getaddrinfo('www.crappytaxidermy.com', 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('www.crappytaxidermy.com', 80, socket.AF_INET,

socket.SOCK_STREAM)

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

Некоторые номера портов для TCP и UDP (http://bit.ly/tcp-udp-ports) зарезервированы определенными службами IANA и связаны с именами служб. Например, HTTP имеет имя http, ему присвоен номер порта TCP 80.

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

>>> import socket

>>> socket.getservbyname('http')

80

>>> socket.getservbyport(80)

'http'

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

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

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

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

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

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

Официальная документация содержит примеры кода (http://bit.ly/py-email) для всех этих библиотек.

Если вы хотите написать собственный SMTP-сервер на Python, попробуйте smtpd (http://bit.ly/py-smtpd).

Написанный на чистом Python SMTP-сервер, который называется Lamson (http://lamsonproject.org/), позволяет хранить сообщения в базе данных, и вы даже сможете блокировать спам.

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

С помощью стандартного модуля ftplib (http://bit.ly/py-ftplib) вы можете перемещать байты с помощью File Transfer Protocol (FTP). Несмотря на свой возраст, протокол FTP все еще хорошо работает.

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

Веб-службы и API

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

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

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

import requests

url = "https://gdata.youtube.com/feeds/api/standardfeeds/top_rated?alt=json"

response = requests.get(url)

data = response.json()

for video in data['feed']['entry'][0:6]:

····print(video['title']['$t'])

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

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

• New York Times (http://developer.nytimes.com/);

• YouTube (http://gdata.youtube.com/demo/index.html);

• Twitter (https://dev.twitter.com/docs/twitter-libraries);

• Facebook (https://developers.facebook.com/tools/);

• Weather Underground (http://www.wunderground.com/weather/api/);

• Marvel Comics (http://developer.marvel.com/).

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

Удаленная обработка

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

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

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

1. Он преобразует аргументы вашей функции в байты (иногда это называется маршаллингом, сериализацией или просто кодированием).

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

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

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

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

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

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

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

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

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

Стандартная библиотека содержит только одну реализацию RPC, которая использует в качестве формата обмена данными XML, — xmlrpc. Вы определяете и регистрируете функции на сервере, а клиент вызывает их так, будто они были импортированы. Сначала рассмотрим файл 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. Наконец, можно запустить ее.

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

import xmlrpc.client

proxy = xmlrpc.client.ServerProxy("http://localhost:6789/")

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. Другими распространенными кодировками, помимо XML, являются JSON, Protocol Buffers и MessagePack. Существует множество пакетов для работы с RPC, использующих JSON, но многие из них либо не поддерживают Python 3, либо кажутся несколько запутанными. Взглянем на кое-что другое — реализацию Python RPC в рамках MessagePack (http://bit.ly/msgpack-rpc). Установить ее можно следующим образом:

$ pip install msgpack-rpc-python

Этот вызов также установит tornado, написанный на Python веб-сервер, основанный на событиях, который эта библиотека использует как транспорт. Как обычно, рассмотрим сервер первым (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:

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

fabric

Пакет fabric позволяет вам запускать удаленные или локальные команды, загружать или закачивать файлы и работать от лица привилегированного пользователя с помощью команды sudo. Пакет использует Secure Shell (SSH, зашифрованный текстовый протокол, заменивший telnet) для того, чтобы запускать программы на удаленных машинах. Вы пишете функции (на Python) в так называемом файле fabric и указываете, как их нужно запустить — локально или удаленно. Когда вы запустите эти файлы с помощью программы fabric (которая называется fab и не является отсылкой к Beatles или моющему веществу), вы указываете, какие удаленные машины нужно использовать и какие функции вызывать. Это проще, чем примеры RPC, которые мы рассмотрели ранее.


На момент написания этой книги автор пакета fabric вносил в свое творение поправки, которые позволят пакету запускаться с помощью Python 3. Если все пройдет успешно, примеры, приведенные далее, будут работать. В противном случае вам придется запускать их с помощью Python 2.


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

$ pip2 install fabric

Вы можете запустить код локально из файла fabric непосредственно, без использования SSH. Сохраните первый файл под именем fab1.py:

def iso():

····from datetime import date

····print(date.today(). isoformat())

Далее введите следующую команду, чтобы запустить его:

$ fab — f fab1.py — H localhost iso

[localhost] Executing task 'iso'

2014-02-22

Done.

Опция — f fab1.py указывает использовать файл fabric fab1.py вместо варианта по умолчанию fabfile.py. Опция — H localhost указывает запустить команду на вашем локальном компьютере. Наконец, iso — это имя функции, которую нужно запустить. Она сработает точно так же, как и в рассмотренном нами примере, где использовались RPC. Вы можете найти большее количество опций на сайте с документацией (http://docs.fabfile.org/).

Для того чтобы запускать внешние программы на локальной или удаленной машинах, вам нужно запустить SSH-сервер. В системах семейства Unix этот сервер называется sshd; команда service sshd status скажет вам, запущен ли сервер, а команда service sshd start запустит его при необходимости. В операционных системах Mac откройте пункт меню System Preferences, щелкните на вкладке Sharing, а затем установите флажок Remote Login. Операционная система Windows не имеет встроенной поддержки SSH, вам стоит установить putty (http://bit.ly/putty-ssh).

Сейчас мы снова используем имя функции iso, но в этот раз заставим ее запускать команду с помощью метода local(). Так выглядят команда и результат ее работы:

from fabric.api import local

def iso():

····local('date — u')

$ fab — f fab2.py — H localhost iso

[localhost] Executing task 'iso'

[localhost] local: date — u

Sun Feb 23 05:22:33 UTC 2014

Done.

Disconnecting from localhost… done.

Удаленный двойник функции local() — функция run(). Так выглядит файл fab3.py:

from fabric.api import run

def iso():

····run('date — u')

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

$ fab — f fab3.py — H localhost iso

[localhost] Executing task 'iso'

[localhost] run: date — u

[localhost] Login password for 'yourname':

[localhost] out: Sun Feb 23 05:26:05 UTC 2014

[localhost] out:

Done.

Disconnecting from localhost… done.

Обратите внимание на то, что мне предложили ввести пароль. Для того чтобы этого избежать, вы можете встроить пароль в файл fabric следующим образом:

from fabric.api import run

from fabric.context_managers import env

env.password = "your password goes here"

def iso():

····run('date — u')

Теперь запустите файл:

$ fab — f fab4.py — H localhost iso

[localhost] Executing task 'iso'

[localhost] run: date — u

[localhost] out: Sun Feb 23 05:31:00 UTC 2014

[localhost] out:

Done.

Disconnecting from localhost… done.


Размещать свой пароль внутри кода ненадежно и небезопасно. Лучший способ указать необходимый пароль — сконфигурировать SSH, задав открытый и закрытый ключи, с помощью ssh-keygen (http://bit.ly/genkeys).


Salt

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

Salt еще не был портирован на Python 3. В этом случае я не стану показывать вам примеры, написанные на Python 2. Если вам интересна эта область, прочтите документацию и ждите объявлений о том, когда закончится портирование.


Альтернативными продуктами являются puppet (http://puppetlabs.com/) и chef (http://www.getchef.com/chef/), тесно связанные с Ruby. Пакет ansible (http://www.ansible.com/home), который, как и salt, написан с помощью Python, также можно поставить с ними в один ряд. Вы можете загрузить и использовать его бесплатно, но поддержка и некоторые пакеты с надстройками требуют коммерческой лицензии. По умолчанию он использует SSH и не требует установки особого программного обеспечения на тех компьютерах, которыми будет управлять.

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

Большие данные и MapReduce

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

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

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

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

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

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

Множество модулей Python были написаны для Hadoop, некоторые из них рассматриваются в статье блога A Guide to Python Frameworks for Hadoop (http://bit.ly/py-hadoop). Компания Spotify, известная передачей потоковой музыки, открыла исходный код своего компонента для отправки потоком с помощью Hadoop, Luigi, написанного на Python. Порт для Python 3 все еще не готов.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Рассмотрим, как Python взаимодействует с некоторыми популярными облаками.

Google

Google часто использует Python для внутренних нужд и нанимает именитых разработчиков Python (у них какое-то время работал сам Гвидо ван Россум).

Перейдите на сайт App Engine (https://developers.google.com/appengine/) и затем в меню Choose a Language (Выберите язык) установите флажок Python. Вы можете вводить код Python в Cloud Playground и видеть результаты внизу. Сразу после этого раздела вы увидите ссылки, с помощью которых можете загрузить на свой компьютер Python SDK. Это позволит вам разрабатывать для облачных API от Google API на собственном компьютере. Далее рассмотрим детали развертывания вашего приложения на AppEngine.

На главной странице Google (https://cloud.google.com/), касающейся темы облаков, вы можете найти детали о его службах, включая следующие:

• App Engine — высокоуровневая платформа, включающая такие инструменты Python, как Flask и django;

• Compute Engine — создает кластеры виртуальных машин для объемных задач, связанных с распределенными вычислениями;

• Cloud Storage — хранилище объектов (объектами являются файлы, здесь нет иерархий каталогов);

• Cloud Datastore — крупная база данных NoSQL;

• Cloud SQL — крупная база данных SQL;

• Cloud Endpoints — обеспечивает доступ к приложениям с помощью Restful;

• BigQuery — большие данные в стиле Hadoop.

Службы Google конкурируют со службами компаний Amazon и OpenStack.

Amazon

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

Неудивительно, что разработчики взялись за дело и с течением времени создали очень крупную архитектуру, ориентированную на службы. Они позаимствовали или придумали сами множество решений, включая Amazon Web Services (AWS) (http://aws.amazon.com/), которое сейчас доминирует на рынке. Теперь оно состоит из множества служб, но самыми важными являются следующие:

• Elastic Beanstalk — высокоуровневая платформа для приложений;

• EC2 (Elastic Compute) — распределенные вычисления;

• S3 (Simple Storage Service) — хранилище объектов;

• RDS — реляционные базы данных (MySQL, PostgreSQL, Oracle, MSSQL);

• DynamoDB — база данных NoSQL;

• Redshift — хранилище данных;

• EMR;

• Hadoop.

Чтобы подробнее узнать об этих и других службах AWS, загрузите Amazon Python SDK (http://bit.ly/aws-py-sdk) и прочтите раздел Помощь.

Официальная библиотека AWS для Python, boto (http://docs.pythonboto.org/), также все еще не полностью портирована на Python 3. Вам понадобится использовать Python 2 или искать альтернативу, что вы можете сделать, введя в строку поиска Python Package Index (http://pypi.python.org/) aws или amazon.

OpenStack

Вторым самым популярным облачным сервисом был Rackspace. В 2010 году компания создала необычное партнерство с NASA, чтобы объединить свои части облачной инфраструктуры в OpenStack (http://www.openstack.org/). Это бесплатное решение с открытым исходным кодом, которое может использоваться для создания открытых, закрытых и гибридных облаков. Новая версия выходит каждые шесть месяцев, самая последняя из них на момент написания книги содержала более 1,25 млн строк кода Python, внесенного многими участниками. OpenStack используется на производстве постоянно увеличивающимся числом организаций, куда входят CERN и PayPal.

Основные API OpenStack являются RESTful, модули Python предоставляют программные интерфейсы, а программы, запускающиеся из командной строки, отвечают за автоматизацию работы с оболочкой. Рассмотрим стандартные службы текущей версии:

• Keystone — служба идентификации, предоставляющая аутентификацию (например, логин/пароль), авторизацию (инструменты) и обнаружение служб;

• Nova — служба вычислений, распределенная работа с серверами сети;

• Swift — хранилище объектов вроде S3 от Amazon. Оно используется службой Cloud Files от Rackspace;

• Glance — служба хранения изображений промежуточного уровня;

• Cinder — служба хранения блоков низкого уровня;

• Horizon — онлайн-панель управления для всех служб;

• Neutron — служба управления сетью;

• Heat — служба управления (мультиоблачная);

• Ceilometer — служба телеметрии (метрики, мониторинг и измерения).

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

OpenStack работает на Linux или внутри виртуальной машины Linux. Установка его основных служб довольно сложна. Самый быстрый способ установить OpenStack на Linux — использовать Devstack (http://devstack.org/) и читать в процессе весь объясняющий текст. В конце вы увидите онлайн-панель инструментов, которая может просматривать другие службы и управлять ими.

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

Разработка и корпоративная поддержка OpenStack все больше ускоряются. Его начали сравнивать с Linux, когда тот мешал распространению версий Unix.

Упражнения