走進異步世界:博客程序的異步化改造以及發布后的不理想情況


最近,我們干了一件“驚天動地”的事——對改了十年、代碼混亂無比、WebForms與MVC混血、ADO.NET與Entity Framework混合的博客程序,用.NET 4.5的async/await特性進行了異步化改造。主要的異步化改造已於昨天完成,並在昨天晚上發布了異步化改造后的博客程序。

觸動我們進行這次異步化改造的是ASP.NET官網上一篇文章(Using Asynchronous Methods in ASP.NET 4.5)中的一段話:

 A web application  using synchronous methods to service high latency calls where the thread pool grows to the .NET 4.5 default maximum  of 5, 000 threads would consume approximately 5 GB more memory than an application able the service the same requests using asynchronous methods and only 50 threads.

在高延遲操作場景下,同步方式需要5000個線程才能完成的工作,采用異步方式只需50個線程!以一敵百,如此的高效,怎能不讓人心動。

而itworld一篇文章中的一句話更是火上澆油,讓我們下定決心實現異步化。

I’ve seen load tests show 300% improvement in response times and concurrent connections boost almost 8x over the synchronous counterparts.

此次異步化改造一共有6個部分,其中三個部分的改造最輕松,它們是MVC,EF,WCF;而另外三個則最艱苦,它們是WebForms,ADO.NET,EnyimMemcached(memcached .NET客戶端)。

下面分別簡單介紹一下這6個部分的改造:

1. MVC的異步化改造

無比輕松,只要把ActionResult改為async Task<AstionResult>:

public async Task<ActionResult> SiteHome(int? pageIndex)
{
    //...
}

2. Entity Framework的異步化

也很輕松,查詢時只需使用異步LINQ:

public async Task<int> GetAsync()
{
    return await Entities
        .Where(...)
        .Select(...)
        .CountAsync();
}

保存時只需SaveChangesAsync():

async Task IUnitOfWork.CommitAsync()
{
    await base.SaveChangesAsync();
}

3. WCF客戶端的異步化

照樣輕松,只要選擇“Generate task-based operations”重新生成WCF客戶端代理:

WCF Generate task-based operations

4. WebForms的異步化

a) 所有實現異步的.aspx都要加上async="true"標記。

<%@ Page Async="true" Language="c#"%>

b) 原來獲取數據進行綁定的代碼要放在異步方法中,並通過Page.RegisterAsyncTask進行注冊。

protected override void OnLoad(EventArgs e)
{
    base.OnLoad(e);
    this.Page.RegisterAsyncTask(new System.Web.UI.PageAsyncTask(GetPostsByMonth));
}

c) 原來靜態綁定的用戶控件不得不改為動態加載。

同步時代:

<%@ Register TagPrefix="uc1" TagName="EntryList" Src="EntryList.ascx" %>
<uc1:EntryList id="Days" DescriptionOnly = "true" runat="server"></uc1:EntryList>

異步時代:

public  class ArchiveMonth : UserControl
{
    protected override void OnLoad(EventArgs e)
    {
        base.OnLoad(e);
        this.Page.RegisterAsyncTask(new System.Web.UI.PageAsyncTask(GetPostsByMonth));
    }

    private async Task GetPostsByMonth()
    {        
        var DaysControl = LoadControl("EntryList.ascx") as EntryList;
        if (DaysControl != null)
        {
            DaysControl.EntryListItems = await postSevice.GetEntriesByMonth(CurrentBlog, dt, PostType.BlogPost);
            DaysControl.DescriptionOnly = true;
            Controls.Add(DaysControl);
        }
    }
}

d) 原來在OnPreRender中的處理代碼(依賴異步任務的處理結果)需要移至Render,因為ASP.NET是在OnPreRender階段檢查所有注冊的異步任務並進行異步執行。

【WebFoms中的異步原理】

如果在.aspx中設置了async="true",ASP.NET線程在處理針對這個頁面的請求時,會在PreRender階段查找是否有注冊的異步任務(async task);如果有,該線程會將當前請求放回隊列中,然后抽身去處理其它請求。當異步任務完成時,該請求會被線程池中的某個線程撿起,直到執行完成。(參考自Async Pages part 2: How to use asynchrony in your Pages)。

5. ADO.NET的異步化

所有進行異步化的數據庫操作都需要用類似下面的ADO.NET代碼進行改造

using(var conn = new SqlConnection(connectionString))
{
    using(var command = conn.CreateCommand())
    {
        command.CommandType = CommandType.StoredProcedure;
        command.CommandText = "...";
        command.Parameters.AddWithValue("...", ...);
        await conn.OpenAsync();
        using (IDataReader reader = await command.ExecuteReaderAsync())
        {
            //...
        }
    }
}

6. EnyimMemcached的異步化

也就是Socket的異步化,參考msdn博客中的博文Awaiting Socket Operations,修改了EnyimMemcached,實現了Memcached客戶端的異步化,修改后的代碼已發布至github(https://github.com/cnblogs/EnyimMemcached)。

public async Task<IGetOperationResult<T>> GetAsync<T>(string key)
{
    //...            
    var commandResult = await node.ExecuteAsync(command);
    //...
}

【發布后的不理想情況】

1. CPU出現抖動

異步化改造后的博客程序發布后,在阿里雲雲服務器上CPU出現抖動,后來發展為瘋狂抖動。

CPU抖動1

CPU抖動2

最后放棄使用異步化的EnyimMemcached,改回原來同步的EnyimMemcached,CPU抖動情況得到了改善(后來發現異步化后的EnyimMemcached存在內存泄漏問題)。

a) 訪問低峰時的CPU抖動情況

訪問低峰時的CPU抖動情況

b)訪問高峰時的CPU抖動情況

訪問高峰時的CPU抖動情況

2. w3wp進程消耗的線程與內存更多

這個地方的表現讓人大跌眼鏡,原以為線程與內存的消耗會明顯降低,實際卻不但不降反而上升。

【更新1】

我們在負載均衡中加了另外一台雲服務器,不理想情況竟然沒出現。

后來,我們將原先2台表現不理想的服務器中的w3wp進程重啟后,不理想情況也消失了。昨天我們發布時只是更新了dll,並沒有對w3wp進程進行回收。

【更新2】

重啟w3wp進程之后,還是會出現CPU抖動的情況,但目前觀測下來對響應速度未造成影響。我們猜測CPU抖動可能與並行處理有關。

CPU依然抖動

【更新3】

解決進展:

1. 發現一個異步方法中調用了System.Web.HttpContext.Current,去掉了這個調用。

2. 增加ConfigureAwait(false)的使用。

【參考資料】

Best Practices in Asynchronous Programming

Using Asynchronous Methods in ASP.NET 4.5

Async Pages part 2: How to use asynchrony in your Pages

How to create Asynchronous device Page in ASP.NET 4.5

Why you should use async tasks in .NET 4.5 and Entity Framework 6?

Awaiting Socket Operations


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM