CDN for HTTPS на Azure

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

 


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


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

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


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


Azure Storage Explorer 5 - Highly functional tool

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

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


Кто я?

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

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

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

Topbot at FeedsBurner