Skip to content

Commit dec815b

Browse files
committed
Update.
1 parent 0d565f1 commit dec815b

File tree

3 files changed

+136
-99
lines changed

3 files changed

+136
-99
lines changed

cmd/dashboard/controller/api_v1.go

Lines changed: 58 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"sort"
66
"strconv"
77
"strings"
8+
"sync"
89
"time"
910

1011
"github.com/gin-gonic/gin"
@@ -18,6 +19,16 @@ type apiV1 struct {
1819
r gin.IRouter
1920
}
2021

22+
// monitor API 短时缓存(包级别)
23+
var (
24+
monitorCacheMu sync.Mutex
25+
monitorCache = map[uint64]struct {
26+
ts time.Time
27+
data []*model.MonitorHistory
28+
dur time.Duration
29+
}{}
30+
)
31+
2132
func (v *apiV1) serve() {
2233
r := v.r.Group("")
2334
// 强制认证的 API
@@ -169,6 +180,18 @@ func (v *apiV1) monitorHistoriesById(c *gin.Context) {
169180
endTime := time.Now()
170181
startTime := endTime.Add(-duration)
171182

183+
// 命中短时缓存则直接返回
184+
monitorCacheMu.Lock()
185+
if ce, ok := monitorCache[server.ID]; ok {
186+
if time.Since(ce.ts) <= 500*time.Millisecond && ce.dur == duration {
187+
data := ce.data
188+
monitorCacheMu.Unlock()
189+
c.JSON(200, data)
190+
return
191+
}
192+
}
193+
monitorCacheMu.Unlock()
194+
172195
// 获取该服务器的监控配置,展示所有监控器
173196
monitors := singleton.ServiceSentinelShared.Monitors()
174197
var networkHistories []*model.MonitorHistory
@@ -182,31 +205,26 @@ func (v *apiV1) monitorHistoriesById(c *gin.Context) {
182205
err error
183206
}
184207

185-
// 创建通道收集结果
208+
// 创建通道收集结果 + 控制并发
186209
resultChan := make(chan monitorResult, len(monitors))
187210
activeQueries := 0
211+
// 限制并发,避免在低配机上压爆 CPU
212+
sem := make(chan struct{}, 4)
188213

189214
// 并发查询所有ICMP/TCP监控器
190215
for _, monitor := range monitors {
191216
if monitor.Type == model.TaskTypeICMPPing || monitor.Type == model.TaskTypeTCPPing {
192217
activeQueries++
193218
go func(monitorID uint64) {
194-
// 使用高效的时间范围查询
195-
allHistories, err := monitorOps.GetMonitorHistoriesByMonitorID(
196-
monitorID, startTime, endTime)
197-
198-
var serverHistories []*model.MonitorHistory
199-
if err == nil {
200-
// 快速过滤出该服务器的记录
201-
for _, history := range allHistories {
202-
if history.ServerID == server.ID {
203-
serverHistories = append(serverHistories, history)
204-
}
205-
}
206-
}
219+
sem <- struct{}{}
220+
defer func() { <-sem }()
221+
// 使用反向限量,优先拿最近的数据,最多取 6000 条/监控器,足够覆盖 72h
222+
allHistories, err := monitorOps.GetMonitorHistoriesByServerAndMonitorRangeReverseLimit(
223+
server.ID, monitorID, startTime, endTime, 6000,
224+
)
207225

208226
resultChan <- monitorResult{
209-
histories: serverHistories,
227+
histories: allHistories,
210228
err: err,
211229
}
212230
}(monitor.ID)
@@ -224,11 +242,35 @@ func (v *apiV1) monitorHistoriesById(c *gin.Context) {
224242
}
225243
}
226244

227-
// 按时间排序(数据已经基本有序,排序很快)
245+
// 将多个监控器结果汇总后统一按时间倒序
228246
sort.Slice(networkHistories, func(i, j int) bool {
229247
return networkHistories[i].CreatedAt.After(networkHistories[j].CreatedAt)
230248
})
231249

250+
// 下采样:当点数超过 5000 时,按等间隔抽取,减少前端渲染负荷
251+
const maxPoints = 5000
252+
if len(networkHistories) > maxPoints {
253+
step := float64(len(networkHistories)) / float64(maxPoints)
254+
sampled := make([]*model.MonitorHistory, 0, maxPoints)
255+
for i := 0; i < maxPoints; i++ {
256+
idx := int(float64(i) * step)
257+
if idx >= len(networkHistories) {
258+
idx = len(networkHistories) - 1
259+
}
260+
sampled = append(sampled, networkHistories[idx])
261+
}
262+
networkHistories = sampled
263+
}
264+
265+
// 写入短时缓存
266+
monitorCacheMu.Lock()
267+
monitorCache[server.ID] = struct {
268+
ts time.Time
269+
data []*model.MonitorHistory
270+
dur time.Duration
271+
}{ts: time.Now(), data: networkHistories, dur: duration}
272+
monitorCacheMu.Unlock()
273+
232274
log.Printf("API /monitor/%d 返回 %d 条记录(范围: %v,所有监控器)", server.ID, len(networkHistories), duration)
233275
c.JSON(200, networkHistories)
234276
} else {

cmd/dashboard/controller/common_page.go

Lines changed: 8 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -8,26 +8,27 @@ import (
88
"log"
99
"math"
1010
"net/http"
11-
"runtime"
12-
"runtime/debug"
1311
"strconv"
1412
"sync"
1513
"time"
1614

15+
"runtime"
16+
17+
"runtime/debug"
18+
1719
"code.cloudfoundry.org/bytefmt"
1820
"github.com/gin-gonic/gin"
1921
"github.com/gorilla/websocket"
2022
"github.com/hashicorp/go-uuid"
2123
"github.com/nicksnyder/go-i18n/v2/i18n"
24+
"github.com/xos/serverstatus/proto"
2225
"golang.org/x/crypto/bcrypt"
2326
"golang.org/x/sync/singleflight"
2427

25-
"github.com/xos/serverstatus/db"
2628
"github.com/xos/serverstatus/model"
2729
"github.com/xos/serverstatus/pkg/mygin"
2830
"github.com/xos/serverstatus/pkg/utils"
2931
"github.com/xos/serverstatus/pkg/websocketx"
30-
"github.com/xos/serverstatus/proto"
3132
"github.com/xos/serverstatus/service/rpc"
3233
"github.com/xos/serverstatus/service/singleton"
3334
)
@@ -403,85 +404,9 @@ func (cp *commonPage) network(c *gin.Context) {
403404
// 获取监控历史记录
404405
var monitorHistories interface{}
405406
if singleton.Conf.DatabaseType == "badger" {
406-
// BadgerDB 模式,查询监控历史记录
407-
// 性能优化:只查询默认服务器的数据,其他服务器数据通过AJAX异步加载
408-
if id > 0 {
409-
// 使用高效的API查询方法,而不是全表扫描
410-
endTime := time.Now()
411-
startTime := endTime.AddDate(0, 0, -3) // 3天数据
412-
413-
// 获取该服务器的监控配置,只查询ICMP/TCP类型
414-
monitors := singleton.ServiceSentinelShared.Monitors()
415-
var networkHistories []*model.MonitorHistory
416-
417-
if monitors != nil {
418-
// 使用我们之前优化的并发查询方法
419-
type monitorResult struct {
420-
histories []*model.MonitorHistory
421-
err error
422-
}
423-
424-
// 创建通道收集结果
425-
resultChan := make(chan monitorResult, len(monitors))
426-
activeQueries := 0
427-
428-
// 并发查询所有ICMP/TCP监控器
429-
for _, monitor := range monitors {
430-
if monitor.Type == model.TaskTypeICMPPing || monitor.Type == model.TaskTypeTCPPing {
431-
activeQueries++
432-
go func(monitorID uint64) {
433-
// 使用高效的时间范围查询
434-
monitorOps := db.NewMonitorHistoryOps(db.DB)
435-
allHistories, err := monitorOps.GetMonitorHistoriesByMonitorID(
436-
monitorID, startTime, endTime)
437-
438-
var serverHistories []*model.MonitorHistory
439-
if err == nil {
440-
// 快速过滤出该服务器的记录
441-
for _, history := range allHistories {
442-
if history.ServerID == id {
443-
serverHistories = append(serverHistories, history)
444-
}
445-
}
446-
}
447-
448-
resultChan <- monitorResult{
449-
histories: serverHistories,
450-
err: err,
451-
}
452-
}(monitor.ID)
453-
}
454-
}
455-
456-
// 收集所有并发查询结果
457-
for i := 0; i < activeQueries; i++ {
458-
result := <-resultChan
459-
if result.err != nil {
460-
log.Printf("并发查询监控历史记录失败: %v", result.err)
461-
continue
462-
}
463-
networkHistories = append(networkHistories, result.histories...)
464-
}
465-
}
466-
467-
// 转换为[]model.MonitorHistory格式
468-
var filteredHistories []model.MonitorHistory
469-
for _, h := range networkHistories {
470-
if h != nil {
471-
filteredHistories = append(filteredHistories, *h)
472-
}
473-
}
474-
monitorHistories = filteredHistories
475-
} else {
476-
monitorHistories = []model.MonitorHistory{}
477-
}
478-
479-
// 序列化为JSON
480-
var err error
481-
monitorInfos, err = utils.Json.Marshal(monitorHistories)
482-
if err != nil {
483-
monitorInfos = []byte("[]")
484-
}
407+
// BadgerDB 模式:为提升首屏速度,network 页面不再在后端预取监控历史,改为前端异步加载
408+
monitorHistories = []model.MonitorHistory{}
409+
monitorInfos = []byte("[]")
485410
} else {
486411
// SQLite 模式,使用 MonitorAPI
487412
if singleton.MonitorAPI != nil {

db/model_ops.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,76 @@ func (o *MonitorHistoryOps) GetMonitorHistoriesByServerAndMonitor(serverID, moni
299299

300300
// 删除有问题的优化方法,恢复使用原始的GetMonitorHistoriesByServerAndMonitor方法
301301

302+
// GetMonitorHistoriesByServerAndMonitorRangeReverseLimit
303+
// 高效查询:按 monitorID 前缀 + 时间范围,使用反向迭代从 endTime 向前扫描,尽快拿到最近的 N 条
304+
func (o *MonitorHistoryOps) GetMonitorHistoriesByServerAndMonitorRangeReverseLimit(serverID, monitorID uint64, startTime, endTime time.Time, limit int) ([]*model.MonitorHistory, error) {
305+
if limit <= 0 {
306+
limit = 1000
307+
}
308+
309+
prefix := fmt.Sprintf("monitor_history:%d:", monitorID)
310+
// 以 endTime 为起点反向扫描
311+
endKey := fmt.Sprintf("%s%d", prefix, endTime.UnixNano())
312+
startSuffix := []byte(fmt.Sprintf(":%d", startTime.UnixNano()))
313+
314+
var histories []*model.MonitorHistory
315+
err := o.db.db.View(func(txn *badger.Txn) error {
316+
opts := badger.DefaultIteratorOptions
317+
opts.PrefetchValues = true
318+
opts.PrefetchSize = 32
319+
opts.Reverse = true
320+
it := txn.NewIterator(opts)
321+
defer it.Close()
322+
323+
// 定位到 endKey 附近
324+
for it.Seek([]byte(endKey)); it.Valid(); it.Next() {
325+
item := it.Item()
326+
key := item.KeyCopy(nil)
327+
328+
if !bytes.HasPrefix(key, []byte(prefix)) {
329+
// 反向迭代一旦越过前缀范围即可退出
330+
break
331+
}
332+
333+
// 时间下界:若 key 的时间戳 < startTime 则可以继续反向,但达到更小时间即可停止
334+
// 由于键格式 monitor_history:{monitorID}:{ts},用最后一个冒号后部分快速判断
335+
if idx := bytes.LastIndexByte(key, ':'); idx != -1 {
336+
tsPart := key[idx:]
337+
if bytes.Compare(tsPart, startSuffix) < 0 {
338+
// 已经早于 startTime,停止
339+
break
340+
}
341+
}
342+
343+
if err := item.Value(func(val []byte) error {
344+
var history model.MonitorHistory
345+
if err := jsonAPI.Unmarshal(val, &history); err != nil {
346+
return err
347+
}
348+
if history.ServerID == serverID &&
349+
!history.CreatedAt.Before(startTime) && history.CreatedAt.Before(endTime) {
350+
histories = append(histories, &history)
351+
}
352+
return nil
353+
}); err != nil {
354+
return err
355+
}
356+
357+
if len(histories) >= limit {
358+
break
359+
}
360+
}
361+
return nil
362+
})
363+
364+
if err != nil {
365+
return nil, err
366+
}
367+
368+
// 当前结果为时间倒序(新->旧),保持这一顺序即可满足大部分展示;如需正序可反转
369+
return histories, nil
370+
}
371+
302372
// CleanupOldMonitorHistories removes monitor histories older than maxAge
303373
func (o *MonitorHistoryOps) CleanupOldMonitorHistories(maxAge time.Duration) (int, error) {
304374
// Get all monitor IDs

0 commit comments

Comments
 (0)