Азы написания GUI на языке Python

Posted by admin On Сентябрь - 1 - 2009

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

Практически любой серьезный язык программирования имеет средства для написания GUI. Python — не исключение. Но разработка GUI на нем имеет два преимущества. Во-первых, это высокая скорость разработки, что и следовало ожидать от такого языка как Python. Во-вторых, — огромное количество GUI-фреймворков. Существуют расширения для почти всех основных GUI-библиотек: Tkinter на основе Tcl/Tk, wxPython для wxWidgets, PyQt для Qt и многое другое (с полным списком можно познакомиться, пройдя по ссылке на боковом выносе). Tkinter поставляется вместе с Python, и работу с GUI можно начинать сразу после его установки В этой статье будет рассматриваться библиотека wxPython. Она является оберткой над популярной кроссплатформенной GUI-библиотекой wxWindows.

По ссылке на боковом выносе можно выбрать подходящий IDE, заточенный для работы с GUI. Выбор весьма богат, так что, скорее всего, ты не разочаруешься, «примеру, для работы именно с wxPython мне приглянулся BoaConstructor. Он удобен для визуального проектирования GUI: избавляет от рутины и, в целом, имеет интуитивно понятный интерфейс (что не отменяет чтения туториала]. Какой IDE выбрать — это дело вкуса, поэтому я больше не буду акцентировать на нем внимание. Дальнейшее чтение статьи должно быть понятным независимо от сделанного выбора. Так что устанавливай wxPython и приступим непосредственно к кодингу.

Немного перефразирую известную фразу: лучший способ изучить работу с GUI — это сразу начать писать GUI! Не откладывая дело в долгий ящик, сразу продемонстрирую программу «Hello. world!» на wxPython’e.

import wx
class HelloFrame(wx.Frame) :
def_init_(self) :
wx.Frame._init_(self, id=-l, parent=None,
pos=wx.Point(422, 270), size= wx.Size(300, 200), title=’Hello Frame’) self.panel = wx.Panel(self) self.helloButton = wx.Button(id=-l, label= ‘Push me. ‘ ,parent=self .panel,
pos=wx.Point(110, 75), size=wx.Size(80, 30)) self.panel.Bind(wx.EVT_BUTTON, self.OnButtonClick, self.helloButton)
def OnButtonClick(self, event) print ‘Hello, world!’
class HelloApp(wx.App) def Onlnit(self): frame = HelloFrame() f rame.Show(True) return True
app = HelloApp () app.MainLoop()

При запуске программы создается окно с кнопкой, при нажатии на которую выводится строка «Hello, world!».

Теперь немного теории. Любая wx Python-программа должна содержать два объекта: прикладной объект и главное окно. Прикладной объект управляет главным циклом обработки событий, а окно содержит элементы интерфейса, посредством которых пользователь может управлять данными. Разберем жизненный цикл нашей программы:

1) app = wx.PySimpleApp() — создается прикладной объект, который должен быть объектом класса wx.App или производного от него. Пока он не создан, невозможно создать никакие другие графические объекты wxPython.

2) Вызов метода OnlnitO для созданного прикладного объекта. Обычно здесь выполняется начальная инициализация виджетов. Если он возвратит False, то приложение тут же завершится.

3) frame = HelloFrameO — создание объекта главного окна (должен быть объектом класса wx.Frame или производного от него). Причем, необязательно, чтобы создание этого объекта происходило в методе OnlnitO прикладного объекта — лишь бы главное окно создалось не раньше, чем прикладной объект! Приложение может содержать несколько окон верхнего уровня (не имеющих родителя), и только одно из них является главным. Главное окно можно объявить явно (вызвав метод SetTopWindowO) либо неявно (главным считается фрейм верхнего уровня, который создан первым).

4) app.MainLoop() — вызов главного цикла обработки событий. Этот цикл отвечает на события, посылая их соответствующим обработчикам (к событиям я подробнее вернусь чуть позже). Когда все окна верхнего уровня закрываются, то происходит возврат из метода MainLoopO и приложение завершает работу. Так же как метод OnlnitO вызывается сразу после создания прикладного объекта, метод OnExitO у этого объекта вызывается после закрытия последнего окна, но перед внутренней очисткой wxPython. Его можно использовать для очистки не-wxPython ресурсов. Если по каким-то причинам приложение должно жить даже после закрытия всех окон, можно изменить поведение прикладного объекта, вызвав у него метод SetExitOnFrameDelete(False). После этого приложение будет жить до тех пор, пока явно не вызовет глобальную функцию wx.Exitd. Ты получил общее представление о том, что происходит«за кулисами». Можно перейти к созданию виджетов, которым будет посвящена оставшаяся часть статьи.

