Arthas接入MCP:让AI替你24小时盯盘,线上问题秒级根因定位

0 阅读6分钟

大家好,我是小悟。

一、需求描述

1.1 业务背景

Arthas作为APM(应用性能监控)系统已接入MCP(Model Context Protocol),需要构建一个智能化的线上问题排查系统,实现以下目标:

  • 自动感知异常:实时捕获系统异常、慢请求、资源瓶颈
  • 智能根因分析:利用AI自动分析日志、链路、指标的多维数据
  • 交互式排查:运维人员可通过自然语言与AI交互,快速定位问题
  • 自动化修复建议:AI提供可执行的修复方案或自动触发预案

1.2 核心痛点

  • 传统监控告警多、噪音大,根因定位耗时
  • 多数据源(日志、链路、指标)割裂,人工关联分析效率低
  • 夜间/节假日突发问题响应慢

1.3 期望效果

  • 异常发生 → 30秒内AI完成初步分析 → 输出根因+建议 → 支持自然语言追问

二、整体架构设计

┌─────────────────────────────────────────────────────────────┐
                         用户层                                
   ┌──────────┐  ┌──────────┐  ┌──────────┐                  
    Web UI      钉钉/企微     CLI                       
   └────┬─────┘  └────┬─────┘  └────┬─────┘                  
└────────┼────────────┼────────────┼─────────────────────────┘
                                 
                                 
┌─────────────────────────────────────────────────────────────┐
                      MCP Server (Arthas集成层)                
  ┌──────────────────────────────────────────────────────┐   
    Tool Registry: get_metrics / query_logs / trace        
    Resource Provider: 实时指标流 / 告警事件流               
    Prompt Templates: 根因分析 / 慢查询优化                  
  └──────────────────────────────────────────────────────┘   
└─────────────────────────────┬───────────────────────────────┘
                               MCP Protocol
                              
┌─────────────────────────────────────────────────────────────┐
                      AI Agent (LLM)                          
            ┌─────────────┐    ┌─────────────┐               
              GPT-4/Claude│     本地Qwen                   
            └─────────────┘    └─────────────┘               
└─────────────────────────────────────────────────────────────┘
                              
                              
┌─────────────────────────────────────────────────────────────┐
                    Arthas 数据平台                            
  ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐              
   日志     链路     指标     事件                 
  │ELK/ Loki│ │Jaeger  │Prometheus│  Alert               
  └────────┘ └────────┘ └────────┘ └────────┘              
└─────────────────────────────────────────────────────────────┘

三、详细实现步骤

步骤1:MCP Server核心实现(Java)

1.1 MCP协议定义

// MCP协议消息模型
public class MCPMessage {
    private String id;
    private String method;  // "tools/call", "resources/read"
    private Map<String, Object> params;
    private String jsonrpc = "2.0";
}

// Tool定义
public class MCPTool {
    private String name;
    private String description;
    private Map<String, Object> inputSchema;
    
    // 示例:获取指标工具
    public static MCPTool metricsTool() {
        MCPTool tool = new MCPTool();
        tool.setName("get_metrics");
        tool.setDescription("获取指定时间范围内的监控指标");
        
        Map<String, Object> schema = new HashMap<>();
        schema.put("type", "object");
        Map<String, Object> properties = new HashMap<>();
        properties.put("metric_name", Map.of("type", "string"));
        properties.put("start_time", Map.of("type", "string"));
        properties.put("end_time", Map.of("type", "string"));
        schema.put("properties", properties);
        tool.setInputSchema(schema);
        return tool;
    }
}

1.2 MCP Server主类

@SpringBootApplication
public class ArthasMCPServer {
    public static void main(String[] args) {
        SpringApplication.run(ArthasMCPServer.class, args);
    }
}

@RestController
@RequestMapping("/mcp")
public class MCPController {
    
    @Autowired
    private LogQueryService logService;
    @Autowired
    private MetricsQueryService metricsService;
    @Autowired
    private TraceService traceService;
    
