HttpClient在高并发场景下的优化实战

在项目中使用HttpClient可能是很普遍,尤其在当下微服务大火形势下,如果服务之间是http调用就少不了跟http客户端找交道.由于项目用户规模不同以及应用场景不同,很多时候可能不需要特别处理也.然而在一些并发场景下必须要做一些优化.

项目是快递公司的快件轨迹查询项目,目前平均每小时调用量千万级别.轨迹查询以Oracle为主要数据源,Mongodb为备用,当Oracle不可用时,数据源切换到Mongodb.今年菜鸟团队加入后,主要数据迁移到了阿里云上,以Hbase为主要存储.其中Hbase数据查询服务由数据解析组以Http方式提供.原有Mongodb弃用,云上数据源变为主数据源,Oracle作为备用.当数据源切换以后,主要的调用方式也就变成了http方式.在第10月初第一轮双11压测试跑上,qps不达标.当然这个问题很好定位,因为十一假之间轨迹域组内已经进行过试跑,当时查的是oracle.十一假期回来后,只有这一处明显的改动,很容易定位到问题出现在调用上.但具体是云上Hbase慢,还是网络传输问题(Hbase是阿里云上的服务,轨迹查询项目部署在IDC机房).通过云服务,解析组和网络运维的配合,确定问题出现在应用程序上.在Http服务调用处打日志记录,发现以下问题:

img

可以看到每隔一段时间,就会有不少请求的耗时明显比其它的要高.

导致这种情况可能可能是HttpClient反复创建销毁造成引起来销,首先凭经验可能是对HttpClient进行了Dispose操作(Using(HttpClient client=new HttpClient){...})

如果你装了一些第三方插件,当你写的HttpClient没有被Using包围的时候会给出重构建议,建议加上Using或者手动dispose.然而实际中是否要dispose还要视情况而定,对于一般项目大家的感觉可能是不加也没有大问题,加了也还ok.但是实际项目中,一般不建议反复重新创建这个对象,关于HttpClient是否需要Dispose请看这里

在对这个问题的答案里,提问者指出微软的一些示例也是没有使用using的.下面一个比较热的回答指出HttpClient生命周期应该和应用程序生命周期一致,只要应用程序需要Http请求,就不应用把它Dispose掉.下面的一个仍然相对比较热的回答指出一般地,带有Dispose方法的对象都应当被dispose掉,但是HttpClient是一个例外.

当然以上只是结合自己的经验对一个大家都可能比较困惑的问题给出一个建议,实际上对于一般的项目,用还是不用Dispose都不会造成很大问题.本文中上面提到的问题跟HttpClient也没有关系,因为程序中使用的Http客户端是基于HttpWebRequest封装的.

问题排查及优化

经过查询相关资料以及同行的经验分享(这篇文章给了很大启发)

查看代码,request.KeepAlive = true;查询,这个属性其实是设置一个'Keep-alive'请求header,当时同事封装Http客户端的场景现场无从得知,然而对于本文中提到的场景,由于每次请求的都是同一个接口,因此保持持续连接显然能够减少反复创建tcp连接的开销.因此注释掉这一行再发布测试,以上问题便不复出现了!

当然实际中做的优化绝不仅仅是这一点,如果仅仅是这样,一句话就能够说完了,大家都记住以后就这样做就Ok了.实际上还参考了不少大家在实际项目中的经验或者坑.下面把整个HttpClient代码贴出来,下面再对关键部分进行说明.

public static string Request(string requestUrl, string requestData, out bool isSuccess, string contentType = "application/x-www-form-urlencoded;charset=utf8") { string apiResult = ""; isSuccess = false; if (string.IsNullOrEmpty(requestData)) { return apiResult; } HttpWebRequest request = null; HttpWebResponse response = null; try { byte[] buffer = Encoding.UTF8.GetBytes(requestData); request = WebRequest.Create($"{requestUrl}") as HttpWebRequest; request.ContentType = "application/json"; request.Method = "POST"; request.ContentLength = buffer.Length; request.Timeout =200; request.ReadWriteTimeout = Const.HttpClientReadWriteTimeout request.ServicePoint.Expect100Continue = false; request.ServicePoint.UseNagleAlgorithm = false; request.ServicePoint.ConnectionLimit = 2000 request.AllowWriteStreamBuffering = false; request.Proxy = null; using (var stream = request.GetRequestStream()) { stream.Write(buffer, 0, buffer.Length); } using (response = (HttpWebResponse)request.GetResponse()) { string encoding = response.ContentEncoding; using (var stream = response.GetResponseStream()) { if (string.IsNullOrEmpty(encoding) || encoding.Length < 1) { encoding = "UTF-8"; //默认编码 } if (stream != null) { using (StreamReader reader = new StreamReader(stream, Encoding.GetEncoding(encoding))) { apiResult = reader.ReadToEnd(); //byte[] bty = stream.ReadBytes(); //apiResult = Encoding.UTF8.GetString(bty); } } else { throw new Exception("响应流为null!"); } } } isSuccess = true; } catch (Exception err) { isSuccess = false; LogUtilities.WriteException(err); } finally { response?.Close(); request?.Abort(); } return apiResult; }

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zygsyd.html