Рассмотрим создание окна (фрейма):

wx.Frame(parent, id=-l, title=»»/
pos=wx.DefaultPosition, size=wx.DefaultSize,
style=wx.DEFAULT_FRAME_STYLE, name=»frame»)

Большинство параметров имеют разумные значения по умолчанию, поэтому их можно опускать. Их назначение понятно из названий. Я только обращу внимание на параметр id, который задает идентификатор виджета. Любой виджет должен иметь уникальный id. Этого можно достичь тремя способами:

1) Самому генерировать уникальное положительное число и передавать его в конструктор.
2) Использовать функцию wx.NewldO.
3) Передать в конструктор виджета константу wx.lD_ANY или -1 (что и сделано в примере).

Между вторым и третьим стилями нет никакого функционального различия. При создании фрейма, втом числе, происходит создание виджетов. которые он должен содержать. Хорошим ходом считается создание виджета \л/х. Рапе такого же размера, как рабочая область окна, и который, по существу, является простым контейнером для других объектов. Это позволяет отделить содержание окна от других элементов, типа панели инструментов и строки состояния. Затем в примере создается виджет кнопки. Список параметров аналогичен таковому у конструктора фрейма. Обрати внимание, что родителем является объект панели. Здесь нужно, чтобы размеры внутреннего виджета не вылезали за рамки виджета-родителя (параметры pos и size). После создания виджета доступ ко всем его параметрам осуществляется через Get/Set методы (такой подход не свойственен Питону, но тут просматривается влияние С++, так как wxPython — это оболочка над библиотекой wxWindows, которая написана как раз на С++).

Как уже упоминалось, главная задача прикладного объекта — обработка событий, которые происходят во время работы приложения. События (events) в wxPython’e — это очень обширная тема, поэтому я рассмотрю только азы. После того как у прикладного объекта вызван метод MainLoopO, программа большую часть времени по сути ничего не делает. Все события, которые происходят при работе программы, помещаются в очередь. Время отвремени программа проверяет эту очередь. Если в ней что-то появилось, она обрабатывает это событие, вызывая программный код. связанный с ним. Любое событие является потомком wx.Event и имеет свой тип. Например, событие wx.MouseEvent имеет U типов, таких KaKwx.EVT_RIGHT_DOWN.wx.EVT_LEFT_UPnT.n. В wxPython. большинство виджетов генерирует высокоуровневые события в ответ на события более низкого уровня. Например, щелчок мыши на кнопке wx.Button генерирует событие wx.CommandEventTnna EVT_BUTTON. Преимущество такого подхода — в том, что он позволяет сосредоточиться на самих событиях, вместо того, чтобы отслеживать каждый щелчок мыши.

Чтобы связать событие, виджет, который его вызвал, и обработчик события, используется так называемый биндер — объект класса wx.PyEventBinder. Для каждого типа события существует свой биндер. Объекты биндеров предопределены и глобальны, но есть возможность создать собственный биндер для собственного типа события. Любой виджет является потомком класса wx.EvtHandler, а значит, имеет метод Bind. Он-то как раз и создает биндеры событий, про которые только что шла речь. Этот метод имеет следующую сигнатуру: Bindlevent, handler, source=None. id=wx.lD_ANY, id2=wx.lD_ANY). Первые два параметра обязательны. Event — объект класса wx.PyEventBinder. и он описывает событие; handler — объект, поддерживающий вызов, обычно это метод или функция с единственным параметром — объектом событием. Параметр source задает виджет, который является источником события (его следует задавать, если этот виджет не тот, который используется как обработчик).
Рассмотрим, какэто все работает в нашей программе. В ней есть такая строчка:

self.panel.Bind(wx.EVT_BUTTON, self.OnButtonClick, self. helloButton). Щ j? -.

В этой строчке объект panel создает биндер. при помощи которого, при нажатии кнопки helloButton, произойдет вызов метода On Button Clicklself, event).

Хочу обратить внимание, что код. который вызывается в ответ на событие, обычно не определяется виджетом, вызвавшим это событие (так называемая распределенная архитектура). В нашем случае событие вызывается виджетом helloButton. но обработчик этого события является методом OnButtonClick!) объекта Frame, который и содержит виджет. Стоит упомянуть еще об одной возможности обработки событий: если в обработчике вызван метод SkipO, это позволяет продолжить поиск и выполнение других обработчиков того же самого события (иначе будет выполнен только тот обработчик, который был найден первым). Иногда такое поведение необходимо.

