Azure auto Scaling

Новая возможность появилась у Azure в статусе preview, Это автоматическое масштабирование в зависимости от величины очереди или нагрузки на один инстанс. То есть наглядный интерфейс типа WASABI

Метки:   Категории:trend | microsoft


Azure SDK 2.0

Перед праздниками вышла новое обновление SDK для Azure. Если кратко, то
Смотреть тут.
Читайть тут
Качать тут
По сути - дальнейшие улучшения интерфейса, чтобы больше кликать, а не писать текстом. Из стоящего - возможность включать и выключать логирование для работающих ролей.
UPDATE: Подумалось, что самое зверское обновление - это новая иконка эмулятора в трее, теперь она более стилизована под windows 8 =)

Метки:   Категории:microsoft


Реклама телефонов Микрософта

Свежее вирусное видео от создателей Lumia 920. В целом целевая аудитория этого телефона действительно не заинтересована в "битвах", им бы скорее хоть в одном телефоне разобраться.



Дополнительно стоит отметить, что Микрософт выпустила демонстрационный проект мобильного приложения с интеграцией с основными социальными сетями . Это сделано для рекламы MobileServices, и также это делает разработку под Windows Phone заметно привлекательнее.

Метки:   Категории:humor | microsoft


Web Survey

Компания Netcraft постоянно опрашивает сервера в Интернете по базе из 17 миллионов доменных имен. Согласно их свежей статистике Микрософт активно набирает популярность, замещая стремительно падающую распространенность серверов с Apache. С начала года доменов на технологии .Net прибавилось на 5%, составив 20%, на втором месте после Apache c 51%.

Метки:   Категории:Analytics | microsoft


Visual Studio 2012 Update 2

Обнаружилось, что стало доступно второе обновление для MSVS2012. Полное описание с картинками того, что было добавлено. Пожалуй самое интерестное - это добавление возможности создания Git репозитария в TFSPreview, то есть бесплатно. По ссылке также есть описание чем Git отличается от TFVC.

Метки:   Категории:microsoft | Code


Windows Azure Storage Certificate Expired - epix fail

Вчера у многих перестали работать BlobStorage, которые открывались по HTTPS. И причиной тому стало истечение сроков валидности серверного сертификата на стороне Azure. Полное обсуждение проблемы на stackoverflow. Но к слову сказать, что отказаться от HTTPS и полностью перейти на HTTP - не лучший вариат, так как если используется локальная отладка с автоматической заменой при публикации на сточку соединения с этим хранилищем, то автоматически будет подставлено соединение в виде HTTPS (опция Update connection string when publishing to Azure). Поэтому наилучшее решение - оставить HTTPS и добавить в Application_Start выполнение команды, отключающей проверку серверного сертификата:
ServicePointManager.ServerCertificateValidationCallback = (s, certificate, chain, sslPolicyErrors) => true;
UPDATE:
Проблема оказалась несколько шире и истечение сертификата привело к нарушениям в работе Azure CDN во всем мире. Это не удивительно, ведь CDN у микрософта построен на репликациях blobstorage.

Ниже пример кода ошибки для поисковиков
System.Net.WebException: The underlying connection was closed: Could not establish trust relationship for the SSL/TLS secure channel. ---> System.Security.Authentication.AuthenticationException: The remote certificate is invalid according to the validation procedure. at System.Net.Security.SslState.StartSendAuthResetSignal(ProtocolToken message, AsyncProtocolRequest asyncRequest, Exception exception) at System.Net.Security.SslState.StartSendBlob(Byte[] incoming, Int32 count, AsyncProtocolRequest asyncRequest) at System.Net.Security.SslState.ProcessReceivedBlob(Byte[] buffer, Int32 count, AsyncProtocolRequest asyncRequest) at System.Net.Security.SslState.StartReceiveBlob(Byte[] buffer, AsyncProtocolRequest asyncRequest) at System.Net.Security.SslState.StartSendBlob(Byte[] incoming, Int32 count, AsyncProtocolRequest asyncRequest) at System.Net.Security.SslState.ProcessReceivedBlob(Byte[] buffer, Int32 count, AsyncProtocolRequest asyncRequest) at System.Net.Security.SslState.StartReceiveBlob(Byte[] buffer, AsyncProtocolRequest asyncRequest) at System.Net.Security.SslState.StartSendBlob(Byte[] incoming, Int32 count, AsyncProtocolRequest asyncRequest) at System.Net.Security.SslState.ProcessReceivedBlob(Byte[] buffer, Int32 count, AsyncProtocolRequest asyncRequest) at System.Net.Security.SslState.StartReceiveBlob(Byte[] buffer, AsyncProtocolRequest asyncRequest) at System.Net.Security.SslState.StartSendBlob(Byte[] incoming, Int32 count, AsyncProtocolRequest asyncRequest) at System.Net.Security.SslState.ForceAuthentication(Boolean receiveFirst, Byte[] buffer, AsyncProtocolRequest asyncRequest) at System.Net.Security.SslState.ProcessAuthentication(LazyAsyncResult lazyResult) at System.Threading.ExecutionContext.runTryCode(Object userData) at System.Runtime.CompilerServices.RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup(TryCode code, CleanupCode backoutCode, Object userData) at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx) at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state) at System.Net.TlsStream.ProcessAuthentication(LazyAsyncResult result) at System.Net.TlsStream.Write(Byte[] buffer, Int32 offset, Int32 size) at System.Net.PooledStream.Write(Byte[] buffer, Int32 offset, Int32 size) at System.Net.ConnectStream.WriteHeaders(Boolean async) --- End of inner exception stack trace --- at System.Net.HttpWebRequest.GetResponse() at System.Data.Services.Client.QueryResult.Execute() at System.Data.Services.Client.DataServiceRequest.Execute[TElement](DataServiceContext context, QueryComponents queryComponents) at System.Data.Services.Client.DataServiceQuery`1.Execute() at System.Data.Services.Client.DataServiceQuery`1.GetEnumerator() at System.Linq.Enumerable.FirstOrDefault[TSource](IEnumerable`1 source) at System.Data.Services.Client.DataServiceQueryProvider.ReturnSingleton[TElement](Expression expression) at System.Linq.Queryable.FirstOrDefault[TSource](IQueryable`1 source)

Метки: ,   Категории:bugs | microsoft


Azure SDK 1.7 -> 1.8 или как потерять 1000р за один апдейт

