定制“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  阅读(8123)  评论(24编辑  收藏  举报