大家好,我是小悟。
一、需求描述
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:23从45%突增到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 核心价值
- 效率提升:问题定位从平均30分钟缩短到1分钟内
- 降低门槛:运维人员通过自然语言即可排查,无需精通多种监控系统
- 知识沉淀:AI分析结果自动归档,形成故障知识库
- 7x24小时:无人值守时AI自动分析并推送结果
5.2 技术亮点
- MCP标准化:统一了LLM与监控系统的交互协议,易于扩展新工具
- 多模态融合:同时分析Metrics/Logs/Trace,避免单一数据源误判
- 自适应上下文:根据告警类型自动调整分析策略和查询范围
- 流式响应:SSE实时推送分析进度,提升交互体验
5.3 注意事项
- 数据安全:日志可能包含敏感信息,需对AI API传输进行脱敏和加密
- 成本控制:使用本地小模型处理简单问题,复杂问题才调用云端大模型
- 幻觉防护:对AI输出的根因进行二次验证,避免错误建议引发二次故障
- 限流熔断:大量告警时需限流,防止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排查系统能够显著提升运维效率,让故障定位从"人找数据"转变为"数据找人",真正实现智能运维。
谢谢你看我的文章,既然看到这里了,如果觉得不错,随手点个赞、转发、在看三连吧,感谢感谢。那我们,下次再见。
您的一键三连,是我更新的最大动力,谢谢
山水有相逢,来日皆可期,谢谢阅读,我们再会
我手中的金箍棒,上能通天,下能探海