“我报名参加金石计划1期挑战——分割10万奖池,这是我的第3篇文章,点击查看活动概况”

今日这篇文章接上文,今日咱们继续聊聊全链路压测的具体架构规划以及5种完结计划流量染色计划、数据阻隔计划、接口阻隔计划、零侵入计划、服务监控计划。

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

大厂宠爱的全链路压测有什么含义?四种压测计划详细比照剖析

业务模块介绍

现在咱们对全体的业务进行介绍以及演示

【代码级】全链路压测的整体架构设计,以及5种实现方案(流量染色、数据隔离、接口隔离、零侵入、服务监控)

5. 全链路全体架构

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

​ 全体架构如下首要是对压测客户端的压测数据染色,全链路中间件辨认出染色数据,并将正常数据和压测数据区分隔,进行数据阻隔,这儿首要触及到mysql数据库,RabbitMQ,Redis,还需求处理由于hystrix线程池不能经过ThreadLocal传递染色表明的问题。

【代码级】全链路压测的整体架构设计,以及5种实现方案(流量染色、数据隔离、接口隔离、零侵入、服务监控)

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种实现方案(流量染色、数据隔离、接口隔离、零侵入、服务监控)

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的恳求,只需求要恳求头上增加一致的压测恳求头。

​ 经过在恳求协议中增加压测恳求的标识,在不同服务的彼此调用时,一路透传下去,这样每一个服务都能辨认出压测的恳求流量,这样做的优点是与业务完全的解耦,只需求运用框架进行感知,对业务方代码无侵入。

【代码级】全链路压测的整体架构设计,以及5种实现方案(流量染色、数据隔离、接口隔离、零侵入、服务监控)

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中,完结微服务之间的火炬传递。

【代码级】全链路压测的整体架构设计,以及5种实现方案(流量染色、数据隔离、接口隔离、零侵入、服务监控)

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,其它的恳求都会被拒绝,然后到达资源阻隔和限流保护的效果。

【代码级】全链路压测的整体架构设计,以及5种实现方案(流量染色、数据隔离、接口隔离、零侵入、服务监控)

线程池

​ 线程池阻隔技能,是用 Hystrix 自己的线程去履行调用;而信号量阻隔技能,是直接让 tomcat 线程去调用依靠服务。信号量阻隔,只是一道关卡,信号量有多少,就允许多少个 tomcat 线程经过它,然后去履行。

【代码级】全链路压测的整体架构设计,以及5种实现方案(流量染色、数据隔离、接口隔离、零侵入、服务监控)

6.1.4.2 Hystrix穿透

​ 假如运用线程池形式,那么存在一个ThreadLocal变量跨线程传递的问题,即在主线程的ThreadLocal变量,无法在线程池中运用,不过Hystrix内部供给了处理计划。

【代码级】全链路压测的整体架构设计,以及5种实现方案(流量染色、数据隔离、接口隔离、零侵入、服务监控)

封装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数据源阻隔

【代码级】全链路压测的整体架构设计,以及5种实现方案(流量染色、数据隔离、接口隔离、零侵入、服务监控)

数据阻隔需求对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 数据阻隔

【代码级】全链路压测的整体架构设计,以及5种实现方案(流量染色、数据隔离、接口隔离、零侵入、服务监控)

6.2.3.1 主动创立影子行列

由于SpringAMQP中的

中的关键办法是私有的,无法经过继承的办法进行完结对以装备好的行列进行扩展,所以需求自定义该类,来完结对主动创立影子行列,并和交换器进行绑定

【代码级】全链路压测的整体架构设计,以及5种实现方案(流量染色、数据隔离、接口隔离、零侵入、服务监控)

代码完结

​ 改造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来完结

【代码级】全链路压测的整体架构设计,以及5种实现方案(流量染色、数据隔离、接口隔离、零侵入、服务监控)

代码完结

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接口调用。

【代码级】全链路压测的整体架构设计,以及5种实现方案(流量染色、数据隔离、接口隔离、零侵入、服务监控)

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

【代码级】全链路压测的整体架构设计,以及5种实现方案(流量染色、数据隔离、接口隔离、零侵入、服务监控)

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

【代码级】全链路压测的整体架构设计,以及5种实现方案(流量染色、数据隔离、接口隔离、零侵入、服务监控)

  • javaagent:仿制的agent目录下探针的jar包路径
  • skywalking.agent.service_name:需求在skywalking显示的服务称号
  • skywalking.collector.backend_service:skywalking服务端地址默许是11800

往期干货:

  • Apache Druid数据查询套件详解计数、排名和分位数核算(送JSON-over-HTTP和SQL两种查询详解)
  • 假如你还没玩过Docker Stack办理服务,你已经out了,(送Portainer集群办理教程)
  • 怎样才能快速成为一名架构师?
  • 怎么从Java工程师生长为架构师?
  • 六种常用业务处理计划,你方唱罢,我上台(没有最好只要更好)
  • 超详细教程,一文入门Istio架构原理及实战运用
  • 【图解源码】Zookeeper3.7源码剖析,Session的办理机制,Leader选举投票规则,集群数据同步流程
  • 【图解源码】Zookeeper3.7源码剖析,包括服务发动流程源、网络通信、RequestProcessor处理恳求
  • 【知其然,知其所以然】装备中心 Apollo源码剖析
  • 【引荐】我以为这是最完好的Apollo教程从入门到通晓
  • 探针技能-JavaAgent 和字节码增强技能-Byte Buddy
  • 100003字,带你解密 双11、618电商大促场景下的系统架构系统
  • 【开悟篇】Java多线程之JUC从入门到通晓
  • 12437字,带你深化探究RPC通讯原理
  • JVM调优实战演练,妈妈再也不同忧虑我的功用优化了
  • 13651个字,给你解说清楚 JVM目标毁掉
  • 搞不懂JVM的类加载机制,JVM功用优化从何谈起?
  • 4859字,609行,一次讲清楚JVM运转数据区
  • 让实习生搭个Redis集群,差点把我”搭“进去~~~
  • 我用Redis分布式锁,抢了瓶茅台,然后GG了~~
  • 新来的,你说下Redis的耐久化机制,哪一种能处理咱们遇到的这个业务问题?

本文由传智教育博学谷教研团队发布。

假如本文对您有协助,欢迎关注点赞;假如您有任何建议也可留言谈论私信,您的支撑是我坚持创作的动力。

转载请注明出处!