CDN for HTTPS на Azure

В этом году Azure совершил еще один небольшой прорыв, сделав возможность использования CDN для HTTPS соединений. Фактически это позволяет экономить на сертификате SSL, так как сертификаты для CDN генерятся автоматически и без дополнительной платы. Как это работает и какой сертификат будет подставлен, можно видеть на сайтах social.t30p.ru и light.t30p.ru, которые теперь должны быстрее открываться.

 


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


Насколько инновационен Микрософт

Есть у Microsoft облачный сервис Azure, которым я давно пользуюсь. И последние месяцы наблюдаю, как инновационно и умело разработчики вгоняются назад в прошлое к использованию текстовых файлов в виде хранилищ баз данных. Раньше был SQL и BlobStorage для хранения данных.

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

UPD: Иными словами получается, что плачу 1) за виртуальную машину, 2) за дисковое пространство, которое она занимает 3) за любой исходящий травик 4) и за любую операцию чтения-записи. Чудное разбиение одной услуги на 4 платных позиции.


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


Исправление производительности Azure SQL (DTU)

Не прошло и 5 лет, и выяснилось, что Azure SQL оптимизирован для работы только с параметризованными запросами. Так как для всех запросов, приходящих к базе, системой наворачивается сложная аналитика, которая и гробит производительность, если этот запрос каждый раз похож на новый. И чтобы все запросы были для системы анализа на одно лицо необходимо их представлять в параметризованном виде. И оказывается можно просто заставить SQL все запросы принимать в правильном виде простой командой

ALTER DATABASE [dbname] SET PARAMETERIZATION FORCED

Вот такая вот оптимизация на ровном месте. И DTU/CPU сразу меньше есть сгорается в пустую.


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


Рекомендованные индексы для Azure SQL

Одно из последних изменений в AzureSQL - рекомендованные индексы. Система анализирует запросы, которые выполняет база данных и предлагает индексы для их оптимизации. Как итог, все выглядит следующим образом - Микрософт придумал проблему в виде DTU, которого всегда не хватает и сам же эту проблему решает в автоматическом режиме. Система сама решает свои же проблемы. На скриншоте ниже видно, что автоматически добавленный индекс экономит мне 0,38 DTU пожирая дополнительное место. Но так как места для всех баз выделено под 250Гб, то места не жалко, можно добавлять сколько угодно индексов, если система будет считать, что она работает быстрее.


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


Разное, проблемное.

Итак значит, обо всем и ни о чем:

  1. Youtube отклонил заявку на монетизацию моего канала "we are unable to accept your application because your channel does not contain sufficient original content." То есть контент на их взгляд не достаточно уникальный.
  2. Apple не дает мне обновлять приложение t30p в AppStore ссылаясь на показ user-generated content, требуя, чтобы был модератор и там прочие требования, которые нужно еще заморочиться выполнить. Тем, кто мне пишет, что перестает работать приложение - простая пере-установка должна помогать.
  3. Microsoft с 20 июля переходит на msvs2015, к ней должны работать ключи полученные через BizSpark, так что проблем не будет.
  4. С сентября Azure закрывает Azure SQL Business, переходят на другую тарифную сетку. Требуется перевести базу данных. Проблема в том, что сколько я не пытался переносить ранее - всегда возникали проблемы с производительностью. Скоро придется перейти в принудительном порядке. 



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


Как запустить Wordpress в Azure

Казалось бы, самый простой сценарий по запуску блога Wordpress в облаке. Однако, даже тут есть подводный камень и ошибка в официальной инструкции, которая опубликована на сайтах Микрософта. В этой инструкции предлагается развернуть отдельно сервак с Apache+Php и отдельно Mysql DB. Проблема последнего в том, что Mysql не является базовой возможностью Azure и оно заводит базу данных предоставленную внешним сервисом с тарификацией по дополнительному тарифу. То есть шанс, что будут списывать бабки с карты втихую.

Правильное решение - это в том же окне поиска обзара диска для разворачивания, найти диск Wordpress от Bitnami. При этом база данных mysql будет поднята на той же машине, что и Apache/PHP. Что дешево (даже бесплатно) и работает без сбоев, вызванных передачей по сети.


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


