使用Hystrix实现断路器

我们将会看到两大类别的Hystrix实现。在第一个类别中,我们将使用Hystrix断路器包装许可证服务和组织服务中所有对数据库的调用。然后,我们将使用Hystrix包装许可证服务和组织服务之间的内部服务调用。虽然这是两个不同类别的调用,但是Hystrix的用法是完全一样的。图5-4展示了使用Hystrix断路器来包装的远程资源。

在这里插入图片描述

图5-4 Hystrix位于每个远程资源调用之间并保护客户端。远程资源调用是数据库调用还是基于REST的服务调用无关紧要
本章将先展示如何使用同步Hystrix断路器从许可数据库中检索许可服务数据,以此开始对Hystrix的讨论。许可证服务将通过同步调用来检索数据,但在继续处理之前会等待SQL语句完成或断路器超时。
Hystrix和Spring Cloud使用@HystrixCommand注解来将Java类方法标记为由Hystrix断路器进行管理。当Spring框架看到@HystrixCommand时,它将动态生成一个代理,该代理将包装该方法,并通过专门用于处理远程调用的线程池来管理对该方法的所有调用。
我们将包装licensing-service/src/main/java/com/thoughtmechanix/licenses/services/License Service.java中的LicenseService类中的getLicensesByOrg()方法,如代码清单5-2所示。

代码清单5-2 用断路器包装远程资源调用

// 为了简洁,省略了import语句 
@HystrixCommand  
// @HystrixCommand注解会使用Hystrix断路器包装getLicenseByOrg()方法 
public List<License> getLicensesByOrg(String organizationId){     
	return licenseRepository.findByOrganizationId(organizationId); 
}

注意
如果读者在源代码库中查看代码清单5-2中的代码,会在@HystrixCommand注解中看到多个参数,而不是像上述代码清单显示的那样。本章稍后将介绍这些参数。代码清单5-2中的代码使用了@HystrixCommand注解,其中包含了所有默认值。

这看起来代码并不多,但在这一个注解中却有很多功能。使用@HystrixCommand注解,在任何时候调用getLicensesByOrg()方法时,Hystrix断路器都将包装这个调用。每当调用时间超过1000 ms时,断路器将中断对getLicensesByOrg()方法的调用。
如果数据库正常工作,这个代码示例就显得很无聊。因此,通过让调用时间稍微超过1 s(每3次调用中大约有1次),让我们来模拟getLicensesByOrg()方法执行慢数据库查询。代码清单5-3展示了上述讨论的内容。

代码清单5-3 对许可证服务数据库的随机超时调用

private void randomlyRunLong(){  
// randomlyRunLong()方法提供了 1/3的概率运行耗时较长的数据库调用     
   Random rand = new Random();     
   int randomNum = rand.nextInt((3 - 1) + 1) + 1;     
   if (randomNum==3) sleep(); 
}
private void sleep(){    
	try {         
   	Thread.sleep(11000);  
   	// 休眠11 000 ms(即11 s),Hystrix的默认调用时间是1 s     
   	} catch (InterruptedException e) {         
   	e.printStackTrace();     
   	}
}
@HystrixCommand
public List<License> getLicensesByOrg(String organizationId){     
   	randomlyRunLong();     
   	return licenseRepository.findByOrganizationId(organizationId); 
}

如果访问http://localhost/v1/organizations/e254f8c-c442-4ebe-a82a- e2fc1d1ff78a/licenses/端点的次数足够多,那么应该会看到从许可证服务返回的超时错误消息。图5-5展示了这个错误。
在这里插入图片描述
图5-5 当远程调用花费时间过长时,会抛出一个HystrixRuntimeException异常

现在,有了@HystrixCommand注解,如果查询花费的时间过长,许可证服务将中断其对数据库的调用。如果需要超过1000 ms的时间来执行Hystrix代码包装的数据库调用,那么服务调用将抛出一个com.nextflix.hystrix.exception.HystrixRuntimeException异常。

5.5.1 对组织微服务的调用超时

我们可以使用方法级注解使被标记的调用拥有断路器功能,其优点在于,无论是访问数据库还是调用微服务,它都是相同的注解。
例如,在许可证服务中,我们需要查找与许可证关联的组织的名称。如果要使用断路器来包装对组织服务的调用的话,一个简单的方法就是将RestTemplate调用分解到自己的方法,并使用@HystrixCommand注解进行标注:
@HystrixCommand 
private Organization getOrganization(String organizationId) {     
	return organizationRestClient.getOrganization(organizationId); 
}

注意

虽然使用@HystrixCommand很容易实现,但在使用没有任何配置的默认的@HystrixCommand注解时要特别小心。在默认情况下,在指定不带属性的@HystrixCommand注解时,这个注解会将所有远程服务调用都放在同一线程池下。这可能会导致应用程序中出现问题。在本章稍后讨论如何实现舱壁模式时,将展示如何将这些远程服务调用隔离到它们自己的线程池中,并配置线程池的行为以相互独立。

5.5.2 定制断路器的超时时间

在与新的开发人员合作使用Hystrix进行开发时,我经常遇到的第一个问题是,他们如何定制Hystrix中断调用之前的时间。这一点通过将附加的参数传递给@HystrixCommand注解可以轻松完成。代码清单5-4演示了如何定制Hystrix在超时调用之前等待的时间。

代码清单5-4 定制断路器调用超时

@HystrixCommand( commandProperties = {         
// commandProperties属性允许开发人员提供附加的属性来定制Hystrix
@HystrixProperty(
			name="execution.isolation.thread.timeoutInMilliseconds",  
// execution.isolation.thread.timeoutInMilliseconds用于设置断路器的超时时间(以毫秒为单位)  
           value="12000")
}
)  
public List<License> getLicensesByOrg(String organizationId){     
	randomlyRunLong();     
	return licenseRepository.findByOrganizationId(organizationId);   
}

