1.1 客户端的弹性模式
1.1.1 Robbin负载均衡
如果客户端负载均衡器检测到问题,它可以从可用服务位置池中删除该服务实例,并防止后续的服务调用命中该服务实例。返正是 Netflix 的 Ribbon 库在没有额外配置的情况下提供的行为。
1.1.2 断路器
- 当一个远程服务被调用时,断路器将监听调用。如果调用需要花费很长时间,断路器将调解和终止调用。
- 此外,断路器将监视对远程资源的所有调用,如果有足够多的调用失败,断路实现将启动,快速失败,并防止对失效资源的后续调用。
1.1.3 回退处理
在回退模式下,当远程服务调用失败时,服务消费者将执行另一个代码路径,并尝试通过另一种方式执行一个操作,而是生成一个异常。返通常包括查找来自另一个数据源的数据戒排队用户后续处理的请求。用户的调用不会显示说明问题的异常,但可能会通知他们的请求必须在以后完成。
1.1.4 舱壁
通过使用舱壁模式,你可以在自己的线程池中中断对远程资源的调用,并减少使用一个缓慢的远程资源调用会降低整个应用程序的风险。线程池为你服务的舱壁。每个远程资源被隔离并分配给线程池。如果一个
服务响应缓慢,这种类型的服务调用的线程池将变得饱和,停止处理请求。对其他服务的服务调用不会被饱和,因为它们被分配给其他线程池。
1.2 客户端弹性重要性
当服务 C 开始运行缓慢,不仅要求服务 C 启动备份线程池,在服务容器的连接池的数据库连接数枯竭,因为服务 C 的调用从未完成,返些连接被打开。最后,服务 A 开始耗尽资源,因为它调用了服务 B,因为服务 C 运行得缓慢。
最终,所有三个应用程序都停止响应,因为它们在等待请求完成时耗尽了资源。
改进:
如果在一个分布式资源被调用(无论是对数据库的调用迓是对服务的调用)的每个点上实现了一个断路器模式,那么可以避免整个场景。在图 5.2 中,如果对服务 C 的调用是用一个断路器来实现的,那么当服务 C 开始表现不佳时,对服务 C 的特定调用的断路器将在不消耗线程的情冴下被忚速地中断和失败。
断路器跳闸的方式:
- 服务 B 现在立即知道有问题,而不必等待断路器超时。
- 服务 B 现在可以选择要么完全失败,要么使用另一组代码(回退)采取行动。
- 服务 C 将有机会恢复,因为当断路器已经跳闸的时候服务 B 没有调用它。返允许服务 C 有喘息空间,并帮劣防止当服务降级发生时发生的级联崩溃
优点:
- 快速失败:当远程服务正在经历降级时,应用程序将快速失败,并通常关闭整个应用程序阻止资源耗尽。在大多数停电情况下,最好是部分停机,而不是完全停机。
- 优雅的失败:通过超时和快速失败,断路器模式使应用程序开发人员能够优雅地失败,或者寻求替代机制来实现用户的意图。例如,如果用户试图从一个数据源检索数据,并且数据源正在经历服务降级,那么应用程序开发人员可以尝试从另一个位置检索该数据。
- 无缝恢复:断路器模式作为中介,断路器可以周期性地检查所请求的资源是否已恢复在线,并在没有人工干预的情况下重新使用所需的资源。
1.3 .搭建许可服务使用spring cloud和Hystrix
1.3.1 配置pom文件
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
1.3.2 @EnableCircuitBreak标注引导类
@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker
public class Application {
@LoadBalanced
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
1.3.3 使用@HystrixCommand标记方法
- @HystrixCommand标记方法,将由断路器对其管理。
- Spring 在看到该注解的时候,将为标记的方法生成代理方法,封装该方法。
- 通过专门用于处理远程方法的线程池来管理该方法的所有调用
- 当客户端调用服务的该方法的时候,其实是先调用断路器,断路器再调用该方法,当该方法的调用超过1000ms的时候,断路器会中断对当前方法的调用。
1.3.4 定制断路器的超时时间
- 超时的时候将抛出一个HystrixRuntimeException
@HystrixCommand(
commandProperties=
{@HystrixProperty(
name="execution.isolation.thread.timeoutInMilliseconds",
value="12000")})
public List<License> getLicensesByOrg(String organizationId){
randomlyRunLong();
return licenseRepository.findByOrganizationId(organizationId);
}
5.3.5 fallbackMethod 设置后备方法
- fallbackMethod 属性在你的类中定义一个单一的功能,如果当前方法执行失败(比如超时的时候),Hystrix 将调用fallbackMethod方法。
- 后备方法的参数和原始方法的参数应该一致,而且原始方法防御性代码后备方法也应该考虑
@HystrixCommand(fallbackMethod = "buildFallbackLicenseList")
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;
}
1.4 实现舱壁模式
- 在基于微服务的应用程序中,开发人员通常需要调用多个微服务来完成一个任务。在不使用舱壁模式的情况下,这些调用默认是使用同一批次的线程处理,如果一个服务出现性能问题,可能导致所有的线程被刷爆等待处理工作,同时阻塞新的请求, 导致java容器的崩溃。
- Hystix使用线程池来委派所有对远程服务的调用,默认情况下,所有Hystrix命令都将共享一个线程池来处理请求。默认一个线程池中由10个线程,可以接受任何形式的远程服务调用,这些服务的调用可以是基于Rest的也可以是对数据库的调用。
- 如果不适用舱壁模式,如果某些服务拥有比其他服务更多的请求或者更长的请求时间。最终Hystrix线程池最终被耗尽,这个服务最终会占用线程池中所有的线程。如下图:
4.幸好Hystrix中有舱壁模式,在不同的资源之间创建舱壁,将资源进行隔离。
1.4.1 实现线程隔离
要实现线程的隔离,需要在@HystrixCommand方法中加入属性。
- @threadPoolKey:定义了线程池的唯一的名称
- @threadPoolProperties:运行定义和定制线程池的行为
- Coresize:设置线程池的大小
- maxQueueSize:线程池中线程繁忙时允许堵塞的请求数
- maxQueueSize中设置为-1,将使用java SynchronousQueue来保存所有的请求。同步队列会强制要求等待请求的数量不能多余线程池中空闲的线程数。
- maxQueueSize设置为大于1的值,使用java LinkedBlockingQueue。
- 需要注意的是maxQueueSize只有在初始化的时候设置(例如,应用程序启动),Hystrix允许使用QueueSizeRejectionThreshold属性动态更改队列的大小。