自研简易apm(基于springcloud)基础组件系列文章(二)

微服务接口信息收集

写在前面

本系统已上传到github上,该系列文章将逐步讲解其作用方式

littlehow-apm系统GitHub地址

littlehow-apm-dashboard前端GitHub地址

apm接入是我为我现有公司编写的微服务治理与监控平台初版,编写了有段时间了,一直在推动公司各java系统接入(非java系统,可基于http上报信息)

收集本系统接口信息(用于上传)

基于spring的接口信息收集器(apm-base模块)

  • RequestMappingConfiguration类,spring解析自有controller会调用
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);
    }

}

  • 自身接口信息管理上下文RequestMappingContext
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));
    }
}

  • 微服务接口信息读取接口定义RequestMappingReader
package com.littlehow.apm.base.web;

/**
 * 全量读取映射信息接口
 * @author littlehow
 */
public interface RequestMappingReader {
    void read(RequestMappingMeta meta);
}

如此就对spring自己定义的接口自动进行分析以及后续的上报功能

  • 上报配置文件OuterProperties
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();
        }
    }
}

接口信息上报(apm-feign模块)

该类不仅仅有接口信息上报,还有心跳、日志信息上报等

  • TraceInfoExecutor类
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;
    }
}

你可能感兴趣的:(java,littlehow-apm)