大型电商架构亿级流量电商详情页系统实战-缓存架构+高可用服务架构+微服务架构(六)

七十五、深入理解hystrix的短路器执行原理以及模拟接口异常时的短路实验

短路器深入的工作原理

1、如果经过短路器的流量超过了一定的阈值,HystrixCommandProperties.circuitBreakerRequestVolumeThreshold()

举个例子,可能看起来是这样子的,要求在10s内,经过短路器的流量必须达到20个;在10s内,经过短路器的流量才10个,那么根本不会去判断要不要短路

2、如果断路器统计到的异常调用的占比超过了一定的阈值,HystrixCommandProperties.circuitBreakerErrorThresholdPercentage()

如果达到了上面的要求,比如说在10s内,经过短路器的流量(你,只要执行一个command,这个请求就一定会经过短路器),达到了30个;同时其中异常的访问数量,占到了一定的比例,比如说60%的请求都是异常(报错,timeout,reject),会开启短路

3、然后断路器从close状态转换到open状态

4、断路器打开的时候,所有经过该断路器的请求全部被短路,不调用后端服务,直接走fallback降级

5、经过了一段时间之后,HystrixCommandProperties.circuitBreakerSleepWindowInMilliseconds(),会half-open,让一条请求经过短路器,看能不能正常调用。如果调用成功了,那么就自动恢复,转到close状态

短路器,会自动恢复的,half-open,半开状态

6、circuit breaker短路器的配置

(1)circuitBreaker.enabled

控制短路器是否允许工作,包括跟踪依赖服务调用的健康状况,以及对异常情况过多时是否允许触发短路,默认是true

HystrixCommandProperties.Setter()
.withCircuitBreakerEnabled(boolean value)

(2)circuitBreaker.requestVolumeThreshold

设置一个rolling window,滑动窗口中,最少要有多少个请求时,才触发开启短路

举例来说,如果设置为20(默认值),那么在一个10秒的滑动窗口内,如果只有19个请求,即使这19个请求都是异常的,也是不会触发开启短路器的

HystrixCommandProperties.Setter()
.withCircuitBreakerRequestVolumeThreshold(int value)

(3)circuitBreaker.sleepWindowInMilliseconds

设置在短路之后,需要在多长时间内直接reject请求,然后在这段时间之后,再重新导holf-open状态,尝试允许请求通过以及自动恢复,默认值是5000毫秒

HystrixCommandProperties.Setter()
.withCircuitBreakerSleepWindowInMilliseconds(int value)

(4)circuitBreaker.errorThresholdPercentage

设置异常请求量的百分比,当异常请求达到这个百分比时,就触发打开短路器,默认是50,也就是50%

HystrixCommandProperties.Setter()
.withCircuitBreakerErrorThresholdPercentage(int value)

(5)circuitBreaker.forceOpen

如果设置为true的话,直接强迫打开短路器,相当于是手动短路了,手动降级,默认false

HystrixCommandProperties.Setter()
.withCircuitBreakerForceOpen(boolean value)

(6)circuitBreaker.forceClosed

如果设置为ture的话,直接强迫关闭短路器,相当于是手动停止短路了,手动升级,默认false

HystrixCommandProperties.Setter()
.withCircuitBreakerForceClosed(boolean value)

7、实战演练

配置一个断路器,流量要求是20,异常比例是50%,短路时间是5s

在command内加入一个判断,如果是productId=-1,那么就直接报错,触发异常执行

写一个client测试程序,写入50个请求,前20个是正常的,但是后30个是productId=-1,然后继续请求,会发现

七十六、深入理解线程池隔离技术的设计原则以及动手实战接口限流实验

1、command的创建和执行:资源隔离
2、request cache:请求缓存
3、fallback:优雅降级
4、circuit breaker:短路器,快速熔断(一旦后端服务故障,立刻熔断,阻止对其的访问)

把一个分布式系统中的某一个服务,打造成一个高可用的服务

资源隔离,优雅降级,熔断

5、判断,线程池或者信号量的容量是否已满,reject,限流