Минусы Windows Azure и проблемы облачных технологий

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

  1. Главный минус, вы никуда уже не денетесь от облачных технологий. Виртуализация всюду и у вас нет выбора, рано или поздно придется переезжать в облака. В крайнем случае, это будут приватное облако на базе Windows Server 2012 R2 Datacenter.
     
  2. В России не строятся облачные дата центры международного уровня. Согласно западному империализму мы должны оставаться варварами. Обычно это объясняют малыми масштабами рынка и т.п. В итоге могут возникнуть трудности, если против нас применят санкции или связь с Европой оборвется.
  3. В облаках вы за все платите в полтора или два раза больше. Тем самым обеспечивается высокая доступность. Например вы не можете отказаться от дублирования ваших данных в Blob Storage (Locally
    Redundant).
  4. Вы платите за себя и за того парня. Прирост пользователей в облачные технологии замедлился. Поэтому, чтобы мощности не простаивали, активно продвигаются бесплатные тарифы. Такие как Bizspark, Ultimate MSDN. То есть полно людей, которые бесплатно пользуются тем, за что вы платите.
  5. Любой сбой теперь касается и вас. Несмотря на обещанную надежность, облака регулярно барахлят, как можно видеть в статусах. Примерно раз в месяц что-нить да сбоит. Даже если это линия связи с другим континентом, это тоже может вас затронуть. Отчасти сбои связаны с регулярным введением новых возможностей и удешевлением старых.
  6. За сбой в работе вам не заплатят. Был случай, когда сбой длился часов 6-8. На вопросы в тех.поддержку был получен ответ, что для компенсации вам необходимо предоставить платные результаты мониторинга ваших сервисов одной из глобальных компаний, которые сертифицированы Микрософт.
  7. Скрытые копейки. Чтобы формально быть дешевыми облака часто неявно прячут некоторые расходы. Так например, если запустить Virtual Machine, то также придется платить и за 120Гб логический диск порядка 4р в месяц за Гб. Также цены для физических и юридических лиц могут отличаться.
  8. Индусы. Так как в Индии построен эффективный полу-фашизм, то индусы работают строго по инструкции и отойти от правил для них не возможно в принципе. Поэтому-то они и выбраны в качестве международной технической поддержки для компании Микрософт. Часто переписка с ними ведется несколько месяцев в течении которых вам успеют ответить 3-4 индуса, два из которых успеют сходить в отпуск. Их фишкой является решение не тех вопросов, которых вы от них хотите, а ваших откладывание в долгий ящик.



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


Отключение логирования в Windows Azure

Есть такая проблема, как слишком много логов. Они по умолчанию включены в Azure и пишутся на диагностический диск. Если у вас роль маленькая, то диск этот размером в 15ГБ (+5ГБ сама система). К слову, есть такая фишка, что если заводить Extra Small Virtual Machine, а не Extra Small Web Role, то можно получить за теже 600 р в месяц диск на 120ГБ. Микрософт такое несоответствие объясняет тем, что на виртуальные машины обычно доставляют большие программы, вот и выделили места как у Small Instance.

Так вот, эти 15ГБ быстро исчерпываются, если у вас много обращений и автоматически не чистятся. Такое приводит к тому, что у роли вызывается процесс Reimage, что дает заметный перебой в работе. Чтобы в автоматическом деплое отключить логирование (конечно же для VM можно соединится по RDP и ручками настроить IIS как надо), достаточно подсоединить к проекту библиотеку Microsoft.Web.Administration, которая находится по пути C:\Windows\System32\inetsrv\Microsoft.Web.Administration.dll. А в фенкции старта самой роли прописать следующее:

Copy Source | Copy HTML
  1. var iisManager = new ServerManager();
  2. Configuration config = iisManager.GetApplicationHostConfiguration();
  3. ConfigurationSection httpLoggingSection = config.GetSection("system.webServer/httpLogging");
  4. httpLoggingSection["selectiveLogging"] = @"LogAll";
  5. httpLoggingSection["dontLog"] = true;
  6. iisManager.CommitChanges();

Метки:   Категории:Csharp | 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


Контекстный поиск с русской и английской морфологией на основе проектов с открытым исходным кодом и облачных сервисов 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 Storage Explorer 5 - Highly functional tool

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

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


256 worker roles @ windows azure

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


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


Azure Tables / Multi Role Counter

