本系统已上传到github上,该系列文章将逐步讲解其作用方式
littlehow-apm系统GitHub地址
littlehow-apm-dashboard前端GitHub地址
apm接入是我为我现有公司编写的微服务治理与监控平台初版,编写了有段时间了,一直在推动公司各java系统接入(非java系统,可基于http上报信息)
package com.littlehow.apm.base.web;
import org.springframework.boot.autoconfigure.web.WebMvcRegistrations;
import org.springframework.boot.autoconfigure.web.WebMvcRegistrationsAdapter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashSet;
import java.util.Set;
/**
* 主要读取自身系统的接口信息
*
* @author littlehow
*/
@Configuration
public class RequestMappingConfiguration {
private static Set<Class<? extends Annotation>> IGNORED = new HashSet<>();
private static Set<Class<? extends Annotation>> IGNORED_COMPATIBILITY = new HashSet<>();
static {
// 当忽略注解和下面注解同时出现时,该接口为自有系统接口
IGNORED_COMPATIBILITY.add(Controller.class);
IGNORED_COMPATIBILITY.add(RestController.class);
}
@Bean
public WebMvcRegistrations feignWebRegistrations() {
return new WebMvcRegistrationsAdapter() {
@Override
public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
return new ApmRequestMappingHandlerMapping();
}
};
}
/**
* 解析具有RequestMapping和Controller注解的类
* meta注解也会记录,如RestController, 其注解上游注解Controller
*/
private static class ApmRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
@Override
protected boolean isHandler(Class<?> beanType) {
boolean handler = super.isHandler(beanType) && !hasIgnored(beanType);
if (handler) {
Method[] methods = beanType.getMethods();
for (Method method : methods) {
if (!Modifier.isPublic(method.getModifiers()) || Modifier.isStatic(method.getModifiers())) {
continue;
}
RequestMappingInfo mappingInfo = super.getMappingForMethod(method, beanType);
if (mappingInfo != null) {
PatternsRequestCondition condition = mappingInfo.getPatternsCondition();
Set<String> patterns = condition.getPatterns();
for (String uri : patterns) {
RequestMappingContext.addMapping(uri, method, beanType);
}
}
}
}
return handler;
}
}
/**
* 如果系统自身引入了需要忽略的注解如FeignClient,这样的不需要统计在自身接口中
*
* @param beanType -
* @return
*/
private static boolean hasIgnored(Class<?> beanType) {
for (Class<? extends Annotation> clazz : IGNORED) {
if (AnnotatedElementUtils.hasAnnotation(beanType, clazz)) {
//判断是否有兼容类型在里面
for (Class<? extends Annotation> clazz1 : IGNORED_COMPATIBILITY) {
if (AnnotatedElementUtils.hasAnnotation(beanType, clazz1)) {
return false;
}
}
return true;
}
}
return false;
}
public static void registerIgnoredAnnotation(Class<? extends Annotation> clazz) {
IGNORED.add(clazz);
}
}
package com.littlehow.apm.base.web;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* 自身接口信息管理上下文
*
* @author littlehow
*/
public class RequestMappingContext {
/**
* 映射对meta
*/
private static final Map<String/* uri */, RequestMappingMeta> mappingMataCache = new HashMap<>();
/**
* 需要忽略的类或uri
*/
private static final Set<String> excludeClass = new HashSet<>();
static {
// swagger相关api
excludeClass.add("springfox.documentation.swagger.web.ApiResourceController");
// springboot内置相关
excludeClass.add("org.springframework.boot.autoconfigure.web.BasicErrorController");
// 自定义服务上下线相关
excludeClass.add("com.littlehow.apm.register.eureka.EurekaManagementController");
}
/**
* 方法签名(忽略参数)对meta
* 如果controller实现是父子关系,并且父类暴露给子类的方法不是拥有RequestMapping注解的方法,需要在此处做兼容缓存
*/
private static final Map<String/* className.methodName*/, RequestMappingMeta> methodMetaCache = new HashMap<>();
/**
* 将request mapping信息缓存起来
* @param uri - 对应的uri
* @param method - 对应的方法
* @param target - 类
*/
static void addMapping(String uri, Method method, Class<?> target) {
if (excludeClass.contains(target.getName())) {
return;
}
RequestMappingMeta meta = RequestMappingMeta.getMeta(method, target, uri);
mappingMataCache.put(uri, meta);
methodMetaCache.put(meta.getClassName() + "." + meta.getMethodName(), meta);
}
/**
* 根据uri获取meta信息
* @param uri -
* @return
*/
public static RequestMappingMeta getRequestMappingMeta(String uri) {
return mappingMataCache.get(uri);
}
/**
* 根据类名和方法名获取
* @param className - 类名
* @param methodName - 方法名
* @return
*/
public static RequestMappingMeta getRequestMappingMeta(String className, String methodName) {
String key = className + "." + methodName;
return methodMetaCache.get(key);
}
/**
* 遍历服务信息
* @param reader - 读取配置信息
*/
public static void readAllMapping(RequestMappingReader reader) {
mappingMataCache.forEach((k, v) -> reader.read(v));
}
}
package com.littlehow.apm.base.web;
/**
* 全量读取映射信息接口
* @author littlehow
*/
public interface RequestMappingReader {
void read(RequestMappingMeta meta);
}
如此就对spring自己定义的接口自动进行分析以及后续的上报功能
package com.littlehow.apm.base.configuration;
import com.littlehow.apm.base.util.IpUtils;
import com.littlehow.apm.base.web.SelfServerContext;
import lombok.Getter;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
* 使用
* @see ConfigurationProperties
* 需要配合
* @see org.springframework.cloud context.environment.EnvironmentChangeEvent
* 才能实现动态修改
*
* @author littlehow
*/
//@ConfigurationProperties("apm")
@Getter
@Configuration
public class OuterProperties implements InitializingBean {
@Value("${apm.application.name:${spring.application.name:NONE}}")
private String applicationName;
@Value("${apm.application.name.cn:}")
private String applicationNameCn;
@Value("${apm.application.heartbeat.distance:30}")
private Integer heartbeatDistance;
@Value("${apm.application.package.start:}")
private String packageStart;
/**
* 本地服务是否缓存,如果缓存,那么多处调用可能不准确
*/
@Value("${apm.application.self.server.cache:true}")
private boolean selfServerCache;
@Value("${apm.system.timezone.hour:8}")
private int timezoneHour;
@Value("${apm.collector.trace.body:true}")
private boolean traceBody;
@Value("${apm.collector.use.log:false}")
private boolean useLog;
private String collectorUrl;
@Value("${apm.collector.server.url:}")
private void setCollectorUrl(String url) {
if (url.startsWith("http")) {
collectorUrl = url;
} else {
collectorUrl = "";
}
}
@Value("${apm.schedule.pool.size:2}")
private int schedulePoolSize;
/**
* 发送日志、服务信息、心跳的线程池工具
* 默认核心线程和最大线程为2
*/
@Value("${apm.thread.pool.size:2}")
private int threadPoolSize;
/**
* 线程池任务队列大小,默认100W
*/
@Value("${apm.thread.pool.queue.size:1000000}")
private int threadPoolQueueSize;
/**
* 本机ip信息
*/
private String selfIp;
/**
* 系统配置
*/
@Autowired
private ServerProperties serverProperties;
@Override
public void afterPropertiesSet() {
SelfServerContext.setProperties(this);
}
@Value("${apm.host.ip:}")
private void setSelfIp(String ip) {
if (IpUtils.verifyIpv4(ip)) {
this.selfIp = ip;
} else {
this.selfIp = IpUtils.getIp();
}
}
}
该类不仅仅有接口信息上报,还有心跳、日志信息上报等
package com.littlehow.apm.feign.advice.support;
import com.alibaba.fastjson.JSONObject;
import com.littlehow.apm.base.ApplicationInfo;
import com.littlehow.apm.base.configuration.OuterProperties;
import com.littlehow.apm.base.enums.ServerStatus;
import com.littlehow.apm.base.enums.YesOrNo;
import com.littlehow.apm.base.req.ApplicationReq;
import com.littlehow.apm.base.req.HeartbeatReq;
import com.littlehow.apm.base.req.InterfaceReq;
import com.littlehow.apm.base.resp.BaseResult;
import com.littlehow.apm.base.util.ApmBeanUtils;
import com.littlehow.apm.base.util.RestTemplateUtil;
import com.littlehow.apm.base.util.SleepUtil;
import com.littlehow.apm.base.web.RequestMappingContext;
import com.littlehow.apm.base.web.RequestMappingMeta;
import com.littlehow.apm.base.web.SelfServerContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* 默认监控收集处理类
* @author littlehow
*/
@Slf4j
public class TraceInfoExecutor implements InitializingBean {
@Autowired
@Qualifier("apmRestTemplate")
private RestTemplate restTemplate;
@Autowired
private OuterProperties outerProperties;
@Autowired
private ScheduledThreadPoolExecutor scheduledThreadPoolExecutor;
@Autowired
@Qualifier("apmThreadPoolExecutor")
private ThreadPoolExecutor executor ;
/**
* 添加追踪,如果队列已满,则抛弃当前信息
* @param traceInfo - 追踪信息
*/
public void addTrace(TraceInfo traceInfo) {
executor.execute(() -> {
if (outerProperties.isUseLog()) {
log.info("apm-collector-trace={}", JSONObject.toJSONString(traceInfo));
}
if (StringUtils.hasText(outerProperties.getCollectorUrl())) {
BaseResult result = RestTemplateUtil.post(outerProperties.getCollectorUrl() + CollectorUri.TRACE_LOG, traceInfo);
if (result == null || !result.isSuccess()) {
log.info("apm-collector-trace-log-error:上传服务信息失败 {}",
result == null ? "" : JSONObject.toJSONString(result));
}
}
});
}
@Override
public void afterPropertiesSet() {
RestTemplateUtil.setRestTemplate(restTemplate);
executor.execute(() -> {
//延时注册
SleepUtil.sleepSeconds(10);
//应用信息
ApplicationInfo applicationInfo = SelfServerContext.getApplicationInfo();
//添加关闭钩子
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
if (outerProperties.isUseLog()) {
log.info("apm-collector-server-shutdown");
}
//下线移除服务
if (StringUtils.hasText(outerProperties.getCollectorUrl())) {
RestTemplateUtil.post(outerProperties.getCollectorUrl() + CollectorUri.REMOVE_SERVER,
buildHeartbeatReq(applicationInfo));
}
}));
List<RequestMappingMeta> mappings = new ArrayList<>();
RequestMappingContext.readAllMapping(mappings::add);
//本机注册服务信息
if (outerProperties.isUseLog()) {
log.info("apm-collector-application={}", JSONObject.toJSONString(applicationInfo));
for (RequestMappingMeta meta : mappings) {
log.info("apm-collector-interface={}", JSONObject.toJSONString(meta));
}
}
if (StringUtils.hasText(outerProperties.getCollectorUrl())) {
BaseResult result = RestTemplateUtil.post(outerProperties.getCollectorUrl() + CollectorUri.UPLOAD_SERVER_INFO,
buildReq(applicationInfo, mappings));
if (result != null && result.isSuccess()) {
log.info("apm-collector-interface-remote:上传服务信息成功");
} else {
log.info("apm-collector-interface-remote-error:上传服务信息失败 {}",
result == null ? "" : JSONObject.toJSONString(result));
}
//构建心跳请求信息
HeartbeatReq req = buildHeartbeatReq(applicationInfo);
//发送心跳链接
scheduledThreadPoolExecutor.scheduleAtFixedRate(() -> {
BaseResult r = RestTemplateUtil.post(outerProperties.getCollectorUrl() + CollectorUri.HEARTBEAT_URI, req);
if (r == null || !r.isSuccess()) {
log.info("apm-collector-heartbeat-error:心跳失败 {}",
r == null ? "" : JSONObject.toJSONString(r));
}
}, applicationInfo.getHeartbeatDistance().longValue(),
applicationInfo.getHeartbeatDistance().longValue(), TimeUnit.MILLISECONDS);
}
});
}
/**
* 默认应用为上线
* 默认接口都支持rpc调用
* @param applicationInfo -
* @param mappingMetas -
* @return
*/
private ApplicationReq buildReq(ApplicationInfo applicationInfo, List<RequestMappingMeta> mappingMetas) {
ApplicationReq req = ApmBeanUtils.copyNewOnNull(applicationInfo, ApplicationReq.class);
// 也可以和eureka状态保持一致
req.setStatus(ServerStatus.UP);
req.setInterfaceReqs(mappingMetas.stream().map(o -> {
InterfaceReq interfaceReq = ApmBeanUtils.copyNewOnNull(o, InterfaceReq.class);
interfaceReq.setRpcFlag(YesOrNo.YES);
return interfaceReq;
}).collect(Collectors.toList()));
return req;
}
/**
* 构建心跳
* @param applicationInfo -
* @return
*/
private HeartbeatReq buildHeartbeatReq(ApplicationInfo applicationInfo) {
HeartbeatReq req = new HeartbeatReq();
req.setApplicationName(applicationInfo.getApplicationName());
req.setIp(applicationInfo.getIp());
req.setServerPort(applicationInfo.getServerPort());
// 这里未来可以和eureka状态保持一致
req.setStatus(ServerStatus.UP);
return req;
}
}