限流,限制对后端的服务的访问量,比如说你对mysql,redis,zookeeper,各种后端的中间件的资源,访问,其实为了避免过大的流浪打死后端的服务,线程池,信号量,限流

限制服务对后端的资源的访问

1、线程池隔离技术的设计原则

Hystrix采取了bulkhead舱壁隔离技术,来将外部依赖进行资源隔离,进而避免任何外部依赖的故障导致本服务崩溃

线程池隔离,学术名称:bulkhead,舱壁隔离

外部依赖的调用在单独的线程中执行,这样就能跟调用线程隔离开来,避免外部依赖调用timeout耗时过长,导致调用线程被卡死

Hystrix对每个外部依赖用一个单独的线程池,这样的话,如果对那个外部依赖调用延迟很严重,最多就是耗尽那个依赖自己的线程池而已,不会影响其他的依赖调用

Hystrix选择用线程池机制来进行资源隔离,要面对的场景如下:

(1)每个服务都会调用几十个后端依赖服务,那些后端依赖服务通常是由很多不同的团队开发的
(2)每个后端依赖服务都会提供它自己的client调用库,比如说用thrift的话,就会提供对应的thrift依赖
(3)client调用库随时会变更
(4)client调用库随时可能会增加新的网络请求的逻辑
(5)client调用库可能会包含诸如自动重试,数据解析,内存中缓存等逻辑
(6)client调用库一般都对调用者来说是个黑盒,包括实现细节,网络访问,默认配置,等等
(7)在真实的生产环境中,经常会出现调用者,突然间惊讶的发现,client调用库发生了某些变化
(8)即使client调用库没有改变,依赖服务本身可能有会发生逻辑上的变化
(9)有些依赖的client调用库可能还会拉取其他的依赖库,而且可能那些依赖库配置的不正确
(10)大多数网络请求都是同步调用的
(11)调用失败和延迟,也有可能会发生在client调用库本身的代码中,不一定就是发生在网络请求中

简单来说,就是你必须默认client调用库就很不靠谱,而且随时可能各种变化,所以就要用强制隔离的方式来确保任何服务的故障不能影响当前服务

我不知道在学习这个课程的学员里,有多少人,真正参与过一些复杂的分布式系统的开发,不是说一个team,你们五六个人,七八个人,去做的

在一些大公司里,做一些复杂的项目的话,广告计费系统,特别复杂,可能涉及多个团队,总共三四十个人,五六十个人,一起去开发一个系统,每个团队负责一块儿

每个团队里的每个人,负责一个服务,或者几个服务,比较常见的大公司的复杂分布式系统项目的分工合作的一个流程

线程池机制的优点如下:

(1)任何一个依赖服务都可以被隔离在自己的线程池内,即使自己的线程池资源填满了,也不会影响任何其他的服务调用
(2)服务可以随时引入一个新的依赖服务,因为即使这个新的依赖服务有问题,也不会影响其他任何服务的调用
(3)当一个故障的依赖服务重新变好的时候,可以通过清理掉线程池,瞬间恢复该服务的调用,而如果是tomcat线程池被占满,再恢复就很麻烦
(4)如果一个client调用库配置有问题,线程池的健康状况随时会报告,比如成功/失败/拒绝/超时的次数统计,然后可以近实时热修改依赖服务的调用配置,而不用停机
(5)如果一个服务本身发生了修改,需要重新调整配置,此时线程池的健康状况也可以随时发现,比如成功/失败/拒绝/超时的次数统计,然后可以近实时热修改依赖服务的调用配置,而不用停机
(6)基于线程池的异步本质,可以在同步的调用之上,构建一层异步调用层

简单来说,最大的好处,就是资源隔离,确保说,任何一个依赖服务故障,不会拖垮当前的这个服务

线程池机制的缺点:

(1)线程池机制最大的缺点就是增加了cpu的开销

除了tomcat本身的调用线程之外,还有hystrix自己管理的线程池

