关于java:代码级全链路压测的整体架构设计以及5种实现方案

今日这篇文章接上文,明天咱们持续聊聊全链路压测的具体架构设计以及5种实现计划流量染色计划、数据隔离计划、接口隔离计划、零侵入计划、服务监控计划。

看不懂的小伙伴,能够先看这个:

大厂钟爱的全链路压测有什么意义?四种压测计划具体比照剖析

业务模块介绍

当初咱们对整体的业务进行介绍以及演示

5. 全链路整体架构

下面介绍了为什么须要全链路压测,上面来看下全链路压测的整体架构。

​ 整体架构如下次要是对压测客户端的压测数据染色,全链路中间件辨认出染色数据,并将失常数据和压测数据辨别开,进行数据隔离,这里次要波及到mysql数据库,RabbitMQ,Redis,还须要解决因为hystrix线程池不能通过ThreadLocal传递染色示意的问题。

5.1 须要应答的问题

5.1.1 业务问题

如何发展全链路压测?在说这个问题前,咱们先思考下,全链路压测有哪些问题比拟难解决。

  1. 波及的零碎太多,牵扯的开发人员太多

    ​ 在压测过程中,做一个全链路的压测个别会波及到大量的零碎,在整个压测过程中,光各个产品的人员协调就是一个比拟大的工程,牵扯到太多的产品经理和开发人员,如果公司对全链路压测晚期没有足够的器重,那么这个压测工作是十分难发展的。

  2. 模仿的测试数据和拜访流量不实在

    ​ 在压测过程中常常会遇到压测后失去的数据不精确的问题,这就使得压测出的数据参考性不强,为什么会产生这样的问题?次要就是因为压测的环境可能和生成环境存在误差、参数存在不一样的中央、测试数据存在不一样的中央这些因素综合起来导致测试后果的不可信。

  3. 压测生产数据未隔离,影响生产环境

    ​ 在全链路压测过程中,压测数据可能会影响到生产环境的实在数据,举个例子,电商零碎在生产环境进行全链路压测的时候可能会有很多压测模仿用户去下单,如果不做解决,间接下单的话会导致系统一下子会产生很多废订单,从而影响到库存和生产订单数据,影响到日常的失常经营。

5.1.2 技术问题
5.1.2.1 探针的性能耗费

APM组件服务的影响应该做到足够小。

服务调用埋点自身会带来性能损耗,这就须要调用跟踪的低损耗,理论中还会通过配置采样率的形式,抉择一部分申请去剖析申请门路。在一些高度优化过的服务,即便一点点损耗也会很容易察觉到,而且有可能迫使在线服务的部署团队不得不将跟踪零碎关停。

5.1.2.2 代码的侵入性

即也作为业务组件,该当尽可能少入侵或者无入侵其余业务零碎,对于应用方通明,缩小开发人员的累赘。

​ 对于利用的程序员来说,是不须要晓得有跟踪零碎这回事的。如果一个跟踪零碎想失效,就必须须要依赖利用的开发者被动配合,那么这个跟踪零碎也太软弱了,往往因为跟踪零碎在利用中植入代码的bug或忽略导致利用出问题,这样才是无奈满足对跟踪零碎“无所不在的部署”这个需要。

5.1.2.3 可扩展性

​ 一个优良的调用跟踪零碎必须反对分布式部署,具备良好的可扩展性。可能反对的组件越多当然越好。或者提供便捷的插件开发API,对于一些没有监控到的组件,利用开发者也能够自行扩大。

5.1.2.4 数据的剖析

​ 数据的剖析要快 ,剖析的维度尽可能多。跟踪零碎能提供足够快的信息反馈,就能够对生产环境下的异样情况做出快速反应。剖析的全面,可能防止二次开发。

5.2 全链路压测核心技术

下面从总体架构层面剖析了全链路压测的外围,上面就剖析下全链路压测用到的核心技术点

5.2.1 全链路流量染色

做到微服务和中间件的染色标记的穿透

​ 通过压测平台对输入的压力申请打上标识,在订单零碎中提取压测标识,确保残缺的程序上下文都持有该标识,并且可能穿透微服务以及各种中间件,比方 MQ,hystrix,Fegin等。