Возможно, тебе покажется, что для такой простой программки. как«Не11о, world!», тут слишком много теории. Может быть. Но зато теперь ты готов писать GUI практически любой сложности. Основной алгоритм написания большинства GUI:

1. Создание прикладного объекта и фрейма.
2. Создание и размещение виджетов в фрейме.
3. Размещение всей логики работы GUI в обработчиках событий. Следующим этапом у нас будет написание более серьезной программы — калькулятора (наподобие стандартного в Windows). Вместе с подробными комментариями весь код занял у меня около 270 строк. Естественно, что целиком в рамки статьи он не поместится. Поэтому я буду делать акцент только на самых интересных местах, а сам исходник можно найти на диске. Калькулятор будет представлять из себя окно, на котором размещены поле для ввода/вывода чисел, кнопки для набора цифр, знаков операций и вывода результата. Так как эта статья посвящена обзору GUI. то не буду подробно разбирать логику работы калькулятора. Рассмотрю только ту ее часть, которая имеет непосредственное отношение к GUI.

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

1. Рекомендуется давать объектам виджетов «говорящие» имена, чтобы по названию сразу можно было понять, для чего они предназначены (например, кнопку умножения есть смысл назвать buttonMul вместо button J 2).
2. То же самое касается именования обработчиков событий. Их название принято начинать с префикса «Оп». За ним идет название виджета, с которым этот обработчик связан, а затем — название события, которое обрабатывает обработчик (например, из названия обработчика On Button Erase Click ясно, что он обрабатывает событие, возникающее при «клике» на кнопку buttonErase). Возможно, это провоцирует появление длинных имен, но я рекомендую делать выбор в пользу понятности, пусть и в ущерб краткости.
3. Часто есть возможность уменьшить количество обработчиков путем назначения одного и того же обработчика на разные события. В нашем случае вместо того, чтобы на каждый «клик» на кнопке с цифрой назначать отдельный обработчик (который всего лишь должен эту цифру дописать
в окно вывода), можно обойтись одним единственным обработчиком. Правда, перед тем как дописать цифру, этому обработчику вначале придется найти ту кнопку, которая вызвала это событие, так как эта цифра расположена на label’е этой кнопки. Что делается следующим образом:

# Получаем список всех виджетов, которые содержат
# panel.
children = self.panel.GetChildren()
# Находим виджет, который вызвал событие, for child in children:
if child. Get Id () == event .Get Id() :
# Знак, который нужно вывести, содержится
# в label’е виждета.
self.textCtrllnfo.AppendText(child. GetLabelO)   у– -   ШШ !^|LM^

Тут быстродействие приносится в жертву ради большей «прозрачности» кода. Но именно такой подход и соответствует идеологии Python”a. поэтому настоятельно рекомендую придерживаться его и впредь. Кроме того, на нажатие кнопок арифметических операций и кнопки вывода результата («=») также можно назначить один и тот же обработчик (в примере это метод OnOperationClick). Правда, при разборе этого метода могут возникнуть некоторые трудности для тех, кто слабо разбирается в Python”e. Но это лишь из-за не совсем тривиальной логики, по которой должен работать калькулятор (например, если было введено 2+3, то ввод следующей операции подразумевает вычисление этого выражения 2+3=5 и применения новой операции к результату). Даже если ты не до конца разберешься с логикой, — ничего страшного, ведь наша главная задача это GUI.

Есть несколько нюансов, о которых начинающие разработчики часто забывают.
1. Важно помнить, что пользователю может прийти в голову мысль изменить размер твоего приложения (например, развернуть его на весь экран). Обычно после этого приложение приобретает неприглядный вид — все виджеты будут расположены в рамках старого размера, а оставшаяся часть останется пустой. Самый простой вариант избежать этой неприятности — запретить изменения размера окна
и его максимизацию. Это можно сделать при инициализации фрейма, инициализировав параметр style значением wx.DEFAULT_FRAME3TYLE&Mwx.MAXIMIZE_B0X wx.RESIZE_BORDER).
2. Поскольку наш калькулятор позволяет вводить цифры не только путем нажатия кнопок, но и с клавиатуры, то необходим контроль за пользовательским вводом. Пользователь должен вводить только целые и дробные числа. Проверку правильности ввода можно организовать следующим образом:

