十一、 Hystrix的高阶知识

11.1 request collapser请求合并技术

第六章(Request Cache请求缓存)优化过一个批量查询的接口了,request cache来做优化,相同的请求参数就可以直接取用缓存了的记录。但仍需要一次业务操作接口中需要发送多次网络请求,调用多次接口,才能拿到结果。可以使用HystrixCollapser将多个HystrixCommand合并到一起,多个command放在一个command里面去执行,发送一次网络请求,就拉取到多条数据。用请求合并技术,将多个请求合并起来,可以减少高并发访问下需要使用的线程数量以及网络连接数量,这都是hystrix自动进行的,其实对于高并发的访问来说,是可以提升性能。

 

 

 

 

  • 请求合并有很多种级别:

(1)global context,tomcat所有调用线程,对一个依赖服务的任何一个command调用都可以被合并在一起,hystrix就传递一个HystrixRequestContext。

(2)user request context,tomcat内某一个调用线程,将某一个tomcat线程对某个依赖服务的多个command调用合并在一起

(3)object modeling,基于对象的请求合并,如果有几百个对象,遍历后依次调用每个对象的某个方法,可能导致发起几百次网络请求,基于hystrix可以自动将对多个对象模型的调用合并到一起。

 

  • 请求合并的优缺点(不针对那种访问延时特别低的请求的):

缺点:使用请求合并技术的开销就是导致延迟大幅度增加,因为需要一定的时间将多个请求合并起来。

优点:可以大幅度削减你的线程池的资源耗费,线程池。减少你对后端服务访问时的网络资源的开销。

 

  • 请求合并模式及参数选择

将多个command请求合并到一个command中执行

请求合并时,可以设置一个batch size,以及elapsed time(控制什么时候触发合并后的command执行)

有两种合并模式,一种是request scope,另一种是global scope,默认是rquest scope,在collapser构造的时候指定scope模式。

request scope的batch收集是建立在一个request context内的,而global scope的batch收集是横跨多个request context的,所以对于global context来说,必须确保能在一个command内处理多个requeset context的请求

在netflix,是只用request scope请求合并的,因为默认是用唯一一个request context包含所有的command,所以要做合并,肯定就是request scope。请求合并技术,对于那种访问同一个资源的command,但是参数不同,是很有效的。

 

public static void main(String[] args) {

        String associatedIds = "1,2,3,3,4,4,5";

        String url ="http://localhost:……?associatedIds=";

        List<Future<String>> futures = new ArrayList<Future<String>>();

        for(String associatedId : associatedIds.split(",")) {

            JoinExamWholeAnalysisCollapser getUserInfosCollapser =

                    new JoinExamWholeAnalysisCollapser(associatedId,url);

            futures.add(getUserInfosCollapser.queue());

        }

        try {

            for(Future<String> future : futures) {

                System.out.println("CacheController的结果:" + future.get()); 

            }

        } catch (Exception e) {

            e.printStackTrace();

        }

    }

 

 

 

一个批量的查询ids过来以后,我们还是多个command的方式去执行,request collapser+request cache,相同的查询id还是就查询一次,不同的商品合并到一起通过一个网络请求得到结果。

private final class BatchCommand extends HystrixCommand<List<String>>{

        public final Collection<CollapsedRequest<String,String>> requests;

        private String url;

       

        public BatchCommand(Collection<CollapsedRequest<String,String>> requests, String url) {

            super(Setter

                    .withGroupKey(HystrixCommandGroupKey.Factory.asKey("AssociatedExamAllCourses"))

                    .andCommandKey(HystrixCommandKey.Factory.asKey("JoinExamWholeAnalysisesCollapserBatchCommand"))

                    );

            this.requests = requests;

            this.url = url;

        }

 

        @Override

        protected List<String> run() throws Exception {

            StringBuilder paramsBuilder = new StringBuilder("");

            for(CollapsedRequest<String, String> request : requests) {

                paramsBuilder.append(request.getArgument()).append(",");

            }

            String params = paramsBuilder.toString();

            params = params.substring(0, params.length() - 1);

            // 在这里,我们可以做到什么呢,将多个id合并在一个batch内,直接发送一次网络请求,获取到所有的结果

            String batchurl = url+"?associatedIds=" + params;

            String response = HttpClientUtils.simpleGetInvoke(batchurl, null);

            List<String> results = JSONArray.parseArray(response, String.class);

            for(String result : results) {

                System.out.println("BatchCommand内部,associatedId=" + result);

            }

            return results;

        }

    }

 

