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