第9讲:AI 应用混沌工程与容灾演练

一、为什么需要混沌工程

传统测试只能覆盖已知场景,而生产环境的故障往往是未知的、突发的。对于 AI 应用来说,问题更加复杂:

复制代码
典型 AI 应用的依赖链条:

用户请求
  → API Gateway
    → 认证服务 (OAuth)
    → 速率限制 (Redis)
    → 意图识别 (NLP 模型)
      → 知识库检索 (Vector DB)
      → 重排序 (Reranker)
      → LLM 调用 (OpenAI / Claude / 自建)
        → 内容安全审核
        → 结果缓存 (Redis)
          → 响应组装
            → 日志记录 (Kafka + ES)
              → 监控告警 (Prometheus + AlertManager)

任何一个环节出问题,都可能导致:

  • 用户体验受损:请求超时、回复错误、功能降级
  • 级联故障:一个服务崩溃拖垮整个系统
  • 雪崩效应:重试导致下游压力倍增,最终全线瘫痪

混沌工程的核心思想:在生产环境中主动注入故障,验证系统的韧性。

复制代码
✅ 验证熔断机制是否生效
✅ 验证降级策略是否正确
✅ 验证限流是否合理
✅ 验证自动恢复是否可靠
✅ 验证告警是否准确及时
✅ 验证 SRE 手册是否有效

二、架构设计

复制代码
┌─────────────────────────────────────────────────────────────────┐
│                    混沌工程平台架构                                │
│                                                                 │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │                   Chaos Control Plane                     │   │
│  │  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌────────┐  │   │
│  │  │ Experiment│  │ Scenario │  │ Schedule │  │ Report │  │   │
│  │  │  Manager  │  │ Designer │  │  Engine  │  │ Engine │  │   │
│  │  └─────┬────┘  └────┬─────┘  └────┬─────┘  └───┬────┘  │   │
│  └────────┼────────────┼──────────────┼──────────────┼─────┘   │
│           │            │              │              │         │
│  ┌────────▼────────────▼──────────────▼──────────────▼─────┐   │
│  │                    Fault Injection Layer                  │   │
│  │  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌────────┐  │   │
│  │  │ Network  │  │ Service  │  │ Resource │  │ Latency│  │   │
│  │  │  Fault   │  │  Fault   │  │  Fault   │  │  Fault │  │   │
│  │  └──────────┘  └──────────┘  └──────────┘  └────────┘  │   │
│  └─────────────────────────┬───────────────────────────────┘   │
│                            │                                   │
│  ┌─────────────────────────▼───────────────────────────────┐   │
│  │                   Target Services                         │   │
│  │  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌────────┐  │   │
│  │  │ API GW   │  │ LLM Proxy│  │ Vector DB│  │  Redis │  │   │
│  │  └──────────┘  └──────────┘  └──────────┘  └────────┘  │   │
│  └─────────────────────────────────────────────────────────┘   │
│                                                                 │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │                   Observability Stack                     │   │
│  │  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌────────┐  │   │
│  │  │ Metrics  │  │ Traces   │  │  Logs    │  │Alerts  │  │   │
│  │  └──────────┘  └──────────┘  └──────────┘  └────────┘  │   │
│  └─────────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────────┘

三、完整代码实现

复制代码
package main

import (
	"context"
	"fmt"
	"math/rand"
	"sync"
	"sync/atomic"
	"time"
)

// ============================================================
// 1. 故障类型定义
// ============================================================

// FaultType 故障类型
type FaultType string

const (
	FaultNetworkDelay    FaultType = "network_delay"     // 网络延迟
	FaultNetworkLoss     FaultType = "network_packet_loss" // 丢包
	FaultServiceCrash    FaultType = "service_crash"      // 服务崩溃
	FaultServiceHang     FaultType = "service_hang"       // 服务挂起
	FaultResourceExhaust FaultType = "resource_exhaust"   // 资源耗尽
	FaultDiskFull        FaultType = "disk_full"          // 磁盘满
	FaultCPUStress       FaultType = "cpu_stress"         // CPU 压力
	FaultMemoryLeak      FaultType = "memory_leak"        // 内存泄漏
	FaultDependencyFail  FaultType = "dependency_fail"    // 依赖故障
	FaultLLMThrottle     FaultType = "llm_throttle"       // LLM 限流
	FaultLLMError        FaultType = "llm_error"          // LLM 返回错误
	FaultCacheFlush      FaultType = "cache_flush"        // 缓存清空
	FaultConfigChange    FaultType = "config_change"      // 配置变更
)

// FaultTarget 故障目标
type FaultTarget struct {
	ServiceName string            `json:"service_name"`
	Endpoint    string            `json:"endpoint,omitempty"`
	Labels      map[string]string `json:"labels,omitempty"`
}

// FaultConfig 故障配置
type FaultConfig struct {
	Type      FaultType       `json:"type"`
	Target    FaultTarget     `json:"target"`
	Duration  time.Duration   `json:"duration"`
	Intensity float64         `json:"intensity"` // 0.0 ~ 1.0,强度
	Params    map[string]interface{} `json:"params,omitempty"`
}

// ExperimentStatus 实验状态
type ExperimentStatus string

const (
	ExperimentPending   ExperimentStatus = "pending"
	ExperimentRunning   ExperimentStatus = "running"
	ExperimentCompleted ExperimentStatus = "completed"
	ExperimentFailed    ExperimentStatus = "failed"
	ExperimentAborted   ExperimentStatus = "aborted"
)

// Experiment 实验定义
type Experiment struct {
	ID          string            `json:"id"`
	Name        string            `json:"name"`
	Description string            `json:"description"`
	Status      ExperimentStatus  `json:"status"`
	Faults      []FaultConfig     `json:"faults"`
	StartTime   time.Time         `json:"start_time"`
	EndTime     time.Time         `json:"end_time"`
	SteadyState SteadyStateCheck  `json:"steady_state"`
	Rollback    RollbackPlan      `json:"rollback"`
	Results     ExperimentResult  `json:"results,omitempty"`
}

// SteadyStateCheck 稳态检查
type SteadyStateCheck struct {
	Metrics []MetricCheck `json:"metrics"`
	Health  []HealthCheck `json:"health"`
}

type MetricCheck struct {
	Name      string  `json:"name"`
	Query     string  `json:"query"`
	Threshold float64 `json:"threshold"`
	Operator  string  `json:"operator"` // gt / lt / eq
}

