Раздел "Кодинг". Содержание:
Статьи:
Множественная диспетчеризация
Delphi. Эмуляция нажатия клавиши Enter
MySQL — “зеленым” админам
Создание больших web-проектов
Подпишитесь на рассылку от ведущего раздела "Кодинг"
Раздел "Кодинг". Статьи:
Множественная диспетчеризация
Обобщение полиморфизма с помощью мультиметодов
Во многом универсальность объектно-ориентрованного программирования (ООП) возможна благодаря полиморфизму: в надлежащем контексте объекты разных видов могут вести себя различным образом. Однако большая часть ООП - это единичная диспетчеризация, то есть единственный выделенный объект определяет, какую ветвь кода избрать. Концептуально более общий подход - позволить всем аргументам, передаваемым в функцию/метод, определять ее специализацию. В этой статье рассматривается реализация множественной диспетчеризации в Python и показано, как это улучшает программы.
Что такое полиморфизм?
Большинство программистов, использующих полиморфизм - на Python или других языках объектно-ориентированного программирования -, находят ему весьма практическое и конкретное применение. Возможно, наиболее общий случай использования полиморфизма - это создание семейства объектов, которые придерживаются общего протокола. В Python это обычно просто вопрос нерегламентированного полиморфизма; в других языках чаще объявляются формальные интерфейсы, и/или эти семейства обладают общим предком.
Например, существует множество функций, которые работают с объектами, "подобными файлам", где это подобие файлам определяется просто посредством поддержания нескольких методов, как .read(), .readlines() и, возможно, .seek(). Функция, как read_app_data(), может принимать аргумент src - когда мы вызовем эту функцию, мы, возможно, решим передать ей локальный файл, объект urllib, объект cStringIO или некий объект, определенный пользователем, который разрешает этой функции вызывать src.read(). Каждый тип объекта равнозначен с точки зрения того, как он функционирует в read_app_data().
Давайте вернемся немного назад, чтобы понять, что здесь действительно происходит. По существу, нас интересует, как выбрать надлежащую ветвь кода для выполнения в контексте; старомодный процедурный код может принимать эквивалентные решения, ООП просто придает элегантность. Например, фрагмент процедурного (псевдо) кода мог бы выглядеть следующим образом:
Листинг 1. Процедурный выбор ветвей кода по типу объекта
...bind 'src' in some manner...
if <<src is a file object>>:
read_from_file(src)
elif <<src is a urllib object>>:
read_from_url(src)
elif <<src is a stringio object>>:
read_from_stringio(src)
...etc...
Организовав поддержку общих методов объектами различных типов, мы перемещаем решение о диспетчеризации в объекты из явного условного блока. Просматривая дерево наследования, данный объект src узнает, какие блоки кода ему нужно вызывать. Однако, по-прежнему происходит неявное переключение, но по типу объекта src.
Объект src привилегирован по отношению к любым аргументам, передаваемым в его методы. Из-за синтаксиса ООП эта привилегированность кажется неизбежной, но на самом деле это не так. Во многих случаях процедурное переключение просто переносится в тела методов классов. Например, мы могли бы реализовать совместимые по протоколу классы Foo и Bar следующим образом:
Листинг 2. Реализация метода .meth() с помощью Foo и Bar
class Foo:
def meth(self, arg):
if <<arg is a Foo>>:
...FooFoo code block...
elif <<arg is a Bar>>:
...FooBar code block...
class Bar:
def meth(self, arg):
if <<arg is a Foo>>:
...BarFoo code block...
elif <<arg is a Bar>>:
...BarBar code block...
# Function to utilize Foo/Bar single-dispatch polymorphism
def x_with_y(x, y):
if <<x is Foo or Bar>> and <<y is Foo or Bar>>:
x.meth(y)
else:
raise TypeError,"x, y must be either Foo's or Bar's"
Имеется пять различных ветвей/блоков кода, которые могут выполняться при вызове x_with_y(). Если типы x и y не подходят, возбуждается исключение (разумеется, вы могли бы сделать что-нибуль другое). Но, предполагая, что с типами все в порядке, ветвь кода выбирается сначала посредством полиморфной диспетчеризации, а затем посредством процедурного переключения. Кроме того, переключения внутри определений Foo.meth() и Bar.meth() в значительной степени эквивалентны. Полиморфизм - в разновидности с единичной диспетчеризацией - решает лишь половину задачи.
Полная реализация полиморфизма
В случае полиморфизма с единичной диспетчеризацией выделяется объект, который "владеет" методом. Синтаксически в Python его выделяют, располагая его имя перед точкой - все, что следует за точкой: имя метода и левая скобка - просто аргумент. Но семантически этот объект является особенным при использовании дерева наследования для выбора метода.
А что если бы мы обрабатывали особым образом не один объект, а позволили бы каждому объекту, задействованному в блоке кода, участвовать в выборе ветви выполнения? Например, мы могли бы выразить наше пятистороннее переключение более симметрично:
Листинг 3. Множественная диспетчеризация Foo и Bar
x_with_y = Dispatch([((object, object), <<exception block>>)])
x_with_y.add_rule((Foo,Foo), <<FooFoo block>>)
x_with_y.add_rule((Foo,Bar), <<FooBar block>>)
x_with_y.add_rule((Bar,Foo), <<BarFoo block>>)
x_with_y.add_rule((Bar,Bar), <<BarBar block>>)
#...call the function x_with_y() using some arguments...
x_with_y(something, otherthing)
Я думаю, что эта симметричность полиморфной диспетчеризации по множеству аргументов гораздо более элегантна, чем предшествующий стиль. Кроме того, этот стиль позволяет документировать одинаковую роль этих двух объектов, задействованных в определении подходящей ветви кода.
Стандартный Python не разрешает конфигурировать этот тип множественной диспетчеризации; но, к счастью, вы можете сделать это, воспользовавшись написанным мною модулем multimethods. См. Ресурсы, чтобы скачать этот модуль отдельно или в составе утилит Gnosis. После того, как вы установили multimethods, все, что от вас требуется - включить в начало своего приложения следующую строку:
from multimethods import Dispatch
"Мультиметоды", как правило, это синоним множественной диспетчеризации; но термин мультиметод предполагает конкретную функциональную/объектную реализацию более абстрактной концепции множественной диспетчеризации.
Экземпляр Dispatch - это вызываемый объект, его можно конфигурировать с любым желаемым количеством правил. К тому же, можно использовать метод Dispatch.remove_rule(), чтобы удалять правила; благодаря этому множественная диспетчеризация с использованием multimethods становится несколько более динамичной, чем статическая иерархия классов (но вы также можете совершить некие замысловатые действия с классами Python во время исполнения). Также заметьте, экземпляр Dispatch может принимать переменное число аргументов; сопоставление выполняется сначала по числу аргументов, затем по их типам. Если экземпляр Dispatch вызывается с любым шаблоном, который не определен в правиле, возбуждается TypeError. Инициализация x_with_y() с запасным шаблоном (object,object) необязательна, если вы просто хотите, чтобы в неопределенных ситуациях возбуждалось исключение.
Каждый кортеж (pattern,function), перечисленный в инициализации Dispatch, просто передается далее в метод .add_rule(); это исключительно вопрос удобства программирования - устанавливать правила при инициализации или позже (можно комбинировать подходы, как в предшествующем примере). При вызове функции из диспетчера аргументы, используемые при вызове, передаются диспетчеру; вы должны обеспечить, чтобы функция, которую вы используете, могла принять то число аргументом, с которым она сопоставляется. Например, приведенные ниже вызовы эквиваленты:
Листинг 4. Явный вызов и вызов функции при диспетчеризации
# Define function, classes, objects
def func(a,b): print "The X is", a, "the Y is", b
class X(object): pass
class Y(object): pass
x, y = X(), Y()
# Explicit call to func with args
func(x,y)
# Dispatched call to func on args
from multimethods import Dispatch
dispatch = Dispatch()
dispatch.add_rule((X,Y), func)
dispatch(x,y) # resolves to 'func(x,y)'
Очевидно, что если вы знаете типы x и y во время проектирования, алгоритм задания диспетчера - просто накладные расходы. Но то же ограничение справедливо и для полиморфизма - он удобен, лишь когда вы не можете ограничить объект единственным типом для каждой ветви исполнения.
Улучшение наследования
Множественная диспетчеризация не просто обобщает полиморфизм, она предоставляет более гибкую альтернативу наследованию во многих контекстах. Рассмотрим в качестве иллюстрации следующий пример. Предположим, что вы пишете программу построения чертежей или автоматизированного проектирования, которая работает с различными фигурами (shape); в частности, вы хотите, чтобы вы могли комбинировать две фигуры таким образом, чтобы результат зависел от обеих задействованных фигур. Кроме того, набор рассматриваемых фигур будет расширяться производными приложениями или подключаемыми библиотеками. Расширение набора классов фигур является неизящным подходом при модернизации, например:
Листинг 5. Наследование для расширения возможностей
# Base classes
class Circle(Shape):
def combine_with_circle(self, circle): ...
def combine_with_square(self, square): ...
class Square(Shape):
def combine_with_circle(self, circle): ...
def combine_with_square(self, square): ...
# Enhancing base with triangle shape
class Triangle(Shape):
def combine_with_circle(self, circle): ...
def combine_with_square(self, square): ...
def combine_with_triangle(self, triangle): ...
class NewCircle(Circle):
def combine_with_triangle(self, triangle): ...
class NewSquare(Square):
def combine_with_triangle(self, triangle): ...
# Can optionally use original class names in new context
Circle, Square = NewCircle, NewSquare
# Use the classes in application
c, t, s = Circle(...), Triangle(...), Square(...)
newshape1 = c.combine_with_triangle(t)
newshape2 = s.combine_with_circle(c)
# discover 'x' of unknown type, then combine with 't'
if isinstance(x, Triangle): new3 = t.combine_with_triangle(x)
elif isinstance(x, Square): new3 = t.combine_with_square(x)
elif isinstance(x, Circle): new3 = t.combine_with_circle(x)
В частности, каждый существующий класс фигуры должен добавлять возможности потомку, что приводит к комбинаторной сложности и трудностям при сопровождении.
Напротив, метод множественной диспетчеризации более прост:
Листинг 6. Мультиметоды для расширения возможностей
# Base rules (stipulate combination is order independent)
class Circle(Shape): pass
class Square(Shape): pass
def circle_with_square(circle, square): ...
def circle_with_circle(circle, circle): ...
def square_with_square(square, square): ...
combine = Dispatch()
combine.add_rule((Circle, Square), circle_with_square)
combine.add_rule((Circle, Circle), circle_with_circle)
combine.add_rule((Square, Square), square_with_square)
combine.add_rule((Square, Circle),
lambda s,c: circle_with_square(c,s))
# Enhancing base with triangle shape
class Triangle(Shape): pass
def triangle_with_triangle(triangle, triangle): ...
def triangle_with_circle(triangle, circle): ...
def triangle_with_square(triangle, square): ...
combine.add_rule((Triangle,Triangle), triangle_with_triangle)
combine.add_rule((Triangle,Circle), triangle_with_circle)
combine.add_rule((Triangle,Square), triangle_with_square)
combine.add_rule((Circle,Triangle),
lambda c,t: triangle_with_circle(t,c))
combine.add_rule((Square,Triangle),
lambda s,t: triangle_with_square(t,s))
# Use the rules in application
c, t, s = Circle(...), Triangle(...), Square(...)
newshape1 = combine(c, t)[0]
newshape2 = combine(s, c)[0]
# discover 'x' of unknown type, then combine with 't'
newshape3 = combine(t, x)[0]
Определение новых правил (и поддержка функций/методов) в значительной степени эквивалентны. Но огромное преимущество стиля множественной диспетчеризации - это цельность, с помощью которой вы комбинировать фигуры неизвестных типов. Вместо того, чтобы возвращаться к явным (и длинным) условным блокам, определения правил автоматически решают эти вопросы. Что еще лучше, все комбинирование выполняется одним вызовом combine(), а не с помощью "зверинца" из разных комбинирующих методов.
Передача диспетчеризации
Не испытывая необходимости больше думать о диспетчеризации, класс multimethods.Dispatch будет выбирать "наилучшее совпадение" для данного обращения к диспетчеру. Однако, иногда стоит заметить, что "лучшее" не значит "единственное". То есть, обращение к dispatch(foo,bar) может давать точное совпадение с правилом (Foo,Bar) - но оно также может задавать менее точное совпадение (не промах!) для (FooParent,BarParent). Точно так, как иногда вы хотите вызывать методы базовых классов в методе производного класса, вы также иногда желаете вызывать менее специфические правила в диспетчере.
Модуль multimethods позволяет задавать вызовы менее специфических правил как грубо, так и с тонкой настройкой. На грубом уровне, обычно вы просто хотите автоматически вызывать менее специфичное правило в начале, либо в конце выполнения блока кода. Подобным образом вы практически всегда вызываете метод надкласса в начале, либо в конце тела метода потомка. Общий вариант начального/конечного вызова менее специфичных методов может быть задан просто как часть правила. Например:
Листинг 7. Автоматическое воспроизведение диспетчеризации
class General(object): pass
class Between(General): pass
class Specific(Between): pass
dispatch = Dispatch()
dispatch.add_rule((General,), lambda _:"Gen", AT_END)
dispatch.add_rule((Between,), lambda _:"Betw", AT_END)
dispatch.add_rule((Specific,), lambda _:"Specif", AT_END)
dispatch(General()) # Result: ['Gen']
dispatch(Specific()) # Result: ['Specif', 'Betw', 'Gen']
Разумеется, в некоторых ситуациях (как для правила (General)) менее специфичное правило отсутствует. Для обеспечения единообразия, однако, каждое обращение к диспетчеру возвращает список значений из всех функций, которым передается управление таким образом. Если в правиле не определены ни AT_END, ни AT_START, распространение вызовов не производится (и возвращается список из одного элемента). Этим объясняется индекс [0] в примере с фигурами, который, вероятно, кажется загадочным .
Для тонкой настройки распространения вызовов применяется метод диспетчера .next_method(). Чтобы задать распространение вызовов вручную, нужно использовать для определения правил метод .add_dispatchable(), а не метод .add_rule(). Кроме того, диспетчеризованные функции сами должны принимать аргумент dispatch. При вызове диспетчера вы либо должны передать аргумент, задающий диспетчер, либо вы можете воспользоваться вспомогательным методом .with_dispatch(). Например:
Листинг 8. Программирование с ручной передачей
def do_between(x, dispatch):
print "do some initial stuff"
val = dispatch.next_method() # return simple value of up-call
print "do some followup stuff"
return "My return value"
foo = Foo()
import multimethods
multi = multimethods.Dispatch()
multi.add_dispatchable((Foo,), do_between)
multi.with_dispatch(foo)
# Or: multi(foo, multi)
Вызов менее специфичных мультиметодов вручную может оказаться запутанным - примерно так же, как и обращение к методам базовых классов. Чтобы эти вопросы стали управляемыми, обращение к .next_method() всегда возвращает простой результат вызова верхнего уровня - если вы хотите собрать такие результаты в список, как тот, что создает аргумент AT_END, вам нужно добавлять и обрабатывать те величины, которые вы считаете уместными. Наиболее общий вариант использования, однако - выполнение последовательности связанных инициализаций; в этом случае возвращаемые величины обычно неважны.
Замечания выполнении в многонитевой среде
Стоит привести краткое замечание, пока читатель не столкнулся с проблемой. Из-за необходимости сохранения состояния для отслеживания, какие (последовательно менее специфичные) правила вызывались, диспетчер не является нитебезопасным. Если нужно использовать диспетчер в многонитевой среде, необходимо "клонировать" его для каждой нити. Это ненакладно с точки зрения ресурсов: памяти и процессора, так что клонирование диспетчеров не вызывает существенных неудобств. Например, предположим, что функция могла бы вызываться из разных нитей; вы можете написать:
Листинг 9. Клонирование для безопасности нити
def threadable_dispatch(dispatcher, other, arguments)
dispatcher = dispatcher.clone()
#...do setup activities...
dispatcher(some, rule, pattern)
#...do other stuff...
Если внутри threadable_dispatch() не запускаются новые нити, все нормально.
Вам потребуется некоторое время, чтобы освоиться с идей множественной диспетчеризации, даже - или особенно - если вы весьма опытны в объектно-ориентированном программировании. Но после того, как вы немного с ней поэкспериментируете, вероятно, вы обнаружите, что множественная диспетчеризация обобщает и усиливает преимущества, которыми ООП обладает прежде всего над процедурным программированием.
Ресурсы
- Вы можете получить multimethods как отдельный модуль, либо как часть пакета Gnosis Utilities.
- Gnosis Utilities выходит как Питоновский пакет distutils.
- Другие языки реализовали множественную диспетчеризацию либо в самом языке, либо в библиотеках. Например, MultiJava - расширенный набор Java, который реализует множественную диспетчеризацию.
- CLOS и Dylan используют множественную диспетчеризацию в качестве базиса своей системы ООП. Возможно, "Обсуждение подхода Dylan" (a discussion of Dylan's mechanism) покажется вам интересным.
- В Perl есть модуль под названием Class::Multimethods, предназначенный для реализации множественной диспетчеризации (и, по-видимому, предполагается, что в Perl 6 эта концепция будет более глубоко встроена в язык). Дэмиан Конуэей рассматривает этот модуль (Damian Conway discusses his module).
- Ресурсы для разработчиков Linux в зоне Linux developerWorks.
Об авторе
Дэвид Мертц предчувствует, что программисты, страдающие синдромом раздвоения личности, захотят, чтобы все их функции были общими. Дэвид доступен по адресу: mertz@gnosis.cx, а жизнь его описана на http://gnosis.cx/publish/. Присылайте свои замечания и предложения касательно этой, прошлых или будущих статей.
Delphi. Эмуляция нажатия клавиши Enter
Перемещение фокуса курсора между элементами управления окон в операционной системе Windows, как известно, выполняется по нажатию клавиши Tab. Из-за расположения данной клавиши в левой части клавиатуры затруднено ее использование при заполнении большого количества данных в форме. Особенно это неудобство проявляется, когда в левой руке держишь оригинал документа.
Решается данная проблема достаточно просто.
Воспользуемся системным сообщением WM_NEXTDLGCTL, которое перемещает фокус
курсора на следующий (или предыдущий, в зависимости от параметров) элемент
управления. Для генерации данного сообщения добавим общий для формы
обработчик нажатий клавиш FormKeyPress. После чего назначим данный обработчик
на все элементы управления.
В общем случае решение об изменении
поведения обработчика нажатий клавиш должен принимать пользователь. Для
определения разрешения на перехват нажатия клавиш введем глобальную переменную
gEnterToTab, значение которой можно устанавливать в отдельной форме
настроек. В листинге приведен типовой код такого обработчика нажатия
клавиш.
procedure TfrmConstrObject.FormKeyPress(Sender: TObject; var
Key: Char); begin if (Key=#13) and (gEnterToTab)
then begin //Enter->Эмуляция нажатия клавиши Tab if
(ActiveControl.Tag in [1,2]) then begin Key := #0;// "съедаем" код
клавиши PostMessage( Self.Handle, WM_NEXTDLGCTL, 0, 0); end else if
(ActiveControl.Tag = 3) and (Not TDBLookupComboBox(ActiveControl).ListVisible)
then begin Key := #0;// "съедаем" код
клавиши PostMessage(Self.Handle, WM_NEXTDLGCTL, 0, 0) end end else
if (Key=#38) and (gEnterToTab) then //Up->Эмуляция нажатия
Shift+Tab //для движения на предыдущий элемент управления if
ActiveControl.Tag<>2 then //На первом элементе
останавливаемся PostMessage( Handle, WM_NEXTDLGCTL, 1,
0); Application.ProcessMessages; end;
Особенностью данного
примера является использование свойства Tag для идентификации элементов
управления. Дело в том, что некоторые элементы управления требуют
дополнительного анализа текущего состояния. Например, для элемента
TDBLookupComboBox нужно предварительно проверить, не развернут ли список
выбора элементов. И в случае, если список развернут, оставляем стандартный
обработчик. В данном примере реализован также перехват нажатия
клавиши Up, а также эмуляция нажатия клавиш Shift+Tab. Но на практике для
возврата на предыдущий элемент управления пользователи чаще всего пользуются
мышкой.
MySQL — “зеленым” админам
Данная статья не является полным руководством по администрированию
MySQL-сервера, здесь скорее представлены первичные, самые необходимые данные
для новичков. А посему рассматриваться будет сервер для ОС Windows, хотя
большинство команд справедливы и для *nix-систем. В работе используется в
основном консоль, т.к. команды, вводимые в консоли, можно легко перенести,
допустим, в web-интерфейс (например, в PHP для этого существуют специальные
функции).
Для чего нужна база данных? Реляционные базы данных
представляют собой не что иное, как просто несколько таблиц с записями. СУБД
предназначены для управления этими таблицами, т.е. создания новых таблиц,
удаления ненужных, вставки в таблицы новых записей, изменения записей и,
конечно же, обработки запросов, а также некоторых иных, менее часто
используемых функций. Собственно, ведь и базы данных необходимы для того,
чтобы оперативно выдавать информацию, причем в определенном порядке. СУБД
существует великое множество (хотя бы всем известная Microsoft Access). Но на
принципах работы мы останавливаться не будем. Пожалуй, наиболее
распространенной СУБД в Интернете является MySQL, которая из-за своей быстроты
и удобства в использовании получила широкое распространение. Да, и большое
значение имеет то, что она совершенно бесплатная, т.е. распространяется по GNU
GPL (в общем это значит, что вы можете скачать исходные коды и откомпилировать
их у себя или вообще сделать СУБД "под себя", если, конечно, являетесь
достаточно грамотным программистом. Правда, если это так, данная статья не для
вас:-)).
Итак, в теперешней "лабораторной работе" понадобится:
компьютер, собственно сам сервер СУБД MySQL, ярлык консоли под рукой
(собственно, он всегда должен быть под рукой: хоть удобство работы под cmd и
значительно уступает работе в консольке в Linux, но очень много вещей можно
сделать из консоли быстрее, чем лезть через 3-5 менюшек за нужной опцией).
Много вещей будет делаться именно через консоль. Конечно, существует множество
"юзеро-ориентированных" программ, предназначенных для управления сервером, но
рассматривать какую-либо одну не имеет смысла. Если вы будете знать основные
команды, использовать MySQL в web-приложениях станет не просто легко, а очень
просто! Установка MySQL. Устанавливать и использовать мы будем MySQL-сервер
3.23.38. Зачем вообще нужен SQL? На первый взгляд, для создания более-менее
приличного сайта не требуется особых наворотов (каковыми некоторые считают PHP
и SQL), но даже просто лог счетчика после пары-другой тысяч "оборотов" вам
смотреть интересно не будет. Ежели в лог счетчика писать только время-дату, IP
и имя компьютера посетителя, то лог в несколько тысяч строчек не только
вытягивать каждый раз с сервера надоест, но и оперативно посмотреть, кто был,
допустим, в ночь с четверга на пятницу на вашем, безусловно, замечательном
сайте, будет очень трудно. А форум писать только, допустим, на PHP не просто
довольно сложно, но и "неприятно".
Итак, инсталляция. Как таковая
инсталляция не требует особо больших танцев с бубном. Жмем энное количество
раз на кнопочку Next — и наслаждаемся. Пару слов о том, как хранятся данные в
MySQL. По умолчанию каждая база данных хранится в отдельной папочке, название
которой совпадает с названием этой базы данных, которые, в свою очередь,
хранятся (опять же, по умолчанию) в папке DATA директории, в которой
установлен сервер. Изменить дефолтовое местоположение данных можно следующим
образом. Например, вы хотите создать базу данных, которая будет храниться,
скажем, в E:/data/database_name, где database_name — имя базы данных. Тогда вы
создаете файл database_name.sym в директории DATA и записываете в него всего
одну строчку, которая содержит путь к местоположению вашей базы данных:
"E:/data/database_name", и сохраняете. Если заглянуть в папки, в которых
хранятся данные, то вы увидите там 3 файла. Данные хранятся в
"оптимизированном" виде, т.е. не в виде простого текста — так просто их
просмотреть не удастся. Итак, три файла: с расширениями .frm — в нем хранится
информация о таблицах, содержащихся в БД, .myd — собственно данные и .myi —
индексные данные. Теперь займемся безопасностью. Данные о пользователях MySQL
хранит в специальной базе данных, которая называется mysql (оригинально,
ничего не скажешь:-)). В ней 5 таблиц, в которых хранятся логины и пароли в
зашифрованном виде, а также атрибуты доступа к таблице. Эти таблицы
называются:
В таблицах columns_priv, tables_priv хранятся
привилегии пользователей для доступа, соответственно, к колонкам и таблицам, в
db — привилегии для доступа к базам данных, в host — информация о компьютерах,
с которых подключаются юзвери к MySQL, и, наконец, в таблице user хранится
информация о пользователях. Более подробные сведения можно получить из
мануала, который есть в установочном пакете (после установки он находится в
mysql/Docs). Это почти "двухметровый" документ, в котором написано практически
все, что нужно знать на первых порах, и не только на первых:-). Нас
интересует, каким образом сменить юзверя с логином root (те, кто знаком с
*nux, знают, что это такое, а для остальных поясняю: суперпользователь с
неограниченными правами), а также сделать юзверя, из-под имени которого мы
будем работать с сервером. В идеале такой пользователь должен быть только
один. Остальные пользователи должны быть с ограниченными правами (главный
принцип администрирования — что не разрешено, то запрещено). Дело в том, что
по умолчанию суперпользователь не имеет пароля!! И если вы, например, занесете
в свою базу данных какие-то сверхсекретные сведения, посмотреть, удалить,
изменить их сможет каждый:-). Итак, сервер мы установили, начинаем
администрирование. Находим директорию, в которую мы установили наш сервер (по
умолчанию это C:\mysql). Находим директорию C:\mysql\bin и видим множество
"экзешников". Для начала нам потребуется mysql.exe. Запускаем. Да, опции
запуска из консоли можно посмотреть, написав что-то вроде
C:\mysql\bin\mysql.exe -h, после чего будет выведена справка. Но пока они нам
не понадобятся. Все sql-команды вводятся в командной строке и должны
завершаться ";" или "\g". Для того, чтобы использовать базу данных, необходимо
сначала ее выбрать. Для этого существует команда USE. Нам необходима БД
"mysql": "mysql> use mysql;". Для того, чтобы посмотреть, какие базы данных
имеются на сервере, существует команда "mysql> show databases;". Просмотр
имеющихся таблиц так же прост, как и просмотр имеющихся БД: "mysql> show
tables from databaseNAME;", где databaseName — имя базы данных. Посмотрим, кто
у нас числится в юзверях нашего сервера. Пишем "mysql> select
user,host,password from user;", после чего получим список юзверей с названиями
компьютеров, с которых им можно подключаться к серверу. Прежде, чем вы начнете
экспериментировать с правами доступа к базам данных, советую вам скопировать
папку mysql (с файлами соответствующей базы данных) куда-нибудь в отдаленное
место для того, чтобы, если что-то не получится, можно было бы
"откатиться".
Команда SELECT — она из самых частоиспользуемых
команд. С ее помощью мы можем показать, какие данные нас интересуют, и
попросить вывести ее на экран. Синтаксис ее таков: "SELECT
интересующие_столбцы_в_таблице FROM название_таблицы <опции>;" Вместо
"интересующие_столбцы_в_таблице" можно поставить "*", тогда нам будут выданы
значения всех колонок, которые присутствуют в таблице. Чаще всего вместо
"<параметры>" пишут еще одно ключевое слово — WHERE. Синтаксис таков:
"WHERE название_колонки_таблицы='значение'". Так, теперь пишем:
"mysql>UPDA-TE user SET Password=PASSWORD ('NEWpasword') WHERE
user='root';", где NEWpassword — новый пароль для суперпользователя. К слову,
таким образом можно изменить пароль для любого юзверя. Немного поясню, что
значат эти команды. Функция PASSWORD('string') шифрует строку по определенному
алгоритму, а команда UPDATE изменяет значение записи WHERE (где) в таблице
user. К слову, все пароли в MySQL хранятся в зашифрованном виде. Теперь нам
нужно запретить суперпользователю подключаться с удаленных компьютеров. Для
этого удаляем строчки со знаком "%". Пишем "DE-LETE FROM user WHERE
Host='%';". Команда DELETE имеет похожий синтаксис с командой SELECT: "SELECT
FROM название_таблицы WHERE название_колонки_таблицы='значение';". Я надеюсь,
понятно, что команды необходимо набирать без кавычек. Да, последней командой
мы также удалили и "пустого" юзверя. Теперь у нас только два пользователя с
неограниченными правами: root и mentalzavr, пароль которого, к слову, вы
должны были задать при первой загрузке WinMySQLAdmin (теперь он должен висеть
в трее этаким светофором, зеленый свет на котором означает нормальную работу
сервера, желтый — переходное состояние, а красный — останов сервера). Для
того, чтобы наши изменения вступили в силу, необходимо либо перезапустить
сервер, либо дать команду "FLUSH PRIVILEGES;". Дело в том, что большинство
серверов самого различного назначения читают свой конфиг, где хранится
большинство их настроек, только один раз при загрузке. Так и сервер MySQL
читает базу данных с параметрами доступа к базам данных при загрузке либо
принудительно после специальной команды. Перезапустить сервер можно из панели
управления, если вы работаете в Windows NT-серии, к которой относятся Windows
XP, 2000 и собственно сама NT:-), либо из той же самой пресловутой командной
строки. Сначала посмотрим, как называется наша служба. Для этого воспользуемся
так называемой расширенной консолью WMIC (WMI Command-line). Если вы до этого
ею не пользовались, система вам ее установит (XP). Это очень мощный
инструмент. Но не тема данной статьи:-). Итак, пишем "wmic:root\cli>service
list". Видим название службы "MySql". Выходим из WMIC: "wmic:root\cli>
exit". Теперь останавливаем службу "C:\>net stop mysql" и стартуем снова:
"c:\>net stop mysql". Вы спрашиваете, зачем я все это рассказываю? Одна из
причин — таким же образом можно останавливать и запускать любые другие службы.
А вторая... Ну, представьте, что у вас стоит сервер MySQL где-нибудь в
локальной сети, а ваш компьютер находится в другом здании, и вам необходимо
срочно переконфигурировать его. Бежать к серверу и вживую его настраивать? Ну,
вы как хотите, а мне лично лень. Коннектимся к серверу и удаленно админим:-).
Кстати, программка mysql.exe, которой мы пользовались для подключения к
серверу (а именно это мы и делали), может работать и с удаленным сервером. Это
легко увидеть, запустив mysql.exe следующим образом: "c:\>mysql -h
IP_adresss", где вместо "IP_address" — либо имя компьютера, к которому мы
подключаемся, либо его IP-адрес:-). Ну вот, сервер мы перезапустили (или
просто написали "FLUSH PRIVILEGES;"). Теперь, ежели все правильно, вы не
сможете подключиться к своему серверу, просто запустив mysql.exe. Необходимо
ввести пароль. Делается это таким образом: "c:\>mysql -u root -p". Вас
попросят ввести пароль на root, которые мы с вами задавали выше. Если все
нормально, появится приглашение "mysql>", как и ранее. Теперь попробуем
создать еще юзверей. Делается это так. Создадим троих юзверей с разными
правами: " mysql> GRANT ALL PRIVILEGES ON *.* TO
ferst@localhost IDENTIFIED BY 'some_pass' WITH GRANT
OPTION; mysql> GRANT ALL PRIVILEGES ON *.* TO
ferst@"%" IDENTIFIED BY 'some_pass' WITH GRANT
OPTION; mysql> GRANT RELOAD,PROCESS ON *.* TO
admin@localhost; mysql> GRANT USAGE ON *.* TO
doom@localhost; " Кстати, если вы дадите команду "mysql> status",
сервер вам выдаст некоторое количество полезной информации. Итак, мы создали
трех юзверей. Первый — ferst — получает все права и может подключаться к
серверу как с локального компьютера, так и удаленно, пароль для доступа ему
назначен sam_pass. Второй юзверь — admin — получает права на reload и refresh,
но может подключаться только с локального компьютера (что означает значок
"%"), пароль на вход не назначен. Третий юзверь с именем doom может
подключаться с локального компьютера, и он создан, так сказать, "бесправным",
т.е. все глобальные привилегии ему не даны. То же самое можно было бы сделать
по-другому. Например, вот так:
" mysql> INSERT INTO user VALUES
('localhost','ferst',PASSWORD('some_pass'), 'Y','Y','Y','Y','Y','Y','Y',
'Y','Y','Y','Y','Y','Y','Y'); mysql> INSERT INTO user
VALUES('%','ferst',PASSWORD('some_pass'), 'Y','Y','Y','Y','Y','Y','Y',
'Y','Y','Y','Y','Y','Y','Y'); mysql> INSERT INTO user SET
Host='localhost',User='admin', Reload_priv='Y', Process_priv=
'Y'; mysql> INSERT INTO user(Host,
User,Password) VALUES('localhost','doom',''); mysql> FLUSH
PRIVILEGES; " Наверное, необходимо пояснить, что это значит. Команда
INSERT вставляет строчку в таблицу, название которой следует после слова INTO,
в скобках даны названия столбцов, имеющихся в этих таблицах. После VALUES
следуют значения, которые заносятся в соответствующие колонки. Значения
необходимо заключать в кавычки. Последняя команда обновляет права доступа.
Буква 'Y' означает включение соответствующей привилегии, 'N' — соответственно
выключение. Таблица user имеет следующие столбцы (даны в том же порядке, что и
в реальности): "| Host | User | Password | Select_priv | Insert_priv |
Update_priv | Delete_priv | Create_priv | Drop_priv | Reload_priv |
Shutdown_priv | Process_priv | File_priv | Grant_priv | References_priv |
Index_priv | Alter_priv |"
Я думаю, что в пояснениях данная таблица не
нуждается. Названия колонок говорят сами за себя (если вы, конечно, знаете
английский, как я — на уровне 5-го класса хотя бы:-)). Естественно, что, ежели
в предыдущих командах буквы 'Y' заменить на 'N', соответствующий пользователь
соответствующего права и не получит:-). Теперь разберемся, каким образом можно
изменять права юзверей при подключении к базе данных. Для этого существует
команда GRANT. " mysql> GRANT SELECT,INSERT,UPDATE,
DELETE,CREATE,DROP ON
bankaccount.* TO custom@localhost
IDENTIFIED BY 'stupid'; mysql> GRANT SELECT,INSERT,UPDATE,
DELETE,CREATE,DROP ON expenses.*
TO custom@whitehouse.gov IDENTIFIED BY
'stupid'; mysql> GRANT SELECT,INSERT,UPDATE,
DELETE,CREATE,DROP ON customer.*
TO custom@'%' IDENTIFIED BY 'stupid'; " Поясню,
что делают эти команды. После команды GRANT следует список привилегий, которые
необходимо предоставить юзверю custom. После слова ON идет название базы
данных, после точки можно указать конкретную таблицу в этой базе данных, а
звездочка означает "любая". А после слова TO идет имя_юзверя@имя_ компьютера,
с которого этот юзверь сможет подключаться. Знак процентов означает
"любой_компьютер". После слов IDENTIFIED BY следует указать в кавычках пароль,
который будет требоваться у юзверя при входе. В данном случае это 'stupid'.
Конечно, подобного результата можно добиться и непосредственной модификацией
таблицы user. Например, вот так:
" mysql> INSERT INTO user
(Host,User, Password) VALUES('localhost','custom',
PASSWORD('stupid')); " Я думаю, понятно, что делает данная команда. Она
вставляет в таблицу user запись со значениями в столбцах, которые записаны
после слова VALUES. Соответствующей модификацией этой команды можно достичь
тех же результатов, что и использованием команд, изложенных выше. И не
забудьте после того, как внесете изменения, дать команду FLUSH PRIVILEGES;.
Теперь, после того, как вы успешно создали юзеров, переходим к более
интересной части — собственно созданию базы данных. Для создания базы данных
необходимо из той же директории, что и программу mysql.exe, запустить
программу mysqladmin.exe. Если вы запустите эту программу просто без опций,
она выдаст вам список параметров запуска. Собственно, и все использование этой
программы сводится к запуску ее с соответствующими опциями. Итак, нас прежде
всего интересует создание и удаление баз данных, а также подключение через
логин и пароль к удаленному серверу (лень — воистину двигатель прогресса!).
Подключаемся к удаленному серверу hostname с использованием логина root
пароля, который у нас после спросят.
"C:\mysql\bin>mysqladmin -h
hostname -u root -p OPTIONS". Вместо OPTIONS необходимо написать собственно
то, что мы хотим сделать. Создание базы данных с именем database_name
"C:\mysql\bin>mysqladmin -h mentalzavr -u mentalzavr -p create
database_name". Удаление базы данных database_name после подтверждения и ввода
пароля: "C:\mysql\bin>mysqladmin -h mentalzavr -u mentalzavr -p drop
database_name". Существуют также и другие параметры запуска, на которых мы
особо останавливаться не будем, хотя они не менее важны. Сведения о них вы
получите, как я вам уже сказал, запустив mysqladmin без опций. Итак, мы
создали базу данных. Теперь необходимо создать таблицы, в которых, собственно,
и будут храниться данные. Любая таблица должна относиться к какой-либо базе
данных. Поэтому запускаем mysql.exe и выбираем только что созданную базу
данных "mysql> use database_name" Для создания таблицы существует команда
CREATE. Синтаксис таков: "mysql>create table table_name(name int(3),name
char(15));", где table_name — имя таблицы, после которой в скобках через
запятую указаны последовательно имя столбца и его тип. Конечно, столбцов может
быть и больше, чем два. Цифры в столбцах показывают размер значения в столбце.
Ну, и для того, чтобы удалить таблицу, соответственно, необходимо написать:
"mysql>drop table table_name" Таблиц в базе данных может быть несколько.
Теперь вкратце остановимся на типах данных в таблице. Их довольно много, и
поэтому я думаю на первых порах (а данная статья предназначена для
предоставления только НАЧАЛЬНЫХ сведений об администрировании СУБД MySQL, и ни
в коем разе не является полным руководством) ограничиться нижеизложенным.
Пожалуй, наиболее часто встречающимся полем в таблицах является порядковый
номер. Это довольно удобно. Так, в MySQL существует возможность создать
таблицу со столбцом, значение которого будет изменяться на единицу с каждой
новой записью. Теперь о том, как это делается. Как обычно, пишете в
консоли: " mysql>use test; mysql> CREATE TABLE table_name
(No-mer int auto_increment,text char (10),key (Nomer)); " Данные команды
выбирают в качестве текущей базу данных test и создают таблицу с именем
table_name с двумя колонками. Первая колонка имеет атрибут Extra
"auto_increment". Это значит, что при добавлении записи в эту таблицу значение
(число) в этом столбце будет автоматически увеличиваться на единицу.
Посмотреть свойства колонок таблицы можно следующим образом (дав
соответствующую команду): "mysql> desc table_name;". Так, теперь
коснемся остальных типов данных. Числовые. Их присутствует большое количество,
основными являются (на мой взгляд) INT и FLOAT. Те, кто знаком хотя бы немного
с языком программирования С++, поймут сразу же, что первый из них — это
целочисленный тип данных. FLOAT — тип данных, используемый для хранения чисел
с плавающей точкой. Типы данных даты и времени. К ним относятся DATA, TIME,
YEAR, а также некоторые другие. Формат хранения данных в типе DATA —
"YYYY-MM-DD", в типе TIME — "HH:MM:SS", в типе YEAR — "YYYY". И практически
основным типом данных является CHAR. К текстовым типам также относятся TEXT
(65535 символов максимально) и некоторые другие.
И в заключение я хотел
бы дать несколько полезных команд, которые помогут вам использовать консоль
более эффективно. Все советы действительны для cmd.exe — командного процессора
Windows. Для начала вам необходимо включить использование мыши в консоли. Для
этого щелкните правой кнопкой мыши на заголовке открытого окна консоли и
выберите Свойства. И на первой закладке поставьте галочки напротив пунктов
Выделение мышью и Быстрая вставка, а также напротив Отбрасывать повторения.
Теперь вы можете в консоли использовать все прелести работы с буфером обмена.
Выделение мышью происходит как обычно, копирование в буфер обмена — при
нажатии на правую кнопку. Вставка производится в место нахождения курсора
также правой кнопкой. Пара слов об автоматизации процесса первичного занесения
данных в таблицы. Если вы создаете базу данных с нуля, то никаких проблем
практически не возникает (ну, если не считать большого количества
организационной работы и непосредственно кодинга, в процесс которого входит
создание хотя бы web-интерфейса:-). Конечно, пару-другую строчек можно занести
и в консоли. Ну, а ежели у вас имеется какой-то текстовый файл с записями,
данные из которых нужно перевести в БД MySQL? Тут воевать только буфером
обмена с бесконечным повторением действий "копирование-вставка" может только
ну очень большой… трудоголик. Ну, а мы как умные люди можем поступить проще
(правда, все в этом мире относительно). Дело в том, что можно в качестве
аргумента в команде "mysql> \. File_name" передать имя файла "File_name", и
этот файл будет выполнен. Что он собой представляет? Это просто
последовательный список SQL-команд. Теперь нам остается только сгенерировать
такой файл, что в каждом отдельно взятом случае делается по-разному — тут
нужно смотреть по обстановке. Конечно, можно пойти еще дальше и написать
программу для автоматического занесения данных в БД. Но для простых задач
можно просто даже текстовый файл с написанными в нем командами скопировать в
буфер и вставить в приглашение MySQL. "Вводы" будут восприниматься как знак
завершения команды. Только не забывайте ставить в конце каждой строчки ";",
ведь в mysql.exe команды можно вводить и в многострочном режиме, поэтому могут
случаться накладки. Правда, есть и альтернативный способ. Если у вас есть
текстовый файл с записями в виде таблицы, и они в нем разделены при помощи
запятых и знаков окончания строки либо каких-то иных символов (тогда следует
внести изменения в параметры следующей команды, заменив запятую и "\n" на
соответствующие знаки), можно использовать команду "mysql> LOAD DATA INFILE
"data.txt" INTO TABLE my_table FIELDS TERMINATED BY ',' LINES TERMINATED BY
'\n';". Результатом данной команды должна явиться таблица. В файле поля должны
быть разделены запятыми, а каждая запись — начинаться с новой строки. Следует
помнить еще и о такой вещи, как кодировка. Если все данные вносить через
консоль, то они будут храниться в DOS-кодировке, и при выводе таких данных —
например, если в html-странице используют PHP — вы увидите "абракадабру".
Чтобы этого не происходило, заносите данные только в одной кодировке или
перекодируйте их с помощью специальных функций (для PHP это функция
$text_after=convert_cyr_string ($text,"w","d"), где два последних параметра
указывают соответственно на то, что исходная кодировка — Windows, а конечная
после перекодировки — DOS). И напоследок. Гораздо приятней работать с
консолью, так всеми нелюбимой, ежели сделать что-то вроде этого: "C:\>title
LINUX". На несведущих людей действует довольно
своеобразно:-).
Создание больших web-проектов
У любого успешного web-проекта
рано или поздно возникает проблема роста.
Существующие программно-аппаратные ресурсы
перестают справляться с растущей нагрузкой.
Универсальных рецептов, к сожалению не существует.
В каждом проекте хороший программист будет
программировать по-разному. Тем не менее, в этой
статье я попробую дать несколько типичных
рекомендаций по созданию больших web-проектов.
Такие проекты в процессе создания и развития
сталкиваются, как правило, с двумя почти
противоположными по способам решения проблемами -
большими скоростями и большими объемами
данных.
Большие скорости
В качестве идеального примера сайта, для
которого жизненно важна скорость, можно взять
баннерную сеть. Итак, несколько приемов для
ускорения работы баннерных сетей и других
серверов, критичных к скорости работы.
Создание модулей
Смысл этого приема -
вкомпилировать наиболее важные функции в сервер.
Идея очень проста. Если мы посмотрим на
соотношение времени, которое тратится на различные
стадии выполнения запроса, то увидим интересную
картину. Например, при выполнении простейшего
perl-скрипта последовательно происходит
следующее:
1) сервер Apache определяет
perl-скрипт для запуска, подготавливает и
запускает его; 2) запуск скрипта фактически
начинается с запуска perl-интерпретатора (это
файл, размером около полумегабайта).
Perl-интерпретатор, запустившись, размещается на
2-х мегабайтах в памяти машины, и только после
этого приступает к работе с пользовательским
скриптом; 3) эта работа начинается с компиляции
программы. Компиляция программы - это, как
правило, один из самых длительных этапов обработки
программы; 4) только после предварительной
компиляции (в байткод) скрипт начнет
выполняться.
Статистика удручает:
время, которое тратится на запуск
perl-интерпретатора и компиляцию скрипта, как
правило, на порядок больше времени, за которое он
выполняется. На каждом сайте существуют
узкие места - программы, которые вызываются очень
часто. Например, баннерный движок. Как правило, на
один просмотр страницы приходится два-три баннера,
а значит и вызова программы. Понятно, что если
избавиться от накладных расходов (пункты 2 и 3),
работа сервера значительно ускорится. Это можно
сделать двумя похожими способами. Первый -
написать модуль к Apache и вкомпилировать его в
сервер. Именно так в баннерной сети Фламинго-2
(http://www.f2.ru), в создании которой я принимал
участие, была реализована часть системы, которая
раздавала баннеры пользователям. Это был модуль,
написанный на языке C, который функционировал как
часть сервера Apache и поэтому работал очень
быстро. Второй способ - использовать технологии
предкомпиляции программ. Таких технологий
достаточно много. Например, для perl-скриптов это
могут быть FastCGI и mod_perl. Расскажу подробней
о mod_perl. Это вкомпилированный (опять же в виде
модуля) в Apache perl-компилятор. Во-первых, даже
для простых скриптов (при надлежащей настройке)
это исключает вторую стадию выполнения. Но кроме
этого mod_perl дает возможность писать хэндлеры -
обработчики определенных стадий выполнения
запроса. Это очень мощная технология, поэтому
рассмотрим ее подробнее. Можно, например,
написать хэндлер, который будет вызываться при
запросе определенного URL. Делается это так. В
файл httpd.conf вы прописываете следующие строки:
<Perl>
unshift(@INC,
'Путь к Вашему модулю');
@PerlModule =
qw(MyHandler);
%Location =
( '/myhandler' => { 'PerlHandler' =>
'MyHandler::view', 'SetHandler' =>
'perl-script', 'PerlSendHeader' =>
'on' }, );
</Perl>
Тем самым вы указываете Apache и
модулю mod_perl, что если пользователь запросит
URL /myhandler, то для его обработки должен
запуститься модуль MyHandler, а в нем процедура
view. После изменения httpd.conf надо
перезагрузить Apache. Кстати, все указанные в
конфигурационном модуле файлы будут
компилироваться при загрузке сервера, а не при
первом запросе. Это в несколько раз увеличит
скорость работы сервера. Модуль MyHandler.pm
может выглядеть, например, так:
package
MyHandler; use strict;
# Процедура
view sub view { print
"<HTML>\n<BODY>\nУра! Это отработал
наш
хэндлер!</BODY>\n</HTML>\n"; }
1;
Механизм хэндлеров обладает
мощными возможностями. Фактически вы можете
заменить любую стадию обработки запросов.
Рассмотрим для примера создание собственного
механизма проверки пароля:
package
MyAuthorization; use strict;
#
Обработчик, запрашивающий пароль sub handler
{ my $r = shift;
return AUTH_REQUIRED
unless $r;
my (undef, $password) =
$r->get_basic_auth_pw; my ($login) =
$r->connection->user;
return
AUTH_REQUIRED unless $password;
#
Проверяем, все ли в порядке # Проверка может
быть любой # Можно свериться с базой данных, а
мы будем считать, что пароль должен быть #
равен логину, прочитанному задом
наперед.
my $rev_login =
reverse($login);
# Проверка пароля if
($rev_login ne $passwd_sent) { return
AUTH_REQUIRED; } else { return
OK; }
};
1;
В файле настроек сервера
httpd.conf необходимо указать, что авторизовать
пользователя мы будем сами:
%Location =
( '/myhandler' => { 'PerlHandler' =>
'MyHandler::view', 'SetHandler' =>
'perl-script', 'PerlSendHeader' =>
'on' 'require' => 'valid-user', 'Limit'
=> { 'METHODS' => 'GET
POST' }, 'AuthType' =>
'Basic', 'AuthName' =>
'PersonaUser', 'PerlAuthenHandler' =>
'MyAuthorization ->handler()' }, );
Теперь доступ к /myhandler
защищен - браузер выведет пользователю стандартное
окно для ввода пароля. Более подробно с
технологией mod_perl можно познакомиться на сайте
http://perl.apache.org/
Использование конвейеров
Старайтесь не производить
обработку данных в интерактивных скриптах.
Записывайте их в лог-файлы, а затем агрегируйте и
обрабатывайте уже отдельным процессом. Например,
ответ пользователя в интерактивном голосовании
может вызывать у вас изменения в десятке различных
параметров статистики (распределение ответов,
активность пользователей, общее число
проголосовавших и так далее). Не проводите их
сразу. Вместо этого разбейте процедуру на две
части. Первая - непосредствен- но голосование,
запись результата и вывод ответной страницы
пользователю. Вторая - обработка голосования,
изменение статистики и т.д. Вообще надо
стараться минимизировать количество интерактивных
операций. В идеальном случае скрипт для учета
голосования вообще ничего не делает, кроме записи
информации в лог-файл. А для обработки данных из
лог-файла можно запускать отдельный процесс-демон.
Для примера рассмотрим механизм обработки
статистики в баннерной сети Фламинго-2. В ней был
реализован 4-х ступенчатый конвейер: 1)
Информация о каждом запросе записывалась в полный
лог. Это была очень подробная информация и
записывалась она без всякого сжатия, на которое
потратилось бы много времени. Размер этого лога
очень велик - одна запись в нем занимала 250 байт.
Данные в этом логе не хранились дольше нескольких
часов. 2) С периодичностью раз в 10 минут
запускалась программа, которая обрабатывала полный
лог и в компактном виде писала информацию в
таблицы базы данных. На этой же стадии учитывались
показы, изменялись временные таблицы, используемые
для выдачи баннеров пользователю и для работы
следующих стадий. 3) Часовой демон, который
строил почасовую статистику, производил сложные
географические расчеты и многое другое, запускался
в конвейере один раз в час. Он уже не имел доступа
к полному логу и использовал информацию
исключительно из второй стадии. 4) В задачи
последней стадии входила дневная ротация файлов,
статистика, подведение балансов и рассылка
почтовых предупреждений. Эта стадия работала
каждые сутки поздно ночью, когда нагрузка на
сервер была минимальной. Как видите,
механизм достаточно сложный, и наладить его
корректную работу было нелегко. Чем больше стадий,
тем больше проблем при их сопряжении друг с
другом. Тем не менее, такая система позволяла
достаточно эффективно распределять нагрузку и
шустро работала на простом IDE-диске (расчетная
пропускная способность была около 2-3 миллионов
обращений в день при пиковой нагрузке 200
обращений в секунду). При этом система вела
большое количество статистики. Итак,
резюмируем: для увеличения скорости работы
программ, взаимодействующих с пользователем,
разбиваем их работу на части, причем интерактивная
часть должна содержать минимум расчетов и операций
записи. Все необходимые расчеты можно произвести
позднее, в более благоприятное с точки зрения
нагрузки время и более эффективно.
Базы данных
Используйте хорошую базу данных.
Какую выбрать? Единого рецепта нет. Все зависит от
решаемой задачи. Если она достаточно простая и вам
не требуется выполнять сложные SQL-запросы
(например, вложенные), то наилучшим решением
будет, пожалуй, база данных MySQL.
MySQL - один из самых простых серверов
БД. Но даже в этой простой базе есть свои способы
оптимизации для ускорения запросов. Например, не
секрет, что INSERT - одна из самых длительных
операций (вычисление физического адреса для
вставки, вставка, решение проблемы фрагментации,
изменение индексов и служебных таблиц). Хороший
прием для ускорения работы скрипта, который
вставляет данные в БД - замена операции INSERT
операцией INSERT DELAYED (отложенная вставка).
Обновление данных будет выполнено только тогда,
когда это не приведет к замедлению работы сервера.
Другой пример: если внимательно почитать
документацию MySQL, можно найти упоминание о
таблицах, расположенных в памяти (HEAP tables).
Очевидно, что операции с такими таблицами
совершаются значительно быстрее. Heap-таблицы
можно использовать для решения некоторых задач.
Существует большое количество параметров
запуска сервера БД, оптимизирующих буферы
сортировки, вычислений, количество детей и другие
параметры. Как правило, вам заранее известно, что
вы будете делать с базой, и для повышения
быстродействия можно задать соответствующие
параметры. Например, возьмем вполне реальную
задачу: построение какого-нибудь каталога. Ясно,
что это будет одна большая таблица с большим
количеством индексов. Вы знаете, что будете
использовать представления. Работа с этой таблицей
будет заключаться в запросах по индексу без
использования сортировки. Посмотрим, как можно
настроить сервер БД на выполнение такой задачи
(пример из MySQL 3.23.25):
-
join_buffer_size - буфер для
создания представлений, по умолчанию равен
131072 байта;
-
key_buffer_size - буфер для
работы с ключами и индексами. Размер по
умолчанию - 1048540;
-
sort_buffer - буфер для
сортировки. По умолчанию - 2097116 байт.
Скорее всего, при увеличении
какого-то буфера, скорость выполнения связанной с
ним задачи увеличится. Исходя из нашей задачи, мы
увеличим буфер для работы с ключами (скорость
выборки значений из таблицы увеличится), уменьшим
буфер сортировки (уменьшится скорость сортировки)
и буфер представлений (уменьшится скорость работы
с представлениями). Строка запуска демона
MySQL будет выглядеть примерно так (конкретные
значения зависят от количества памяти в системе):
shell>safe_mysqld
-O key_buffer=8M -O sort_buffer=1M -O
join_buffer=16K
Резюмируем. При
использовании базы данных работу скрипта можно
значительно ускорить правильной настройкой сервера
БД. В руководстве базы данных MySQL есть
специальный раздел, посвященный оптимизации. За
более подробной информацией можно обратиться на
сайты: Разработчики MySQL -
http://www.mysql.com Разработчики PostgreSQL -
http://www.PostgreSQL.org/ Оптимизация MySQL -
http://www.mysql.cz/information/presentations/presentation-oscon2000-20000719/index.html
и http://support.ultrahost.ru/mysql_opt.php
Большие объемы
Еще одна проблема больших сайтов
- большой объем информации. Если не применять
никаких ухищрений, то поддержка простого
html-сайта в какой-то момент потребует слишком
много времени.
Объектно-ориентированное
программирование
О пользе
объектно-ориентированного подхода я уже
рассказывал . Повторю вкратце. Каждый, кто хоть
раз пробовал создавать динамические сайты, знает,
что во многом это - очень однообразная задача.
Гостевая книга, конференция, форма для отправления
комментариев, подписка, регистрация. Как правило,
эти скрипты слабо интегрированы и, в лучшем
случае, используют общую библиотеку с константами
и общими процедурами.
Однако если
перечислить сущности, с которыми имеют дело
вышеперечисленные скрипты, мы получим очень
интересные результаты:
-
Сущность "пользователь". Имеет
свое имя, фамилию, ник, пароль, электронный
адрес… Используется практически во всех скриптах
в разных ипостасях.
-
Сущность "сообщение". Вы можете
возразить, что сообщения везде разные. Ничего
подобного! Различаются формы представления
сообщений, а данные, структура полей и методы
обработки - одни. Автор, заголовок, тело - и так
во всех проектах.
Вот фактически и все сущности, с
которыми оперирует большинство скриптов на сайте.
Гостевая книга (она, кстати, сама может быть
объектом в более сложных проектах) представляет
собой цепочку объектов класса "сообщение". Форум
или конференция - те же сообщения, организованные
иерархически. Отправка письма владельцу сайта -
сообщение. Рассылка анонсов - перебор объектов
класса "пользователь" и отправка каждому объекта
класса "сообщение". Было бы эффективно описать
все эти объекты в одном месте, а потом строить из
них, как из кирпичиков, программы и скрипты,
просто вставляя вызовы объектов в код. К тому же,
единое пространство сообщений, пользователей и
других объектов значительно расширяет поле для
творчества. В этом и есть сущность объектного
подхода. Вы создаете множество объектов -
кирпичиков будущих программ - и из них строите
свои сайты. Кроме того, вы можете использовать
такие мощные методы ООП как наследование и
полиформизм, без которых уже немыслимо построение
крупных проектов.
Шаблонирование
Об этом я тоже расскажу вкратце;
возможно этому будет посвящена статья в одном из
следующих номеров "Программиста". Вернемся к
системе Фламинго. Как был организован интерфейс
этой баннерной сети? 400 видов статистики
соответствуют 400 страницам? Нет. Один
скрипт-шаблонизатор, которому передаются параметры
- номер статистики и другие данные: даты,
ограничения и т.д. По уникальному номеру
статистики скрипт считывал описание, которое
состояло из имени файла с псевдо-html и имен
файлов с SQL-запросами. Файл с описанием выглядел
так: 2:data/html/2.htx,data/queries/info.sql 9:data/html/9.htx,data/queries/ban-list-one.sql,data/queries/get-banners-list.sql 12:data/html/12.htx,data/queries/ban-getinfo.sql 38:data/html/38.htx,data/queries/acc-hosts-hits.sql 44:data/html/44.htx,data/queries/acc-getsites-today.sql
Общая схема очень проста -
выполнить все SQL-запросы и вставить результаты в
псевдо-html, получив таким образом полноценную
страничку, и выдать ее пользователю. Например, для
вывода статистики с номером 2 (информация об
аккаунте), требовалось выполнить SQL-запрос
data/queries/info.sql, результаты вставить в
data/html/2.htx. Результат вывести на экран. А
вот как обстояло дело подробнее. Первая задача -
формирование SQL-запроса. В него нужно вставить
идентификатор пользователя и другие параметры,
которые переданы скрипту. Типичный пример
SQL-запроса (data/queries/info.sql):
select AccountName, OwnerName, OwnerEmail, MainSite, SiteName from Accounts where AccountId
= <--AccountId-->
При разборе такого запроса
значение параметра вставлялось на место строки
<--ИмяПараметра-->. Существовали и
специальные параметры, например -
<--UserName--> - имя пользователя и
<--AccountId--> - вычисленный по имени
идентификатор аккаунта. Результат выполнения
полученного запроса заносился в html следующим
образом. Каждое полученное из базы данных значение
получало "имя", с помощью которого обозначалось
его местоположение в html-шаблоне. Имя было
составным. Первая часть - порядковый номер
SQL-запроса, вторая часть - индекс значения в
массиве результатов. Допустим, выполнялся
SQL-запрос с порядковым номером 1 (для примера
рассмотрим запрос data/queries/info.sql). Запрос
возвращал массив значений. Соответственно,
значение AccountName, возвращенное базой данных,
имело порядковый номер 0 в этом массиве. В
html-шаблоне место, куда необходимо было вставить
AccountName обозначалось как
<--1.1-->.
Кусочек HTML-шаблона
data/html/2.htx из нашего примера:
<TABLE BORDER=0
WIDTH=460> <TR> <TD
WIDTH="50%"> <FONT SIZE="-1"> Имя,
фамилия
ответственного: </FONT> </TD><TD> <INPUT
type="text" name="OwnerName" size=33
value="<--1.1-->"> </TD> </TR>
<TR> <TD> <FONT
SIZE="-1"> Электронный
адрес: </TD><TD> <INPUT
type="text" name="OwnerEmail" size=33
value="<--1.2-->"> </TD> </TR>
Несмотря на кажущуюся сложность
схемы, она имеет ряд преимуществ. С ее помощью мы
смогли за короткое время построить систему с более
чем 400 видами различных статистик. Впоследствии
для добавления новой статистики надо было только
написать SQL-запросы, нарисовать HTML-шаблон и
изменить конфигурацию скрипта-шаблонизатора. Новая
страница статистики появлялась в системе
автоматически.
Заключение
Я хотел бы еще раз повторить: нет
решений на все случаи жизни. Каждый раз, в каждом
проекте вам придется придумывать собственные
методы оптимизации быстродействия и удобства
работы. Я надеюсь, что приемы, о которых я
рассказал, пригодятся вам. Если у вас возникнут
какие-нибудь вопросы или уточнения, я готов
обсудить их с вами -
vbob@aha.ru
|