一、背景 Hystrix是Netlifx开源的一款容错框架,防雪崩利器,具备服务降级,服务熔断,依赖隔离,监控(Hystrix Dashboard)等功能。 尽管说Hystrix官方已不再维护,且有Alibaba Sentinel等新框…
一、背景 Hystrix是Netlifx开源的一款容错框架,防雪崩利器,具备服务降级,服务熔断,依赖隔离,监控(Hystrix Dashboard)等功能。
二、经验总结 2.1 隔离策略的选择 Hystrix提供两种资源隔离策略,线程池和信号量。它们之间的异同点如下:
2.2 线程池大小与超时时间设置 在线程池隔离策略下,线程池大小及超时时间的设置至关重要,直接影响着系统服务的响应能力。如线程池大小若设置的太大会造成资源浪费及线程切换等开销;若设置的太小又支撑不了用户请求,造成请求排队。而超时时间设置的太长会出现部分长耗时请求阻塞线程,造成其它正常请求排队等待;若设置的太短又会造成太多正常请求被熔断。
线程池大小 = 服务TP99响应时长(单位秒) * 每秒请求量 + 冗余缓冲值
例如某服务TP99情况下每秒钟会接收30个请求,然后每个请求的响应时长是200ms,按如上公式计算可得:线程池大小 = 0.2 * 30 + 4(冗余缓���值��= 10,超时时间 = 300ms
2.3 注解叠加 在实际开发中可能会遇到某外部调用方法有Hystrix注解与其它注解一起使用的情况,例如查询方法加上缓存注解。此时需特别注意注解间的执行顺序,避免出现非预期的结果:
缓存注解未生效
因缓存异常造成该查询方法被熔断
2.4 服务的异常处理 先给大家时间看如下代码,检查是否存在问题:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19   @HystrixCommand(fallbackMethod="queryUserByIdFallback")  public  User queryUserById (String userId)  {  if (StringUtils.isEmpty(userId)) {     throw  new  BizException ("参数不合法" );   }      Result<User> result;   try  {     result = userFacade.queryById(userId);   } catch (Exception e) {     log.error("query user error. id={}" , id, e);   }      if (result != null  && result.isSuccess()) {     return  result.getData();   }      return  null ; } 
Hystrix在运行过程中会根据调用请求的成功率或失败率信息来确定每个依赖命令的熔断器是否打开。如果打开,后续的请求都会被拒绝。由此可见,对异常的控制是Hystrix运行效果起很大影响。
参数校验不通过时的异常处理
try-catch远程调用的异常处理
2.5  fallback方法 Hystrix在依赖服务调用时通过增加fallback方法返回默认值的方式来支持服务优雅降级。但fallback的使用也有很多需要注意的地方,大致总结如下:
fallback 方法访问级别、参数等要与对应依赖服务一致
2.6  groupKey、commandKey、threadPoolKey 在使用Hystrix开发中肯定都见过这三个key,但很多人并不理解这三个key的意义以及对Hystrix的作用,尤其是threadPooKey,故在此总结下:
2.7 参数优先级 Hystrix默认提供4个级别的参数值配置方式:
2.8  基于配置中心实现参数动态配置 Hystrix默认使用Archaius实现动态设置,而Archaius默认会加载classpath下的config.properties文件,可通过在配置文件中加入对应属性key-value实现动态控制Hystrix行为。在分布式项目中使用配置中心进行统一配置管理是标配,因此需要基于配置中心的扩展实现Hystrix参数动态配置功能。
通过系统参数
2.8.1 基于Java SPI机制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34   public  interface  HystrixDynamicProperties  {               public  HystrixDynamicProperty<String> getString (String name, String fallback) ;          public  HystrixDynamicProperty<Integer> getInteger (String name, Integer fallback) ;          public  HystrixDynamicProperty<Long> getLong (String name, Long fallback) ;          public  HystrixDynamicProperty<Boolean> getBoolean (String name, Boolean fallback) ; } 
而HystrixDynamicProperty类具体表示一个参数属性,且有动态变更的能力,接口定义如下:
1 2 3 4 5 6 7 8 9 10 11   public  interface  HystrixDynamicProperty <T> extends  HystrixProperty <T>{          public  String getName () ;               public  void  addCallback (Runnable callback) ;      } 
其中addCallback方法是实现属性动态变更的核心所在,如其注释说明的那样,它会在属性变更时注册callback回调方法进行属性动态刷新。而这块动态刷新逻辑是Hystrix内部已实现的,对于我们只需要自定义扩展时将callback保存,然后在配置中心变更时触发对应属性对象的callback方法即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 private  abstract  static  class  CustomDynamicProperty <T> implements  HystrixDynamicProperty <T>, PropertyObserver {      protected  final  String name;       protected  final  T defaultValue;       protected  List<Runnable> callbacks;       protected  CustomDynamicProperty (String propName, T defaultValue)  {           this .name = propName;           this .defaultValue = defaultValue;           PropertyObserverManager.add(this );       }       @Override        public  String getName ()  {           return  name;       }       @Override        public  void  addCallback (Runnable callback)  {           if  (callbacks == null )               callbacks = new  ArrayList <>(1 );           this .callbacks.add(callback);       }       @Override        public  String keyName ()  {           return  name;       }       @Override        public  void  update (PropertyItem item)  {           if (getName().equals(item.getName())) {               for (Runnable r : callbacks) {                   r.run();               }           }       }   }   private  static  class  StringDynamicProperty  extends  CustomDynamicProperty <String> {       protected  StringDynamicProperty (String propName, String defaultValue)  {           super (propName, defaultValue);       }       @Override        public  String get ()  {           return  ConfigManager.getString(name, defaultValue);       }   }   private  static  class  IntegerDynamicProperty  extends  CustomDynamicProperty <Integer> {       protected  IntegerDynamicProperty (String propName, Integer defaultValue)  {           super (propName, defaultValue);       }       @Override        public  Integer get ()  {           String  configValue  =   ConfigManager.get(name);           if (StringUtils.isNotEmpty(configValue)) {               return  Integer.valueOf(configValue);           }           return  defaultValue;       }   }   private  static  class  LongDynamicProperty  extends  CustomDynamicProperty <Long> {       protected  LongDynamicProperty (String propName, Long defaultValue)  {           super (propName, defaultValue);       }       @Override        public  Long get ()  {           String  configValue  =   ConfigManager.get(name);           if (StringUtils.isNotEmpty(configValue)) {               return  Long.valueOf(configValue);           }           return  defaultValue;       }   }   private  static  class  BooleanDynamicProperty  extends  CustomDynamicProperty <Boolean> {       protected  BooleanDynamicProperty (String propName, Boolean defaultValue)  {           super (propName, defaultValue);       }       @Override        public  Boolean get ()  {           String  configValue  =   ConfigManager.get(name);           if (StringUtils.isNotEmpty(configValue)) {               return  Boolean.valueOf(configValue);           }           return  defaultValue;       }   } 
其中ConfigManager类暂时默认为配置中心配置管理类,提供参数获取与参数监听器等功能。而PropertyObserver类(keyName/update方法属于其定义)、PropertyObserverManager类就是参照观察者模式定义实现的,负责观察者的注册与通知管理,来完成动态属性与配置中心变更通知间的联动。这两个类实现比较简单就不展示描述。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22    public  class  DemoHystrixDynamicProperties  implements  HystrixDynamicProperties  {    @Override      public  HystrixDynamicProperty<String> getString (String name, String fallback)  {         return  new  StringDynamicProperty (name, fallback);     }     @Override      public  HystrixDynamicProperty<Integer> getInteger (String name, Integer fallback)  {         return  new  IntegerDynamicProperty (name, fallback);     }     @Override      public  HystrixDynamicProperty<Long> getLong (String name, Long fallback)  {         return  new  LongDynamicProperty (name, fallback);     }     @Override      public  HystrixDynamicProperty<Boolean> getBoolean (String name, Boolean fallback)  {         return  new  BooleanDynamicProperty (name, fallback);     } } 
3、注册SPI实现类
2.8.2 基于默认Archaius进行扩展 Hystrix默认通过Archaius实现参数动态获取,而Archaius自身也提供自定义的参数获取方式,分别是 PolledConfigurationSource接口 和AbstractPollingScheduler类,其中PolledConfigurationSource接口表示配置获取源,AbstractPollingScheduler类表示配置定时刷新机制。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22   public  class  CustomCfgConfigurationSource  implements  PolledConfigurationSource  {     private  final  static  String  CONFIG_KEY_PREFIX  =  "hystrix" ;       @Override      public  PollResult poll (boolean  initial, Object checkPoint)  throws  Exception {         Map<String, Object> map = load();         return  PollResult.createFull(map);     }       private  Map<String, Object> load ()  throws  Exception{         Map<String, Object> map = new  HashMap <>();           Set<String> keys = ConfigManager.keys();         for (String key : keys) {             if (key.startsWith(CONFIG_KEY_PREFIX)) {                 map.put(key, ConfigManager.get(key));             }         }           return  map;     } } 
其实现非常简单,核心实现就是poll方法,遍历配置中心中所有hystrix开头的配置参数并返回保存。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63    public  class  CustomCfgPollingScheduler  extends  AbstractPollingScheduler  {    private  final  static  Logger  logger  =  LoggerFactory.getLogger("CustomCfgPollingScheduler" );       private  final  static  String  CONFIG_KEY_PREFIX  =  "hystrix" ;       @Override      public  void  startPolling (PolledConfigurationSource source, final  Configuration config)  {         super .startPolling(source, config);                  ConfigManager.addListener(new  ConfigListener () {             @Override              public  void  eventReceived (PropertyItem item, ChangeEventType type)  {                 String  name  =  item.getName();                 if (name.startsWith(CONFIG_KEY_PREFIX)) {                     String  newValue  =  item.getValue();                                          if (ChangeEventType.ITEM_ADDED.equals(type) || ChangeEventType.ITEM_UPDATED.equals(type)) {                         addOrChangeProperty(name, newValue, config);                     }                                          else  if (ChangeEventType.ITEM_REMOVED.equals(type)) {                         deleteProperty(name, config);                     }                     else  {                         logger.error("error config change event type {}." , type);                     }                 }             }         });     }       private  void  addOrChangeProperty (String name, Object newValue, final  Configuration config)  {         if  (!config.containsKey(name)) {             config.addProperty(name, newValue);         } else  {             Object  oldValue  =  config.getProperty(name);             if  (newValue != null ) {                 if  (!newValue.equals(oldValue)) {                     config.setProperty(name, newValue);                 }             } else  if  (oldValue != null ) {                 config.setProperty(name, null );             }         }     }       private  void  deleteProperty (String key, final  Configuration config)  {         if  (config.containsKey(key)) {             config.clearProperty(key);         }     }       @Override      protected  void  schedule (Runnable pollingRunnable)  {              }       @Override      public  void  stop ()  {              } } 
但对应实际项目,通过定时刷新的方式一是不太实时,二是每次都得全量检查配置中心是否有修改,逻辑复杂,所以此处改用 ConfigManager.addListener 增加配置中心监听来实现。
1 2   DynamicConfiguration  dynamicConfiguration  =  new  DynamicConfiguration (new  CustomCfgConfigurationSource (), new  CustomCfgPollingScheduler ()); ConfigurationManager.install(dynamicConfiguration); 
最后只需要在容器启动时执行以上初始化脚本即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79   public  class  CustomCfgDynamicConfiguration  extends  ConcurrentMapConfiguration  {     private  final  static  Logger  logger  =  LoggerFactory.getLogger("CustomCfgDynamicConfiguration" );       private  final  static  String  CONFIG_KEY_PREFIX  =  "hystrix" ;       public  CustomCfgDynamicConfiguration ()  {         super ();         load();         initEvent();     }            private  void  load ()  {         Set<String> keys = ConfigManager.keys();         for (String key : keys) {             if (key.startsWith(CONFIG_KEY_PREFIX)) {                 map.put(key, ConfigManager.get(key));             }         }     }            private  void  initEvent ()  {         ConfigManager.addListener(new  ConfigListener () {             @Override              public  void  eventReceived (PropertyItem item, ChangeEventType type)  {                 String  name  =  item.getName();                 if (name.startsWith(CONFIG_KEY_PREFIX)) {                     String  newValue  =  item.getValue();                                          if (ChangeEventType.ITEM_ADDED.equals(type) || ChangeEventType.ITEM_UPDATED.equals(type)) {                         addOrChangeProperty(name, newValue);                     }                                          else  if (ChangeEventType.ITEM_REMOVED.equals(type)) {                         deleteProperty(name);                     }                     else  {                         logger.error("error config change event type {}." , type);                     }                 }             }         });     }            private  void  addOrChangeProperty (String name, Object newValue)  {         if  (!this .containsKey(name)) {             this .addProperty(name, newValue);         } else  {             Object  oldValue  =  this .getProperty(name);             if  (newValue != null ) {                 if  (!newValue.equals(oldValue)) {                     this .setProperty(name, newValue);                 }             } else  if  (oldValue != null ) {                 this .setProperty(name, null );             }         }     }            private  void  deleteProperty (String key)  {         if  (this .containsKey(key)) {             this .clearProperty(key);         }     } } 
最后通过 ConfigurationManager.install(new CustomCfgDynamicConfiguration());“安装”该实现即可。
三、写在最后 笔者结合项目实战对Hystrix使用进行总结分享,有关于隔离策略、线程池设置、参数优先级等知识点讲解,也��关于注解叠加、异常处理、参数动态配置等具体问题解决方案,希望对大家有所帮助。
本文标题: Hystrix 实战经验分享
本文作者: OSChina
发布时间: 2021年04月15日 09:46
最后更新: 2025年07月13日 05:44
原始链接: https://haoxiang.eu.org/db4d793b/ 
版权声明: 本文著作权归作者所有,均采用CC BY-NC-SA 4.0 许可协议,转载请注明出处!