Skip to content

Commit e5547ce

Browse files
committed
refactor(client): 优化连接管理与心跳机制,增强业务操作跟踪
- 调整连接重连策略,采用指数退避和快速重连机制 - 新增TCP keepalive和应用层心跳间隔,保持连接稳定 - 重构HTTP客户端配置,完善传输层参数设置 - 增加busyOperations计数,跟踪并发业务操作状态 - 优化连接通知处理逻辑,改进日志打印和错误处理 - 在执行业务及测试连接过程中,准确标记业务操作开始和结束 - 统一客户端ID变量命名为clientId,修改相关方法名和测试代码
1 parent a39b9f0 commit e5547ce

6 files changed

Lines changed: 99 additions & 65 deletions

File tree

internal/client/client.go

Lines changed: 82 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"errors"
66
"fmt"
77
"io"
8+
"net"
89
"net/http"
910
"net/url"
1011
"os"
@@ -23,16 +24,20 @@ import (
2324
)
2425

2526
const (
26-
downloadTimeout = 5 * time.Minute
27-
maxReconnectDelay = 5 * time.Minute
27+
downloadTimeout = 30 * time.Second
28+
minReconnectDelay = 1 * time.Second // 最小重连延迟
29+
maxReconnectDelay = 30 * time.Second // 最大重连延迟
30+
fastReconnectAttempt = 3 // 快速重连尝试次数
31+
tcpKeepaliveInterval = 30 * time.Second // TCP keepalive 间隔
32+
heartbeatInterval = 30 * time.Second // 应用层心跳间隔 - 保持连接活跃
2833
)
2934

3035
var (
3136
isConnected atomic.Bool
3237
)
3338

3439
type Client struct {
35-
clientID string
40+
clientId string
3641
serverURL string
3742
httpClient *http.Client
3843
connectClient deployPBconnect.DeployServiceClient
@@ -42,49 +47,70 @@ type Client struct {
4247
systemInfo *system.SystemInfo // 缓存的系统信息
4348
systemInfoOnce sync.Once // 确保系统信息只获取一次
4449
httpServer *server.HTTPServer // HTTP-01 验证服务器
50+
busyOperations atomic.Int32 // 正在执行的业务操作数量
4551
}
4652

4753
func NewClient(ctx context.Context) (*Client, error) {
4854
cfg := config.GetConfig()
4955

5056
// 生成客户端ID
51-
clientID, err := system.GetUniqueClientID(ctx)
57+
clientId, err := system.GetUniqueClientId(ctx)
5258
if err != nil {
5359
return nil, err
5460
}
5561

56-
// 配置 HTTP 客户端
57-
httpClient := &http.Client{
58-
Timeout: 30 * time.Second,
59-
}
62+
// 配置 HTTP Transport
63+
var transport http.RoundTripper
64+
6065
if cfg.Server.Env == "local" {
6166
p := new(http.Protocols)
6267
p.SetUnencryptedHTTP2(true)
63-
httpClient = &http.Client{
64-
Timeout: 30 * time.Second,
65-
Transport: &http.Transport{
66-
Protocols: p,
67-
MaxIdleConns: 100,
68-
MaxIdleConnsPerHost: 10,
69-
IdleConnTimeout: 90 * time.Second,
70-
},
68+
transport = &http.Transport{
69+
Protocols: p,
70+
MaxIdleConns: 100,
71+
MaxIdleConnsPerHost: 10,
72+
IdleConnTimeout: 90 * time.Second,
73+
DisableKeepAlives: false,
74+
ForceAttemptHTTP2: true,
75+
TLSHandshakeTimeout: 10 * time.Second,
76+
ResponseHeaderTimeout: 30 * time.Second,
77+
ExpectContinueTimeout: 1 * time.Second,
78+
DialContext: (&net.Dialer{
79+
Timeout: 10 * time.Second,
80+
KeepAlive: tcpKeepaliveInterval,
81+
}).DialContext,
7182
}
7283
} else {
73-
httpClient.Transport = &http.Transport{
74-
MaxIdleConns: 100,
75-
MaxIdleConnsPerHost: 10,
76-
IdleConnTimeout: 90 * time.Second,
84+
transport = &http.Transport{
85+
MaxIdleConns: 100,
86+
MaxIdleConnsPerHost: 10,
87+
IdleConnTimeout: 90 * time.Second,
88+
DisableKeepAlives: false,
89+
ForceAttemptHTTP2: true,
90+
TLSHandshakeTimeout: 10 * time.Second,
91+
ResponseHeaderTimeout: 30 * time.Second,
92+
ExpectContinueTimeout: 1 * time.Second,
93+
DialContext: (&net.Dialer{
94+
Timeout: 10 * time.Second,
95+
KeepAlive: tcpKeepaliveInterval,
96+
}).DialContext,
7797
}
7898
}
7999

100+
httpClient := &http.Client{
101+
Timeout: 0,
102+
Transport: transport,
103+
}
104+
80105
client := &Client{
81-
clientID: clientID,
106+
clientId: clientId,
82107
serverURL: config.URL,
83108
httpClient: httpClient,
84109
ctx: ctx,
85110
accessKey: cfg.Server.AccessKey,
86111
}
87112

113+
// 创建 connect client
88114
client.connectClient = deployPBconnect.NewDeployServiceClient(httpClient, config.URL)
89115

90116
// 启动连接通知
@@ -107,9 +133,9 @@ func (c *Client) SetHTTPServer(httpServer *server.HTTPServer) {
107133
c.httpServer = httpServer
108134
}
109135

110-
// StartConnectNotify 启动连接通知
136+
// StartConnectNotify 启动连接通知 - 建立持久连接并通过心跳保持
111137
func (c *Client) StartConnectNotify() {
112-
reconnectDelay := time.Second
138+
reconnectDelay := minReconnectDelay
113139
consecutiveFailures := 0
114140

115141
for {
@@ -123,24 +149,27 @@ func (c *Client) StartConnectNotify() {
123149
stream, err := c.connectClient.Notify(c.ctx)
124150
if err != nil {
125151
consecutiveFailures++
126-
127-
// 只在状态变化时打印日志
128152
if isConnected.Load() || consecutiveFailures == 1 {
129-
logger.Error("连接失败", "error", err)
153+
logger.Error("连接失败", "error", err, "attempt", consecutiveFailures)
130154
}
131155

132156
isConnected.Store(false)
133157
c.lastDisconnectLogged.Store(true)
134158

135-
// 等待重连
159+
// 指数退避重连
160+
if consecutiveFailures <= fastReconnectAttempt {
161+
reconnectDelay = minReconnectDelay
162+
} else {
163+
reconnectDelay = min(reconnectDelay*2, maxReconnectDelay)
164+
}
165+
136166
time.Sleep(reconnectDelay)
137-
reconnectDelay = min(reconnectDelay*2, maxReconnectDelay)
138167
continue
139168
}
140169

141-
// 连接成功,重置计数器
170+
// 连接成功,重置失败计数
142171
consecutiveFailures = 0
143-
reconnectDelay = time.Second
172+
reconnectDelay = minReconnectDelay
144173

145174
// 获取系统信息(使用缓存)
146175
systemInfo, err := c.getSystemInfo()
@@ -151,10 +180,10 @@ func (c *Client) StartConnectNotify() {
151180
continue
152181
}
153182

154-
// 构造注册请求
183+
// 构造并发送注册请求
155184
registerReq := &deployPB.NotifyRequest{
156185
AccessKey: c.accessKey,
157-
ClientId: c.clientID,
186+
ClientId: c.clientId,
158187
Version: config.Version,
159188
Data: &deployPB.NotifyRequest_RegisterResponse{
160189
RegisterResponse: &deployPB.RegisterResponse{
@@ -168,54 +197,53 @@ func (c *Client) StartConnectNotify() {
168197
},
169198
}
170199

171-
// 注册客户端
172200
if err := stream.Send(registerReq); err != nil {
201+
logger.Error("注册失败", "error", err)
173202
stream.CloseRequest()
174203
time.Sleep(reconnectDelay)
175204
continue
176205
}
177206

178-
// 流断开,先检查主 context 是否被取消(而不是检查错误类型)
179-
// 因为错误链中可能包含 context.Canceled,但实际是连接断开导致的
180-
select {
181-
case <-c.ctx.Done():
182-
logger.Info("主 context 已取消,退出连接循环")
183-
return
184-
default:
185-
}
207+
logger.Info("连接已建立,开始处理消息")
186208

187-
// 处理消息流
188-
if err := c.handleNotifyStream(stream); err != nil {
189-
// logger.Error("连接断开", "error", err)
209+
// 处理消息流 - 正常情况下会因为心跳保持而永不返回
210+
streamErr := c.handleNotifyStream(stream)
211+
212+
// 只有在异常情况下才会到这里(网络故障、服务端主动断开等)
213+
stream.CloseRequest()
214+
215+
busyOps := c.busyOperations.Load()
216+
if busyOps > 0 {
217+
logger.Warn("连接意外断开(有业务正在执行)", "error", streamErr, "busyOps", busyOps)
218+
} else {
219+
logger.Info("连接断开,准备重连", "error", streamErr)
190220
}
191221

192-
// 标记断开连接
193222
isConnected.Store(false)
194223
c.lastDisconnectLogged.Store(true)
195224

196-
// 等待后重连
225+
// 短暂延迟后重连
197226
time.Sleep(reconnectDelay)
198-
reconnectDelay = min(reconnectDelay*2, maxReconnectDelay)
199227
}
200228
}
201229

202230
// handleNotifyStream 处理通知流
203231
func (c *Client) handleNotifyStream(stream *connect.BidiStreamForClientSimple[deployPB.NotifyRequest, deployPB.NotifyResponse]) error {
204-
// 启动心跳 goroutine
232+
// 启动心跳 goroutine - 持续发送心跳保持连接活跃
205233
heartbeatCtx, cancelHeartbeat := context.WithCancel(c.ctx)
206234
defer cancelHeartbeat()
207235

208236
go c.sendHeartbeat(heartbeatCtx, stream)
209237

210-
receiveCount := 0
238+
// 简单的消息接收循环
211239
for {
212240
select {
213241
case <-c.ctx.Done():
214242
return c.ctx.Err()
215243
default:
216244
}
217245

218-
// 阻塞接收消息
246+
// 阻塞接收消息 (无超时限制,依赖 TCP keepalive 和心跳保持连接)
219247
req, err := stream.Receive()
220248
if err != nil {
221249
if errors.Is(err, io.EOF) {
@@ -224,15 +252,11 @@ func (c *Client) handleNotifyStream(stream *connect.BidiStreamForClientSimple[de
224252
return fmt.Errorf("接收消息失败: %w", err)
225253
}
226254

227-
receiveCount++
228-
229255
// 首次收到消息,标记连接成功
230256
if !isConnected.Load() {
231257
isConnected.Store(true)
232-
233-
// 如果之前断开过连接,打印重连成功日志
234258
if c.lastDisconnectLogged.Load() {
235-
// logger.Info("重新连接成功")
259+
logger.Info("重新连接成功")
236260
c.lastDisconnectLogged.Store(false)
237261
}
238262
}
@@ -274,7 +298,7 @@ func (c *Client) handleMessage(stream *connect.BidiStreamForClientSimple[deployP
274298

275299
// sendHeartbeat 定期发送心跳(保持连接活跃)
276300
func (c *Client) sendHeartbeat(ctx context.Context, stream *connect.BidiStreamForClientSimple[deployPB.NotifyRequest, deployPB.NotifyResponse]) {
277-
ticker := time.NewTicker(30 * time.Second)
301+
ticker := time.NewTicker(heartbeatInterval)
278302
defer ticker.Stop()
279303

280304
for {
@@ -285,11 +309,11 @@ func (c *Client) sendHeartbeat(ctx context.Context, stream *connect.BidiStreamFo
285309
// 发送心跳消息
286310
err := stream.Send(&deployPB.NotifyRequest{
287311
AccessKey: c.accessKey,
288-
ClientId: c.clientID,
312+
ClientId: c.clientId,
289313
Version: config.Version,
290314
})
291315
if err != nil {
292-
// logger.Error("发送心跳失败", "error", err)
316+
logger.Debug("发送心跳失败", "error", err)
293317
return
294318
}
295319
}

internal/client/connect.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ import (
1111

1212
// handleConnect 处理测试连接
1313
func (c *Client) handleConnect(stream *connect.BidiStreamForClientSimple[deployPB.NotifyRequest, deployPB.NotifyResponse], requestId string, data *deployPB.ConnectRequest) error {
14+
// 标记开始执行业务操作
15+
c.busyOperations.Add(1)
16+
defer c.busyOperations.Add(-1)
17+
1418
logger.Info("收到【测试连接提供商】请求", "provider", data.Provider, "requestId", requestId)
1519

1620
success := false
@@ -57,10 +61,10 @@ func (c *Client) handleConnect(stream *connect.BidiStreamForClientSimple[deployP
5761
success = false
5862
}
5963

60-
// 发送响应给服务端
64+
// 发送响应
6165
if err := stream.Send(&deployPB.NotifyRequest{
6266
AccessKey: c.accessKey,
63-
ClientId: c.clientID,
67+
ClientId: c.clientId,
6468
Version: config.Version,
6569
RequestId: requestId,
6670
Data: &deployPB.NotifyRequest_ConnectRequest{
@@ -70,6 +74,7 @@ func (c *Client) handleConnect(stream *connect.BidiStreamForClientSimple[deployP
7074
},
7175
},
7276
}); err != nil {
77+
logger.Error("发送测试连接响应失败", "error", err, "requestId", requestId)
7378
return err
7479
}
7580

internal/client/execute_busines.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ import (
1515

1616
// executeBusines 执行业务
1717
func (c *Client) executeBusines(stream *connect.BidiStreamForClientSimple[deployPB.NotifyRequest, deployPB.NotifyResponse], requestId string, resp *deployPB.ExecuteBusinesResponse) {
18+
// 标记开始执行业务操作
19+
c.busyOperations.Add(1)
20+
defer c.busyOperations.Add(-1)
21+
1822
providerName := resp.Provider
1923
executeBusinesType := resp.ExecuteBusinesType
2024
domain := resp.Domain
@@ -124,15 +128,16 @@ func (c *Client) sendExecuteBusinesResponse(stream *connect.BidiStreamForClientS
124128
RequestResult: result,
125129
}
126130

131+
// 使用传入的 stream 发送
127132
if err := stream.Send(&deployPB.NotifyRequest{
128133
AccessKey: c.accessKey,
129-
ClientId: c.clientID,
134+
ClientId: c.clientId,
130135
Version: config.Version,
131136
RequestId: requestId,
132137
Data: &deployPB.NotifyRequest_ExecuteBusinesRequest{
133138
ExecuteBusinesRequest: req,
134139
},
135140
}); err != nil {
136-
logger.Error("发送执行业务响应给服务端失败", "error", err, "requestId", requestId)
141+
logger.Error("发送执行业务响应失败", "error", err, "requestId", requestId)
137142
}
138143
}

internal/client/provider.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ func (c *Client) handleGetProvider(stream *connect.BidiStreamForClientSimple[dep
2323

2424
err := stream.Send(&deployPB.NotifyRequest{
2525
AccessKey: c.accessKey,
26-
ClientId: c.clientID,
26+
ClientId: c.clientId,
2727
RequestId: requestID,
2828
Data: &deployPB.NotifyRequest_GetProviderResponse{
2929
GetProviderResponse: &deployPB.GetProviderResponse{

internal/system/info.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ func ValidateSystemRequirements() error {
6767

6868
// GetUniqueClientID 获取唯一客户端ID
6969
// 确保同一台机器每次启动都获得相同的ID
70-
func GetUniqueClientID(ctx context.Context) (string, error) {
70+
func GetUniqueClientId(ctx context.Context) (string, error) {
7171
// 先尝试读取缓存
7272
if id := readCachedID(); id != "" {
7373
return id, nil

internal/system/info_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ func TestGetSystemInfo(t *testing.T) {
2222
}
2323

2424
func TestGetClientID(t *testing.T) {
25-
clientID, err := system.GetUniqueClientID(t.Context())
25+
clientID, err := system.GetUniqueClientId(t.Context())
2626
if err != nil {
2727
t.Fatalf("获取客户端 ID2失败: %v", err)
2828
}

0 commit comments

Comments
 (0)