5.2.2 全链路服务监控

须要可能实时监控服务的运行状况以及剖析服务的调用链,咱们采纳skywalking进行服务监控和压测剖析

5.2.3 全链路日志隔离

做到日志隔离,避免净化生产日志

​ 当订单零碎向磁盘或外设输入日志时,若流量是被标记的压测流量,则将日志隔离输入,防止影响生产日志。

5.2.4 全链路危险熔断

流量管制,避免流量超载,导致集群不可用

​ 当订单零碎拜访会员零碎时,通过RPC协定连续压测标识到会员零碎,两个零碎之间服务通信将会有白黑名单开关来管制流量流入许可。该方案设计能够肯定水平上防止上游零碎呈现瓶颈或不反对压测所带来的危险,这里能够采纳Sentinel来实现危险熔断。

5.3 全链路数据隔离

对各种存储服务以及中间件做到数据隔离,形式数据净化

2.3.1 数据库隔离

​ 当会员零碎拜访数据库时,在长久化层同样会依据压测标识进行路由拜访压测数据表。数据隔离的伎俩有多种,比方影子库影子表,或者影子数据,三种计划的仿真度会有肯定的差别,他们的比照如下。

隔离性 兼容性 安全级别 技术难度
影子库
影子表
影子数据
5.3.2 音讯队列隔离

​ 当咱们生产的音讯扔到MQ之后,接着让消费者进行生产,这个没有问题,压测的数据不可能间接扔到MQ中的,因为它会被失常的消费者生产到的,要做好数据隔离,计划有队列隔离音讯隔离,他们比照如下。

隔离性 兼容性 安全级别 技术难度
队列隔离
音讯隔离
5.3.3 Redis 隔离

​ 通过 key 值来辨别,压测流量的 key 值加对立后缀,通过革新RedisTemplate来实现key的路由。

框架实现

6.1 流量染色计划

下面剖析了从整体剖析了全链路压测用的的核心技术,上面就来实现第一个流量染色。

6.1.1 流量辨认

​ 要想压测的流量和数据不影响线上实在的生产数据,就须要线上的集群能辨认出压测的流量,只有能辨认出压测申请的流量,那么流量触发的读写操作就很好对立去做隔离了。

全链路压测发动的都是Http的申请,只须要要申请头上增加对立的压测申请头。

​ 通过在申请协定中增加压测申请的标识,在不同服务的互相调用时,一路透传下去,这样每一个服务都能辨认出压测的申请流量,这样做的益处是与业务齐全的解耦,只须要利用框架进行感知,对业务方代码无侵入。

6.1.2 MVC接收数据

​ 客户端传递过去的数据能够通过获取Header的形式获取到,并将其设置进以后的ThreadLocal,交给前面的办法应用。

6.1.2.1 MVC拦截器实现
/**
 * 链路跟踪Request设置值
 */
public class MvcWormholeWebInterceptor implements WebRequestInterceptor {


    @Override
    public void preHandle(WebRequest webRequest) {
        //生效上下文,解决Tomcat线程复用问题
        WormholeContextHolder.invalidContext();
        String wormholeValue = webRequest.getHeader(WormholeContextHolder.WORMHOLE_REQUEST_MARK);
        if (StringUtils.isNotEmpty(wormholeValue)) {
            WormholeContextHolder.setContext(new WormholeContext(wormholeValue));
        }
    }

    @Override
    public void postHandle(WebRequest webRequest, ModelMap modelMap) throws Exception {

    }

    @Override
    public void afterCompletion(WebRequest webRequest, Exception e) throws Exception {

    }
}
6.1.2.2 Tomcat线程复用问题

​ tomcat默认应用线程池来治理线程,一个申请过去,如果线程池外面有闲暇的线程,那么会在线程池外面取一个线程来解决该申请,一旦该线程以后在解决申请,其余申请就不会被调配到该线程上,直到该申请解决实现。申请解决实现后,会将该线程重新加入线程池,因为是通过线程池复用线程,就会如果线程外部的ThreadLocal没有革除就会呈现问题,须要新的申请进来的时候,革除ThreadLocal。