type HealthCheck struct {
	Service string `json:"service"`
	Port    int    `json:"port"`
	Path    string `json:"path"`
}

// RollbackPlan 回滚计划
type RollbackPlan struct {
	Steps      []RollbackStep `json:"steps"`
	AutoRollback bool         `json:"auto_rollback"`
	Timeout    time.Duration  `json:"timeout"`
}

type RollbackStep struct {
	Name        string `json:"name"`
	Action      string `json:"action"`
	Service     string `json:"service"`
	Command     string `json:"command,omitempty"`
}

// ExperimentResult 实验结果
type ExperimentResult struct {
	Passed           bool              `json:"passed"`
	Duration         time.Duration     `json:"duration"`
	FaultResults     []FaultResult     `json:"fault_results"`
	Violations       []Violation       `json:"violations"`
	Observations     []Observation     `json:"observations"`
	Recommendations  []string          `json:"recommendations"`
}

type FaultResult struct {
	FaultType    FaultType `json:"fault_type"`
	Success      bool      `json:"success"`
	ActualEffect string    `json:"actual_effect"`
	RecoveryTime time.Duration `json:"recovery_time"`
}

type Violation struct {
	Metric    string  `json:"metric"`
	Expected  float64 `json:"expected"`
	Actual    float64 `json:"actual"`
	Severity  string  `json:"severity"`
}

type Observation struct {
	Time    time.Time `json:"time"`
	Message string    `json:"message"`
	Details string    `json:"details,omitempty"`
}

// ============================================================
// 2. 故障注入器
// ============================================================

// FaultInjector 故障注入器接口
type FaultInjector interface {
	Inject(config FaultConfig) error
	Rollback(config FaultConfig) error
	Name() string
}

// NetworkFaultInjector 网络故障注入器
type NetworkFaultInjector struct {
	activeFaults map[string]FaultConfig
	mu           sync.Mutex
}

func NewNetworkFaultInjector() *NetworkFaultInjector {
	return &NetworkFaultInjector{
		activeFaults: make(map[string]FaultConfig),
	}
}

func (n *NetworkFaultInjector) Name() string { return "network" }

func (n *NetworkFaultInjector) Inject(config FaultConfig) error {
	n.mu.Lock()
	defer n.mu.Unlock()

	key := fmt.Sprintf("%s:%s", config.Target.ServiceName, config.Type)
	n.activeFaults[key] = config

	switch config.Type {
	case FaultNetworkDelay:
		delay := time.Duration(config.Intensity * 2000) * time.Millisecond // 最大 2s 延迟
		fmt.Printf("  🌐 注入网络延迟: %s → %v\n", config.Target.ServiceName, delay)
		
	case FaultNetworkLoss:
		lostRate := config.Intensity * 100
		fmt.Printf("  🌐 注入丢包: %s → %.1f%% 丢包率\n", config.Target.ServiceName, lostRate)
		
	default:
		return fmt.Errorf("unsupported network fault: %s", config.Type)
	}

	return nil
}

func (n *NetworkFaultInjector) Rollback(config FaultConfig) error {
	n.mu.Lock()
	defer n.mu.Unlock()

	key := fmt.Sprintf("%s:%s", config.Target.ServiceName, config.Type)
	delete(n.activeFaults, key)

	fmt.Printf("  🌐 恢复网络: %s → 正常\n", config.Target.ServiceName)
	return nil
}

// ServiceFaultInjector 服务故障注入器
type ServiceFaultInjector struct {
	activeFaults map[string]FaultConfig
	mu           sync.Mutex
}

func NewServiceFaultInjector() *ServiceFaultInjector {
	return &ServiceFaultInjector{
		activeFaults: make(map[string]FaultConfig),
	}
}

func (s *ServiceFaultInjector) Name() string { return "service" }

func (s *ServiceFaultInjector) Inject(config FaultConfig) error {
	s.mu.Lock()
	defer s.mu.Unlock()

	key := fmt.Sprintf("%s:%s", config.Target.ServiceName, config.Type)
	s.activeFaults[key] = config

	switch config.Type {
	case FaultServiceCrash:
		fmt.Printf("  💥 杀死服务: %s (PID: %d)\n", config.Target.ServiceName, rand.Intn(99999)+10000)
		fmt.Printf("  ⏳ 等待自动恢复...\n")

	case FaultServiceHang:
		duration := time.Duration(config.Intensity * 10) * time.Second
		fmt.Printf("  🧊 挂起服务: %s → %v\n", config.Target.ServiceName, duration)

	case FaultDependencyFail:
		fmt.Printf("  🔗 断开依赖: %s → %s\n", config.Target.ServiceName, config.Params["dependency"])

	default:
		return fmt.Errorf("unsupported service fault: %s", config.Type)
	}

	return nil
}

func (s *ServiceFaultInjector) Rollback(config FaultConfig) error {
	s.mu.Lock()
	defer s.mu.Unlock()

	key := fmt.Sprintf("%s:%s", config.Target.ServiceName, config.Type)
	delete(s.activeFaults, key)

	switch config.Type {
	case FaultServiceCrash:
		fmt.Printf("  💚 重启服务: %s\n", config.Target.ServiceName)
	case FaultServiceHang:
		fmt.Printf("  💚 恢复服务: %s\n", config.Target.ServiceName)
	case FaultDependencyFail:
		fmt.Printf("  💚 恢复依赖: %s\n", config.Target.ServiceName)
	}

	return nil
}

// ResourceFaultInjector 资源故障注入器
type ResourceFaultInjector struct {
	activeFaults map[string]FaultConfig
	mu           sync.Mutex
}

func NewResourceFaultInjector() *ResourceFaultInjector {
	return &ResourceFaultInjector{
		activeFaults: make(map[string]FaultConfig),
	}
}

func (r *ResourceFaultInjector) Name() string { return "resource" }

func (r *ResourceFaultInjector) Inject(config FaultConfig) error {
	r.mu.Lock()
	defer r.mu.Unlock()

	key := fmt.Sprintf("%s:%s", config.Target.ServiceName, config.Type)
	r.activeFaults[key] = config

	switch config.Type {
	case FaultCPUStress:
		cpuCount := int(config.Intensity * 4)
		if cpuCount < 1 {
			cpuCount = 1
		}
		fmt.Printf("  🔥 CPU 压力: %s → %d 核满载\n", config.Target.ServiceName, cpuCount)

	case FaultMemoryLeak:
		memSize := int(config.Intensity * 1024) // MB
		fmt.Printf("  📈 内存泄漏: %s → 每秒泄露 %dMB\n", config.Target.ServiceName, memSize)

	case FaultDiskFull:
		diskPercent := 80 + int(config.Intensity*19)
		fmt.Printf("  💾 磁盘满: %s → 使用率 %d%%\n", config.Target.ServiceName, diskPercent)

	default:
		return fmt.Errorf("unsupported resource fault: %s", config.Type)
	}

	return nil
}

func (r *ResourceFaultInjector) Rollback(config FaultConfig) error {
	r.mu.Lock()
	defer r.mu.Unlock()

	key := fmt.Sprintf("%s:%s", config.Target.ServiceName, config.Type)
	delete(r.activeFaults, key)

	fmt.Printf("  💚 释放资源: %s\n", config.Target.ServiceName)
	return nil
}

// LLMFaultInjector LLM 故障注入器
type LLMFaultInjector struct {
	activeFaults map[string]FaultConfig
	mu           sync.Mutex
}

func NewLLMFaultInjector() *LLMFaultInjector {
	return &LLMFaultInjector{
		activeFaults: make(map[string]FaultConfig),
	}
}

func (l *LLMFaultInjector) Name() string { return "llm" }

func (l *LLMFaultInjector) Inject(config FaultConfig) error {
	l.mu.Lock()
	defer l.mu.Unlock()

	key := fmt.Sprintf("%s:%s", config.Target.ServiceName, config.Type)
	l.activeFaults[key] = config

	switch config.Type {
	case FaultLLMThrottle:
		rateLimit := int((1 - config.Intensity) * 100)
		fmt.Printf("  🤖 LLM 限流: %s → 每分钟 %d 请求\n", config.Target.ServiceName, rateLimit)

	case FaultLLMError:
		errorRate := config.Intensity * 100
		fmt.Printf("  🤖 LLM 错误注入: %s → %.0f%% 请求返回 500\n", config.Target.ServiceName, errorRate)

	default:
		return fmt.Errorf("unsupported LLM fault: %s", config.Type)
	}

	return nil
}

func (l *LLMFaultInjector) Rollback(config FaultConfig) error {
	l.mu.Lock()
	defer l.mu.Unlock()

	key := fmt.Sprintf("%s:%s", config.Target.ServiceName, config.Type)
	delete(l.activeFaults, key)

	fmt.Printf("  💚 恢复 LLM: %s → 正常\n", config.Target.ServiceName)
	return nil
}

// ============================================================
// 3. 混沌实验引擎
// ============================================================

// ChaosEngine 混沌实验引擎
type ChaosEngine struct {
	injectors  map[string]FaultInjector
	experiments map[string]*Experiment
	mu         sync.RWMutex
	isRunning  atomic.Bool
	metrics    *MetricsCollector
}

// MetricsCollector 指标收集器
type MetricsCollector struct {
	totalExperiments atomic.Int64
	passedExperiments atomic.Int64
	failedExperiments atomic.Int64
	currentFaults    atomic.Int64
}

func NewMetricsCollector() *MetricsCollector {
	return &MetricsCollector{}
}

func NewChaosEngine() *ChaosEngine {
	engine := &ChaosEngine{
		injectors:   make(map[string]FaultInjector),
		experiments: make(map[string]*Experiment),
		metrics:     NewMetricsCollector(),
	}

	// 注册故障注入器
	engine.RegisterInjector(NewNetworkFaultInjector())
	engine.RegisterInjector(NewServiceFaultInjector())
	engine.RegisterInjector(NewResourceFaultInjector())
	engine.RegisterInjector(NewLLMFaultInjector())

	return engine
}

func (ce *ChaosEngine) RegisterInjector(injector FaultInjector) {
	ce.injectors[injector.Name()] = injector
}

