咱们通常用每秒查询率(Query Per Second,简称QPS)来衡量一个网站的流量,QPS是指一台服务器在一秒里能处理的查询次数,它能够被用来衡量服务器的性能。java
假设一个Web应用有20个基于微服务的子模块,好比某电商系统里有订单、合同管理和会员管理等子模块,该系统的平均QPS是1000,也就是说平均每秒有1000个访问量,这个数值属于中等水平,并不高。web
算术题一,请计算天天的访问总量?注:通常网站在凌晨1点到上午9点的访问量比较少,因此计算时按天天16个小时算。spring
答:1000*60*60*16=57600000=5.76乘以10的8次方。服务器
算术题二:因为该系统中有20个子模块,在处理每次请求时,该模块有99.9999%的几率不出错(百万分之一的出错几率,这个几率很低了),而任何一个模块出错,整个系统就出错,那么问题是,每小时该系统出错的几率是多少?天天(按16小时算)是多少?每个月(按30天算)又是多少?并发
答:针对每次访问,一个模块正常工做的几率是99.9999%,那么每小时20个模块都不出错的几率是99.9999%的(20*3600)次方,大约是93%,换句话说,在一个小时内,该系统出错的几率是7%。app
咱们再来算天天的正常工做几率,是93%的16次方,大约是31%,换句话说,天天出错的几率高达69%。同理咱们能算出,每个月出错的几率高达95%。异步
经过这组数据,咱们能看到,规模尚属中等的网站(至关于尚能正常盈利不亏本的网站)平均每个月就会出现一次故障,对于哪些模块故障率高于百万分之一或平均QPS更高的网站,这个出故障周期会更频繁,因此说,对于互联网公司而言,服务容错组件是必配,而不是优化项。async
这里咱们将在HystrixServerDemo项目里,提供两个供Hystrix调用的服务,其中一个是可用的,而在另一个服务里,是经过sleep机制,故意让服务延迟返回,从而形成不可用的后果。函数
这是一个基本的Spring Boot的服务,以前相似的博文里咱们已经反复讲述过,因此这里仅给出实现要点,具体信息请你们本身参照代码。spring-boot
要点1,在pom.xml里引入spring boot的依赖项,关键代码以下。
1 <dependency> 2 <groupId>org.springframework.boot</groupId> 3 <artifactId>spring-boot-starter-web</artifactId> 4 <version>1.5.4.RELEASE</version> 5 </dependency>
要点2,在ServerStarter.java里,开启服务,代码以下。
1 //省略必要的package和import代码 2 @SpringBootApplication 3 public class ServerStarter{ 4 public static void main( String[] args ) 5 { SpringApplication.run(ServerStarter.class, args); } 6 }
要点3,在控制器Controller.java里,编写两个提供服务的方法,代码以下。
1 @RestController 2 public class Controller { 3 @RequestMapping(value = "/available", method = RequestMethod.GET ) 4 public String availabieService() 5 { return "This Server works well."; } 6 7 @RequestMapping(value = "/unavailable", method = RequestMethod.GET ) 8 public String unavailableServicve () { 9 try { Thread.sleep(5000); } 10 catch (InterruptedException e) 11 { e.printStackTrace(); } 12 return "This service is unavailable."; 13 } 14 }
其中在第3行提供了一个可用的服务,在第8行的unavailableServicve的服务里,是经过第9行的sleep方法,形成“服务延迟返回”的效果。
这里咱们新建一个HystrixClientDemo项目,在其中开发各类Hystrix调用服务的代码。
在这个项目里,咱们将经过Ribbon和Hystrix结合的方式,调用在上部分里提供的服务,因此在pom.xml文件里,咱们将引入这两部分的依赖包,关键代码以下。
1 <dependencies> 2 <dependency> 3 <groupId>com.netflix.ribbon</groupId> 4 <artifactId>ribbon-httpclient</artifactId> 5 <version>2.2.0</version> 6 </dependency> 7 <dependency> 8 <groupId>com.netflix.hystrix</groupId> 9 <artifactId>hystrix-core</artifactId> 10 <version>1.5.12</version> 11 </dependency> 12 </dependencies>
在上述代码的第2到第6行里,咱们引入了Ribbon的依赖项,从第7到第11里,咱们引入了Hystrix的依赖项。
在NormalHystrixDemo.java里,咱们将演示经过Hystrix调用正常服务的开发方式,代码以下。
1 //省略必要的package和import代码 2 //继承HystrixCommand<String>,因此run方法返回String类型对象 3 public class NormalHystrixDemo extends HystrixCommand<String> { 4 //定义访问服务的两个对象 5 RestClient client = null; 6 HttpRequest request = null; 7 //在构造函数里指定命令组的名字 8 public NormalHystrixDemo() { 9 super(HystrixCommandGroupKey.Factory.asKey("demo")); 10 } 11 //在initRestClient方法里设置访问服务的client对象 12 private void initRestClient() { 13 client = (RestClient) ClientFactory.getNamedClient("HelloCommand"); 14 try { 15 request = HttpRequest.newBuilder().uri(new URI("/available")).build(); 16 } catch (URISyntaxException e) 17 { e.printStackTrace(); } 18 ConfigurationManager.getConfigInstance().setProperty( "HelloCommand.ribbon.listOfServers", "localhost:8080"); 19 }
在第12行的initRestClient方法里,咱们作好了以基于Ribbon的RestClient对象访问服务的准备工做,具体而言,在第13行里经过工厂初始化了client对象,在第18行,设置了待访问的url,在第15行,设置了待访问的服务名。
20 protected String run() { 21 System.out.println("In run"); 22 HttpResponse response; 23 String result = null; 24 try { 25 response = client.executeWithLoadBalancer(request); 26 System.out.println("Status for URI:" + response.getRequestedURI()+ " is :" + response.getStatus()); 27 result = response.getEntity(String.class); 28 } catch (ClientException e) 29 { e.printStackTrace();} 30 catch (Exception e) { e.printStackTrace(); } 31 return "Hystrix Demo,result is: " + result; 32 }
咱们在第20行定义了返回String类型的run方法, 这里的返回类型须要和第3行里本类继承的HystrixCommand对象的泛型一致。在其中,咱们是经过第25行的代码调用服务,并在第31行,返回一个包括调用结果的String字符串。
public static void main(String[] args) { 34 NormalHystrixDemo normalDemo = new NormalHystrixDemo(); 35 //初始化调用服务的环境 36 normalDemo.initRestClient(); 37 // 睡眠1秒 38 try {Thread.sleep(1000);} 39 catch (InterruptedException e) 40 {e.printStackTrace(); } 41 //调用execute方法后,会自动地执行定义在第20行的run方法 42 String result = normalDemo.execute(); 43 System.out.println("Call available function, result is:" + result); 44 } 45 }
在main方法里,咱们指定了以下的工做流程。
第一步,在第36行里,经过调用initRestClient方法完成了初始化的工做。
第二步,在第42行里执行了execute方法,这个方法是封装在HystrixCommand方法里的,一旦调用,则会触发第20行的run方法。
请注意,这里一旦执行execute方法,则会当即(即以同步的方式)执行run方法,在run方法返回结果以前,代码是会阻塞在第42行的,即不会继续日后执行。
第三步,在第20行的run方法里,咱们以localhost:8080/available的方式调用了服务端的服务。
执行本段代码,会看到以下的打印语句,这些打印语句很好地验证了上述讲述的过程流程。
1 In run 2 Status for URI:http://localhost:8080/available is :200 3 Call available function, result is:Hystrix Demo,result is: This Server works well.
在上部分的Hystrix案例中,请求是被依次执行,在处理完上个请求以前,后一个请求处于阻塞等待状态,这种Hystrix同步的处理方式适用于并发量通常的场景。
但单台服务器的负载处理能力毕竟是有限的,若是并发量高于(或远远高于)这个极限时,那么咱们就得考虑采用Hystrix基于异步的保护机制,从下图里,咱们能看到基于异步处理的效果图。
从上图里咱们能看到,请求不是被同步地当即执行,而是被放入到一个队列(queue)中,封装在HystrixCommand的处理代码是从queue里拿出请求,并以基于hystrix保护措施的方式处理该请求。在下面的AsyncHystrixDemo.java里,咱们将演示hystrix异步执行的方式。
1 //省略必要的package和import代码 2 //这里一样是继承HystrixCommand<String>类 3 public class AsyncHystrixDemo extends HystrixCommand<String> { 4 RestClient client = null; 5 HttpRequest request = null; 6 public AsyncHystrixDemo() { 7 // 指定命令组的名字 8 super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup")); 9 } 10 private void initRestClient() { 11 client = (RestClient) ClientFactory.getNamedClient("AsyncHystrix"); 12 try { 13 request = HttpRequest.newBuilder().uri(new URI("/available")).build(); 14 } 15 catch (URISyntaxException e) 16 { e.printStackTrace(); } 17 ConfigurationManager.getConfigInstance().setProperty( 18 "AsyncHystrix.ribbon.listOfServers", "localhost:8080"); 19 } 20 protected String run() { 21 System.out.println("In run"); 22 HttpResponse response; 23 String result = null; 24 try { 25 response = client.executeWithLoadBalancer(request); 26 System.out.println("Status for URI:" + response.getRequestedURI() + " is :" + response.getStatus()); 27 result = response.getEntity(String.class); 28 } 29 catch (ClientException e) {e.printStackTrace(); } 30 catch (Exception e) { e.printStackTrace(); } 31 return "Hystrix Demo,result is: " + result; 32 }
在上述代码的第6行里,咱们定义了构造函数,在第10行里,定义了初始化Ribbon环境的initRestClient方法,在第20行里,定义了执行hytrix业务的run方法。这三个方法和刚才讲到的NormalHystrixDemo类里很类似,因此就再也不详细讲述。
33 public static void main(String[] args) { 34 AsyncHystrixDemo asyncDemo = new AsyncHystrixDemo(); 35 asyncDemo.initRestClient(); 36 try { Thread.sleep(1000);} 37 catch (InterruptedException e) 38 { e.printStackTrace(); } 39 //上述代码是初始化环境并sleep 1秒 40 //获得Future对象 41 Future<String> future = asyncDemo.queue(); 42 String result = null; 43 try { 44 System.out.println("Start Async Call"); 45 //经过get方法以异步的方式调用请求 46 result = future.get(); 47 } catch (InterruptedException e) 48 { e.printStackTrace();} 49 catch (ExecutionException e) 50 { e.printStackTrace(); } 51 System.out.println("Call available function, result is:" + result); 52 } 53 }
在main函数的34到38行,咱们一样是初始化了Ribbon环境,这和以前的NormalHystrixDemo类的作法是同样的。
在第41行里,咱们经过queue方法,获得了一个包含调用请求的Future<String>类型的对象,而在第46行里,咱们是经过future对象的get方法执行请求。
这里有两个看点,第一,在执行第46行的get方法后,HystrixComman会自动调用定义在第20行的run方法,第二,这里获得请求对象是在第41行,而调用请求则在46行,也就是说,并非在请求到达时就当即执行,而是经过异步的方式执行。
本部分代码的执行结果和NormalHystrixDemo.java是同样的,因此就再也不给出了。