(2)每个command的执行都依托一个独立的线程,会进行排队,调度,还有上下文切换
(3)Hystrix官方自己做了一个多线程异步带来的额外开销,通过对比多线程异步调用+同步调用得出,Netflix API每天通过hystrix执行10亿次调用,每个服务实例有40个以上的线程池,每个线程池有10个左右的线程
(4)最后发现说,用hystrix的额外开销,就是给请求带来了3ms左右的延时,最多延时在10ms以内,相比于可用性和稳定性的提升,这是可以接受的

我们可以用hystrix semaphore技术来实现对某个依赖服务的并发访问量的限制,而不是通过线程池/队列的大小来限制流量

sempahore技术可以用来限流和削峰,但是不能用来对调研延迟的服务进行timeout和隔离

execution.isolation.strategy,设置为SEMAPHORE,那么hystrix就会用semaphore机制来替代线程池机制,来对依赖服务的访问进行限流

如果通过semaphore调用的时候,底层的网络调用延迟很严重,那么是无法timeout的,只能一直block住

一旦请求数量超过了semephore限定的数量之后,就会立即开启限流

2、接口限流实验

假设,一个线程池,大小是15个,队列大小是10个,timeout时长设置的长一些,5s

模拟发送请求,然后写死代码,在command内部做一个sleep,比如每次sleep 1s,10个请求发送过去以后,直接被hang死,线程池占满

再发送请求,就会堵塞在缓冲队列,queue,10个,20个,10个,后10个应该就直接reject,fallback逻辑

15 + 10 = 25个请求,15在执行,10个缓冲在队列里了,剩下的流量全部被reject,限流,降级

withCoreSize:设置你的线程池的大小
withMaxQueueSize:设置的是你的等待队列,缓冲队列的大小
withQueueSizeRejectionThreshold:如果withMaxQueueSize<withQueueSizeRejectionThreshold,那么取的是withMaxQueueSize,反之,取得是withQueueSizeRejectionThreshold

线程池本身的大小,如果你不设置另外两个queue相关的参数,等待队列是关闭的

queue大小,等待队列的大小,timeout时长

先进去线程池的是10个请求,然后有8个请求进入等待队列,线程池里有空闲,等待队列中的请求如果还没有timeout,那么就进去线程池去执行

10 + 8 = 18个请求之外,7个请求,直接会被reject掉,限流,fallback

withExecutionTimeoutInMilliseconds(20000):timeout也设置大一些,否则如果请求放等待队列中时间太长了,直接就会timeout,等不到去线程池里执行了
withFallbackIsolationSemaphoreMaxConcurrentRequests(30):fallback,sempahore限流,30个,避免太多的请求同时调用fallback被拒绝访问

/**
*withCoreSize:设置你的线程池的大小
withMaxQueueSize:设置的是你的等待队列,缓冲队列的大小
withQueueSizeRejectionThreshold:如果withMaxQueueSize<withQueueSizeRejectionThreshold,那么取的是withMaxQueueSize,反之,取得是withQueueSizeRejectionThreshold
*/
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ProductInfoService"))
				.andCommandKey(HystrixCommandKey.Factory.asKey("GetProductInfoCommand"))
				.andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("GetProductInfoPool"))
				.andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter()
						.withCoreSize(10)
						.withMaxQueueSize(12)
						.withQueueSizeRejectionThreshold(15)) 
				.andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
						.withCircuitBreakerRequestVolumeThreshold(30)
						.withCircuitBreakerErrorThresholdPercentage(40)
						.withCircuitBreakerSleepWindowInMilliseconds(3000)
						.withExecutionTimeoutInMilliseconds(20000)
						.withFallbackIsolationSemaphoreMaxConcurrentRequests(30))  
				);  
				
public class RejectTest {
	
	public static void main(String[] args) throws Exception {
		for(int i = 0; i < 25; i++) {
			new TestThread(i).start();
		}
	}
	
	private static class TestThread extends Thread {
		
		private int index;
		
		public TestThread(int index) {
			this.index = index;
		}
		
		@Override
		public void run() {
			String response = HttpClientUtils.sendGetRequest("http://localhost:8081/getProductInfo?productId=-2");  
			System.out.println("第" + (index + 1) + "次请求,结果为:" + response);  
		}
		
	}
	
}
七十七、基于timeout机制来为商品服务接口的调用超时提供安全保护

