走进异步世界:博客程序的异步化改造以及发布后的不理想情况 – 博客园团队
最近,我们干了一件“惊天动地”的事——对改了十年、代码混乱无比、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>:
{
//…
}
2. Entity Framework的异步化
也很轻松,查询时只需使用异步LINQ:
{
return await Entities
.Where(…)
.Select(…)
.CountAsync();
}
保存时只需SaveChangesAsync():
{
await base.SaveChangesAsync();
}
3. WCF客户端的异步化
照样轻松,只要选择“Generate task-based operations”重新生成WCF客户端代理:
4. WebForms的异步化
a) 所有实现异步的.aspx都要加上async=”true”标记。
b) 原来获取数据进行绑定的代码要放在异步方法中,并通过Page.RegisterAsyncTask进行注册。
{
base.OnLoad(e);
this.Page.RegisterAsyncTask(new System.Web.UI.PageAsyncTask(GetPostsByMonth));
}
c) 原来静态绑定的用户控件不得不改为动态加载。
同步时代:
<uc1:EntryList id=”Days” DescriptionOnly = “true” runat=”server”></uc1:EntryList>
异步时代:
{
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 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)。
{
//…
var commandResult = await node.ExecuteAsync(command);
//…
}
注:Memcached客户端是否真的需要异步化,有待商榷。
【发布后的不理想情况】
1. CPU出现抖动
异步化改造后的博客程序发布后,在阿里云云服务器上CPU出现抖动,后来发展为疯狂抖动。
最后放弃使用异步化的EnyimMemcached,改回原来同步的EnyimMemcached,CPU抖动情况得到了改善。
a) 访问低峰时的CPU抖动情况
b)访问高峰时的CPU抖动情况
2. w3wp进程消耗的线程与内存更多
这个地方的表现让人大跌眼镜,原以为线程与内存的消耗会明显降低,实际却不但不降反而上升。
【更新】
我们在负载均衡中加了另外一台云服务器,不理想情况竟然没出现。
后来,我们将原先2台表现不理想的服务器中的w3wp进程重启后,不理想情况也消失了。太奇怪了!昨天晚上我们重启了无数次w3wp进程也无济于事。
【参考资料】
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?
本文链接:走进异步世界:博客程序的异步化改造以及发布后的不理想情况,转载请注明。