Еще в октябре прошлого года одновременно с MSVS2012 появилась версия Azure SDK 1.8 и казалось, что это чисто формальное обновление связанное с переходом на новую Visual Studio. Конечно есть и улучшения, такие как поиск по имени ошибки во многочисленных логах IntelliTrace (как можно было не догадаться сделать это с самомго начала?). Возможно рано или поздно у нас также появится средства для нормальной работы с blobstorage из самой студии. Однако, подозрения, что с 1.8 что-то не так начали появляться сразу, о чем был поднять вопрос в сообществе по рекламе Azure в России.

По опыту выяснилось,что
1) В результате автоматического обновления ссылки на библиотеки 1.8 ставятся CopyLocal = False, даже если до этого у вас стояло копирования этой библиотеки.
2) Уничтожен класс CloudConfigurationManager, отвечавший за получение настроек роли. Вместо него можно использовать RoleEnvironment.GetConfigurationSettingValue("Diagnostics.ConnectionString"); С той лишь разницей, что последняя может падать, если RoleEnvironment.IsAvailiable = false.Например, если обратиться до завершения WebRole.OnStart
3) И самое страшное проиллюстрирую следующим кодом. Заключается в различной обработке Таблицами параметров содержащих символов попадающих под UrlEncode.
Copy Source | Copy HTML
  1. string pageuri = pageElement.Attribute("uri").Value.ToLower().Replace("/default.aspx", "/");
  2. string pageuri_encoded = HttpUtility.UrlEncode(pageuri);
  3. SapePageAzure result = (from e in CreateQuery()
  4.                         where e.RowKey == pageuri_encoded &&
  5.                         e.PartitionKey == partitionid select e).FirstOrDefault();
  6. if (result == null)
  7. {
  8.     result = new SapePageAzure
  9.     {
  10.         RowKey = pageuri,
  11.         PartitionKey = partitionid,
  12.         LastAccess = DateTime.Now
  13.     };
  14.     ContainerTable.AddObject(TableName, result);
  15.     //ContainerTable.SaveChangesWithRetries();
  16. }

Если в Azure SDK1.7 нужно было использовать в обоих местах pageuri_encoded, и в выворе и во вставке. То в Azure SDK1.8 добавилась скрытая перекодировка, и получилось, что при выборе параметров из таблицы нужно использовать pageuri_encoded, а при вставке pageuri, так как теперь кодируется автоматически.
Как результат - все блоги из blogscloud.ru, которые синхронизированы с Sape и хранят набор ссылок в виде URL в таблицах, перестали корректно работать. Sape обнаруживает, что все ссылки на сайтах не отображаются и блокирует все сайты отправляя их на повторную можерацию. End of story.


PS: По случаю отзыв про tfspreview.com - возможность бесплатного использования TFS2012 для одиноких разработчиков. В общем ни в коем случае не расчитывайте на нее. Очень сильно тормозит и не дает чекинить большое количество документов. Появилось сие творение видимо потому, что все нормальные платные TFS провайдеры, такие как discountasp.net не смогли или не захотели вовремя предоставить TFS2012 для коммерческих пользователей.

Метки:   Категории:Analytics | Csharp | microsoft | Code


Автоматическая настройка Application Pool в Windows Azure Web Role

Для чего может потребоваться изменить настройки по умолчанию? Например, если при старте выполняется операция по скачиванию файла из блоба на 100Гб, что занимает несколько минут, то запуск вашего пула подвисает. А так как есть настройка его пингования раз в полторы минуты, то управляющий IIS может решить что запуск провалился и прервать передачу данных, убив процесс и запустив новый, который опять подвиснет при попытке скачать большой файл. Выход следующий.
1. Добавьте управляющую библиотеку - Add Microsoft.Web.Administration DLL to the project reference. (Path of Microsoft.Web.Administration DLL: C:\Windows\System32\inetsrv).

2. Установите ее копирование - Set Copy local property of the above DLL to true. (Right click on the DLL -> properties -> copy local = true)

3. Для изменения настроек нужны специальные привилегии, которые даются - Run the WebRole in elevated execution context. (Add the below tag in servicedefinition.csdef file to run the code in elevated privileges)
Runtime executionContext="elevated"

4. В функцию OnStart() запуска роли вставляем следующий код:

Copy Source | Copy HTML
  1. /// <summary>
  2. /// Запуск роли
  3. /// </summary>
  4. /// <returns></returns>
  5. public override bool OnStart()
  6. {
  7.     try
  8.     {
  9.         ConfigureAzureDiagnostics();
  10.         ServerManager iisManager = new ServerManager();
  11.         //iisManager.ApplicationPoolDefaults.ProcessModel.PingingEnabled = false;//запрещаем пинги! НО ТАК НЕЛЬЗЯ
  12.         iisManager.ApplicationPoolDefaults.ProcessModel.PingResponseTime = TimeSpan.FromMinutes(20);
  13.         iisManager.ApplicationPoolDefaults.ProcessModel.PingInterval = TimeSpan.FromMinutes(5);
  14.         iisManager.ApplicationPoolDefaults.ProcessModel.StartupTimeLimit = TimeSpan.FromMinutes(20);
  15.         iisManager.ApplicationPoolDefaults.QueueLength = 3000;
  16.         //iisManager.ApplicationPoolDefaults.Recycling.PeriodicRestart.Schedule.Clear();
  17.         //iisManager.ApplicationPoolDefaults.Recycling.PeriodicRestart.Schedule.Add(TimeSpan.FromHours(8));
  18.         iisManager.CommitChanges();
  19.         Trace.WriteLine("Role Started!");
  20.     }
  21.     catch (Exception e1)
  22.     {
  23.         Trace.Write(e1);
  24.     }
  25.     return base.OnStart();
  26. }

5. PS: Отмечу, что полностью отключать функцию пингования ни в коем случае нельзя, так как тогда все портальные сервисы азура будут говорить об неопределенном статусе инстанса, что поставит вас в тупик.

Метки:   Категории:microsoft | Code


Качество работы Azure Blob Storage

Как мы знаем, Azure гарантирует 99,95% доступности сервисов. Отчести эти 0,05% стабильно зарыты в качестве работы BlobStorage. Если для BlobStorage включить статистику, то получается примерно следующая картина, которая наглядно говорит о том, что AzureTable не настолько надежны, чтобы полностью заменить SQL базы данных. Примерно в 0,5% соединений происходит Network Error. Поразмышляв над этим я не исключаю, что такая картина может являться причиной ThreadAbortException на стороне WebRole, и никоим образом не есть ошибка Storage. Но пока ясных доказательств предположения я не получил.

