Очаровательный Python: Организуем распределенные вычисления с помощью RPyC

Проcтой способ гибкого управления удаленными машинами из Python

RPyC - это библиотека для интеграции процессов Python на многих машинах/процессах. В этой статье рассматриваются преимущества и недостатки RPyC по сравнению с другими распределенными инфраструктурами Python, такими как XML-RPC и Pyro. Также, чтобы показать эту библиотеку в работе, приводятся несколько простых примеров использования RPyC.

Дэвид Мерц, писатель, Gnosis Software, Inc.

Дэвид Мерц (David Mertz) - большой знаток в области открытых стандартов и только умеренно пугает многословием. С Дэвидом можно связаться по mertz@gnosis.cx его жизнь описывается более подробно на http://gnosis.cx/dW/. Предложения и комментарии по этой, предыдущей или будущей статьям приветствуются. Можете также посмотреть книгу Дэвида Text Processing in Python.



07.07.2009

В 2002 г. я написал серию статей о распределенных вычислениях (см. ссылку в разделе Ресурсы). В ней я рассказал об инструменте Pyro и библиотеке xmlrpclib; RPyC в то время еще не существовала. Удивительно, но за прошедшие семь лет мало что изменилось: пакет Pyro все еще популярен и активно поддерживается, модули xmlrpclib и SimpleXMLRPCServer все также присутствуют в Python (хотя и были переработаны соответственно в xmlrpc.client и xmlrpc.server в Python 3).

Место, на которое претендует RPyC, во многом уже занято пакетами Pyro и XML-RPC. Пакет RPyC не привносит ничего кардинально нового по сравнению с этими инструментами-ветеранами. Тем не менее он имеет ряд новых интересных возможностей.

В сущности RPyC 3.0+ имеет 2 режима работы: «классический режим», который был доступен и в предыдущих версиях, и «сервисный режим», появившийся в версии 3.0. В классическом режиме нет никакой серьезной инфраструктуры безопасности (что не всегда плохо), в нем удаленные машины просто представлены как локальные ресурсы. Новый сервисный режим является безопасным, так как он изолирует коллекцию опубликованных интерфейсов, поддерживаемых сервером, и запрещает все, что не разрешено явно. Классический режим - это по сути Pyro, но без опциональной инфраструктуры безопасности Pyro, а сервисный режим – это в сущности RPC (например, XML_RPC), отличающийся только деталями правил вызовов и реализации.