Hystrix允许通过commandProperties属性来定制断路器的行为。commandProperties属性接受一个
HystrixProperty对象数组,它可以传入自定义属性来配置Hystrix断路器。在代码清单5-4中,使用execution.isolation.thread.timeoutInMilliseconds属性设置Hystrix调用的最大超时时间为12 s。

现在,如果重新构建并重新运行这个代码示例,则永远都不会出现超时错误,因为人工超时时间为11 s,而@HystrixCommand注解现在配置为12 s后才会超时。

服务超时
显然,12 s的断路器超时只是我用来作为教学的一个例子。在分布式环境中,如果我开始听到开发团队反馈,说远程服务调用上的1 s超时时间太少了,因为他们的服务X平均需要5~6 s的时间,那么我就会经常感到紧张。
这些反馈通常告诉我,被调用的服务存在未解决的性能问题。开发人员应避免在Hystrix调用上增加默认超时的诱惑,除非实在无法解决运行缓慢的服务调用。
如果确实遇到一些比其他服务调用需要更长时间的服务调用,务必将这些服务调用隔离到单独的线程池中。

5.6 后备处理

断路器模式的一部分美妙之处在于,由于远程资源的消费者和资源本身之间存在“中间人”,因此开发人员有机会拦截服务故障,并选择替代方案。
在Hystrix中,这被称为后备策略(fallback strategy),并且很容易实现。让我们看看如何为许可数据库构建一个简单的后备策略,该后备策略简单地返回一个许可对象,这个许可对象表示当前没有可用的许可信息。代码清单5-5展示了上述讨论的内容。
代码清单5-5 在Hystrix中实现一个后备
@HystrixCommand(fallbackMethod = "buildFallbackLicenseList")  
// fallbackMethod属性定义了类中的一个方法,如果来自Hystrix的调用失败,那么就会调用该方法 
public List<License> getLicensesByOrg(String organizationId){     
   randomlyRunLong();     
   return licenseRepository.findByOrganizationId(organizationId); 
} 

private List<License> buildFallbackLicenseList(String organizationId){  
//  在后备方法中,返回了一个硬编码的值     
   List<License> fallbackList = new ArrayList<>();     
   License license = new License()
   						.withId("0000000-00-00000") 
   						.withOrganizationId( organizationId ) 
   						.withProductName( "Sorry no licensing information currently available");     
   fallbackList.add(license);     
   return fallbackList; 
}

注意

在来自GitHub存储库的源代码中,我注释掉了fallbackMethod对应的行,以便读者可以看到服务调用的随机失败。要查看代码清单5-5中的后备代码,读者需要取消注释掉fallbackMethod属性,否则,永远不会看到后备代码实际被调用。

要使用Hystrix实现一个的后备策略,开发人员必须做两件事情。第一件是,需要在@HystrixCommand注解中添加一个名为fallbackMethod的属性。该属性将包含一个方法的名称,当Hystrix因为调用耗费时间太长而不得不中断该调用时,该方法将会被调用。

第二件是,需要定义一个待执行的后备方法。此后备方法必须与由@HystrixCommand保护的原始方法位于同一个类中,并且必须具有与原始方法完全相同的方法签名,因为传递给由@HystrixCommand保护的原始方法的所有参数都将传递给后备方法。

在代码清单5-5所示的示例中,后备方法buildFallbackLicenseList()只是简单构建一个包含虚拟信息的单个License 对象。读者可以使用后备方法从备用数据源读取这些数据,但出于演示的目的,我们将构建一个列表,该列表由原始的方法调用返回。

后备
在微服务检索数据并且调用失败的情况下,后备策略非常有效。在我工作过的一个组织中,我们将客户信息存储在操作型数据存储(Operational Data Store,ODS)中,并在数据仓库中进行汇总。
我们的愉快路径总是检索最新的数据,并为其动态计算摘要信息。然而,在一次特别严重的中断之后,由于数据库连接的速度慢,我们决定使用Hystrix后备实现来保护检索和汇总客户信息的服务调用。 如果由于性能问题或错误导致对ODS的调用失败,我们就使用后备来从数据仓库表中检索汇总数据。
我们的业务团队认为,提供旧数据给客户比让客户看到错误或整个应用程序崩溃更为可取。选择是否使用后备策略的关键是客户对数据“年龄”的宽容程度,以及永远不要让他们看到应用程序出现问题的重要程度。

在确定是否要实施后备策略时,要注意以下两点:

  1. 后备是一种在资源超时或失败时提供行动方案的机制。如果发现自己使用后备来捕获超时异常,然后只做日志记录错误,就应该在服务调用周围使用标准的try…catch块,捕获HystrixRuntime Exception异常,并将日志记录逻辑放在try…catch块中。

  2. 注意使用后备方法所执行的操作。如果在后备服务中调用另一个分布式服务,就可能需要使用@HystrixCommand注解来包装后备方法。记住,在主要行动方案中经历的相同的失败有可能也会影响次要的后备方案。要进行防御性编码。我试过在使用后备的时候没有考虑到这个问题,最终吃了很大苦头。

现在我们拥有了后备方案,接下来继续访问端点。这一次,当我们访问这个端点并遇到一个超时错误(有1/3的机会)时,我们不会从服务调用中得到一个返回的异常,而是得到虚拟的许可证值。图5-6展示了上面讨论的内容。
在这里插入图片描述
图5-6 使用Hystrix后备的服务调用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值