Метки:   Категории:Analytics | microsoft


Lucene net 3.0.3

Полтора месяца назад доступна стала обновленная версия Lucene 3.0, а значит команда, которая полтора года назад забросила развитие этого проекта получила новое финансирование. В связи с этим была обновлена и моя поисковая система. Замечено заметное улучшение скорости индексирования при ограниченных ресурсах. Примерно в 2-2,5 раза. То есть даже при минимальных мощностях можно индексировать по 300 постов в секунду. Обусловлено прежде всего тем, что анализаторы в новой библиотеки оперируют не токенами, а байтами и буферами, что сильно ускоряет токенизацию. Также в связи с новой библиотекой потребуется обновить Azure Directory, но там ничего сложного. Общая концепция изменения объекта Directory в том, что объединили функции Close и Dispose и во многих местах ушли от функций в пользу get-property что уменьшает расход памяти.

AzureDirectory.rar (1,58 mb)


Метки:   Категории:microsoft | Code | t30p


Amazon RDS news

Амазон продолжает развивать свои облачные базы данных быстрыми шагами. На этот раз на 14% понижена стоимость RDS и вводится в эксплуатацию MSSQL и WindowsServer 2012. Эти шаги наверняка направлены, чтобы сделать Амазон более конкурентоспособным по сравнению с достаточно дешевым Azure. У микрософта также было сезонное снежение цен - они предоставили скидки от 20% в начале сентября 2012, однако, российских пользователей это не затронуло, так как они получают доступ к облаку Azure через ряд посредников.

Метки:   Категории:microsoft | news


Безопастность Skype

Несколько блогеров подняли шумиху из-за возможности взлома skype. Официально Микрософт уже принял нужные меры. Кроме того отмечает Касперский, что о баге было известно уже 2 месяца.

Метки:   Категории:microsoft | news


Контекстный поиск с русской и английской морфологией на основе проектов с открытым исходным кодом и облачных сервисов Microsoft Azure

keywords: контекстный поиск, русская морфология, поточное индексирование, облачные сервисы
abstract: В статье дается общее представление об архитектуре поискового сервиса, построенного на проектах с открытым исходным кодом и рассматриваются базовые проблемы реализации безотказной его работы в условиях поточных данных и минимальных системных ресурсах. Реализация опирается на следующие проекты:

На скриншотах присутствует программа для работы с BlobStorage - AzureStorageExplorer, которая также доступна с открытым кодом - http://azurestorageexplorer.codeplex.com/. Сам пост написан и хостится в облаке Azure с использованием BlogEngine - http://blogengine.codeplex.com/

 

Общее

Разложим задачу создания поискового сервиса на пять составляющих: 1. Сбор данных. 2. Фильтрация, токенизация, стемминг, синонимизация данных. 3. Помещение в поисковый индекс. 4. Обработка поисковых запросов. 5. Распознавание что хотел пользователь и предоставление нативного языка запросов.

В сегодняшней статье не будут разобраны пункты 2 и 5. Так как токенизация и стемминг осуществляется при помощи библиотеки Lemmatizer.dll, разработанной сотрудниками Яндекса, и рассматривалась мною 4 года назад. За прошедшее время статью открывали более 20 тыс. раз. Сама же библиотека Lemmatizer.dll перестала быть платной и с 2011 года доступна всем желающим в исходниках. Последний пункт - создание человеческого языка, запросов требует большую наработку данных уже от пользователей производящих запросы, и может дорабатываться в процессе работы поискового сервиса. На сегодняшний момент языком запросов будет являться стандартный синтаксис Lucene, который достаточно гибкий. Так например, орфографические ошибки в запросах можно исправлять с помощью нечеткого поиска "word~",

Сбор данных

В поточном режиме при помощи Twitterizer.NET подключимся к Twitter.Streaming API. Вытаскиваем все русскоязычные твиты, фотографии instagram, видео с youtube (+Vimeo), посты на стене Вконтакте, фотографии Facebook, посты GooglePlus. Для разбора данных воспользуемся библиотеками BlogsAPI, GoogleGData, FacebookSDK соответственно. Вытаскиваем из соц.сетей все данные, которые не требуют access_token - то есть присутствия пользователя. Примеры извлечения этих данных есть в примерах самих библиотек. Также важно понимать, что большинство ссылок на информацию пропущено через сервисы сокращения ссылок. Для разворачивания таких ссылок есть простая функция в BlogsAPI для большинства известных сокращателей:

Copy Source | Copy HTML
  1. Shortener shrt = Common.GetShortenerByLink(url.ExpandedUrl) as Shortener;
  2. if (shrt != null)
  3. {
  4.         fulllink = shrt.ConvertDataTo(url.ExpandedUrl, ItemType.ShortUrl, ItemType.FullUrl);
  5. }

После извлечения - данные группируются в пакеты (Batch) и отправляются на WCF-сервис индексирования. Для сложных данных, требующих дополнительных запросов используется Azure BlobQueue(примеры работы) для отложенной индексации или, как в случае Facebook и Vkontakte, группового извлечения данных, когда в одном запросе к социальной сети запрашивается сразу несколько постов.

Так как много данных не бывает, то в тотже индекс мы добавим все посты (если быть точным - большую часть) livejournal.com, liveinternet.ru, qip.ru,juick.com, blogi.mail.ru и т.д. Отдельно отмечу, что такой интернет гигант как Яндекс не выдает информации по своим блогам wow.ya.ru и видеоблогам на video.yandex.ru через API.

Индексирование

