Sync README files from source repository [skip ci]
This commit is contained in:
@@ -0,0 +1,218 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user