try:
number = float(self.textCtrllnfo.GetValueO) except (TypeError, ValueError): self.errorStatusBar.SetStatusText(ОШИБКА! Введите число правильно.)
return

Этот код пытается привести строку к типу float; если не удается, то бросается исключение, которое ловится, и в виджет errorStatusBar выводится сообщение об ошибке. Обрати внимание, что ловятся ошибки определенного типа (TypeError, ValueError). Все остальные ошибки ввода уже не будут выводиться в виджет errorStatusBar Это считается хорошим стилем — ловитьтолько те ошибки, которые ты готов обработать.
3. По умолчанию длина входной строки не ограничена, и при вводе большого числа оно не будет влезать в поле ввода. Чтобы этого избежать, можно указать максимальную длину вводимой строки при создании виджета: textCtrllnfo. SetMaxLength(30).

Надеюсь, теперь тебе не составит труда продолжить изучение wxPython самостоятельно. На примере двух простых программок был рассмотрен фундамент любого wxPython-приложения. А если есть фундамент, — возвести фасад куда легче. В процессе дальнейшего изучения wxPython я настоятельно рекомендую обратить внимание на wxPython Demo, который поставляется вместе с самой библиотекой. Это большая сборка различных примеров с исходным кодом. Если захочется изучить новый виджет — в первую очередь ищи там. Также есть замечательная книга «WxPython in action» отавторов Noel Rappin и Robin Dunn. Ссылку для скачивания можно найти на боковом выносе. Написана она на английском, но если погуглить, то можно найти русский перевод нескольких глав (я нашел главы 1,2,3,11,14). Для начального знакомства с wxPython вполне хватит первых трех. Я вскрыл только верхушку айсберга программирования GUI на Python’e, все остальное остается за тобой. Удачи!

Но как же без него? Если нет привычной базы данных SQL, то что? Вот, что я тебе скажу. Первым кинь камень в того, кто скажет, что в большом проекте без SQL не обойтись. Обойтись можно. И при этом некисло выиграть!

Скажи честно, тебе ведь интересно, как устроены изнутри те суперпроекты, на которых висишь сутками ты и еще сотни миллионов пользователей сразу? Google, Amazon, eBay, Twitter, тот же Facebook или наш ВКонтакте? Они совсем не похожи на большинство обычных веб-сайтов, написанных на PHP+mySQL. База данных в них — все. Там и новости, и информация о товарах в интернет-магазине, и статьи с комментариями в блоге, и самое вкусное — логины и пароли.