Пост содержит много кода и призван рассказать о реализации счетчика посещений в мульти-инстансовой мультипоточной среде, коей является любое веб приложение под Windows Azure. Впервые простая реализация счетчика была продемонстрирована в шоу channel9 и в целом он работал неправильно и решал лишь проблему мультипоточности при помощи Interlocked объекта.
Во-первых сразу определимся, что данные у нас по счетчику будут складироваться в Azure Table - по сути это следующее этап развития SQL баз данных, которые не требуют знания SQL, а позволяют описывать структуру объекта прямо в коде. Для работы с таблицами используется Entity Framework, для которого удобно использовать обертку SyncTimes примерно следующего вида:
Copy Source | Copy HTML
  1. /// <summary>
  2. /// Многопоточная работа с таблицами Азуре
  3. /// </summary>
  4. /// <typeparam name="T"></typeparam>
  5. public class SyncTimes<T> where T: TableServiceEntity,new()
  6. {
  7.     #region Azure Table's Row
  8.     public T _tc;
  9.     public T tc
  10.     {
  11.         get
  12.         {
  13.             if (_tc != null) return _tc;
  14.             _tc = Reload();
  15.             return _tc;
  16.         }
  17.         set { _tc = value; }
  18.     }
  19.     #endregion
  20.     public string roleid
  21.     {
  22.         get
  23.         {
  24.             return RoleEnvironment.IsAvailable ? RoleEnvironment.CurrentRoleInstance.Id : "0";
  25.         }
  26.     }
  27.     #region Работа через Таблицы
  28.     [ThreadStatic]
  29.     public static TableServiceContext _containerTable;
  30.     public TableServiceContext ContainerTable
  31.     {
  32.         get
  33.         {
  34.             if (_containerTable != null) return _containerTable;
  35.             lock (CurrentInstance)
  36.             {
  37.                 if (_containerTable != null) return _containerTable;
  38.                 CloudStorageAccount storageAccount = CloudStorageAccount.Parse(
  39.                     "DefaultEndpointsProtocol=http;AccountName=imagecontainer;AccountKey=");
  40.                 CloudTableClient tableClient = storageAccount.CreateCloudTableClient();
  41.                 tableClient.CreateTableIfNotExist(typeof(T).Name);
  42.                 // Retrieve a reference to a container  
  43.                 _containerTable = tableClient.GetDataServiceContext();
  44.                 _containerTable.IgnoreResourceNotFoundException = true;
  45.                 _containerTable.MergeOption = MergeOption.AppendOnly;
  46.             }
  47.             return _containerTable;
  48.         }
  49.         set { _containerTable = value; }
  50.     }
  51.     #endregion
  52.     public static SyncTimes<T> CurrentInstance
  53.     {
  54.         get
  55.         {
  56.             var table = HttpContext.Current.Items[typeof(T).Name] as SyncTimes<T>;
  57.             if (table != null) { return table; }
  58.             table = new SyncTimes<T>();
  59.             HttpContext.Current.Items[typeof(T).Name] = table;
  60.             return table;
  61.         }
  62.     }
  63.     /// <summary>
  64.     /// Создание запросного объекта
  65.     /// </summary>
  66.     /// <returns></returns>
  67.     public IQueryable<T> CreateQuery()
  68.     {
  69.         return ContainerTable.CreateQuery<T>(typeof(T).Name);
  70.     }
  71.     /// <summary>
  72.     /// загрузка-обновление таблицы
  73.     /// </summary>
  74.     /// <returns></returns>
  75.     private T Reload()
  76.     {
  77.         try
  78.         {
  79.             string blogid = Blog.CurrentInstance.Id.ToString();
  80.                 ReadOnlyCollection<EntityDescriptor> oEntities = ContainerTable.Entities;
  81.                 if (oEntities.Count >  0)
  82.                 {
  83.                     EntityDescriptor ed = oEntities.FirstOrDefault(
  84.                         p => p.Entity.GetType() == typeof (T) &&
  85.                              ((T) p.Entity).RowKey == roleid &&
  86.                              ((T) p.Entity).PartitionKey == blogid);
  87.                     if (ed != null)
  88.                     {
  89.                         ContainerTable.Detach(ed.Entity); //удалим из трекинга(!)
  90.                     }
  91.                 }
  92.                 T te = (from e in CreateQuery() where e.RowKey == roleid && e.PartitionKey == blogid select e).FirstOrDefault();
  93.                 if (te == null)
  94.                 {
  95.                     te = new T{PartitionKey = blogid,RowKey = roleid};
  96.                     ContainerTable.AddObject(typeof(T).Name, te);
  97.                     //ContainerTable.SaveChangesWithRetries();
  98.                 }
  99.                 return te;
  100.         }catch(Exception e1)
  101.         {
  102.             Trace.WriteLine(e1);
  103.         }
  104.         return null;
  105.     }
  106.     /// <summary>
  107.     /// Делаем обновление в БД.
  108.     /// </summary>
  109.     /// <remarks>передаем нул в качестве объекта, если хотим просто вызвать обновление</remarks>
  110.     /// <returns>TRUE if no error</returns>
  111.     public Boolean Update(object te, bool applychanges = true)
  112.     {
  113.             try
  114.             {
  115.                 if (te != null)
  116.                 {
  117.                         try
  118.                         {
  119.                             ContainerTable.UpdateObject(te);
  120.                         }
  121.                         catch(ArgumentException e0)//not tracking
  122.                         {
  123.                             ContainerTable.Detach(te);
  124.                             ContainerTable.AttachTo(typeof(T).Name, te, "*");
  125.                             ContainerTable.UpdateObject(te);
  126.                         }
  127.                         catch (DataServiceRequestException e1)//tracking by different uri
  128.                         {
  129.                             ContainerTable.Detach(te);
  130.                             ContainerTable.AttachTo(typeof(T).Name, te, "*");
  131.                             ContainerTable.UpdateObject(te);
  132.                         }
  133.                 }
  134.                 if (applychanges &&
  135.                     ContainerTable.Entities.Count(p => p.State != EntityStates.Unchanged && p.State!=EntityStates.Detached) >  0)
  136.                 {
  137.                     ContainerTable.SaveChangesWithRetries();
  138.                 }
  139.             }
  140.             catch (DataServiceRequestException ex)
  141.             {
  142.                 //значит объект не соответствует тому, что хранится в БД и нужно его обновить
  143.                 if (typeof(T) != typeof(HitsCounter))
  144.                 {
  145.                     Utils.Log(ex);
  146.                 }
  147.                 return false;
  148.             }
  149.         return true;
  150.     }
  151.     /// <summary>
  152.     /// Делаем обновление в БД.
  153.     /// </summary>
  154.     /// <returns></returns>
  155.     public T[] GetAllInstances(string blogid)
  156.     {
  157.         lock (ContainerTable)
  158.         {
  159.             try
  160.             {
  161.                 return (from e in CreateQuery() where e.PartitionKey == blogid && e.RowKey != roleid select e).ToArray();
  162.             }
  163.             catch (Exception e1)
  164.             {
  165.                 Trace.WriteLine(e1);
  166.             }
  167.         }
  168.         return null;
  169.     }
  170. }