Благодаря облачным технологиям Микрософта у нас появляется замечательный объект BlobStorage, который предоставляет до 100 ТБ под хранение индекса. Чтобы разместить в нем наш поисковый индекс формируемый Lucene, достаточно воспользоваться проектом AzureDirectory. Основной индекс будет храниться в BlobStorage, а на локальном инстансе WebRole создается специальная промежуточная директория - LocalStorage, где производятся все операции с индексом по поиску и изменению его. Важно понимать, что инстансов WebRole у нас должно быть 2 и более, так как к конечный момент времени один из них заблокировал индекс на запись (добавление объектов), а с остальных идет непрерывное чтение (поисковые команды). При этом чтение на инстансе, где идет запись - невозможно. Модель "один писатель, много читателей". При завершении записи данные с локального LocalStorage копируются в центральный BlobStorage откуда синхронизируются на остальные машины. Ниже приводится класс, который реализует связь Lucene и AzureDirectory, функции работы с Lucene не приводятся, примеры этого есть в интернете.

  1. public abstract class IndexerBase
  2. {
  3.     protected IndexerBase()
  4.     {
  5.     }
  6.     /// <summary>
  7.     /// Constructor
  8.     /// </summary>
  9.     protected IndexerBase(string sDir):base()
  10.     {
  11.         IndexDir = sDir;
  12.     }
  13.     /// <summary>
  14.     /// Объект блокировки, переопределяемый дочерними классами
  15.     /// </summary>
  16.     protected static object oWriting = new object();
  17.     /// <summary>
  18.     /// Название папки с индексом
  19.     /// </summary>
  20.     protected string IndexDir = "IndexBase";
  21.     public string cachepath
  22.     {
  23.         get
  24.         {
  25. #if DEBUG
  26.             return Path.Combine(Environment.ExpandEnvironmentVariables("%temp%"), IndexDir);
  27. #else
  28.             return Path.Combine(RoleEnvironment.GetLocalResource("MyStorage").RootPath,IndexDir);
  29. #endif
  30.         }
  31.     }
  32.     private AzureDirectory _azureDirectory;
  33.     /// <summary>
  34.     /// Директория
  35.     /// </summary>
  36.     protected AzureDirectory azureDirectory
  37.     {
  38.         get
  39.         {
  40.             if (_azureDirectory != null) return _azureDirectory;
  41.             _azureDirectory = new AzureDirectory(CloudStorageAccount.Parse(
  42.                         RoleEnvironment.GetConfigurationSettingValue("SearchConnectionString")), IndexDir,
  43.                         new SimpleFSDirectory(new DirectoryInfo(cachepath)));
  44.             return _azureDirectory;
  45.         }
  46.     }
  47.     /// <summary>
  48.     /// Указывает на первое время запуска и открытия поискового индекса
  49.     /// </summary>
  50.     protected static Boolean firsttime = true;
  51.     /// <summary>
  52.     /// Статический поток занимающийся перезаписью сегментов
  53.     /// </summary>
  54.     protected static ConcurrentMergeScheduler Merger = new ConcurrentMergeScheduler();
  55.     /// <summary>
  56.     /// То, как писать
  57.     /// </summary>
  58.     private IndexWriter _MyWriter = null;
  59.     protected IndexWriter MyWriter
  60.     {
  61.         get
  62.         {
  63.             if (_MyWriter != null) return _MyWriter;
  64.             lock (oWriting)
  65.             {
  66.                 if (_MyWriter != null) return _MyWriter;
  67.                 CreateMyWriter();
  68.             }
  69.             return _MyWriter;
  70.         }
  71.         set { _MyWriter = value; }
  72.     }
  73.     private void CreateMyWriter()
  74.     {
  75.         try
  76.         {
  77.             _MyWriter = new IndexWriter(azureDirectory, MyAnalyser, false);
  78.             //важны атомарные операции, так как несмотря на lock(oWriting) в случае нескольких дочерних классов индексирования, возникнуть может многопоточность
  79.             Interlocked.Exchange(ref LockFailsCounter,  0); //успешно открылось на запись, сбросили счетчик неудач
  80.         }
  81.         catch (LockObtainFailedException e0)
  82.         {
  83.             //если уже много ошибок подряд, около часа
  84.             if (LockFailsCounter > 30)
  85.             {
  86.                 //принудительно удаляем блокировку =(
  87.                 azureDirectory.ClearLock("write.lock");
  88.                 //Trace.Write(e0);
  89.                 Trace.Write("Принудительное снятие старой блокировки на запись.");
  90.                 Thread.Sleep(60 * 1000); //1 min for unlock
  91.                 _MyWriter = new IndexWriter(azureDirectory, MyAnalyser, false);
  92.                 Interlocked.Exchange(ref LockFailsCounter,  0);//успешно открылось на запись, сбросили счетчик неудач
  93.             }
  94.             else
  95.             {
  96.                 Interlocked.Increment(ref LockFailsCounter);
  97.                 throw;
  98.                 //вызовем ошибку, чтобы запрос на индексирование был повторен через некоторое время, когда файл разблокируется
  99.             }
  100.         }
  101.         catch (Exception err)
  102.         {
  103.             Trace.WriteLine("Не удалось открыть старый индекс: " + err.Message);
  104.             if (System.IO.Directory.Exists(cachepath))
  105.             {
  106.                 System.IO.Directory.Delete(cachepath, true);
  107.                 //удаляем возможный старый хлам помешавший открытию
  108.                 System.IO.Directory.CreateDirectory(cachepath);
  109.             }
  110.             //создадим новый еще раз!
  111.             _MyWriter = new IndexWriter(azureDirectory, MyAnalyser, true);
  112.         }
  113.         _MyWriter.SetMergeScheduler(Merger);
  114.         _MyWriter.SetMergeFactor(10);
  115.         //_MyWriter.SetUseCompoundFile(false);
  116.     }
  117.     /// <summary>
  118.     /// Время открытия поискового индекса
  119.     /// </summary>
  120.     protected DateTime? SyncTime
  121.     {
  122.         get { return HttpContext.Current.Application[GetType() + "synctime"] as DateTime?; }
  123.         set { HttpContext.Current.Application[GetType() + "synctime"] = value; }
  124.     }
  125.     /// <summary>
  126.     /// Количество неудачных попыток открыть индекс на запись
  127.     /// </summary>
  128.     /// <remarks>Важно, что это один объект на все дочерние классы индексирования</remarks>
  129.     private static int LockFailsCounter =  0;
  130.     /// <summary>
  131.     /// То, как писать
  132.     /// </summary>
  133.     protected IndexSearcher _MySearcher
  134.     {
  135.         get { return HttpContext.Current.Application[GetType() + "searcher"] as IndexSearcher; }
  136.         set { HttpContext.Current.Application[GetType() + "searcher"] = value; }
  137.     }
  138.     public IndexSearcher MySearcher
  139.     {
  140.         get
  141.         {
  142.             if (SyncTime.HasValue && SyncTime.Value.AddHours(1)<DateTime.Now)//более ~  с момента последней синхронизации(!)
  143.             {
  144.                 //делаем запись только в том, случае, если индекс уже был успешно открыт ранее на запись, и сделана синхронизация с основным
  145.                 //иначе может произойти потеря
  146.                 IndexCommit();
  147.             }
  148.             if (_MySearcher != null) return _MySearcher;
  149.             lock (oWriting)
  150.             {
  151.                 if (_MySearcher != null) return _MySearcher;
  152.                 var myPerf = new PerformanceTimer();
  153.                 myPerf.StartTimer();
  154.                 try
  155.                 {
  156.                     _MySearcher = new IndexSearcher(azureDirectory, true);
  157.                 }
  158.                 catch (CorruptIndexException)
  159.                 {
  160.                     //требуется починка индекса
  161.                     FixIndex(RoleEnvironment.DeploymentId);
  162.                 }
  163.                 catch (Exception e1) //если у нас нету еще директории индекса или сбой соединения
  164.                 {
  165.                     Trace.Write(e1);
  166.                     if (_MyWriter != null)
  167.                     {
  168.                         Trace.Write("Warning: have to close MyWriter to open MySearcher!");
  169.                         _MyWriter.Commit();
  170.                         _MyWriter.Close();
  171.                         _MyWriter = null;
  172.                     }
  173.                     if (System.IO.Directory.Exists(cachepath))
  174.                     {
  175.                         System.IO.Directory.Delete(cachepath, true); //удаляем возможный старый хлам помешавший открытию
  176.                         System.IO.Directory.CreateDirectory(cachepath);
  177.                     }
  178.                     try
  179.                    &nbssearcherp;{
  180.   nbsp;запись                      //важно для всех случаев когда индекс открывается в отсутствующую папку.
  181.                         var openindex = MyWriter;
  182.                     }catch(LockObtainFailedException)
  183.                     {
  184.                         //Папка с индексом успешно открыта и создана, однако другая роль уже пишет индекс
  185.                     }
  186.                     try
  187.                     {
  188.                         _MySearcher = new IndexSearcher(azureDirectory, true);
  189.                     }catch(FileNotFoundException foi)
  190.                     {
  191.                         //индекс скопированный с мастера не содержит нужного сегмента
  192.                         if(foi.InnerException!=null && foi.InnerException.GetType() == typeof(StorageClientException))
  193.                         {
  194.                             //вызываем утилиту исправления ошибок
  195.                             FixIndex(RoleEnvironment.DeploymentId);
  196.                         }
  197.                     }
  198.                     Trace.Write(String.Format("BadMode: _MySearcher {0} loaded in {1}", GetType().Name, myPerf.StopTimer()));
  199.                 }
  200.                 if(firsttime)
  201.                 {
  202.                     firsttime = false;
  203.                     Trace.Write(String.Format("GoodMode: _MySearcher {0} loaded in {1}", GetType().Name, myPerf.StopTimer()));
  204.                 }
  205.             }
  206.             return _MySearcher;
  207.         }
  208.     }
  209.     /// <summary>
  210.     /// Морфологический анализатор
  211.     /// </summary>
  212.     internal static MorphologyAnalyzer _MyAnalyser;
  213.     protected MorphologyAnalyzer MyAnalyser
  214.     {
  215.         get
  216.         {
  217.             if (_MyAnalyser != null && _MyAnalyser.Morph != null && _MyAnalyser.Morph.isLoaded) return _MyAnalyser;
  218.             Trace.Write("Заново загружаем MorphologyAnalyzer");
  219.             _MyAnalyser = new MorphologyAnalyzer(null, false);
  220.             return _MyAnalyser;
  221.         }
  222.     }
  223.     static IndexerBase()
  224.     {
  225.         RoleEnvironment.Stopping += delegate
  226.         {
  227.             try
  228.             {
  229.                 //дожидаемся завершения записи
  230.                 lock (oWriting)
  231.                 {
  232.                     //останавливаем поток слияния
  233.                     Merger.Close();
  234.                     Merger = null;
  235.                 }
  236.             }catch(Exception e)
  237.             {
  238.                 Trace.Write(e);
  239.             }
  240.         };
  241.     }
  242.     /// <summary>
  243.     /// Запись индекса на диск и переоткрытие поискового механизма
  244.     /// </summary>
  245.     protected void IndexCommit()
  246.     {
  247.         SyncTime = DateTime.Now;
  248.         lock (oWriting)
  249.         {
  250.             if (_MyWriter != null)
  251.             {
  252.                 //закрыли текущий коммит
  253.                 try
  254.                 {
  255.                     _MyWriter.Commit();
  256.                 }catch(CorruptIndexException)
  257.                 {
  258.                     FixIndex(RoleEnvironment.DeploymentId);
  259.                 }
  260.                 _MyWriter.Close();
  261.                 _MyWriter = null;
  262.             }
  263.             if (_MySearcher != null)
  264.             {
  265.                 //закрыли тукущий поисковик
  266.                 _MySearcher.Close();
  267.                 _MySearcher = null;
  268.             }
  269.         }
  270.         //открыли новый поисковик
  271.         var opensearcher = MySearcher;
  272.     }
  273.     /// <summary>
  274.     /// Серьезный процесс по востановлению индекса 
  275.     /// </summary>
  276.     /// <param name="DeploymentId"></param>
  277.     /// <returns></returns>
  278.     public string FixIndex(string DeploymentId)
  279.     {
  280.         var myPerf = new PerformanceTimer();
  281.         myPerf.StartTimer();
  282.         Trace.WriteLine(String.Format("Вызов проверки статуса и исправления индекса со значением  {0} / {1}", DeploymentId, GetType().Name));
  283.         string lockfile = GetType().Name + "_" + DeploymentId + ".lock";
  284.         Lock indexLock = azureDirectory.MakeLock(lockfile);
  285.         if (indexLock.Obtain())//блокировка основного индекса
  286.         {
  287.             try
  288.             {
  289.                 //устроим проверку индекса!
  290.                 CheckIndex fixer = new CheckIndex(azureDirectory);
  291.                 CheckIndex.Status oStatus = fixer.CheckIndex_Renamed_Method();
  292.                 Trace.WriteLine(GetType().Name+":status=" + oStatus.numBadSegments + ":" + oStatus.totLoseDocCount);
  293.                 if (oStatus.numBadSegments != oStatus.numSegments && oStatus.numBadSegments >  0)
  294.                 {
  295.                     fixer.FixIndex(oStatus); //опасная функция перезаписи
  296.                 }
  297.                 Trace.WriteLine(GetType().Name + ":FixIndex finished");
  298.                 //убедимся, что разблокировали индекс после правки
  299.                 azureDirectory.ClearLock("write.lock");
  300.             }
  301.             catch (Exception e1)
  302.             {
  303.                 Trace.Write(e1);
  304.             }
  305.             finally
  306.             {
  307.                 indexLock.Release();
  308.                 azureDirectory.DeleteFile(lockfile);
  309.             }
  310.             //////////******************////////////
  311.         }
  312.         return myPerf.StopTimer().ToString();
  313.     }
  314. }


