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

2次阅读

共计 18699 个字符,预计需要花费 47 分钟才能阅读完成。

今日这篇文章接上文,明天咱们持续聊聊全链路压测的具体架构设计以及 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

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

转载请注明出处!

正文完
 0