如何用jQAssistant识别微服务及多类客户端的REST调用关联?
基于jQAssistant与Cypher实现微服务调用关系匹配
前提准备:提取REST服务提供者的URI元数据
先通过已识别的@RestController提取所有服务端点的完整URI模板(包含请求方法与路径),为后续匹配做基础:
MATCH (c:Spring:RestController)-[:DECLARES]->(m:Method) WHERE EXISTS(m.httpMethod) AND EXISTS(m.requestMapping) WITH c, m, apoc.text.join(COALESCE(m.requestMapping.value, []), ',') AS path, m.httpMethod AS method MERGE (endpoint:REST:Endpoint { serviceName: c.type.fqn, fullUri: apoc.text.join([method, path], ' '), method: method, pathTemplate: path }) MERGE (c)-[:EXPOSES]->(endpoint)
1. 匹配Spring/GitHub OpenFeign客户端调用
Feign接口通过@FeignClient定义,方法上的@RequestMapping直接指定调用URI,可直接关联:
// 提取Feign客户端的调用端点 MATCH (feign:Spring:FeignClient)-[:DECLARES]->(m:Method) WHERE EXISTS(m.httpMethod) AND EXISTS(m.requestMapping) WITH feign, m, apoc.text.join(COALESCE(m.requestMapping.value, []), ',') AS callPath, m.httpMethod AS callMethod MERGE (caller:REST:ClientCall { clientName: feign.type.fqn, callUri: apoc.text.join([callMethod, callPath], ' '), method: callMethod, pathTemplate: callPath }) MERGE (feign)-[:MAKES_CALL]->(caller) // 匹配提供者与消费者,创建INVOKES_REMOTE关系 MATCH (caller:REST:ClientCall), (endpoint:REST:Endpoint) WHERE caller.method = endpoint.method AND apoc.text.match(caller.pathTemplate, endpoint.pathTemplate) MERGE (caller)-[:INVOKES_REMOTE]->(endpoint)
2. 匹配RestTemplate/OAuthRestTemplate调用
RestTemplate通过代码硬编码URI,需提取方法调用中的字符串参数或变量,同时从方法名推断请求方法:
// 提取RestTemplate的URI调用 MATCH (m:Method)-[:INVOKES]->(rm:Method {name: ["getForObject", "postForObject", "exchange"]}) WHERE rm.declaringType.fqn CONTAINS "RestTemplate" WITH m, rm, [param IN rm.parameters WHERE param.type.fqn = "java.lang.String"][0] AS uriParam, [arg IN m.arguments WHERE arg.type.fqn = "java.lang.String"][0] AS uriArg WITH m, COALESCE(uriArg.value, uriParam.name) AS callPath, CASE rm.name WHEN "getForObject" THEN "GET" WHEN "postForObject" THEN "POST" WHEN "exchange" THEN [arg IN m.arguments WHERE arg.type.fqn CONTAINS "HttpMethod"][0].value ELSE "UNKNOWN" END AS callMethod MERGE (caller:REST:ClientCall { clientName: m.declaringType.fqn, callUri: apoc.text.join([callMethod, callPath], ' '), method: callMethod, pathTemplate: callPath }) MERGE (m.declaringType)-[:MAKES_CALL]->(caller) // 匹配关联 MATCH (caller:REST:ClientCall), (endpoint:REST:Endpoint) WHERE caller.method = endpoint.method AND apoc.text.match(caller.pathTemplate, endpoint.pathTemplate) MERGE (caller)-[:INVOKES_REMOTE]->(endpoint)
3. 匹配Apache HttpClient调用
HttpClient通过HttpGet/HttpPost等构造方法传入URI,提取构造参数并匹配请求方法:
// 提取HttpClient的URI调用 MATCH (m:Method)-[:NEW]->(req:Type {fqn: ["org.apache.http.client.methods.HttpGet", "org.apache.http.client.methods.HttpPost"]}) WITH m, req, [arg IN m.arguments WHERE arg.type.fqn = "java.lang.String"][0] AS uriArg WITH m, uriArg.value AS callPath, CASE req.fqn WHEN "org.apache.http.client.methods.HttpGet" THEN "GET" WHEN "org.apache.http.client.methods.HttpPost" THEN "POST" ELSE "UNKNOWN" END AS callMethod MERGE (caller:REST:ClientCall { clientName: m.declaringType.fqn, callUri: apoc.text.join([callMethod, callPath], ' '), method: callMethod, pathTemplate: callPath }) MERGE (m.declaringType)-[:MAKES_CALL]->(caller) // 匹配关联 MATCH (caller:REST:ClientCall), (endpoint:REST:Endpoint) WHERE caller.method = endpoint.method AND apoc.text.match(caller.pathTemplate, endpoint.pathTemplate) MERGE (caller)-[:INVOKES_REMOTE]->(endpoint)
4. 匹配OkHttpClient调用
OkHttpClient通过Request.Builder的url()方法传入URI,提取参数并从method()调用推断请求方法(默认GET):
// 提取OkHttpClient的URI调用 MATCH (m:Method)-[:INVOKES]->(urlMethod:Method {name: "url", declaringType: "okhttp3.Request$Builder"}) WITH m, urlMethod, [arg IN urlMethod.arguments WHERE arg.type.fqn = "java.lang.String"][0] AS uriArg WITH m, uriArg.value AS callPath, COALESCE( [methodCall IN m.invokes WHERE methodCall.name = "method" AND methodCall.declaringType = "okhttp3.Request$Builder"][0].arguments[0].value, "GET" ) AS callMethod MERGE (caller:REST:ClientCall { clientName: m.declaringType.fqn, callUri: apoc.text.join([callMethod, callPath], ' '), method: callMethod, pathTemplate: callPath }) MERGE (m.declaringType)-[:MAKES_CALL]->(caller) // 匹配关联 MATCH (caller:REST:ClientCall), (endpoint:REST:Endpoint) WHERE caller.method = endpoint.method AND apoc.text.match(caller.pathTemplate, endpoint.pathTemplate) MERGE (caller)-[:INVOKES_REMOTE]->(endpoint)
循环依赖排查
完成所有调用关系匹配后,可用以下查询检测微服务间的循环依赖:
MATCH path = (s:Spring:RestController)-[:EXPOSES]->()-[:INVOKES_REMOTE*1..]->()-[:EXPOSES]-(s) RETURN path, length(path) AS cycleLength ORDER BY cycleLength DESC
内容的提问来源于stack exchange,提问作者Venkat
相关产品推荐
相关产品推荐

