• 亿级流量场景下,大型缓存架构设计实现【3】---- 实现高可用


     正文前先来一波福利推荐:

    福利一:

    百万年薪架构师视频,该视频可以学到很多东西,是本人花钱买的VIP课程,学习消化了一年,为了支持一下女朋友公众号也方便大家学习,共享给大家。

    福利二:

    毕业答辩以及工作上各种答辩,平时积累了不少精品PPT,现在共享给大家,大大小小加起来有几千套,总有适合你的一款,很多是网上是下载不到。

    获取方式:

    微信关注 精品3分钟 ,id为 jingpin3mins,关注后回复   百万年薪架构师 ,精品收藏PPT  获取云盘链接,谢谢大家支持!

    ------------------------正文开始---------------------------

    1、Hystrix是什么?

    在分布式系统中,每个服务都可能会调用很多其他服务,被调用的那些服务就是依赖服务,有的时候某些依赖服务出现故障也是很正常的。

    Hystrix可以让我们在分布式系统中对服务间的调用进行控制,加入一些调用延迟或者依赖故障的容错机制。

    Hystrix通过将依赖服务进行资源隔离,进而组织某个依赖服务出现故障的时候,这种故障在整个系统所有的依赖服务调用中进行蔓延,同时Hystrix还提供故障时的fallback降级机制

    总而言之,Hystrix通过这些方法帮助我们提升分布式系统的可用性和稳定性。

    初步看一看Hystrix的设计原则是什么?

    hystrix为了实现高可用性的架构,设计hystrix的时候,一些设计原则是什么?

    (1)对依赖服务调用时出现的调用延迟和调用失败进行控制和容错保护

    (2)在复杂的分布式系统中,阻止某一个依赖服务的故障在整个系统中蔓延,服务A->服务B->服务C,服务C故障了,服务B也故障了,服务A故障了,整套分布式系统全部故障,整体宕机

    (3)提供fail-fast(快速失败)和快速恢复的支持

    (4)提供fallback优雅降级的支持

    (5)支持近实时的监控、报警以及运维操作

    调用延迟+失败,提供容错

    阻止故障蔓延

    快速失败+快速恢复

    降级

    监控+报警+运维

    2、再看Hystrix的更加细节的设计原则是什么?

    (1)阻止任何一个依赖服务耗尽所有的资源,比如tomcat中的所有线程资源

    (2)避免请求排队和积压,采用限流和fail fast来控制故障

    (3)提供fallback降级机制来应对故障

    (4)使用资源隔离技术,比如bulkhead(舱壁隔离技术),swimlane(泳道技术),circuit breaker(短路技术),来限制任何一个依赖服务的故障的影响

    (5)通过近实时的统计/监控/报警功能,来提高故障发现的速度

    (6)通过近实时的属性和配置热修改功能,来提高故障处理和恢复的速度

    (7)保护依赖服务调用的所有故障情况,而不仅仅只是网络故障情况

    调用这个依赖服务的时候,client调用包有bug,阻塞,等等,依赖服务的各种各样的调用的故障,都可以处理.。

    3、Hystrix是如何实现它的目标的?

    (1)通过HystrixCommand或者HystrixObservableCommand来封装对外部依赖的访问请求,这个访问请求一般会运行在独立的线程中,资源隔离。

    (2)对于超出我们设定阈值的服务调用,直接进行超时,不允许其耗费过长时间阻塞住。这个超时时间默认是99.5%的访问时间,但是一般我们可以自己设置一下

    (3)为每一个依赖服务维护一个独立的线程池,或者是semaphore,当线程池已满时,直接拒绝对这个服务的调用

    (4)对依赖服务的调用的成功次数,失败次数,拒绝次数,超时次数,进行统计

    (5)如果对一个依赖服务的调用失败次数超过了一定的阈值,自动进行熔断,在一定时间内对该服务的调用直接降级,一段时间后再自动尝试恢复

    (6)当一个服务调用出现失败,被拒绝,超时,短路等异常情况时,自动调用fallback降级机制

    (7)对属性和配置的修改提供近实时的支持

    画图分析,对依赖进行资源隔离后,如何避免依赖服务调用延迟或失败导致当前服务的故障

    二、在商品详情页缓存服务中可能出现的问题:

     1、资源的隔离

    一次获取一个商品信息:

        /**
         * nginx开始,各级缓存都失效了,nginx发送很多的请求直接到缓存服务要求拉取最原始的数据
         * @param productId
         * @return
         */
        @RequestMapping("/getProductInfo")
        @ResponseBody
        public String getProductInfo(Long productId) {
            // 拿到一个商品id
            // 调用商品服务的接口,获取商品id对应的商品的最新数据
            // 用HttpClient去调用商品服务的http接口
            HystrixCommand<ProductInfo> getProductInfoCommand = new GetProductInfoCommand(productId);
            ProductInfo productInfo = getProductInfoCommand.execute();
            
    //        Future<ProductInfo> future = getProductInfoCommand.queue();
    //        try {
    //            Thread.sleep(1000); 
    //            System.out.println(future.get());  
    //        } catch (Exception e) {
    //            e.printStackTrace();
    //        }
            
            System.out.println(productInfo);  
            return "success";
        }
    /**
     * 获取商品信息
     * @author Administrator
     *
     */
    public class GetProductInfoCommand extends HystrixCommand<ProductInfo> {
    
        private Long productId;
        
        public GetProductInfoCommand(Long productId) {
            super(HystrixCommandGroupKey.Factory.asKey("GetProductInfoGroup"));
            this.productId = productId;
        }
        
        @Override
        protected ProductInfo run() throws Exception {
            String url = "http://127.0.0.1:8082/getProductInfo?productId=" + productId;
            String response = HttpClientUtils.sendGetRequest(url);
            return JSONObject.parseObject(response, ProductInfo.class);  
        }
    
    }

    一次性获取多个商品信息:

    	/**
    	 * 一次性批量查询多条商品数据的请求
    	 */
    	@RequestMapping("/getProductInfos")
    	@ResponseBody
    	public String getProductInfos(String productIds) {
    		HystrixObservableCommand<ProductInfo> getProductInfosCommand = 
    				new GetProductInfosCommand(productIds.split(","));  
    		Observable<ProductInfo> observable = getProductInfosCommand.observe();
    		
    //		observable = getProductInfosCommand.toObservable(); // 还没有执行
    		
    		observable.subscribe(new Observer<ProductInfo>() { // 等到调用subscribe然后才会执行
    
    			public void onCompleted() {
    				System.out.println("获取完了所有的商品数据");
    			}
    
    			public void onError(Throwable e) {
    				e.printStackTrace();
    			}
    
    			public void onNext(ProductInfo productInfo) {
    				System.out.println(productInfo);  
    			}
    			
    		});
    		return "success";
    	}
    	
    }
    

      

    /**
     * 批量查询多个商品数据的command
     * @author Administrator
     *
     */
    public class GetProductInfosCommand extends HystrixObservableCommand<ProductInfo> {
    
        private String[] productIds;
        
        public GetProductInfosCommand(String[] productIds) {
            super(HystrixCommandGroupKey.Factory.asKey("GetProductInfoGroup"));
            this.productIds = productIds;
        }
        
        @Override
        protected Observable<ProductInfo> construct() {
            return Observable.create(new Observable.OnSubscribe<ProductInfo>() {
    
                public void call(Subscriber<? super ProductInfo> observer) {
                    try {
                        for(String productId : productIds) {
                            String url = "http://127.0.0.1:8082/getProductInfo?productId=" + productId;
                            String response = HttpClientUtils.sendGetRequest(url);
                            ProductInfo productInfo = JSONObject.parseObject(response, ProductInfo.class); 
                            observer.onNext(productInfo); 
                        }
                        observer.onCompleted();
                    } catch (Exception e) {
                        observer.onError(e);  
                    }
                }
                
            }).subscribeOn(Schedulers.io());
        }
    
    }

    容量池大小的控制

    1、execution.isolation.strategy

    指定了HystrixCommand.run()的资源隔离策略,THREAD或者SEMAPHORE,一种是基于线程池,一种是信号量

    线程池机制,每个command运行在一个线程中,限流是通过线程池的大小来控制的

    信号量机制,command是运行在调用线程中,但是通过信号量的容量来进行限流

    如何在线程池和信号量之间做选择?

    默认的策略就是线程池

    线程池其实最大的好处就是对于网络访问请求,如果有超时的话,可以避免调用线程阻塞住

    而使用信号量的场景,通常是针对超大并发量的场景下,每个服务实例每秒都几百的QPS,那么此时你用线程池的话,线程一般不会太多,可能撑不住那么高的并发,如果要撑住,可能要耗费大量的线程资源,那么就是用信号量,来进行限流保护

    一般用信号量常见于那种基于纯内存的一些业务逻辑服务,而不涉及到任何网络访问请求

    netflix有100+的command运行在40+的线程池中,只有少数command是不运行在线程池中的,就是从纯内存中获取一些元数据,或者是对多个command包装起来的facacde command,是用信号量限流的

    // to use thread isolation
    HystrixCommandProperties.Setter()
    .withExecutionIsolationStrategy(ExecutionIsolationStrategy.THREAD)
    // to use semaphore isolation
    HystrixCommandProperties.Setter()
    .withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE)

    2、command名称和command组

    线程池隔离,依赖服务->接口->线程池,如何来划分

    你的每个command,都可以设置一个自己的名称,同时可以设置一个自己的组

    private static final Setter cachedSetter =
    Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))
    .andCommandKey(HystrixCommandKey.Factory.asKey("HelloWorld"));

    public CommandHelloWorld(String name) {
    super(cachedSetter);
    this.name = name;
    }

    command group,是一个非常重要的概念,默认情况下,因为就是通过command group来定义一个线程池的,而且还会通过command group来聚合一些监控和报警信息

    同一个command group中的请求,都会进入同一个线程池中

    3、command线程池

    threadpool key代表了一个HystrixThreadPool,用来进行统一监控,统计,缓存

    默认的threadpool key就是command group名称

    每个command都会跟它的threadpool key对应的thread pool绑定在一起

    如果不想直接用command group,也可以手动设置thread pool name

    public CommandHelloWorld(String name) {
    super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))
    .andCommandKey(HystrixCommandKey.Factory.asKey("HelloWorld"))
    .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("HelloWorldPool")));
    this.name = name;
    }

    command threadpool -> command group -> command key

    command key,代表了一类command,一般来说,代表了底层的依赖服务的一个接口

    command group,代表了某一个底层的依赖服务,合理,一个依赖服务可能会暴露出来多个接口,每个接口就是一个command key

    command group,在逻辑上去组织起来一堆command key的调用,统计信息,成功次数,timeout超时次数,失败次数,可以看到某一个服务整体的一些访问情况

    command group,一般来说,推荐是根据一个服务去划分出一个线程池,command key默认都是属于同一个线程池的

    比如说你以一个服务为粒度,估算出来这个服务每秒的所有接口加起来的整体QPS在100左右

    你调用那个服务的当前服务,部署了10个服务实例,每个服务实例上,其实用这个command group对应这个服务,给一个线程池,量大概在10个左右,就可以了,你对整个服务的整体的访问QPS大概在每秒100左右

    一般来说,command group是用来在逻辑上组合一堆command的

    举个例子,对于一个服务中的某个功能模块来说,希望将这个功能模块内的所有command放在一个group中,那么在监控和报警的时候可以放一起看

    command group,对应了一个服务,但是这个服务暴露出来的几个接口,访问量很不一样,差异非常之大

    你可能就希望在这个服务command group内部,包含的对应多个接口的command key,做一些细粒度的资源隔离

    对同一个服务的不同接口,都使用不同的线程池

    command key -> command group

    command key -> 自己的threadpool key

    逻辑上来说,多个command key属于一个command group,在做统计的时候,会放在一起统计

    每个command key有自己的线程池,每个接口有自己的线程池,去做资源隔离和限流

    但是对于thread pool资源隔离来说,可能是希望能够拆分的更加一致一些,比如在一个功能模块内,对不同的请求可以使用不同的thread pool

    command group一般来说,可以是对应一个服务,多个command key对应这个服务的多个接口,多个接口的调用共享同一个线程池

    如果说你的command key,要用自己的线程池,可以定义自己的threadpool key,就ok了

    4、coreSize

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

    HystrixThreadPoolProperties.Setter().withCoreSize(int value)

    一般来说,用这个默认的10个线程大小就够了

    5、queueSizeRejectionThreshold

    控制queue满后reject的threshold,因为maxQueueSize不允许热修改,因此提供这个参数可以热修改,控制队列的最大大小

    HystrixCommand在提交到线程池之前,其实会先进入一个队列中,这个队列满了之后,才会reject

    默认值是5

    HystrixThreadPoolProperties.Setter().withQueueSizeRejectionThreshold(int value)

    6、execution.isolation.semaphore.maxConcurrentRequests

    设置使用SEMAPHORE隔离策略的时候,允许访问的最大并发量,超过这个最大并发量,请求直接被reject

    这个并发量的设置,跟线程池大小的设置,应该是类似的,但是基于信号量的话,性能会好很多,而且hystrix框架本身的开销会小很多

    默认值是10,设置的小一些,否则因为信号量是基于调用线程去执行command的,而且不能从timeout中抽离,因此一旦设置的太大,而且有延时发生,可能瞬间导致tomcat本身的线程资源本占满

    HystrixCommandProperties.Setter() .withExecutionIsolationSemaphoreMaxConcurrentRequests(int value)

  • 相关阅读:
    NOIP 2017逛公园(记忆化搜索)
    NOIP 2012疫情控制 (二分+倍增+贪心)
    NOIP 2005过河(DP+路径压缩)
    P1198 [JSOI2008]最大数
    [Noip2016]蚯蚓
    [六省联考2017]期末考试
    六省联考:组合数问题
    蒜头君的兔子
    bzoj1015 [JSOI2008]星球大战starwar
    luogu P3370 【模板】字符串哈希
  • 原文地址:https://www.cnblogs.com/gxyandwmm/p/11747048.html
Copyright © 2020-2023  润新知