5.9 KiB
5.9 KiB
gRPC "Client Reset Request Stream" Debug Guide
❌ Lỗi
System.IO.IOException: The client reset the request stream.
🔍 Nguyên nhân
Lỗi này xảy ra khi gRPC client đóng/hủy connection trước khi server xử lý xong.
Các nguyên nhân phổ biến:
1. Client Timeout ⏱️ (Phổ biến nhất)
- Client có timeout ngắn hơn thời gian server xử lý
- Ví dụ: Client timeout 5s, nhưng server cần 10s để xử lý
Cách kiểm tra:
# Xem logs với thời gian xử lý
grep "GetProxy completed" logs.txt
grep "GetProxy cancelled" logs.txt
Giải pháp:
- Tăng timeout ở client
- Tối ưu performance ở server
2. Server xử lý chậm 🐌
Các nguyên nhân server chậm:
- Database query chậm
- Redis cache chậm
- ClickHouse logging chậm (đã fix)
- Logic phức tạp trong
RequestResource()
Cách kiểm tra:
# Xem thời gian xử lý trung bình
grep "GetProxy completed" logs.txt | grep -oP '\d+ms' | sort -n
Giải pháp:
- Profile code để tìm bottleneck
- Add index vào database
- Optimize queries
- Cache kết quả
3. Network Issues 🌐
- Connection bị drop
- Load balancer timeout
- Firewall rules
Cách kiểm tra:
# Kiểm tra network errors
kubectl logs <pod-name> | grep -i "connection\|network\|timeout"
4. Client Implementation Issues 🐛
- Client không đợi response đủ lâu
- Client có retry logic và cancel request cũ
- Client crash/restart
📊 Monitoring với logs mới
Sau khi deploy code mới, bạn sẽ thấy logs như:
Successful request:
[abc12345] GetProxy started - AccessToken: token123
[abc12345] GetProxy completed in 150ms - ProxyId: 456
Cancelled request:
[xyz67890] GetProxy started - AccessToken: token456
[xyz67890] GetProxy cancelled by client after 5000ms - AccessToken: token456
Failed request:
[def11111] GetProxy started - AccessToken: token789
[def11111] GetProxy failed after 2000ms - AccessToken: token789
🔧 Cách debug
Bước 1: Xác định thời gian xử lý
# Xem requests bị cancel và thời gian của chúng
kubectl logs <pod-name> | grep "cancelled by client"
# Nếu thấy nhiều requests bị cancel sau ~5s, 10s, 30s
# => Client timeout ở các mốc đó
Bước 2: So sánh với requests thành công
# Xem thời gian xử lý của requests thành công
kubectl logs <pod-name> | grep "completed in" | grep -oP '\d+ms'
# Nếu hầu hết < 1000ms nhưng có vài cái > 5000ms
# => Có vấn đề performance không ổn định
Bước 3: Kiểm tra client timeout
// Ở phía client, kiểm tra timeout config
var channel = GrpcChannel.ForAddress("http://server:5002", new GrpcChannelOptions
{
HttpHandler = new SocketsHttpHandler
{
PooledConnectionIdleTimeout = Timeout.InfiniteTimeSpan,
KeepAlivePingDelay = TimeSpan.FromSeconds(60),
KeepAlivePingTimeout = TimeSpan.FromSeconds(30),
EnableMultipleHttp2Connections = true
}
});
// Hoặc set deadline cho mỗi call
var response = await client.GetProxyAsync(request, deadline: DateTime.UtcNow.AddSeconds(30));
✅ Giải pháp đã áp dụng
1. Cấu hình Kestrel HTTP/2 (Program.cs)
// HTTP/2 limits để tránh connection faults
options.Limits.Http2.MaxStreamsPerConnection = 100;
options.Limits.Http2.InitialConnectionWindowSize = 131072; // 128KB
options.Limits.Http2.InitialStreamWindowSize = 98304; // 96KB
// Tăng timeout
options.Limits.KeepAliveTimeout = TimeSpan.FromMinutes(2);
options.Limits.RequestHeadersTimeout = TimeSpan.FromSeconds(30);
// Disable rate limits cho gRPC
options.Limits.MinRequestBodyDataRate = null;
options.Limits.MinResponseDataRate = null;
Lợi ích:
- Tránh "HTTP/2 connection faulted" errors
- Cho phép nhiều concurrent streams hơn
- Tăng window size để handle large messages
- Không bị timeout khi network chậm
2. Cấu hình gRPC (Startup.cs)
services.AddGrpc(options =>
{
options.MaxReceiveMessageSize = 10 * 1024 * 1024; // 10MB
options.MaxSendMessageSize = 10 * 1024 * 1024; // 10MB
options.EnableDetailedErrors = true;
});
Lợi ích:
- Tránh lỗi message size limit
- Detailed errors giúp debug dễ hơn
3. Thêm logging chi tiết (ProxyService.cs)
- Track thời gian xử lý từng request với Stopwatch
- Log khi client cancel request
- Request ID để trace từng request
- Catch
OperationCanceledExceptionriêng
4. Error handling
- Log warning thay vì error cho client cancellation
- Không crash service khi client disconnect
- Continue service khi ClickHouse quá tải
📈 Recommended Actions
Ngay lập tức:
- ✅ Deploy code mới với logging
- ✅ Monitor logs để xác định pattern
- ✅ Xác định thời gian xử lý trung bình
Nếu thấy nhiều cancellations:
Option A: Tăng client timeout
// Client code
var deadline = DateTime.UtcNow.AddSeconds(30); // Tăng từ 5s lên 30s
var response = await client.GetProxyAsync(request, deadline: deadline);
Option B: Tối ưu server performance
- Profile
RequestResource()method - Add caching cho hot data
- Optimize database queries
- Consider async logging (fire-and-forget)
Option C: Implement timeout gracefully
// Server code - return cached/fallback data nếu quá lâu
using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
cts.CancelAfter(TimeSpan.FromSeconds(5));
try
{
return await GetFreshData(cts.Token);
}
catch (OperationCanceledException)
{
// Return cached data as fallback
return await GetCachedData();
}
🎯 Expected Metrics
Sau khi fix:
- ✅ Cancellation rate < 1%
- ✅ P95 response time < 1000ms
- ✅ P99 response time < 3000ms
- ✅ No service crashes due to client disconnects