记一次.NET HttpHandler接口性能优化过程

第1章 简介

本文介绍基于.NET Framework4.0环境的一次接口优化全过程:当接口接收来自同一客户端(同一Session会话)的多个异步请求时,处理并发问题的优化;案例以笔者近期改进项目上处理的接口性能问题为例。
接口以实现HTTPHandler的方式接收客户端请求。主函数实现了IHttpHandler和IRequiresSessionState接口。下面开始详细的问题发现和解决步骤。

第2章 发现问题

2.1 JMeter压力测试现象
 

2.2 服务器运行资源情况

2.2.1 应用服务器

2.2.2 数据库服务器

第3章 问题分析

3.1 现象分析

在上一章的问题现象中可以看到:
Jmeter的聚合报告中:响应平均值到了50毫秒,这显示是不可接受的。
再往后看,最小值在738毫秒,在1秒以内,属于正常的调用。但是最大值高到离谱:100毫秒;
吞吐量:几乎是1秒钟只能处理一个请求。
以上现象,很容易让人怀疑是在排队处理,先到的请求,先处理完,后面的请求一直在等待。既然有所怀疑,那我们就想办法论证一下。

第4章 问题确认

4.1 证明怀疑的点

在接口用stopwatch计时并生成日志,证明我们的怀疑,代码片段如下:

public class EatsunMobileNewHandler : IHttpHandler, IRequiresSessionState  
{  
    public void ProcessRequest(HttpContext context)  
    {  
        //......  
  
        /************************计时************************/  
        Stopwatch sw = new Stopwatch();  
        int key = 0;  
        if (bDebug)  
        {  
            Random rd = new Random();  
            key = rd.Next();  
            sw.Start();  
        }  
        /****************************************************/  
          
        /************************业务************************/  
        if (bDebug)  
        {  
            BaseFunc.DebugLog(string.Format("开始业务逻辑之前的耗时,key:{0},action:{1},time:{2}", key, action, sw.ElapsedMilliseconds));  
            sw.Restart();  
        }  
        /****************************************************/  
        //......  
        /************************业务************************/  
        if (bDebug)  
        {  
            BaseFunc.DebugLog(string.Format("业务逻辑完成的耗时,key:{0},action:{1},time:{2}", key, action, sw.ElapsedMilliseconds));  
            sw.Stop();  
        }  
        /****************************************************/  
          
        //......  
    }  
}  

再次用jmeter发起请求,查看日志结果如下:

可以看到,相同的key为一组请求,所有的请求都是成对出现,下一次请求开始都是在上一次结束之后。这就论证了我们怀疑的点:接口只能串行处理来自同一客户端的请求,造成了性能问题。我们需要将其改成并行处理的方式。

第5章 调查步骤与修改

5.1 思考

对于web处理程序,对请求进行串行处理是没有道理的,即使是同一个客户端来的请求,也可能是并发性质的,现今用户操作界面无论是PC还是手机,界面越来越丰富,同一个界面在同一时间向服务端发起请求是很常见的,所以这个问题不符合常理,既然不符合常理,就必定有解决办法。我们从实现的IHttpHandler接口开始调查。

5.2 调查

相对于IHttpHandler,微软官方还提供了一个异步的接口IHttpAsyncHandler,尝试使用IHttpAsyncHandler解决此问题:实现IHttpAsyncHandler接口,主类继承HttpAsyncHandler并实现BeginRqeuest抽象方法,实现异步,代码如下:

public abstract class HttpAsyncHandler : IHttpAsyncHandler, IAsyncResult  
{  
    private HttpContext _context;  
   
    public bool IsReusable  
    {  
        get { return true; }  
    }  
      
    public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData)  
    {  
        ThreadPool.QueueUserWorkItem(new WaitCallback(DoRequest), this);  
        return this;  
    }  
  
    public void EndProcessRequest(IAsyncResult result)  
    {  
  
    }  
  
    public abstract void BeginRqeuest(HttpContext context);  
  
    private static void DoRequest(object state)  
    {  
        HttpAsyncHandler handler = (HttpAsyncHandler)state;  
        handler.BeginProcess(handler._context);  
    }  
    //...... 
}  


修改完后,实际测试,并不能达到效果,因为IHttpAsyncHandler只是在接口内部实现了逻辑的异步。来自同一个客户端的并发请求任然不能并行处理。

继续调查后发现:IRequiresSessionState这个session相关接口的一些信息:实现了IRequiresSessionState接口的类,为了保证Session的正确性,.net内部机制会保证同一个会话期间,第一次请求执行完之前,不会执行第二次请求。而相应的还有一个接口IReadOnlySessionState,这个接口因为Session是只读方式的使用,.net不对同一个会话进行控制,不管多少此请求,在服务器可承受范围内,就会同时进行处理。

5.3 修改

在检查完代码之后,尝试将IRequiresSessionState接口更换为IReadOnlySessionState,代码如下:

public class EatsunMobileNewHandler : IHttpHandler, IReadOnlySessionState  
{  
    public void ProcessRequest(HttpContext context)  
    {  
        //......  
     }
}

修改后,测试正常,同一个客户端请求可以同时进入主函数:ProcessRequest。

第6章 测试验证

 
(注,笔者这里压力测试的线程数较多,均值耗时在5秒左右,在此环境下,相比于修改前提升了。)

应用服务器

数据库服务器
 
可以看到应用服务器CPU资源已经被充分利用起来了,数据库服务器CPU耗用也有所提高。
再一次论证了我们修改达到了预期。

第7章 总结

  • 对于实现IRequiresSessionState接口的web处理程序,要特别注意Session的使用,对于同一个客户端发起的请求(同一会话Session),IRequiresSessionState会限制只有一个请求执行,下一个请求必须等待上一个请求执行完毕,才能开始处理。可以根据自己的业务情况,选择IReadOnlySessionState接口,或去除IRequiresSessionState(如果方法内部不需要操作session)。
  • 现今很多接口已经使用token取代session,但session仍然有一些厂商在使用,而需要同时读写session的操作大部分是在登录和注销过程,普通的业务通常只需要读取session;所以笔者最后建议大家开发过程中:登录和注销的web处理程序用IRequiresSessionState去实现。而普通业务的web处理程序采用IReadOnlySessionState去实现。

 

至此,同一客户端并发请求问题已经解决,但是目前的方案仍然不是最优,还需要将IRequiresSessionStateIReadOnlySessionState相关业务拆开,并实现IHttpAsyncHandler异步的方式,才是最佳效果,笔者将在下一篇文章中进行完善。《记一次.NET HttpHandler接口性能优化过程(续)》

 

猜你喜欢

转载自blog.csdn.net/dzh284616172/article/details/108348552