Konstantinov Aleksandr - textx

advertisement
Санкт-Петербургский Государственный Университет
Математико-механический факультет
Кафедра системного программирования
РАЗРАБОТКА ТЕХНОЛОГИИ ВЗАИМОДЕЙСТВИЯ
ГЕТЕРОГЕННЫХ СИСТЕМ С ИСПОЛЬЗОВАНИЕМ
МЕТАПРОГРАММИРОВАНИЯ
Дипломная работа студента 545 группы
Константинова Александра Сергеевича
Научный руководитель
………………
/ подпись /
Соломатов К.В.
Рецензент
………………
/ подпись /
Шкредов С.Д.
“Допустить к защите”
заведующий кафедрой,
………………
/ подпись /
д.ф.-м.н., проф. Терехов А.Н.
Санкт-Петербург
2011
Saint-Petersburg State University
Mathematics and Mechanics Faculty
Software Engineering Department
IMPLEMENTATION OF HETEROGENOUS SYSTEM
INTEROPERABILITY TECHNOLOGY
WITH METAPROGRAMMING
Graduate paper by
Alexander Konstantinov
545 group
Scientific advisor
………………
K.V.Solomatov
Reviewer
………………
S.D.Shkredov
“Approved by”
………………
Professor A. N. Terekhov
Head of Department
Saint-Petersburg
2016
Оглавление
Введение ................................................................................................................................................. 4
Постановка задачи ............................................................................................................................. 5
Глава 1. Обзор существующих решений ............................................................................... 6
XML-RPC ............................................................................................................................................ 6
SOAP ................................................................................................................................................... 7
JSON-RPC ........................................................................................................................................... 7
.NET Remoting .................................................................................................................................... 8
RMI ...................................................................................................................................................... 8
Corba .................................................................................................................................................... 8
GWT-RPC ............................................................................................................................................ 9
ONC-RPC ............................................................................................................................................ 9
DCE/RPC ............................................................................................................................................. 9
Routix-RPC .......................................................................................................................................... 9
ZeroC ................................................................................................................................................. 10
Burlap ................................................................................................................................................. 11
Hessian ............................................................................................................................................... 11
Abstract Syntax Notation One ........................................................................................................... 11
Etch .................................................................................................................................................... 12
Thrift .................................................................................................................................................. 13
Protobuf.............................................................................................................................................. 15
Kryo ................................................................................................................................................... 17
Protostuff............................................................................................................................................ 17
Avro ................................................................................................................................................... 17
Сравнение ......................................................................................................................................... 17
Выводы из обзора............................................................................................................................. 19
Глава 2. Алгоритм работы ........................................................................................................... 20
Глава 3. Реализация......................................................................................................................... 23
Выбор платформы ............................................................................................................................ 24
Язык асинхронных вызовов ............................................................................................................ 26
Язык сообщений ............................................................................................................................... 27
Язык сервисов................................................................................................................................... 28
Исключения ...................................................................................................................................... 29
Генерация в Objective C ................................................................................................................... 31
События............................................................................................................................................. 33
Тестирование .................................................................................................................................... 34
Глава 4. Внедрение .......................................................................................................................... 35
Заключение .......................................................................................................................................... 36
Литература ........................................................................................................................................... 38
3
Введение
В данный момент самой частой архитектурой приложений является трехзвенная
архитектура, представляющая из себя клиент, сервер приложений, далее просто сервер, и
сервер базы данных. При этом чаще всего клиенты должны быть реализованы для разных
платформ и на разных языках. Самой сложной задачей становится процедура
взаимодействия гетерогенных систем, поскольку сервер может быть написан на Java, а
клиенты на одном из языков, которые чаще всего предоставлены для мобильных
устройств: Java, Objective C, JavaScript (устройства типа iPad, android-клиенты, вебклиенты).
Проект, в рамках которого был выполнен данный диплом, является приложением,
имеющим различные клиенты: GWT, AWT, iPad, android. При этом каждый запущенный
клиент постоянно делает запросы к серверу и получает от него ответы большого объёма.
Поэтому реализация эффективного взаимодействия – первостепенная задача.
Существует множество решений для удаленного вызова процедур и для передачи
данных, но у всех есть какие-либо недостатки: тяжеловесность, большое потребление
трафика, низкая скорость кодирования, сложность использования, отсутствие поддержки
какой-либо
платформы,
отсутствие
необходимой
функциональности,
отсутствие
возможности расширения, низкий уровень абстракции.
Если клиентское приложение запущено на мобильной платформе, то большой объём
трафика является самым тонким местом, поскольку многие RPC реализованы на базе
XML, который, как известно, крайне увеличивает объём передаваемой информации.
Кроме того, важной частью работы была возможность поддержки веб-клиента,
поскольку он является одним из ключевых, так как у абсолютного большинства
пользователей есть возможность запустить приложение из браузера. Изначально
технологией для разработки был выбран фреймворк GWT, который позволяет писать код
на Java, а затем транслировать его в JavaScript. В комплект поставки GWT входит GWTRPC, но его реализация есть только для Java, а значит нужно использовать стороннее RPC.
Практически никакое существующее решение не поддерживает данную технологию, так
как создает код на Java, который не может быть транслирован в JavaScript.
Для достижения более высокого уровня абстракции, возможности расширения, было
выбрано метапрграммирование [1], поскольку оно позволяет быстро и удобно создавать
языки, создавать для них генераторы, отходить от парадигм каких-то конкретных языков
программирования. Кроме того проект, для которого нужно было RPC, был реализован с
4
помощью MPS [2], поэтому конкретной платформой для метапрграммирования был также
выбран JetBrains MPS, чтобы добиться лучшего уровня и простоты интеграции.
Постановка задачи
В рамках проводимой работы было необходимо:

Исследовать существующие технологии взаимодействий сервера и клиентов,
выявить ключевые моменты, сформировать необходимые требования к своему
решению

Создать дизайн для языка сообщений

Реализовать язык для сообщений, которые будут передаваться. В эту часть
входит задача создания кодирования и декодирования

Реализовать язык для сервисов, используя язык сообщений

Реализовать генераторы перечисленных выше языков для Java и Objective C.
Для Java требуется как клиентская, так и серверная часть. Для Objective C
требуется только клиентская часть

Внедрить решение в проект