Очень многие разработчики и архитекторы также думали, что без базы не обойтись, продолжая создавать все более мощные сайты. Но вскоре столкнулись с тем, что сколько ни тужься, какие только хаки и умные штуки ни придумывай — при нагрузке в сто миллионов пользователей, базы данных все равно мрут, как мухи. Ребята тоже слышали о кластерах, и о распределенных системах и даже об облачных вычислениях (подробнее читай статью «Заоблачные вычисления» в #125 номере зс). Если надо, чтобы больше людей скачали новый порно-ролик с Берковой, достаточно поставить еще пару серверов и скопировать на них файлы. А вот базы данных так просто не работают. Вот тут-то и нарисовалась проблема масштабирования. Каждый решает ее по-своему. Сначала ставят второй сервер: с него приложение читает данные, записывая только на первый, а он уже сам, в фоновом режиме, переносит новые данные. Такая архитектура называется master-slave, но ничего связанного с BDSM здесь нет!

Позже можно доставить еще сервер, и еще, но это уже не поможет, если писать надо много и постоянно. Ведь каналы между серверами рано или поздно будут забиты так, что новые данные будут появляться на подчиненных узлах гораздо позже, чем это допустимо. А кому интересно ждать, пока его комментарий появится на странице (самые нетерпеливые тупо жмут рефреш, чем еще сильнее нагружают систему)? Светлые умы подумали и решили: а что, если все базы данных будут сразу главными? У тебя есть три сервера, и на каждом из них — вся информация, а приложение случайно или по определенному алгоритму выбирает, с каким сервером работать. Изменения на одном сервере сразу передаются на два других (это называется master-master или multi-master репликация), и в любой момент везде есть самые последние данные, при этом писать и читать информацию можно с любого сервера. Но тут одна сложность — у самых популярных баз эта функция появилась только недавно, да и в настройке и поддержке очень уж сложная. И не дай бог, придется восстанавливать данные или потерянные транзакции — тут вообще без пива не разберешься. Ну и, конечно, до бесконечности наращивать количество серверов также нельзя. Все будет неплохо, пока не дойдешь до десятка. А там не оберешься проблем с взаимной связью и трафиком внутри такого хозяйства. А результат тот же — медленно и ненадежно.

А тем временем сайты растут, пользователей становится все больше, счет идет уже на десятки миллионов. Что же делать? А вот что — отказаться от обычной базы данных! Ведь что такое база данных? Это специальное хранилище данных (обычно это просто файлы, но с собственной структурой и кешем в памяти) с движком, который принимает от тебя команды в виде языка SQL (например, на выборку данных и выполняет их.

Особенно достают кривые руки разработчика или админа, когда для самого простого запроса «а сколько юзеров у меня на странице?» приходится тупо перебирать весь список пользователей и проверять, у кого статус «онлайн». Ведь юзеров может быть реально много, а если ты еще не озаботишься правильными индексами, то на каждый такой запрос придется серверу доставать всю табличку с данными (а это может и гигабайт быть] и считать снова и снова. А если в этот момент Вася скинул своим френдам в ЖЖ ссылку на твой суперпроект и пришла еще тысяча юзеров, каждого из которых надо записать в базу? Все — капут серверу! Все потому что и базы данных, и язык SQL, которым эти данные выбираются, достаточно плохо приспособлены к масштабированию. То есть, пока один-два сервера, все будет окей. Но как только больше — начинаются проблемы. Нельзя добавить еще машинку и гарантированно заставить работать все быстрее. В Гугле это давно поняли и изобрели свое решение, полностью отказавшись от применения таких обычных баз данных. Но это Гугл со своими ноу-хау, а что делают остальные? Остальные используют key-value database! По сути, это максимально упрощенная база данных. Скорее, даже просто хранилище, где все данные сведены к обычной паре: ключ (или индекс) и сами данные, которые обычно представляют собой строку и — в некоторых случаях — числа. То есть, вся база данных — это просто список ключей и сопоставленных с ними строк данных. Что именно хранится в такой базе, ей совершенно неважно — это забота самого приложения. Интерфейс доступа к такой базе также максимально прост — обычно это простейшие команды типа get (получить данные по ключу], set (записать данные с ключем), delete (удаляет ключ и его данные), update (обновляет уже существующие данные). Самым главным преимуществом является то, что если правильно все сделать, сложность таких операций (то есть, время вычисления результата) будет заранее известна и не зависит от объема данных или количества серверов. Более того, операции обычно атомарные (в SQL базах данных это называется транзакциями). Т.е., задавая команду, ты можешь быть уверенным, что она либо успешно отработает, либо сразу вернет ошибку — при этом другие пользователи не помешают тебе, даже если будут пытаться сделать то же самое. Это самый обычный тип key-value баз данных.

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

Сильная сторона таких решений — масштабируемость и скорость. Свойства DHT такие, что можно присоединять новые сервера постоянно, и база будет расти и расти. Столько — сколько надо. При этом в самих приложениях ничего менять не нужно, все делается автоматически! Скорость очень и очень высокая, так как практически все такие базы работают в памяти, а на диск пишется лишь бекап (при этом, он может быть постоянным — в таком случае в него записывается только новая инфа). Показатели в сотни тысяч запросов в секунду на одном дохленьком сервере — это обычное дело для таких баз. Но, несмотря на восторги, есть и сложности. Первая — это скудность возможностей работы с данными. Ага, вот и расплата за скорость и расширяемость! Сервер знает только ключ и данные, которые с ним ассоциированы, а вот, что это за информация — номер кредитной карточки или дата регистрации — уже не ведает. Этим должно заниматься само приложение! Поэтому просто взять и, например, написать один SQL-запрос, чтобы выбрать всех пользователей, которые регистрировались год назад и совершили больше одного платежа за это время, уже не получится. В базе просто нет возможности выборки по какому-либо признаку, кроме ключа. Но не спеши отворачиваться — это ограничение легко решается за счет ввода дополнительных данных (так же как в SQL-базе постоянные данные выделяются в отдельную таблицу-справочник). Правда, в этом случае нужно с самого начала проектировать сайт под такие типы базы. Ведь то, что делается одной строкой на SQL, здесь потребует как нескольких запросов и обработки, так и предварительного форматирования данных при записи. Увы, автоматических трансляторов SQL в key-value запросы пока нет, но работы в этом направлении ведутся. Еще одним недостатком таких баз является требовательность к параметрам сервера и в особенности оперативной памяти, которой, как известно, много не бывает. Прожорливость удается удовлетворить за счет хранения неиспользуемых данных на диске. Подобным образом поступили разработчики MemcacheDB, где скрестили популярный сервер memcache и базу данных BerkleyDB, используемую как постоянное хранилище данных. В более молодом, но очень сильном проекте проекте — Redis — используется асинхронная запись в фоновом режиме на диск. Другие также не брезгуют использовать традиционные базы данных для хранения, ведь их совсем не видно за фасадом сервера и они работают локально, поэтому на скорость работы почти не влияют.

Memcached/MemcacheDB (memcachedb.org) — наверное, самый известный представитель семейства key-value DB. Многие используют его как кеширующую систему, что, по большому счету, то же самое. Проект хранит данные в оперативной памяти, занимает места столько, сколько ему выделили, и может объединяться с другими серверами, чтобы распределить данные между собратьями. Доступ к данным идет через UDP-порт и сокеты, что очень быстро, а с выходом последней версии, 1.4, добавлен и экономичный бинарный протокол. Хотя в Facebook считают иначе и ускоряют, как могут, добиваясь нескольких сотен тысяч одновременных подключений! Кстати, именно эта социальная сеть имеет самую большую инсталляцию Memcached-серверов — в архитектуре участвует более тысячи серваков! Недостаток мемкеша в том, что он хранит все в памяти. По этой причине в местах, где необходима сохранность данных, придется использовать MemcacheDB, который использует обычную базу данных как постоянное хранилище данных. Другие недостатки — ограниченность на данные, которые понимает сервер (это только числа и строки), а также сложности выборки одним запросом множества ключей. Project Voldemort (project-voldemort.com) — такой же мощный, как и Темный Лорд, только в царстве баз данных. Штука написана на Java и изначально нацелена на распределенность. Добавлять новые сервера можно без остановки — данные по ним «расползутся» без посторонней помощи. Кроме обычного сетевого доступа, Project Voldemort поддерживает JavaAPI и различные сетевые протоколы, например, Google ProtoBuf или Thrift, что сильно экономит трафик и повышает скорость. Данные хранятся как в памяти, так и на диске (можно использовать и обычные базы данных), так что сбои питания никак не нарушат целостности. Сильной стороной является поддержка версионности, то есть каждая единица данных имеет историю версий и изменений, поэтому можно откатываться назад, если что-то записали не то или возникли ошибки. Быстродействие также на высоте: в среднем 10-20 тысяч операций в секунду, и такой гигант, как соцсеть Linkedln не прогадал, используя кластер из этих серверов для своей работы. Apache CouchDB (couchdb.apache.org) — это уже тяжелое оружие из будущего! Шутка, CouchDB это представитель отдельного семейства баз данных, называемых документно-ориентированными. В этой штуке хранят документы, представляющие собой некоторую группу данных, которые вместе составляют один объект-документ. Например, статья (текст), краткая аннотация, имя автора, дата публикации и статус. По отдельности, это просто значения, а вот документная база позволяет их сгруппировать как один объект и производить над ним операции. Apache CouchDB написана на Erlang (просто замечательная платформа, если речь идет о расширяемости) и имеет HTTP REST-интерфейс или JSON API. так что можно получать данные сразу напрямую из JavaScripta-a на веб-странице! Кстати, она имеет встроенный язык запросов, и какой ты думаешь? Да, JavaScript вместо традиционного SQL.

Справедливости ради стоит сказать, что о промышленном применении базы пока не слышно. Уж сильно экспериментальная разработка, хотя и чрезвычайно перспективная. Redis (code.google.com/p/redis) — проект молодой и достаточно простой, но по возможностям мощнее всех предыдущих вместе взятых! Почему? Да взять хотя бы производительность. Более 100 запросов в секунду на простеньком сервере или мощном лаптопе. Знакомься, Redis или, как он сам себя называет, сервер структурированных данных. Проект позволяет хранить не только обычные ключи и значения, но и списки, наборы данных (группы пар ключ-значение), а также производить всего одной командой (и с гарантированным временем выполнения!) сложнейшие операции над такими списками. Там, где для memcached надо писать вручную две, три или десяток команд и еще вычислять что-то в самом приложении, при использовании Redis-a можно обойтись одной! Поддерживается даже сортировка, что является самой сложной и практически не выполнимой командой для всех key-value баз (в отличие от SQL, где это самая тривиальная операция). Написанный на ANSI С сервер умещается в паре десятков Кб исходных текстов (по лицензии BSD), работает на любой системе и сотворит чудеса с твоими данными. Команды посылаются no TCP или напрямую через telnet. Помимо этого, есть и API или модули на любой вкус и язык. Не буду скрывать, что сам являюсь автором класса-интерфейса для РНР. расширяющего возможности сервера еще сильнее! :)

Понимаю, что все, что я выше с таким трудом рассказал, это фигня, и хочется сразу почувствовать мощь новой технологии (ладно, не сильно новой, но все равно интересной). Давай попробуем ее в действии. Известно, что у самого быстрорастущего сервиса в мире [Twitter) долгое время
были проблемы с производительностью. Писать об этом проекте нам уже надоело, поэтому предлагаю забабахать собственную альтернативу. С использованием обычной БД — это вполне тривиальная задача (если не брать в расчет вопрос масштабируемости). Но мы реализуем тот же функционал без привычной БД — используя только сервер Redis. С кодом сложностей не должно возникнуть, HTML-странички ты сверстаешь сам, а вот как использовать такую необычную базу внутри, я тебе сейчас расскажу. ШАГ 0. Определимся, что мы делаем. Наш простой твиттер должен уметь хранить акка-унты пользователей (и пускать тех, кто знает пароль), хранить твои записи и выводить их, позволять добавлять и удалять друзей (фол-ловеров) и показывать их список, а также отображать полную ленту сообщений (как твоих, так и всех твоих друзей).

ШАГ 1. Аккаунты будем хранить в виде отдельных пар ключ-значение, где ключом будет логин пользователя, а значением — сериализированный массив(язык не имеет значения, например, РНР), в котором уже все о юзере, его имя, пол, дата регистрации и остальные данные. Вместо сериализации лучше использовать JS0N — тогда мы вообще не будем зависеть от языка приложения, ведь JS0N умеет обрабатывать любой современный язык программирования. Команда SET admin «{name:'supervasya',age:21,sex:'m',registered:'27.07.2009'} » — записывает нового юзера с логином admin. Теперь, выполнив запрос GET admin, мы получим JSON-строку с данными.

Для авторизации мы используем отдельное значение: SET admin_pass «md5(password)» — ключом здесь также служит логин, но с добавлением строки «_pass», а значение — md5 хеш пароля. Авторизация будет в два шага (для надежности, но можно и в один]. Сначала проверим, существует ли логин: EXISTS admin, если все ОК (значит, в базе есть такой юзер], извлекаем его хеш пароля для проверки: GET admin_pass. Саму проверку и сравнение хешей придется делать уже в приложении. Не забудем счетчик всех юзеров (а то ведь SELECT COUNT!) здесь нету): INCR count_user — увеличит счетчик юзеров на 1. Если тебе захочется иметь весь список юзеров. придется раскошелиться на еще одну переменную, например, загнав все логины в набор (set): SADD all_user_list admin. Таким образом, в all_user_list у нас будет храниться список всех логинов, по которым можно извлечь профайл аккаунта.

