408 lines
10 KiB
Go
408 lines
10 KiB
Go
package tracing
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"strings"
|
||
"time"
|
||
|
||
"github.com/redis/go-redis/v9"
|
||
"go.opentelemetry.io/otel/attribute"
|
||
"go.opentelemetry.io/otel/trace"
|
||
"go.uber.org/zap"
|
||
|
||
"tyapi-server/internal/shared/interfaces"
|
||
)
|
||
|
||
// TracedRedisCache Redis缓存自动追踪包装器
|
||
type TracedRedisCache struct {
|
||
client redis.UniversalClient
|
||
tracer *Tracer
|
||
logger *zap.Logger
|
||
prefix string
|
||
config RedisTracingConfig
|
||
}
|
||
|
||
// RedisTracingConfig Redis追踪配置
|
||
type RedisTracingConfig struct {
|
||
IncludeKeys bool
|
||
IncludeValues bool
|
||
MaxKeyLength int
|
||
MaxValueLength int
|
||
SlowThreshold time.Duration
|
||
SanitizeValues bool
|
||
}
|
||
|
||
// DefaultRedisTracingConfig 默认Redis追踪配置
|
||
func DefaultRedisTracingConfig() RedisTracingConfig {
|
||
return RedisTracingConfig{
|
||
IncludeKeys: true,
|
||
IncludeValues: false, // 生产环境建议设为false保护敏感数据
|
||
MaxKeyLength: 100,
|
||
MaxValueLength: 1000,
|
||
SlowThreshold: 50 * time.Millisecond,
|
||
SanitizeValues: true,
|
||
}
|
||
}
|
||
|
||
// NewTracedRedisCache 创建带追踪的Redis缓存
|
||
func NewTracedRedisCache(client redis.UniversalClient, tracer *Tracer, logger *zap.Logger, prefix string) interfaces.CacheService {
|
||
return &TracedRedisCache{
|
||
client: client,
|
||
tracer: tracer,
|
||
logger: logger,
|
||
prefix: prefix,
|
||
config: DefaultRedisTracingConfig(),
|
||
}
|
||
}
|
||
|
||
// Name 返回服务名称
|
||
func (c *TracedRedisCache) Name() string {
|
||
return "redis-cache"
|
||
}
|
||
|
||
// Initialize 初始化服务
|
||
func (c *TracedRedisCache) Initialize(ctx context.Context) error {
|
||
c.logger.Info("Redis缓存服务已初始化")
|
||
return nil
|
||
}
|
||
|
||
// HealthCheck 健康检查
|
||
func (c *TracedRedisCache) HealthCheck(ctx context.Context) error {
|
||
_, err := c.client.Ping(ctx).Result()
|
||
return err
|
||
}
|
||
|
||
// Shutdown 关闭服务
|
||
func (c *TracedRedisCache) Shutdown(ctx context.Context) error {
|
||
c.logger.Info("Redis缓存服务已关闭")
|
||
return c.client.Close()
|
||
}
|
||
|
||
// Get 获取缓存值
|
||
func (c *TracedRedisCache) Get(ctx context.Context, key string, dest interface{}) error {
|
||
// 开始追踪
|
||
ctx, span := c.tracer.StartCacheSpan(ctx, "get", key)
|
||
defer span.End()
|
||
|
||
// 添加基础属性
|
||
c.addBaseAttributes(span, "get", key)
|
||
|
||
// 记录开始时间
|
||
startTime := time.Now()
|
||
|
||
// 构建完整键名
|
||
fullKey := c.buildKey(key)
|
||
|
||
// 执行Redis操作
|
||
result, err := c.client.Get(ctx, fullKey).Result()
|
||
|
||
// 计算执行时间
|
||
duration := time.Since(startTime)
|
||
c.tracer.AddSpanAttributes(span,
|
||
attribute.Int64("redis.duration_ms", duration.Milliseconds()),
|
||
)
|
||
|
||
// 检查慢操作
|
||
if duration > c.config.SlowThreshold {
|
||
c.tracer.AddSpanAttributes(span,
|
||
attribute.Bool("redis.slow_operation", true),
|
||
)
|
||
c.logger.Warn("Redis慢操作检测",
|
||
zap.String("operation", "get"),
|
||
zap.String("key", c.sanitizeKey(key)),
|
||
zap.Duration("duration", duration),
|
||
zap.String("trace_id", c.tracer.GetTraceID(ctx)),
|
||
)
|
||
}
|
||
|
||
// 处理结果
|
||
if err != nil {
|
||
if err == redis.Nil {
|
||
// 缓存未命中
|
||
c.tracer.AddSpanAttributes(span,
|
||
attribute.Bool("redis.hit", false),
|
||
attribute.String("redis.result", "miss"),
|
||
)
|
||
c.tracer.SetSpanSuccess(span)
|
||
return interfaces.ErrCacheMiss
|
||
} else {
|
||
// Redis错误
|
||
c.tracer.SetSpanError(span, err)
|
||
c.logger.Error("Redis GET操作失败",
|
||
zap.String("key", c.sanitizeKey(key)),
|
||
zap.Error(err),
|
||
zap.String("trace_id", c.tracer.GetTraceID(ctx)),
|
||
)
|
||
return err
|
||
}
|
||
}
|
||
|
||
// 缓存命中
|
||
c.tracer.AddSpanAttributes(span,
|
||
attribute.Bool("redis.hit", true),
|
||
attribute.String("redis.result", "hit"),
|
||
attribute.Int("redis.value_size", len(result)),
|
||
)
|
||
|
||
// 反序列化
|
||
if err := c.deserialize(result, dest); err != nil {
|
||
c.tracer.SetSpanError(span, err)
|
||
return err
|
||
}
|
||
|
||
c.tracer.SetSpanSuccess(span)
|
||
return nil
|
||
}
|
||
|
||
// Set 设置缓存值
|
||
func (c *TracedRedisCache) Set(ctx context.Context, key string, value interface{}, ttl ...interface{}) error {
|
||
// 开始追踪
|
||
ctx, span := c.tracer.StartCacheSpan(ctx, "set", key)
|
||
defer span.End()
|
||
|
||
// 添加基础属性
|
||
c.addBaseAttributes(span, "set", key)
|
||
|
||
// 处理TTL
|
||
var expiration time.Duration
|
||
if len(ttl) > 0 {
|
||
if duration, ok := ttl[0].(time.Duration); ok {
|
||
expiration = duration
|
||
c.tracer.AddSpanAttributes(span,
|
||
attribute.Int64("redis.ttl_seconds", int64(expiration.Seconds())),
|
||
)
|
||
}
|
||
}
|
||
|
||
// 记录开始时间
|
||
startTime := time.Now()
|
||
|
||
// 序列化值
|
||
serialized, err := c.serialize(value)
|
||
if err != nil {
|
||
c.tracer.SetSpanError(span, err)
|
||
return err
|
||
}
|
||
|
||
// 构建完整键名
|
||
fullKey := c.buildKey(key)
|
||
|
||
// 执行Redis操作
|
||
err = c.client.Set(ctx, fullKey, serialized, expiration).Err()
|
||
|
||
// 计算执行时间
|
||
duration := time.Since(startTime)
|
||
c.tracer.AddSpanAttributes(span,
|
||
attribute.Int64("redis.duration_ms", duration.Milliseconds()),
|
||
attribute.Int("redis.value_size", len(serialized)),
|
||
)
|
||
|
||
// 检查慢操作
|
||
if duration > c.config.SlowThreshold {
|
||
c.tracer.AddSpanAttributes(span,
|
||
attribute.Bool("redis.slow_operation", true),
|
||
)
|
||
c.logger.Warn("Redis慢操作检测",
|
||
zap.String("operation", "set"),
|
||
zap.String("key", c.sanitizeKey(key)),
|
||
zap.Duration("duration", duration),
|
||
zap.String("trace_id", c.tracer.GetTraceID(ctx)),
|
||
)
|
||
}
|
||
|
||
// 处理错误
|
||
if err != nil {
|
||
c.tracer.SetSpanError(span, err)
|
||
c.logger.Error("Redis SET操作失败",
|
||
zap.String("key", c.sanitizeKey(key)),
|
||
zap.Error(err),
|
||
zap.String("trace_id", c.tracer.GetTraceID(ctx)),
|
||
)
|
||
return err
|
||
}
|
||
|
||
c.tracer.SetSpanSuccess(span)
|
||
return nil
|
||
}
|
||
|
||
// Delete 删除缓存
|
||
func (c *TracedRedisCache) Delete(ctx context.Context, keys ...string) error {
|
||
// 开始追踪
|
||
ctx, span := c.tracer.StartCacheSpan(ctx, "delete", strings.Join(keys, ","))
|
||
defer span.End()
|
||
|
||
// 添加基础属性
|
||
c.tracer.AddSpanAttributes(span,
|
||
attribute.String("redis.operation", "delete"),
|
||
attribute.Int("redis.key_count", len(keys)),
|
||
)
|
||
|
||
// 记录开始时间
|
||
startTime := time.Now()
|
||
|
||
// 构建完整键名
|
||
fullKeys := make([]string, len(keys))
|
||
for i, key := range keys {
|
||
fullKeys[i] = c.buildKey(key)
|
||
}
|
||
|
||
// 执行Redis操作
|
||
deleted, err := c.client.Del(ctx, fullKeys...).Result()
|
||
|
||
// 计算执行时间
|
||
duration := time.Since(startTime)
|
||
c.tracer.AddSpanAttributes(span,
|
||
attribute.Int64("redis.duration_ms", duration.Milliseconds()),
|
||
attribute.Int64("redis.deleted_count", deleted),
|
||
)
|
||
|
||
// 处理错误
|
||
if err != nil {
|
||
c.tracer.SetSpanError(span, err)
|
||
c.logger.Error("Redis DELETE操作失败",
|
||
zap.Strings("keys", c.sanitizeKeys(keys)),
|
||
zap.Error(err),
|
||
zap.String("trace_id", c.tracer.GetTraceID(ctx)),
|
||
)
|
||
return err
|
||
}
|
||
|
||
c.tracer.SetSpanSuccess(span)
|
||
return nil
|
||
}
|
||
|
||
// Exists 检查键是否存在
|
||
func (c *TracedRedisCache) Exists(ctx context.Context, key string) (bool, error) {
|
||
// 开始追踪
|
||
ctx, span := c.tracer.StartCacheSpan(ctx, "exists", key)
|
||
defer span.End()
|
||
|
||
// 添加基础属性
|
||
c.addBaseAttributes(span, "exists", key)
|
||
|
||
// 记录开始时间
|
||
startTime := time.Now()
|
||
|
||
// 构建完整键名
|
||
fullKey := c.buildKey(key)
|
||
|
||
// 执行Redis操作
|
||
count, err := c.client.Exists(ctx, fullKey).Result()
|
||
|
||
// 计算执行时间
|
||
duration := time.Since(startTime)
|
||
c.tracer.AddSpanAttributes(span,
|
||
attribute.Int64("redis.duration_ms", duration.Milliseconds()),
|
||
attribute.Bool("redis.exists", count > 0),
|
||
)
|
||
|
||
// 处理错误
|
||
if err != nil {
|
||
c.tracer.SetSpanError(span, err)
|
||
return false, err
|
||
}
|
||
|
||
c.tracer.SetSpanSuccess(span)
|
||
return count > 0, nil
|
||
}
|
||
|
||
// GetMultiple 批量获取(基础实现)
|
||
func (c *TracedRedisCache) GetMultiple(ctx context.Context, keys []string) (map[string]interface{}, error) {
|
||
result := make(map[string]interface{})
|
||
|
||
// 简单实现:逐个获取(实际应用中可以使用MGET优化)
|
||
for _, key := range keys {
|
||
var value interface{}
|
||
if err := c.Get(ctx, key, &value); err == nil {
|
||
result[key] = value
|
||
}
|
||
}
|
||
|
||
return result, nil
|
||
}
|
||
|
||
// SetMultiple 批量设置(基础实现)
|
||
func (c *TracedRedisCache) SetMultiple(ctx context.Context, data map[string]interface{}, ttl ...interface{}) error {
|
||
// 简单实现:逐个设置(实际应用中可以使用pipeline优化)
|
||
for key, value := range data {
|
||
if err := c.Set(ctx, key, value, ttl...); err != nil {
|
||
return err
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// DeletePattern 按模式删除(基础实现)
|
||
func (c *TracedRedisCache) DeletePattern(ctx context.Context, pattern string) error {
|
||
// 这里需要实现模式删除逻辑
|
||
return fmt.Errorf("DeletePattern not implemented")
|
||
}
|
||
|
||
// Keys 获取匹配的键(基础实现)
|
||
func (c *TracedRedisCache) Keys(ctx context.Context, pattern string) ([]string, error) {
|
||
// 这里需要实现键匹配逻辑
|
||
return nil, fmt.Errorf("Keys not implemented")
|
||
}
|
||
|
||
// Stats 获取缓存统计(基础实现)
|
||
func (c *TracedRedisCache) Stats(ctx context.Context) (interfaces.CacheStats, error) {
|
||
return interfaces.CacheStats{}, fmt.Errorf("Stats not implemented")
|
||
}
|
||
|
||
// 辅助方法
|
||
|
||
// addBaseAttributes 添加基础属性
|
||
func (c *TracedRedisCache) addBaseAttributes(span trace.Span, operation, key string) {
|
||
c.tracer.AddSpanAttributes(span,
|
||
attribute.String("redis.operation", operation),
|
||
attribute.String("db.system", "redis"),
|
||
)
|
||
|
||
if c.config.IncludeKeys {
|
||
sanitizedKey := c.sanitizeKey(key)
|
||
if len(sanitizedKey) <= c.config.MaxKeyLength {
|
||
c.tracer.AddSpanAttributes(span,
|
||
attribute.String("redis.key", sanitizedKey),
|
||
)
|
||
}
|
||
}
|
||
}
|
||
|
||
// buildKey 构建完整的Redis键名
|
||
func (c *TracedRedisCache) buildKey(key string) string {
|
||
if c.prefix == "" {
|
||
return key
|
||
}
|
||
return fmt.Sprintf("%s:%s", c.prefix, key)
|
||
}
|
||
|
||
// sanitizeKey 清理键名用于日志记录
|
||
func (c *TracedRedisCache) sanitizeKey(key string) string {
|
||
if len(key) <= c.config.MaxKeyLength {
|
||
return key
|
||
}
|
||
return key[:c.config.MaxKeyLength] + "..."
|
||
}
|
||
|
||
// sanitizeKeys 批量清理键名
|
||
func (c *TracedRedisCache) sanitizeKeys(keys []string) []string {
|
||
result := make([]string, len(keys))
|
||
for i, key := range keys {
|
||
result[i] = c.sanitizeKey(key)
|
||
}
|
||
return result
|
||
}
|
||
|
||
// serialize 序列化值(简单实现)
|
||
func (c *TracedRedisCache) serialize(value interface{}) (string, error) {
|
||
// 这里应该使用JSON或其他序列化方法
|
||
return fmt.Sprintf("%v", value), nil
|
||
}
|
||
|
||
// deserialize 反序列化值(简单实现)
|
||
func (c *TracedRedisCache) deserialize(data string, dest interface{}) error {
|
||
// 这里应该实现真正的反序列化逻辑
|
||
return fmt.Errorf("deserialize not fully implemented")
|
||
}
|