一般来说,在调用依赖服务的接口的时候,比较常见的一个问题,就是超时

超时是在一个复杂的分布式系统中,导致不稳定,或者系统抖动,或者出现说大量超时,线程资源hang死,吞吐量大幅度下降,甚至服务崩溃

超时最大的一个问题

你去调用各种各样的依赖服务,特别是在大公司,你甚至都不认识开发一个服务的人,你都不知道那个人的水平怎么样,不了解

比尔盖茨说过一句话,在互联网的另外一头,你都不知道甚至坐着一条狗

分布式系统,大公司,多个团队,大型协作,服务是谁的,不了解,很可能说那个哥儿们,实习生都有可能

在一个复杂的系统里,可能你的依赖接口的性能很不稳定,有时候2ms,200ms,2s

如果你不对各种依赖接口的调用,做超时的控制,来给你的服务提供安全保护措施,那么很可能你的服务就被各种垃圾的依赖服务的性能给拖死了

大量的接口调用很慢,大量线程就卡死了,资源隔离,线程池的线程卡死了,超时的控制

(1)execution.isolation.thread.timeoutInMilliseconds

手动设置timeout时长,一个command运行超出这个时间,就被认为是timeout,然后将hystrix command标识为timeout,同时执行fallback降级逻辑

默认是1000,也就是1000毫秒

HystrixCommandProperties.Setter()
.withExecutionTimeoutInMilliseconds(int value)

(2)execution.timeout.enabled

控制是否要打开timeout机制,默认是true

HystrixCommandProperties.Setter()
.withExecutionTimeoutEnabled(boolean value)

让一个command执行timeout,然后看是否会调用fallback降级

七十八、基于hystrix的高可用分布式系统架构项目实战课程的总结

hystrix的核心知识

1、hystrix内部工作原理:8大执行步骤和流程
2、资源隔离:你如果有很多个依赖服务,高可用性,先做资源隔离,任何一个依赖服务的故障不会导致你的服务的资源耗尽,不会崩溃
3、请求缓存:对于一个request context内的多个相同command,使用request cache,提升性能
4、熔断:基于短路器,采集各种异常事件,报错,超时,reject,短路,熔断,一定时间范围内就不允许访问了,直接降级,自动恢复的机制
5、降级:报错,超时,reject,熔断,降级,服务提供容错的机制
6、限流:在你的服务里面,通过线程池,或者信号量,限制对某个后端的服务或资源的访问量,避免从你的服务这里过去太多的流量,打死某个资源
7、超时:避免某个依赖服务性能过差,导致大量的线程hang住去调用那个服务,会导致你的服务本身性能也比较差

hystrix的高阶知识

1、request collapser,请求合并技术
2、fail-fast和fail-slient,高阶容错模式
3、static fallback和stubbed fallback,高阶降级模式
4、嵌套command实现的发送网络请求的降级模式
5、基于facade command的多级降级模式
6、request cache的手动清理
7、生产环境中的线程池大小以及timeout配置优化经验
8、线程池的自动化动态扩容与缩容技术
9、hystrix的metric高阶配置
10、基于hystrix dashboard的可视化分布式系统监控
11、生产环境中的hystrix工程运维经验

七十九、基于request collapser请求合并技术进一步优化批量查询

hystrix,高级的技术,request collapser,请求合并技术,collapser折叠

优化过一个批量查询的接口了,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可以自动将对多个对象模型的调用合并到一起

请求合并技术的开销有多大

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

发送过来10个请求,每个请求本来大概是2ms可以返回,要把10个请求合并在一个command内,统一一起执行,先后等待一下,5ms

所以说,要考量一下,使用请求合并技术是否合适,如果一个请求本来耗费的时间就比较长,那么进行请求合并,增加一些延迟影响并不大

请求合并技术,不是针对那种访问延时特别低的请求的,比如说你的访问延时本身就比较高,20ms,10个请求合并在一起,25ms,这种情况下就还好

