定制“Server Too Busy”错误信息

     当你访问博客园,出现“Server Error in '/' Application.Runtime Error.”的错误时,你知道这个错误的背后是什么吗? 你也许会想,博客园怎么不设计个定制错误页面?这样的错误页面让访问者不知所措,只能抱怨“服务器出问题了”。当出现这个问题时,我急啊!  真想站到互联网上,拿着大喇叭对大家喊,我刚更新了程序或者修改了web.config的设置,asp.net在进行首次编译,在编译的同时还要处理大量的请求,忙不过来,只能拒绝请求,实际的错误信息是"Server Too Busy",错误来自HttRuntime的RejectRequestInternal方法。你也许会说用customErrors页面处理一下啊!可是HttRuntime已经拒绝了这个请求,重定向到defaultRedirect定制错误页面,还是被拒绝,结果就出现“Server Error in '/' Application.Runtime Error.”错误。这个问题困扰我很长时间,当更新程序(或者修改web.config的设置、应用程序池回收、IIS重启)时,就会出现这个问题,尤其是访问高峰期,要几分种才能恢复正常,郁闷! 要是在这时显示一个友好的错误显示页面,那该多好啊!今天晚上更新程序时,又遇到这个问题。我再次下决心要解决这个问题。
     要解决问题,首先要分析出为什么会出现问题。既然是HttRuntime抛出的异常,那就从HttRuntime下手。怎么下手呢?用强大的Reflector工具,微软这点做的不错,很多.NET类库都可以通过Reflector工具查看源代码。通过分析HttRuntime的源代码,我找到了问题的原因,这里我简单地描述了一下:

     当请求被发送到ASP.NET Worker Process时,先是由HttpRuntime的ProcessRequest方法处理,在ProcessRequest方法中,先从请求队列(RequestQueue)中获取有效的请求(GetRequestToExecute),如果有有效的请求,就调用HttpRuntime.ProcessRequestNow进行进一步的处理。而"Server Too Busy"的异常就是在GetRequestToExecute中产生的,GetRequestToExecute方法代码如下:

internal HttpWorkerRequest GetRequestToExecute(HttpWorkerRequest wr)
{
      
int num1;
      
int num2;
      ThreadPool.GetAvailableThreads(
out num1, out num2);
      
int num3 = (num2 > num1) ? num1 : num2;
      
if ((num3 < this._minExternFreeThreads) || (this._count != 0))
      
{
            
bool flag1 = RequestQueue.IsLocal(wr);
            
if ((flag1 && (num3 >= this._minLocalFreeThreads)) && (this._count == 0))
            
{
                  
return wr;
            }

            
if (this._count >= this._queueLimit)
            
{
                  HttpRuntime.RejectRequestNow(wr);
                  
return null;
            }

            
this.QueueRequest(wr, flag1);
            
if (num3 >= this._minExternFreeThreads)
            
{
                  wr 
= this.DequeueRequest(false);
                  
return wr;
            }

            
if (num3 >= this._minLocalFreeThreads)
            
{
                  wr 
= this.DequeueRequest(true);
                  
return wr;
            }

            wr 
= null;
            
this.ScheduleMoreWorkIfNeeded();
      }

      
return wr;
}

     我们分析一下上面的代码:程序先从管理线程中获取有效线程的数目(num3)。如是num3大于minFreeThreads(该值对应于machine.config或者web.config中HttpRuntime段的minFreeThreads属性,即使执行新请求所需要的最小空闲线程数)并且请求队列中没有请求,则直接处理当前请求;如果num3小于minFreeThreads或者当前请求队列不为空("Server Too Busy"的请求就是这种情况), 从上面的代码中我们会发现当请求队列(RequestQueue)中请求数大于
_queueLimit(该值对应于machine.config或者web.config中HttpRuntime段的appRequestQueueLimit属性)时,HttpRumtime就会拒绝当前请求(RejectRequestNow),RejectRequestNow中直接调用RejectRequestInternal(HttpWorkerRequest wr), 在RejectRequestInternal中抛出了"Server_too_busy", 抛出异常后,立即捕获异常,你说怪吧,为什么不直接处理?为什么要在抛出异常后又捕获?有点多此一举?可能设计者还有其他考虑吧,比如为了代码更好的重用。