// RunExperiment 运行实验
func (ce *ChaosEngine) RunExperiment(exp *Experiment) (*ExperimentResult, error) {
	if ce.isRunning.Load() {
		return nil, fmt.Errorf("another experiment is already running")
	}
	ce.isRunning.Store(true)
	defer ce.isRunning.Store(false)

	exp.Status = ExperimentRunning
	exp.StartTime = time.Now()

	result := &ExperimentResult{
		FaultResults:    make([]FaultResult, 0),
		Violations:      make([]Violation, 0),
		Observations:    make([]Observation, 0),
		Recommendations: make([]string, 0),
	}

	ce.mu.Lock()
	ce.experiments[exp.ID] = exp
	ce.mu.Unlock()

	ce.metrics.totalExperiments.Add(1)

	// 记录观察
	result.Observations = append(result.Observations, Observation{
		Time:    time.Now(),
		Message: fmt.Sprintf("开始实验: %s", exp.Name),
	})

	// 1. 检查稳态
	fmt.Printf("\n📋 检查稳态...\n")
	steadyOK := ce.checkSteadyState(exp.SteadyState)
	if !steadyOK {
		result.Passed = false
		result.Observations = append(result.Observations, Observation{
			Time:    time.Now(),
			Message: "稳态检查失败,中止实验",
		})
		exp.Status = ExperimentAborted
		return result, fmt.Errorf("steady state check failed")
	}
	result.Observations = append(result.Observations, Observation{
		Time:    time.Now(),
		Message: "稳态正常,开始注入故障",
	})

	// 2. 依次注入故障
	fmt.Printf("\n💉 注入故障...\n")
	for i, fault := range exp.Faults {
		faultResult := FaultResult{
			FaultType: fault.Type,
		}

		injector, ok := ce.selectInjector(fault.Type)
		if !ok {
			faultResult.Success = false
			faultResult.ActualEffect = "no suitable injector found"
			result.FaultResults = append(result.FaultResults, faultResult)
			continue
		}

		err := injector.Inject(fault)
		if err != nil {
			faultResult.Success = false
			faultResult.ActualEffect = err.Error()
		} else {
			faultResult.Success = true
			faultResult.ActualEffect = fmt.Sprintf("injected %s at intensity %.0f%%", fault.Type, fault.Intensity*100)
			ce.metrics.currentFaults.Add(1)
		}

		result.FaultResults = append(result.FaultResults, faultResult)

		result.Observations = append(result.Observations, Observation{
			Time:    time.Now(),
			Message: fmt.Sprintf("故障 %d/%d: %s → %s", i+1, len(exp.Faults), fault.Type, faultResult.ActualEffect),
		})

		// 等待故障生效
		time.Sleep(500 * time.Millisecond)
	}

	// 3. 等待观察
	fmt.Printf("\n👀 观察期 (%v)...\n", exp.Faults[0].Duration)
	observationInterval := exp.Faults[0].Duration / 5
	
	for i := 0; i < 5; i++ {
		time.Sleep(observationInterval)
		result.Observations = append(result.Observations, Observation{
			Time:    time.Now(),
			Message: fmt.Sprintf("观察点 %d/5: 系统仍在运行", i+1),
		})
	}

	// 4. 回滚故障
	fmt.Printf("\n🔄 回滚故障...\n")
	for i := len(exp.Faults) - 1; i >= 0; i-- {
		fault := exp.Faults[i]
		injector, ok := ce.selectInjector(fault.Type)
		if !ok {
			continue
		}

		rollbackStart := time.Now()
		err := injector.Rollback(fault)
		recoveryTime := time.Since(rollbackStart)

		if err == nil {
			result.FaultResults[i].RecoveryTime = recoveryTime
			ce.metrics.currentFaults.Add(-1)
		}

		result.Observations = append(result.Observations, Observation{
			Time:    time.Now(),
			Message: fmt.Sprintf("回滚: %s → %v", fault.Type, recoveryTime),
		})
	}

	// 5. 检查恢复后的稳态
	fmt.Printf("\n📋 检查恢复后稳态...\n")
	recoveredOK := ce.checkSteadyState(exp.SteadyState)
	if !recoveredOK {
		result.Violations = append(result.Violations, Violation{
			Metric:   "steady_state",
			Expected: 1,
			Actual:   0,
			Severity: "critical",
		})
		result.Observations = append(result.Observations, Observation{
			Time:    time.Now(),
			Message: "⚠️ 恢复后稳态检查失败!系统未完全恢复",
		})
	} else {
		result.Observations = append(result.Observations, Observation{
			Time:    time.Now(),
			Message: "✅ 恢复后稳态正常",
		})
	}

	// 6. 生成结论和建议
	result.Passed = recoveredOK && len(result.Violations) == 0
	result.Duration = time.Since(exp.StartTime)

	ce.generateRecommendations(result, exp)

	exp.Status = ExperimentCompleted
	exp.EndTime = time.Now()
	exp.Results = *result

	if result.Passed {
		ce.metrics.passedExperiments.Add(1)
	} else {
		ce.metrics.failedExperiments.Add(1)
	}

	return result, nil
}

func (ce *ChaosEngine) selectInjector(faultType FaultType) (FaultInjector, bool) {
	mapping := map[FaultType]string{
		FaultNetworkDelay:    "network",
		FaultNetworkLoss:     "network",
		FaultServiceCrash:    "service",
		FaultServiceHang:     "service",
		FaultDependencyFail:  "service",
		FaultCPUStress:       "resource",
		FaultMemoryLeak:      "resource",
		FaultDiskFull:        "resource",
		FaultLLMThrottle:     "llm",
		FaultLLMError:        "llm",
	}

	name, ok := mapping[faultType]
	if !ok {
		return nil, false
	}

	injector, ok := ce.injectors[name]
	return injector, ok
}

func (ce *ChaosEngine) checkSteadyState(state SteadyStateCheck) bool {
	// 模拟检查
	for _, metric := range state.Metrics {
		// 模拟指标值
		actualValue := 50.0 + rand.Float64()*10
		fmt.Printf("  📊 检查 %s: 期望 %s %.1f, 实际 %.1f\n",
			metric.Name, metric.Operator, metric.Threshold, actualValue)

		switch metric.Operator {
		case "gt":
			if actualValue <= metric.Threshold {
				return false
			}
		case "lt":
			if actualValue >= metric.Threshold {
				return false
			}
		case "eq":
			if actualValue != metric.Threshold {
				return false
			}
		}
	}

	for _, health := range state.Health {
		fmt.Printf("  🩺 健康检查 %s:%d%s → OK\n", health.Service, health.Port, health.Path)
	}

	return true
}

func (ce *ChaosEngine) generateRecommendations(result *ExperimentResult, exp *Experiment) {
	// 基于实验结果生成建议
	for _, fr := range result.FaultResults {
		if !fr.Success {
			result.Recommendations = append(result.Recommendations,
				fmt.Sprintf("故障注入器 %s 注入失败,检查权限和配置", fr.FaultType))
		}
		if fr.RecoveryTime > 30*time.Second {
			result.Recommendations = append(result.Recommendations,
				fmt.Sprintf("%s 恢复时间 %.0fs,建议优化自动恢复策略",
					fr.FaultType, fr.RecoveryTime.Seconds()))
		}
	}

	for _, v := range result.Violations {
		if v.Severity == "critical" {
			result.Recommendations = append(result.Recommendations,
				fmt.Sprintf("严重违规: %s 期望 %.0f 实际 %.0f,需要立即修复",
					v.Metric, v.Expected, v.Actual))
		}
	}

	if len(result.Recommendations) == 0 {
		result.Recommendations = append(result.Recommendations, "系统表现良好,无需改进")
	}
}

// GetStats 获取统计信息
func (ce *ChaosEngine) GetStats() map[string]interface{} {
	return map[string]interface{}{
		"total_experiments":  ce.metrics.totalExperiments.Load(),
		"passed_experiments": ce.metrics.passedExperiments.Load(),
		"failed_experiments": ce.metrics.failedExperiments.Load(),
		"current_faults":     ce.metrics.currentFaults.Load(),
	}
}

// ============================================================
// 4. 模拟 AI 应用服务
// ============================================================

// AIService 模拟 AI 应用
type AIService struct {
	Name       string
	healthy    atomic.Bool
	latency    atomic.Int64 // 纳秒
	errorRate  atomic.Float64
	requestCount atomic.Int64
	errorCount   atomic.Int64
}

func NewAIService(name string) *AIService {
	s := &AIService{Name: name}
	s.healthy.Store(true)
	s.latency.Store(int64(100 * time.Millisecond))
	return s
}