Важно понимать, что [ThreadStatic] объект необъодимо занулять в начале хендлера каждого запроса, чтобы гарантировать, что внутри каждого потока у нас уникальный DataContext постоянный на протяжении всей обработки запроса. Теперь перейдем к реализации класса учета обращений. В моем случае мультипоточность учитывается классом ConcurrentDictionary и сама запись в Таблицу вызывается только при достижении 10 обработанных запросов в инстансе. Это не идеально, и имеет некоторую долю ошибок, однако реализация точного учета обращений в мульти-истансовой веб-роле привело бы к блокировки потока на 100мс, чего мы избегаем. Отмечу, что для разных инстонсов роли пишутся разные объекты в таблице, а при итоговом выводе, когда нужно получить сумму - они суммируются.
Copy Source | Copy HTML
  1. /// <summary>
  2. /// Summary description for TopPosts
  3. /// </summary>
  4. /// <remarks></remarks>
  5. [Extension("Counts and displays the number of viewers for a post", "3.0", "")]
  6. public class TopPosts
  7. {
  8.     /// <summary>
  9.     /// Initializes a new instance of the <see cref="TopPosts"/> class.
  10.     /// </summary>
  11.     /// <remarks></remarks>
  12.     static TopPosts()
  13.     {
  14.         Post.Serving += new EventHandler<ServingEventArgs>(OnPostServing);
  15.     }
  16.     /// <summary>
  17.     /// Called when [post serving].
  18.     /// </summary>
  19.     /// <param name="sender">The sender.</param>
  20.     /// <param name="e">The <see cref="BlogEngine.Core.ServingEventArgs"/> instance containing the event data.</param>
  21.     /// <remarks></remarks>
  22.     private static void OnPostServing(object sender, ServingEventArgs e)
  23.     {
  24.         NameValueCollection headers = HttpContext.Current.Request.Headers;
  25.         if (headers["X-moz"] == "prefetch")
  26.         {
  27.             return;
  28.         }
  29.         IPublishable ipub = (IPublishable)sender;
  30.         try
  31.         {
  32.             // Check For Single Post View, When viewing Specific Post, basically through post.aspx)
  33.             if (e.Location == ServingLocation.SinglePost)
  34.             {
  35.                 int viewCount;
  36.                 // Fetch out total views of current viewing post.
  37.                 viewCount = IncrementPostViewCount(ipub.Id.ToString());
  38.                 // Override the body of the post (temporary) to display total views
  39.                 if (Security.IsAuthenticated)
  40.                 {
  41.                     e.Body = String.Format(Resources.labels.totalViews + ": {0}<br/>", viewCount) + e.Body;
  42.                 }
  43.             }
  44.             else if (e.Location == ServingLocation.PostList && Security.IsAuthenticated)
  45.             {
  46.                 int viewCount = GetCountForPost(ipub.Id.ToString());
  47.                 // Override the body of the post (temporary) to display total views
  48.                 e.Body = String.Format(Resources.labels.totalViews + ": {0}<br/>", viewCount) + e.Body;
  49.             }
  50.         }
  51.         catch (Exception)
  52.         {
  53.         }
  54.     }
  55.     /// <summary>
  56.     /// Gets the popular posts.
  57.     /// </summary>
  58.     /// <param name="numberOfTopPosts">The number of top posts.</param>
  59.     /// <returns></returns>
  60.     /// <remarks></remarks>
  61.     public static List<KeyValuePair<string, int>> GetPopularPosts(int numberOfTopPosts)
  62.     {
  63.         List<KeyValuePair<string,int>> list = new List<KeyValuePair<string, int>>( 0);
  64.         IQueryable<HitsCounter> q = SyncTimes<HitsCounter>.CurrentInstance.CreateQuery();
  65.         HitsCounter[] posts =
  66.             (from e in q where e.PartitionKey == Blog.CurrentInstance.Id.ToString() select e).OrderByDescending(
  67.                 z => z.hits).Take(numberOfTopPosts).ToArray();
  68.         list.AddRange(posts.Select(hitsCounter => new KeyValuePair<string, int>(hitsCounter.RowKey, hitsCounter.hits)));
  69.         return list;
  70.     }
  71.     /// <summary>
  72.     /// Gets total view count for a certain post
  73.     /// </summary>
  74.     /// <param name="postId">the post id</param>
  75.     /// <returns>total live views count</returns>
  76.     /// <remarks></remarks>
  77.     public static int GetCountForPost(string postId)
  78.     {
  79.         return (from e in SyncTimes<HitsCounter>.CurrentInstance.CreateQuery() where e.RowKey == postId select e).
  80.             Sum(hitsCounter => hitsCounter.hits);
  81.     }
  82.     /// <summary>
  83.     /// Количество просомтров для обновления
  84.     /// </summary>
  85.     public static ConcurrentDictionary<string, int> oViews = new ConcurrentDictionary<string, int>();
  86.     /// <summary>
  87.     /// Increment view count of a post
  88.     /// </summary>
  89.     /// <param name="postId">Id of the post</param>
  90.     /// <returns>the post's view count</returns>
  91.     /// <remarks></remarks>
  92.     public static int IncrementPostViewCount(string postId)
  93.     {
  94.         int viewCount =  0;
  95.         try
  96.         {
  97.             string PartitionKey = RoleEnvironment.IsAvailable ? RoleEnvironment.CurrentRoleInstance.Id : "0";
  98.             PartitionKey += "_"+Blog.CurrentInstance.Id.ToString();
  99.             //если есть несохраненные изменения, то это для нас!
  100.             HitsCounter h = null;
  101.             IQueryable<HitsCounter> q = SyncTimes<HitsCounter>.CurrentInstance.CreateQuery();
  102.             HitsCounter[] hArray = (from e in q where e.RowKey == postId select e).ToArray();
  103.             foreach (HitsCounter hitsCounter in hArray)
  104.             {
  105.                 viewCount += hitsCounter.hits;
  106.                 if (hitsCounter.PartitionKey == PartitionKey)
  107.                 {
  108.                     h = hitsCounter;
  109.                 }
  110.             }
  111.             viewCount++;
  112.             bool success = false;
  113.             oViews.AddOrUpdate(postId, 1, (key, oldValue) => oldValue+1);
  114.             if (oViews[postId] > 10)
  115.             {
  116.                 if (h == null)
  117.                 {
  118.                     h = new HitsCounter(PartitionKey, postId)
  119.                             {
  120.                                 hits = oViews[postId]
  121.                             };
  122.                     SyncTimes<HitsCounter>.CurrentInstance.ContainerTable.AddObject("HitsCounter", h);
  123.                     success = SyncTimes<HitsCounter>.CurrentInstance.Update(null);
  124.                 }
  125.                 else
  126.                 {
  127.                     h.hits += oViews[postId];
  128.                     success = SyncTimes<HitsCounter>.CurrentInstance.Update(h);
  129.                     //успешное обновление
  130.                 }
  131.                 if (!success)
  132.                 {
  133.                     Utils.Log("Postid=" + postId + "; views=" + oViews[postId]);
  134.                 }
  135.                 else
  136.                 {
  137.                     int viewCount2;
  138.                     oViews.TryRemove(postId, out viewCount2);
  139.                 }
  140.             }
  141.         }
  142.         catch (DataServiceRequestException e1)
  143.         {
  144.             Utils.Log(e1);
  145.         }
  146.         catch(Exception e2)
  147.         {
  148.             Utils.Log(e2);
  149.         }
  150.         return viewCount;
  151.     }
  152. }

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

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