Поясню основные моменты:
1. Класс абстрактный, так как от него наследуется WCF сервис, который может содержать свой индекс. То есть один инстанс может работать с несколькими индексами Lucene которые хранятся в одном BlobStorage в разных папках.
2. В Azure любой инстанс может быть выключен в любую секунду, поэтому важно обрабатывать событие остановки и корректно завершать работу отдельного потока ConcurrentMergeScheduler занимающегося оптимизацией индекса.
3. Объект чтения MySearcher у нас открыт всегда, чтобы минимизировать время поиска. Объект записи MyWriter создается только при получении данных на запись и соответственно закрывает и блокирует чтение для текущего индекса на инстансе.
4. Очень важна отказоустойчивость, поэтому такая детальная обработка ошибок LockObtainFailedException - когда пришли данные на запись, а основной индекс в BlogStorage заблокирован. Например, другим инстансом, который после этого падает по OutOfMemoryException и не снимает блокировки. Соответственно через несколько таких ошибок блокировка снимается автоматически и буфера поставщиков данных не успевают переполниться и потери данных не происходит.
5. Другая стандартная ошибка - CorruptIndexException - вполне может быть следствием сбоем сети при передачи больших файлов. При этом последняя операция записи отменяется, однако это занимает значительное время и блокирует другие операции записи. Ниже, как это выглядит в логах.