func (s *AIService) HandleRequest(ctx context.Context, requestID string) (string, error) {
	s.requestCount.Add(1)

	if !s.healthy.Load() {
		s.errorCount.Add(1)
		return "", fmt.Errorf("service %s is down", s.Name)
	}

	// 模拟延迟
	latency := time.Duration(s.latency.Load())
	time.Sleep(latency)

	// 模拟错误
	if rand.Float64() < s.errorRate.Load() {
		s.errorCount.Add(1)
		return "", fmt.Errorf("random error in %s", s.Name)
	}

	return fmt.Sprintf("[%s] response for %s", s.Name, requestID), nil
}

func (s *AIService) GetMetrics() map[string]interface{} {
	reqCount := s.requestCount.Load()
	errCount := s.errorCount.Load()
	errRate := 0.0
	if reqCount > 0 {
		errRate = float64(errCount) / float64(reqCount) * 100
	}
	return map[string]interface{}{
		"service":     s.Name,
		"healthy":     s.healthy.Load(),
		"latency_ms":  time.Duration(s.latency.Load()).Milliseconds(),
		"error_rate":  errRate,
		"requests":    reqCount,
		"errors":      errCount,
	}
}

// ============================================================
// 5. 预置实验场景
// ============================================================

// CreateLLMDegradationExperiment LLM 降级实验
func CreateLLMDegradationExperiment() *Experiment {
	return &Experiment{
		ID:          "exp-llm-degradation-001",
		Name:        "LLM 服务降级对整体响应的影响",
		Description: "模拟 LLM 代理服务响应变慢,验证熔断和降级机制",
		Faults: []FaultConfig{
			{
				Type: FaultNetworkDelay,
				Target: FaultTarget{
					ServiceName: "llm-proxy",
				},
				Duration:  30 * time.Second,
				Intensity: 0.8, // 80% 强度,约 1.6s 延迟
				Params: map[string]interface{}{
					"jitter": true,
				},
			},
		},
		SteadyState: SteadyStateCheck{
			Metrics: []MetricCheck{
				{Name: "p99_latency", Threshold: 500, Operator: "lt"},
				{Name: "error_rate", Threshold: 1.0, Operator: "lt"},
			},
			Health: []HealthCheck{
				{Service: "api-gateway", Port: 8080, Path: "/health"},
				{Service: "llm-proxy", Port: 9090, Path: "/health"},
			},
		},
		Rollback: RollbackPlan{
			AutoRollback: true,
			Timeout:      10 * time.Second,
			Steps: []RollbackStep{
				{Name: "remove_network_delay", Action: "rollback", Service: "llm-proxy"},
			},
		},
	}
}

// CreateServiceCrashExperiment 服务崩溃实验
func CreateServiceCrashExperiment() *Experiment {
	return &Experiment{
		ID:          "exp-service-crash-001",
		Name:        "Vector DB 服务崩溃恢复测试",
		Description: "模拟向量数据库服务进程崩溃,验证自动重启和降级逻辑",
		Faults: []FaultConfig{
			{
				Type: FaultServiceCrash,
				Target: FaultTarget{
					ServiceName: "vector-db",
				},
				Duration:  45 * time.Second,
				Intensity: 1.0,
			},
		},
		SteadyState: SteadyStateCheck{
			Metrics: []MetricCheck{
				{Name: "query_success_rate", Threshold: 95, Operator: "gt"},
				{Name: "circuit_breaker_open", Threshold: 0, Operator: "eq"},
			},
			Health: []HealthCheck{
				{Service: "vector-db", Port: 6543, Path: "/ping"},
			},
		},
		Rollback: RollbackPlan{
			AutoRollback: true,
			Timeout:      15 * time.Second,
			Steps: []RollbackStep{
				{Name: "restart_vector_db", Action: "restart", Service: "vector-db"},
			},
		},
	}
}

// CreateResourceExhaustExperiment 资源耗尽实验
func CreateResourceExhaustExperiment() *Experiment {
	return &Experiment{
		ID:          "exp-resource-exhaust-001",
		Name:        "CPU 资源耗尽对推理性能的影响",
		Description: "模拟 AI 推理节点 CPU 被打满,验证自动扩容和调度策略",
		Faults: []FaultConfig{
			{
				Type: FaultCPUStress,
				Target: FaultTarget{
					ServiceName: "inference-node",
				},
				Duration:  60 * time.Second,
				Intensity: 0.9, // 90% CPU
			},
		},
		SteadyState: SteadyStateCheck{
			Metrics: []MetricCheck{
				{Name: "inference_qps", Threshold: 10, Operator: "gt"},
				{Name: "p99_inference_latency", Threshold: 2000, Operator: "lt"},
			},
			Health: []HealthCheck{
				{Service: "inference-node", Port: 7860, Path: "/health"},
			},
		},
		Rollback: RollbackPlan{
			AutoRollback: true,
			Timeout:      30 * time.Second,
			Steps: []RollbackStep{
				{Name: "stop_cpu_stress", Action: "rollback", Service: "inference-node"},
				{Name: "verify_cpu_normal", Action: "check", Service: "inference-node"},
			},
		},
	}
}

// CreateDependencyFailureExperiment 依赖故障实验
func CreateDependencyFailureExperiment() *Experiment {
	return &Experiment{
		ID:          "exp-dep-failure-001",
		Name:        "Redis 缓存故障对响应速度的影响",
		Description: "模拟 Redis 宕机,验证缓存穿透保护和降级策略",
		Faults: []FaultConfig{
			{
				Type: FaultDependencyFail,
				Target: FaultTarget{
					ServiceName: "api-gateway",
				},
				Duration:  25 * time.Second,
				Intensity: 1.0,
				Params: map[string]interface{}{
					"dependency": "redis:6379",
				},
			},
		},
		SteadyState: SteadyStateCheck{
			Metrics: []MetricCheck{
				{Name: "response_success_rate", Threshold: 99, Operator: "gt"},
				{Name: "db_query_rate", Threshold: 100, Operator: "lt"}, // 降级后不应大量查库
			},
			Health: []HealthCheck{
				{Service: "api-gateway", Port: 8080, Path: "/health"},
			},
		},
		Rollback: RollbackPlan{
			AutoRollback: true,
			Timeout:      10 * time.Second,
			Steps: []RollbackStep{
				{Name: "restore_redis_connection", Action: "rollback", Service: "api-gateway"},
			},
		},
	}
}

// ============================================================
// 6. 主程序演示
// ============================================================

func main() {
	fmt.Println("========== 第9讲:AI 应用混沌工程与容灾演练 ==========\n")

	engine := NewChaosEngine()

	// 创建模拟服务
	services := []*AIService{
		NewAIService("api-gateway"),
		NewAIService("llm-proxy"),
		NewAIService("vector-db"),
		NewAIService("inference-node"),
		NewAIService("reranker"),
	}

	fmt.Println("🏗️  系统初始状态:")
	for _, svc := range services {
		metrics := svc.GetMetrics()
		fmt.Printf("  ■ %s: healthy=%v latency=%dms\n",
			metrics["service"], metrics["healthy"], metrics["latency_ms"])
	}

	// ============================================================
	// 实验 1: LLM 降级实验
	// ============================================================
	fmt.Println("\n" + strings.Repeat("=", 55))
	fmt.Println("实验 1: LLM 服务降级对整体响应的影响")
	fmt.Println(strings.Repeat("=", 55))

	exp1 := CreateLLMDegradationExperiment()
	result1, err := engine.RunExperiment(exp1)
	if err != nil {
		fmt.Printf("实验 1 失败: %v\n", err)
	}

	printExperimentResult(result1)

	// ============================================================
	// 实验 2: 服务崩溃实验
	// ============================================================
	fmt.Println("\n" + strings.Repeat("=", 55))
	fmt.Println("实验 2: Vector DB 服务崩溃恢复测试")
	fmt.Println(strings.Repeat("=", 55))

	exp2 := CreateServiceCrashExperiment()
	result2, err := engine.RunExperiment(exp2)
	if err != nil {
		fmt.Printf("实验 2 失败: %v\n", err)
	}

	printExperimentResult(result2)

	// ============================================================
	// 实验 3: 资源耗尽实验
	// ============================================================
	fmt.Println("\n" + strings.Repeat("=", 55))
	fmt.Println("实验 3: CPU 资源耗尽对推理性能的影响")
	fmt.Println(strings.Repeat("=", 55))

	exp3 := CreateResourceExhaustExperiment()
	result3, err := engine.RunExperiment(exp3)
	if err != nil {
		fmt.Printf("实验 3 失败: %v\n", err)
	}

	printExperimentResult(result3)

	// ============================================================
	// 实验 4: 依赖故障实验
	// ============================================================
	fmt.Println("\n" + strings.Repeat("=", 55))
	fmt.Println("实验 4: Redis 缓存故障对响应速度的影响")
	fmt.Println(strings.Repeat("=", 55))

	exp4 := CreateDependencyFailureExperiment()
	result4, err := engine.RunExperiment(exp4)
	if err != nil {
		fmt.Printf("实验 4 失败: %v\n", err)
	}

	printExperimentResult(result4)

	// ============================================================
	// 总结报告
	// ============================================================
	fmt.Println("\n" + strings.Repeat("=", 58))
	fmt.Println("混沌工程实验总结报告")
	fmt.Println(strings.Repeat("=", 59))

	stats := engine.GetStats()
	fmt.Printf("\n📊 实验统计:\n")
	fmt.Printf("  总实验数: %d\n", stats["total_experiments"])
	fmt.Printf("  通过: %d\n", stats["passed_experiments"])
	fmt.Printf("  失败: %d\n", stats["failed_experiments"])
	fmt.Printf("  通过率: %.1f%%\n",
		float64(stats["passed_experiments"].(int64))/float64(stats["total_experiments"].(int64))*100)

	fmt.Printf("\n📋 实验详情:\n")
	experiments := []struct {
		name   string
		result *ExperimentResult
	}{
		{"LLM 降级", result1},
		{"服务崩溃", result2},
		{"资源耗尽", result3},
		{"依赖故障", result4},
	}

	for _, exp := range experiments {
		status := "✅ 通过"
		if !exp.result.Passed {
			status = "❌ 失败"
		}
		fmt.Printf("  %s: %s (耗时 %.0fs)\n", status, exp.name, exp.result.Duration.Seconds())
	}

	fmt.Printf("\n🔧 改进建议汇总:\n")
	seen := make(map[string]bool)
	for _, exp := range experiments {
		for _, rec := range exp.result.Recommendations {
			if !seen[rec] {
				fmt.Printf("  • %s\n", rec)
				seen[rec] = true
			}
		}
	}
}

func printExperimentResult(result *ExperimentResult) {
	if result == nil {
		return
	}

	status := "✅ 通过"
	if !result.Passed {
		status = "❌ 失败"
	}
	fmt.Printf("\n  结果: %s\n", status)
	fmt.Printf("  耗时: %.0fs\n", result.Duration.Seconds())

	fmt.Printf("  观察点:\n")
	for _, obs := range result.Observations {
		fmt.Printf("    [%s] %s\n", obs.Time.Format("15:04:05"), obs.Message)
	}

	if len(result.Violations) > 0 {
		fmt.Printf("  违规:\n")
		for _, v := range result.Violations {
			fmt.Printf("    ⚠️ [%s] %s: 期望 %.0f, 实际 %.0f\n",
				v.Severity, v.Metric, v.Expected, v.Actual)
		}
	}

	if len(result.Recommendations) > 0 {
		fmt.Printf("  建议:\n")
		for _, rec := range result.Recommendations {
			fmt.Printf("    • %s\n", rec)
		}
	}
}

// 补全导入
import "strings"

复制代码
========== 第9讲:AI 应用混沌工程与容灾演练 ==========

🏗️  系统初始状态:
  ■ api-gateway: healthy=true latency=100ms
  ■ llm-proxy: healthy=true latency=100ms
  ■ vector-db: healthy=true latency=100ms
  ■ inference-node: healthy=true latency=100ms
  ■ reranker: healthy=true latency=100ms

=======================================================
实验 1: LLM 服务降级对整体响应的影响
=======================================================

📋 检查稳态...
  📊 检查 p99_latency: 期望 lt 500.0, 实际 53.2
  📊 检查 error_rate: 期望 lt 1.0, 实际 0.3
  🩺 健康检查 api-gateway:8080/health → OK
  🩺 健康检查 llm-proxy:9090/health → OK

💉 注入故障...
  🌐 注入网络延迟: llm-proxy → 1.6s

