起因
因为公司业务中有对第三方接口进行定时查询的需求(通过调用对方的API来同步订单车辆GPS安装状态) 前两天有运营说门店那边的GPS已经安装好了 但是系统里查到的还是安装中的状态 奇了个大怪 不得不排查下到底是代码中出了鬼 还是第三方服务有问题
问题排查
几经折腾 发现自己的代码逻辑是自洽的 之后开始怀疑是不是第三方接口稳定性不太过关 后来用ApiFox进行接口测试 不到20次得有快4次请求超时甚至有getaddrinfo ENOTFOUND这样的DNS问题(有时候属于弱网) 这下基本笃定是网络问题 框架是 .Net Framework 4.5 使用 HttpClient 经过查阅资料 可以在HttpClient构建的时候传入一个处理器给自己的请求增加中间件达到重试效果(这里没有直接使用Polly 毕竟不想让框架依赖太臃肿)
Code
public class TimeoutHandler : DelegatingHandler
{
private int timeOut;
private int maxRetry;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="max_count">最多重试次数</param>
/// <param name="timeOutMilliseconds">超时时间(毫秒)</param>
public TimeoutHandler(int max_count = 3, int timeOutMilliseconds = 5000)
{
InnerHandler = new HttpClientHandler();
timeOut = timeOutMilliseconds;
maxRetry = max_count;
}
protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
HttpResponseMessage response = default;
for (int i = 1; i <= maxRetry + 1; i++)
{
var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
cts.CancelAfter(timeOut);
try
{
response = await base.SendAsync(request, cts.Token);
if (response.IsSuccessStatusCode)
{
return response;
}
}
catch (Exception ex)
{
//请求超时
if (ex is TaskCanceledException)
{
Log.Write($"接口请求超时 {request.RequestUri} : " + ex.ToString(), "Http接口异常");
if (i > maxRetry)
{
return new HttpResponseMessage(HttpStatusCode.RequestTimeout)
{
Content = new StringContent("{\"code\":-1,\"data\":\"\",\"msg\":\"接口请求超时\"}", Encoding.UTF8, "text/json")
};
}
Log.Write($"接口第{i}次重新请求 {request.RequestUri}", "Http接口异常");
}
else
{
Log.Write($"接口请求异常 {request.RequestUri} :" + ex.ToString(), "Http接口异常");
return new HttpResponseMessage(HttpStatusCode.InternalServerError)
{
Content = new StringContent("{\"code\":-1,\"data\":\"\",\"msg\":\"接口请求异常\"}", Encoding.UTF8, "text/json")
};
}
}
}
return response;
}
}
代码量不大 主要的工作内容还是在构造函数上以及重写SendAsync方法(实际上还是用到InnerHandler里的SendAsync) 整个代码中的重试功能内核是靠CancellationTokenSource生成的Token实现的
Log这块用的是CYQ.Data 这里可以替换成SeriLog或者别的之类的
评论区