6.1.3 Fegin传递传递染色标识

​ 咱们我的项目的微服务是应用Fegin来实现近程调用的,跨微服务传递染色标识是通过MVC拦截器获取到申请Header的染色标识,并放进ThreadLocal中,而后交给Fegin拦截器在发送申请之前从ThreadLocal中获取到染色标识,并放进Fegin构建申请的Header中,实现微服务之间的火炬传递。

6.1.3.1 代码实现
public class WormholeFeignRequestInterceptor implements RequestInterceptor {

    @Override
    public void apply(RequestTemplate requestTemplate) {
        WormholeContext wormholeContext = WormholeContextHolder.getContext();
        if (null != wormholeContext) {
            requestTemplate.header(WormholeContextHolder.WORMHOLE_REQUEST_MARK, wormholeContext.toString());
        }
    }
}
6.1.4 Hystrix传递染色标识
6.1.4.1 Hystrix隔离技术

Hystrix 实现资源隔离,次要有两种技术:

信号量

​ 信号量的资源隔离只是起到一个开关的作用,比方,服务 A 的信号量大小为 10,那么就是说它同时只容许有 10 个 tomcat 线程来拜访服务 A,其它的申请都会被回绝,从而达到资源隔离和限流爱护的作用。

线程池

​ 线程池隔离技术,是用 Hystrix 本人的线程去执行调用;而信号量隔离技术,是间接让 tomcat 线程去调用依赖服务。信号量隔离,只是一道关卡,信号量有多少,就容许多少个 tomcat 线程通过它,而后去执行。

6.1.4.2 Hystrix穿透

​ 如果应用线程池模式,那么存在一个ThreadLocal变量跨线程传递的问题,即在主线程的ThreadLocal变量,无奈在线程池中应用,不过Hystrix外部提供了解决方案。

封装Callable工作

public final class DelegatingWormholeContextCallable<V> implements Callable<V> {
    private final Callable<V> delegate;
    // 用户信息上下文(依据我的项目理论状况定义ThreadLocal上下文)
    private WormholeContext orginWormholeContext;

    public DelegatingWormholeContextCallable(Callable<V> delegate,
                                             WormholeContext wormholeContext) {
        this.delegate = delegate;
        this.orginWormholeContext = wormholeContext;
    }

    public V call() throws Exception {
        //避免线程复用销毁ThreadLocal的数据
        WormholeContextHolder.invalidContext();
        // 将以后的用户上下文设置进Hystrix线程的TreadLocal中
        WormholeContextHolder.setContext(orginWormholeContext);
        try {
            return delegate.call();
        } finally {
            // 执行结束,记得清理ThreadLocal资源
            WormholeContextHolder.invalidContext();
        }
    }

    public static <V> Callable<V> create(Callable<V> delegate,
                                         WormholeContext wormholeContext) {
        return new DelegatingWormholeContextCallable<V>(delegate, wormholeContext);
    }
}

实现Hystrix的并发策略类

因为Hystrix默认的并发策略不反对ThreadLocal传递,咱们能够自定义并发策略类继承HystrixConcurrencyStrategy

public class ThreadLocalAwareStrategy extends HystrixConcurrencyStrategy {

    // 最简略的形式就是引入现有的并发策略,进行性能扩大
    private final HystrixConcurrencyStrategy existingConcurrencyStrategy;

    public ThreadLocalAwareStrategy(
            HystrixConcurrencyStrategy existingConcurrencyStrategy) {
        this.existingConcurrencyStrategy = existingConcurrencyStrategy;
    }

    @Override
    public BlockingQueue<Runnable> getBlockingQueue(int maxQueueSize) {
        return existingConcurrencyStrategy != null
                ? existingConcurrencyStrategy.getBlockingQueue(maxQueueSize)
                : super.getBlockingQueue(maxQueueSize);
    }

    @Override
    public <T> HystrixRequestVariable<T> getRequestVariable(
            HystrixRequestVariableLifecycle<T> rv) {
        return existingConcurrencyStrategy != null
                ? existingConcurrencyStrategy.getRequestVariable(rv)
                : super.getRequestVariable(rv);
    }