ШАГ 2. Теперь будем хранить все твои сообщения. Ключом в данном случае будет метка времени, потому что ключ должен быть уникальным, да и вряд ли ты будешь постить что-то чаще раза в секунду (нефиг спамить!). Можем просто создать ключ, используя логин и метку времени, например, admin_11232142135. и хранить его как отдельное значение вместе с сообщением: SET admin_1123214-2135 «{аи1Ьог:^гпт*,1ех1:’моя супер статья!’ .time: 11232142135,title:’статья!*}». Но чтобы облегчить себе жизнь, мы сделаем еще список, где будут храниться данные о времени постов каждого автора. Вот так: RPUSH admin_msgs 11232142135. Команда добавит в конец списка admin_msgs новое значение — метку времени твоего поста. Зачем? Для облегчения получения потом всех постов за определенное время или просто указанного количества, например, для постраничного вывода. Внутри списка даты уже отсортированные по времени, поэтому дополнительной сортировки не нужно.
ШАГ 3. Если ты хочешь зафолловить (читать) Васю, необходимо сохранить логин Васи в твоем списке фолловеров. Для этого также применим списки, создав для каждого юзера список фолловеров: RPUSH admin_follow vasja. В списке admin_follow теперь будут храниться логины всех юзеров, которых хочет читать admin. Аналогично, если Вася хочет читать, что же про него пишет админ: RPUSH vasja _follow admin.