Провести сравнение с существующими RPC
В поставленную задачу не входит реализация генераторов для большого числа языков,
но требуется сделать возможность дальнейшего расширения количества поддерживаемых
языков.
Реализация должна легко интегрироваться в проект,
быстро
кодировать и
декодировать информацию, создавая при этом максимально компактные сообщения.
5
Глава 1. Обзор существующих решений
В данный момент существует достаточно много средств для взаимодействия
различных объектов по сети. Некоторые решения предлагают только протокол, который
соответствующим образом представляет данные. Другие являются лишь стандартами для
реализации взаимодействия по сети, а конкретные компоненты нужно искать среди
реализаций этого стандарта. Третьи же являются достаточно полными и представляют не
только
протокол,
но
и
средства
для
интеграции
сразу в
несколько
языков
программирования.
Основным свойством для классификации подобной технологии является то, какой
протокол используется для передачи: сжатие, формат и прочее. Так например, очевидно,
что использование XML не позволяет уменьшить объём передаваемой информации,
поскольку он является текстовым, а кроме того и избыточным.
В данной части работы будут рассмотрены наиболее популярные решения всех частей
поставленной задачи: кодирование сообщений, генерация в языки, сама технология
взаимодействия, будут подчеркнуты слабые и сильные стороны, исходя из которых станут
ясны требования к разрабатываемой технологии.
Приведены такие популярные решения системы удаленного вызова процедур, как
XML-RPC [3], SOAP [4], JSON-RPC [5], .NET Remoting [6], RMI [7], Corba [8], GWT-RPC
[9], ONC-RPC [10], DCE/RPC [11, 12], Routix-RPC [13], Zero Ice [14], Burlap [15], Etch [18],
Thrift [19]. Также представлены самые популярные технологии для кодирования структур:
Hessian [16], ASN.1 [17], Protobuf [20], Kryo [21], Protostuff [22], Avro [23].
XML-RPC
Является стандартом, разработанным в 1998 году. Является прародителем популярной
технологии SOAP. В свою же очередь XML-RPC был определен как слишком
упрощенный, но именно по этой его использование часто становится выгоднее, так как
ресурсы для разработки будут не столь высоки, как при использовании SOAP.
Данная технология является только стандартом, поэтому имеет большое число
реализаций, которые не всегда согласованы друг с другом. Таким образом, для каждого
языка нужно производить подключение библиотек, писать тесты, которые будут
проверять, что эти библиотеки совместимы. Поскольку нет единой системы написания и
генерации кода, то для каждой платформы нужно делать это по-своему.
Отдельное внимание стоит уделить тому, что данная технология основывается на
языке разметки XML, поэтому присутствуют потери в производительности, а также
повышенное
потребление
трафика.
Так
6
для
примера:
передача
сообщения
с
использованием JSON сократит объём примерно в 2-4 раза. Это является критичным при
использовании RPC для хранения какой-то информации или при передаче её часто или в
больших объёмах.
SOAP
Является расширенной и доработанной технологией, описанной выше. Изначально
данный протокол предназначался для удаленного вызова процедур, но в дальнейшем стал
использоваться и для простой передачи сообщений.
Протокол может использовать практически любой протокол прикладного уровня:
SMTP, FTP, HTTP, HTTPS и другие. Но чаще всего используется только HTTP. В рамках
данной задачи использование протоколов отличных от HTTP неоправданно, поскольку
веб-клиент базируется именно на нём.
SOAP
предоставляет
огромный
функционал,
позволяет
писать
приложения
практически любой сложности, связывая гетерогенные системы и не задумываясь о том,
где физически находится удаленный объект.
В минусы данной технологии можно отнести тоже самое, что и в XML-RPC, поскольку
SOAP является расширением. Кроме этого скорость обработки сообщений понижается,
так как количество уровней обработки и сложность увеличена. Так же к минусам можно
отнести сложность использования, повышенные трудозатраты. Существует множество
реализаций, но не все согласованы, поэтому выбор библиотек для конкретных языков
также трудозатратен.
JSON-RPC
Во многом является похожим на XML-RPC, только для транспорта используется не
XML-сообщения, а JSON, что заметно уменьшает размер передаваемых сообщений.
Существует множество реализаций для различных языков. Таким образом, нет единой
системы описания интерфейсов, интегрирование должно производиться вручную.
Данный формат сообщений используется сайтом vkontakte.ru для предоставления
взаимодействия стороннего разработчика с сервером сайта. Также там предоставлен
формат XML ответов.
Очевидно, что это решение имеет достаточно низкий уровень абстракции и не
позволяет получать классы из сообщений и наоборот, то есть эта задача возложена на
программиста. Некоторые из реализаций избавлены от этого недостатка посредством
использования дополнительных библиотек. Например, Jettison использует для этих целей
Xstream.
7
.NET Remoting
Данное решение используется как часть .NET Framework. Позволяет создавать
удаленные объекты в другом процессе использовать их так, будто они находятся в одном
процессорном пространстве.
Является удобным решением при использовании его в рамках данного фреймворка. Но
не позволяет использовать его на других платформах, например, на iOS.
RMI
Данная технология является программным интерфейсом для языка Java. Реализует
удаленный вызов процедур, автоматическую передачу сообщений. Вызов удаленных
методов скрыт за вызовом обычных методов объектов, которые по сути являются только
заглушками.
Так как решение предназначено только для Java, то его использование затруднено или
даже невозможно на других платформах. Кроме того, RMI основывается на сериализации,
то есть на reflection-api, а это означает, что генерация в JavaScript крайне сложна.
К плюсам данного решения можно отнести его простоту:
кодирование и
раскодирование происходит незаметно для разработчика, ему не нужно определять
никаких дополнительных сообщений.
Corba
Это стандарт, который позволяет различным компонентам системы, написанным на
разных языках и запущенных на разных компьютерах, работать так же, как если бы они
были в одном адресном пространстве процесса.
Данная технология использует язык
OMG IDL для описания интерфейсов
взаимодействия объектов с внешним миром. Кроме этого существует порядка десятка
стандартных отображений в языки, используемые разработчиками на CORBA, также есть
несколько нестандартных. Основные поддерживаемые языки: C, C++, C#, Java, Python,
Perl, VB.
Существует множество критики в адрес Corba. Изначально была предложена только
спецификация для интерфейсов, поэтому некоторые реализации открытых проектов
несовместимы. Также спецификация является крайне сложной и обширной, так как её
создатели хотели вместить все возможности, какие могут быть, не уделив внимания
согласованности. Кроме этого к минусам стоит отнести повышенную ресурсоемкость.
8
GWT-RPC
Стандартное решение для технологии GWT. Во многом схоже с RMI. Использует своё
понятие сериализуемости. Присутствуют асинхронные вызовы методов. Использование
возможно только на Java.
ONC-RPC
Известен также как SUN-RPC. Изначально создавалось Sun как часть проекта сетевой
файловой
структуры.
Является
низкоуровневым
взаимодействием,
чаще
всего
применяется на языке C и C++.
Особое внимание нужно уделить формату передачи данных – XDR. Он стал
стандартом для многих клиент-серверных приложений. Используется для того, чтобы
программисту не нужно было обращать внимание на различные форматы представления
данных на разных платформах: порядок хранения байтов для чисел, перечисления и
другие.
DCE/RPC
Является технологией во многом схожей с Corba. Позволяет создавать гибкие
приложения, при этом существуют стандарты, которые поддерживаются, за счёт этого нет
проблем с интеграцией реализаций для разных платформ.
Данная технология имеет много минусов, которые часто останавливают разработчиков
от выбора именно её. В первых реализациях вызовы удаленных процедур являются
синхронными и блокирующими. Асинхронные вызовы в дальнейшем были добавлены, но
другими разработчиками, например, сообществом Encompass. Главным свойством
DCE/RPC является то, что оно имеет громадное число функций, сложную архитектуру и
не подходит для разработки простых клиент-серверных приложений, где не требуется
сложное администрирование и большой набор сервисов. Таким образом, технология
крайне тяжеловесна и частично устаревшая.
Routix-RPC
Является коммерческой реализацией удаленного вызова процедур. Существует
реализация для нескольких языков, основным критерием является поддержка языком
технологии COM, так как происходит генерация COM-объекта. Сообщения в Routix
передаются как XML, то есть нерационально. Удаленным объектом может являться
любой, который поддерживает интерфейс IDispatch, то есть ActiveX.
9
Поскольку это решение не содержит открытых код, то исследование шифрования не
может быть произведено. Но поскольку используется XML и COM (это означает, что
решение подходит только для платформы Windows), то данная технология не является
универсальным и подходящим решением.
ZeroC
Данная технология была создана под влиянием Corba, поэтому имеет с ней общие
черты, но и ряд отличий, которые улучшают её работу. Позволяет построить приложение
на платформах C++, Java, Ruby, C#, VB, Objective C, Python, Php.
При изучении ZeroC стоит уделить особое внимание языку описания объектов,
поскольку он является удобным и функциональным.
Slice – язык описания – имеет поддержку стандартных типов. Однако, разработчики
сделали упрощение, убрав беззнаковые типы, также сделав строки только Unicode. В
данный момент при разработке веб-сервисов достаточно редко встречаются не
юникодные
строки,
поэтому
данное
упрощение
является
важным.
class Human {
string name;
short age = 0;
};
class Person extends Human {
string profession;
};
Листинг 1. Пример сообщения в ZeroC
Как видно из примера (лист. 1), язык описания является понятным, поскольку он Cподобный – многие современные частоиспользуемые языки являются такими, поэтому в
данной работе была выбрана такая же парадигма.
Slice поддерживает наследование, коллекции объектов, пользовательские исключения,
интерфейсы, асинхронные вызовы и многие другие функции. Тонким местом данной
технологии является невозможность использования её совместно с GWT.
Данное решение поставляется под лицензией GPL, что означает, что его использование
возможно только с предоставлением исходных кодов, что является невозможным для
проекта. Второй же вариант предоставления лицензии – коммерческий, данный вариант
не входит в политику компании JetBrains.
Таким образом, ZeroC – решение, которое включает в себя практически всё
необходимое, но требует конфигурирования генерации и имеет не столь высокий уровень
абстракции.
10
Burlap
Является протоколом для Java основанным для XML и большей частью предназначен
для реализации взаимодействия мобильных клиентов с серверов. Данную технологию
используют в основном для J2ME.
Burlap крайне прост в использовании, но предоставляет узкий набор возможностей.
Hessian
Представляет собой бинарный протокол. Таким образом, он не может быть
использован при разработке веб-клиента, т.к. в JavaScript следует использовать base64.
Данный протокол умеет кодировать структуры и простые типы. Однако, информация,
генерируемая им, избыточна, если формат передаваемых сообщений заранее определен,
так как при кодировании также сохраняется тип информации. Для примера, целое число
“300” будет закодировано как “I x00 x00 x01 x2C” (I – помечает, что это integer, а
остальные байты кодируют информацию), то есть будет использован лишний байт.
Также при кодировании коллекций используется указание типа, что практически
никогда не нужно, если мы используем типизированные коллекции. В данной работе была
выбрана политика строгой типизации, поскольку это уменьшает число ошибок и
облегчает разработку.
Передача вызываемой процедуры происходит по названию, что затрудняет изменение
названий методов: старые версии клиентов не смогут взаимодействовать с новыми
версиями сервера.
Abstract Syntax Notation One
Состоит из двух разных частей: первая определяет стандарт записи сообщений, вторая
- стандарт кодирования и декодирования.
Ниже приводится пример протокола (лист.2), описанного с помощью этого
стандарта.
Work DEFINITIONS ::= BEGIN
Human ::= SEQUENCE {
name
IA5String,
age
INTEGER
}
Person ::= SEQUENCE {
hasWork
BOOLEAN
}
END
Листинг 2. Пример сообщений в ASN.1
11
Из данного примера видно, что дизайн языка не является правильным, поскольку
сильно отличается от описания структур и классов в популярных современных языках
программирования, таких как C#, Java, C++, Objective C, Java. Также он является
избыточным.
В данном стандарте содержится несколько видов кодирования.