HystrixCollapser<BatchReturnType, ResponseType, RequestArgumentType>

public class JoinExamWholeAnalysisCollapser extends HystrixCollapser <List<String>, String, String>{

    private String associatedId;

    private String url;

    public JoinExamWholeAnalysisCollapser(String associatedId,String url) {

        super(Setter

                .withCollapserKey(HystrixCollapserKey.Factory.asKey("JoinExamWholeAnalysisCollapser"))

                .andCollapserPropertiesDefaults(HystrixCollapserProperties.Setter()

            //控制一个Batch中最多允许多少个request被合并,然后才会触发一个batch的执行

            //默认值是无限大,就是不依靠这个数量来触发执行,而是依靠时间

                           .withMaxRequestsInBatch(100)

             //控制一个batch创建之后,多长时间以后就自动触发batch的执行,默认是10毫秒

                           .withTimerDelayInMilliseconds(20)

                )

              );

        this.associatedId = associatedId;

        this.url = url;

    }

    @Override

    public String getRequestArgument() {

        return associatedId;

    }

    @Override

    protected HystrixCommand<List<String>> createCommand(Collection<com.netflix.hystrix.HystrixCollapser.CollapsedRequest<String, String>> requests) {

        StringBuilder paramsBuilder = new StringBuilder("");

        for(CollapsedRequest<String, String> request : requests) {

            paramsBuilder.append(request.getArgument()).append(",");

        }

        String params = paramsBuilder.toString();

        params = params.substring(0, params.length() - 1);

        System.out.println("createCommand方法执行,params=" + params);

        return new BatchCommand(requests,url);

    }

    @Override

    protected void mapResponseToRequests(List<String> batchResponse, Collection<com.netflix.hystrix.HystrixCollapser.CollapsedRequest<String, String>> requests) {

        int count = 0;

        for(CollapsedRequest<String, String> request : requests) {

            request.setResponse(batchResponse.get(count++)); 

        }

    }

    /**

     * HystrixCommandHystrixObservableCommand都可以指定一个缓存key

     * 然后hystrix会自动进行缓存,接着在同一个request context内,再次访问的时候,就会直接取用缓存用请求缓存,

     * 可以避免重复执行网络请求

     */

    @Override

    protected String getCacheKey() {

        return "joint_examWholeAnalysis_" + associatedId;

    }

  }

 

timeout问题解释:开发机上,特别慢,第一次请求的时候,几百毫秒,默认的timeout时长比较短,第二次的时候,访问的速度会快很多,就不会超时了

反应在系统上,第一次启动的时候,会有个别的超时,但是后面就好了,手动将timeout时长设置的大一些。

 

(1)maxRequestsInBatch

控制一个Batch中最多允许多少个request被合并,然后才会触发一个batch的执行

默认值是无限大,就是不依靠这个数量来触发执行,而是依靠时间

HystrixCollapserProperties.Setter()

   .withMaxRequestsInBatch(int value)

 

(2)timerDelayInMilliseconds

控制一个batch创建之后,多长时间以后就自动触发batch的执行,默认是10毫秒

HystrixCollapserProperties.Setter()

   .withTimerDelayInMilliseconds(int value)

super(Setter.withCollapserKey(HystrixCollapserKey.Factory.asKey("GetUserInfosCollapser"))

                                     .andCollapserPropertiesDefaults(HystrixCollapserProperties.Setter()

                                                           .withMaxRequestsInBatch(100)

                                                           .withTimerDelayInMilliseconds(20)));

public JoinExamWholeAnalysisCollapser(String associatedId,String url) {

        super(Setter

                .withCollapserKey(HystrixCollapserKey.Factory.asKey("JoinExamWholeAnalysisCollapser"))

                .andCollapserPropertiesDefaults(HystrixCollapserProperties.Setter()

                //控制一个Batch中最多允许多少个request被合并,然后才会触发一个batch的执行

                //默认值是无限大,就是不依靠这个数量来触发执行,而是依靠时间

                           .withMaxRequestsInBatch(100)

                //控制一个batch创建之后,多长时间以后就自动触发batch的执行,默认是10毫秒

                           .withTimerDelayInMilliseconds(20)

                )

              );

        this.associatedId = associatedId;

        this.url = url;

    }

 

