hystrix弹性客户端

This commit is contained in:
fxb 2018-12-17 21:45:04 +08:00
parent c8bbd1cfda
commit 132abf6edc
10 changed files with 686 additions and 366 deletions

View File

@ -0,0 +1,204 @@
---
id="2018-11-28-15-57-00"
title="springCloud之Netflix Hystrix弹性客户端"
headWord="在任何分布式架构中,都需要找到机器所在的物理地址,这个概念自分布式计算开始就已经存在,并且被正式称为服务发现,本篇是对服务发现的一个学习总结"
tags=["spring-boot", "spring-cloud","netflix-hystrix"]
category="java"
serie="springCloud实战"
---
# 一、为什么要有客户端弹性模式
  所有的系统都会遇到故障,分布式系统单点故障概率更高。如何构建应用程序来应对故障,是每个软件开发人员工作的关键部分。但是通常在构建系统时,大多数工程师只考虑到基础设施或关键服务彻底发生故障,使用诸如集群关键服务器、服务间的负载均衡以及异地部署等技术。尽管这些方法考虑到组件系统的彻底故障,但他们之解决了构建弹性系统的一小部分问题。当服务崩溃时,很容易检测到该服务以及失效,因此应用程序可以饶过它。然而,当服务运行缓慢时,检测到这个服务性能越发低下并绕过它是非常困难的,因为以下几个原因:
- 服务的降级可以是以间歇性的故障开始,并形成不可逆转的势头————可能开始只是一小部分服务调用变慢,直到突然间应用程序容器耗尽了线程(所有线程都在等待调用完成)并彻底崩溃。
- 应用程序通常的设计是处理远程资源的彻底故障,而不是部分降级————通常,只要服务没有完全死掉,应用程序将继续调用这个服务,直到资源耗尽崩溃。
  性能较差的远程服务会导致很大的潜在问题,它们不仅难以检测,还会触发连锁反应,从而影响整个应用程序生态系统。如果没有适当的保护措施,一个性能不佳的服务可以迅速拖垮整个应用程序。基于云、基于微服务的应用程序特别容易受到这些类型的终端影响,因为这些应用由大量细粒度的分布式服务组成,这些服务在完成用户的事务时涉及不同的基础设施。
# 二、什么是客户端弹性模式
  客户端弹性模式是在远程服务发生错误或表现不佳时保护远程资源另一个微服务调用或者数据库查询免于崩溃。这些模式的目标是为了能让客户端“快速失败”不消耗诸如数据库连接、线程池之类的资源还可以避免远程服务的问题向客户端的消费者进行传播引发“雪崩”效应。spring cloud 主要使用的有四种客户端弹性模式:
- 客户端负载均衡client load balance模式
  上一篇已经说过,这里不再赘述。
- 断路器(circuit breaker)模式
  本模式模仿的是电路中的断路器。有了软件断路器,当远程服务被调用时,断路器将监视这个调用,如果调用时间太长,断路器将介入并中断调用。此外,如果对某个远程资源的调用失败次数达到某个阈值,将会采取快速失败策略,阻止将来调用失败的远程资源。
- 后备(fallback)模式
  当远程调用失败时,将执行替代代码路径,并尝试通过其他方式来处理操作,而不是产生一个异常。也就是为远程操作提供一个应急措施,而不是简单的抛出异常。
- 舱壁(bulkhead)模式
  舱壁模式是建立在造船的基础概念上。我们都知道一艘船会被划分为多个水密舱(舱壁),因而即使少数几个部位被击穿漏水,整艘船并不会被淹没。将这个概念带入到远程调用中,如果所有调用都使用的是同一个线程池来处理,那么很有可能一个缓慢的远程调用会拖垮整个应用程序。在舱壁模式中可以隔离每个远程资源,并分配各自的线程池,使之互不影响。
  下图展示了这些模式是如何运用到微服务中的:
![客户端弹性模式架构](./picFolder/客户端弹性模式架构.png)
# 三、spring cloud 中使用
  使用 Netflix 的 Hystrix 库来实现上述弹性模式。继续使用上一节的项目,给 licensingservice 服务实现弹性模式。