    @Override
    public ThreadPoolExecutor getThreadPool(HystrixThreadPoolKey threadPoolKey,
                                            HystrixProperty<Integer> corePoolSize,
                                            HystrixProperty<Integer> maximumPoolSize,
                                            HystrixProperty<Integer> keepAliveTime, TimeUnit unit,
                                            BlockingQueue<Runnable> workQueue) {
        return existingConcurrencyStrategy != null
                ? existingConcurrencyStrategy.getThreadPool(threadPoolKey, corePoolSize,
                maximumPoolSize, keepAliveTime, unit, workQueue)
                : super.getThreadPool(threadPoolKey, corePoolSize, maximumPoolSize,
                keepAliveTime, unit, workQueue);
    }

    @Override
    public <T> Callable<T> wrapCallable(Callable<T> callable) {
        return existingConcurrencyStrategy != null
                ? existingConcurrencyStrategy
                .wrapCallable(new DelegatingWormholeContextCallable<>(callable, WormholeContextHolder.getContext()))
                : super.wrapCallable(new DelegatingWormholeContextCallable<T>(callable, WormholeContextHolder.getContext()));
    }
}

Hystrix注入新并发策略并进行刷新

public class HystrixThreadLocalConfiguration {

    @Autowired(required = false)
    private HystrixConcurrencyStrategy existingConcurrencyStrategy;

    @PostConstruct
    public void init() {
        // Keeps references of existing Hystrix plugins.
        HystrixEventNotifier eventNotifier = HystrixPlugins.getInstance()
                .getEventNotifier();
        HystrixMetricsPublisher metricsPublisher = HystrixPlugins.getInstance()
                .getMetricsPublisher();
        HystrixPropertiesStrategy propertiesStrategy = HystrixPlugins.getInstance()
                .getPropertiesStrategy();
        HystrixCommandExecutionHook commandExecutionHook = HystrixPlugins.getInstance()
                .getCommandExecutionHook();

        HystrixPlugins.reset();

        HystrixPlugins.getInstance().registerConcurrencyStrategy(new ThreadLocalAwareStrategy(existingConcurrencyStrategy));
        HystrixPlugins.getInstance().registerEventNotifier(eventNotifier);
        HystrixPlugins.getInstance().registerMetricsPublisher(metricsPublisher);
        HystrixPlugins.getInstance().registerPropertiesStrategy(propertiesStrategy);
        HystrixPlugins.getInstance().registerCommandExecutionHook(commandExecutionHook);
    }
}

6.2 数据隔离计划

6.2.1 JDBC数据源隔离

数据隔离须要对DB,Redis,RabbitMQ进行数据隔离

​ 通过实现Spring动静数据源AbstractRoutingDataSource,通过ThreadLocal辨认进去压测数据,如果是压测数据就路由到影子库,如果是失常流量则路由到主库,通过流量辨认的革新,各个服务都曾经可能辨认出压测的申请流量了。

6.2.1.1 代码实现

数据源路由Key持有对象

依据路由Key将抉择将操作路由给那个数据源

/**
 * 动静数据源上下文
 */
public class DynamicDataSourceContextHolder {
    public static final String PRIMARY_DB = "primary";
    public static final String SHADOW_DB = "shadow";

    private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>() {
        /**
         * 将 master 数据源的 key作为默认数据源的 key
         */
        @Override
        protected String initialValue() {
            return PRIMARY_DB;
        }
    };


    /**
     * 数据源的 key汇合,用于切换时判断数据源是否存在
     */
    public static List<Object> dataSourceKeys = new ArrayList<>();

    /**
     * 切换数据源
     *
     * @param key
     */
    public static void setDataSourceKey(String key) {
        contextHolder.set(key);
    }

    /**
     * 获取数据源
     *
     * @return
     */
    public static String getDataSourceKey() {
        return contextHolder.get();
    }

    /**
     * 重置数据源
     */
    public static void clearDataSourceKey() {
        contextHolder.remove();
    }