Формат CER , DER - кодирования очень близкие к Hessian. Являются
избыточными, хранят в себе описание типов. Отличаются друг от друга
правилами хранения длины: CER хранит завершающий октет, DER хранит
длину.

Формат BER – отличается от CER и DER тем, что может поддерживать внутри
себя несколько типов кодирования. (хранение длины может быть разным)

Формат XER – кодирование с помощью XML, нужно для возможности чтения
сообщений людьми, неоптимальное.

Формат PER (Packed Encoding Rule) – для этого кодирования важно знать
структуру передаваемых сообщений. Является компактным. Поддерживает два
режима: с выравниванием по байтам и без. Умеет кодировать информацию в
битовые массивы.
В данной технологии описаны только стандарты. Стандарт же отображения на
конкретный язык программирования не стандартизирован. Стандарт является сложным и
для его использования требуется приобретение ASN.1 компилятора. Если же есть
отображение в несколько языков, то требуется несколько компиляторов. Таким образом,
эта технология является слишком сложной для собственной реализации, покупка же не
целесообразна.
Etch
Является независимым от платформы и языка, свободнорасширяемым решением. В
данный момент существуют отображения в языки Java, C, C#, Go, JavaScript, Python.
Позволяет определять сервисы, сообщения, создавать простые и быстрые приложения.
Для описания сервисов был создан специальный язык. Пример сервиса из такого языка
приведен в листинге (лист.3).
12
module org.apache.etch.examples.helloworld
service HelloWorld {
struct User (
int id,
string name
)
exception UserUnknownException (
string mes
)
@Direction(Server)
string sayHello(User toWhom) throws UserUnknownException
}
Листинг 3. Пример сервиса в Etch
Из примера видно, что синтаксис языка очень похоже на синтаксис языка Java. Кроме
того, разработчики проводят тестирование производительности и сравнение именно на
Java. В данный момент поддержка языка Objective C не реализована и её нет в планах на
ближайшее будущее, что является проблемой при разработке приложений под платформу
iOS.
Разработчики добились ускорения почти в 2 раза по сравнению с RMI и более чем в 5
раз по сравнению с Corba. Для тестирования был выбран сервис складывающий два числа.
Таким образом, данное решение является хорошим, поскольку обеспечивает должную
простоту при нужных возможностях, но пока что является неполным и незавершенным. В
данный момент реализованными протоколами являются бинарный и xml. Однако, как и
некоторые упомянутые выше форматы, бинарный формат etch является избыточным,
потому что хранит в себе теги, определяющие тип содержимого, что можно избежать,
если заранее знать структуру. Имена структур передаются не строкой, а 32битным хешем
от строки, поэтому достигается большая компактность, но отсутствует возможность
переименования.
Thrift
Предлагает решение для таких языков, как C++, C#, Java, Python, Php, Ruby, Erlang,
Perl, Haskell, Objective C, JavaScript, SmallTalk, OCaml, AS3.
В данном решении присутствует ряд возможностей, которые не поддержаны:
наследование, циклические структуры, null как результат вызова, то есть если
13
пользователь хочет вернуть null метода, то он должен сделать обёртку, некий маркер,
который будет означать пустой результат.
Поддержано три основных метода кодирования: JSON, binary и compact. Однако
компактный метод кодирования в данный момент реализован лишь для нескольких
платформ и не имеет стандарта, на сайте разработчиков можно предложить свою версию
алгоритма, который сможет уменьшить размер сообщений.
В примере (лист.4) приведен пример описания структуры.
struct Work {
1: i32 num1 = 0,
2: i32 num2,
3: Operation op,
4: optional string comment,
}
Листинг 4. Пример структуры в Thrift
Из данного примера видно, что возможны опциональные поля. Также можно указывать
номера полей, чтобы в дальнейшем при добавлении новых не происходило ошибок.
Данный момент является очень тонким: сохранение номеров позволяет добиться
версионирования, но заметно увеличивает размер передаваемых сообщений, так как при
кодировании также нужно закодировать номер. Заметим, что при удалении поля
устаревшие клиенты чаще всего будут выдавать ошибку, если им необходимо это поле.
При добавлении же нового поля в конец структуры ошибок не будет, так как на
устаревших клиентах процесс разбора сообщения может просто пропустить лишние поля.
Поэтому в данной работе было решено не передавать дополнительную информацию, как
это делает Thrift.
Как было описано во введении, в рамках данного проекта важна возможность
интеграции с GWT. В данный момент уже существует возможность добиться интеграции
двух этих технологий, однако, это сложная задача и предлагаемые решения не являются
универсальными, также нет гарантии качества, поэтому при изменении одной из
технологий может оказаться, что существующая интеграция больше не работает.
Thrift мог бы являться одним из лучших решений поставленной задачи, поскольку он
обладает простым интерфейсом, не осложнен избыточным функционалом, быстрый и
легковесный, поддерживает большое число платформ.
Однако его использование неудобно в рамках данного проекта, поскольку в нём нет
циклических структур, существуют ограничения на возвращаемые значения, решение
является не расширяемым.
14
Protobuf
Protocol Buffers – решение, которое предлагает корпорация Google. Представляет
собой способ кодирования структурированной информации в эффективный формат.
Имеет много пользовательских дополнений к реализации, которые представляют способ
генерации кода в языки, не предусмотренные Google. В данный момент имеется
поддержка порядка 25 языков программирования. Качество реализации под языки,
которые не поддерживают разработчики Google, остается на ответственности сторонних
разработчиков, нет никаких гарантий, что там нет ошибок, каких-либо недоработок и
неоптимального кода.
На основе Protobuf построено некоторое количество реализаций RPC. Примером такой
технологии является ZeroC, рассмотренный выше.
В примере (лист.5) приведено сообщение, которое содержит описание человека.
message Human {
required string name = 1;
required int32 age = 2;
optional bool hasWork = 3;
enum Sex {
MAN = 0;
WOMAN = 1;
UNKNOWN = 2;
}
required Sex sex = 4 [default = UNKNOWN];
repeated string phone = 5;
}
Листинг 5. Пример сообщения в Protobuf
Из примера видно, что в данном языке описания сообщений присутствуют
дополнительные дескрипторы. В данном синтаксисе определено два разных понятия:
обязательное поле и опциональное. Если в одной версии приложения используется
обязательное поле, а в следующей нет, то программа может перестать работать. Поэтому
документация призывает внимательно выбирать, делать ли поле обязательным. Сами
разработчики Google используют разные подходы при описании структур: некоторые
используют обязательные поля, а некоторые в абсолютно всех ситуациях используют
опциональные.
В синтаксисе данного языка также просматривается неудобство: разработчик может
перепутать задание значений по умолчанию и номер поля, поскольку данный синтаксис
используется
практически
во
всех
языках
15
программирования
для
задания
инициализирующего значения. Кроме этого, как было указано при рассмотрении
предыдущих вариантов протоколов, хранение и передача номеров полей не всегда
является оптимальным, так как делает протокол избыточным.
Кодирование производится в битовый формат, который в дальнейшем преобразуется в
байты. То есть, нет поддержки base64, хотя она могла бы быть, так как из битового
массива легко получить его.
Чаще всего при передаче числе типа integer передаются не столь большие значения: их
можно сохранить в 7 бит. Реже встречаются такие, которые можно записать в 14 бит, и так
далее. Разработчики protobuf придумали простую оптимизацию, которая не может
гарантировать всегда лучший результат, но чаще всего даёт уменьшение объёма
сообщения: числа хранятся не по байтам, а по 7 бит, 8ой же бит означает, содержатся ли
ещё байты в числе.
Рассмотрим пример кодирования числа «9», оно будет закодировано так: «0 0001001».
То есть вместо 2 байтов или 4 будет использован всего 1. Если же кодируется число
«300», то получится «1 0101100 0 0000010», то есть два байта. Здесь первый бит равный 1
означает, что сообщение содержит ещё байты из этого числа.
Алгоритм получается следующий: закодировать число в base128, отрезать первые
септеты, в которых содержатся нулевые значения, далее ко всем септетам, кроме
последнего, добавить 1, а к последнему – 0.
Использование base128 является эмпирическим, но в среднем будет давать лучшие
результаты, чем хранение всего числа или длины числа. Также этот алгоритм даёт
простоту реализации. Многие разработчики выбирают protobuf, а не ASN.1 именно из-за
простоты и легковесности.
Protocol Buffers предлагает некоторые возможности для расширения сообщений:
добавление extensions, но их использование не является удобным. Для примера, если
объявлено новое поле age в сообщении Human, то в коде программы придется делать
следующий вызов:
«human.setExtension(age, 22)». Очевидно, что это не является
наследованием и возможности его реализовать нет.
Решение от Google представляет собой достаточно ёмкий протокол, который удобен
для многих программистов, но его возможности не столь широки, как хотелось бы.
Однако основной алгоритм кодирования может быть перенят, поскольку прост, но
продуктивен.
16
Kryo
По своей сути является упрощенной версией protobuf: не имеет поддержку большого
числа языков, совместима только с java, поэтому размер передаваемой информации
немного уменьшен за счёт отсутствия необходимости в дальнейшем иметь совместимость
с другими языками.
В этом решении практически отсутствует абстракция: программист должен сам
указывать, какие классы нужно сериализовать, должен регистрировать классы и делать
прочие ненужные действия.
Protostuff
Технология, которая расширяет возможности protobuf. Позволяет производить
кодирование в такие форматы: binary, protobuf, json, xml, а также несколько популярных
форматов, основанных на xml и json.
В этом решении реализована поддержка только одного языка: Java. Поэтому protostuff
умеет сериализовать plain old java object и объекты из ejb. Это неоспоримо является
плюсом, поскольку разработчик может не заботиться о сериализации объектов и просто
заменить стандартный механизм Java.
Avro
Имеет реализацию для трёх языков: Java, C, C++. Ключевой момент данного решения
заключается в хранении схем для структур. Благодаря ограничениям на языки,
используется динамическая типизация, нет необходимости генерировать классы. Это
также позволяет решить проблему версионирования: если поступил другой вариант
кодирования, то также будет передана схема, с помощью которой производилась
сериализация. Имея две схемы – старую и новую – avro может попытаться провести
между ними соответствия, тем самым поддержав новую версию.
В этом решении присутствуют некоторые оптимизации, которые позволяю не
передавать схему при каждом сообщении, а только при некоторых из них, то есть
сэкономить трафик.
Сравнение
Для сравнения всех технологий были выбраны те характеристики, которые можно
обобщить на несколько решений и которые являются важными при выборе технологии
для использования её в проекте. Далее приведены характеристики:
17