    // MCP协议端点
    @PostMapping("/v1/messages")
    public ResponseEntity<MCPMessage> handleMessage(@RequestBody MCPMessage message) {
        switch (message.getMethod()) {
            case "tools/list":
                return ResponseEntity.ok(listTools());
            case "tools/call":
                return ResponseEntity.ok(callTool(message.getParams()));
            case "resources/read":
                return ResponseEntity.ok(readResource(message.getParams()));
            default:
                return ResponseEntity.badRequest().build();
        }
    }
    
    private MCPMessage listTools() {
        List<MCPTool> tools = Arrays.asList(
            createMetricsTool(),
            createLogTool(),
            createTraceTool(),
            createSlowQueryTool()
        );
        MCPMessage response = new MCPMessage();
        response.setMethod("tools/list");
        response.setParams(Map.of("tools", tools));
        return response;
    }
    
    private MCPMessage callTool(Map<String, Object> params) {
        String toolName = (String) params.get("name");
        Map<String, Object> args = (Map<String, Object>) params.get("arguments");
        
        Object result;
        switch (toolName) {
            case "get_metrics":
                result = metricsService.queryMetrics(args);
                break;
            case "query_logs":
                result = logService.queryLogs(args);
                break;
            case "get_trace":
                result = traceService.getTraceDetail(args);
                break;
            default:
                result = Map.of("error", "Unknown tool");
        }
        
        MCPMessage response = new MCPMessage();
        response.setMethod("tools/call");
        response.setParams(Map.of("result", result));
        return response;
    }
}

1.3 数据查询服务实现

@Service
public class LogQueryService {
    
    @Autowired
    private RestTemplate restTemplate;
    
    public Map<String, Object> queryLogs(Map<String, Object> args) {
        String serviceName = (String) args.get("service_name");
        String level = (String) args.getOrDefault("level", "ERROR");
        Long startTime = (Long) args.get("start_time");
        Long endTime = (Long) args.get("end_time");
        String keyword = (String) args.get("keyword");
        
        // 构建Loki/ES查询
        String query = String.format(
            "{service=\"%s\", level=\"%s\"} |~ \"%s\"",
            serviceName, level, keyword
        );
        
        // 实际调用日志存储
        List<LogEntry> logs = fetchLogsFromES(query, startTime, endTime);
        
        // 提取关键特征
        Map<String, Object> analysis = analyzeLogPattern(logs);
        
        return Map.of(
            "total", logs.size(),
            "logs", logs.stream().limit(50).collect(Collectors.toList()),
            "patterns", analysis.get("patterns"),
            "error_rate", analysis.get("error_rate")
        );
    }
    
    private Map<String, Object> analyzeLogPattern(List<LogEntry> logs) {
        // 聚合相同异常堆栈
        Map<String, Long> errorCount = logs.stream()
            .filter(l -> "ERROR".equals(l.getLevel()))
            .collect(Collectors.groupingBy(
                l -> extractExceptionType(l.getMessage()),
                Collectors.counting()
            ));
        
        return Map.of(
            "patterns", errorCount,
            "error_rate", (double) errorCount.size() / logs.size()
        );
    }
}

@Service
public class MetricsQueryService {
    
    public Map<String, Object> queryMetrics(Map<String, Object> args) {
        String metricName = (String) args.get("metric_name");
        Long startTime = (Long) args.get("start_time");
        Long endTime = (Long) args.get("end_time");
        
        // 从Prometheus查询
        List<TimeSeriesPoint> points = queryPrometheus(
            metricName, startTime, endTime
        );
        
        // 异常检测
        AnomalyDetectionResult anomaly = detectAnomaly(points);
        
        return Map.of(
            "metric", metricName,
            "values", points,
            "anomaly", anomaly.isAnomaly(),
            "anomaly_points", anomaly.getAnomalyPoints(),
            "baseline", anomaly.getBaseline()
        );
    }
    