    /**
     * 判断是否蕴含数据源
     *
     * @param key 数据源key
     * @return
     */
    public static boolean containDataSourceKey(String key) {
        return dataSourceKeys.contains(key);
    }

    /**
     * 增加数据源keys
     *
     * @param keys
     * @return
     */
    public static boolean addDataSourceKeys(Collection<? extends Object> keys) {
        return dataSourceKeys.addAll(keys);
    }
}

动静数据源实现类

依据路由Key实现数据源的切换

/**
 * 动静数据源实现类
 */
public class DynamicDataSource extends AbstractRoutingDataSource {

    /**
     * 如果不心愿数据源在启动配置时就加载好,能够定制这个办法,从任何你心愿的中央读取并返回数据源
     * 比方从数据库、文件、内部接口等读取数据源信息,并最终返回一个DataSource实现类对象即可
     */
    @Override
    protected DataSource determineTargetDataSource() {
        //获取以后的上下文
        WormholeContext wormholeContext = WormholeContextHolder.getContext();
        //如果不为空应用影子库
        if (null != wormholeContext) {
            DynamicDataSourceContextHolder.setDataSourceKey(DynamicDataSourceContextHolder.SHADOW_DB);
        } else {
            //为空则应用主数据源
            DynamicDataSourceContextHolder.setDataSourceKey(DynamicDataSourceContextHolder.PRIMARY_DB);
        }
        return super.determineTargetDataSource();
    }

    /**
     * 如果心愿所有数据源在启动配置时就加载好,这里通过设置数据源Key值来切换数据,定制这个办法
     */
    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceContextHolder.getDataSourceKey();
    }

 
}
6.2.2 Redis 数据源隔离

​ 同时通过ThreadLocal辨认进去压测数据,自定义Redis的主键的序列化形式,如果是压测数据则在主键前面加上后缀,这样就能够通过不同主键将Redis数据进行隔离。

6.2.2.1 实现key序列化
public class KeyStringRedisSerializer extends StringRedisSerializer {

    @Resource
    private WormholeIsolationConfiguration isolationConfiguration;

    public byte[] serialize(@Nullable String redisKey) {
        WormholeContext wormholeContext = WormholeContextHolder.getContext();
        if (null != wormholeContext) {
            redisKey = isolationConfiguration.generateIsolationKey(redisKey);
        }
        return super.serialize(redisKey);
    }
}
6.2.2.2 配置序列化器
/**
 * Redis 配置类
 */
@Configuration
@ConditionalOnClass({RedisTemplate.class, RedisOperations.class, RedisConnectionFactory.class})
public class WormholeRedisAutoConfiguration {


    @Bean
    public KeyStringRedisSerializer keyStringRedisSerializer() {
        return new KeyStringRedisSerializer();
    }

    @Bean("redisTemplate")
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate template = new RedisTemplate();
        //应用fastjson序列化
        FastJsonRedisSerializer fastJsonRedisSerializer = new FastJsonRedisSerializer(Object.class);
        // value值的序列化采纳fastJsonRedisSerializer
        template.setValueSerializer(fastJsonRedisSerializer);
        template.setHashValueSerializer(fastJsonRedisSerializer);
        // key的序列化采纳StringRedisSerializer
        template.setKeySerializer(keyStringRedisSerializer());
        template.setHashKeySerializer(keyStringRedisSerializer());
        template.setConnectionFactory(factory);
        return template;
    }

    @Bean
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory factory) throws UnknownHostException {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setKeySerializer(keyStringRedisSerializer());
        template.setHashKeySerializer(keyStringRedisSerializer());
        template.setConnectionFactory(factory);
        return template;
    }
}
6.2.3 RabbitMQ 数据隔离

6.2.3.1 主动创立影子队列

因为SpringAMQP中的

中的要害办法是公有的,无奈通过继承的形式进行实现对以配置好的队列进行扩大,所以须要自定义该类,来实现对主动创立影子队列,并和交换器进行绑定

代码实现

​ 革新RabbitListenerAnnotationBeanPostProcessor类来实现创立MQ影子队列以及将影子Key绑定到影子队列。