Мультиплатформенность – возможность получить решение для нескольких
языков и платформ

Предлагает ли технология решение для удаленного вызова процедур или же
является только решением для сериализации

Наличие у технологии комьюнити – это влияет на то, как легко решится любой
возникший вопрос по использованию и внедрению технологии

Эффективное кодирование данных

Простота использования и интеграции
Далее в таблице (табл.1) приведены результаты сравнения всех представленных выше
технологий.
Мультиплатформенность
RPC
Community
Эффективное
хранение
Простота
XML-RPC
+
+
+
-
+
SOAP
+
+
+
-
-
JSON-RPC
+
+
+
-
+
.Net remoting
-
+
+
+
+
RMI
-
+
+
+
+
Corba
+
+
+
+
-
GWT-RPC
-
+
+
+
+
ONC-RPC
+
+
-
+
-
DCE/RPC
+
+
-
+
+
Routix
+
+
-
-
+
ZeroC
+
+
-
+
+
Burlap
+
+
-
-
+
Hessian
+
+
-
+
+
ASN.1
+
-
+
+
-
Etch
+
+
-
+
+
Thrift
+
-
+
+
+
Protobuf
+
-
+
+
+
Kryo
-
-
-
+
+
Protostuff
+
-
-
+
+
Avro
-
-
-
+
+
MyRPC
+
+
+
+
+
Таблица 1. Сравнение существующих технологий
18
Выводы из обзора
В
данный
момент
реализовано
множество
решений:
как
кодирования
структурированной информации, так и удаленного вызова процедур. Данный обзор
позволяет сформулировать требования к реализации, набору возможностей, дизайну
сообщений и сервисов.
Как было видно из обзора, синтаксис языка для сообщений и сервисов должен быть Cподобным, чтобы быть легковоспринимаемым для разработчика. Требуется возможность
делать иерархии объектов. Наличие исключений также обязательно.
Возможности технологии не должны быть слишком обширными, чтобы не получилась
тяжеловесная система. Со стороны проекта существует требование, чтобы решение было
максимально простым и понятным для стороннего разработчика.
Также требуется высокий уровень абстракции, вся низкоуровневая работа должна быть
скрыта от разработчика. Разработчик должен иметь возможность задавать иерархию
объектов и не задумываться о технических деталях. Сервисы должны быть такими, чтобы
внешне вызов метода выглядел как вызов локального метода.
19
Глава 2. Алгоритм работы
Основными характеристиками производительности RPC является скорость сжатия и
качество сжатия. Для достижения лучших результатов были проделаны некоторые
оптимизации, которые позволили уменьшить объём потребляемого трафика: объединение
запросов, использование идентификаторов, жесткая структура сообщений, пулы объектов,
оптимальное создание объектов.
Чаще всего клиент реализован так, что делает разнообразные запросы к серверу
достаточно часто. Поэтому возможна некоторая оптимизация, которая позволит сократить
количество физических запросов к серверу. Для этого необходимо объединять запросы в
один и делать отправку на сервер. Сервер вернет ответ сразу на все запросы, а клиент
далее вызовет необходимые методы соответствующие каждому запросу. Данная
оптимизация позволяет не делать вызовы по несколько раз в секунду, а делать, например,
один вызов в секунду.
Использование метапрограммирования, а конкретно системы MPS, позволяет
сохранять и получать идентификаторы узлов сущностей, узлов сервисов, узлов методов.
Поэтому правильным решением является идентифицировать метод сервиса не по его
названию или порядковому номеру, а по идентификатору узла этого метода. Это даёт
возможность переименовывать методы, менять их порядок, сигнатуру, при этом всё будет
работать точно также. Кроме того, это дает возможность называть методы одинаково и
определять различные типы или наборы параметров, поддерживать полиморфизмперегрузку.
В
обзоре
литературы
было
указано
несколько
методов
кодирования
структурированной информации. Основой для алгоритма кодирования был выбран
protobuf, поскольку он предлагает простую реализацию и хорошие результаты работы.
Сжатие чисел происходит посредством отсечения ненужных септетов. Далее информация
приводится в base64 вариант, чтобы иметь возможность передавать работать с вебклиентами. Существовал вариант сделать несколько вариантов кодирования, но на данном
этапе было принято решение ограничиться одним, поскольку в противном случае клиенту
и серверу пришлось бы обмениваться дополнительной информацией, хранящей в себе
необходимый формат.
Данное ограничение увеличивают размер по сравнению с protobuf, однако были
произведены улучшения, которые помогли улучшить сжатие. Был произведен отказ от
хранения номеров полей, что позволило добиться лучшего сжатия. Это стало возможно
благодаря тому, что нет требований к такой жесткой поддержке версионирования. Если
20
поле удалено из сообщения, то клиент, который несет в себе старую версию сообщений,
стал нерабочим в силу того, что не может выполнить какую-то функциональность, т.к.
получает от сервера или передает ему недостаточную информацию. Также это позволяет
добиться некоторого ускорения: при раскодировании нет
нужды определять, какое
именно поле раскодируется, его тип и прочее, всё это известно заранее и может быть сразу
же сгенерировано.
Однако все методы кодирования никак не основываются на знаниях о том, какие
данные передаются в сообщениях. Например, при передаче объединенных сообщений,
скорее всего, будут похожие запросы. При написании веб-сервиса почти всегда
присутствует база данных на сервере, а значит, присутствуют идентификаторы
сущностей. Также было указано, что присутствуют идентификаторы методов.
В данной работе была использована оптимизация, которая не может быть применена
на любых структурных данных, но даёт заметный выигрыш при использовании
описанных выше данных: имеющих одинаковые идентификаторы. Было решено, что
программист может определить каждому полю в сообщении дескриптор “usepool”,
описание которого дано дальше.
В язык были включены пулы объектов. Данный алгоритм работает следующим
образом: при кодировании сообщения обработчик кладёт объект в пул и заменяет его в
иерархии на порядковый идентификатор в пуле. Если объект уже находится там, то
второй раз он не добавляется туда. Данный алгоритм применяется только для базовых
типов, поскольку не требует больше никаких действий по кодированию структур.
Если в пуле уже много объектов, то новые идентификаторы будут большими, поэтому
существует вариант создать несколько пулов. Было проведено тестирование и определено,
что создание нового пула не дает существенного уменьшения объема информации.
Однако поддержка нескольких пулов менее понятна для разработчика и тратит больше
процессорного времени. Поэтому было решено отказаться от поддержки возможности
иметь несколько пулов объектов и ограничиться одним.
Ниже
(табл.2)
приведены
результаты
замеров
времени
кодирования
результирующего объема сообщений.
Время на кодирование
Итоговый объём
Без пулов
152034 нс
1467482 байта
С одним пулом
176403 нс
1096588 байта
С несколькими пулами
193698 нс
1081232 байта
Таблица 2. Результаты тестирования трех вариантов кодирования
21
и
Тестирование производилось следующим образом: было выбрано около сотни
реальных моделей данных различных размеров: от 1кб до 10мб (размеры в кодировании
без пулов). Далее для каждого варианта была запущена 100000 раз цепочка кодированиедекодирование, взято среднее значение. После чего тестирование было произведено 500
раз и взято среднее значение.
Из таблицы видно, что итоговый объём без применения пулов уменьшился
приблизительно на 26%, замедление же составило порядка 11%. Таким образом, данный
алгоритм является выгодным. Применение же нескольких пулов практически не дало
уменьшения объёма сообщений, вызвав при этом замедление и усложнение синтаксиса
языка.
Технология protobuf использует паттерн Builder для создания сообщений. Это заметно
замедляет время создания объектов, поскольку требует выполнение дополнительного
кода. Поэтому в данной работе создание объектов происходит в стиле POJO, что ускоряет
создание примерно в два раза, тем самым повышая скорость десиарилизации.
Из этой части работы становится видно, что алгоритм сжатия является оптимальным
при условии осведомленности о кодируемых данных. Так как за основу был выбран
protobuf, то кодирование производится быстро и качественно, не уступает другим
алгоритмам по скорости и качеству сжатия: существует открытый проект, в рамках
которого производится сравнение существующих сжимающих алгоритмов [24].
22
Глава 3. Реализация
Разработка данной технологии проводилась в несколько этапов:
1. Описание дизайна языка асинхронных вызовов, расширяющих язык Java
2. Реализация этого языка в стиле try-catch
3. Осознание необходимости добавления в этот язык блока finally, реализация
4. Представление дизайна IDL для сообщений
5. Создание языка для сообщений
6. Поддержка удобства редактирования: проверка корректности кода, реализованы
доступные действия для пользователя
7. Реализован генератор в язык Java с простейшим кодированием в JSON
8. Произведены исследования по оптимизациям данного кодирования в рамках
JSON: пулы объектов, массивы без разделителей (так как известно как
разбирать каждое сообщение в массиве), другие мелкие оптимизации
9. Составление дизайна языка для сервисов так, чтобы он был максимально
простым. На данном этапе был произведен отказ от удаленных объектов и были
выбраны именно сервисы
10. Реализован генератор сервисов в язык Java
11. Реализован первый тестовый сервер, который позволял проводить исследования
12. Был создан дизайн для исключений и произведена их реализация на сервере и
клиенте
13. Далее были реализованы генераторы в язык Objective C [25], что позволило
проверить корректность выбора дизайна
14. Введена
возможность
создавать
абстрактные
сервисы,
поддержано
наследование
15. Реализовано наследование сообщений: возможность наследовать поля, при этом
при указании типа поля желательно указывать точный тип, а не тип предка: это
позволит добиться ускорения кодирования и уменьшения объема сообщений
16. Произведена работа по реализации другого кодирования
17. Реализована возможность создавать события, тем самым уменьшая нагрузку на
сервер и на сеть: не нужно делать однотипные запросы, чтобы узнать изменения
системы
18. Проведение тестирования качества реализованной технологии
19. Интегрирование с проектом производилось по всему хожу работы
23
Выбор платформы
Было решено, что данная технология должна быть реализовано с помощью
метапрограммирования. Это позволяет вынести множество стереотипного кода в
отдельные абстрактные функции, избавляет от необходимости писать один и тот же код
по несколько раз, просто задав правила генерации. Таким образом, программа становится
понятной и легкосопровождаемой, изменение части генерации требует лишь замены
небольшого фрагмента генератора. Кроме того, практически всегда существует среда
разработки для метапрораммирования, а это означает, что возможно динамически давать
программисту подсказки, проверять правильность кода, создавать подсветку для
синтаксиса, а так же пользоваться другими возможностями среды. Для рассмотренных
реализаций RPC, которые поддерживают сразу несколько языков программирования, нет
среды для разработки.
Именно по этим причинам был произведен отказ от написания на Java парсеров для
IDL, а реализация производилась с помощью метапрограммирования. Платформой для
этого был выбран MPS (Meta Programming System), которая была реализована JetBrains.
Данная система позволяет:

