mirror of
https://github.com/Gouryella/drip.git
synced 2026-05-04 23:52:18 +00:00
perf(client): Optimize client performance and introduce a data frame processing worker pool
- Add runtime performance optimization configurations to main.go, including setting GOMAXPROCS, adjusting GC frequency, and memory limits. - Implement a worker pool-based data frame processing mechanism in connector.go to improve processing capabilities under high concurrency. - Adjust frame writer configuration to improve batch write efficiency and enable adaptive refresh strategy. - Add callback handling support for write errors to enhance connection stability. refactor(server): Introduce an adaptive buffer pool to optimize memory usage - Add adaptive_buffer_pool.go to implement large and small buffer reuse, reducing memory allocation overhead. - Apply buffer pool management for large/medium temporary buffers in proxy handlers and TCP connections. - Change the HTTP response writer to a cached bufio.Writer to improve I/O performance. - Optimize HTTP request reading logic and response sending process. build(docker): Update mount paths and remove unused named volumes - Modify the data directory mount method in docker-compose.release.yml. ./data:/app/data - Remove the unnecessary drip-data named volume definition test(script): Add performance testing and profiling scripts - Add profile-test.sh script for automating stress testing and performance data collection - Supports collecting pprof data such as CPU, stack traces, and coroutines and generating analysis reports
This commit is contained in:
@@ -43,6 +43,10 @@ type Connector struct {
|
||||
handlerWg sync.WaitGroup // Tracks active data frame handlers
|
||||
closed bool
|
||||
closedMu sync.RWMutex
|
||||
|
||||
// Worker pool for handling data frames
|
||||
dataFrameQueue chan *protocol.Frame
|
||||
workerCount int
|
||||
}
|
||||
|
||||
// ConnectorConfig holds connector configuration
|
||||
@@ -71,16 +75,21 @@ func NewConnector(cfg *ConnectorConfig, logger *zap.Logger) *Connector {
|
||||
localHost = "127.0.0.1"
|
||||
}
|
||||
|
||||
numCPU := pool.NumCPU()
|
||||
workerCount := max(numCPU+numCPU/2, 4)
|
||||
|
||||
return &Connector{
|
||||
serverAddr: cfg.ServerAddr,
|
||||
tlsConfig: tlsConfig,
|
||||
token: cfg.Token,
|
||||
tunnelType: cfg.TunnelType,
|
||||
localHost: localHost,
|
||||
localPort: cfg.LocalPort,
|
||||
subdomain: cfg.Subdomain,
|
||||
logger: logger,
|
||||
stopCh: make(chan struct{}),
|
||||
serverAddr: cfg.ServerAddr,
|
||||
tlsConfig: tlsConfig,
|
||||
token: cfg.Token,
|
||||
tunnelType: cfg.TunnelType,
|
||||
localHost: localHost,
|
||||
localPort: cfg.LocalPort,
|
||||
subdomain: cfg.Subdomain,
|
||||
logger: logger,
|
||||
stopCh: make(chan struct{}),
|
||||
dataFrameQueue: make(chan *protocol.Frame, workerCount*100),
|
||||
workerCount: workerCount,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,6 +144,11 @@ func (c *Connector) Connect() error {
|
||||
|
||||
c.frameWriter.EnableHeartbeat(constants.HeartbeatInterval, c.createHeartbeatFrame)
|
||||
|
||||
for i := 0; i < c.workerCount; i++ {
|
||||
c.handlerWg.Add(1)
|
||||
go c.dataFrameWorker(i)
|
||||
}
|
||||
|
||||
go c.frameHandler.WarmupConnectionPool(3)
|
||||
go c.handleFrames()
|
||||
|
||||
@@ -200,6 +214,29 @@ func (c *Connector) register() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Connector) dataFrameWorker(workerID int) {
|
||||
defer c.handlerWg.Done()
|
||||
|
||||
for {
|
||||
select {
|
||||
case frame, ok := <-c.dataFrameQueue:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
if err := c.frameHandler.HandleDataFrame(frame); err != nil {
|
||||
c.logger.Error("Failed to handle data frame",
|
||||
zap.Int("worker_id", workerID),
|
||||
zap.Error(err))
|
||||
}
|
||||
frame.Release()
|
||||
|
||||
case <-c.stopCh:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// handleFrames handles incoming frames from server
|
||||
func (c *Connector) handleFrames() {
|
||||
defer c.Close()
|
||||
@@ -246,14 +283,15 @@ func (c *Connector) handleFrames() {
|
||||
frame.Release()
|
||||
|
||||
case protocol.FrameTypeData:
|
||||
c.handlerWg.Add(1)
|
||||
go func(f *protocol.Frame) {
|
||||
defer c.handlerWg.Done()
|
||||
defer f.Release()
|
||||
if err := c.frameHandler.HandleDataFrame(f); err != nil {
|
||||
c.logger.Error("Failed to handle data frame", zap.Error(err))
|
||||
}
|
||||
}(frame)
|
||||
select {
|
||||
case c.dataFrameQueue <- frame:
|
||||
case <-c.stopCh:
|
||||
frame.Release()
|
||||
return
|
||||
default:
|
||||
c.logger.Warn("Data frame queue full, dropping frame")
|
||||
frame.Release()
|
||||
}
|
||||
|
||||
case protocol.FrameTypeClose:
|
||||
frame.Release()
|
||||
@@ -280,7 +318,6 @@ func (c *Connector) handleFrames() {
|
||||
}
|
||||
}
|
||||
|
||||
// createHeartbeatFrame creates a heartbeat frame to be sent by the write loop.
|
||||
func (c *Connector) createHeartbeatFrame() *protocol.Frame {
|
||||
c.closedMu.RLock()
|
||||
if c.closed {
|
||||
@@ -293,7 +330,6 @@ func (c *Connector) createHeartbeatFrame() *protocol.Frame {
|
||||
c.heartbeatSentAt = time.Now()
|
||||
c.heartbeatMu.Unlock()
|
||||
|
||||
c.logger.Debug("Heartbeat sent")
|
||||
return protocol.NewFrame(protocol.FrameTypeHeartbeat, nil)
|
||||
}
|
||||
|
||||
@@ -306,7 +342,6 @@ func (c *Connector) SendFrame(frame *protocol.Frame) error {
|
||||
return c.frameWriter.WriteFrame(frame)
|
||||
}
|
||||
|
||||
// Close closes the connection
|
||||
func (c *Connector) Close() error {
|
||||
c.once.Do(func() {
|
||||
c.closedMu.Lock()
|
||||
@@ -314,9 +349,8 @@ func (c *Connector) Close() error {
|
||||
c.closedMu.Unlock()
|
||||
|
||||
close(c.stopCh)
|
||||
close(c.dataFrameQueue)
|
||||
|
||||
// Wait for active handlers with timeout
|
||||
c.logger.Debug("Waiting for active handlers to complete")
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
c.handlerWg.Wait()
|
||||
@@ -325,7 +359,6 @@ func (c *Connector) Close() error {
|
||||
|
||||
select {
|
||||
case <-done:
|
||||
c.logger.Debug("All handlers completed")
|
||||
case <-time.After(3 * time.Second):
|
||||
c.logger.Warn("Force closing: some handlers are still active")
|
||||
}
|
||||
|
||||
@@ -89,19 +89,21 @@ func NewFrameHandler(conn net.Conn, frameWriter *protocol.FrameWriter, localHost
|
||||
httpClient: &http.Client{
|
||||
// No overall timeout - streaming responses can take arbitrary time
|
||||
Transport: &http.Transport{
|
||||
MaxIdleConns: 1000,
|
||||
MaxIdleConnsPerHost: 500,
|
||||
MaxConnsPerHost: 0,
|
||||
IdleConnTimeout: 180 * time.Second,
|
||||
DisableCompression: true,
|
||||
DisableKeepAlives: false,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
MaxIdleConns: 2000, // Increased from 1000 for better connection reuse
|
||||
MaxIdleConnsPerHost: 1000, // Increased from 500 for high concurrency
|
||||
MaxConnsPerHost: 0, // Unlimited connections per host
|
||||
IdleConnTimeout: 180 * time.Second, // Keep connections alive for reuse
|
||||
DisableCompression: true, // Disable compression for better CPU efficiency
|
||||
DisableKeepAlives: false, // Enable keep-alive for connection reuse
|
||||
TLSHandshakeTimeout: 5 * time.Second, // Reduced from 10s for faster failure detection
|
||||
TLSClientConfig: tlsConfig,
|
||||
ResponseHeaderTimeout: 30 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
ResponseHeaderTimeout: 15 * time.Second, // Reduced from 30s for faster timeout
|
||||
ExpectContinueTimeout: 500 * time.Millisecond, // Reduced from 1s for better responsiveness
|
||||
WriteBufferSize: 32 * 1024, // 32KB write buffer
|
||||
ReadBufferSize: 32 * 1024, // 32KB read buffer
|
||||
DialContext: (&net.Dialer{
|
||||
Timeout: 5 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
Timeout: 3 * time.Second, // Reduced from 5s for faster connection attempts
|
||||
KeepAlive: 30 * time.Second, // Keep TCP keepalive
|
||||
}).DialContext,
|
||||
},
|
||||
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||
@@ -516,7 +518,12 @@ func (h *FrameHandler) adaptiveHTTPResponse(streamID, requestID string, resp *ht
|
||||
break
|
||||
}
|
||||
if readErr != nil {
|
||||
if errors.Is(readErr, context.Canceled) || errors.Is(readErr, context.DeadlineExceeded) || errors.Is(readErr, http.ErrBodyReadAfterClose) || errors.Is(readErr, net.ErrClosed) {
|
||||
// Check for expected errors that indicate connection/body closure
|
||||
if errors.Is(readErr, context.Canceled) ||
|
||||
errors.Is(readErr, context.DeadlineExceeded) ||
|
||||
errors.Is(readErr, http.ErrBodyReadAfterClose) ||
|
||||
errors.Is(readErr, net.ErrClosed) ||
|
||||
strings.Contains(readErr.Error(), "read on closed response body") {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("read response body: %w", readErr)
|
||||
@@ -652,7 +659,12 @@ func (h *FrameHandler) streamHTTPResponse(streamID, requestID string, resp *http
|
||||
break
|
||||
}
|
||||
if readErr != nil {
|
||||
if errors.Is(readErr, context.Canceled) || errors.Is(readErr, context.DeadlineExceeded) || errors.Is(readErr, http.ErrBodyReadAfterClose) || errors.Is(readErr, net.ErrClosed) {
|
||||
// Check for expected errors that indicate connection/body closure
|
||||
if errors.Is(readErr, context.Canceled) ||
|
||||
errors.Is(readErr, context.DeadlineExceeded) ||
|
||||
errors.Is(readErr, http.ErrBodyReadAfterClose) ||
|
||||
errors.Is(readErr, net.ErrClosed) ||
|
||||
strings.Contains(readErr.Error(), "read on closed response body") {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("read response body: %w", readErr)
|
||||
|
||||
Reference in New Issue
Block a user