CloudSearch

Сегодня Амазон объявил о выходе нового продукта - поисковик в облаке. То есть все то, о чем можно мечтать при обработке больших объемов данных. Есть интерфейс стоп слов , управление полями индексирования и т.п. Пугает лишь то, что за это придется платить и так как сервис новый, то стоит ожидать крупных сбоев. По цене все предлагается за от $0.12 per hour, что есть 1 инстанс и меньше 100у.е. в месяц. На базе cloudsearch уже проиндексировали Википедию, правда только англоязычную. Русский язык там не работает. В любом случае это мощная заявка на то, чтобы подвинуть другие корпоративные решения по контекстному поиску, который в перспективе станет мощнее яндексовского.

Что касается Azure, то для него пока нет решения из коробки в виде отдельного поискового сервиса, однако есть библиотека Lucene, которая позволяет индексировать и искать документы в облаке, хранить индекс будет в storageblob, но настраивать придется все самому. Но и тут нет морфологии. Если бы морфология поставлялась из коробки, а не приходилось бы прикручивать Lemmatizer, то это решение бы победило.

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


Windows Azure - на заметку

Раскопал блог евангелиста WindowsAzure в России. Есть несколько интересных ссылок про CDN. В частности геокарта расположения микрософтных CDN.

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


Manual DNS Azure Solution with Power Shell