    private AnomalyDetectionResult detectAnomaly(List<TimeSeriesPoint> points) {
        // 3-sigma异常检测
        List<Double> values = points.stream()
            .map(TimeSeriesPoint::getValue)
            .collect(Collectors.toList());
        
        double mean = values.stream().mapToDouble(v -> v).average().orElse(0);
        double std = Math.sqrt(values.stream()
            .mapToDouble(v -> Math.pow(v - mean, 2))
            .average().orElse(0));
        
        List<Long> anomalyPoints = new ArrayList<>();
        for (TimeSeriesPoint point : points) {
            if (Math.abs(point.getValue() - mean) > 3 * std) {
                anomalyPoints.add(point.getTimestamp());
            }
        }
        
        return new AnomalyDetectionResult(
            !anomalyPoints.isEmpty(), anomalyPoints, mean
        );
    }
}

步骤2:AI Agent集成

2.1 AI Agent核心类

@Service
public class AIAgentService {
    
    @Autowired
    private MCPClient mcpClient;
    
    private final OpenAI LLM;
    
    public AIAgentService() {
        this.LLM = OpenAI.builder()
            .apiKey(System.getenv("OPENAI_API_KEY"))
            .model("gpt-4")
            .build();
    }
    
    // 根因分析主流程
    public String rootCauseAnalysis(String alertMessage, AlertContext context) {
        // 1. 构建系统提示词
        String systemPrompt = buildSystemPrompt();
        
        // 2. 获取MCP工具定义
        List<MCPTool> tools = mcpClient.listTools();
        
        // 3. 构建用户消息
        String userMessage = buildUserMessage(alertMessage, context);
        
        // 4. 调用LLM(支持工具调用)
        ChatCompletionRequest request = ChatCompletionRequest.builder()
            .model("gpt-4")
            .messages(Arrays.asList(
                new SystemMessage(systemPrompt),
                new UserMessage(userMessage)
            ))
            .tools(tools.stream().map(this::convertToOpenAITool).collect(Collectors.toList()))
            .build();
        
        ChatCompletionResponse response = LLM.chat(request);
        
        // 5. 处理工具调用
        return executeToolCalls(response);
    }
    
    private String buildSystemPrompt() {
        return """
            你是一个专业的系统运维专家,擅长通过分析监控指标、日志和链路数据定位线上问题。
            
            可用的工具:
            - get_metrics: 查询CPU、内存、QPS、错误率等监控指标
            - query_logs: 查询服务日志,支持按级别、关键字过滤
            - get_trace: 查询调用链,定位慢调用或错误调用
            - get_slow_queries: 查询慢SQL
            
            分析步骤:
            1. 根据告警信息确定问题时间和影响范围
            2. 查询相关服务的错误率、延迟指标
            3. 查询错误日志,提取异常堆栈
            4. 如果涉及调用链,获取trace详情
            5. 综合分析,给出根因和修复建议
            
            输出格式:
            【根因】xxx
            【影响范围】xxx
            【证据链】1.指标异常xxx 2.日志错误xxx
            【修复建议】1.xxx 2.xxx
            """;
    }
    
    private String buildUserMessage(String alertMessage, AlertContext context) {
        return String.format("""
            告警信息:%s
            服务名称:%s
            发生时间:%s
            影响实例:%s
            
            请帮我分析这个问题的根本原因。
            """,
            alertMessage,
            context.getServiceName(),
            context.getStartTime(),
            String.join(",", context.getAffectedInstances())
        );
    }
    
    private String executeToolCalls(ChatCompletionResponse response) {
        // 递归处理工具调用
        for (ToolCall toolCall : response.getToolCalls()) {
            Object result = mcpClient.callTool(
                toolCall.getName(), 
                toolCall.getArguments()
            );
            
            // 将结果返回给LLM继续分析
            return continueAnalysis(result, response);
        }
        return response.getContent();
    }
}

2.2 MCP客户端实现

@Component
public class MCPClient {
    
    @Autowired
    private RestTemplate restTemplate;
    
    private final String mcpServerUrl = "http://localhost:8080/mcp/v1/messages";
    
    public List<MCPTool> listTools() {
        MCPMessage request = new MCPMessage();
        request.setMethod("tools/list");
        
        MCPMessage response = restTemplate.postForObject(
            mcpServerUrl, request, MCPMessage.class
        );
        
        return (List<MCPTool>) response.getParams().get("tools");
    }
    