try
      
{
            
throw new HttpException(0x1f7, HttpRuntime.FormatResourceString("Server_too_busy"));
      }

      
catch (Exception exception1)
      
{
            context1.Response.InitResponseWriter();
            
this.FinishRequest(wr, context1, exception1);
            
return;
      }

      接着就是异常的处理,向客户端浏览器显示异常信息,调用FinishRequest, 在Finish中调用context.Response.ReportRuntimeError(e, true)显示异常信息,ReportRuntimeError会根据web.config中的CustomErrors设置决定是否重定向到defaultRedirect。
     当你设置CustomErrors的defaultRedirect来定制处理异常信息时,如果遇到"Server_too_busy"就麻烦了,重定向到错误处理页面后,又被HttpRuntime拒绝,拒绝后又被重定向到defaultRedirect页面,HttpRuntime《———》HttpRuntime.....,似乎进入了一种恶性循环。原来问题有这么严重,写文章之前我还没想到,写到这我才发现。这样不停的来回,CPU岂不累死,当CPU累的不行时,就随便抛出一个其他异常:),也就是“Server Error in '/' Application.Runtime Error.”,这个异常我没找到在哪抛出的。难道在首次编译时,CPU占用很高与这个也有关系。我更新博客园服务器上的程序时,要几分钟才能恢复正常,而这时CPU基本是满负荷工作,难道也是这个问题引起,我想明天在访问高峰期测试一下就能得到证实。这似乎是设计者的一个疏忽,正确的做法应该是对于"Server_too_busy"异常,不管用户是否设置了defaultRedirect, 都不应该去重定向到defaultRedirect,而是直接向客户端发送异常信息,这是一个很特殊的情况,设计者在设计时可能没考虑到。
     在写文章之前,我并没有发觉这个问题如此严重。但我想到了一个另类的解决方法,既然发生"Server_too_busy"时,HttpRuntime不会处理任何请求,那该Web应用程序中再怎么处理也是无济于事的, 我们从其他地方找突破口,发生"Server_too_busy"异常时,重定向到defaultRedirect会带来严重的问题,但却可以利用这一点把问题化解为解决方法,这个解决方法就是:将defaultRedirect设置为另外一个网站的地址,既然这个网站拒绝处理请求,那我把请求转给另外一个网站,总可以吧,由另外一个网站来显示友好的错误信息,这样不仅解决了定制“Server Too Busy”错误信息的问题,而且可以减少对主网站的请求,让主网站有更多的时间处理当前的任务。比如:博客园现在就建立了一个sorry.cnblogs.com的站点,将defaultRedirect设置为这个站点。这个方法另类吧! 
     有了这个方法,我们以后就不怕"Server_too_busy"了,可以轻松更新程序、修改web.config, 只要将defaultRedirect的页面设计得让大家觉得等几分钟也不难受就行了。比如:放一段美妙的音乐、笑话、搞笑图片、***,当然过份的网站也许会放广告。

posted @ 2005-10-19 02:02 dudu 阅读(4337) 评论(16)  编辑 收藏 网摘 所属分类: 网站性能优化

  回复  引用  查看    
#1楼 2005-10-19 07:36 | zputee      
“.NET类库都可以通过Reflector工具查看源代码”,好方法,多谢
  回复  引用  查看    
#2楼 2005-10-19 09:04 | Vincent Trent      
照这样分析,如果 redirect 的页面是普通html的话,估计就没问题了
  回复  引用  查看    
#3楼 [楼主]2005-10-19 09:25 | dudu      
@Vincent Trent
但博客园建立了*.*到asp.net的映射, html也是由asp.net处理的, 所以redirect到普通html也是不行的。
  回复  引用  查看    
#4楼 [楼主]2005-10-19 12:21 | dudu      
用“Server Too Busy”定制错误信息页面取代原来的“Server Error in '/' Application.Runtime Error.”, 感觉好多了!呵呵!
  回复  引用  查看    