好处在哪里,大幅度削减你的线程池的资源耗费,线程池,10个线程,一秒钟可以执行10个请求,合并在一起,1个线程执行10个请求,10个线程就可以执行100个请求

增加你的吞吐量

减少你对后端服务访问时的网络资源的开销,10个请求,10个command,10次网络请求的开销,1次网络请求的开销了

每个请求就2ms,batch,810ms,延迟增加了45倍

每个请求本来就30ms50ms,batch,35ms55ms,延迟增加不太明显

将多个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,但是参数不同,是很有效的

批量查询,HystrixObservableCommand,HystrixCommand+request cache,都是每个商品发起一次网络请求

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

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(“GetProductInfosCollapser”))
.andCollapserPropertiesDefaults(HystrixCollapserProperties.Setter()
.withMaxRequestsInBatch(100)
.withTimerDelayInMilliseconds(20)));

public class GetProductInfosCollapser extends HystrixCollapser<List<ProductInfo>, ProductInfo, Long> {

    private Long productId;

    public GetProductInfosCollapser(Long productId) {
        this.productId = productId;
    }

    @Override
    public Long getRequestArgument() {
        return productId;
    }

    @Override
    protected HystrixCommand<List<ProductInfo>> createCommand(Collection<CollapsedRequest<ProductInfo, Long>> requests) {
        StringBuilder paramsBuilder = new StringBuilder("");
        for(CollapsedRequest<ProductInfo, Long> 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);
    }

    @Override
    protected void mapResponseToRequests(List<ProductInfo> productInfos, Collection<CollapsedRequest<ProductInfo, Long>> requests) {
        int count = 0;
        for (CollapsedRequest<ProductInfo, Long> request : requests) {
            request.setResponse(productInfos.get(count++));
        }
    }

    private static final class BatchCommand extends HystrixCommand<List<ProductInfo>> {

        private final Collection<CollapsedRequest<ProductInfo, Long>> requests;

        public BatchCommand(Collection<CollapsedRequest<ProductInfo, Long>> requests) {
            super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ProductInfoService"))
                    .andCommandKey(HystrixCommandKey.Factory.asKey("GetProductInfosCollapserBatchCommand")));
            this.requests = requests;
        }

        @Override
        protected List<ProductInfo> run() throws Exception {
            // 将一个批次内的商品id给拼接在了一起
            StringBuilder sb = new StringBuilder("");
            for (CollapsedRequest<ProductInfo, Long> request : requests) {
                sb.append(request.getArgument()).append(",");
            }
            String params = sb.toString();
            params = params.substring(0, params.length() - 1);

            // 在这里,我们可以做到什么呢,将多个商品id合并在一个batch内,直接发送一次网络请求,获取到所有的结果
            String response = HttpClientUtils.sendGetRequest("http://localhost:8082/getProductInfos?productIds=" + params);
            List<ProductInfo> productInfos = JSONArray.parseArray(response, ProductInfo.class);
            for (ProductInfo productInfo : productInfos) {
                System.out.println("BatchCommand内部,productInfo=" + productInfo);
            }
            return productInfos;
        }
    }

    @Override
    protected String getCacheKey() {
        return "product_info_" + productId;
    }
}
八十、hystirx的fail-fast与fail-silient两种最基础的容错模式

fail-fast,就是不给fallback降级逻辑,HystrixCommand.run(),直接报错,直接会把这个报错抛出来,给你的tomcat调用线程

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

HystrixObservableCommand,Observable.empty()

很少会用fail-fast模式,比较常用的可能还是fail-silent,特别常用,既然都到了fallback里面,肯定要做点降级的事情

八十一、为商品服务接口调用增加stubbed fallback降级机制

stubbed fallback,残缺的降级

用请求中的部分数据拼装成结果,然后再填充一些默认值,返回

比如说你发起了一个请求,然后请求中可能本身就附带了一些信息,如果主请求失败了,走到降级逻辑

在降级逻辑里面,可以将这个请求中的数据,以及部分本地缓存有的数据拼装在一起,再给数据填充一些简单的默认值

