219 lines
5.9 KiB
Markdown
219 lines
5.9 KiB
Markdown
# 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:**
|
|
```bash
|
|
# 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:**
|
|
```bash
|
|
# 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:**
|
|
```bash
|
|
# 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ý
|
|
```bash
|
|
# 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
|
|
```bash
|
|
# 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
|
|
```csharp
|
|
// Ở 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)
|
|
```csharp
|
|
// 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)
|
|
```csharp
|
|
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 `OperationCanceledException` riê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:
|
|
1. ✅ Deploy code mới với logging
|
|
2. ✅ Monitor logs để xác định pattern
|
|
3. ✅ Xác định thời gian xử lý trung bình
|
|
|
|
### Nếu thấy nhiều cancellations:
|
|
|
|
#### Option A: Tăng client timeout
|
|
```csharp
|
|
// 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
|
|
1. Profile `RequestResource()` method
|
|
2. Add caching cho hot data
|
|
3. Optimize database queries
|
|
4. Consider async logging (fire-and-forget)
|
|
|
|
#### Option C: Implement timeout gracefully
|
|
```csharp
|
|
// 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
|