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")
 | ||
| }
 |