public class WormholeRabbitListenerAnnotationBeanPostProcessor extends RabbitListenerAnnotationBeanPostProcessor {

    @Resource
    private WormholeIsolationConfiguration wormholeIsolationConfiguration;

    /**
     * routingKey 前置处理器
     *
     * @param queueName
     * @param routingKey
     * @return
     */
    @Override
    public String preProcessingRoutingKey(String queueName, String routingKey) {
        //如果是影子队列就将routingKey转换为 影子routingKey
        if (wormholeIsolationConfiguration.checkIsolation(queueName) && !wormholeIsolationConfiguration.checkIsolation(routingKey)) {
            return wormholeIsolationConfiguration.generateIsolationKey(routingKey);
        }
        return routingKey;
    }

    /**
     * 解决队列问题,如果来了一个队列就生成一个shadow的队列
     *
     * @param queues
     * @return
     */
    @Override
    public List<String> handelQueues(List<String> queues) {
        List<String> isolationQueues = new ArrayList<>();
        if (null != queues && !queues.isEmpty()) {
            for (String queue : queues) {
                //增加shadow队列
                isolationQueues.add(wormholeIsolationConfiguration.generateIsolationKey(queue));
            }
            queues.addAll(isolationQueues);
        }
        return queues;
    }
}
6.2.3.2 传递染色标识

​ 因为MQ是异步通信,为了传递染色标识,会在发送MQ的时候将染色标识传递过去,MQ接管到之后放进以后线程的ThreadLocal外面,这个须要扩大Spring的SimpleRabbitListenerContainerFactory来实现

代码实现

public class WormholeSimpleRabbitListenerContainerFactory extends SimpleRabbitListenerContainerFactory {

    @Override
    protected SimpleMessageListenerContainer createContainerInstance() {
        SimpleMessageListenerContainer simpleMessageListenerContainer = new SimpleMessageListenerContainer();
        simpleMessageListenerContainer.setAfterReceivePostProcessors(message -> {
            //避免线程复用 销毁ThreadLocal
            WormholeContextHolder.invalidContext();
            //获取音讯属性标识
            String wormholeRequestContext = message.getMessageProperties().getHeader(WormholeContextHolder.WORMHOLE_REQUEST_MARK);
            if (StringUtils.isNotEmpty(wormholeRequestContext)) {
                WormholeContextHolder.setContext(wormholeRequestContext);
            }
            return message;
        });
        return simpleMessageListenerContainer;
    }
}
6.2.3.3 发送MQ音讯解决

​ 同上,须要传递染色标识,就通过继承RabbitTemplate重写convertAndSend办法来实现传递染色标识。

public class ShadowRabbitTemplate extends RabbitTemplate {
    public ShadowRabbitTemplate(ConnectionFactory connectionFactory) {
        super(connectionFactory);
    }

    @Autowired
    private WormholeIsolationConfiguration isolationConfiguration;

    @Override
    public void send(final String exchange, final String routingKey,
                     final Message message, @Nullable final CorrelationData correlationData)
            throws AmqpException {
        WormholeContext wormholeContext = WormholeContextHolder.getContext();
        if (null == wormholeContext) {
            super.send(exchange, routingKey, message, correlationData);
        } else {
            message.getMessageProperties().setHeader(WormholeContextHolder.WORMHOLE_REQUEST_MARK, wormholeContext.toString());
            //生成Rabbit 隔离Key
            String wormholeRoutingKey = isolationConfiguration.generateIsolationKey(routingKey);
            //调用父类进行发送
            super.send(exchange, wormholeRoutingKey, message, correlationData);
        }
    }
}

6.3 接口隔离办法

6.3.1 Mock 第三方接口

​ 对于第三方数据接口须要进行隔离,比方短信接口,失常的数据须要发送短信,对于压测数据则不能间接调用接口发送短信,并且须要可能辨认进去压测数据,并进行MOCK接口调用。

6.3.1.1 外围类实现
@Aspect
public class WormholeMockSection {

