Hystrix出现的原因
我先展示一个在前面博客里出现过的架构图,如果我的“Eureka客户端(服务调用者)”是用做于限时抢购的,从业务逻辑的判断到操作数据库都需要一定的时间才能完成,在我的一个业务还没处理完成的情况下,这个时候又有新的请求进来了,请求就会被堆积,等待着处理。而对于用户来说,如果你的网站服务等待了半天还没有出结果,他们就会反复刷新,更是加剧了请求的堆积,服务器承受不住这种压力,就会宕机,给企业造成损失。
再者,我这个“Eureka客户端(服务调用者)”只是一个普通的业务提供者,在正常情况下,他是能够满足服务的提供的,但是某一天我们的底层数据库宕机了,任何一个查询都得等待10s左右,而且还得不到结果,用户不停地刷新浏览器,也同样造成请求堆积,服务器宕机,虽然产生问题的原因不在于“Eureka客户端(服务调用者)”,但是结果却是相同的,我们的“Eureka客户端(服务调用者)”都挂掉了,进而造成整个集群都不可用。
传统的解决方式可能如下:
- 对于秒杀、抢购类的服务,服务器请求数过多,宕机的,就反复重启服务,做人肉运维。这种工作费力不讨好,收效甚微。
- 对于数据库宕机不能用造成的服务提供失败,可以在持久层添加超时处理机制,规定在多少时间内不能返回结果就算数据库异常。
但是,这就像“头痛医头,脚痛医脚”一样,没有从根本上解决我们的问题,也许在未来的某个时间点里又出现了新的问题,比如“Eureka客户端(服务提供者)”都挂了,或者调用这个“服务提供者”的网络故障,传输效率十分低下,并不能在短时间内返回请求结果,那我们又该怎么办呢?
Hystrix是什么,能解决什么?
在分布式环境中,不可避免地,许多服务依赖项将会失败。Hystrix是一个通过添加延迟容忍和容错逻辑来帮助您控制这些分布式服务之间的交互的库。Hystrix通过隔离服务之间的访问点来实现这一点,停止跨级的级联故障,并提供备用选项,这些工作都提高了系统的整体弹性。
Hystrix是由Netflix的API团队在2011年开始的弹性工程工作演变而来的。2012年,Hystrix继续发展和成熟,Netflix的许多团队都采用了它。如今,在Netflix上,每天都有数百亿的线程被隔离,以及数以千亿计的信号隔离。正常运行时间和弹性都有了显著改善。
Hystrix支持以下操作处理:
- 通过第三方客户端库,对访问(通常是通过网络)的依赖项进行保护,并控制延迟和失败。
- 在一个复杂的分布式系统中停止级联故障。
- 快速失败,迅速恢复。
- 在可能的情况下,后退并优雅地降级。
- 启用近实时监控、警报和操作控制。
编写第一个Hystrix程序
我们这里通过实现Hystrix的回退逻辑的方式,来实现第一个Hystrix程序的编写
首先创建一个简单java的maven项目hystrix-order,引入Spring Boot的依赖,将他作为一个web项目,pom.xml配置如下:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.init.springcloud</groupId> <artifactId>hystrix-order</artifactId> <version>0.0.1-SNAPSHOT</version> <build /> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.14.BUILD-SNAPSHOT</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> </project>
接着在项目下创建org.init.springCloud包,新建启动类ProviderApp和供外部调用的ProviderController类。
ProviderApp.class
package org.init.springCloud; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class ProviderApp { public static void main(String[] args) { SpringApplication.run(ProviderApp.class, args); } }
ProviderController.class,这个类提供两个方法,一个是正常返回结果的方法,一个作为模拟数据库故障的方法:
package org.init.springCloud; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; @RestController public class ProviderController { @RequestMapping(value = "/normal", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) public String normalHello(){ return "success"; }; @RequestMapping(value = "/trouble", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) public String errorHello(){ try { //模拟数据库故障,不能及时返回数据 Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } return "success"; }; }
之后运行ProviderApp类的main方法,启动hystrix-order项目备用
新建简单java的maven项目hystrix-sale,在pom.xml中引入httpClient的依赖和Hystrix的核心包:
<dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.5</version> </dependency> <dependency> <groupId>com.netflix.hystrix</groupId> <artifactId>hystrix-core</artifactId> <version>1.5.9</version> </dependency>
接着创建SaleTest类,用创建httpClient的方式去调用hystrix-order提供的服务:
package org.init.springCloud; import java.io.IOException; import org.apache.http.HttpResponse; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils; public class SaleTest { public static void main(String[] args) { String url = "http://localhost:8080/normal"; //1.创建一个默认的http客户端 CloseableHttpClient httpClient = HttpClients.createDefault(); //2.创建一个GET请求 HttpGet httpGet = new HttpGet(url); try { //3.获取响应 HttpResponse httpResponse = httpClient.execute(httpGet); System.out.println(EntityUtils.toString(httpResponse.getEntity())); } catch (ClientProtocolException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }
运行SaleTest类的main方法,是可以在控制台看见返回结果的:
接下来测试用Hystrix的方式来执行请求,新建SaleCommand类,继承HystrixCommand类,在空构造器里需要为我们的Command提供一个GroupKey,同时我们新建一个run方法,将我们的执行内容放到Hystrix维护的一个线程池里,这里的执行内容就是SaleTest类里面的代码:
package org.init.springCloud; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils; import com.netflix.hystrix.HystrixCommand; import com.netflix.hystrix.HystrixCommandGroupKey; public class SaleCommand extends HystrixCommand<String> { public SaleCommand(){ super(HystrixCommandGroupKey.Factory.asKey("myGroup")); } protected String run() throws Exception { String url = "http://localhost:8080/normal"; //1.创建一个默认的http客户端 CloseableHttpClient httpClient = HttpClients.createDefault(); //2.创建一个GET请求 HttpGet httpGet = new HttpGet(url); //3.获取响应 HttpResponse httpResponse = httpClient.execute(httpGet); return EntityUtils.toString(httpResponse.getEntity()); } }
编写一个SaleCommandTest类来测试,是否可以通过Hystrix拿到请求结果:
package org.init.springCloud; public class SaleCommandTest { public static void main(String[] args) { SaleCommand saleCommand = new SaleCommand(); System.out.println(saleCommand.execute()); } }
运行SaleCommandTest类的main()方法,控制器能正常返回请求结果:
这是在正常情况下,Hystrix发起的请求,那不正常情况下呢?也就是我们模拟的数据库故障的情况。同样,新建ErrorCommand类,内容和SaleCommand类似,只是修改了url:
package org.init.springCloud; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils; import com.netflix.hystrix.HystrixCommand; import com.netflix.hystrix.HystrixCommandGroupKey; public class ErrorCommand extends HystrixCommand<String>{ public ErrorCommand(){ super(HystrixCommandGroupKey.Factory.asKey("myGroup")); } protected String run() throws Exception { String url = "http://localhost:8080/trouble"; //1.创建一个默认的http客户端 CloseableHttpClient httpClient = HttpClients.createDefault(); //2.创建一个GET请求 HttpGet httpGet = new HttpGet(url); //3.获取响应 HttpResponse httpResponse = httpClient.execute(httpGet); return EntityUtils.toString(httpResponse.getEntity()); } }
编写ErrorCommandTest类测试:
package org.init.springCloud; public class ErrorCommandTest { public static void main(String[] args) { ErrorCommand errorCommand = new ErrorCommand(); System.out.println(errorCommand.execute()); } }
运行ErrorCommandTest类的main()方法,控制台看见如下返回结果:
抛出异常的原因Hystrix默认的超时时间是1秒,请求超时,就进入了Hystrix的回退逻辑,但是我们又没有编写回退逻辑,才造成了错误。
这里可以为ErrorCommand增加回退逻辑,做起来也很简单,只要去实现getFallback()方法就OK了:
@Override protected String getFallback() { System.out.println("这里是回退逻辑"); return "fail"; }
再次运行ErrorCommandTest的main()方法,可以看到控制台已经返回了回退逻辑的内容: