вторник, 29 мая 2012 г.

Google App Engine. Обзор хранилища данных


Среда App Engine предоставляет несколько вариантов для хранения данных:
  • App Engine Datastore предоставляет NoSQL безсхемное хранилище с языком запросов и атомарными транзакциями
  • Google Cloud SQL предоставляет реляционную SQL базу данных для приложений App Engine, основываясь на знакомой базе MySQL
  • Google Cloud Storage предоставляет сервис хранения для объектов и файлов размером до терабайтов
Python API хранилища данных
App Engine Datastore это безсхемное объектное хранилище предоставляющее надежное, масштабируемое хранилище для вашего вэб-приложения без плановых простоев, с атомарными транзакциями, высокодоступными чтением и записью, строгой согласованностью при запросах чтения и в конечном итоге всех иных запросов. Интерфейс хранилища включает в себя обширное API моделирования данных и похожий на SQL язык запросов, называемый GQL.
Вступление
Хранилище App Engine содержит объекты с данными, так называемыми сущностями. Сущность имеет одно или множество свойств — именованных значений одного из нескольких поддерживаемых типов данных: например свойство может быть строкой, числом или ссылкой на другую сущность. Каждая сущность идентифицируется по виду (типу), который определяет ее для выполнения запросов, а также уникальный ключ, который идентифицирует ее внутри этого вида.
Хранилище может выполнять множество операций в одной транзакции. По-определению транзакция не может быть успешно закончена, пока каждая из ее операций не выполнена успешно, если любая из операций прервана, следует автоматический откат транзакции. Это особенно полезно при распределенных приложениях где множество пользователей могут одновременно получать доступ или манипулировать одними данными в один момент времени.
Репозитарий данных
Основной репозитарий данных App Engine это High Replication Datastore (HRD), высокореплицируемое хранилище, в котором данные реплицируются во множестве центров данных с помощью системы, основанной на алгоритме Pasox. Это предоставляет высокий уровень доступности для чтения и записи. Большинство запросов в конечном счете согласованы.
Второй вариант хранилища — Master/Slave перестал поддерживаться в апреле 2012 года. Хотя Google продолжает поддержку Master/Slave хранилищ в соответствии с условиями обслуживания, рекомендуется чтобы все новые приложения использовали HRD, а уже существующие приложения использующие Master/Slave хранилища мигрировали на HRD. Только HRD поддерживается в Python 2.7.
Сравнение с традиционными базами данных
В отличие от традиционных реляционных БД, App Engine использует распределенную архитектуру для автоматического управления масштабированием до очень больших наборов данных. И несмотря на то, что интерфейс хранилища имеет множество похожих на традиционные БД возможностей он отличается от них тем, как описываются связи между объектами данных. Сущности одного типа могут иметь различные свойства, а различные сущности могут иметь свойства с теми же именами но другими типами значений.
Эти уникальные характеристики предполагают иные пути для моделирования и управления данными для получения возможности автоматического масштабирования. В частности App Engine отличается от традиционных реляционных БД по следующим важным характеристикам:
- App Engine спроектирована для масштабирования, позволяющего приложениям обеспечивать высокую производительность при большом трафике:
  • запись в хранилище масштабируется автоматически распределяется по мере необходимости
  • чтение из хранилища масштабируется так как единственные поддерживаемые запросы те, чья производительность масштабируется согласно результирующему выходному набору данных (в отличие от просто набора данных). Это означает что запрос, выдающий в ответе 100 сущностей выполняется также как если бы поиск происходил по сотням или миллионам записей. Это свойство - ключевая причина, по которой не поддерживаются некоторые типы запросов.
- так как все запросы в App Engine строятся на основе предварительно построенных индексов, типы запросов, которые могут выполняться более ограничены, чем позволенные в реляционных базах данных. В частности не поддерживаются следующие типы запросов:
  • Join-операции объединения
  • фильтрация по неравенству по нескольким свойствам
  • фильтрация или данные, основанные на результатах вложенных запросов
- В отличии от традиционных реляционных баз данных, хранилище App Engine может работать с сущностями одного типа с различными не строго определенными наборами наборами свойств (хотя вы можете сами наложить эти ограничения в приложении). В настоящий момент невозможно выполнить запрос, возвращающий только подмножество результирующих свойств сущностей.

Для получения более детальной информации по архитектуре хранилища читайте нашу серию статей Mastering the Datastore.

Введение в Python Datastore API
В Python сущности хранилища создаются из объектов Python; атрибуты объекта становятся свойствами сущности. Для создания новой сущности вы вызываете базовый класс родительской сущности (если таковая имеется), устанавливает атрибуты объектов, а затем сохраняет объект (вызывая такой метод как put()).
import datetime
from google.appengine.ext import db
from google.appengine.api import users


class Employee(db.Model):
 name = db.StringProperty(required=True)
 role = db.StringProperty(required=True, choices=set(["executive", "manager", "producer"]))
 hire_date = db.DateProperty()
 new_hire_training_completed = db.BooleanProperty(indexed=False)
 account = db.UserProperty()


e = Employee(name="John",
            role="manager",
            account=users.get_current_user())
e.hire_date = datetime.datetime.now().date()
e.put()

API хранилища предоставляет два интерфейса для запросов: интерфейс объектных запросов и похожий на SQL язык запросов, называемый GQL. Запрос возвращает сущности в форме экземпляра объекта класса модели, который можно изменить и обратно поместить в хранилище:

training_registration_list = [users.User("Alfred.Smith@example.com"),
                             users.User("jharrison@example.com"),
                             users.User("budnelson@example.com")]
employees_trained = db.GqlQuery("SELECT * FROM Employee WHERE account IN :1",
                               training_registration_list)
for e in employees_trained:
 e.new_hire_training_completed = True
 db.put(e)


Сущности
Объекты в хранилище App Engine известны как сущности. Сущность имеет одно или несколько именованных свойств, каждое из которых может иметь одно или несколько значений. Значения свойств могут быть данными различного типа, включая целые числа, числа с плавающей запятой, строки, даты, бинарные данные и многие другие. Запрос по свойству с множеством значений проверяет, соответствует ли какое-нибудь из значений нужному критерию запроса. Такие свойства очень удобны при проверке совокупности значений.
Примечание: сущности хранилища безсхемные, как в традиционных реляционных БД, хранилище App Engine не требует, чтобы все сущности одного типа имели одинаковый набор свойств или чтобы все значения свойств сущности были одинакового типа данных. Если потребуется формальная схема. то само приложение ответственно за то, чтобы сущности соответствовали ей; Python SDK включает богатую библиотеку возможностей моделирования данных для этого.

Типы, ключи и идентификаторы
Каждая сущность хранилища имеет свой тип или вид, который классифицирует сущность по назначению запроса: например, приложение управления человеческими ресурсами может представлять каждого работника в компании с помощью сущности типа Employee. Также, каждая сущность имеет собственный ключ, который однозначно ее идентифицирует. Ключ состоит из следующих компонентов:
  • Тип сущности
  • Опциональный родительский путь определяющий сущность в иерархии хранилища
  • Идентификатор, который может быть:
  • ключевой строкой
  • целым числом ID
Идентификатор ассоциируется с сущностью при ее создании. Так как он является частью ключа сущности, он ассоциируется с сущностью навсегда и не может изменяться. Он может быть присвоен двумя путями:
Ваше приложение может само указать ключевую строку-значение для сущности
Вы можете определить для хранилища автоматическое присвоение идентификатору численное значение ID
Примечание: вместо именованных строковых значений или генерирования числовых значений ID автоматически, сложные приложения могут иногда присваивать собственные числовые значения ID сущностям, которые они создают. Будьте внимательны, если вы выберите этот путь, вам придется предпринять специальные меры, чтобы предотвратить возможные конфликты вручную присвоенных ID с автоматически-сгенерированными хранилищем, смотрите Сущности, свойства и Ключи для подробной информации.

Родительские пути
Сущности в хранилище формируют иерархическую структуру, похожую на структуру директорий файловой системы. При создании сущности вы опционально можете указать другую сущность как родительскую; новая сущность будет потомком родительской сущности. Эта связь между сущностью и ее родителем неразрывна и ее нельзя будет изменить после ее создания. Сущность у которой нет родителя является корневой (root) сущностью. Хранилище никогда не присвоит двум сущностям с одинаковым родителем одинаковый ID, также как и двум корневым сущностям.
Родитель сущности, родитель родителя и так далее по рекурсии являются предками. Ее наследники, потомки потомков называются потомками. Сущность и ее потомки принято называть одной группой потомков. Последовательность сущностей, начиная с корневой и следую от родителя к потомкам до указанной сущности составляю родительский путь или путь предков сущности. Полный ключ, идентифицирующий сущность состоит из последовательности пар идентификатор-тип, указывающих их родительский путь и заканчивающихся на самой сущности:
Person:GreatGrandpa / Person:Grandpa / Person:Dad / Person:Me

У корневой сущности родительский путь пуст и ключ состоит только из собственно типа и идентификатора.
Person:GreatGrandpa


Запросы и индексы
Кроме прямого получения сущностей из хранилища по их ключам, приложение может выполнять запрос для их получения по значениям свойств. Запрос работает с сущностями указанного типа, он может фильтровать сущности по значениям свойств или ключей и может возвращать ноль и более сущностей в результате. (Для экономии памяти и увеличения производительности запросу следует где возможно указывать лимит на количество результирующих данных). Запросу также можно указать порядок сортировки по значениям свойств. Результат включает все сущности, которые имеют хотя бы одно (возможно нулевое) значение для каждого свойства, указанного в фильтре и порядке сортировки и свойства которых соответствуют указанному в фильтре критерию. Запрос может возвращать всю сущность, ее проекцию или только ключи.
Запрос может также включать родительский фильтр, ограничивающий результаты только группой потомков указанного родителя. Такие запросы известны также как родительские запросы. По-умолчанию родительские запросы возвращают строго соответствующие результаты, которые гарантировано синхронизированы с последними изменениями в данных. Неродительские запросы напротив, могут охватывать все хранилище, а не только одну группу сущностей, которые в конечном счете соответствуют реальным, но могут возвращать устаревшие данные. Если приложению важно строгое соответствие, то вам необходимо учесть это при структурировании данных, размещая связанные сущности в той же группе сущностей, так что их можно будет получить с помощью родительского а не обычного запроса; смотрите для подробностей Структурирование данных для точной согласованности.
Примечание: соблюдение согласованности немного отличается в Master/Slave хранилищах; смотрите Использование Master/Slave хранилищ для детального ознакомления.
Помимо фильтров  в запросе можно указать порядок сортировки по значениям свойств. Результаты запроса будут включать все сущности, которые имеют по крайней мере одно (возможно нулевое) значение для каждого свойства, указанного в фильтре и порядке сортировки и чьи свойства соответствуют указанным критериям фильтра. Запрос может возвращать целиком сущности, указанное подмножество их свойств или просто их ключи (в ключевых запросах).
Примечание: для экономии памяти и увеличения производительности запросы следует где только возможно ограничивать по количеству выдаваемых результатов.
Каждый запрос использует индекс - таблицу, содержащую потенциальные результаты запроса в необходимом порядке. Хранилище обновляет индексы постепенно для отражения любых изменений, которые приложение делает с сущностями. Таким образом, корректные результаты всех запросов мгновенно доступны напрямую из индексов, без необходимости проведения дополнительных расчетов.
Индексы для некоторых типов запросов предоставляются автоматически; приложение может определить дополнительные индексы для себя в файле индексной конфигурации под названием index.yaml. Вэб сервер разработчика автоматически добавляет предложения в конфигурационный файл, когда находит запросы, у которых еще не сконфигурированы индексы. Вы можете вручную настроить индексы, отредактировав файл перед загрузкой приложения, смотрите статью Выбор индексов и расширенный поиск для дополнительной информации.
Примечание: Этот индексный механизм поддерживает широкий спектр запросов и подходит для большинства приложений. Однако он не поддерживает некоторые типы запросов, общие для других технологий БД, в частности объединение и запросы агрегирования не поддерживаются в это механизме запросов.

Транзакции
Каждая попытка создать, обновить или удалить сущность происходит в контексте транзакции. Отдельная транзакция может содержать любое количество таких операций. Для поддержки целостности данных, транзакция проходит если все ее операции применены к хранилищу успешно как блок или если хоть одна не завершилась успешно, отменяется весь  блок операций.
Вы можете выполнить множество действий с сущностью внутри одной транзакции. Например для увеличения поля счетчика объекта вам необходимо считать значение счетчика, рассчитать новое значение и сохранить его обратно. Без использования транзакции возможна ситуация, когда другой процесс изменит значение поля счетчика в промежуток времени между тем, когда вы его прочитали и моментом обновления счетчика и ваше приложение перезапишет это значение своим.Использование транзакций обеспечит уверенность в том, что никакой другой процесс в это время не повлияет на данные.

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

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

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

Запись в хранилище и видимость данных.
Примечание: эта секция описывает работу с High replication datastore (HRD). Устаревшие Master/Slave хранилища ведут себя по-другому; смотрите Использование Master/Slave хранилища. Для дополнения к этому топику смотрите также статьи Жизнь записи в хранилище и Изоляция транзакций в App Engine.

Данные записываются в хранилище в две фазы:
1. В фазе фиксации (Commit) данные сущности записываются в журнал
2. Фаза применения состоит из двух параллельных действий:
  • Данные сущности записываются
  • Записывается индексный ряд для сущности (этот процесс может занять больше времени чем простая запись данных)
Операция записи возвращается сразу после фазы Commit а фаза применения происходит асинхронно. Если произошла ошибка при первом шаге Commit, то он автоматически повторяется, однако если ошибки продолжаются, хранилище возвращает сообщение об ошибке, которое ваше приложение принимает как исключение. Если Commit фаза проходит, а фаза применения вызывает ошибку то она переносится вперед до завершения пока не произойдет следующее:
Периодическая зачистка хранилища на предмет незавершенных Commit операций и их применение
Некоторые операции применения (get, put, delete и родительские запросы) которые используют затронутые группы сущностей не будут выполнятся пока не будут завершены предыдущие предыдущие.

четверг, 24 мая 2012 г.

Работа с изображениями на canvas с помощью JavaScript

Начинаем

Настало время что-нибудь нарисовать на нашем экране. Сначала нам понадобится HTML страница на которой собственно и будет отображаться canvas элемент.




     <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
     <html lang="en">
        <head>
           <title>JavaScript Platformer 1</title>
           <script type="text/javascript" src="jsplatformer1.js"></script>
           <style type="text/css">
              body { font-family: Arial,Helvetica,sans-serif;}
           </style>
        </head>
       <body>
          <p>
             <a href="http://www.brighthub.com/internet/web-development/articles/38364.aspx">
                Game Development with Javascript and the canvas element
             </a>
          </p>
          <canvas id="canvas" width="600" height="400">
             <p>Your browser does not support the canvas element.</p>
          </canvas>
       </body>
    </html>


В коде страницы все достаточно просто и понятно. Есть два важных элемента:
<script type="text/javascript" src="jsplatformer1.js"></script>
Эта строчка подключает файл с кодом JavaScript, с помощью которого будет изменяться canvas элемент, который будет описан ниже.
<canvas id="canvas" width="600" height="400"> <p>Your browser does not support the canvas element.</p> </canvas>
Так мы создаем элемент canvas. Броузеры, которые не поддерживают элемент canvas, такие как IE (до версии 8), игнорируют его, отображая вместо него дочерний элемент. В нашем случае это простой параграф, предупреждающий пользователя. что их браузер не поддерживает элемент canvas. Все броузеры, которые этот элемент поддерживают - не отображают его дочерний элемент.
Атрибут ID элемента canvas очень важен, так как понадобится нам для получения ссылки на этот элемент с помощью JavaScript. Атрибуты ширины и высоты просто указывают размеры canvas, как и для других html элементов вроде таблицы или изображения.
Содержимое файла jsplatformer1.js:


   


     // target frames per second
     const FPS = 30;
     var x = 0;
     var y = 0;
     var xDirection = 1;
     var yDirection = 1;
     var image = new Image();
     image.src = "http://javascript-tutorials.googlecode.com/files/jsplatformer1-smiley.jpg";
     var canvas = null;
    var context2D = null;

    window.onload = init;

    function init()
    {
       canvas = document.getElementById('canvas');
       context2D = canvas.getContext('2d');
       setInterval(draw, 1000 / FPS);
    }

    function draw()
    {
       context2D.clearRect(0, 0, canvas.width, canvas.height);
       context2D.drawImage(image, x, y);
       x += 1 * xDirection;
       y += 1 * yDirection;

       if (x >= 450)
       {
          x = 450;
          xDirection = -1;
       }
       else if (x <= 0)
       {
          x = 0;
          xDirection = 1;
       }

       if (y >= 250)
       {
          y = 250;
          yDirection = -1;
      }
       else if (y <= 0)
       {
          y = 0;
          yDirection = 1;
       }
    }


Сам по себе элемент canvas достаточно прост и бесполезен. Необходимо с помощью JavaScript что-нибудь отрисовывать на canvas и в нашем случае этот код находится в файле jsplatformer1.js. В качестве простого примера мы загружаем изображение, отрисовываем его на canvas и двигаем его.
Сначала определим несколько глобальных переменных:
const FPS = 30;
FPS определяет частоту с которой будет перерисовываться canvas.
var x = 0; var y = 0; var xDirection = 1; var yDirection = 1;
Переменные x, y, xDirection и yDirection используются для определения положения изображения (относительно левого верхнего угла canvas) и указывают направление движения изображения.
var image = new Image(); image.src = "http://javascript-tutorials.googlecode.com/files/jsplatformer1-smiley.jpg";

Чтобы отрисовать изображение на canvas нам сначала необходимо загрузить это изображение. Для этого мы создаем объект Image и устанавливаем свойство src на месторасположение этого файла.

var canvas = null;
var context2D=null;

Нам также понадобится ссылка на элемент canvas, также как и на отрисовывамый контекст. Мы присвоим необходимые значения элементу позже, а пока оставим их в null.

windows.onload = init;

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




html 5. Об элементе Canvas


Элемент canvas появившийся в JavaScript движках наряду с некоторыми другими возможностями дает вэб-разработчикам возможность создавать детализированные и интерактивные 2D вэбстраницы без использования сторонних 3D плагинов. В этом топике обсудим некоторые возможности canvas и его потенциальные возможности.

JavaScript и элемент Canvas
HTML начал жизнь как формат для статичных страниц. Анимированные GIF изображения и мигающий текст - вот и вся динамика, которую он мог предоставить. JavaScript изменил все, позволяя динамическое изменение вэб-страниц и многие вэб-сервисы теперь используют и технологию AJAX для создания сайтов, которые дают пользователю более привычную работу, чем "нажал, обновилась страница, нажал", которая обычно так часто используется в обычных html-страницах.
К сожалению, JavaScript несколько ограничен функциональностью браузеров, в которых он работает. И если вы вполне можете создавать и изменять элемент на странице, то с помощью JavaScript вы не сможете (просто) заставить клиентскую машину отображать новый тип объектов. JavaScript может изменять текст, вставлять изображение или с легкостью изменять таблицу, так как эти элементы уже поддерживаются в HTML. Но что, если вы захотите чего-то больше, как например создать игру? Все, что вы могли использовать это стандартные элементы html таким образом, для которого они вовсе не предназначены. Ну или вам пришлось бы использовать плагины вроде Flash или Silverlight.

Появился элемент canvas. Этот новый HTML элемент предоставляет возможность JavaScript разработчикам рисовать прямо на странице без использования плагинов. Он впервые был представлен компанией Apple в их фреймворке WebKit и используется браузером Safari и виджетами Dashboard. Элемент canvas теперь является частью html 5 и теперь поддерживается большинством браузеров, таких как Chrome, Firefox, Opera и Konqueror. Internet Explorer (по-крайней мере до версии 8)  является исключением, хотя проекты вроде ExplorerCanvas предоставляют некоторую функциональность  элемента canvas в IE.
Любой кто работал с программированием 2D графики увидит что использование элемента canvas достаточно легкое и простое. Вы можете рисовать линии, фигуры, градиенты и даже изменять отдельные пиксели с помощью функций, которые очень похожи на большинство подобных в других 2D API. И огромное спасибо производительности последних JavaScript движков Chrome V8, Firefox SpiderMonkey и Safari Nitro за то что создание детализированных и интерактивных вэб-приложений теперь вполне доступно.
В этой серии статей мы посмотрим как создать простую игру с помощью JavaScript и элемента canvas. Мы раскроем такие темы как анимация, загрузка ресурсов, рендеринг слоев, прокрутка и взаимодействие.