## 1、代码修改
### 1、依赖引入
  首先修改 POM 文件,添加下面两个依赖:
```xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
<!--本依赖不是必须的spring-cloud-starter-hystrix已经带了但是在Camden.SR5发行版本中使用了1.5.6,这个版本有一个不一致的地方在没有后备的情况下会抛出java.lang.reflect.UndeclaredThrowableException而不是com.netflix.hystrix.exception.HystrixRuntimeException,
在后续版本中修复了这个问题-->
<dependency>
<groupId>com.netflix.hystrix</groupId>
<artifactId>hystrix-javanica</artifactId>
<version>1.5.9</version>
</dependency>
```
&emsp;&emsp;然后在启动类上加入`@EnableCircuitBreaker`启用 Hystrix。
## 2、实现断路器
&emsp;&emsp;首先修改 organizationservice 项目中的 OrganizationController模拟延迟每隔两次让线程 sleep 2 秒
```java
@RestController
public class OrganizationController {
private static int count=1;
@GetMapping(value = "/organization/{orgId}")
public Object getOrganizationInfo(@PathVariable("orgId") String orgId) throws Exception{
if(count%2==0){
TimeUnit.SECONDS.sleep(2);
}
count++;
Map<String, String> data = new HashMap<>(2);
data.put("id", orgId);
data.put("name", orgId + "公司");
return data;
}
}
```
&emsp;&emsp;只需在方法上添加`@HystrixCommand`,即可实现超时短路。如果 Spring 扫描到该注解注释的类,它将动态生成一个代理,来包装这个方法,并通过专门用于处理远程调用的线程池来管理对该方法的所有调用。
&emsp;&emsp;修改 licensingservice 服务中的 OrganizationByRibbonService,OrganizationFeignClient给其中的方法加上`@HystrixCommand`的注解。然后再访问接口[localhost:10011/licensingByRibbon/11313](localhost:10011/licensingByRibbon/11313),[localhost:10011/licensingByFeign/11313](localhost:10011/licensingByFeign/11313)。多次访问可发现抛出错误`com.netflix.hystrix.exception.HystrixRuntimeException`,断路器生效,默认情况下操时时间为 1s。
```json
{
"timestamp": 1543823192424,
"status": 500,
"error": "Internal Server Error",
"exception": "com.netflix.hystrix.exception.HystrixRuntimeException",
"message": "OrganizationFeignClient#getOrganization(String) timed-out and no fallback available.",
"path": "/licensingByFeign/11313/"
}
```
&emsp;&emsp;可通过设置注解参数来修改操时时间。设置超时时间大于 2s 后便不会报操时错误。(不知道为什么在 Feign 中设置失败ribbon 中正常。)。一般都是将配置写在配置文件中。
```java
@HystrixCommand(commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "20000")
})
```
## 3、后备处理
&emsp;&emsp;由于远程资源的消费者和资源本身之间存在存在一个"中间人",因此开发人员能够拦截服务故障,并选择替代方案。在 Hystrix 中进行后备处理,非常容易实现。
1. 在 ribbon 中的实现
&emsp;&emsp;只需在`@HystrixCommand`注解中加入属性 fallbackMethod="methodName",那么在执行失败时,便会执行后备方法。注意防备方法必须和被保护方法在同一个类中,并且方法签名必须相同。修改 licensingservice 中 service 包下的 OrganizationByRibbonService 类,改为如下:
```java
@Component
public class OrganizationByRibbonService {
private RestTemplate restTemplate;
@Autowired
public OrganizationByRibbonService(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
@HystrixCommand(commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1000")
},fallbackMethod = "getOrganizationWithRibbonBackup")
public Organization getOrganizationWithRibbon(String id) throws Exception {
ResponseEntity<Organization> responseEntity = restTemplate.exchange("http://organizationservice/organization/{id}",
HttpMethod.GET, null, Organization.class, id);
return responseEntity.getBody();
}
public Organization getOrganizationWithRibbonBackup(String id)throws Exception{
Organization organization = new Organization();
organization.setId("0");
organization.setName("组织服务调用失败");
return organization;
}
}
```
&emsp;&emsp;启动应用,多次访问[localhost:10011/licensingByRibbon/11313/](localhost:10011/licensingByRibbon/11313/),可以发现调用失败时,会启用后备方法。
2. 在 feign 中实现
&emsp;&emsp;在 feign 中实现后备模式,需要编写一个 feign 接口的实现类,然后在 feign 接口中指定该类。以 licensingservice 为例。首先在 client 包中添加一个 OrganizationFeignClientImpl 类,代码如下:
```java
@Component
public class OrganizationFeignClientImpl implements OrganizationFeignClient{
@Override
public Organization getOrganization(String orgId) {
Organization organization=new Organization();
organization.setId("0");
organization.setName("后备模式返回的数据");
return organization;
}
}
```
然后修改OrganizationFeignClient接口的注解`@FeignClient("organizationservice")`改为`@FeignClient(name="organizationservice",fallback = OrganizationFeignClientImpl.class`
&emsp;&emsp;重启项目,多次访问[localhost:10011/licensingByFeign/11313/](localhost:10011/licensingByFeign/11313/),可发现后备服务起作用了。
&emsp;&emsp;在确认是否要启用后备服务时,要注意以下两点:
- 后备是一种在资源操时或失败时提供行动方案的机制。如果只是用后备来捕获操时异常然后只做日志记录那只需要try..catch即可捕获HystrixRuntimeException异常。
- 注意后备方法所执行的操作。如果在后备服务中调用另一个分布式服务,需要注意用@HystrixCommand方法注解包装后备方法
## 4、实现舱壁模式
&emsp;&emsp;在基于微服务的应用程序中通常需要调用多个微服务来完成特定的任务在不适用舱壁的模式下这些调用默认是使用同一批线程来执行调用的而这些线程是为了处理整个Java容器的请求而预留的。因此在存在大量请求的情况下一个服务出现性能问题会导致Java容器内的所有线程被占用同时阻塞新请求最终容器彻底崩溃。
&emsp;&emsp;Hystrix使用线程池来委派所有对远程服务的调用,默认情况下这个线程池有10个工作线程。但是这样很容易出现一个运行缓慢的服务占用全部的线程所有hystrix提供了一种一种易于使用的机制在不同的远程资源调用间创建舱壁将不同服务的调用隔离到不同的线程池中使之互不影响。
&emsp;&emsp;要实现隔离的线程池,只需要在`@HystrixCommand`上加入线程池的注解这里以ribbon为例Feign类似)。修改licensingservice中service包下的OrganizaitonByRibbonService类`getOrganizationWithRibbon`方法的注解改为如下:
```java
@HystrixCommand(commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1000")
}, fallbackMethod = "getOrganizationWithRibbonBackup",
threadPoolKey = "licenseByOrgThreadPool",
threadPoolProperties = {
@HystrixProperty(name = "coreSize", value = "30"),
@HystrixProperty(name = "maxQueueSize", value = "10")
})
```
如果将`maxQueueSize`属性值设为-1将使用`SynchronousQueue`保存所有的传入请求同步队列会强制要求正在处理中的请求数量永远不能超过线程池的大小。设为大于1的值将使用`LinkedBlockingQueue`
&emsp;&emsp;**注意**示例代码中都是硬编码属性值到Hystrix注解中的。在实际应用环境中一般都是将配置项配置在Spring Cloud Config中的方便统一管理。
本次用到全部代码:[记得补全代码呀]()

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View File