ШАГ 4. Выводим полную ленту сообщений. Мы уже умеем хранить все сообщения одного пользователя и хранить список тех, за кем он следит. Теперь выводим ленту сообщений, в которой будут как собственные сообщения юзера, так и все сообщения тех, за кем он следит. При этом, все сообщения должны идти в хронологическом порядке.

Допустим, мы будем показывать только сообщения за последний час. Здесь уже немного сложнее. Сначала выберем список всех пользователей, которых надо показать. Для этого сначала получим количество наших фолловеров (длину списка): LLEN admin_follow. Допустим, мы получили 2 (админ отслеживает двух юзеров):
LRANGE admin_follow 0 1 — получаем в виде массива логины юзеров. Не забываем, что надо прибавить сюда и свой логин, так как наши сообщения тоже должны быть видны. Это придется делать уже самому приложению.

Далее, имея список логинов, нам надо выбрать все списки сообщений каждого юзера. К сожалению, для этого надо N раз вызвать команду LRANGE, указав ей каждый раз другой список (комбинацию логин игрока + _msgs). Конечно, в этом нет ничего страшного, ведь скорость работы Redis-a очень высокая, но этот момент может нуждаться в оптимизации. Например, есть команда KEYS, которая ищет по паттерну все ключи и возвращает сразу список. Поэтому можно попробовать задать ей такое выражение, чтобы сразу получить все ключи сообщений (ведь они формируются через логин и метку времени, значит можно отфильтровать). Но это уже тебе как домашнее задание (на самом деле задача имеет несколько решений и не факт, что каждое из них самое лучшее).