    /**
     * 切点 拦挡@WormholeMock的注解
     */
    @Pointcut("@annotation(com.heima.wormhole.component.mock.annotation.WormholeMock)")
    public void pointCut() {
    }

    /**
     * 盘绕告诉
     *
     * @param point
     * @return
     * @throws Throwable
     */
    @Around("pointCut()")
    public Object section(ProceedingJoinPoint point) throws Throwable {
        WormholeContext wormholeContext = WormholeContextHolder.getContext();
        Object[] parameter = point.getArgs();
        //如果没有wormholeContext 就执行失常办法
        if (null == wormholeContext) {
            return point.proceed(parameter);
        }
        //如果存在就执行MOCK办法
        WormholeMock wormholeMock = WormholeMockUtils.getMethodAnnotation(point, WormholeMock.class);
        if (null != wormholeMock) {
            //获取到 Mock 回调类
            WormholeMockCallback wormholeMockCallback = WormholeMockUtils.getWormholeMockCallback(wormholeMock);
            if (null != wormholeMockCallback) {
                return wormholeMockCallback.handelMockData(parameter);
            }
        }
        return null;
    }

}
6.3.1.2 应用形式

在具体方法下面加上注解就能够应用了

@Override
//退出注解进行MOCK测试拦挡 设置最大耗时
@WormholeMock(maxDelayTime = 10, minDelayTime = 2)
public boolean send(NotifyVO notifyVO) {
    logger.info("开始发送短信告诉.....");
    try {
        //模仿发送短信耗时
        Thread.sleep(5);
    } catch (InterruptedException e) {
    }
    return true;
}

6.4 零侵入计划

如果开发的中间件须要各个微服务大量革新,对开发人员来说就是一个劫难,所以这里采纳零侵入的springboot starter 来解决

6.4.1 主动拆卸

应用微服务得@Conditional来实现配置得主动拆卸,这里用MVC得配置来演示主动拆卸,其余得都是相似

这样能够最大限度的优化代码并进步很高的可扩展性。

/**
 * MVC 主动拆卸
 */
@Configuration
//当DispatcherServlet存在时该配置类才会被执行到
@ConditionalOnClass(org.springframework.web.servlet.DispatcherServlet.class)
public class WormholeMVCAutoConfiguration {

    @ConditionalOnClass
    @Bean
    public WormholeMVCConfiguration wormholeMVCConfiguration() {
        return new WormholeMVCConfiguration();
    }
}
6.4.1.1 Conditional 简介

@Conditional示意仅当所有指定条件都匹配时,组件才有资格注册 。 该@Conditional正文能够在以下任一形式应用:

  • 作为任何@Bean办法的办法级正文
  • 作为任何类的间接或间接正文的类型级别正文 @Component,包含@Configuration类
  • 作为元正文,目标是组成自定义构造型正文
6.4.1.2 Conditional派生注解

@Conditional派生了很多注解,上面给个表格列举一下派生注解的用法

@Conditional派生注解 作用(都是判断是否合乎指定的条件)
@ConditionalOnJava 零碎的java版本是否符合要求
@ConditionalOnBean 有指定的Bean类
@ConditionalOnMissingBean 没有指定的bean类
@ConditionalOnExpression 合乎指定的SpEL表达式
@ConditionalOnClass 有指定的类
@ConditionalOnMissingClass 没有指定的类
@ConditionalOnSingleCandidate 容器只有一个指定的bean,或者这个bean是首选bean
@ConditionalOnProperty 指定的property属性有指定的值
@ConditionalOnResource 门路下存在指定的资源
@ConditionalOnWebApplication 零碎环境是web环境
@ConditionalOnNotWebApplication 零碎环境不是web环境
@ConditionalOnjndi JNDI存在指定的项
6.4.2 SpringBoot starter

​ 和主动拆卸一样,Spring Boot Starter的目标也是简化配置,而Spring Boot Starter解决的是依赖治理配置简单的问题,有了它,当我须要构建一个Web应用程序时,不用再遍历所有的依赖包,一个一个地增加到我的项目的依赖治理中,而是只须要一个配置spring-boot-starter-web

6.4.2.1 应用标准