11.2 fail-fast和fail-slient高阶容错模式

fail-fast:就是不给fallback降级逻辑,HystrixCommand.run(),直接报错,直接会把这个报错抛出来。

 

public class FailureModeCommand extends HystrixCommand<Boolean> {

         private boolean failure;

         public FailureModeCommand(boolean failure) {

                   super(HystrixCommandGroupKey.Factory.asKey("FailureModeGroup"));

                   this.failure = failure;

         }

         @Override

         protected Boolean run() throws Exception {

                   if(failure) {

                            throw new Exception();

                   }

                   return true;

         }

}

 

public class FailureModeCommandTest {

        

         public static void main(String[] args) {

                   try {

                            FailureModeCommand failureModeCommand = new FailureModeCommand(true);

                            failureModeCommand.execute();

                   } catch (Exception e) {

                            e.printStackTrace();

                   }

         }

        

}

 

fail-silent:给一个fallback降级逻辑,如果HystrixCommand.run()报错了,会走fallback降级,直接返回一个空值;

HystrixCommand,就给一个null;

HystrixObservableCommand,Observable.empty()。

public class FailureModeCommand extends HystrixCommand<Boolean> {

         private boolean failure;

         public FailureModeCommand(boolean failure) {

                  super(HystrixCommandGroupKey.Factory.asKey("FailureModeGroup"));

                   this.failure = failure;

         }

         @Override

         protected Boolean run() throws Exception {

                   if(failure) {

                            throw new Exception();

                   }

                   return true;

         }

 

@Override

         protected Boolean getFallback() {

                   return false;

         }

}

 

public class FailureModeCommandTest {

        

         public static void main(String[] args) {

                   try {

                            FailureModeCommand failureModeCommand = new FailureModeCommand(true);

                            System.out.println(failureModeCommand.execute());

                  } catch (Exception e) {

                            e.printStackTrace();

                   }

         }

        

}

 

11.3 stubbed fallback高阶降级模式

stubbed fallback,残缺的降级。用请求中的部分数据拼装成结果,然后再填充一些默认值,返回。比如说你发起了一个请求,然后请求中参数可能本身就附带了一些信息,如果主请求失败了,走到降级逻辑。在降级逻辑里面,可以将这个请求中的数据,以及部分本地缓存有的数据拼装在一起,再给数据填充一些简单的默认值,然后尽可能将自己有的数据返回到请求方。

         @Override

         protected UserInfo getFallback() {

                   UserInfo UserInfo = new UserInfo();

                   // 从请求参数中获取到的唯一条数据

                   UserInfo.setId(userId);

                   // 从本地缓存Ehcache中获取一些数据

                   UserInfo.setName(UserCache.getName(UserInfo.getNameById(userId))); 

                   UserInfo.setCityId(UserCache.getCityByUserId(userId));

                   // 手动填充一些默认的数据

                   UserInfo.setName("默认商品"); 

                   UserInfo.setPicture ("default.jpg");   

                   return UserInfo;

         }

 

<!-- 缓存配置 -->

    <bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">

       <property name="configLocation" value="classpath: cache/ehcache-local.xml" />

    </bean>

 

<!—用户缓存配置. -->

<userCache maxEntriesLocalHeap="100" eternal="false" timeToIdleSeconds="300" timeToLiveSeconds="600"

overflowToDisk="true" maxEntriesLocalDisk="100000" />

 

<diskStore>   : 当内存缓存中对象数量超过maxElementsInMemory,将缓存对象写到磁盘缓存中(需对象实现序列化接口)  
<diskStore path="">     : 用来配置磁盘缓存使用的物理路径,Ehcache磁盘缓存使用的文件后缀名是*.data*.index  
name : "缓存名称,cache的唯一标识(ehcache会把这个cache放到HashMap)  
maxElementsInMemory  : 缓存最大个数。
eternal="false"   : 对象是否永久有效,一但设置了,timeout将不起作用。 (必须设置)
maxEntriesLocalHeap="1000"  : 堆内存中最大缓存对象数,0没有限制(必须设置)
maxEntriesLocalDisk= "1000"   : 硬盘最大缓存个数。
overflowToDisk="false"   : 当缓存达到maxElementsInMemory值是,是否允许溢出到磁盘(必须设置)(内存不足时,是否启用磁盘缓存。)
diskSpoolBufferSizeMB  : 这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
diskPersistent="false"  : 磁盘缓存在JVM重新启动时是否保持(默认为false)
timeToIdleSeconds="0" : 导致元素过期的访问间隔(秒为单位),即当缓存闲置n秒后销毁。 eternalfalse时,这个属性才有效,0表示可以永远空闲,默认为0
timeToLiveSeconds="600" : 元素在缓存里存在的时间(秒为单位),即当缓存存活n秒后销毁. 0 表示永远存在不过期
memoryStoreEvictionPolicy="LFU" : 当达到maxElementsInMemory,如何强制进行驱逐默认使用"最近使用(LRU)"策略,其它还有先入先出FIFO,最少使用LFU,较少使用LRU
diskExpiryThreadIntervalSeconds :磁盘失效线程运行时间间隔,默认是120秒。
clearOnFlush   : 内存数量最大时是否清除。

 

 

public class CacheUtils { 

     private static CacheManager cacheManager = ((CacheManager)SpringContextHolder.getBean("cacheManager"));

 

private static Cache getCache(String cacheName){

       Cache cache = cacheManager.getCache(cacheName);

       if (cache == null){

           cacheManager.addCache(cacheName);

           cache = cacheManager.getCache(cacheName);

           cache.getCacheConfiguration().setEternal(true);

       }

       return cache;

  }

}

 

11.4 双层嵌套command实现的发送网络请求的降级模式

多级降级:command嵌套command,先降一级,尝试用一个备用方案去执行,如果备用方案失败了,再用最后下一个备用方案去执行。

 

常见的多级降级的做法,有一个操作,要访问MySQL数据库

mysql数据库访问报错,降级,去redis中获取数据

如果说redis又挂了,然后就去从本地ehcache缓存中获取数据

 

多级降级的策略:command,fallback,又套了一个command,第二个command其实是第一级降级策略。

public class GetUserInfoInfoCommand extends HystrixCommand<UserInfoInfo> {

 

    public static final HystrixCommandKey KEY = HystrixCommandKey.Factory.asKey("GetUserInfoInfoCommand");

    private Long userInfoId;

    public GetUserInfoInfoCommand(Long userInfoId) {

    super(HystrixCommandGroupKey.Factory.asKey("UserInfoInfoService"));

        this.userInfoId = userInfoId;

    }

 

    @Override

    protected UserInfoInfo run() throws Exception {

        String url = "http://127.0.0.1:8082/getUserInfoInfo?userInfoId=" + userInfoId;

        String response = HttpClientUtils.sendGetRequest(url);

        return JSONObject.parseObject(response, UserInfoInfo.class);

    }

 

    @Override

    protected UserInfoInfo getFallback() {

        return new FirstLevelFallbackCommand(userInfoId).execute();

    }

    private static class FirstLevelFallbackCommand extends HystrixCommand<UserInfoInfo> {

 

        private Long userInfoId;

        public FirstLevelFallbackCommand(Long userInfoId) {

// 第一级的降级策略,因为这个command是运行在fallback中的

// 所以至关重要的一点是,在做多级降级的时候,要将降级command的线程池单独做一个出来

// 如果主流程的command都失败了,可能线程池都已经被占满了

// 降级command必须用自己的独立的线程池

            super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("UserInfoInfoService")).andCommandKey(HystrixCommandKey.Factory.asKey("FirstLevelFallbackCommand")).andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("FirstLevelFallbackPool")));

            this.userInfoId = userInfoId;

        }

        @Override

        protected UserInfoInfo run() throws Exception {

            // 这里,因为是第一级降级的策略,所以说呢,其实是要从备用机房的机器去调用接口

            // 但是,我们这里没有所谓的备用机房,所以说还是调用同一个服务来模拟

            String url = "http://127.0.0.1:8082/getUserInfoInfo?userInfoId=" + userInfoId;

            String response = HttpClientUtils.sendGetRequest(url);

            return JSONObject.parseObject(response, UserInfoInfo.class);

        }

        @Override

        protected UserInfoInfo getFallback() {

            UserInfo UserInfo = new UserInfo();

            // 从请求参数中获取到的唯一条数据

            UserInfo.setId(userId);

            // 从本地缓存Ehcache中获取一些数据

            UserInfo.setName(UserCache.getName(UserInfo.getNameById(userId)));

            UserInfo.setCityId(UserCache.getCityByUserId(userId));

            // 手动填充一些默认的数据

            UserInfo.setName("默认商品");

            UserInfo.setPicture("default.jpg");

            return UserInfo;

        }

    }

}

 

11.5 基于facade command的服务接口的手动降级机制

在一个command它的主流程中,根据一个标识位,判断要执行哪个业务流程。如果现在知道有问题了,希望能够手动降级的话,动态给服务发送个请求在请求中修改标识位,自动就让command以后都直接过来执行备用command。

 

一般会有3个command,套在最外面的command,是用semaphore信号量做限流和资源隔离的,因为这个command不用去care timeout的问题,嵌套调用的command会自己去管理timeout超时的。

 

降级标志位:

public class IsDegrade {

    private static boolean degrade = false;

    public static boolean isDegrade() {

       return degrade;

    }

    public static void setDegrade(boolean degrade) {

       IsDegrade.degrade = degrade;

    }

}

 

套在最外面的command,是用semaphore信号量做限流和资源隔离的,不用去care timeout的问题。

public class GetUserInfoInfoFacadeCommand extends HystrixCommand<UserInfoInfo> {

    private Long userInfoId;

    public GetUserInfoInfoFacadeCommand(Long userInfoId) {

    super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("UserInfoInfoService"))

    .andCommandKey(HystrixCommandKey.Factory.asKey("GetUserInfoInfoFacadeCommand"))

                .andCommandPropertiesDefaults(HystrixCommandProperties.Setter()

                .withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE)

                     .withExecutionIsolationSemaphoreMaxConcurrentRequests(15)));

        this.userInfoId = userInfoId;

    }

    @Override

    protected UserInfoInfo run() throws Exception {

        if(!IsDegrade.isDegrade()) {

            return new GetUserInfoInfoCommand(userInfoId).execute();

        } else {

            return new GetUserInfoInfoFromMySQLCommand(userInfoId).execute();

        }

    }

   

    @Override

    protected UserInfoInfo getFallback() {

        return new UserInfoInfo();

    }

}

 

11.6 生产环境中的线程池大小以及timeout配置优化经验

(1)一开始先不要设置timeout超时时长,默认就是1000ms,也就是1s

(2)一开始也不要设置线程池大小,默认就是10

(3)直接部署hystrix到生产环境,如果运行的很良好,那么就让它这样运行好了

(4)让hystrix应用,24小时运行在生产环境中

(5)依赖标准的监控和报警机制来捕获到系统的异常运行情况

(6)在24小时之后,看一下调用延迟的占比,以及流量,来计算出让短路器生效的最小的配置数字

(7)直接对hystrix配置进行热修改,然后继续在hystrix dashboard上监控

(8)看看修改配置后的系统表现有没有改善

 

下面是根据系统表现优化和调整线程池大小,队列大小,信号量容量,以及timeout超时时间的经验

 

假设对一个依赖服务的高峰调用QPS是每秒30次

一开始如果默认的线程池大小是10

我们想的是,理想情况下,每秒的高峰访问次数 * 99%的访问延时 + buffer = 30 * 0.2 + 4 = 10线程,10个线程每秒处理30次访问应该足够了,每个线程处理3次访问

此时,我们合理的timeout设置应该为300ms,也就是99.5%的访问延时,计算方法是,因为判断每次访问延时最多在250ms(TP99如果是200ms的话),再加一次重试时间50ms,就是300ms,感觉也应该足够了

 

因为如果timeout设置的太多了,比如400ms,比如如果实际上,在高峰期,还有网络情况较差的时候,可能每次调用要耗费350ms,也就是达到了最长的访问时长

那么每个线程处理2个请求,就会执行700ms,然后处理第三个请求的时候,就超过1秒钟了,此时会导致线程池全部被占满,都在处理请求

 

这个时候下一秒的30个请求再进来了,那么就会导致线程池已满,拒绝请求的情况,就会调用fallback降级机制。因此对于短路器来说,timeout超时一般应该设置成TP99.5,比如设置成300ms,那么可以确保说,10个线程,每个线程处理3个访问,每个访问最多就允许执行300ms,过时就timeout了,这样才能保证说每个线程都在1s内执行完,才不会导致线程池被占满,然后后续的请求过来大量的reject

对于线程池大小来说,一般应该控制在10个左右,20个以内,最少5个,不要太多,也不要太少

 

大家可能会想,每秒的高峰访问次数是30次,如果是300次,甚至是3000次,30000次呢???

30000 * 0.2 = 6000 + buffer = 6100,一个服务器内一个线程池给6000个线程把

如果你一个依赖服务占据的线程数量太多的话,会导致其他的依赖服务对应的线程池里没有资源可以用了

6000 / 20 = 300台虚拟机也是ok的

虚拟机,4个cpu core,4G内存,虚拟机,300台

物理机,十几个cpu core,几十个G的内存,5~8个虚拟机,300个虚拟机 = 50台物理机

11.7 线程池的自动化动态扩容与缩容技术

  • 弹性的线程资源调度的模式

可能会出现一种情况,比如说我们的某个依赖,在高峰期,需要耗费100个线程,但是在那个时间段,刚好其他的依赖的线程池其实就维持一两个就可以了。但是,如果我们都是设置死的,每个服务就给10个线程,那就很坑,可能就导致有的服务在高峰期需要更多的资源,但是没资源了,导致很多的reject。但是其他的服务,每秒钟就易一两个请求,结果也占用了10个线程,占着茅坑不拉屎

刚开始的时候,每个依赖服务都是给1个线程,3个线程,但是我们允许说,如果你的某个线程池突然需要大量的线程,最多可以到100个线程。如果你使用了100个线程,高峰期过去了,自动将空闲的线程给释放掉。

 

(1)coreSize

设置线程池的大小,默认是10

HystrixThreadPoolProperties.Setter() .withCoreSize(int value)

HystrixThreadPoolProperties.Setter().withCoreSize(10)

//CoreSize:设置线程池的大小,默认是10,如果你不设置另外两个queue相关的参数,等待队列是关闭

 

(2)maximumSize

设置线程池的最大大小,只有在设置allowMaximumSizeToDivergeFromCoreSize的时候才能生效,默认是10。

HystrixThreadPoolProperties.Setter()

   .withMaximumSize(int value)

//设置线程池的最大大小,只有在设置allowMaximumSizeToDivergeFromCoreSize的时候才能生效

.withMaximumSize(15)

 

(3)allowMaximumSizeToDivergeFromCoreSize

允许线程池大小自动动态调整,设置为true之后,maxSize就生效了,此时如果一开始是coreSize个线程,随着并发量上来,那么就会自动获取新的线程,但是如果线程在keepAliveTimeMinutes内空闲,就会被自动释放掉

默认是fales

 

HystrixThreadPoolProperties.Setter().withAllowMaximumSizeToDivergeFromCoreSize(boolean value)

//允许线程池大小自动动态调整(默认为false),设置为true之后,maxSize就生效了,此时如果一开始是coreSize个线程,随着并发量上来,那么就会自动获取新的线程,

但是如果线程在keepAliveTimeMinutes内空闲,就会被自动释放掉

.withAllowMaximumSizeToDivergeFromCoreSize(true)

 

(4)keepAliveTimeMinutes

设置保持存活的时间,单位是分钟,默认是1

如果设置allowMaximumSizeToDivergeFromCoreSize为true,那么coreSize就不等于maxSize,此时线程池大小是可以动态调整的,可以获取新的线程,也可以释放一些线程

如果coreSize < maxSize,那么这个参数就设置了一个线程多长时间空闲之后,就会被释放掉

//如果coreSize < maxSize,那么这个参数就设置了一个线程多长时间空闲之后,就会被释放掉

.withKeepAliveTimeMinutes(1)

 

生产环境中,这块怎么玩儿的?

也是根据你的服务的实际的运行的情况切看的,比如说你发现某个服务,平时3个并发QPS就够了,高峰期可能要到30个,那么你就可以给设置弹性的资源调度。

注:因为有可能一个服务会有多个线程池,你要计算好,每个线程池的最大的大小加起来不能过大,30个依赖,30个线程池,每个线程池最大给到30,900个线程,很坑的

 

还有一种模式,就是说让多个依赖服务共享一个线程池,我们不推荐,多个依赖服务就做不到资源隔离,互相之间会影响的

11.8 Hystrix的metric高阶配置