Мы пока сделаем по старинке, получив список сообщений для каждого юзера. программно сформируем из него список заранее подготовленных ключей для извлечения сообщений. Так как все сообщения идут по времени, достаточно полученный массив преобразовать из JSON-a в родной для твоего языка программирования и отбросить все значения, меньшие за текущее время минус 3600 (мы ведь за последний час выбираем). Если брать не за час, а просто последние 100, то задача еще более упростится.
Далее простым циклом формируем ассоциативный массив из комбинации логин + метка времени, где ключом будет метка времени (число, для обеспечения правильной сортировки), а значение — строка вида loginjime (то есть так, как хранится у нас в Redis-e), а потом просто объединяем эти массивы. Язык сам позаботится о правильной последовательности, например, РНРтак и сделает, используя команду аггау_тегде и. если надо array_sort.

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

Последний штрих — сформируем команду к Redis-y на извлечение всех сообщений, ключи которых мы уже подготовили. У сервера есть волшебная команда, которой так не хватает другим популярным системам вроде memcached (там пытаются приспособить для этого теги) — MGET список^ключей, то есть одной командой получаем все ключи, имена которых передали. Остается только превратить наш массив в строку, разделителем служит символ пробела — и мы сразу получим массив JSON-строк с сообщениями. Его сразу можно передать на веб-страницу, с JSON умеет работать любой AJAX-фреймворк. Насчет производительности не стоит переживать — операция декодирования JS0N в родной для языка массив везде очень и очень быстрая, даже если речь идет о сотнях или тысячах преобразований. Аналогично можно отобразить список всех фолловеров — ведь мы храним их в списке admin_follow. в котором хранятся логины, а значит, используя потом MGET-команду, мы сразу достанем профайлы всех юзеров, за которыми следит админ.

Я ничего не сказал об удалении данных — вдруг Вася окажется занудным типом или спаммером и ты захочешь отписаться от него. Для этого надо просто удалить из списка admin_follow его логин, что
Сейчас реляционные базы данных (SQL СУБД) уже не правят миром, особенно если речь идет о высоконагруженных проектах или сайтах, где надо без задержки обслуживать клиентов. Если раньше все проблемы пытались решить кешированием, то сегодня мы видим, как гиганты индустрии просто уперлись в ограничения баз данных и в поисках выхода попробовали посмотреть на традиционные кеши с другой стороны. И получилось! Добавив чуточку смекалки и пару новых команд, теперь можно делать почти все, что раньше требовало сложных SQL-запросов, используя всего пять-шесть команд. При этом неважно, один сервер, десять или тысяча, мы вообще никак не ограничены в масштабировании! Конечно, не стоит сразу бросать любимый мускул и переписывать под Redis или MemcachedDB, но если ты готовишь сайт, где надо что-то делать быстро, очень быстро, как можно быстрее (ну типа чата, твиттера или онлайн-игры, а то и биржевой системы) — попробуй посмотреть на мир key-value баз данных! Может, это то, что надо! А SQL-базам оставим нудные дела вроде построения аналитики и анализа данных.