    public Object callTool(String toolName, Map<String, Object> arguments) {
        MCPMessage request = new MCPMessage();
        request.setMethod("tools/call");
        request.setParams(Map.of(
            "name", toolName,
            "arguments", arguments
        ));
        
        MCPMessage response = restTemplate.postForObject(
            mcpServerUrl, request, MCPMessage.class
        );
        
        return response.getParams().get("result");
    }
}

步骤3:异常触发与智能排查

@Component
public class AlertListener {
    
    @Autowired
    private AIAgentService aiAgent;
    
    @EventListener
    public void onAlert(AlertEvent event) {
        // 1. 告警降噪 - 避免重复分析
        if (isDuplicateAlert(event)) {
            return;
        }
        
        // 2. 异步触发AI分析
        CompletableFuture.supplyAsync(() -> {
            return aiAgent.rootCauseAnalysis(
                event.getMessage(),
                buildAlertContext(event)
            );
        }).thenAccept(analysis -> {
            // 3. 推送分析结果
            notifyOperator(event, analysis);
            
            // 4. 存储分析记录
            saveAnalysisResult(event, analysis);
        });
    }
    
    private AlertContext buildAlertContext(AlertEvent event) {
        AlertContext context = new AlertContext();
        context.setServiceName(event.getService());
        context.setStartTime(event.getStartTime());
        context.setEndTime(event.getEndTime());
        context.setAffectedInstances(event.getInstances());
        
        // 自动扩展时间窗口(问题前后15分钟)
        context.setExtendedStartTime(event.getStartTime() - 900000);
        context.setExtendedEndTime(event.getEndTime() + 900000);
        
        return context;
    }
}

步骤4:交互式排查API

@RestController
@RequestMapping("/api/chat")
public class ChatDiagnosticController {
    
    @Autowired
    private AIAgentService aiAgent;
    
    @PostMapping("/diagnose")
    public Mono<DiagnosticResponse> diagnose(@RequestBody DiagnosticRequest request) {
        return Mono.fromCallable(() -> {
            // 支持多轮对话
            String sessionId = request.getSessionId();
            String userQuestion = request.getQuestion();
            
            // 结合历史上下文
            String context = getSessionContext(sessionId);
            
            // 调用AI分析
            String analysis = aiAgent.interactiveDiagnose(userQuestion, context);
            
            return new DiagnosticResponse(sessionId, analysis);
        }).subscribeOn(Schedulers.boundedElastic());
    }
    
    // 流式输出(SSE)
    @GetMapping(value = "/stream/{sessionId}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<String> streamDiagnose(@PathVariable String sessionId, @RequestParam String question) {
        return aiAgent.streamDiagnose(question, sessionId);
    }
}

步骤5:前端集成

<!DOCTYPE html>
<html>
<head>
    <title>Arthas AI 诊断助手</title>
    <script src="https://cdn.socket.io/4.5.0/socket.io.min.js"></script>
</head>
<body>
    <div id="chat-container">
        <div id="messages"></div>
        <input type="text" id="question" placeholder="描述问题...">
        <button onclick="sendQuestion()">发送</button>
    </div>
    
    <script>
        const socket = io('http://localhost:8080');
        
        socket.on('diagnosis-result', (data) => {
            const msgDiv = document.createElement('div');
            msgDiv.className = 'message ai';
            msgDiv.innerHTML = formatMarkdown(data.analysis);
            document.getElementById('messages').appendChild(msgDiv);
        });
        
        async function sendQuestion() {
            const question = document.getElementById('question').value;
            const response = await fetch('/api/chat/diagnose', {
                method: 'POST',
                headers: {'Content-Type': 'application/json'},
                body: JSON.stringify({
                    sessionId: getSessionId(),
                    question: question
                })
            });
            const result = await response.json();
            // 处理结果...
        }
        
        function formatMarkdown(text) {
            // 简单的Markdown渲染
            return text.replace(/\n/g, '<br>')
                .replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
        }
    </script>
</body>
</html>

四、实战场景演示