О проблеме настройки DNS для Windows Azure уже упоминалось, но оказалось все сложнее. Российский регистратор webnames не смог выставить DNS для доменов на нужные, ссылаясь на ошибки в SOA записях, даже техподдержка пока не помогла. Так что домены по 90р за штуку до добра не доведут. Поэтому я озадачился поднятием и настройкой своего пула DNS серверов и видимо с этим столкнется любой разработчик, который захочет работать с Windows Azure без использования CDN (Content Delivery Network).

Алгоритм настройки получился следующим:
1) Заказываем на windows хостинге, например Агаве, пару VPS серверов IIS Web за 500р в месяц, включаем на каждом из них DNS Server, на втором настраиваем репликацию первого.Заводим нужные dns-зоны. Записи A указывающую на IP адрес myserver.cloudapp.net и CNAME на сайт и приставкой www. Также настраиваем SOA, чтобы TTL был секунд 20, а не час, для более быстрого обновления.
2) На primary DNS сервере ставим PowerShell из стандартного пакета. Для этого открываем раздел Features в списке возможных установок на сервер. Далее пишем Windows Service который бы раз в 5 минут опрашивает ваши сайты на предмет не изменился ли у них IP адрес. Если изменился, то выполняем определенный PowerShell-скрипт, код которого приведен ниже.
3) Первый PS-скрипт вызывается, когда служба только запускается и ей нужно определить список всех доменных зон на сервере, делается это так:
Copy Source | Copy HTML
  1. Get-WmiObject -ComputerName localhost -Namespace 'root\MicrosoftDNS' -Class MicrosoftDNS_AType |Sort-Object -unique containername|Select containername,ipaddress