Еще раз перечислю основные проблемы, к которым нужно быть готовыми: одновременный приход запросов на индексирование, произвольное выключение инстанса, нечитаемость индекса, нехватка памяти, невозможность эксклюзивно заблокировать индекс, очищение LocalStorage. Последние подразумевает то, что согласно документации, подключаемый к инстансу диск несмотря на настройку cleanOnRecycle="false" всеравно не является величиной постоянной и может быть очищен в любой момент. Последнее означает повторную синхронизацию с центральным BlobStorage и открытие объектов чтения и записи займет дольше обычного (2-3 сек) на несколько минут. В итоге все работает стабильно и загрузка одного индексирующего инстанса показана ниже. К слову, мы используем всего-лишь два инстанса с минимальными мощностями, так как сильно ограничены в ресурсах. Пикам по 100% соответствует обработка пакета на индексацию. С уверенностью можно сказать, что за простой ресурсов мы не платим.


Вторая картинка приведена для сравнения, и формируется на системном портале Azure как среднее по WebRoles в Endpoint. Очевидно, что она не дает представления о том, что твориться с инстансами.

Выполнение поисковых запросов

В условиях, когда мы не знаем в каком состоянии находится инстанс, пишет он или нет, единственный правильный вариант - отправлять поисковый запрос на все инстансы, и брать первый ненулевой ответ. При наличии ресурсов можно было бы выделить отдельную Endpoint и WebRole, которая работала бы только на чтение. Но мы не ищем простых путей, поэтому на каждой webrole с индексом имеется следующая поисковая функция, выполняющаяся только если инстанс не пишет в индекс

Copy Source | Copy HTML
  1. /// <summary>
  2. /// Контекстный поиск с проверкой на поток записи, если открыта запись, то возвращаем NULL
  3. /// </summary>
  4. /// <param name="opts">Слова и параметры поиска</param>
  5. /// <param name="ignorelock">Не проверять идет ли запись</param>
  6. /// <returns>найденные результаты, CAN BE NULL</returns>
  7. public IndexedDocument[] SearchForAdvanced(SearchForOptions opts, bool ignorelock)
  8. {
  9.     //без проверки на блокировку
  10.     if (ignorelock) return SearchFor(opts);
  11.     bool isNotLocked = false;
  12.     try
  13.     {
  14.         isNotLocked = Monitor.TryEnter(oWriting);
  15.     }
  16.     finally
  17.     {
  18.         if (isNotLocked)
  19.         {
  20.             //важно освободить блокировку на поиск как можно скорее
  21.             Monitor.Exit(oWriting);
  22.         }
  23.     }
  24.     if (isNotLocked)
  25.     {
  26.         return SearchFor(opts);
  27.     }
  28.     //заблокировано
  29.     return null;
  30. }

И соответственно на поисковом клиенте реализуется многопоточная отправка с ожидаем быстрейшего ненулевого результата через ThreadPool.QueueUserWorkItem(state => SearchForAdvancedAsync(oData));

Copy Source | Copy HTML
  1. /// <summary>
  2. /// Объет параллельного запроса
  3. /// </summary>
  4. public class SearchAsyncObject
  5. {
  6.     /// <summary>
  7.     /// Число параллельных запросов
  8.     /// </summary>
  9.     public readonly int Count = 3;
  10.     /// <summary>
  11.     /// Число обработанных вызовов
  12.     /// </summary>
  13.     public int Finished =  0;
  14.     /// <summary>
  15.     /// Поисковые параметры
  16.     /// </summary>
  17.     public SearchForOptions opts = null;
  18.     /// <summary>
  19.     /// Результат поиска
  20.     /// </summary>
  21.     public IndexedDocument[] Founds = null;
  22.     /// <summary>
  23.     /// Mutex
  24.     /// </summary>
  25.     public ManualResetEvent mre = new ManualResetEvent(false);
  26. }
  27. /// <summary>
  28. /// Функция завершения поиска
  29. /// </summary>
  30. private static void SearchForAdvancedAsync(SearchAsyncObject data)
  31. {
  32.     try
  33.     {
  34.         IndexClient service = Indexer.GetIndexClient();
  35.         IndexedDocument[] founds = service.SearchForAdvanced(data.opts, false);
  36.         if (founds != null && data.Founds == null) //найден результат(!)
  37.         {
  38.             data.Founds = founds;
  39.             data.mre.Set();
  40.         }else if (Interlocked.Increment(ref data.Finished) >= data.Count){
  41.             data.mre.Set();//все потоки завершили работу
  42.         }
  43.     }
  44.     catch (Exception e1)
  45.     {
  46.         Trace.Write(e1);
  47.     }
  48. }

Функции добавления документов в индекс и выполнение операций поиска уже на конкретной машине выполняются как описано в документации Lucene.

 

Результат