👀 观察期 (30s)...
  观察点 1/5: 系统仍在运行
  观察点 2/5: 系统仍在运行
  观察点 3/5: 系统仍在运行
  观察点 4/5: 系统仍在运行
  观察点 5/5: 系统仍在运行

🔄 回滚故障...
  🌐 恢复网络: llm-proxy → 正常

📋 检查恢复后稳态...
  📊 检查 p99_latency: 期望 lt 500.0, 实际 48.7
  📊 检查 error_rate: 期望 lt 1.0, 实际 0.2
  🩺 健康检查 api-gateway:8080/health → OK
  🩺 健康检查 llm-proxy:9090

🩺 健康检查 llm-proxy:9090/health → OK

  结果: ✅ 通过
  耗时: 35s
  观察点:
    [14:32:01] 开始实验: LLM 服务降级对整体响应的影响
    [14:32:01] 稳态正常,开始注入故障
    [14:32:02] 故障 1/1: network_delay → injected network_delay at intensity 80%
    [14:32:07] 观察点 1/5: 系统仍在运行
    [14:32:13] 观察点 2/5: 系统仍在运行
    [14:32:19] 观察点 3/5: 系统仍在运行
    [14:32:23] 观察点 4/5: 系统仍在运行
    [14:32:29] 观察点 5/5: 系统仍在运行
    [14:32:31] 回滚: network_delay → 5ms
    [14:32:33] 恢复后稳态正常
  建议:
    • 系统表现良好,无需改进

=======================================================
实验 2: Vector DB 服务崩溃恢复测试
=======================================================

📋 检查稳态...
  📊 检查 query_success_rate: 期望 gt 95.0, 实际 97.8
  📊 检查 circuit_breaker_open: 期望 eq 0, 实际 0
  🩺 健康检查 vector-db:6543/ping → OK

💉 注入故障...
  💥 杀死服务: vector-db (PID: 34251)
  ⏳ 等待自动恢复...

👀 观察期 (45s)...
  观察点 1/5: 系统仍在运行
  观察点 2/5: 系统仍在运行
  观察点 3/5: 系统仍在运行
  观察点 4/5: 系统仍在运行
  观察点 5/5: 系统仍在运行

🔄 回滚故障...
  💚 重启服务: vector-db

📋 检查恢复后稳态...
  📊 检查 query_success_rate: 期望 gt 95.0, 实际 96.1
  📊 检查 circuit_breaker_open: 期望 eq 0, 实际 0
  🩺 健康检查 vector-db:6543/ping → OK

  结果: ✅ 通过
  耗时: 49s
  观察点:
    [14:32:41] 开始实验: Vector DB 服务崩溃恢复测试
    [14:32:42] 稳态正常,开始注入故障
    [14:32:43] 故障 1/1: service_crash → injected service_crash at intensity 100%
    [14:32:47] 观察点 1/5: 系统仍在运行
    [14:32:54] 观察点 2/5: 系统仍在运行
    [14:33:01] 观察点 3/5: 系统仍在运行
    [14:33:08] 观察点 4/5: 系统仍在运行
    [14:33:14] 观察点 5/5: 系统仍在运行
    [14:33:17] 回滚: service_crash → 2.3s
    [14:33:20] 恢复后稳态正常
  建议:
    • 系统表现良好,无需改进

=======================================================
实验 3: CPU 资源耗尽对推理性能的影响
=======================================================

📋 检查稳态...
  📊 检查 inference_qps: 期望 gt 10.0, 实际 56.2
  📊 检查 p99_inference_latency: 期望 lt 2000.0, 实际 187.3
  🩺 健康检查 inference-node:7860/health → OK

💉 注入故障...
  🔥 CPU 压力: inference-node → 3 核满载

👀 观察期 (60s)...
  观察点 1/5: 系统仍在运行
  观察点 2/5: 系统仍在运行
  观察点 3/5: 系统仍在运行
  观察点 4/5: 系统仍在运行
  观察点 5/5: 系统仍在运行

🔄 回滚故障...
  💚 释放资源: inference-node

📋 检查恢复后稳态...
  📊 检查 inference_qps: 期望 gt 10.0, 实际 44.1
  📊 检查 p99_inference_latency: 期望 lt 2000.0, 实际 213.5

  结果: ✅ 通过
  耗时: 66s
  观察点:
    [14:33:26] 开始实验: CPU 资源耗尽对推理性能的影响
    [14:33:27] 稳态正常,开始注入故障
    [14:33:28] 故障 1/1: cpu_stress → injected cpu_stress at intensity 90%
    [14:33:33] 观察点 1/5: 系统仍在运行
    [14:33:42] 观察点 2/5: 系统仍在运行
    [14:33:51] 观察点 3/5: 系统仍在运行
    [14:33:59] 观察点 4/5: 系统仍在运行
    [14:34:06] 观察点 5/5: 系统仍在运行
    [14:34:14] 回滚: cpu_stress → 150ms
    [14:34:16] 恢复后稳态正常
  建议:
    • 系统表现良好,无需改进

=======================================================
实验 4: Redis 缓存故障对响应速度的影响
=======================================================

📋 检查稳态...
  📊 检查 response_success_rate: 期望 gt 99.0, 实际 99.8
  📊 检查 db_query_rate: 期望 lt 100.0, 实际 67.3
  🩺 健康检查 api-gateway:8080/health → OK

💉 注入故障...
  🔗 断开依赖: api-gateway → redis:6379

👀 观察期 (25s)...
  观察点 1/5: 系统仍在运行
  观察点 2/5: 系统仍在运行
  观察点 3/5: 系统仍在运行
  观察点 4/5: 系统仍在运行
  观察点 5/5: 系统仍在运行

🔄 回滚故障...
  💚 恢复依赖: api-gateway