然后尽可能将自己有的数据返回到请求方

stubbed,残缺了,比如说应该查询到一个商品信息,里面包含20个字段

请求参数搂出来一两个字段,从本地的少量缓存中比如说,可以搂出来那么两三个字段,最终的话返回的字段可能就五六个,其他的字段都是填充的默认值

数据有残缺

八十二、基于双层嵌套command开发商品服务接口的多级降级机制

多级降级

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

command嵌套command

尝试从备用服务器接口去拉取结果

给大家科普一下,常见的多级降级的做法,有一个操作,要访问MySQL数据库

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

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

hystrix command fallback语义,很容易就可以实现多级降级的策略

商品服务接口,多级降级的策略

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

第二个command的fallback是第二级降级策略

第一级降级策略,可以是

storm,我们之前做storm这块,第一级降级,一般是搞一个storm的备用机房,部署了一套一模一样的拓扑,如果主机房中的storm拓扑挂掉了,备用机房的storm拓扑定顶上

如果备用机房的storm拓扑也挂了

第二级降级,可能就降级成用mysql/hbase/redis/es,手工封装的一套,按分钟粒度去统计数据的系统

第三季降级,离线批处理去做,hdfs+spark,每个小时执行一次数据统计,去降级

特别复杂,重要的系统,肯定是要搞好几套备用方案的,一个方案死了,立即上第二个方案,而且要尽量做到是自动化的

商品接口拉取

主流程,访问的商品服务,是从主机房去访问的,服务,如果主机房的服务出现了故障,机房断电,机房的网络负载过高,机器硬件出了故障

第一级降级策略,去访问备用机房的服务

第二级降级策略,用stubbed fallback降级策略,比较常用的,返回一些残缺的数据回去

public class GetProductInfoCommand extends HystrixCommand<ProductInfo> {

    private Long productId;

    public GetProductInfoCommand(Long productId) {
        super(HystrixCommandGroupKey.Factory.asKey("ProductInfoService"));
        this.productId = productId;
    }

    @Override
    protected ProductInfo run() throws Exception {
        if (productId == -1L) {
            throw new Exception();
        }
        if (productId == -2L) {
            throw new Exception();
        }
        String response = HttpClientUtils.sendGetRequest("http://127.0.0.1:8082/getProductInfo/" + productId);
        return JSONObject.parseObject(response, ProductInfo.class);
    }

    /**
     * 采用嵌套多级降级
     */
    private static class FirstLevelFallbackCommand extends HystrixCommand<ProductInfo> {

        private Long productId;

        public FirstLevelFallbackCommand(Long productId) {
            /**
             * 第一级的降级策略,因为这个command是运行在fallback中的
             * 所以至关重要的一点是,在做多级降级的时候,要将降级command的线程池单独做一个出来
             * 如果主流程的command都失败了,可能线程池都已经沾满了
             * 降级command必须用自己的独立线程池
             */
            super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ProductInfoService"))
                    .andCommandKey(HystrixCommandKey.Factory.asKey("FirstLevelFallbackCommand"))
                    .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("FirstLevelFallbackPool"))
            );
            this.productId = productId;
        }

        @Override
        protected ProductInfo run() throws Exception {
            // 这里,因为是第一级降级的策略,所以说呢,其实是要从备用机房的机器去调用接口
            // 但是,我们这里没有所谓的备用机房,所以说还是调用同一个服务来模拟
            if (productId == -2L) {
                throw new Exception();
            }
            String response = HttpClientUtils.sendGetRequest("http://127.0.0.1:8082/getProductInfo/" + productId);
            return JSONObject.parseObject(response, ProductInfo.class);
        }

        @Override
        protected ProductInfo getFallback() {
            // 第二级降级策略,第一级降级策略,都失败了
            ProductInfo productInfo = new ProductInfo();
            // 从请求参数中获取到的唯一条数据
            productInfo.setId(productId);
            // 从本地缓存中获取一些数据
            productInfo.setBrandId(BrandCache.getBrandId(productId));
            productInfo.setBrandName(BrandCache.getBrandName(productInfo.getBrandId()));
            // 手动填充一些默认的数据
            productInfo.setColor("默认颜色");
            productInfo.setModifyTime(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
            productInfo.setName("默认商品");
            productInfo.setPictureList("default.jpg");
            productInfo.setPrice(0.0);
            productInfo.setService("默认售后服务");
            productInfo.setShopId(-1L);
            productInfo.setSize("默认大小");
            productInfo.setSpecification("默认规格");
            return productInfo;
        }
    }

    @Override
    protected ProductInfo getFallback() {
        return new FirstLevelFallbackCommand(productId).execute();
    }