Немного о распределенных вычислениях

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

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

  • Сети хранения данных (SAN) централизуют дисковые ресурсы большого количества компьютеров.
  • Одноранговые (Р2Р) протоколы, такие как Gnutella и Freenet, напротив, децентрализуют хранение и извлечение данных.
  • Система X Window и VNC (Virtual Network Computing от AT&T) позволяют видеоустройствам и устройствам ввода соединяться с физически удаленными машинами.
  • Протоколы, подобные Beowulf в Linux, позволяют распределять сложные вычисления между множеством процессоров. В то же время такие проекты, как SETI@Home (проект NASA по поиску внеземного разума - NASA's Search for Extraterrestrial Intelligence), GIMPS (проект по поиску простых чисел Мерсенна - Great Internet Mersenne Prime Search) а также различные криптографические задачи делают то же самое, но с гораздо меньшей необходимостью в координации.
  • Ajax – это еще одно средство использования ресурсов от многих источников, хотя и специфичное для Web-браузеров.

Протоколы и программы, распределяющие то, что для старомодных РС-приложений было просто аппаратными ресурсами, составляют лишь часть всей картины распределенных вычислений. На более абстрактном уровне можно распределять гораздо больше интересных вещей: данные, информацию, программную логику, объекты и, наконец, зоны ответственности. Технологии СУБД традиционно используются для централизации данных и структурирования доступа к ним. С другой стороны, протоколы NNTP, а затем P2P, радикально децентрализовали хранение информации. Другие технологии, в частности поисковые машины, позволяют реструктурировать и рецентрализовать собрания информации. Фактические правила выполняемых вычислений описываются программной логикой (которую распределяют различные типы RMI и RPC), а протоколы взаимодействия между объектами, такие как DCOM, CORBA и SOAP переносят суть логики в инфраструктуру ООП. Конечно же, даже старомодные СУБД с триггерами, ограничениями и нормализацией всегда содержат в себе некоторую степень программной логики. Все эти абстрактные ресурсы в определенные ммоменты сохраняются на диске или магнитной ленте, отображаются в оперативной памяти и передаются битовыми потоками по сетям.

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


В чем польза RPyC

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

  1. Вычислительные (аппаратные) ресурсы. Некоторые машины имеют более мощный процессор, чем другие, также у некоторых процессоры имеют больше чем у других свободного времени из-за небольшой загрузки текущими процессами и приложениями. Аналогично, одни компьютеры имеют больше памяти, чем другие, или больше дискового пространства (что важно, например, в некоторых крупномасштабных научных вычислениях). В некоторых случаях специализированные периферийные устройства могут быть подключены только к некоторым машинам, а не к другим.
  2. Информационные ресурсы. Некоторые компьютеры могут иметь привилегированный доступ к определенным данным. Эти привилегии могут быть нескольких видов. С одной стороны, некоторая машина может быть изначальным источником данных – если, например, машина является терминалом ввода данных (кассовый аппарат, стол регистрации, смотровая площадка и т.д.) или к ней подключен некий автоматизированный сборщик данных, такой как научный прибор. С другой стороны, база данных может являться локальной по отношению к привилегированному компьютеру или некоторой ограниченной группе компьютеров или учетных записей. Но, тем не менее, непривилегированные компьютеры также могут иметь необходимость доступа к некоторым агрегированным или отфильтрованным данным из базы данных.
  3. Деловая компетенция. Внутри каждой организации (или между организациями) существуют стороны (люди или подразделения) ответственные за принятие решений в определенных областях. Например, бухгалтерский отдел может определять и изменять бизнес процессы, связанные с учетом больничных дней и бонусов. Или, например, администратор базы данных может быть ответственным за выявление наиболее эффективного способа извлечения каких-либо данных из сложных реляционных таблиц.

RPyC позволяет вам распределять все эти ресурсы.


Классический режим RPyC

Классический режим RPyC позволяет использовать все возможности удаленной системы Python, запуская ее со своей локальной системы. По умолчанию в нем нет никакого управления безопасностью: учетная запись для работы в оболочке выделяется всем соединяющимся. Однако если безопасность для вас критична, библиотека шифрования tlslite может шифровать соединения и требовать логина при их установлении. Другими словами, вы вместо эквивалента telnet легко можете создать эквивалент ssh. Конечно, преимуществом здесь является то, что соединениями можно управлять из скриптов Python, что дает более широкие возможности взаимодействия между локальными и удаленными ресурсами по сравнению, например, с языком Expect.

Для запуска сервера на удаленной машине просто запустите classic_server.py, поставляемый вместе с RPyC. Если вы хотите устанавливать безопасные соединения, добавьте опцию --vdb. Чтобы задать порт используйте --port, а с помощью --help вы можете посмотреть дополнительные параметры, с которыми может быть запущен сервер. Само собой, вы можете запускать ваш сервер во время общей инициализации системы или с помощью задачи cron, чтобы гарантировать, что он будет запущен на нужной машине. Как только сервер запущен, вы можете соединяться с ним с любого количества клиентов. Заметьте, что сервер не является чем-то особенным: один и тот же процесс на определенной машине может использовать много серверов и в то же время сам выступать сервером для многих клиентов (включая симметричную ситуацию, когда две машины одновременно являются друг для друга серверами и клиентами).

Сеанс оболочки (когда запущены несколько серверов) выглядит так:

Листинг 1. Информация о системе и интерпретаторе Python
>>> import sys,os
>>> os.uname()        # информация о локальной машине
('Darwin', 'Mary-Anns-Laptop.local', '10.0.0d1', 
'Darwin Kernel Version 10.0.0d1: Tue Jun  3 23:40:01 PDT 2008; 
root:xnu-1292.3~1/RELEASE_I386', 'i386')
>>> sys.version_info  # информация о версии локального интерпретатора Python
(2, 6, 1, 'final', 0)
>>> os.getcwd()
'/Users/davidmertz'

Теперь мы выполним импорт RPyC и соединимся с несколькими серверами. Перед тем как сделать это, я запустил два локальных сервера из разных версий Python и один на удаленной машине.

Листинг 2. Импортирование RPyC и соединение с серверами
>>> import rpyc
>>> conn26 = rpyc.classic.connect('localhost')
>>> conn26.modules.os.uname()
('Darwin', 'Mary-Anns-Laptop.local', '10.0.0d1',
'Darwin Kernel Version 10.0.0d1: Tue Jun  3 23:40:01 PDT 2008; 
root:xnu-1292.3~1/RELEASE_I386', 'i386')
>>> conn26.modules.sys.version_info
(2, 6, 1, 'final', 0)
>>> conn26.modules.os.getcwd()
'/Users/davidmertz/Downloads/rpyc-3.0.3/rpyc/servers'
>>> conn25 = rpyc.classic.connect('localhost',port=18813)
>>> conn25.modules.os.uname()
('Darwin', 'Mary-Anns-Laptop.local', '10.0.0d1',
'Darwin Kernel Version 10.0.0d1: Tue Jun  3 23:40:01 PDT 2008; 
root:xnu-1292.3~1/RELEASE_I386', 'i386')
>>> conn25.modules.sys.version_info
(2, 5, 1, 'final', 0)
>>> conn25.modules.os.getcwd()
'/Users/davidmertz/Downloads/rpyc-3.0.3/rpyc/servers'
>>> connGlarp = rpyc.classic.connect("71.218.122.169")
>>> connGlarp.modules.os.uname()
('FreeBSD', 'antediluvian.glarp.com', '6.1-RELEASE',
'FreeBSD 6.1-RELEASE #0: Fri Jul 18 00:01:34 MDT 2008;
root@antediluvian.glarp.com:/usr/src/sys/i386/compile/ANTEDILUVIAN',
'i386')
>>> connGlarp.modules.sys.version_info
(2, 5, 2, 'final', 0)
>>> connGlarp.modules.os.getcwd()
'/home/dmertz/tmp/rpyc-3.0.3/rpyc/servers'

Как вы видите, у нас есть соединения с несколькими различными машинами с различными версиями Python. Здесь мы обращаемся к произвольным функциям и атрибутам, но важно то, что мы можем обращаться к любым функциям и классам, доступным на этих машинах. Например, если я знаю, что на машине antediluvian.glarp.com есть модуль Python Payroll, имеющий функцию get_salary(), я мог бы выполнить следующий вызов:

Листинг 3. Вызов get_salary()
>>> connGlarp.modules.Payroll.get_salary(last='Mertz',first='David')

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

Размещение кода на удаленной машине

Запуск функции стандартного модуля на удаленной машине – это занятная возможность, но все же чаще более полезным является удаленное выполнение собственного кода. Есть несколько способов сделать это в классическом режиме RPyC. Возможно, самый прямолинейный способ – это просто открыть оболочку Python на удаленной машине с помощью установленного нами соединения. Например:

Листинг 4. Открытие оболочки Python на удаленной машине
>>> conn = rpyc.classic.connect('linux-server.example.com')
>>> rpyc.classic.interact(conn)
Python 2.5.2 (r252:60911, Oct  5 2008, 19:24:49)
[GCC 4.3.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> #... запускаем любые команды на удаленной машине

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

Также в RPyC есть функция, называемая deliver(), которая, по всей видимости, посылает локальный объект на удаленный сервер, где с ним, предположительно, можно работать в локальном контексте. К сожалению, у меня не получилось заставить работать эту функцию так, как я ожидал (я уверен, что просто где-то применяю неправильный синтаксис, так как документация довольно расплывчатая). В качестве «костыля» вы можете напрямую выполнять код (или использовать eval()) на удаленном сервере. Например:

Листинг 5. Исполнение кода на удаленной машине
>>> # Определяем функцию (или класс и т.д.) в виде исходного кода
>>> hello_txt = """
...  def hello():
...     import os
...     print "Hello from", os.getcwd()
...  """
>>> exec hello_txt            # Выполняем функцию локально
>>> hello()
Hello from /tmp/clientdir
>>> conn.execute(hello_txt)   # Выполняем функцию удаленно
>>> remote_hello = conn.namespace['hello']
>>> remote_hello()            # Отображается на удаленном сервере
>>> with rpyc.classic.redirected_stdio(conn): # Перенаправляется локальному клиенту
...     conn.namespace['hello']()
...
Hello from /tmp/serverdir

Здесь мы скомпилировали функцию в удаленное пространство имен и выполнили ее, как будто бы она была локальной. Однако если мы хотим видеть результат, выводимый ее инструкцией print, нам нужно перехватывать поток вывода удаленного терминала. Также, если бы hello() возвращала какое-нибудь значение, оно бы возвращалось в локальный контекст, как и в предыдущих примерах.

Monkey patching

То, что мы сделали выше в блоке with, представляет собой «monkey patching» ("партизанское программирование"). Вместо стандартных потоков ввода/вывода удаленной системы мы временно использовали локальные потоки. В RPyC вы можете делать подобное с любыми системными ресурсами. Код может выполняться (т.е. использовать CPU и память) на локальной машине и в то же время использовать какие-либо ресурсы удаленной машины. С точки зрения вычислительных ресурсов такой вызов функции может быть дорогостоящим, если сервер располагает большими ресурсами процессора, но часто подобный вызов представляет собой что-то вроде сокета, в котором удаленная машина имеет различные домены доступа. Например:

Листинг 6. Создаем сокетное соединение, используя monkey patching
import myserver
c = rpyc.classic.connect('machine-outside-firewall')
myserver.socket = c.modules.socket
# ...запускаем myserver, который откроет сокеты снаружи межсетевого экрана

Асинхронные ресурсы

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

Листинг 7. Асинхронный вызов
>>> conn.modules.time.sleep(15)     # Это займет некоторое время
>>> # Пусть этим занимается сервер
>>> asleep = rpyc.async(conn.modules.time.sleep)
>>> asleep
async(<built-in function sleep>)
>>> resource = asleep(15)
>>> resource.ready
False
>>> # какое-то время занимаемся другими делами
>>> resource.ready
True
>>> print resource.value
None
>>> resource
<AsyncResult object (ready) at 0x0102e960>

В нашем случае возвращаемое значение resource.value не представляет интереса. Однако если бы удаленный метод, который мы вызываем асинхронно, возвращал какое-нибудь значение, его можно было бы получить после перехода resource.ready в True.


Сервисный режим RPyC

О более новом сервисном режиме RPyC я напишу относительно немного. Классический режим является более общей системой, хотя и выполнен сам в виде сервиса нового стиля, реализованного в нескольких строках кода. Сервис, лежащий в основе RPyC, в действительности мало чем отличается от XML-RPC (или любого другого RPC). Классический режим – это сервис, открывающий для использования на удаленной системе всё, но вы можете несколькими строками кода создать сервис, позволяющий работать лишь с некоторыми вещами. Например:

Листинг 8. Использование сервисного режима для выставления нескольких методов
import rpyc
class DoStuffService(rpyc.Service):
   def on_connect(self):
       "Do some things when a connection is made"
   def on_disconnect(self):
       "Do some things AFTER a connection is dropped"
   def exposed_func1(self, *args, **kws):
       "Do something useful and maybe return a value"
   def exposed_func2(self, *args, **kws):
       "Like func1, but do something different"

if __name__ == '__main__':
   rpyc.utils.server.ThreadedServer(DoStuffService).start()

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

Листинг 9. Вызов метода, доступного в сервисном режиме
>>> import rpyc
>>> conn = rpyc.connect('dostuff.example.com')
>>> myval = conn.root.func1()   # «корень» соединения
>>> local_computation(myval)

Заключение

Я упомянул не обо всех возможностях RPyC. Например, в RPyC, как и в Pyro, имеется «реестр», позволяющий присваивать сервисам имена и использовать их потом при обращении к сервису вместо IP-адреса или доменного имени. Это достаточно прямолинейно, к тому же в документации RPyC объясняется, как это сделать.

Как я уже говорил в этой статье, RPyC во многом является просто еще одним RPC пакетом – об этом открыто говорится и в документации RPyC. Например, сервис, который мы только что набросали, можно было бы сделать, используя SimpleXMLRPCServer, причем почти с тем же самым кодом. Единственной разницей был бы протокол передачи запросов и ответов. Тем не менее, несмотря на некоторые обнаруженные мной недочеты, RPyC - хорошо сконструированный пакет, с которым очень просто начать работать. С помощью лишь нескольких строк RPyC-кода можно легко организовать надежное распределение ресурсов и обязанностей .

Ресурсы

Научиться

Получить продукты и технологии

  • На домашней страничке проекта RPyC вы можете загрузить пакет, найти хорошую документацию, а также ссылки на обучающие видео (скринкасты) и дискуссионные пространства.
  • Разработайте ваш следующий Linux-проект с помощью ознакомительного ПО от IBM, которое можно загрузить непосредственно с сайта developerWorks.

Обсудить

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Linux, Open source
ArticleID=407113
ArticleTitle=Очаровательный Python: Организуем распределенные вычисления с помощью RPyC
publish-date=07072009