1、为什么需要监控与报警?

 

HystrixCommand执行的时候,会生成一些执行耗时等方面的统计信息。这些信息对于系统的运维来说,是很有帮助的,因为我们通过这些统计信息可以看到整个系统是怎么运行的。hystrix对每个command key都会提供一份metric,而且是秒级统计粒度的。

 

这些统计信息,无论是单独看,还是聚合起来看,都是很有用的。如果将一个请求中的多个command的统计信息拿出来单独查看,包括耗时的统计,对debug系统是很有帮助的。聚合起来的metric对于系统层面的行为来说,是很有帮助的,很适合做报警或者报表。hystrix dashboard就很适合。

 

2、hystrix的事件类型

 

对于hystrix command来说,只会返回一个值,execute只有一个event type,fallback也只有一个event type,那么返回一个SUCCESS就代表着命令执行的结束

对于hystrix observable command来说,多个值可能被返回,所以emit event代表一个value被返回,success代表成功,failure代表异常

 

(1)execute event type

 

EMIT                                              observable command返回一个value

SUCCESS                                      完成执行,并且没有报错

FAILURE                                        执行时抛出了一个异常,会触发fallback

TIMEOUT                                               开始执行了,但是在指定时间内没有完成执行,会触发fallback

BAD_REQUEST                                     执行的时候抛出了一个HystrixBadRequestException

SHORT_CIRCUITED                   短路器打开了,触发fallback

THREAD_POOL_REJECTED      线程成的容量满了,被reject,触发fallback

SEMAPHORE_REJECTED          信号量的容量满了,被reject,触发fallback

 

(2)fallback event type

 

FALLBACK_EMIT                         observable command,fallback value被返回了

FALLBACK_SUCCESS                  fallback逻辑执行没有报错

FALLBACK_FAILURE          fallback逻辑抛出了异常,会报错

FALLBACK_REJECTION              fallback的信号量容量满了,fallback不执行,报错

FALLBACK_MISSING                  fallback没有实现,会报错

 

(3)其他的event type

 

EXCEPTION_THROWN              command生命自周期是否抛出了异常

RESPONSE_FROM_CACHE       command是否在cache中查找到了结果

COLLAPSED                                  command是否是一个合并batch中的一个

 

(4)thread pool event type

 

EXECUTED                                    线程池有空间,允许command去执行了

REJECTED                                   线程池没有空间,不允许command执行,reject掉了

 

(5)collapser event type

 

BATCH_EXECUTED                    collapser合并了一个batch,并且执行了其中的command

ADDED_TO_BATCH                            command加入了一个collapser batch

RESPONSE_FROM_CACHE                没有加入batch,而是直接取了request cache中的数据

 

3、metric storage

 

metric被生成之后,就会按照一段时间来存储,存储了一段时间的数据才会推送到其他系统中,比如hystrix dashboard

 

另外一种方式,就是每次生成metric就实时推送metric流到其他地方,但是这样的话,会给系统带来很大的压力(不建议采用)

 

hystrix的方式是将metric写入一个内存中的数据结构中,在一段时间之后就可以查询到

 

hystrix 1.5x之后,采取的是为每个command key都生成一个start event和completion event流,而且可以订阅这个流。每个thread pool key也是一样的,包括每个collapser key也是一样的。

 

每个command的event是发送给一个线程安全的RxJava中的rx.Subject,因为是线程安全的,所以不需要进行线程同步

 

因此每个command级别的,threadpool级别的,每个collapser级别的,event都会发送到对应的RxJava的rx.Subject对象中。这些rx.Subject对象接着就会被暴露出Observable接口,可以被订阅。

 

4、metric统计相关的配置

 

(1)metrics.rollingStats.timeInMilliseconds

 

设置统计的rolling window,单位是毫秒,hystrix只会维持这段时间内的metric供短路器统计使用

 

这个属性是不允许热修改的,默认值是10000,就是10秒钟

 

HystrixCommandProperties.Setter()

   .withMetricsRollingStatisticalWindowInMilliseconds(int value)

 

(2)metrics.rollingStats.numBuckets

 

该属性设置每个滑动窗口被拆分成多少个bucket,而且滑动窗口对这个参数必须可以整除,同样不允许热修改

 

默认值是10,也就是说,每秒钟是一个bucket

 