/*    @Override
    protected String getCacheKey() {
        return "product_info_" + productId;
    }*/
}
八十三、基于facade command开发商品服务接口的手动降级机制

手动降级

你写一个command,在这个command它的主流程中,根据一个标识位,判断要执行哪个流程

可以执行主流程,command,也可以执行一个备用降级的command

一般来说,都是去执行一个主流程的command,如果说你现在知道有问题了,希望能够手动降级的话,动态给服务发送个请求

在请求中修改标识位,自动就让command以后都直接过来执行备用command

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

商品服务接口的手动降级的方案

主流程还是去走GetProductInfoCommand,手动降级的方案,比如说是从某一个数据源,自己去简单的获取一些数据,尝试封装一下返回

手动降级的策略,就比较low了,调用别人的接口去获取数据的,业务逻辑的封装

主流程有问题,那么可能你就需要立即自己写一些逻辑发布上去,从mysql数据库的表中获取一些数据去返回,手动调整一下降级标识,做一下手动降级

八十四、生产环境中的线程池大小以及timeout超时时长优化经验总结

生产环境里面,一个是线程池的大小怎么设置,timeout时长怎么

不合理的话,问题还是很大的

在生产环境中部署一个短路器,一开始需要将一些关键配置设置的大一些,比如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台物理机

你要真的说是,你的公司服务的用户量,或者数据量,或者请求量,真要是到了每秒几万的QPS,

3万QPS,60 * 3 = 180万访问量,1800,1亿8千,1亿,10个小时,10亿的访问量,app,系统

几十台服务器去支撑,我觉得很正常
QPS每秒在几千都算多的了

八十五、生产环境中的线程池自动扩容与缩容的动态资源分配经验

可能会出现一种情况,比如说我们的某个依赖,在高峰期,需要耗费100个线程,但是在那个时间段,刚好其他的依赖的线程池其实就维持一两个就可以了

但是,如果我们都是设置死的,每个服务就给10个线程,那就很坑,可能就导致有的服务在高峰期需要更多的资源,但是没资源了,导致很多的reject

但是其他的服务,每秒钟就易一两个请求,结果也占用了10个线程,占着茅坑不拉屎

做成弹性的线程资源调度的模式

刚开始的时候,每个依赖服务都是给1个线程,3个线程,但是我们允许说,如果你的某个线程池突然需要大量的线程,最多可以到100个线程

如果你使用了100个线程,高峰期过去了,自动将空闲的线程给释放掉

(1)coreSize

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

HystrixThreadPoolProperties.Setter()
.withCoreSize(int value)

(2)maximumSize

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

默认是10

HystrixThreadPoolProperties.Setter()
.withMaximumSize(int value)

(5)keepAliveTimeMinutes

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

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

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

HystrixThreadPoolProperties.Setter()
.withKeepAliveTimeMinutes(int value)

(6)allowMaximumSizeToDivergeFromCoreSize

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

默认是fales

HystrixThreadPoolProperties.Setter()
.withAllowMaximumSizeToDivergeFromCoreSize(boolean value)

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

也是根据你的服务的实际的运行的情况切看的,比如说你发现某个服务,平时3个并发QPS就够了,高峰期可能要到30个

那么你就可以给设置弹性的资源调度

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

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

1,coreSize

八十六、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接口,可以被订阅。

5、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)

八十七、hystrix dashboard可视化分布式系统监控环境部署

1、安装metrics stream