#5楼 2005-10-19 17:14 | 工业酒精      
我记得博客园的程序里面是这样写的

在globl.aspx中间,定义了
protected void Application_Error(Object sender, EventArgs e)
{
Server.Transfer("error.aspx", false);
}


然后error.aspx页面获取错误信息,再显示出来。

并不是通过web.config中的defaultRedirect来转的,这样不会有问题嘛?

  回复  引用  查看    
#6楼 [楼主]2005-10-19 19:46 | dudu      
对于“Server Too Busy”错误, Application_Error没有机会执行。
文中提到HttpRuntime抛出异常后立即捕获异常并处理, 处理时会找defaultRedirect, 如果defaultRedirect没有设置, HttpRuntime就自己处理异常。
  回复  引用  查看    
#7楼 [楼主]2005-10-19 19:54 | dudu      
今天发觉解决这个问题后, 网站性能得到了提高。
前几天, 当网站Current Connetions超过800时, 网站访问速度变慢, 很多请求被拒绝。
今天, Current Connetions达到1000时, 网站运行还能比较正常。
  回复  引用  查看    
#8楼 2005-10-19 20:07 | 周奔驰      
哪里可以看到Current Connetions
  回复  引用  查看    
#9楼 [楼主]2005-10-19 21:33 | dudu      
@周奔驰
在性能监视器>Web Service。
  回复  引用  查看    
#10楼 2005-10-19 22:02 | 工业酒精      
好的,明天挂到项目上试试看。。。

不过刚部署了负载均衡,不知道能不能看到效果
  回复  引用  查看    
#11楼 2005-10-20 11:39 | 风之翼      
问个问题:
可以通过判断错误代码来重定向这个异常吗?
<error statusCode="500"
redirect="InternalError.htm"/>

这个时候,有没有错误代码返回的?
  回复  引用  查看    
#12楼 2005-10-21 09:25 | 难得一蠢      
我觉得既然数据库和服务器在一起,也可以对数据库进行一些优化,例如,对于超大数据的表进行分割,就好像CSDN前端时间的优化就是很明显的例子,将以前的数据导出..博客园的访问速度慢除了物理带宽的限制外,大数据的查询也应该是一个问题所在..

另外,对于一些比较固定的数据,或者更新较少的数据,可以使用XML,减缓数据库的压力.减少数据库的处理量来达到一些优化..
  回复  引用    
#13楼 2005-10-21 09:44 | raozr [未注册用户]
我的处理一般都在基类中重写,
protected override void OnError(EventArgs e)
{
Exception LastError;
LastError = Server.GetLastError();

if(LastError != null)
{
if(LastError.GetBaseException() is System.OutOfMemoryException)
{
Response.Redirect("Themes\\SystemPage.htm",true);
}
else
{
base.OnError(e);
Response.Redirect("ErrorPagePublic.aspx",true);
}
}
}

一般错误可在ErrorPagePublic.aspx中处理, 如回发邮件之类的.

  回复  引用  查看    
#14楼 [楼主]2005-10-21 10:19 | dudu      
@风之翼
你无法捕获这个异常。

@难得一蠢
谢谢你的建议!

  回复  引用    
#15楼 2006-07-07 13:44 | nunu [未注册用户]
听好
  回复  引用    
#16楼 2007-09-04 10:41 | program [未注册用户]
果然,高手.....




标题  
姓名  
主页
Email (博主才能看到) 
验证码 *  看不清,换一张 [登录][注册]
内容(请不要发表任何与政治相关的内容)  
  登录  使用高级评论  新用户注册  返回页首  恢复上次提交      
该文被作者在 2005-10-19 20:14 编辑过
Google站内搜索

China-pub 计算机图书网上专卖店!6.5万品种 2-8折!
近千种 9-95 新二手计算图书火热销售中!
开发者征途系统新作:《设计模式——基于C#的工程化实现及扩展》

相关文章:

相关链接:

历史上的今天:
2004-10-19 [博客园公告]Url地址支持htm