GoWind CMS 集成了完整的可观测性体系,包括 Jaeger 链路追踪、Redis 缓存监控、数据库慢查询分析和应用性能指标采集。本教程讲解 CMS 三服务架构下的性能监控方案。
server:
tracer:
enabled: true
provider: jaeger
endpoint: "http://jaeger:14268/api/traces"
sampler:
type: const
param: 1
func (s *PostService) Create(ctx context.Context, req *contentV1.CreatePostRequest) (*contentV1.Post, error) {
ctx, span := tracer.Start(ctx, "PostService.Create")
defer span.End()
span.SetAttributes(
attribute.String("post.title", req.Data.GetTitle()),
attribute.Int("post.author_id", int(req.Data.GetCreatedBy())),
)
post, err := s.postRepo.Create(ctx, req)
if err != nil {
span.RecordError(err)
return nil, err
}
span.SetAttributes(attribute.Int("post.id", int(post.GetId())))
return post, nil
}
访问 http://localhost:16686:
| 功能 | 说明 |
|---|
| Find Traces | 按服务/时间/标签搜索链路 |
| Compare | 对比两条链路 |
| Dependencies | 服务依赖图 |
| Service Map | 服务拓扑 |
func (r *PostRepo) List(ctx context.Context, req *paginationV1.PagingRequest) (*contentV1.ListPostResponse, error) {
ctx, span := tracer.Start(ctx, "PostRepo.List")
defer span.End()
start := time.Now()
result, err := r.query(ctx, req)
duration := time.Since(start)
span.SetAttributes(
attribute.Int64("db.duration_ms", duration.Milliseconds()),
attribute.Int("result.count", len(result.Items)),
)
if duration > 200*time.Millisecond {
span.SetAttributes(attribute.Bool("db.slow_query", true))
log.Warnf("慢查询: PostRepo.List 耗时 %v", duration)
}
return result, err
}
type Cache struct {
client *redis.Client
log *log.Helper
}
func (c *Cache) GetOrSet(ctx context.Context, key string, ttl time.Duration, fn func() (interface{}, error)) (interface{}, error) {
val, err := c.client.Get(ctx, key).Result()
if err == nil {
c.log.Debugf("缓存命中: %s", key)
return val, nil
}
result, err := fn()
if err != nil {
return nil, err
}
data, _ := json.Marshal(result)
c.client.Set(ctx, key, data, ttl)
c.log.Debugf("缓存写入: %s, TTL=%v", key, ttl)
return result, nil
}
func (s *PostService) Get(ctx context.Context, req *contentV1.GetPostRequest) (*contentV1.Post, error) {
cacheKey := fmt.Sprintf("post:detail:%d:%s", req.GetId(), req.GetLocale())
return s.cache.GetOrSet(ctx, cacheKey, 10*time.Minute, func() (*contentV1.Post, error) {
return s.postRepo.Get(ctx, req)
})
}
内容更新时自动清除相关缓存:
func (s *PostService) Update(ctx context.Context, req *contentV1.UpdatePostRequest) (*contentV1.Post, error) {
post, err := s.postRepo.Update(ctx, req)
if err != nil {
return nil, err
}
s.cache.Del(ctx, fmt.Sprintf("post:detail:%d", post.GetId()))
s.cache.DelPattern(ctx, "post:list:*")
return post, nil
}
log_min_duration_statement = 200
log_line_prefix = '%t [%p] %u@%d '
CREATE INDEX idx_posts_status_published_at ON posts (status, published_at DESC);
CREATE INDEX idx_posts_site_id ON posts (site_id);
CREATE INDEX idx_posts_slug ON posts (slug);
CREATE INDEX idx_posts_author_id ON posts (created_by);
CREATE INDEX idx_comments_post_id_status ON comments (post_id, status);
CREATE INDEX idx_comments_parent_id ON comments (parent_id);
CREATE INDEX idx_post_translations_post_lang ON post_translations (post_id, language_code);
data:
database:
driver: postgres
source: "host=postgres port=5432 ..."
max_open_conns: 25
max_idle_conns: 10
conn_max_lifetime: 300s
conn_max_idle_time: 60s
| 指标 | 说明 | 告警阈值 |
|---|
| HTTP QPS | 每秒请求数 | > 5000/s |
| HTTP P99 延迟 | 99 分位响应时间 | > 500ms |
| gRPC 延迟 | Core Service 调用耗时 | > 200ms |
| 数据库查询 | 慢查询比例 | > 5% |
| 缓存命中率 | Redis 缓存效率 | < 80% |
| 错误率 | HTTP 5xx 比例 | > 1% |
| Goroutine | 活跃协程数 | > 10000 |
var (
httpRequestDuration = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "http_request_duration_seconds",
Help: "HTTP request duration",
},
[]string{"method", "path", "status"},
)
grpcRequestDuration = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "grpc_request_duration_seconds",
Help: "gRPC request duration",
},
[]string{"service", "method"},
)
cacheHitRate = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "cache_hit_rate",
Help: "Cache hit rate",
},
[]string{"cache_type"},
)
)
wrk -t4 -c100 -d30s http://localhost:6700/app/v1/posts
wrk -t4 -c100 -d30s -H "Authorization: Bearer xxx" http://localhost:6600/admin/v1/posts
echo "GET http://localhost:6700/app/v1/posts" | vegeta attack -duration=60s -rate=100 | vegeta report
vegeta report -type=json results.bin | jq
| 优化项 | 方法 | 预期收益 |
|---|
| 数据库索引 | 分析慢查询添加索引 | 50-90% |
| Redis 缓存 | 热点数据缓存 | 70-95% |
| 连接池 | 合理配置 | 20% |
| N+1 查询 | Ent 预加载 | 50-80% |
| 分页优化 | 游标分页 | 大数据量显著 |
| CDN 静态资源 | 图片/JS/CSS | 前端 80% |
| Gzip 压缩 | HTTP 响应压缩 | 带宽 70% |
| 检查项 | 说明 |
|---|
| Jaeger 集成 | 链路追踪配置 |
| Redis 缓存 | 热点数据缓存 |
| 数据库索引 | 慢查询分析 |
| 连接池配置 | 合理的连接数 |
| 性能指标 | Prometheus 采集 |
| 压力测试 | wrk / Vegeta |
| 慢查询监控 | PostgreSQL 日志 |