@ -1 +1 @@
{"2018-11-22-15-57-00":0,"2018-11-20-10-38-05":0,"2018-11-19-15-57-00":0,"2018-10-22-10-38-05":4,"2018-10-21-10-38-05":0,"2018-10-20-10-38-05":20} {"2018-11-22-15-57-00":14,"2018-11-20-10-38-05":18,"2018-11-19-15-57-00":3,"2018-10-22-10-38-05":14,"2018-10-21-10-38-05":3,"2018-10-20-10-38-05":20}

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@ -0,0 +1,116 @@
---
id="2018-12-17-13-58"
title="vscode开发调试typescript"
headWord="本篇用于记录如何在vscode下开发typescript以及端点调试ts代码"
tags=["vscode", "node","typescript","ts"]
category="node"
serie="node开发环境配置"
---
### 1、安装 typescript
&emsp;&emsp;npm 全局安装 typescript
```bash
npm i -g typescript
```
### 2、初始化 ts 配置
&emsp;&emsp;以 test 项目为例,在 test 目录下执行`tsc --init`,会在项目目录下产生一个`tsconfig.json`的配置文件,每项配置都有说明,如下图所示:
![tscofnig文件部分截图](./picFolder/tsconfig界面.png)
主要用到的配置项如下:
```javascript
{
"compilerOptions": {
"module": "commonjs", //指定生成哪个模块系统代码
"target": "ES2017", //目标代码类型
"noImplicitAny": false, //在表达式和声明上有隐含的'any'类型时报错。
"sourceMap": true, //用于debug,断点调试需打开
"rootDir":"./", //需要编译的目录
"outDir":"./dist", //编译后的js存放路径
"watch":true //在监视模式下运行编译器。会监视输出文件,在它们改变时重新编译。
"strict":true //严格模式
},
//需要监控的路径,非必要无需配置有rootDir就够了
"include":[
"./"
],
//需要排除的路径,非必要无需配置
//排除监控目录
"exclude":[
"node_module",
"temp"
]
}
```
### 3、编译
&emsp;&emsp;执行`tsc -w`,将 ts 编译位 js 并输出到 dist 目录下,`-w`监控文件变动,自动编译。
### 4、断点运行
&emsp;&emsp;首先添加 launch.json,如图所示:
![添加配置](./picFolder/添加配置.png)
选中 node 环境,然后将要调试的文件改为输出目录中对应的 js 文件,修改 program 属性:
```json
"program": "${workspaceFolder}\\dist\\index.js"
```
最后**F5**运行即可断点调试。
### 5、热更新
&emsp;&emsp;如何在修改文件保存后自动重启服务器,首先全局安装`nodemon`,然后在 test 根目录下编写 nodemon 配置文件--nodemon.json实际使用中不能带注释需删除
```json
{
"restartable": "rs", //重启的命令,默认是 rs
"ignore": [""], //忽略的文件后缀名或者文件夹,文件路径的书写用相对于 nodemon.json 所在位置的相对路径
"verbose": true, //表示输出详细启动与重启信息
"events": {
"start": "node ./dist/index.js", //子进程(即监控的应用)启动
"crash": "", //子进程崩溃,不会触发 exit
"exit": "", //子进程完全退出,不是非正常的崩溃
"restart": "" //子进程重启
},
"ext": "js json", //监控指定后缀名的文件,用空格间隔。默认监控的后缀文件:.js, .coffee, .litcoffee, .json。但是对于没有文件后缀的文件比如 www 文件,我暂时找不到怎么用 nodemon 去监控,就算在 watch 中包含了nodemon 也会忽略掉
"watch": ["./src/**"],
"env": {
"NODE_ENV": "env", // env 是开发环境; 运行环境 development; production 是生产环境
"PORT": "3000"
},
"legacy-watch": false //nodemon 使用 Chokidar 作为底层监控系统但是如果监控失效或者提示没有需要监控的文件时就需要使用轮询模式polling mode即设置 legacy-watch 为 true也可以在命令行中指定
}
```
然后使用`nodemon --inspect nodemon.json`命令启动(--inspect 是 vscode 断点调试关键),接着在.vscode 目录中将 launch.json 修改为下面的代码:
```json
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "attach",
"name": "Node: Nodemon",
"processId": "${command:PickProcess}",
"restart": true,
"protocol": "inspector"
}
]
}
```
最后调试页面启动调试,选择代码真实运行的进程,如下图所示:
![vscode符加进程](./picFolder/vscode附加进程.png)
&emsp;&emsp;大功告成。。