dotnetcore / WebApiClient

A REST API library with better functionality, performance, and scalability than refit
https://webapiclient.github.io/
MIT License
2.06k stars 444 forks source link

webapiclientcore 并发问题 #266

Closed xuefuruanjian closed 2 months ago

xuefuruanjian commented 2 months ago

.net6中使用webapiclientcore 2.1.4,设置超时间为10s,使用阿里的性能测试工具进行压测,发现并发到50以后,就会出现超时异常。而使用httpclient对同一地址做对比测试,并没有发现此问题。

异常信息为:The request was canceled due to the configured HttpClient.Timeout of 10 seconds elapsing.; at WebApiClientCore.Implementations.DefaultApiActionInvoker`1.InvokeAsync(HttpClientContext context, Object[] arguments)

实测是超时的请求并没有把请求发出去,下游接口没有收到对应的请求信息。

以下是两个代码:

public interface IDeviceNetworkServiceApi1 : IHttpApi { [HttpPost("/deviceManager/action/device/intent")] Task<TResponse> DeviceControl([Header("transType")] int transType, [JsonContent] IoTHubServiceBaseModel model); }

public static class WebApiClientServiceCollectionExtensions { public static IServiceCollection UseWebApiClient(this IServiceCollection services, IConfiguration configuration) { services.AddHttpApi() .ConfigureHttpApi(options => options.HttpHost = new Uri(GlobalConstant.DeviceNetworkServiceApi)) .ConfigureHttpClient(httpClient => { httpClient.Timeout = TimeSpan.FromSeconds(10); }) .ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler { MaxConnectionsPerServer = 200 }); return services; } }

调用实现: try { IoTHubServiceBaseModel serviceRequestModel = new IoTHubServiceBaseModel(); serviceRequestModel.msgType = msgType; serviceRequestModel.did = uint.Parse(device.DeviceId); serviceRequestModel.hexStr = ByteUtility.ByteToHex(message.ToArray()); serviceRequestModel.gatewayDeviceId = device.GatewayDeviceId; var api = ServiceLocator.GetService(); var response = await api.DeviceControl(1, serviceRequestModel); } catch (Exception ex) { LogManager.Fatal($"error:{ex.Message};messageType:{msgType};StackTrace:{ex.StackTrace};"); }

以下为httpclient的实现----------------------------------------

var requestUri = $"{GlobalConstant.DeviceNetworkServiceApi}deviceManager/action/device/intent"; try { IoTHubServiceBaseModel serviceRequestModel = new IoTHubServiceBaseModel(); serviceRequestModel.msgType = msgType; serviceRequestModel.did = uint.Parse(device.DeviceId); serviceRequestModel.hexStr = ByteUtility.ByteToHex(message.ToArray()); serviceRequestModel.gatewayDeviceId = device.GatewayDeviceId;

                var request = new HttpRequestMessage(HttpMethod.Post, requestUri);

                request.Headers.Add("transType", "1");

                var jsonContent = JsonUtilities.ToJson(serviceRequestModel);
                request.Content = new StringContent(jsonContent, Encoding.UTF8, "application/json");

                // var handler = new HttpClientHandler
                // {
                //     MaxConnectionsPerServer = 200
                // };

                HttpClient _httpClient = new HttpClient()
                {
                    Timeout = TimeSpan.FromSeconds(10)
                };

                var response = await _httpClient.SendAsync(request);
                response.EnsureSuccessStatusCode();

                var responseContent = await response.Content.ReadAsStringAsync();
                var baseResponse = JsonUtilities.ToObject<TResponse<ResponseModel>>(responseContent);
            }
            catch (Exception ex)
            {
                LogManager.Fatal($"error:{ex.Message};messageType:{msgType};StackTrace:{ex.StackTrace};");
            }
xljiulang commented 2 months ago

貌似你在做http转发层(或者包装代理等),然后使用http测试工具来请求测试,上面两个实现的主要差异在于:

  1. webapiclientcore使用了HttpClientFactory,所以你的HttpClient代码要确保所有HttpClient实例共用同一个HttpClientHandler实例;
  2. 确保MaxConnectionsPerServer 参数要要设置为一样值,这是一个非常重要的参数
xuefuruanjian commented 2 months ago

改为使用HttpClientFactory复用httpclient来做对比测试,结果和webapicliectcore的性能一致。 那么,需要根据目标接口的响应情况,适当的提高MaxConnectionsPerServer的值来提高并发。

在program中配置HttpClientFactory // 配置 HttpClientFactory builder.Services.AddHttpClient("DeviceNetworkServiceClient", client => { client.Timeout = TimeSpan.FromSeconds(10); client.BaseAddress = new Uri(GlobalConstant.DeviceNetworkServiceApi); }) .ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler { MaxConnectionsPerServer = 200 });

调用 var requestUri = $"{GlobalConstant.DeviceNetworkServiceApi}deviceManager/action/device/intent"; try { IoTHubServiceBaseModel serviceRequestModel = new IoTHubServiceBaseModel(); serviceRequestModel.msgType = msgType; serviceRequestModel.did = uint.Parse(device.DeviceId); serviceRequestModel.hexStr = ByteUtility.ByteToHex(message.ToArray()); serviceRequestModel.gatewayDeviceId = device.GatewayDeviceId;

                var request = new HttpRequestMessage(HttpMethod.Post, requestUri);
                request.Headers.Add("transType", "1");

                var jsonContent = JsonUtilities.ToJson(serviceRequestModel);
                request.Content = new StringContent(jsonContent, Encoding.UTF8, "application/json");

                var _httpClientFactory = ServiceLocator.GetService<IHttpClientFactory>();
                var _httpClient = _httpClientFactory.CreateClient("DeviceNetworkServiceClient");

                var response = await _httpClient.SendAsync(request);
                response.EnsureSuccessStatusCode();

                var responseContent = await response.Content.ReadAsStringAsync();
                var baseResponse = JsonUtilities.ToObject<TResponse<ResponseModel>>(responseContent);
            }
            catch (Exception ex)
            {
                MiddleTier.Instance.LogManager.Fatal($"error:{ex.Message};Result:{ex.StackTrace};");
            }
xljiulang commented 2 months ago

改最大连接数多少值合适,取决于业务需要和硬件水平,目前看最需要解决的是目标上游服务器的响应性能

xuefuruanjian commented 2 months ago

谢谢,了解大致情况了,我们是旧版本WebApiClient.JIT升上来了,所以沿用了升级前的一些设置,旧版本上MaxConnectionsPerServer默认值是128,所以更改了这个值。 而新版本WebApiClientCore上并没有设置默认值,而是使用框架的设置,那么默认值是int.MaxValue,也可以不用再进行设置。