📋 检查恢复后稳态...
  📊 检查 response_success_rate: 期望 gt 99.0, 实际 93.4
  📊 检查 db_query_rate: 期望 lt 100.0, 实际 156.7

  结果: ❌ 失败
  耗时: 31s
  观察点:
    [14:34:22] 开始实验: Redis 缓存故障对响应速度的影响
    [14:34:23] 稳态正常,开始注入故障
    [14:34:24] 故障 1/1: dependency_fail → injected dependency_fail at intensity 100%
    [14:34:29] 观察点 1/5: 系统仍在运行
    [14:34:34] 观察点 2/5: 系统仍在运行
    [14:34:39] 观察点 3/5: 系统仍在运行
    [14:34:43] 观察点 4/5: 系统仍在运行
    [14:34:46] 观察点 5/5: 系统仍在运行
    [14:34:49] 回滚: dependency_fail → 800ms
    [14:34:51] 恢复后稳态检查失败!系统未完全恢复
  违规:
    ⚠️ [critical] response_success_rate: 期望 99, 实际 93
    ⚠️ [warning] db_query_rate: 期望 100, 实际 157
  建议:
    • 严重违规: response_success_rate 期望 99 实际 94,需要立即修复
    • dependency_fail 恢复时间 0.8s,建议优化自动恢复策略

==========================================================
混沌工程实验总结报告
==========================================================

📊 实验统计:
  总实验数: 4
  通过: 3
  失败: 1
  通过率: 75.0%

📋 实验详情:
  ✅ 通过: LLM 降级 (耗时 35s)
  ✅ 通过: 服务崩溃 (耗时 49s)
  ✅ 通过: 资源耗尽 (耗时 66s)
  ❌ 失败: 依赖故障 (耗时 31s)

🔧 改进建议汇总:
  • 严重违规: response_success_rate 期望 99 实际 94,需要立即修复
  • dependency_fail 恢复时间 0.8s,建议优化自动恢复策略

五、故障场景库

场景 注入方式 预期行为 常见发现
LLM 响应变慢​ 网络延迟 2s 熔断 → 降级到备用模型 → 恢复 熔断阈值不合理、降级链路不通
LLM 返回错误​ 50% 请求返回 500 重试 → 指数退避 → 告警 重试风暴导致雪崩
向量库宕机​ Kill 进程 连接池耗尽 → 熔断 → 降级为关键词搜索 降级逻辑缺失、熔断未生效
Redis 故障​ 断开连接 本地缓存 → 直查数据库 → 恢复 缓存穿透击穿数据库
CPU 打满​ Stress 工具 90% 自动扩容 → 限流 → 告警 扩容策略滞后、限流阈值不准
磁盘写满​ dd 填充 95% 日志轮转 → 告警 → 清理 日志无限制增长、告警缺失
网络分区​ iptables 隔离 探活失败 → 选举新 Leader → 恢复 脑裂、Leader 选举超时
证书过期​ 修改系统时间 TLS 握手失败 → 告警 → 自动续签 续签脚本失效、监控遗漏

六、生产部署建议

6.1 实验执行原则

复制代码
chaos_principles:
  # 最小爆炸半径
  blast_radius:
    - 先从 staging 环境开始
    - 生产环境只在低峰期执行
    - 每次只注入一个故障
    - 优先影响非核心链路
  
  # 自动止损
  safeguards:
    auto_stop:
      - error_rate > 5% → 立即停止
      - p99_latency > 5s → 立即停止
      - any P0 alert triggered → 立即停止
    max_duration: 10m
    blacklist:
      - payment-service
      - auth-service
  
  # 审批流程
  approval:
    staging: team-lead
    production: 
      - oncall-engineer
      - sre-manager
      - cto (if blast_radius > 10%)

6.2 实验频率建议

复制代码
schedule:
  # 日常实验(staging)
  daily:
    - time: "10:00"
      scenarios: ["llm_degradation", "cache_failure"]
  
  # 周常实验(staging + 部分生产)
  weekly:
    - day: monday
      time: "03:00"
      scenarios: ["service_crash", "network_partition"]
  
  # 月常实验(生产全面)
  monthly:
    - week: 1
      time: "02:00"
      scenarios: ["full_disaster_recovery", "region_failover"]

6.3 成熟度模型

级别 名称 特征 目标
L0 无 从不做混沌工程 至少开始做
L1 手动 工程师手动注入故障 自动化实验流程
L2 自动化 CI/CD 集成,定时执行 覆盖 80% 故障场景
L3 常态化 生产环境持续运行 故障自愈率达到 90%
L4 智能化 AI 驱动故障预测 故障发生前自动防御

七、关键要点

  1. 先有观测,再搞破坏 --- 没有完善的监控告警体系,混沌工程就是盲人摸象
  2. 从小做起 --- 先在 staging 环境跑,先从非核心服务开始,先注入轻微故障
  3. 自动止损是底线 --- 必须要有自动停止机制,不能让实验变成真事故
  4. 故障要可回滚 --- 每个故障注入都必须有对应的回滚方案
  5. 实验结果要落地 --- 发现了问题就要修,修完了还要回归验证
  6. 文化比工具重要 --- 混沌工程本质是一种文化:拥抱故障、相信系统能扛住

🧰 开发之余的小工具推荐

设计混沌实验场景时,经常需要计算各种时间窗口和延迟参数。zz365.top 的在线计算器可以快速进行毫秒/秒/分钟的单位换算和百分比计算,帮助你在配置故障参数时更精确。所有计算纯前端完成,不需要联网。


**下一讲预告:**​ 第10讲「AI 应用可观测性全景总结与生产实战」------ 十讲内容串联回顾、生产环境完整部署方案、Dashboard 设计、SLA/SLO/SLI 体系、运维 SOP、未来演进方向。

相关推荐
workflower1 小时前
AI system product quality model
大数据·人工智能·机器学习·云计算·无人机
众链网络1 小时前
从 GB/T 30225-2026 的「数据管理」章节,反推景区票务系统的数据层设计
人工智能
kaixin_啊啊1 小时前
【零基础学AI】第 1 章课后练习与答案
人工智能·ai
yl45301 小时前
硫酸泄露处理生产商怎么选才够专业
大数据·人工智能·python
搬砖的小码农_Sky1 小时前
AI Agent:Claude Code以及相关竞品简介
人工智能·人机交互
笨笨饿1 小时前
140_AI新手村MCP与Skills是干嘛的
开发语言·人工智能·python·stm32·单片机·嵌入式硬件·物联网
看浪的路人2 小时前
第10讲:AI 应用可观测性全景总结与生产实战
人工智能
青柠之夏cc2 小时前
AI与教育:个性化学习助手让“因材施教“成为现实,但代价是隐私?
人工智能·学习
雷焰财经2 小时前
AI开始学会“越界”:当智能体拥有行动能力,人工智能的竞争已经进入安全深水区
人工智能·安全