Построенный поисковый сервис используется для выявления наиболее интересной информации в русскоязычных социальных сетях и ранжирования этой информации для формирования топа событий на сайте http://t30p.ru. Полученное таким образом самостоятельное независимое СМИ сильно снижает стоимость и увеличивает качество интернет журналистики. В поисковый индекс можно добавлять новые индексные поля и обновлять код без нарушения работы сервиса или потери данных (проверено). Сейчас в индексе более 200 млн. объектов от 2,5 млн. социальных аккаунтов; примерно 2,5 млн. новых объектов в сутки; размер индекса в пределах 100 GB; время выполнения запросов - 0,5 сек; к индексу выполняется примерно 1000 запросов в час, в том числе внутре-технические. Ожидаемое время запаздывания между нахождением данных и появлением их в поисковой выдаче - 5-15 мин. (в частности, задержку дает "кэширующий" CDN на поисковой странице).

Проблемы и решения

Более int.MaxValue объектов
Поисковый индекс Lucene ограничен примерно в 2 млрд. объектов. Поэтому для работы с большим числом объектов на помощь приходит MultiSeacher, который еще не портирован в .Net версию. Он позволяет производить поиск по конечному набору поисковых индексов при этом теряется скорость.
Нехватка места или превышение 100ТБ
При использовании MultiSeacher для поиска по нескольким поисковым индексам каждый индекс может быть привязан к своему диску или BlobStorage, что решает проблему с ограничением места.
Дублирование информации
Несмотря на обработку поточных данных от самих социальных сервисов, в них часто происходят сбои и разрывы соединений, что приводит к разного рода ошибкам. Так возможно повторное индексирование постов. Поэтому на поисковом клиенте производится проверка уникальности ссылки на пост в результате. В теории эту задачу можно переложить на фильтр DuplicateFilter
Lucene 4.0 имеет классы встроенной русскоязычной морфологии
Какой бы не была встроенная морфология, я бы рекомендовал использовать более тяжеловесный Lemmatizer, библиотеки которого в архиве zip занимают 100МБ, что говорит о серьезности подхода к составлению словаря.
И куда потом все эти собранные данные?
Например, можно создать сервис в Azure Marketplace и продавать доступ к ним по фиксированной цене. Там же есть интересный платный сервис перевода текстов на многие языки, что позволит в автоматическом режиме перевести всю собранную информацию на любой язык и сделать международный топ русскоязычных социальных новостей.


Метки: , , , , , ,   Категории:Blogs | Csharp | Yandex | microsoft | Code


Azure SQL connectivity fail

С завидной регулярность раз в две недели продолжает барахлить наш любимый Azure. Если две недели назад падал CDN, то на этот раз почти на час пропадала связь с Azure SQL Database и статус был переведен в RED. По факту работающие приложения не могли открыть новое соединение с базой данных, однако простая перезагрузка инстанса - удивительным образом помогала. Ради справедливости, отмечу, что в других облаках, таких как Amazon RDS тоже планируется недоступность базы, но она будет вызвана плановыми техническими работами в начале ноября, о которых предупредили за месяц.
UPDATE: Как пишут на днях у Амазона случился куда более серьезный сбой, положивший ряд известных проектов.
Oct 24 2012 10:54AM We are experiencing an issue with access to servers & databases in the North Europe Sub-Region. We are actively investigating this issue and working to resolve it as soon as possible. Further updates will be published to keep you apprised of the situation. We apologize for any inconvenience this causes our customers.
Oct 24 2012 11:25PM The root cause was diagnosed and recovery steps were implemented to restore availability on the affected cluster. Azure SQL Database is now functioning normally in the North Europe sub-region. We understand the root cause of this issue, and are taking necessary steps to prevent this issue from reoccurring in the future. We apologize for any inconvenience this has caused our customers

Метки: ,   Категории:bugs | microsoft


Azure CDN Down 4th time in row

Как и было замечено ранее, CDN на азуре падает ровно раз в 2 недели на час. Вот и сегодня произошло очередное падение, которое привело к неработоспособности на час.

Метки:   Категории:microsoft


самая слабая сторона Azure - это CDN

С завидной регулярностью продолжает барахлить Azure CDN (Content Distribution Network). Недоступность сервисов подключенных к CDN происходит на час раз в 2-3 недели уже третий раз подряд. Это может говорить о том, что сервис молодой и ведется постоянная его доработка. Наблюдать за состоянием можно на Dashboard. Так вчерашний сбой был помечен как "Красный", в отличии от предыдущих "желтых". Но не смотря на это сервисные инженеры Микрософта говорят, что компенсаций не будет и часовые сбои не выходят за рамки условий предоставления сервиса.

Метки:   Категории:microsoft


Azure StorageAccount's Queues

Как известно Storage в Азуре состоит из 3х основных элементов - BlobStorage, TableStorage, QueueStorage. Подробное описание работы с первыми двумя есть в документации MSDN, а вот про очереди написано мало. Более того, последняя версия Azure SDK не содержит встроенного в MSVS интерфейса для работы с очередями, как приведено на скриншотах ниже. Поэтому для ручной работы с очередями можно использовать упоминавшийся ранее AzureStorageExplorer 5.0 alpha. По сути, очереди используются для отладки в IntelliTrace и реализуют интерфейс FirstInFirstOut, который в принципе каждый смог бы реализовать и на более привычном TableStorage.
Также как и для Таблиц, для очередей нужно создавать клиента:
Copy Source | Copy HTML
  1. #region Работа через queue
  2. private static CloudQueue Queue
  3. {
  4.     get
  5.     {
  6.         CloudQueue _container;
  7.         CloudStorageAccount storageAccount = CloudStorageAccount.Parse(ConfigurationManager.AppSettings["StorageAccount"]);
  8.         CloudQueueClient queueClient = storageAccount.CreateCloudQueueClient();
  9.         // Retrieve a reference to a container  
  10.         _container = queueClient.GetQueueReference("postsforindex");
  11.         // Create the container if it doesn't already exist
  12.         //_container.CreateIfNotExist();
  13.         return _container;
  14.     }
  15. }
  16. #endregion
Важно, чтобы контекст клиента не был просто статической переменной класса, так как это приведет к блокировке. Когда контекст клиента создан, нас интересует 2 базовые операции. Помещение в очередь и обработка очереди. Помещение в очередь возможно одной командой, где одно сообщение представляет из себя простое текстовое сообщение. Уникальная особенность очередей Азура в том, что можно сразу сказать, что помещенное сообщение в очередь станет доступно не сразу, а через некоторое время, в нашем случае это 15 минут.
Copy Source | Copy HTML
  1. //добавили ссылку в очередь на индексирование
  2. Queue.AddMessage(new CloudQueueMessage(url.ExpandedUrl), null, TimeSpan.FromMinutes(15));
  3. //делаем 15 минут задержку на проставление лайков 