<dependency>
    <groupId>com.netflix.hystrix</groupId>
    <artifactId>hystrix-metrics-event-stream</artifactId>
    <version>1.4.10</version>
</dependency>

@Bean
public ServletRegistrationBean indexServletRegistration() {
ServletRegistrationBean registration = new ServletRegistrationBean(new HystrixMetricsStreamServlet());
registration.addUrlMappings("/hystrix.stream");
return registration;
}

2、安装gradle

类似于maven,一种java里面的打包和构建的工具,hystrix是用gradle去管理打包和构建的

配置环境变量,GRADLE_HOME
配置PATH,%GRADLE_HOME%/bin

gradle -v

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

turbin是用来监控一个集群的,可以将一个集群的所有机器都配置在这里

6、启动我们的服务

7、启动tomcat中的hystrix dashboard和turbin

localhost:8080/hystrix-dashboard

http://localhost:8081/hystrix.stream,监控单个机器
http://localhost:8080/turbine/turbine.stream,监控整个集群

8、发送几个请求,看看效果

9、hystrix dashboard

hystrix的dashboard可以支持实时监控metric

netflix开始用这个dashboard的时候,大幅度优化了工程运维的操作,帮助节约了恢复系统的时间。大多数生产系统的故障持续时间变得很短,而且影响幅度小了很多,主要是因为hystrix dashborad提供了可视化的监控。

截图说明,dashboard上的指标都是什么?

圆圈的颜色和大小代表了健康状况以及流量,折线代表了最近2分钟的请求流量

集群中的机器数量,请求延时的中位数以及平均值

最近10秒内的异常请求比例,请求QPS,每台机器的QPS,以及整个集群的QPS

断路器的状态

最近一分钟的请求延时百分比,TP90,TP99,TP99.5

几个有颜色的数字,代表了最近10秒钟的统计,以1秒钟为粒度

成功的请求数量,绿颜色的数字; 短路的请求数量,蓝色的数字; timeout超时的请求数量,黄色的数字; 线程池reject的请求数量,紫色的数字; 请求失败,抛出异常的请求数量,红色的数字

八十八、生产环境中的hystrix分布式系统的工程运维经验总结

如果发现了严重的依赖调用延时,先不用急着去修改配置,如果一个command被限流了,可能本来就应该限流

在netflix早期的时候,经常会有人在发现短路器因为访问延时发生的时候,去热修改一些皮遏制,比如线程池大小,队列大小,超时时长,等等,给更多的资源,但是这其实是不对的

如果我们之前对系统进行了良好的配置,然后现在在高峰期,系统在进行线程池reject,超时,短路,那么此时我们应该集中精力去看底层根本的原因,而不是调整配置

为什么在高峰期,一个10个线程的线程池,搞不定这些流量呢???代码写的太烂了,异步,更好的算法

千万不要急于给你的依赖调用过多的资源,比如线程池大小,队列大小,超时时长,信号量容量,等等,因为这可能导致我们自己对自己的系统进行DDOS攻击(疯狂的大量的访问你的机器,最后给打垮)

举例来说,想象一下,我们现在有100台服务器组成的集群,每台机器有10个线程大小的线程池去访问一个服务,那么我们对那个服务就有1000个线程资源去访问了

在正常情况下,可能只会用到其中200~300个线程去访问那个后端服务

但是如果再高峰期出现了访问延时,可能导致1000个线程全部被调用去访问那个后端服务,如果我们调整到每台服务器20个线程呢?

如果因为你的代码等问题导致访问延时,即使有20个线程可能还是会导致线程池资源被占满,此时就有2000个线程去访问后端服务,可能对后端服务就是一场灾难

这就是断路器的作用了,如果我们把后端服务打死了,或者产生了大量的压力,有大量的timeout和reject,那么就自动短路,一段时间后,等流量洪峰过去了,再重启访问

简单来说,让系统自己去限流,短路,超时,以及reject,直到系统重新变得正常了

就是不要随便乱改资源配置,不要随便乱增加线程池大小,等待队列大小,异常情况是正常的

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值