4) И второй PS-скрипт, когда обнаружены домены, для которых нужно поменять А записи, вызывается такой вот скрипт. Вызов выглядит как "./update-DNSAddress -forward localhost -reverse localhost". Где в файле dnsaddresslist.csv со списком нужных изменений IP адресов присутствует шапка "Name,Address" и далее по паре домен-айпи на каждой строчке разделенных запятой.

Это базовые знания, чтобы решить проблемы динамических адресов в Azure, но при желании могу добавить ваш домен на свои обновляемые DNS server'a.

bonus: Пример Csharp функции по запуску PowerShell скрипта, сохраненного на диск файлом, с параметром и получением результата работы скрипта.

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


Кто я?

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

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

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

Topbot at FeedsBurner

Мои Твиты

Twitter августа 20, 10:35
Где искать инопланетян или как зародилась жизнь http://dlvr.it/PgKlxl https://twitter.com/f1ashr/status/899218224268451842/photo/1

Twitter августа 16, 13:24
Меч короля Артура установили в Киеве http://dlvr.it/PfStTW https://twitter.com/f1ashr/status/897811213312114688/photo/1

Twitter августа 13, 10:22
The Samsung Galaxy S8 Giveaway | TNW Deals https://deals.thenextweb.com/giveaways/samsung-galaxy-s8?gid=1541200 via @TNWDeals

Twitter августа 9, 12:32
По игре престолов небольшая схема http://dlvr.it/Pcs9BD https://twitter.com/f1ashr/status/895261436863168512/photo/1

Twitter июля 28, 13:19
Мир Дикого Запада 2-й сезон http://dlvr.it/PZ8Pb8 https://twitter.com/f1ashr/status/890924587562565632/photo/1

Twitter июля 28, 12:54
Рутин тутин Путин http://dlvr.it/PZ8860

Twitter июля 28, 12:21
Лайков ноль - один репост - Голос Омерики http://dlvr.it/PZ7r42 https://twitter.com/f1ashr/status/890909982220099584/photo/1

Twitter июля 28, 11:47
Что смотрят на Youtube http://dlvr.it/PZ7WN6

Twitter июля 28, 10:47
Идеальное решение для Курильских островов http://dlvr.it/PZ6z7t

Twitter июля 28, 10:47
Instagram закрывает API http://dlvr.it/PZ6z6g

Twitter июля 21, 09:02
Новости Америки - смерть Честера и посол Лесник http://dlvr.it/PXJD32

Twitter июля 19, 21:14
☠Разбор сериала Американские боги (1й сезон) http://dlvr.it/PWtYJ2 https://twitter.com/f1ashr/status/887782628924968960/photo/1

Twitter июля 16, 17:11
Давай же оторвемся, как в 1986 http://dlvr.it/PW1Dfj

Twitter июля 12, 21:05
читать спутник и погром бесплатно http://dlvr.it/PV81Dt

Twitter июля 10, 20:44
Разбор сериала Кремниевая долина (Silicon Valley) http://dlvr.it/PTdGJk https://twitter.com/f1ashr/status/884513586814439424/photo/1

Twitter июля 9, 15:34
Heineken 0 0 http://dlvr.it/PTLNKG

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

Twitter июля 2, 10:35
Добродел обновился http://dlvr.it/PRdlv1 https://twitter.com/f1ashr/status/881461233085366272/photo/1

Twitter июня 28, 09:48
Самоуправляемые ездящие картошки http://dlvr.it/PQfwZc

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

Мой твиттер

Копирайт

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

© Copyright 2008