场景:某服务CPU飙升排查

告警order-service CPU使用率 > 85% (当前92%)

AI分析过程

用户: 分析order-service CPU飙升问题

AI: 正在排查中...
    1. 调用get_metrics("cpu_usage", "order-service", 最近15分钟)
    结果:CPU在10:2345%突增到92%
    
    2. 调用query_logs("order-service", level="ERROR", 时间范围10:20-10:30)
    结果:发现大量 "Database connection pool exhausted" 错误
    
    3. 调用get_slow_queries("order-service", 时间范围10:20-10:30)
    结果:发现慢查询 "SELECT * FROM orders WHERE user_id=? AND status='PENDING'"
    执行时间从正常50ms飙升到3500ms
    
    4. 调用get_trace(关联traceId)
    结果:该慢查询未使用索引,全表扫描100w行
    
【根因】数据库索引缺失导致慢查询,连接池被耗尽,应用线程阻塞导致CPU飙升

【影响范围】order-service所有实例,影响下单接口,错误率从0.1%升至15%

【证据链】
- 指标:10:23开始CPU突增,与慢查询出现时间吻合
- 日志:连接池满错误(1023次/分钟)
- 链路:查询耗时3.5s,SQL无索引扫描

【修复建议】
1. 紧急:重启order-service释放连接池
2. 短期:为orders表的(user_id, status)添加组合索引
3. 长期:配置慢查询阈值200ms,添加索引监控告警

五、总结

5.1 核心价值

  1. 效率提升:问题定位从平均30分钟缩短到1分钟内
  2. 降低门槛:运维人员通过自然语言即可排查,无需精通多种监控系统
  3. 知识沉淀:AI分析结果自动归档,形成故障知识库
  4. 7x24小时:无人值守时AI自动分析并推送结果

5.2 技术亮点

  • MCP标准化:统一了LLM与监控系统的交互协议,易于扩展新工具
  • 多模态融合:同时分析Metrics/Logs/Trace,避免单一数据源误判
  • 自适应上下文:根据告警类型自动调整分析策略和查询范围
  • 流式响应:SSE实时推送分析进度,提升交互体验

5.3 注意事项

  1. 数据安全:日志可能包含敏感信息,需对AI API传输进行脱敏和加密
  2. 成本控制:使用本地小模型处理简单问题,复杂问题才调用云端大模型
  3. 幻觉防护:对AI输出的根因进行二次验证,避免错误建议引发二次故障
  4. 限流熔断:大量告警时需限流,防止AI服务被打爆

5.4 后续演进

  • 自动修复:AI分析后自动执行预案(如重启、扩容、降级)
  • 预测预警:基于历史数据预测潜在故障,主动排查
  • 多模态输入:支持上传截图、监控面板图片进行分析
  • 联邦MCP:跨多个MCP Server联合分析(应用监控+数据库监控+网络监控)

5.5 部署建议

# docker-compose.yml
version: '3.8'
services:
  arthas-mcp:
    build: ./arthas-mcp
    ports:
      - "8080:8080"
    environment:
      - PROMETHEUS_URL=http://prometheus:9090
      - LOKI_URL=http://loki:3100
      - JAEGER_URL=http://jaeger:16686
      
  ai-agent:
    build: ./ai-agent
    environment:
      - OPENAI_API_KEY=${OPENAI_API_KEY}
      - MCP_SERVER_URL=http://arthas-mcp:8080
      
  web-ui:
    build: ./web-ui
    ports:
      - "3000:3000"

通过以上实现,Arthas+MCP构建的AI排查系统能够显著提升运维效率,让故障定位从"人找数据"转变为"数据找人",真正实现智能运维。

Arthas接入MCP:让AI替你24小时盯盘,线上问题秒级根因定位.png

谢谢你看我的文章,既然看到这里了,如果觉得不错,随手点个赞、转发、在看三连吧,感谢感谢。那我们,下次再见。

您的一键三连,是我更新的最大动力,谢谢

山水有相逢,来日皆可期,谢谢阅读,我们再会

我手中的金箍棒,上能通天,下能探海