<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Блог Андрея Смирнова &#187; Qik</title>
	<atom:link href="http://www.smira.ru/category/development/qik/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.smira.ru</link>
	<description></description>
	<lastBuildDate>Wed, 24 Aug 2011 05:09:27 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.2.1</generator>
		<item>
		<title>UDF в MySQL, json или то, как забрать обновления данных из БД</title>
		<link>http://www.smira.ru/2010/10/30/mysql-udf-json-memcacheq/</link>
		<comments>http://www.smira.ru/2010/10/30/mysql-udf-json-memcacheq/#comments</comments>
		<pubDate>Sat, 30 Oct 2010 19:26:54 +0000</pubDate>
		<dc:creator>Андрей</dc:creator>
				<category><![CDATA[Qik]]></category>
		<category><![CDATA[Разработка]]></category>
		<category><![CDATA[json]]></category>
		<category><![CDATA[memcached]]></category>
		<category><![CDATA[mysql]]></category>
		<category><![CDATA[udf]]></category>

		<guid isPermaLink="false">http://www.smira.ru/?p=394</guid>
		<description><![CDATA[Иногда необходимо забирать данные из БД MySQL в режиме реального времени во внешнюю систему, которая никак не связана с MySQL. Существует множество возможных решений, например, можно реализовать &#171;слейва&#187; MySQL, который бы хранил полученные данные во внешней системе. Одно из возможных решений &#8211; сделать &#171;выгрузку&#187; данных из MySQL с помощью UDF (User Defined Functions) и триггеров. [...]]]></description>
			<content:encoded><![CDATA[<p>Иногда необходимо забирать данные из БД MySQL в режиме реального времени во внешнюю систему, которая никак не связана с <a href="http://www.mysql.com/">MySQL</a>. Существует множество возможных решений, например, можно реализовать &laquo;слейва&raquo; MySQL, который бы хранил полученные данные во внешней системе.</p>

<p>Одно из возможных решений &#8211; сделать &laquo;выгрузку&raquo; данных из MySQL с помощью <a href="http://dev.mysql.com/doc/refman/5.1/en/adding-functions.html">UDF (User Defined Functions)</a> и триггеров. Для этого необходимо поставить слейв MySQL, на котором уже повесить на интересующие таблицы триггеры, которые с помощью UDF будут выгружать поток изменений таблиц во внешнюю систему. Слейв необходим, т.к. если триггеры поставить на мастере, то в случае отката транзакции действия, уже сделанные триггерами, откатить не получится, а на слейв попадают только зафиксированные транзакции. Второе,чтобы триггеры работали на слейве, тип репликации должен быть выставлен на <a href="http://www.smira.ru/2010/02/15/mysql-row-statement-mixed-replication-triggers/">STATEMENT-based</a>.</p>

<p>Порывшись в одном <a href="http://www.mysqludf.org/">интересном архиве</a> UDF для MySQL я нашел несколько функций, которые мне подошли:</p>

<ul>
<li>преобразование строки MySQL в json;</li>
<li>интерфейс с memcached.</li>
</ul>

<p>В результате получился следующий план действий: данные модифицируются на мастере, реплицируются на слейв с помощью STATEMENT-репликации. В процессе репликации на слейве запускаются триггеры, формируют с помощью UDF пакет обновлений в JSON, и передают его во внешнюю очередь (<a href="http://memcachedb.org/memcacheq/">memcacheq</a>) по memcached-протоколу. Конечно, это не единственный возможный способ, но все UDF уже были почти готовы. После доделывания напильником UDF получился вполне стабильно работающий вариант.</p>

<p>Триггеры выглядят примерно следующим образом:</p>


<div class="wp_syntax"><table><tr><td class="line_numbers"><pre>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
</pre></td><td class="code"><pre class="mysql" style="font-family:monospace;"><span style="color: #990099; font-weight: bold;">CREATE</span> <span style="color: #990099; font-weight: bold;">FUNCTION</span> kick_photos <span style="color: #FF00FF;">&#40;</span>row_id <span style="color: #999900; font-weight: bold;">INT</span><span style="color: #FF00FF;">&#41;</span> <span style="color: #990099; font-weight: bold;">RETURNS</span> <span style="color: #999900; font-weight: bold;">INT</span> 
<span style="color: #990099; font-weight: bold;">BEGIN</span> 
<span style="color: #990099; font-weight: bold;">SELECT</span> memc_set<span style="color: #FF00FF;">&#40;</span><span style="color: #008000;">'queue<span style="color: #008080; font-weight: bold;">_</span>db'</span><span style="color: #000033;">,</span> <span style="color: #FF00FF;">&#40;</span>json_object<span style="color: #FF00FF;">&#40;</span><span style="color: #008000;">'insert'</span> <span style="color: #990099; font-weight: bold;">AS</span> <span style="color: #990099; font-weight: bold;">action</span><span style="color: #000033;">,</span> <span style="color: #008000;">'photos'</span> <span style="color: #990099; font-weight: bold;">AS</span> table_name<span style="color: #000033;">,</span> photos.id <span style="color: #990099; font-weight: bold;">AS</span> id<span style="color: #000033;">,</span> json_members<span style="color: #FF00FF;">&#40;</span><span style="color: #008000;">'data'</span><span style="color: #000033;">,</span> json_object<span style="color: #FF00FF;">&#40;</span>photos.user_id <span style="color: #990099; font-weight: bold;">AS</span> <span style="color: #008000;">`user<span style="color: #008080; font-weight: bold;">_</span>id`</span><span style="color: #000033;">,</span>photos.width <span style="color: #990099; font-weight: bold;">AS</span> <span style="color: #008000;">`width`</span><span style="color: #000033;">,</span>photos.created_at <span style="color: #990099; font-weight: bold;">AS</span> <span style="color: #008000;">`created<span style="color: #008080; font-weight: bold;">_</span>at`</span><span style="color: #000033;">,</span>photos.filename <span style="color: #990099; font-weight: bold;">AS</span> <span style="color: #008000;">`filename`</span><span style="color: #000033;">,</span>photos.parent_id <span style="color: #990099; font-weight: bold;">AS</span> <span style="color: #008000;">`parent<span style="color: #008080; font-weight: bold;">_</span>id`</span><span style="color: #000033;">,</span>photos.content_type <span style="color: #990099; font-weight: bold;">AS</span> <span style="color: #008000;">`content<span style="color: #008080; font-weight: bold;">_</span>type`</span><span style="color: #000033;">,</span>photos.height <span style="color: #990099; font-weight: bold;">AS</span> <span style="color: #008000;">`height`</span><span style="color: #000033;">,</span>photos.thumbnail <span style="color: #990099; font-weight: bold;">AS</span> <span style="color: #008000;">`thumbnail`</span><span style="color: #000033;">,</span>photos.size <span style="color: #990099; font-weight: bold;">AS</span> <span style="color: #008000;">`size`</span><span style="color: #FF00FF;">&#41;</span><span style="color: #FF00FF;">&#41;</span><span style="color: #FF00FF;">&#41;</span><span style="color: #FF00FF;">&#41;</span><span style="color: #FF00FF;">&#41;</span> <span style="color: #990099; font-weight: bold;">INTO</span> @dummy <span style="color: #990099; font-weight: bold;">FROM</span> photos <span style="color: #990099; font-weight: bold;">WHERE</span> id <span style="color: #CC0099;">=</span> row_id<span style="color: #000033;">;</span> 
RETURN @dummy<span style="color: #000033;">;</span> 
<span style="color: #009900;">END</span>
&nbsp;
<span style="color: #990099; font-weight: bold;">CREATE</span> <span style="color: #990099; font-weight: bold;">TRIGGER</span> photos_INSERT <span style="color: #990099; font-weight: bold;">AFTER</span> <span style="color: #990099; font-weight: bold;">INSERT</span> <span style="color: #990099; font-weight: bold;">ON</span> photos <span style="color: #990099; font-weight: bold;">FOR EACH ROW</span> 
<span style="color: #990099; font-weight: bold;">SET</span> @dummy <span style="color: #CC0099;">=</span> memc_set<span style="color: #FF00FF;">&#40;</span><span style="color: #008000;">'queue<span style="color: #008080; font-weight: bold;">_</span>db'</span><span style="color: #000033;">,</span> <span style="color: #FF00FF;">&#40;</span>json_object<span style="color: #FF00FF;">&#40;</span><span style="color: #008000;">'insert'</span> <span style="color: #990099; font-weight: bold;">AS</span> <span style="color: #990099; font-weight: bold;">action</span><span style="color: #000033;">,</span> <span style="color: #008000;">'photos'</span> <span style="color: #990099; font-weight: bold;">AS</span> table_name<span style="color: #000033;">,</span> NEW.id <span style="color: #990099; font-weight: bold;">AS</span> id<span style="color: #000033;">,</span> json_members<span style="color: #FF00FF;">&#40;</span><span style="color: #008000;">'data'</span><span style="color: #000033;">,</span> json_object<span style="color: #FF00FF;">&#40;</span>NEW.user_id <span style="color: #990099; font-weight: bold;">AS</span> <span style="color: #008000;">`user<span style="color: #008080; font-weight: bold;">_</span>id`</span><span style="color: #000033;">,</span>NEW.parent_id <span style="color: #990099; font-weight: bold;">AS</span> <span style="color: #008000;">`parent<span style="color: #008080; font-weight: bold;">_</span>id`</span><span style="color: #000033;">,</span>NEW.created_at <span style="color: #990099; font-weight: bold;">AS</span> <span style="color: #008000;">`created<span style="color: #008080; font-weight: bold;">_</span>at`</span><span style="color: #000033;">,</span>NEW.filename <span style="color: #990099; font-weight: bold;">AS</span> <span style="color: #008000;">`filename`</span><span style="color: #000033;">,</span>NEW.width <span style="color: #990099; font-weight: bold;">AS</span> <span style="color: #008000;">`width`</span><span style="color: #000033;">,</span>NEW.content_type <span style="color: #990099; font-weight: bold;">AS</span> <span style="color: #008000;">`content<span style="color: #008080; font-weight: bold;">_</span>type`</span><span style="color: #000033;">,</span>NEW.height <span style="color: #990099; font-weight: bold;">AS</span> <span style="color: #008000;">`height`</span><span style="color: #000033;">,</span>NEW.thumbnail <span style="color: #990099; font-weight: bold;">AS</span> <span style="color: #008000;">`thumbnail`</span><span style="color: #000033;">,</span>NEW.size <span style="color: #990099; font-weight: bold;">AS</span> <span style="color: #008000;">`size`</span><span style="color: #FF00FF;">&#41;</span><span style="color: #FF00FF;">&#41;</span><span style="color: #FF00FF;">&#41;</span><span style="color: #FF00FF;">&#41;</span><span style="color: #FF00FF;">&#41;</span><span style="color: #000033;">;</span>
&nbsp;
<span style="color: #990099; font-weight: bold;">CREATE</span> <span style="color: #990099; font-weight: bold;">TRIGGER</span> photos_DELETE BEFORE <span style="color: #990099; font-weight: bold;">DELETE</span> <span style="color: #990099; font-weight: bold;">ON</span> photos <span style="color: #990099; font-weight: bold;">FOR EACH ROW</span> 
<span style="color: #990099; font-weight: bold;">SET</span> @dummy <span style="color: #CC0099;">=</span> memc_set<span style="color: #FF00FF;">&#40;</span><span style="color: #008000;">'queue<span style="color: #008080; font-weight: bold;">_</span>db'</span><span style="color: #000033;">,</span> <span style="color: #FF00FF;">&#40;</span>json_object<span style="color: #FF00FF;">&#40;</span><span style="color: #008000;">'delete'</span> <span style="color: #990099; font-weight: bold;">AS</span> <span style="color: #990099; font-weight: bold;">action</span><span style="color: #000033;">,</span> <span style="color: #008000;">'photos'</span> <span style="color: #990099; font-weight: bold;">AS</span> table_name<span style="color: #000033;">,</span> OLD.id <span style="color: #990099; font-weight: bold;">AS</span> id<span style="color: #000033;">,</span> json_members<span style="color: #FF00FF;">&#40;</span><span style="color: #008000;">'data'</span><span style="color: #000033;">,</span> json_object<span style="color: #FF00FF;">&#40;</span>OLD.user_id <span style="color: #990099; font-weight: bold;">AS</span> <span style="color: #008000;">`user<span style="color: #008080; font-weight: bold;">_</span>id`</span><span style="color: #000033;">,</span>OLD.parent_id <span style="color: #990099; font-weight: bold;">AS</span> <span style="color: #008000;">`parent<span style="color: #008080; font-weight: bold;">_</span>id`</span><span style="color: #000033;">,</span>OLD.created_at <span style="color: #990099; font-weight: bold;">AS</span> <span style="color: #008000;">`created<span style="color: #008080; font-weight: bold;">_</span>at`</span><span style="color: #000033;">,</span>OLD.filename <span style="color: #990099; font-weight: bold;">AS</span> <span style="color: #008000;">`filename`</span><span style="color: #000033;">,</span>OLD.width <span style="color: #990099; font-weight: bold;">AS</span> <span style="color: #008000;">`width`</span><span style="color: #000033;">,</span>OLD.content_type <span style="color: #990099; font-weight: bold;">AS</span> <span style="color: #008000;">`content<span style="color: #008080; font-weight: bold;">_</span>type`</span><span style="color: #000033;">,</span>OLD.height <span style="color: #990099; font-weight: bold;">AS</span> <span style="color: #008000;">`height`</span><span style="color: #000033;">,</span>OLD.thumbnail <span style="color: #990099; font-weight: bold;">AS</span> <span style="color: #008000;">`thumbnail`</span><span style="color: #000033;">,</span>OLD.size <span style="color: #990099; font-weight: bold;">AS</span> <span style="color: #008000;">`size`</span><span style="color: #FF00FF;">&#41;</span><span style="color: #FF00FF;">&#41;</span><span style="color: #FF00FF;">&#41;</span><span style="color: #FF00FF;">&#41;</span><span style="color: #FF00FF;">&#41;</span><span style="color: #000033;">;</span>
&nbsp;
<span style="color: #990099; font-weight: bold;">CREATE</span> <span style="color: #990099; font-weight: bold;">TRIGGER</span> photos_UPDATE <span style="color: #990099; font-weight: bold;">AFTER</span> <span style="color: #990099; font-weight: bold;">UPDATE</span> <span style="color: #990099; font-weight: bold;">ON</span> photos <span style="color: #990099; font-weight: bold;">FOR EACH ROW</span> 
<span style="color: #990099; font-weight: bold;">BEGIN</span> 
<span style="color: #009900;">IF</span> json_object<span style="color: #FF00FF;">&#40;</span>OLD.user_id <span style="color: #990099; font-weight: bold;">AS</span> <span style="color: #008000;">`user<span style="color: #008080; font-weight: bold;">_</span>id`</span><span style="color: #000033;">,</span>OLD.parent_id <span style="color: #990099; font-weight: bold;">AS</span> <span style="color: #008000;">`parent<span style="color: #008080; font-weight: bold;">_</span>id`</span><span style="color: #000033;">,</span>OLD.created_at <span style="color: #990099; font-weight: bold;">AS</span> <span style="color: #008000;">`created<span style="color: #008080; font-weight: bold;">_</span>at`</span><span style="color: #000033;">,</span>OLD.filename <span style="color: #990099; font-weight: bold;">AS</span> <span style="color: #008000;">`filename`</span><span style="color: #000033;">,</span>OLD.width <span style="color: #990099; font-weight: bold;">AS</span> <span style="color: #008000;">`width`</span><span style="color: #000033;">,</span>OLD.content_type <span style="color: #990099; font-weight: bold;">AS</span> <span style="color: #008000;">`content<span style="color: #008080; font-weight: bold;">_</span>type`</span><span style="color: #000033;">,</span>OLD.height <span style="color: #990099; font-weight: bold;">AS</span> <span style="color: #008000;">`height`</span><span style="color: #000033;">,</span>OLD.thumbnail <span style="color: #990099; font-weight: bold;">AS</span> <span style="color: #008000;">`thumbnail`</span><span style="color: #000033;">,</span>OLD.size <span style="color: #990099; font-weight: bold;">AS</span> <span style="color: #008000;">`size`</span><span style="color: #FF00FF;">&#41;</span> <span style="color: #CC0099;">&lt;&gt;</span> json_object<span style="color: #FF00FF;">&#40;</span>NEW.user_id <span style="color: #990099; font-weight: bold;">AS</span> <span style="color: #008000;">`user<span style="color: #008080; font-weight: bold;">_</span>id`</span><span style="color: #000033;">,</span>NEW.parent_id <span style="color: #990099; font-weight: bold;">AS</span> <span style="color: #008000;">`parent<span style="color: #008080; font-weight: bold;">_</span>id`</span><span style="color: #000033;">,</span>NEW.created_at <span style="color: #990099; font-weight: bold;">AS</span> <span style="color: #008000;">`created<span style="color: #008080; font-weight: bold;">_</span>at`</span><span style="color: #000033;">,</span>NEW.filename <span style="color: #990099; font-weight: bold;">AS</span> <span style="color: #008000;">`filename`</span><span style="color: #000033;">,</span>NEW.width <span style="color: #990099; font-weight: bold;">AS</span> <span style="color: #008000;">`width`</span><span style="color: #000033;">,</span>NEW.content_type <span style="color: #990099; font-weight: bold;">AS</span> <span style="color: #008000;">`content<span style="color: #008080; font-weight: bold;">_</span>type`</span><span style="color: #000033;">,</span>NEW.height <span style="color: #990099; font-weight: bold;">AS</span> <span style="color: #008000;">`height`</span><span style="color: #000033;">,</span>NEW.thumbnail <span style="color: #990099; font-weight: bold;">AS</span> <span style="color: #008000;">`thumbnail`</span><span style="color: #000033;">,</span>NEW.size <span style="color: #990099; font-weight: bold;">AS</span> <span style="color: #008000;">`size`</span><span style="color: #FF00FF;">&#41;</span> <span style="color: #009900;">THEN</span> 
  <span style="color: #990099; font-weight: bold;">SET</span> @dummy <span style="color: #CC0099;">=</span> memc_set<span style="color: #FF00FF;">&#40;</span><span style="color: #008000;">'queue<span style="color: #008080; font-weight: bold;">_</span>db'</span><span style="color: #000033;">,</span> <span style="color: #FF00FF;">&#40;</span>json_object<span style="color: #FF00FF;">&#40;</span><span style="color: #008000;">'update'</span> <span style="color: #990099; font-weight: bold;">AS</span> <span style="color: #990099; font-weight: bold;">action</span><span style="color: #000033;">,</span> <span style="color: #008000;">'photos'</span> <span style="color: #990099; font-weight: bold;">AS</span> table_name<span style="color: #000033;">,</span> OLD.id <span style="color: #990099; font-weight: bold;">AS</span> id<span style="color: #000033;">,</span> json_members<span style="color: #FF00FF;">&#40;</span><span style="color: #008000;">'new'</span><span style="color: #000033;">,</span> json_object<span style="color: #FF00FF;">&#40;</span>NEW.user_id <span style="color: #990099; font-weight: bold;">AS</span> <span style="color: #008000;">`user<span style="color: #008080; font-weight: bold;">_</span>id`</span><span style="color: #000033;">,</span>NEW.parent_id <span style="color: #990099; font-weight: bold;">AS</span> <span style="color: #008000;">`parent<span style="color: #008080; font-weight: bold;">_</span>id`</span><span style="color: #000033;">,</span>NEW.created_at <span style="color: #990099; font-weight: bold;">AS</span> <span style="color: #008000;">`created<span style="color: #008080; font-weight: bold;">_</span>at`</span><span style="color: #000033;">,</span>NEW.filename <span style="color: #990099; font-weight: bold;">AS</span> <span style="color: #008000;">`filename`</span><span style="color: #000033;">,</span>NEW.width <span style="color: #990099; font-weight: bold;">AS</span> <span style="color: #008000;">`width`</span><span style="color: #000033;">,</span>NEW.content_type <span style="color: #990099; font-weight: bold;">AS</span> <span style="color: #008000;">`content<span style="color: #008080; font-weight: bold;">_</span>type`</span><span style="color: #000033;">,</span>NEW.height <span style="color: #990099; font-weight: bold;">AS</span> <span style="color: #008000;">`height`</span><span style="color: #000033;">,</span>NEW.thumbnail <span style="color: #990099; font-weight: bold;">AS</span> <span style="color: #008000;">`thumbnail`</span><span style="color: #000033;">,</span>NEW.size <span style="color: #990099; font-weight: bold;">AS</span> <span style="color: #008000;">`size`</span><span style="color: #FF00FF;">&#41;</span><span style="color: #FF00FF;">&#41;</span><span style="color: #000033;">,</span> json_members<span style="color: #FF00FF;">&#40;</span><span style="color: #008000;">'old'</span><span style="color: #000033;">,</span> json_object<span style="color: #FF00FF;">&#40;</span>OLD.user_id <span style="color: #990099; font-weight: bold;">AS</span> <span style="color: #008000;">`user<span style="color: #008080; font-weight: bold;">_</span>id`</span><span style="color: #000033;">,</span>OLD.parent_id <span style="color: #990099; font-weight: bold;">AS</span> <span style="color: #008000;">`parent<span style="color: #008080; font-weight: bold;">_</span>id`</span><span style="color: #000033;">,</span>OLD.created_at <span style="color: #990099; font-weight: bold;">AS</span> <span style="color: #008000;">`created<span style="color: #008080; font-weight: bold;">_</span>at`</span><span style="color: #000033;">,</span>OLD.filename <span style="color: #990099; font-weight: bold;">AS</span> <span style="color: #008000;">`filename`</span><span style="color: #000033;">,</span>OLD.width <span style="color: #990099; font-weight: bold;">AS</span> <span style="color: #008000;">`width`</span><span style="color: #000033;">,</span>OLD.content_type <span style="color: #990099; font-weight: bold;">AS</span> <span style="color: #008000;">`content<span style="color: #008080; font-weight: bold;">_</span>type`</span><span style="color: #000033;">,</span>OLD.height <span style="color: #990099; font-weight: bold;">AS</span> <span style="color: #008000;">`height`</span><span style="color: #000033;">,</span>OLD.thumbnail <span style="color: #990099; font-weight: bold;">AS</span> <span style="color: #008000;">`thumbnail`</span><span style="color: #000033;">,</span>OLD.size <span style="color: #990099; font-weight: bold;">AS</span> <span style="color: #008000;">`size`</span><span style="color: #FF00FF;">&#41;</span><span style="color: #FF00FF;">&#41;</span><span style="color: #FF00FF;">&#41;</span><span style="color: #FF00FF;">&#41;</span><span style="color: #FF00FF;">&#41;</span><span style="color: #000033;">;</span> 
<span style="color: #009900;">END</span> <span style="color: #009900;">IF</span><span style="color: #000033;">;</span> 
<span style="color: #009900;">END</span><span style="color: #000033;">;</span></pre></td></tr></table></div>


<p>Комментарии:</p>

<ul>
<li>функция <code>kick_photos</code> позволяет скопировать строчку таблицы в очередь как пакет обновления типа &laquo;вставка&raquo;, может использоваться для начального наполнения внешней системы;</li>
<li>триггеры на удаление и вставку просто формируют соответствующие пакеты;</li>
<li>триггер на обновление проверяет, действительно ли в пакете произошли изменения (например, мы можем использовать не все поля в пакете); </li>
<li>необходимо учесть, что работе FOREIGN KEY CONSTRAINT триггеры не вызываются (очередной прикол MySQL), т.е., например, при если при выполнении запроса на удаление из таблицы <code>A</code> будут по FOREIGN KEY удалятся записи из таблицы <code>B</code>, то в триггере на удаление из <code>A</code> необходимо отработать этот случай, т.к. триггеры на таблице <code>B</code> не будут вызваны.</li>
</ul>

<p>Код UDF доступен на github, это &#8211; &laquo;подпиленный&raquo; код из репозитория UDF или собственные разработки:</p>

<ul>
<li><a href="http://github.com/smira/lib_mysqludf_json">превращение строки в json</a> &#8211; были грязно исправлены проблемы с buffer overrun;</li>
<li><a href="http://github.com/smira/memcached_functions_mysql">запись в memcacheq</a> &#8211; при запуске сервера будет настроена на запись в localhost:22201, плюс исправления для многопоточного режима и работы из нити workerа репликации;</li>
<li><a href="http://github.com/smira/mysql_udf_unix_timestamp_ms">timestamp с миллисекундной точностью</a> &#8211; полезно для проставления временных меток и анализа производительности репликации.</li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://www.smira.ru/2010/10/30/mysql-udf-json-memcacheq/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>HL++ (2009): Twisted Framework</title>
		<link>http://www.smira.ru/2009/10/13/hl-2009-twisted-framework/</link>
		<comments>http://www.smira.ru/2009/10/13/hl-2009-twisted-framework/#comments</comments>
		<pubDate>Tue, 13 Oct 2009 18:42:27 +0000</pubDate>
		<dc:creator>Андрей</dc:creator>
				<category><![CDATA[Python]]></category>
		<category><![CDATA[Qik]]></category>
		<category><![CDATA[Twisted]]></category>
		<category><![CDATA[Разработка]]></category>
		<category><![CDATA[highload]]></category>
		<category><![CDATA[python]]></category>
		<category><![CDATA[twisted]]></category>

		<guid isPermaLink="false">http://www.smira.ru/?p=517</guid>
		<description><![CDATA[Сегодня выступал на HighLoad++ с докладом Twisted Framework &#8211; фреймворк для написания сетевых приложений в Python. Введение Последнее время в области web происходит смещение внимания с тяжелых application-серверов, которые тратят на обработку запроса сотни миллисекунд, а то и секунды, к более легковесным сервисам, передающим меньшие объемы данных с минимальной задержкой. Переход от генерации десятков и [...]]]></description>
			<content:encoded><![CDATA[<p>Сегодня выступал на <a href="http://highload.ru/">HighLoad++</a> с докладом <a href="http://www.highload.ru/papers2009/12261.html">Twisted Framework &#8211; фреймворк для написания сетевых приложений в Python</a>. </p>

<h2>Введение</h2>

<p>Последнее время в области web происходит смещение внимания с тяжелых application-серверов, которые тратят на обработку запроса сотни миллисекунд, а то и секунды, к более легковесным сервисам, передающим меньшие объемы данных с минимальной задержкой. Переход от генерации десятков и сотен килобайт HTML-кода в ответ на запрос к передаче изменений в данных, запакованных в JSON и измеряемых сотнями байт. В качестве примеров таких сервисов можно привести Gmail, FriendFeed, Twitter Live Search и т.п.</p>

<p>Для обеспечения минимальной задержки для пользователя необходимо либо поддерживать постоянное соединение (например, Adobe Flash, RTMP) или использовать технику HTTP long polling в сочетании с keep alive. Так или иначе на стороне сервера это приводит к появлению большого количества одновременных соединений (тысячи, десятки тысяч), по каждому из которых передается не такой большой объем данных. Эту ситуацию называют обычно проблемой <a href="http://www.kegel.com/c10k.html">C10k</a>.</p>

<p><span id="more-517"></span></p>

<p>Для обработки соединений архитектурный выбор на стороне сервера не такой большой: процесс на соединение, нить на соединение, комбинированный вариант процесс-нити или асинхронный ввод-вывод (возможно, в сочетании с дополнительными процессами или нитями). При наличии более 10 тысяч одновременных соединений с точки зрения расхода ресурсов совершенно невозможно представить создание 10 тысяч процессов; 10 тысяч нитей также вряд ли будет разумным решением. Необходимо дополнительно учесть, что при наличии такого большого числа соединений объем работы по каждому из них относительно невелик, большинство их них простаивают в ожидании поступления новых данных. Поэтому бóльшая часть процессов или нитей будет просто находиться в состоянии ожидания, расходуя впустую  системные ресурсы.
Асинхронный ввод-вывод позволяет осуществлять неблокирующийся сетевой ввод-вывод по тысячам открытых сокетов в рамках одной нити выполнения (одного процесса). Механизмы реализации в разных ОС разные, например: select(), poll(), epoll(), kqueue() и т.п. Примеры приложений, использующих асинхронный ввод-вывод:</p>

<ul>
<li>nginx (используются дополнительные процессы для обслуживания задач, требующих большего объема CPU);</li>
<li>haproxy;</li>
<li>memcached;</li>
<li>и другие.</li>
</ul>

<p>Тем не менее, асинхронный ввод-вывод не является универсальным решением: для сервера БД это вряд ли было бы хорошим способом организации обслуживания соединений, так как для обработки каждого запроса требуется большой объем дискового ввода-вывода и процессорного времени, что не позволяет это сделать в рамках одного процесса. </p>

<p><a href="http://www.twistedmatrix.com/">Twisted Framework</a> — это обширный набор классов и модулей для реализации асинхронных сетевых приложений. Twisted Framework — это:</p>

<ul>
<li>ядро, абстрагирующее все операции асинхронного ввода-вывода и использующее соответствующий механизм конкретной ОС;</li>
<li>концепция Deferred, которая позволяет реализовать в простой форме обслуживание запроса: асинхронные сетевые обращения (например, к БД, memcached), обработку ошибочных ситуаций; Deferred является аналогом обычных конструкций последовательного программирования для асинхронной модели программирования;</li>
<li>обширный набор уже реализованных сетевых протоколов: HTTP, DNS, SMTP, IMAP, memcached, Jabber, ICQ и т.д.; еще большее количество протоколов доступно в виде дополнительных модулей;</li>
<li>дополнительная инфраструктура: unit-testы с поддержкой Deferred, пулы нитей, процессов и т.д.; </li>
<li>качественная концепция разработки — полное покрытие unit-testами, строгий review любого изменения.</li>
</ul>

<p>Основная часть доклада будет посвящена конкретным примерам приложений, реализованными с помощью Twisted — с архитектурой, конкретными параметрами производительности, приемами оптимизации, преимуществами и недостатками Twisted для решения данной задачи:</p>

<ul>
<li><a href="http://www.smira.ru/2008/04/09/rit-2008/">RTMP-сервер pyFMS</a>, сервер вещаний сервиса Smotri.Com (сотни трансляций, десятки тысяч зрителей);</li>
<li>backend-сервер <a href="http://www.mdc.ru/">проекта MDC</a> &#8211; хранение и обработка истории общения пользователей, хранение настроек и т.п.;</li>
<li><a href="http://www.smira.ru/2009/07/12/qik-push-engine-api-private-beta/">Qik Push Engine</a> &#8211; сервер немедленной доставки изменений информации о видео, созданных пользователями сервиса, в том числе push-нотификация о появившихся live-стримах, масштабирование, обработка больших объемов информации.</li>
</ul>

<p>Дополнительная информация:</p>

<ul>
<li><a href="http://twistedmatrix.com/projects/core/documentation/howto/">Документация Twisted</a></li>
<li><a href="http://www.smira.ru/category/development/python-development/">О Python</a>, а также о <a href="http://www.smira.ru/category/development/twisted-development/">Twisted</a></li>
<li><a href="http://habrahabr.ru/blogs/twisted/">Блог на Хабрахабре про Twisted</a></li>
<li><a href="http://burus.org/2008/12/16/twisted-classic-examples/">Александр Бурцев о Twisted</a></li>
<li><a href="http://www.smira.ru/2009/02/10/deferred-async-programming/">Deferred в Twisted</a> и <a href="http://www.smira.ru/2009/02/24/more-about-deferred/">не только</a> .</li>
</ul>

<h2>Презентация</h2>

<div style="width:425px;text-align:left" id="__ss_2211313"><a style="font:14px Helvetica,Arial,Sans-serif;display:block;margin:12px 0 3px 0;text-decoration:underline;" href="http://www.slideshare.net/Smirnov.Andrey/twisted-framework-python-2211313" title="Twisted Framework - сетевые приложения в Python">Twisted Framework &#8211; сетевые приложения в Python</a><object style="margin:0px" width="425" height="355"><param name="movie" value="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=smirnov-twisted-python-091013134034-phpapp02&#038;stripped_title=twisted-framework-python-2211313" /><param name="allowFullScreen" value="true"/><param name="allowScriptAccess" value="always"/><embed src="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=smirnov-twisted-python-091013134034-phpapp02&#038;stripped_title=twisted-framework-python-2211313" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="425" height="355"></embed></object><div style="font-size:11px;font-family:tahoma,arial;height:26px;padding-top:2px;">View more <a style="text-decoration:underline;" href="http://www.slideshare.net/">documents</a> from <a style="text-decoration:underline;" href="http://www.slideshare.net/Smirnov.Andrey">Smirnov.Andrey</a>.</div></div>

<ul>
<li><a href='http://www.smira.ru/wp-content/uploads/2009/10/smirnov-twisted-python.pdf'>Скачать презентацию (PDF)</a></li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://www.smira.ru/2009/10/13/hl-2009-twisted-framework/feed/</wfw:commentRss>
		<slash:comments>17</slash:comments>
		</item>
		<item>
		<title>Mongrel vs. Phusion Passenger: выбор очевиден</title>
		<link>http://www.smira.ru/2009/10/05/mongrel-vs-phusion-passenger-obvious-choice/</link>
		<comments>http://www.smira.ru/2009/10/05/mongrel-vs-phusion-passenger-obvious-choice/#comments</comments>
		<pubDate>Mon, 05 Oct 2009 19:41:00 +0000</pubDate>
		<dc:creator>Андрей</dc:creator>
				<category><![CDATA[Qik]]></category>
		<category><![CDATA[Ruby on Rails]]></category>
		<category><![CDATA[mongrel]]></category>
		<category><![CDATA[passenger]]></category>
		<category><![CDATA[phusion]]></category>
		<category><![CDATA[rails]]></category>
		<category><![CDATA[ruby]]></category>

		<guid isPermaLink="false">http://www.smira.ru/?p=507</guid>
		<description><![CDATA[Предыдущая конфигурация: nginx (главный proxy), который раздает трафик в haproxy (ради возможности балансировать по нагрузке), который распределяет нагрузку по нескольким webapp-серверам с 16-ю mongrelами на каждом Проблемы: &#171;Утекающая&#187; память, периодический out of memory на серверах, лечится только перезапуском mongrelов. Запросы, занимающие десятки секунд из-за неверной балансировки (в нагруженный mongrel все-таки попадает несколько &#171;тяжелых&#187; запросов). Сложность [...]]]></description>
			<content:encoded><![CDATA[<p>Предыдущая конфигурация:</p>

<ul>
<li><a href="http://sysoev.ru/nginx/">nginx</a> (главный proxy), который раздает трафик в</li>
<li><a href="http://haproxy.1wt.eu/">haproxy</a> (ради возможности балансировать по нагрузке), который распределяет нагрузку по нескольким webapp-серверам</li>
<li>с 16-ю <a href="http://mongrel.rubyforge.org/">mongrelами</a> на каждом</li>
</ul>

<p>Проблемы:</p>

<ol>
<li>&laquo;Утекающая&raquo; память, периодический out of memory на серверах, лечится только перезапуском <a href="http://mongrel.rubyforge.org/">mongrelов</a>.</li>
<li>Запросы, занимающие десятки секунд из-за неверной балансировки (в нагруженный <a href="http://mongrel.rubyforge.org/">mongrel</a> все-таки попадает несколько &laquo;тяжелых&raquo; запросов).</li>
<li>Сложность управления кластером монгрелов &#8211; постоянные проблемы при перезапуске, &laquo;не стартующие&raquo; <a href="http://mongrel.rubyforge.org/">mongrelы</a> и т.п.</li>
</ol>

<p>Новая конфигурация:</p>

<ul>
<li><a href="http://sysoev.ru/nginx/">nginx</a> (proxy) остался</li>
<li><a href="http://www.modrails.com/">Phusion Passenger</a> + <a href="http://www.rubyenterpriseedition.com/">Ruby Enterprise Edition</a> на каждой машине.</li>
</ul>

<p>Результат:</p>

<p><img src="http://www.smira.ru/wp-content/uploads/2009/10/webapp01-passenger-mongrel.png" alt="webapp01-passenger-mongrel" title="webapp01-passenger-mongrel" width="603" height="250" class="aligncenter size-full wp-image-508" /></p>

<p>Комментарий: переход на <a href="http://www.modrails.com/">Phusion Passenger</a> на Week 39, объем занятой памяти &#8211; это белая область на графике, растущая сверху вниз. До перехода на Passenger объем свободной памяти стремительно уменьшался, иногда доходя до нуля, после перехода остается более-менее стабильным. Использование CPU осталось на прежнем уровне (как и ожидалось).</p>

<p>После перехода исчезли запросы, которые по непонятным причинам занимали десятки секунд &#8211; время выполнения коррелирует со сложностью запроса.</p>

<p>Так что если вы еще не переключились, мы идем к вам <img src='http://www.smira.ru/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </p>

<p>P.S. Отдельное спасибо <a href="http://github.com/glebpom">glebpom</a> за подсказку.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.smira.ru/2009/10/05/mongrel-vs-phusion-passenger-obvious-choice/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>Qik Push Engine API: приглашаем разработчиков</title>
		<link>http://www.smira.ru/2009/07/12/qik-push-engine-api-private-beta/</link>
		<comments>http://www.smira.ru/2009/07/12/qik-push-engine-api-private-beta/#comments</comments>
		<pubDate>Sun, 12 Jul 2009 13:56:40 +0000</pubDate>
		<dc:creator>Андрей</dc:creator>
				<category><![CDATA[Qik]]></category>
		<category><![CDATA[Twisted]]></category>
		<category><![CDATA[Разработка]]></category>
		<category><![CDATA[api]]></category>
		<category><![CDATA[engine]]></category>
		<category><![CDATA[push]]></category>
		<category><![CDATA[twisted]]></category>

		<guid isPermaLink="false">http://www.smira.ru/?p=461</guid>
		<description><![CDATA[Qik &#8211; это сервис стриминга (вещания) и загрузки видео с мобильных телефонов. Загруженное видео можно посмотреть на сайте или на его специальной версии с мобильного телефона. Доступна интеграция с другими сервисами, такими как Twitter, Facebook и другие. Клиенты для практически всех современных моделей телефонов: iPhone, Windows Mobile, Symbian, Android, Blackberry и другие. Qik Push Engine [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://qik.com/"><img src="http://www.smira.ru/wp-content/uploads/2009/07/qik_logo.png" alt="qik_logo" title="qik_logo" width="177" height="92" class="alignleft size-full wp-image-462" /></a> <a href="http://qik.com/">Qik</a> &#8211; это сервис стриминга (вещания) и загрузки видео с мобильных телефонов. Загруженное видео можно посмотреть на <a href="http://qik.com/">сайте</a> или на его <a href="http://m.qik.com/">специальной версии</a> с мобильного телефона. Доступна интеграция с другими сервисами, такими как Twitter, Facebook и другие. Клиенты для практически всех современных моделей телефонов: iPhone, Windows Mobile, Symbian, Android, Blackberry и другие.</p>

<p>Qik Push Engine &#8211; это механизм, который позволяет получать мгновенные оповещения о новых/изменившихся Qik-видео. Например, можно посмотреть постоянно обновляющийся список live-видео, все видео из района Новопеределкино или все видео со словом &laquo;кошка&raquo;. На основе Qik Push Engine API можно построить интересные приложения, интегрированные с Qik, или добавить функциональность в уже существующие. Можно написать собственную систему нотификации, desktop-widget 
или что-то еще.</p>

<p>Сегодня мы открываем API для работы c Qik Push Engine. Это первая ласточка в большом списке API, открывающих доступ к платформе стриминга Qik. Если вам интересно посмотреть Qik Push Engine в действии, заходите на одну из <a href="http://engine.qik.com/examples/all.videos.html">страниц примеров</a>.</p>

<p><span id="more-461"></span></p>

<h2>Основы работы с API</h2>

<p><a href="http://qikapi.pbworks.com/">Qik Push Engine API</a> доступно в виде набора удаленных процедур по протоколу JSON-RPC (over HTTP). В ближайшее время будет открыт REST-подобный интерфейс. Точкой входа для JSON-RPC является http://engine.qik.com/api/jsonrpc. Пока проект находится в закрытом бета-тестировании для доступа к API необходимо указать ключ разработчика, добавив параметр apikey: http://engine.qik.com/api/jsonrpc?apikey=xxxxxxx. Ключ разработчика можно получить, отправив письмо с запросом на <a href="mailto:api@qik.com">api@qik.com</a>.</p>

<p>Пример HTTP-сессии:</p>

<pre>
POST /api/jsonrpc
Host : engine.stage.qik.com
Content-Type:  application/json; charset=UTF-8
Content-Length: 46

{"method": "qik.session.create", "params": []}
</pre>

<p>Ответ:</p>

<pre>
Content-Type: text/json 

["a56c1603-1fbb-4140-8b35-8c36abbd8b27"]
</pre>

<h2>Пример подписки на поток событий</h2>

<p>Здесь и далее я буду использовать простую запись вызова команд, которая похожа на вызов обычных функций, опуская детали JSON-RPC взаимодействия. Итак, пусть мы хотим подписаться на список всех live-стримов, которые есть данный момент.</p>

<p>Первым делом создадим сессию:
<pre>
qik.session.create() -> "a56c1603-1fbb-4140-8b35-8c36abbd8b27"
</pre></p>

<p>Подписываемся в рамках сессии на view всех публичных live-стримов, при этом указываем максимальное количество элементов во view (limit). Элементы view будут упорядочены в порядке убывания даты начала стрима (самые новые первыми):
<pre>
qik.stream.subscribe&#95;public_live("a56c1603-1fbb-4140-8b35-8c36abbd8b27", 10) -> 
 [ "174/('PublicLiveStreamView', 'Stream', [('stoptime', None)], 'alllive;limit=10')", [ ... ] ]
</pre></p>

<p>В ответ на запрос мы получаем массив из двух элементов: первым является ключ подписки (длинная строка, начинающаяся с &laquo;174/..&raquo;). Не ищите смысла в ключе подписки, его необходимо просто сохранить, он потребуется в дальнейшем.</p>

<p>Вторым элементом является начальное состояние view, то есть текущий список live-стримов в нашем случае. Мы получим не больше 10 элементов (так как указали limit 10 при вызове метода <code>qik.stream.subscribe_public_live</code>). Состояние view выглядит примерно следующим образом:</p>


<div class="wp_syntax"><div class="code"><pre class="javascript" style="font-family:monospace;"><span style="color: #009900;">&#91;</span>
   <span style="color: #009900;">&#123;</span><span style="color: #3366CC;">&quot;url&quot;</span><span style="color: #339933;">:</span> <span style="color: #3366CC;">&quot;http://qik.com/video/2158468&quot;</span><span style="color: #339933;">,</span> 
    <span style="color: #3366CC;">&quot;live&quot;</span><span style="color: #339933;">:</span> <span style="color: #003366; font-weight: bold;">true</span><span style="color: #339933;">,</span> 
    <span style="color: #3366CC;">&quot;user_id&quot;</span><span style="color: #339933;">:</span> <span style="color: #CC0000;">340699</span><span style="color: #339933;">,</span> 
    <span style="color: #3366CC;">&quot;small_thumbnail_url&quot;</span><span style="color: #339933;">:</span> <span style="color: #3366CC;">&quot;http://media.qik.com/media.thumbnails.128/c8ad8fe065ad4ad7ac8491874c043eac.jpg&quot;</span><span style="color: #339933;">,</span> 
    <span style="color: #3366CC;">&quot;title&quot;</span><span style="color: #339933;">:</span> <span style="color: #3366CC;">&quot;Untitled&quot;</span><span style="color: #339933;">,</span> <span style="color: #3366CC;">&quot;duration&quot;</span><span style="color: #339933;">:</span> <span style="color: #CC0000;">0</span><span style="color: #339933;">,</span>
    <span style="color: #3366CC;">&quot;created_at&quot;</span><span style="color: #339933;">:</span> <span style="color: #3366CC;">&quot;2009-07-11 15:56:03&quot;</span><span style="color: #339933;">,</span> 
    <span style="color: #3366CC;">&quot;views&quot;</span><span style="color: #339933;">:</span> <span style="color: #CC0000;">0</span><span style="color: #339933;">,</span> 
    <span style="color: #3366CC;">&quot;id&quot;</span><span style="color: #339933;">:</span> <span style="color: #CC0000;">2158468</span><span style="color: #009900;">&#125;</span><span style="color: #339933;">,</span> 
&nbsp;
   <span style="color: #009900;">&#123;</span><span style="color: #3366CC;">&quot;url&quot;</span><span style="color: #339933;">:</span> <span style="color: #3366CC;">&quot;http://qik.com/video/2158466&quot;</span><span style="color: #339933;">,</span> 
    <span style="color: #3366CC;">&quot;live&quot;</span><span style="color: #339933;">:</span> <span style="color: #003366; font-weight: bold;">true</span><span style="color: #339933;">,</span> 
    <span style="color: #3366CC;">&quot;user_id&quot;</span><span style="color: #339933;">:</span> <span style="color: #CC0000;">340119</span><span style="color: #339933;">,</span> 
    <span style="color: #3366CC;">&quot;small_thumbnail_url&quot;</span><span style="color: #339933;">:</span> <span style="color: #3366CC;">&quot;http://media.qik.com/media.thumbnails.128/984c33e1ead441038f315e1fff109fc5.jpg&quot;</span><span style="color: #339933;">,</span> 
     <span style="color: #3366CC;">&quot;title&quot;</span><span style="color: #339933;">:</span> <span style="color: #3366CC;">&quot;Testando!&quot;</span><span style="color: #339933;">,</span> 
     <span style="color: #3366CC;">&quot;duration&quot;</span><span style="color: #339933;">:</span> <span style="color: #CC0000;">0</span><span style="color: #339933;">,</span> <span style="color: #3366CC;">&quot;created_at&quot;</span><span style="color: #339933;">:</span> <span style="color: #3366CC;">&quot;2009-07-11 15:55:34&quot;</span><span style="color: #339933;">,</span> 
     <span style="color: #3366CC;">&quot;views&quot;</span><span style="color: #339933;">:</span> <span style="color: #CC0000;">0</span><span style="color: #339933;">,</span> 
     <span style="color: #3366CC;">&quot;id&quot;</span><span style="color: #339933;">:</span> <span style="color: #CC0000;">2158466</span><span style="color: #009900;">&#125;</span>
<span style="color: #009900;">&#93;</span></pre></div></div>


<p>Смысл большинства полей очевиден, приведу лишь некоторые комментарии:</p>

<ul>
<li>все даты приведены в UTC;</li>
<li>поле <code>duration</code> хранит длительность видео в секундах.</li>
</ul>

<p>Так как мы подписались на live-стримы, у всех видео <code>live == true</code>.</p>

<p>Теперь нам необходимо получать новые события для тех view, на которые мы подписались. Для этого используются HTTP long polling-запросы. Клиент отправляет запрос серверу, а сервер отправляет ответ, когда появляются события или когда истечет таймаут, если событий не появилось. </p>

<p>Для реализации long polling в цикле вызываем метод <code>qik.session.get_events</code>, указывая желаемый таймаут (не рекомендуется использовать таймаут более 90 секунд).</p>

<pre>
qik.session.get_events("a56c1603-1fbb-4140-8b35-8c36abbd8b27", 60) -> 
  [ {...} ,{...} ]
</pre>

<p>В ответ мы можем получить пустой массив событий, если истек таймаут, или некоторый набор событий следующего вида:</p>


<div class="wp_syntax"><div class="code"><pre class="javascript" style="font-family:monospace;"><span style="color: #009900;">&#91;</span>
   <span style="color: #009900;">&#123;</span>
     <span style="color: #3366CC;">&quot;action&quot;</span><span style="color: #339933;">:</span> <span style="color: #3366CC;">&quot;update&quot;</span><span style="color: #339933;">,</span> 
     <span style="color: #3366CC;">&quot;old&quot;</span><span style="color: #339933;">:</span> <span style="color: #009900;">&#123;</span><span style="color: #3366CC;">&quot;id&quot;</span><span style="color: #339933;">:</span> <span style="color: #CC0000;">104252</span><span style="color: #339933;">,</span> <span style="color: #3366CC;">&quot;title&quot;</span><span style="color: #339933;">:</span> <span style="color: #3366CC;">&quot;Untitled&quot;</span><span style="color: #339933;">,</span> <span style="color: #3366CC;">&quot;views&quot;</span><span style="color: #339933;">:</span> <span style="color: #CC0000;">0</span><span style="color: #339933;">,</span> ...<span style="color: #009900;">&#125;</span><span style="color: #339933;">,</span> 
     <span style="color: #3366CC;">&quot;obj&quot;</span><span style="color: #339933;">:</span> <span style="color: #009900;">&#123;</span><span style="color: #3366CC;">&quot;id&quot;</span><span style="color: #339933;">:</span> <span style="color: #CC0000;">104252</span><span style="color: #339933;">,</span> <span style="color: #3366CC;">&quot;title&quot;</span><span style="color: #339933;">:</span> <span style="color: #3366CC;">&quot;driving to key largo&quot;</span><span style="color: #339933;">,</span> <span style="color: #3366CC;">&quot;views&quot;</span><span style="color: #339933;">:</span> <span style="color: #CC0000;">5</span><span style="color: #339933;">,</span> ...<span style="color: #009900;">&#125;</span><span style="color: #339933;">,</span> 
     <span style="color: #3366CC;">&quot;key&quot;</span><span style="color: #339933;">:</span> <span style="color: #3366CC;">&quot;175/('PublicRecentStreamView', 'Stream', [], 'allrecent;limit=50')&quot;</span>
    <span style="color: #009900;">&#125;</span><span style="color: #339933;">,</span>
<span style="color: #009900;">&#93;</span></pre></div></div>


<p>В каждом событии обязательно передается следующая инормация:</p>

<ul>
<li><code>key</code> &#8211; ключ подписки, это то же самое значение, которое мы получили при подписке на view, ключ позволяет отличать события для разных view в рамках одной сессии;</li>
<li><code>action</code> &#8211; действие (изменение), произошедшее с view:

<ul>
<li><code>ping</code> &#8211; ничего не произошло, просто view сообщает, что все хорошо <img src='http://www.smira.ru/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </li>
<li><code>update</code> &#8211; элемент view изменился (как в примере выше), передается старое и новое состояние объекта в полях <code>old</code> и <code>obj</code></li>
<li><code>insert</code> &#8211; во view добавился новый элемент, его состояние будет записано в поле <code>obj</code></li>
<li><code>delete</code> &#8211; элемент исчез из view (может быть, видео было удалено, или просто перестало удовлетворять критериям view, или ушло за границу &laquo;limit&raquo;), последнее состояние объекта будет записано в поле <code>obj</code>.</li>
</ul></li>
</ul>

<p>В ответ на события мы можем обновить информацию на экране для пользователя, выполнить какие-либо еще действия. Но обязательно надо отправить новый запрос <code>qik.session.get_events</code>, чтобы получить новые события.</p>

<p>В конце работы не забываем &laquo;убраться&raquo; за собой, убиваем сессию:</p>

<pre>
qik.session.destroy("a56c1603-1fbb-4140-8b35-8c36abbd8b27") 
</pre>

<p>Замечания:</p>

<ul>
<li>В рамках одной сессии можно подписаться на произвольное количество view. </li>
<li>Если это необходимо, можно авторизовать сессию от имени пользователя с помощью <code>qik.session.authorize</code>. </li>
<li>Существует большое количество вспомогательных запросов, которым сессия вообще не требуется, например <code>qik.user.public_profile</code>.</li>
<li>Можно отписаться от view при помощи конмады<code>qik.session.unsubscribe</code>.</li>
</ul>

<h2>Reference JS-клиент</h2>

<p>Для демонстрации возможностей Qik Push Engine и проверки его работы был реализован легкий JavaScript-клиент. Его можно увидеть в работе на примерах, полный список которых приведен ниже.</p>

<p>Если вы запустите пример в Firefox с включенным Firebug, в консоли Firebug будет появляться подробная информация о выполняемых запросах к Qik Push Engine API, полученных ответах и т.п.</p>

<p>Исходный код клиента доступен в необфусцированном виде, со всеми комментариями. Код предоставляет по принципам public domain, то есть можете делать с ним все, что захотите &#8211; использовать в своих приложениях, модифицировать, переписывать на другие языкаи программирования и т.п.</p>

<p>Пробежимся быстро по компонентам JS-клиента.</p>

<h3><a href="http://engine.qik.com/examples/js/prototype.js">prototype.js</a></h3>

<p>Совершенно стандартный <a href="http://www.prototypejs.org/">Prototype</a>, ничего интересного.</p>

<h3><a href="http://engine.qik.com/examples/js/deferred.js">deferred.js</a></h3>

<p><code>Deferred</code> &#8211; это одна из основных концепций Twisted Framework (http://twistedmatrix.com). Deferred не является обязательным для взаимодействия с Qik Push Engine API, но позволяет сильно упростить сложные взаимодействия в асинхронном коде. </p>

<p>В качестве дополнительного материала о Deferred можно почитать про <a href="http://www.smira.ru/2009/02/10/deferred-async-programming/">основы работы с Deferred</a>, <a href="http://www.smira.ru/2009/02/24/more-about-deferred/">более сложные случаи использования Deferred</a>, <a href="http://www.smira.ru/2009/05/29/deferred-in-javascript-for-prototype/">данную реализацию Deferred для Prototype</a>, а также взглянуть на Deferred в других JS-фреймворках: <a href="http://mochikit.com/doc/html/MochiKit/Async.html">MochiKit</a>, 
 <a href="http://www.dojotoolkit.org/book/dojo-book-0-9/part-3-programmatic-dijit-and-dojo/miscellaneous/communication-between-threads-do">Dojo</a>.</p>

<p><code>DeferredManager</code> &#8211; простая конструкция на основе Deferred, позволяет запускать не более N асинхронных действий в один момент времени.</p>

<h3><a href="http://engine.qik.com/examples/js/api.js">core.js</a></h3>

<p>Ядро клиента, объявляется namespace <code>QikEngine</code>, отладочные функции, определение конфигурации, создания объекта для доступа к API и т.п.</p>

<h3><a href="http://engine.qik.com/examples/js/api.js">api.js</a></h3>

<p><code>QikEngine.API</code> &#8211; простая обертка JSON-RPC API Qik Push Engine, возвращающая <code>Deferred</code> в качестве результата любого обращения к API. Через <code>Deferred</code> же отправляются ошибки и результаты выполнения методов.</p>

<p><code>QikEngine.Session</code> &#8211; класс, оборачивающий понятие сессии, создание, уничтожение, авторизация в рамках сессии.</p>

<h3><a href="http://engine.qik.com/examples/js/events.js">events.js</a></h3>

<p><code>QikEngine.Events</code> &#8211; получение новых событий с помощью HTTP long polling, технология, похожая на BOSH/Comet. Распределение событий подписчикам (конкретным view). </p>

<h3><a href="http://engine.qik.com/examples/js/view.js">view.js</a></h3>

<p><code>QikEngine.View</code> &#8211; абстрактный класс, представляющий view на стороне клиента. Обслуживание обновлений, отображение в элементах HTML, обработка событий и т.п. Списки элементов хранятся прямо в DOM-контейнере в виде HTML-узлов.</p>

<p>Потомки <code>QikEngine.View</code> для реализации конкретных view:  <code>QikEngine.PublicUserStreamView</code>, <code>QikEngine.PublicRecentStreamsView</code> и т.д.</p>

<h3><a href="http://engine.qik.com/examples/js/updater.js">updater.js</a></h3>

<p>Очень простой интерфейс между данными, полученными из Qik Push Engine (например, информацией о стримах) и HTML-элементами. Адаптация данных, например, перевод дат из UTC в локальный формат, форматирование значений и т.п.</p>

<h3><a href="http://engine.qik.com/examples/js/usercache.js">usercache.js</a></h3>

<p><code>QikEngine.UserCache</code> &#8211; механизм загрузки информации о пользователях (по их ID). Использует <code>DeferredManager</code> для ограничения количества параллельных запросов.</p>

<h2>Что делать дальше?</h2>

<ul>
<li>смотрите <a href="http://qikapi.pbworks.com/">описание API</a>;</li>
<li>пишите письмо на api@qik.com, расскажите о своем проекте и получайте ключ для доступа к API.</li>
</ul>

<h2>Полный список примеров</h2>

<ul>
<li><a href="http://engine.qik.com/examples/all.videos.html">все последние видео на Qik/live-видео</a></li>
<li><a href="http://engine.qik.com/examples/profile.html">открытые стримы пользователя</a></li>
<li><a href="http://engine.qik.com/examples/user.videos.html">профайл пользователя с точки зрения другого авторизованного пользователя</a></li>
<li><a href="http://engine.qik.com/examples/user.home.html">домашняя страница пользователя: собственные стримы, followerы, стримы от following</a></li>
</ul>

<p>Во всех примерах списки видео обновляются автоматически при внесении изменений на сайте или с мобильного телефона, например, если открою доступ Васе к моему приватному видео, он тут же увидит это видео в списке моих стримов и т.п. </p>
]]></content:encoded>
			<wfw:commentRss>http://www.smira.ru/2009/07/12/qik-push-engine-api-private-beta/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