随着时间的滚动,比如又过了一秒钟,那么最久的一秒钟的bucket就会被丢弃,然后新的一秒的bucket会被创建

 

HystrixCommandProperties.Setter()

   .withMetricsRollingStatisticalWindowBuckets(int value)

 

(3)metrics.rollingPercentile.enabled

 

控制是否追踪请求耗时,以及通过百分比方式来统计,默认是true

 

HystrixCommandProperties.Setter()

   .withMetricsRollingPercentileEnabled(boolean value)

 

(4)metrics.rollingPercentile.timeInMilliseconds

 

设置rolling window被持久化保存的时间,这样才能计算一些请求耗时的百分比,默认是60000,60s,不允许热修改

 

相当于是一个大的rolling window,专门用于计算请求执行耗时的百分比

 

HystrixCommandProperties.Setter()

   .withMetricsRollingPercentileWindowInMilliseconds(int value)

 

(5)metrics.rollingPercentile.numBuckets

 

设置rolling percentile window被拆分成的bucket数量,上面那个参数除以这个参数必须能够整除,不允许热修改

 

默认值是6,也就是每10s被拆分成一个bucket

 

HystrixCommandProperties.Setter()

   .withMetricsRollingPercentileWindowBuckets(int value)

 

(6)metrics.rollingPercentile.bucketSize

 

设置每个bucket的请求执行次数被保存的最大数量,如果再一个bucket内,执行次数超过了这个值,那么就会重新覆盖从bucket的开始再写

 

举例来说,如果bucket size设置为100,而且每个bucket代表一个10秒钟的窗口,但是在这个bucket内发生了500次请求执行,那么这个bucket内仅仅会保留100次执行

 

如果调大这个参数,就会提升需要耗费的内存,来存储相关的统计值,不允许热修改

 

默认值是100

 

HystrixCommandProperties.Setter()

   .withMetricsRollingPercentileBucketSize(int value)

 

(7)metrics.healthSnapshot.intervalInMilliseconds

 

控制成功和失败的百分比计算,与影响短路器之间的等待时间,默认值是500毫秒

 

HystrixCommandProperties.Setter()

   .withMetricsHealthSnapshotIntervalInMilliseconds(int value)

11.9 基于hystrix dashboard的可视化分布式系统监控

1、安装metrics stream POM

<dependency>

    <groupId>com.netflix.hystrix</groupId>

    <artifactId>hystrix-metrics-event-stream</artifactId>

    <version>1.4.10</version>

</dependency>

 

2、在系统入口注册Servlet,拦截hystrix.stream

    @Bean

    public ServletRegistrationBean indexServletRegistration() {

        ServletRegistrationBean registration = new ServletRegistrationBean(new HystrixMetricsStreamServlet());

        registration.addUrlMappings("/hystrix.stream");

        return registration;

    }

 

3、下载tomcat7解压缩

4、下载hystrix-dashboard的war包,

cp hystrix-dashboard-*.war apache-tomcat-7.*/webapps/hystrix-dashboard.war

5、下载turbin(整个集群的监控)

下载并解压缩

cp turbine-web/build/libs/turbine-web-*.war ./apache-tomcat-7.*/webapps/turbine.war

在/WEB-INF/classes下放置配置文件

config.properties

turbine.ConfigPropertyBasedDiscovery.default.instances=localhost

turbine.instanceUrlSuffix=:8081/hystrix.stream

6、启动我们的服务

 

7、启动tomcat中的hystrix dashboard和turbin

localhost:8080/hystrix-dashboard

http://localhost:8081/hystrix.stream,监控单个机器

http://localhost:8080/turbine/turbine.stream,监控整个集群

 

以上为配置在SpringBoot中集成Hystrix Dashboard,在SpringCloud中可以直接通过配置

<!---Hystrix Dashboard(只能看到单个应用内的服务信息) 的使用 可以单独部署应用 http://localhost:8761/hystrix http://localhost:8888/hystrix.stream-->

        <dependency>

            <groupId>org.springframework.cloud</groupId>

            <artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>

        </dependency>

 在启动类中添加注解@EnableHystrixDashboard

uploading.4e448015.gif转存失败重新上传取消uploading.4e448015.gif正在上传…重新上传取消uploading.4e448015.gif转存失败重新上传取消

http://localhost:8761/hystrix

http://localhost:8888/hystrix.stream

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章