Обработка сообщений из очереди возможна следующим кодом.
Copy Source | Copy HTML
  1. for (CloudQueueMessage message = Queue.GetMessage();
  2.                     message != null && !MainService._isStoped
  3.                     //After reading the message, the client should delete it.
  4.                     Queue.DeleteMessage(message),message = Queue.GetMessage()
  5.                     )
  6.                 {
  7.                     PostToIndex pst = MessageDecode(message);
  8.                 }
И обещанный вначале скриншот, показывающий инструмент ручной работы с очередями.

Метки: , ,   Категории:microsoft


Windows Azure Mobile Services

В новом портале Windows Azure стала доступна новая вкладка для разработчиков мобильных приложений. Новый интерфейс позволяет полностью абстрагироваться от создания в облаке endpoint'a, storageblob, синхронизацией на на несколько ролей, авторизации пользователей и т.д. Все это объединено в одну "мобильную точку доступа", а все необходимые компоненты создаются автоматически, остается только скопировать нужный код в свое мобильное приложение. Видео презентация новых возможностей. Более детальные инструкции разработчикам. Особенно понравилась генерация JS кода для копирования в клиентское приложение, так как написание своего JavaScript'a для многих становится проблемой из-за быстрого привыкания к csharp-коду.

В целом можно сказать, что инфраструктура для разработки мобильных приложений - это уже третье магистральное направление развития облака Azure. К первым двум я отношу - эмуляция IaaS, чтобы хостинговые компании не чувствовали себя брошенными, а продолжили перепродавать услуги облака. Второе - ранее анонсированные Media Services - хранение, распространение мультимедийных файлов. Теперь и мобильная инфраструктура.

Метки:   Категории:microsoft


aspnet team ms2012

Вот так выглядит среднестатистический разработчик MS2012/asp.net 4.5

Метки:   Категории:microsoft


Azure Storage Explorer 5 - Highly functional tool

Примерно месяц назад одновременно с AzureSDK 1.7 вышла новая версия Azure Storage Explorer. Пятая версия, пока в альфа-превью. Но это пожалуй удобнейший инструмент для работы с азуровскими стораджами, тем более, что встроенные в MSVS обычно тормозит совместно с самой студией. Грандиозность этого обновления в том, что предыдушее обновление 4й версии было лишь в далеком 2010 году и многие считали этот проект заброшенным, что и позволило расподиться разным платным инструментам(типа CloudStorageManager), ссылки на которые давать не буду. Azure Storage Explorer 5 пока вроде бесплатен, как и был.

Метки: , ,   Категории:microsoft


Кто я?

Программист. Я слежу за блогосферой и знаю, как будет развиваться интернет. Когда у меня есть время я даже прилагаю для этого усилия. Подробнее

Последние комментарии

Не отображать

Topbot at FeedsBurner

Мои Твиты

Twitter июля 21, 06:27
Украина готовит на Евровидение песню с быками http://dlvr.it/QcGlCH

Twitter июля 21, 06:27
Суммарная капитализация криптовалют http://dlvr.it/QcGl8J

Twitter июля 21, 06:27
В Твиттере начался обвал фолловеров http://dlvr.it/QcGl9p

Twitter июля 20, 13:36
Разбор трилогии Матрицы (The Matrix Trilogy) http://dlvr.it/QcBwFH https://twitter.com/f1ashr/status/1020301316550086656/photo/1

Twitter июля 20, 01:04
Чем белые отличаются от черных? http://dlvr.it/Qc7sJx https://twitter.com/f1ashr/status/1020112062507233281/photo/1

Twitter июля 19, 23:57
Победа свободы над здравым смыслом http://dlvr.it/Qc7fVm https://twitter.com/f1ashr/status/1020095354983731201/photo/1

Twitter июля 19, 22:17
Роскомнадзор планирует заблокировать IP сайта t30p http://dlvr.it/Qc7JG9

Twitter июля 19, 20:39
коломенский кремль: альтернативная история 2 http://dlvr.it/Qc6v6H https://twitter.com/f1ashr/status/1020045378324914176/photo/1

Twitter июля 18, 22:44
Трансляции Одноклассников (http://Ok.ru/live) добавлены в t30p http://dlvr.it/Qc18JK https://twitter.com/f1ashr/status/1019714458350891009/photo/1

Twitter июля 18, 22:44
Теневой бизнес ГИБДД http://dlvr.it/Qc18HP https://twitter.com/f1ashr/status/1019714453678436352/photo/1

Twitter июля 18, 22:44
Непонятные конкурсы в Твиттере http://dlvr.it/Qc188b https://twitter.com/f1ashr/status/1019714448993337344/photo/1

Twitter июля 18, 22:44
Текущая ситуация с инстаграммом http://dlvr.it/Qc186r

Twitter июля 18, 22:12
Пользователи умудрились поломать гео-локацию в Инстаграм http://dlvr.it/Qc12Fq

Twitter июля 18, 22:12
Разбор сериала Рассказ служанки http://dlvr.it/Qc1228 https://twitter.com/f1ashr/status/1019706394503667714/photo/1

Twitter июля 18, 22:12
Разбор сериала "Мир Дикого Запада" http://dlvr.it/Qc120F

Twitter июля 18, 21:08
Google Lunar X Prize - за 10 лет никто не смог запуститься на Луну http://dlvr.it/Qc0myX https://twitter.com/f1ashr/status/1019690286862102528/photo/1

Twitter июля 18, 21:08
Китайский интернет http://dlvr.it/Qc0mtX https://twitter.com/f1ashr/status/1019690280860053504/photo/1

Twitter июля 18, 20:34
Левитирующие объекты в Японии http://dlvr.it/Qc0dvs

Twitter июля 14, 22:59
Диджеи будущего http://dlvr.it/QbZSKL

Twitter июля 14, 20:46
Китай: скрытая угроза http://dlvr.it/QbZ5XZ

Мой твиттер

Копирайт

Все мысли, высказанные в блоге, являются моим мнением и за это мнение меня никто не забанит! Кроме того, никто не имеет право копировать материалы блога без использования ctrl+C/V!

© Copyright 2008