我的网站发布之后一段时间内正常运行,但过一段时间之后,访问量上来之后出现了502,再访问都是502,重启IIS应用程序池之后才能正常访问。但过了一会儿还会出现相同的问题。
看对应的Windows事件日志以及asp.net core应用程序日志
windows日志没看到什么异常,不过程序的日志里有很多访问第三方接口失败的信息
例如访问微信、阿里云市场、高德地图的第三方接口都失败了
@伊辛伊忆: 难道也是HttpClient惹的祸,HttpClient用的是static
吗?
@dudu: 对的,都是静态方法,这个问题之前遇到过吗?
@dudu: 方法是静态的,静态方法里new的HttpClient实例
public static async Task<string> GetAsync(string url, IEnumerable<KeyValuePair<string, string>> formDatas, IEnumerable<KeyValuePair<string, string>> headers, IEnumerable<KeyValuePair<string, string>> defaultHeaders = null) { formDatas.For(d => url += $"{(url.EndsWith('&') ? "" : "?")}{d.Key}={WebUtility.UrlEncode(d.Value ?? "")}&"); url = url.TrimEnd('&'); HttpClient client = new HttpClient(); defaultHeaders.For(h => client.DefaultRequestHeaders.Add(h.Key, h.Value)); var request = new HttpRequestMessage() { RequestUri = new Uri(url), Method = HttpMethod.Get }; headers.For(h => request.Headers.Add(h.Key, h.Value)); return (await (await client.SendAsync(request)).Content.ReadAsByteArrayAsync()).ToUTF8String(); }
@伊辛伊忆: 很多种原因可以造成502,比如:CPU高、内存高或者TCP连接过多
@伊辛伊忆: 问题应该出在HttpClient client = new HttpClient();
,要么用静态的,要么放在using
中,相关链接:https://q.cnblogs.com/q/106336/
@伊辛伊忆: 如果用的是 .net core 2.1 ,建议使用HttpClientFactory
,参考 HttpClientFactory in ASP.NET Core 2.1
@dudu: CPU、内存都是够的,我准备用“性能监视器”监测一下TCP连接数
@dudu: 好的 , 我这边研究一下 。
@dudu: 把HttpClient设置为全局只有一个可以吗?会不会高并发下出现排队现象?
@伊辛伊忆: 可以,如果不使用HttpClientFactory
,这是推荐做法,没有并发问题
@dudu: 好的,我发布一下,试一下
@dudu: 为什么全局情况下,静态的HttpClient不会出现排队?
@伊辛伊忆: 详见 .NET HttpClient的缺陷和文档错误让开发人员倍感沮丧
@dudu: 我把IIS的最大进程数从1改成8,问题解决了.....很奇怪
@伊辛伊忆: 只是缓解了问题
@dudu: 的确是HttpClient的问题,改成WebClient就没有这个问题了
@dudu:改成静态的还是会出现相同的问题,我在性能监视器里发现Current Connection一大于100,在访问就会502
@伊辛伊忆: 有没有在同步方法中调用异步方法?
@dudu:架构里很多Task算是吗?
@dudu:我们这个架构当时搭建时,用了很多Task,就是想让执行的快一点
@伊辛伊忆: 有没有 .Reslut
这样的调用?
@dudu:很多
@dudu:还有.wait()还有.waitall()
@伊辛伊忆: 应该就是这个引起的,详见 又踩.NET Core的坑:在同步方法中调用异步方法Wait时发生死锁(deadlock)
@dudu:好的,一会儿到公司看一看,昨晚解决这个熬到3点……
@伊辛伊忆: .Result
也是调用了 .Wait()
,如果调用 .Wait()
或 .Result
的地方所在的方法是同步的,就会带来这个问题,必须要把同步方法改为异步方法
@伊辛伊忆: 在 .net core 中,同步方法调用异步方法会造成整个线程池死锁
@dudu:您说的同步是指lock这一类的同步方法?
@伊辛伊忆: 不是,是返回类型不是 Task
的方法,异步方法通常都使用 asnyc/await
@dudu:那就是说,我需要把上层方法都改成返回类型是Task的,然后在最上层才能用.Result?
@伊辛伊忆: 不要用 .Result ,用 await
@dudu:我如果要获取结果不用Result的话,怎么获取呢?
@伊辛伊忆: 用async/await
@dudu: 用async/await的话,返回的是Task<int>,那我怎么从Task<int>获取int呢?不需要.Result吗?
@伊辛伊忆: await 方法名
的结果就是int类型的返回值
@dudu: 这样可以吗?
public IActionResult Async() { return Success(MyMethod()); } static async Task<int> MyMethod() { for (int i = 0; i < 5; i++) { Console.WriteLine("Async start:" + i.ToString() + ".."); await Task.Delay(1000); //模拟耗时操作 } return 0; }
@伊辛伊忆: Async()
也要改为 async
public async Task<IActionResult> Async()
{
return Success(await MyMethod());
}
@dudu:那就是只要方法里用到异步方法,整个管道都要改成异步的?
@伊辛伊忆: 是的,必须的,血的教训
@dudu: 我测一下
@dudu: 的确,按照您说的,我测试了一下
第一种情况,没有用async/await
模拟高并发:
using System; using System.Net.Http; using System.Threading; using System.Threading.Tasks; namespace ConsoleApp5 { class Program { static void Main(string[] args) { ThreadStart t = new ThreadStart(new Action(() => { Parallel.For(1, 500, (i) => { //code GetAsync(); }); })); Thread tx = new Thread(t); tx.IsBackground = true; tx.Start(); Console.ReadKey(); } public static async void GetAsync() { HttpClient client = new HttpClient(); var request = new HttpRequestMessage() { RequestUri = new Uri("http://localhost:50367/api/Test/Async"), Method = HttpMethod.Get }; var a= (await (await client.SendAsync(request)).Content.ReadAsByteArrayAsync()); Console.WriteLine(System.Text.Encoding.Default.GetString(a)); } } }
没有用async/await接口
public IActionResult Async() { return Success(MyMethod()); } //public IActionResult Async() //{ // Random rd = new Random(); // return Success(rd.Next(1, 10)); //} static async Task<int> MyMethod() { await Task.Delay(1000); //模拟耗时操作 Random rd = new Random(); return rd.Next(1, 10); }
过了一会儿线程飙升:
程序卡死,抛出异常:
第二种情况,使用async/await
public async Task<IActionResult> Async() { await MyMethod(); Random rd = new Random(); return Success(rd.Next(1, 10)); } static async Task<int> MyMethod() { await Task.Delay(1000); //模拟耗时操作 Random rd = new Random(); return rd.Next(1, 10); }
程序稳定,线程数没增:
@dudu: 准备开始review代码,不过为什么会出现这种情况呢?
@dudu: Task.WhenAll().Wait()还需要使用async/await吗?我用的时候提示我返回类型是void不能使用await
@伊辛伊忆: 在一定并发的情况下才会出现,很难模拟出来
@伊辛伊忆: await Task.WhenAll(clearCacheTasks);
@dudu: 提示无法等待void
@伊辛伊忆: .Wait()
去掉了吗?
@dudu: 好的 了解了 我去掉
@dudu: 我整个管道都是用await/async在本地vs2017里调试没错,发布到服务器之后有的接口是502,有的可以正常访问,这个遇到过吗
@伊辛伊忆: 502就是IIS将请求转发给Kestrel后,一直等待到超时时间也没收到响应,要么请求本身执行慢,要么是发生了死锁
@dudu: 这两天对于Task的理解更深了,您说的没错,确实Result和wait()会阻塞线程,占用IIS请求池的数量,造成了大量的请求排队,所以才会出现链接飙升,服务挂掉,用了异步接口之后,遇到await会立刻释放掉请求线程,避免了排队的情况,应该是这个原因吧,感谢!!!