Описывать концепты и их взаимосвязь

Описывать редакторы для них

Поддерживать систему типов

Поддерживать проверку правильности кода

Автоматически поддерживать подсказки

Создавать «горячие клавиши» для различных действий пользователя

Реализовывать отображения из одного языка в другой, т.е. из описанного нами
языка создавать генераторы в другие
Кроме того основной проект, для которого требовалась система удаленного вызова
процедур, большей частью был реализован на MPS, поскольку это помогает расширять
стандартные механизмы языка Java: расширенные возможности коллекций, closure,
расширения типов, более удобные тесты, возможность создавать другие расширения. При
этом остается полная интеграция с языком Java: можно подключать сторонние
библиотеки, использовать сторонний код, генерировать обычные java-классы и собирать
их в библиотеки.
Разработка MPS была начала в 2003 году и сейчас работа продолжается. Однако
данная технология уже стала применима в жизни. При работе с этой системой достаточно
часто возникают различные трудности: некорректный разбор какого-либо случая,
24
замедление работы при определенных ситуациях, а также другие мелкие проблемы.
Однако они все решаются в кратчайшие сроки, так как есть система обратной связи и есть
возможность зайти на трекер и добавить новую ошибку или пожелание к системе.
Система находится в состоянии разработки и не существует доступной и полной
документации, так как набор функций постоянно меняется, производятся рефакторинги и
прочие изменения. Поэтому к изучению данной системы приходится подходить
основательно, практически невозможно найти готовое решение в точности поставленной
задачи. Таким образом, написание языка требует изучения других реализованных языков,
разборе исходных кодов, высокого уровня понимания алгоритмов работы MPS, а также
понимание всех концепций метапрограммирования. Чтобы приступить к разработке
серьёзного языка, требуется получить необходимый опыт, поскольку невозможно сразу
же понять всех возможностей языка и всех альтернатив реализаций.
Некоторые разработчики сводятся к мнению, что программисту нужно строго знать,
что и как делается, предоставление альтернатив мешает разработке кода. Однако в ходе
работы с системой MPS было выяснено, что наличие различных подходов к решению
одной и той же задачи позволяет лучше понять задачу и выбрать самое оптимальное
решение для именно той задачи, которая стоит, а не для задачи похожей на шаблонную.
Также разработка с помощью метапрограммирование вынуждает изучать существующие
языки, синтаксисы, дизайны, решения, тем самым заметно расширяя кругозор
разработчика и позволяя тем самым создавать наиболее оптимальные решения. Таким
образом, изначальное время на изучение и понимание всех концепций занимает гораздо
большее время, нежели изучение одного языка программирования, но скорость и качество
разработки в дальнейшем намного выше, поскольку программист практически всегда
заранее знает несколько возможных решений поставленной задачи.
Кроме того, использование данной системы не требует от программиста какой-то
дополнительной настройки: изучение параметров, которые надо передать генератору в
командной строке, написание конфигурационных файлов. Всё это сделано с помощью
пользовательского интерфейса. Также есть возможность интегрироваться с системой
контроля версий. Всё это позволяет разработчику меньше тратить времени на задачи,
которые не связаны напрямую с программированием.
Таким образом, в ходе данной работы был получен опыт разработки языков, изучения
множества других языков, а также опыт применения метапрограммирования для решения
задач, требующих абстрактность.
25
Язык асинхронных вызовов
Данный язык являлся первой частью работы, был необходим для того, чтобы иметь
возможность писать код, который делает асинхронный код. Это нужно для того, чтобы в
дальнейшем писать работу с RPC на этом языке. Так как весь проект разрабатывается на
MPS, то данный язык может быть применен для написания всех частей приложения,
которые в дальнейшем будут сгенерированы в язык Java. На других же языках данное
решение не применимо, поскольку разработка на других языках ведется не в MPS, а в
среде данного языка.
Ниже приведен пример (лист.6) асинхронного блока в этом языке.
async (Human human = myService.getHuman()) {
...
succeed m || failwith new RuntimeException(...)
} handle (Throwable t) {
...
} finally {
...
}
Листинг 6. Пример асинхронного блока
Кроме создания асинхронных блоков, в языке также имеется возможность создавать
асинхронные методы, для этого в начале метода будет записан дескриптор «async».
Внутри асинхронного метода можно указать конструкцию «forward», которая передаст
управление другому асинхронному методу, при этом метод должен возвращать точно
такое же значение. Кроме того если асинхронный вызов делается внутри асинхронной
конструкции, то можно не писать блок обработки ошибок. Блок finally является
необязательным. В любой момент времени внутри асинхронного блока можно написать
«failwith» или «succeed» с ошибкой или с результатом соответственно. Синтаксис asynchandle-finally блоков похож на синтаксис try-catch-finally, с «failwith» и «succeed» вместо
«throw» и «return».
Генерация происходит в анонимный класс, у которого есть два метода onSuccess и
onFailure. Также для каждого метода finally создается класс, который умеет выполнять код
из соответствующего блока, при этом блок может быть выполнен лишь единожды. Это
нужно по той причине, что вызов метода этого класса происходит в нескольких местах:
после удачного завершения, после неудачного завершения или после ошибки во время
вызова метода.
Блок finally был добавлен в язык с целью наличия возможности отследить, когда
закончены все запросы к серверу, чтобы выполнить какой-то код, то есть нужно было
26
выполнять некоторые действия независимо от того, успешно ли выполнен async-блок или
нет. Это вызвало бы дублирование кода при отсутствии finally-блока.
Язык сообщений
В одной из предыдущих глав было разобрано множество IDL для сообщений,
проанализировав все их, был выбран дизайн для собственного языка. Для сообщений
создается контейнер, это выполнено для удобства: сообщения занимают небольшой объем
и было бы странно хранить каждое сообщение в отдельном файле.
В примере (лист.7) указан контейнер сообщений, перечисление в нём, а также одно
сообщение.
messages MyMessages {
enum AccessLevel {
READ,
WRITE,
ADMIN
}
message Resource {
int id;
AccessLevel level;
array<User> editors;
}
...
}
Листинг 7. Пример контейнера сообщений
Изначально в языке присутствовала конструкция oneOf,
которая позволяла явно
задать типы, которые могут быть в данном сообщении. Это единственное место, где
нужно также передавать тип. Поскольку в такой структуре могло быть перечислено много
значений, то размещение oneOf перед названием поля приводило к тому, что поле
становилось нечитаемым, поэтому в первой версии был выбран синтаксис указанный в
примере (лист. 8).
message
field
field
field
field
}
Resource {
id : int;
AccessLevel level;
editors : array(User);
content : oneOf(Text, Sound, Video);
Листинг 8. Пример сообщения в старом стиле
27
Этот синтаксис выполнен в стиле языка Pascal. Это было необходимо на тот момент.
Однако проанализировав другие IDL, были выделены особенности, которые ухудшают
восприятие синтаксиса, также выделены те, которые улучшают. Основным критерием
является C-подобность. Именно поэтому было необходимо избавиться от конструкции
oneOf, а также от самого принципа ручного перечисления всех типов, так как это
загромождает язык и делает его менее удобным, поскольку разработчик должен при
каждом добавлении нового типа проследить все места, где может упоминаться этот тип.
Решением данной задачи является наследование. Оно позволяет избавиться от
перечисления типов. При генерации кода происходит анализ зависимостей классов –
концепт oneOf заполняется автоматически. Если в oneOf содержится всего 1 возможный
класс, то тип сообщения не передается, что сокращает размер передаваемой информации.
Язык сообщений поддерживает не только создание сообщений с некоторым корнем, но
и использование их в коде программы на MPS. Для этого была произведена работа по
поддержке выведения типов сообщений, их полей и массивов. Это объёмная и сложная
часть, поскольку требует высокого уровня аккуратности и понимания.
Также был реализован паттерн Builder, который позволяет создавать сообщения в
программе, используя укорочены код. Данное решение повсеместно используется в
языках, которые реализованы в MPS. Ниже (лист.9) приведен пример того, как можно
создать новое сообщение.
Resource res = new Resource {
id : 12;
level : AccessLevel.READ;
result.editors.add(...);
}
Листинг 9. Пример создания сообщения
К данному языку сначала был реализован генератор в язык Java. Кроме сериализации и
десериализации
было
необходимо
написать
часть,
которая
делает
сообщения
совместимыми с обычным языком Java, то есть нужно было все нахождения сообщений в
коде программ заменить на Java-классы, переопределить все instanseOf, equals и hash, так
как стандартные выдавали бы неверные результаты.
Язык сервисов
Как говорилось ранее, для простоты использования было решено отказаться от
возможности иметь удаленные объекты: так как это вынудило бы реализовывать сложное
управление памятью в языках без автоматического управления памятью. Кроме того, это
28
усложнило архитектуру, уменьшило бы скорость. Также это сделало бы систему более
тяжеловесной и сложной для понимания.
Язык для сервисов определяет некоторый набор методов, которые может вызвать
клиент и не заботиться о том, как происходит этот вызов. Из описания сервиса должно
генерироваться три основных сущности:

Абстрактный stub-класс сервера, который содержит в себе перечисление всех
методов, а также неабстрактный метод, который умеет обработать входящий
запрос, разобрать его и выдать ответ, то есть из входящего потока данных
создать исходящий.

Stub-класс клиента, который поддерживает синхронное выполнение запросов.

Stub-класс клиента, который поддерживает асинхронное выполнение запросов.
Очевидно, что нельзя объединять в один класс два последних: хочется изначально при
создании instance класса указать его тип: синхронный или нет. Для этого в язык была
введена дополнительная конструкция, которая отображает возле типа клиентского сервиса
данную информацию.
Кроме того были описаны концепты «реализации сервиса», то есть реализация сервиса
не на уровне Java, а на уровне MPS, то есть с полной системой наследования, полей и
прочих атрибутов стандартных классов. Это позволяет добавлять методы в RPC, которые
будут реализованы с помощью своего языка, также это избавляет от необходимости
переделывать код имплементаций при рефакторинге интерфейса сервиса.
Ниже (лист.10) приведен пример простого сервиса.
service myService {
AccessLevel getAccessLevel(User user);
}
Листинг 10. Пример сервиса
Язык поддерживает вызов методов без результата. По сути, является описанием
интерфейса.
Исключения
Первая версия языка не включала в себя исключения, но при проведении первой
интеграции с проектом было выявлена необходимость перехватывать и передавать
исключения. Например, такое необходимо при недостаточных правах пользователях или
при других предусмотренных заранее случаях. В непредусмотренных же случаях должен
быть передан ServerException.
29
Для поддержки исключений потребовалось расширить текущее решение. Самым
простым вариантом являлось бы добавление исключений в язык сообщений, так как это
упростило бы генерацию. Однако исключения являются лишь расширением сообщений и
никак не на одном уровне. Если использовать сообщения с другими целями (например,
хранение данных), то исключений там быть не должно. Поэтому исключения входят в
язык сервисов и расширяют сообщения.
Очевидно, что у метода бывает два типа исключений: ожидаемые и неожидаемые.
Например, отсутствие записи на сервере является неожидаемым, а ошибка при
регистрации – ожидаемая. Поэтому в описание сервиса было внесено два основных
изменения:

В заголовке сервиса задается узел, в котором содержатся все возможные
неожидаемые исключения – это нужно для того, чтобы не включать все
возможные существующие исключения, а также для того, чтобы можно было
описать в одном пакете два различных сервиса с разными исключениями.

В заголовке метода задается конструкция “throws” на подобии исключений в
языке Java. Таким образом, можно явно указать исключения, которые могут
быть брошены данным методом.
Ниже (лист.11) приведен пример создания сервиса с подключением контейнера
исключений, а также пример метода, с объявлением бросаемого им исключения.
service myService throws exceptions from MyExceptions {
AccessLevel getAccessLevel(User user) throws UserExeption;
}
Листинг 11. Пример сервиса с исключениями
Кроме этого существует ServerException, который имеет только идентификатор: при
неизвестной ошибке на сервере клиенту будет передано это исключение. Если на сервере
реализована возможность логирования ошибок (запись в базу с id), то в исключении будет
номер исключения в базе данных: это нужно для того, чтобы пользователь мог послать
серверу отчёт о случившейся ошибке, добавив описание того, что он делал. Также будет
добавлено состояние клиентского приложения в момент возникновения ошибка. Таким
образом, разработчик может увидеть ошибку, её описание и два стектрейса: серверный и
клиентский, это помогает править недоработки в кратчайшие сроки.
Исключение должно генерироваться сразу же в два класса: сообщение и обычное
исключение Java: первое для передачи, второе для использования в коде. Это вызвало
30
некоторые сложности, поскольку стандартно предполагается, что каждый концепт
генерируется ровно в одну сущность. Однако для данной задачи было найдено решение.
Также нужно было реализовать возможность написать «throw myException», то есть
использовать
стандартные
конструкции
для
исключений
применительно
к
реализованным. Для этого потребовалось написать несколько генераторов, которые
заменяют данные конструкции на эквивалентные, но с оборачиванием ошибки в
сгенерированный класс исключения. Кроме того нужно было переопределить и
конструкцию catch, поскольку иначе ошибка никогда не будет поймана.
После замены расширения стандартных конструкций возникла сложность в том, что в
блоке catch перехватывается исключение одного типа, а внутри блока идёт работа не и с
исключением, а с сообщением, которое обернуто в это исключение. Поэтому
потребовалось написать генератор, который делает замену всех операций с исходным
исключением, для примера назовём его myException, на myException.getMessage(). То есть
происходит переопределение всех local variable reference на это исключение.
Также при введении исключений потребовалось реализовать клиентские исключения.
Исключения протокола должны оставаться на уровне протокола и небольшой обёртки,
которая им управляет, потому что при смене средства передачи данных, например, при
переходе на файловую систему или соединении с уже готовым решением сторонних
разработчиков, набор исключений практически останется неизменным: отсутствие
доступа, отсутствие записи, отсутствие прав администратора и прочее. Таким образом,
исключения уровня RPC могут быть заменены на другие, но это практически не должно
влиять на клиент. Поэтому был написан небольшой класс, который умеет переделывать
исключения уровня протокола в клиентские исключения. По своей сути они дублируются,
но если произойдёт замена на другую систему взаимодействия, то потребуется лишь
заменить код в одном месте на уровне ядра клиентского приложения, сам же клиент будет
не тронут.
Исключения внесли возможность писать код более удобно и возможность адекватно
обрабатывать их на клиенте, кроме того помогли добиться большей стабильности за счёт
увеличения количества catch-блоков, где их не было до этого, но где могли падать
исключения, которые не были бы перехвачены.
Генерация в Objective C
Генерация в другой язык позволяет добиться большего уровня абстракции и не
завязываться на парадигмах определенного языка. Objective C был выбран потому, что
31
существует большое количество мобильных устройств на платформе iOS, рынок же этой
платформы сильно развит.
Язык Objective C был разработан в 1986, поэтому его синтаксис кажется странным для
разработчика, который пишет на C# или Java. В платформе iOS отсутствует сборщик
мусора, поэтому реализация генератора для ObjC является более трудоёмким, нежели для
Java.
Также в данном языке отсутствуют привычные шаблоны из C++ или дженерики из C#
и Java. Поэтому это вызывает особые неудобства при использовании коллекций, так как
требует приведение типов в некоторых случаях.
Задача реализации генератора в Objective C началась с разработки паттерна, который
бы позволил генерировать привычные для языка Java перечисления, поскольку они
отсутствуют как таковые.
Было решено генерировать класс, который имеет статические методы с названиями
соответствующих значений. При вызове метода возвращается инстанс этого класса,
который создается один раз для каждого элемента перечисления, содержит в себе имя
значения, а также порядковый номер. Таким образом, получение какого-то значения из
enum сводится к такому вызову: [MyEnum VALUE]. То есть это выглядит практически так
же, как и в других языках, где есть enum и позволяет делать сравнение обычным
равенством, а не вызовом метода isEquals.
Следующей задачей было найти решение, как делать асинхронные вызовы. Так как
разработка на Objective C ведется не в MPS, а в XCode [26] или в новом AppCode [27], то
это накладывает ограничение: невозможно написать своё решение языка асинхронных
вызовов для Objetive C. Таким образом, нужно было придумать способ позволить
программисту вызвать асинхронные методы и делать это удобно. Самым очевидным
способом было бы создание класса, у которого есть 2 метода: onSuccess и onFailure. Но не
так давно в язык Objective C были введены блоки. Другое общепринятое название для них
– closure. Можно передавать их в качестве параметров асинхронному методу. Когда
выполнение обращения к серверу будет завершено, будет вызван блок соответствующий
success и передан в него результат.
- (void) getUserUsingSuccessBlock: (void(^)(User*))
successBlock onFailBlock: (void(^)(NSException* e)) failBlock
Листинг 12. Пример асинхронного вызова метода
32
В данном примере приведено описание метода, который получит пользователя. Язык
Objective C является избыточным, так как вместо оператора «точка» используются
квадратные скобки, также потому, что при вызове метода нужно указать названия
параметров.
На
самом
деле,
названием
данного
метода
является
не
«getUserUsingSuccessBlock», а более длинное: «getUserUsingSuccessBlock:onFailBlock», то
есть включаются имена всех параметров.
На текущий момент блока finally в реализации нет, поскольку он был не нужен. Но
решение данной задачи является сложным, так как нет генерации кода и разработчик
должен будет сам поддерживать большую часть работы finally-блока.
Кроме паттерна для enum в ходе данной работы был придуман паттерн для создания
анонимных классов по интерфейсу, так как в языке они не поддержаны. Если нужно более
чем в одном месте определять анонимные классы, то программист может создать класс,
который имплементирует нужный интерфейс, а в его конструктор передавать блоки с
нужными методами. Таким образом, получится, что код будет полностью задаваться там,
где создается экземпляр класса, то есть это будет почти полностью анонимный класс с
некоторыми ограничениями.
Разработка на языке Objective C являлась трудоёмкой, поскольку в среде разработки
XCode нет удобных средств для навигации по коду и его рефакторингу. Также синтаксис
не является привычным. В ходе разработки генератора были изучены многие тонкости
языка Objective C, исследована система управления памятью и создана оптимальная по
скорости и потреблению оперативной памяти система RPC и сообщений.
События
В данный момент существуют серверы [28], которые позволяют создавать cometсоединения [29]. То есть такие, что сервер, может отправлять (push) данные клиенту. Это
похоже на сокеты, но в данной модели важно, что сервер может посылать уведомления,
клиент же не может.
Таким образом, клиент может соединиться с сервером и получать от него уведомления
о произошедших событиях. Решение поддержки такой возможности было принято уже
после разработки основных возможностей RPC и показывает, что использование
метапрограммирования и своей реализации системы удаленного вызова процедур,
позволяет сколь угодно расширять решение и получать результаты, которые не
реализованы ещё нигде или реализованы, но не применимы. Например, comet-соединение
поддержано в GWT-RPC, то есть только для Java.
33
События расширили существующее RPC. Для этого потребовалось добавить
конструкцию, которая позволяла повестить листенер не некоторое событие, а также
посылать эти события с сервера на клиент.
Ниже (лист.13) указан пример того, как в сервисе выглядит описание добавления
слушателя на некоторый объект.
add listener (UserListener listener) for (User user);
Листинг 13. Пример добавление слушателя на событие
Также потребовалось описать язык listeners, который содержит интерфейсы
листенеров. Он практически эквивалентен языку сервисов с некоторыми уточнениями.
Реализация данной системы помогла добиться уменьшения потребления трафика за
счёт отсутствия запросов на сервер. Также она помогла уменьшить нагрузку на сервер, так
как позволила избавиться от запросов состояние системы, которые обращаются к базе
данных при выполнении: обращения на считывание информации большей частью теперь
происходят при создании соединения (первом или переподключении), изменение же
состояния системы приходит в событиях.
Тестирование
Тестирование проводилось на реальных данных, для этого был взят некоторый набор
стандартных операций, которые делают пользователи, и запущены в несколько потоков.
Это позволило убедиться в скорости работы, а также выявить некоторые недочеты в
системе.
Остальная же часть тестирования проводилась в рамках всего проекта, то есть
функционально. Поэтому данное решение является стабильным, количество возможных
ошибок сведено к минимуму. Кроме того было проведено тестирование удобства дизайна
языка: нескольким разработчиком было предложено воспользоваться языком для сервисов
и выявить основные недостатки и предложения, также им было предложено рассмотреть
языки других существующих решений. Данный опыт выявил отсутствие особых
замечаний к дизайну.
Дополнительно было реализовано сравнение производительности и сжимающих
способностей основных алгоритмов, которые были рассмотрены в обзоре литературы.
34
Глава 4. Внедрение
В ходе всей разработки проводилась интеграция с реальным проектом. Таким образом,
как только появилась первая версия RPC, которая могла просто передавать сообщения с
клиента на сервер, была начата интеграция. Требования к изменению данной технологии
происходили из объективных критериев, которые появлялись в ходе использования RPC.
То есть решение было улучшено с момента первого запуска.
Данный
подход
к
разработке
является
оптимальным,
поскольку разработка
абстрактного решения может углубиться в функционал, который вовсе не нужен
потребителю, при этом, не поставляя нужный. По этим причинам требования к задаче
часто менялись: одним из важных решений был отказ от методов в объектах и реализация
сервисов: это делает использование проще и избавляет разработчика от необходимой
работы по управлению памятью.
Проект является высоконагруженной клиент-серверной системой. Уже на момент
тестирования имеется передача порядка 10 гигабайтов запросов к серверу в день. Таким
образом, производительность RPC важна для проекта. Неоднократно был использован
профилятор для выявления узких мест системы, были проведены работы по их
устранению.
Также внедрение позволило выявить некоторые утечки в памяти системы на языке
Objective C. Поэтому были использование инструменты для выявления утечек памяти.
Данные инструменты поставляются компанией Apple и часто являются непривычными
для разработчика на языке Java или C#. К концу разработки абсолютно все утечки памяти
были устранены.
35
Заключение
В рамках данной работы были достигнуты следующие основные результаты:

Изучен язык Objective C

Создан дизайн и реализация собственных языков на платформе MPS

Изучены существующие подходы в реализации RPC

Реализована система удаленного вызова процедур

Придумана и реализована система сжатия

Проведено тестирование результатов работы
Результирующим продуктом данного диплома является реализованная и внедренная
технология удаленного вызова процедур. Разработка вышла за рамки изначально
поставленной задачи: была также реализована система событий, что позволило заметно
ослабить нагрузку на сервер.
Основной целью являлось реализовать систему, которая сможет быть пригодной для
использования на разных платформах и разных языках, а также будет лучше по
некоторым параметрам, чем существующие решения. Одним из таких параметров
является простота: решение удобно и просто использовать. Другой критерий –
функциональность: в языке присутствуют события, такого нет в аналогичных системах.
Ещё одним параметром является степень сжатия, при этом производительность не должна
сильно ухудшиться. Были произведены замеры сжатия данного RPC и других популярных
кодирующих систем. Стоит учесть, что оптимизация сжатия производилась не основе
предположения о существовании идентификаторов сущностей, а также идентификаторов
вызываемых методов. Кроме того полагалось, что передаваемые данные в среднем
достаточно велики, а не ограничены 100 байтами. Поэтому тестирование проводилось на
реальных данных и результаты являются среднестатистическими.
Ниже указана диаграмма (д-ма.1), в которой приведены результаты сравнения степени
сжатия сообщений различными решениями, а также диаграмма (д-ма.2), в которой указана
скорость сжатия этих сообщений. Технология, разработанная в рамках данного диплома,
носит название myRPC в этих диаграммах.
36
Размер сообщения в кБайтах
0
100
200
300
400
myRPC
500
600
700
800
900
1000
421,7
kryo
492.2
avro
501.1
protobuf
523
thrift
537.3
jackson
831.3
xml/xstream
922
Диаграмма 1. Сравнение степени сжатия
Время работы в мс
0
kryo
protobuf
thrift
myRPC
jackson
avro
50
100
150
200
250
34
44
47
54
63
82
xml/xstream
216
Диаграмма 2. Сравнение скорости сжатия
Данные диаграммы показывают, что сжатие улучшено на порядок для данных
моделей, при этом время на сжатие и распаковку отличается от других решений не столь
значительно. При этом kryo использует всевозможные оптимизации применимые
конкретно к Java, что невозможно в общем случае.
В дальнейшем данная технология будет расширена на другие платформы и языки,
будут расширены её возможности. Уже сейчас видно, что данное решение удобно в
использовании, предлагает дополнительный функционал, является быстрым и сжимает
лучше других существующих решений.
37
Литература
[1]
Sergey Dmitriev, Language Oriented Programming: The Next Programming Paradigm, 2004
http://www.jetbrains.com/mps/docs/Language_Oriented_Programming.pdf
[2]
Среда разработки JetBrains MPS
http://confluence.jetbrains.net/display/MPS/Welcome+to+JetBrains+MPS+Space
[3]
Спецификация XML-RPC
http://www.xmlrpc.com/spec
[4]
Спецификация SOAP
http://www.w3.org/TR/soap/
[5]
Официальный портал JSON-RPC
http://json-rpc.org/
[6]
Accessing Objects in Other Application Domains Using .NET Remoting
http://msdn.microsoft.com/en-us/library/72x4h507%28v=VS.71%29.aspx
[7]
Официальный портал Remote Method Invocation
http://www.oracle.com/technetwork/java/javase/tech/index-jsp-136424.html
[8]
Официальный портал Corba
http://www.corba.org/
[9]
Документация по GWT-RPC
http://code.google.com/intl/ru-RU/webtoolkit/doc/latest/tutorial/RPC.html
[10] Спецификация ONC-RPC версии 2
http://tools.ietf.org/html/rfc5531
[11] Официальный портал OpenDCE
http://www.opengroup.org/dce/
[12] Портал DCE/RPC поставляемой Apple
http://www.dcerpc.org/
[13] Официальный портал Routix.PRC
http://www.routix.net/rpc/
[14] Michi Henning, Mark Spruiell, Distributed Programming with Ice
http://www.zeroc.com/doc/Ice-3.4.1-IceTouch/manual/index.html
[15] Спецификация Burlap
http://hessian.caucho.com/doc/burlap-1.0-spec.xtp
[16] Спецификация Hessian
http://hessian.caucho.com/doc/hessian-1.0-spec.xtp
38
[17] Specification of Packed Encoding Rules
http://www.itu.int/ITU-T/studygroups/com17/languages/X.691-0207.pdf
[18] Apache Etch Documentation
https://cwiki.apache.org/ETCH/documentation.html
[19] Thrift documentation
http://wiki.apache.org/thrift/
[20] Protobuf documentation
http://code.google.com/intl/ru-RU/apis/protocolbuffers/docs/overview.html
[21] Kryo homepage
http://code.google.com/p/kryo/
[22] Protostuff documentation
http://code.google.com/p/protostuff/w/list
[23] Avro documentation
http://avro.apache.org/docs/current/
[24] Comparing varius aspects of Serialization libraries on the JVM platform
http://code.google.com/p/thrift-protobuf-compare/wiki/BenchmarkingV2
[25] Learning Objective-C: A Primer
http://developer.apple.com/library/ios/#referencelibrary/GettingStarted/Learning_ObjectiveC_A_Primer/_index.html
[26] XCode official page
http://developer.apple.com/technologies/tools/features.html
[27] AppCode official page
http://www.jetbrains.com/objc/
[28] The Channel API
http://code.google.com/intl/en/appengine/docs/java/channel/
[29] Илья Кантор, Передача данных по инициативе сервера, обзор COMET
http://javascript.ru/ajax/comet/overview
39
Download