​ 在 Spring Boot starter 开发标准中,我的项目中会有一个空的名为 xxx-spring-boot-starter 的我的项目,这个我的项目次要靠 pom.xml 将所有须要的依赖引入进来。同时我的项目还会有一个 xxx-spring-boot-autoconfigure 我的项目,这个我的项目次要写带 @Configuration 注解的配置类,在这个类或者类中带 @Bean 的办法上。

6.4.2.2 我的项目应用

xxx-spring-boot-starter的我的项目下的resources文件夹上面新建一个META-INF文件,并在上面创立spring.factories文件,将咱们的主动配置类配置进去

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.heima.wormhole.autoconfiguration.WormholeAutoConfiguration

6.5 服务监控计划

6.5.1 skywalking简介

​ Skywalking 是一个APM零碎,即利用性能监控零碎,为微服务架构和云原生架构零碎设计。它通过探针主动收集所需的指标,并进行分布式追踪。通过这些调用链路以及指标,Skywalking APM会感知利用间关系和服务间关系,并进行相应的指标统计。目前反对链路追踪和监控利用组件如下,根本涵盖支流框架和容器,如国产PRC Dubbo和motan等,国际化的spring boot,spring cloud都反对了

​ SkyWalking是分布式系统的应用程序性能监督工具,专为微服务、云原生架构和基于容器(Docker、K8S、Mesos)架构而设计

​ SkyWalking是察看性剖析平台和利用性能管理系统。提供分布式追踪、服务网格遥测剖析、度量聚合和可视化一体化解决方案

6.5.1.1 SkyWalking组件
  • Skywalking Agent: 采集tracing(调用链数据)和metric(指标)信息并上报,上报通过HTTP或者gRPC形式发送数据到Skywalking Collector
  • Skywalking Collector : 链路数据收集器,对agent传过来的tracingmetric数据进行整合剖析通过Analysis Core模块解决并落入相干的数据存储中,同时会通过Query Core模块进行二次统计和监控告警
  • Storage: Skywalking的存储,反对以ElasticSearchMysqlTiDBH2等作为存储介质进行数据存储
  • UI: Web可视化平台,用来展现落地的数据,目前官网驳回了RocketBot作为SkyWalking的主UI
6.5.2 配置SkyWalking
6.5.2.1 下载SkyWalking

​ 下载SkyWalking的压缩包,解压后将压缩包外面的agent文件夹放进本地磁盘,探针蕴含整个目录,请不要扭转目录构造。

6.5.2.2 Agent配置

​ 通过理解配置,能够对一个组件性能有一个大抵的理解,解压开skywalking的压缩包,在agent/config文件夹中能够看到agent的配置文件,从skywalking反对环境变量配置加载,在启动的时候优先读取环境变量中的相干配置。

skywalking配置名称 形容
agent.namespace 跨过程链路中的header,不同的namespace会导致跨过程的链路中断
agent.service_name 一个服务(我的项目)的惟一标识,这个字段决定了在sw的UI上的对于service的展现名称
agent.sample_n_per_3_secs 客户端采样率,0或者正数标识禁用,默认-1
agent.authentication 与collector进行通信的平安认证,须要同collector中配置雷同
agent.ignore_suffix 疏忽特定申请后缀的trace
collecttor.backend_service agent须要同collector进行数据传输的IP和端口
logging.level agent记录日志级别

skywalking agent应用javaagent无侵入式的配合collector实现对分布式系统的追踪和相干数据的上下文传递。

6.5.2.3 配置探针

配置SpringBoot启动参数,须要填写如下的运行参数,代码放在前面,须要的本人粘贴。

-javaagent:D:/data/skywalking/agent/skywalking-agent.jar
-Dskywalking.agent.service_name=storage-server
-Dskywalking.collector.backend_service=172.18.0.50:11800

  • javaagent:复制的agent目录下探针的jar包门路
  • skywalking.agent.service_name:须要在skywalking显示的服务名称
  • skywalking.collector.backend_service:skywalking服务端地址默认是11800

如果本文对您有帮忙,欢送关注点赞`,您的反对是我保持创作的能源。

转载请注明出处!

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理