Spring Cloud OpenFeign 基于 Netflix Feign 实现,整合了 Spring Cloud Ribbon 与 Spring Cloud Hystrix ,除了提供这两者的强大功能之外,它还提供了一种声明式的 Web 服务客户端定义方式,让我们可以像使用本地方法一样来进行远程服务调用。
快速入门
通过一个简单的示例来展现 Spring Cloud Feign 在服务客户端定义上所带来的便利。
下面的示例将继续使用之前我们实现的 order-service 服务,这里我们会通过 Spring Cloud Feign 提供的声明式服务绑定功能来实现对该服务接口的调用。
配置代码
1.引入依赖
orderservice 服务的 pom.xml 文件中,引入如下依赖:
<!--spring cloud openFeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
openfeig 依赖中已然包含了 ribbon 的依赖
2.启用 Feign
在 orderservice 的启动类上,添加注解@EnableFeignClients
@MapperScan("com.niit.order.mapper")
@SpringBootApplication
@EnableFeignClients
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
3.定义服务接口
定义一个接口,通过@FeignClient 注解指定服务名来绑定服务,然后再使用 Spring MVC 的注解来绑定具体该服务提供的 REST 接口。
@FeignClient("userservice")
public interface UserClient {
/**
* 根据ID获取用户信息
*/
@GetMapping("/user/{id}")
User findUserById(@PathVariable("id") String id);
}
注意:
注解中的服务名不区分大小写,所以使用USERSERVICE和userservice都 是可以的。注意接口的访问路径要完整, 如果匹配的路径不对, 会报 404 错误.
4.创建 Controller
创建一个 Controller 来实现对 Feign 客户端的调用。使用 @Autowired 直接注入上面定义的 UserClient 实例,并在 helloConsumer 函数中调用这个绑定了 userservice 服务接口的客户端来向该服务发起接口的调用。
@RestController
@RequestMapping("/feign")
public class FeignDemoController {
@Autowired
private OrderService orderService;
@Autowired
private UserClient userClient;
@GetMapping("/order/{orderId}")
public Order queryOrderByUserId(@PathVariable("orderId") Long orderId) {
// 根据id查询订单并返回
Order order = orderService.queryOrderById(orderId);
// 使用 Feign 封装的客户端
User forObject = userClient.findUserById(order.getUserId()+"");
//封装返回的数据
order.setUser(forObject);
return order;
}
}
测试验证
如之前验证 Ribbon 客户端负载均衡一样,我们先启动服务注册中心 Nacos 以及 orderservice 服务和多个 userservice 服务。此时我们在 Nacos 信息面板中可看到 如下内容:

发送几次 GET 请求到 http://localhost:8080/feign/order/{orderId}, 可以得到如 之前 Ribbon 实现时一样的效果,正确返回了 userservice 的数据。

并且根据控制台的输出,我们可以看到 Feign 实现的消费者,依然是利用 Ribbon 维护了针对 userservice 的服务列表信息,并且通过轮询实现了客户端负载均衡。而与 Ribbon 不同的是,通过 Feign 我们只需定义服务绑定接口,以声明式的方法,优雅而简单地实现了服务调用。
Feign 使用起来看起来更像是一个本地接口的实现,而不是一个远程服务的调用。这样的设计使得我们在使用 Feign 的时候,可以像调用本地方法一样调用远程服务。
小问题 :
RestTemplate 己经实现了对 __ 请求的封装处理,形成了一套模板化的调用方法?
- FTP
- HTTP
- TCP
- IP
答案
2. HTTP
参数绑定
在本节中,我们将详细介绍 Feign 中对几种不同形式参数的绑定方法。
扩展一下服务提供方 orderservice 。增加下面这些接口定义,其中包含:
- 带有 Request 参数的请求
- 带有 Header 信息的请求
- 带有 RequestBody 的请求并且请求响应体中是一个对象的请求。
@RestController
@RequestMapping("/provider/feign")
public class FeignDemoController {
/* request参数的请求 */
@GetMapping(value = "/request")
public String hello (@RequestParam("name") String name) {
return "Hello" + name;
}
/* header信息的请求 */
@GetMapping(value = "/header" )
public User hello(@RequestHeader String name, @RequestHeader String address) {
User user = new User();
user.setId(IdUtil.simpleUUID());
user.setUsername(name);
user.setAddress(address);
return user;
}
/* requestbody的请求 */
@PostMapping(value = "/body")
public String hello(@RequestBody User user) {
return "Hello, " + user .getUsername() + ", " + user .getAddress();
}
}
实体类 User 的定义如下:
@Data
public class User {
private String id;
private String username;
private String address;
}
需要注意的是,这里必须要有 User 的默认构造函数。不然, Spring Cloud Feign 根据 JSON 字符串转换 User 对象时会抛出异常。我们使用 Lombok 插件的 @Data 注解, 可以避免编写标准 java bean 的繁琐步骤,同时提供相应的默认构造。
在完成了对 orderservice 的改造之后,下面我们开始在快速入门示例的 UserClient 接口中实现这些新增的请求的绑定。
@FeignClient("userservice")
public interface UserClient {
/**
* 根据ID获取用户信息
* @param id
* @return
*/
@GetMapping("/user/{id}")
User findUserById(@PathVariable("id") String id);
/**
* request参数的请求
* @param name
* @return
*/
@GetMapping(value = "/provider/feign/request")
public String hello (@RequestParam("name") String name);
/**
* header信息的请求
* @param name
* @param address
* @return
*/
@GetMapping(value = "/provider/feign/header" )
public User hello(@RequestHeader String name, @RequestHeader String address);
/**
* requestbody的请求
* @param user
* @return
*/
@PostMapping(value = "/provider/feign/body")
public String hello(@RequestBody User user) ;
}
最后,在 Controller 中新增方法,来对本节新增的声明接口进行调用,修改后的完整代码如下所示:
@RestController
@RequestMapping("/feign")
public class FeignConsumerController {
@Autowired
private OrderService orderService;
@Autowired
private UserClient userClient;
@GetMapping("/order/{orderId}")
public Order queryOrderByUserId(@PathVariable("orderId") Long orderId) {
// 根据id查询订单并返回
Order order = orderService.queryOrderById(orderId);
// 使用Feign 封装的客户端
User forObject = userClient.findUserById(order.getUserId()+"");
//封装返回的数据
order.setUser(forObject);
return order;
}
@GetMapping("/order/request")
public String requestParamDemo(@RequestParam String param){
return userClient.requestParam(param);
}
@GetMapping("/order/header")
public User headerInfo(@RequestHeader String name,@RequestHeader String address){
return userClient.headerInfo(name,address);
}
@GetMapping("/order/body")
public String bodyInfo(@RequestBody User user){
return userClient.bodyInfo(user);
}
}
测试验证
在完成上述改造之后,启动服务注册中心 Nacos、order-service 服务以及 user-service 服务 。通过浏览器访问 order-service 的 FeignConsumerController 的 requestParamDemo 方法, 触发新增接口的调用。最终,我们会获得如下输出,代表接口绑定和调用成功:

其他两种参数绑定的调用,我们可以使用 IDEA 的一款小插件FastRequest进行测试:(FastRequest 是收费插件, 可以用免费插件 RestfulTool 代替)
添加请求头:

点击发送并查看响应结果:

同样的, RequestBody 的参数也可以添加请求体,点击发送按钮进行测试,结果如下:

实践方案
我们几乎完全可以从服务提供方的 Controller 中依靠复制操作,构建出相应的服务客户端绑定接口, 然后添加@FeignClient 注解即可。使用的时候,直接注入到 Controller 中即可。
这部分内容是否可以得到进一步的抽象呢?在 Spring Cloud Feign 中,针对该问题我们通常有两种解决方案,以进一步减少编码量。
- 通过继承的方式实现接口定义的共享
- 创建独立的 Api 模块, 通过依赖的方式实现接口定义的共享
继承方案
一样的代码可以通过继承来共享:
-
定义业务相关接口,利用接口定义,并基于 SpringMVC 注解做声明。
- 注意定义 MappingUrl 时,需要使用绝对路径,否则会出现 404 错误。
-
Feign 客户端和 Controller 都继承该接口。
- 这样接口实际只需要定义一次,就可以在 Feign 客户端和 Controller 中使用。免去了复制操作。

图示中, UserClient 继承了 UserApi 接口。这样,我们就可以在 UserClient 中直接使用 UserApi 中定义的接口,而不需要再次定义。在 Controller 中, 也可以直接注入 UserClient,并调用其中的方法。
使用 Spring Cloud Feign 继承特性的优点很明显,可以将接口的定义从 Controller 中剥离,实现在构建期的接口绑定,从而有效减少服务客户端的绑定配置。这么做虽然可以很方便地实现接口定义和依赖 的共享,不用再复制粘贴接口进行绑定.
但是这样通过共享接口的做法使用不当的话会带来副作用。
由于接口在构建期间就建立起了依赖,所以微服务之间的耦合度会变得很高。因此接口变动就会对项目构建造成影响。
可能服务提供方修改了一个接口定义,那么会直接导致客户端工程的构建失败。所以,如果开发团队通过此方法来实现接口共享的话,建议在开发评审期间严格遵守面向对象的开闭原则,尽可能地做好前后版本的兼容,防止牵一发而动全身的后果,增加团队不必要的维护工作量。
基于以上原因,官方并不推荐在项目中使用继承方案,而是更推荐我们由服务提供方新建立一个 API 服务,提供给所有的消费者使用。接下来,我们将会详细讲解这种实践方案。
独立模块
将 Feign 的 Client 抽取为独立的微服务模块,并且把接口有关的 POJO、默认的 Feign 配置都放到这个微服务模块中,提供给所有消费者微服务项目使用。例如,将 UserClient、User、Feign 的默认配置都抽取到一个 feign-api 包中,所有微服务引用该依赖包,即可直接使用。
下面是构建 feign-api 的具体步骤:

1.创建 module
首先创建一个 module,命名为 feign-api:

2.引入依赖
在 feign-api 中然后引入 feign 的 starter 依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
然后,order-service 中编写的 UserClient、User 都复制到 feign-api 项目中。

通过 maven 的 install 对项目进行构建和安装,将 feign-api 安装到本地仓库中。
3.使用 feign-api
在 order-service 的 pom 文件中从本地 maven 仓库中引入 feign-api 的依赖:
<dependency>
<groupId>com.niit.demo</groupId>
<artifactId>feign-api</artifactId>
<version>1.0</version>
</dependency>
修改 order-service 中的所有与上述三个组件有关的导包部分,改成导入 feign-api 中的包
4.修改启动类
由于 feign-api 与 order-service 项目的包路径并不完全一致,为了解决 spring boot 工程默认包扫描策略导致无法注入问题,需要在启动类上声明所用的 feign 客户端的列表。 修改 order-service 的启动类,补充@EnabledFeignClients 的属性内容。
@MapperScan("com.niit.order.mapper")
@SpringBootApplication
@EnableCircuitBreaker //启用断路器功能
@ServletComponentScan(basePackages = {"com.niit.order.filter"})
@EnableFeignClients(clients = {UserClient.class}) //解决包扫描无法注入问题
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
5.启动测试
现在,像上一小节参数绑定中的案例一样,依次启动服务注册中心 Nacos,order-service 与 user-service 服务进行测试。测试结果如下:

添加 Ribbon 配置
由于 Spring Cloud Feign 的客户端负载均衡是通过 Spring Cloud Ribbon 实现的,所以我们可以直接通过配置 Ribbon 客户端的方式来自定义各个服务客户端调用的参数。那么我们 如何在使用 Spring Cloud Feign 的工程中使用 Ribbon 的配置呢?
全局 Ribbon 配置
全局配置的方法非常简单,我们可以直接在配置文件中设置:
ribbon:
NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule #负载均衡规则的全类名
ConnectTimeout: 250 #请求连接的超时时间
ReadTimeout: 1000 #请求处理的超时时间
OkToRetryOnAllOperations: true #对所有操作请求都进行重试
MaxAutoRetriesNextServer: 2 #切换实例的重试次数
MaxAutoRetries: 1 #对当前实例的重试次数。
对具体服务进行 Ribbon 配置
在配置文件中, 除了针对所有服务进行全局配置, 还可以针对特定的服务进行个性化配置.
在定义 Feign 客户端的时候,我们使用了 @FeignClient 注解。在初始化 过程中,Spring Cloud Feign 会根据该注解的 name 属性或 value 属性指定的服务名,自动创建一个同名的 Ribbon 客户端。
例如使用 @FeignClient(value ="userservice")来创建 Feign 客户端的时候,同时也创建了一个名为 userservice 的 Ribbon 客户端。
既然如此,我们就可以使用 @FeignClient 注解中指定的服务提供者名称来设置对应的 Ribbon 参数,比如:
userservice: # 需要远程访问的服务名
ribbon:
NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule #负载均衡规则的全类名
ConnectTimeout: 250 #请求连接的超时时间
ReadTimeout: 1000 #请求处理的超时时间
OkToRetryOnAllOperations: true #对所有操作请求都进行重试
MaxAutoRetriesNextServer: 2 #切换实例的重试次数
MaxAutoRetries: 1 #对当前实例的重试次数。
添加 Ribbon 重试机制
在 Spring Cloud Feign 中默认实现了请求的重试机制,而上面我们对于 orderservice 客户端的配置内容就是对于请求超时以及重试机制配置的详情,具体内 容可参考第 4 章最后一节关于 Spring Cloud Ribbon 重试机制的介绍。
这里需要注意一点,Ribbon 的超时与 Hystrix 的超时是两个概念。为了让上述实现有效,我们需要让 Hystrix 的超时时间大于 Ribbon 的超时时间,否则 Hystrix 命令超时后,该命令直接熔断,重试机制就没有任何意义了。
hystrix 的超时时间配置如下:
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 3000 #Hystrix 超时时间
Ribbon 的超时时间配置如下:
ribbon:
ConnectTimeout: 250 #请求连接的超时时间
ReadTimeout: 1000 #请求处理的超时时间
添加 Hystrix 配置
在 Spring Cloud Feign 中,除了引入了用于客户端负载均衡的 Spring Cloud Ribbon 之外,还引入了服务保护与容错的工具 Hystrix 。
默认情况Spring Cloud Feign 是关闭Hystrix支持的, 因此需要手动启用Hystrix。开启Hystrix后所有 Feign 客户端的方法都会封装成为 Hystrix 命令中进行服务保护。
在对 Hystrix 进行配置之前,我们需要确认 feign.hystrix.enabled 参数需要先设置为 true, 否则默认会关闭 Feign 客户端的 Hystrix 支持。
feign:
hystrix:
enabled: true # 启用ystrix功能
默认
feign.hystrix.enabled=false, Hystrix 支持是关闭的使用
hystrix.command.default.execution.timeout.enabled=false关闭超时熔断
添加 Hystrix 全局配置
对于 Hystrix 的全局配置同 Spring Cloud Ribbon 的全局配置一样,直接使用它的默认配置前缀 hystrix.command.default 就可以进行设置,比如设置全局的超时时间:
# 断路器的超时时间需要大于Ribbon的超时时间,不然不会触发重试。
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 5000
局部禁用 Hystrix
如果全局启用 Hystrix 支持,但是想要针对某个服务客户端关闭 Hystrix 支持时,可以通过使用 @Scope("prototype") 注解为指定的客户端配置 Feign.Builder 实例.
详细实现步骤如下所示。
1.构建一个配置类
/**
* 针对指定的Feign客户端禁用Hystrix的配置
*/
@Configuration
public class UserClientConfig {
@Bean
@Scope("prototype")
public Feign.Builder feignBuilder() {
return Feign.builder();
}
}
2.引入配置到 Feign 客户端
在 UserClient 的@FeignClient 注解中,通过 configuration 参数引入上面实现的配置。
@FeignClient(value = "userservice",configuration = UserClientConfig.class)
public interface UserClient {
......
}
这样,就可以针对指定的 Feign 客户端来关闭 Hystrix 支持了。
原理是通过
@Scope("prototype")注解为每个客户端创建一个新的实例,而此实例不受 Hystrix 配置的影响。
通过 Hystrix 命令的键值进行配置
对于 Hystrix 命令的配置,在实际应用时往往也会根据实际业务情况制定出不同的配置方案。配置方法也跟传统的 Hystrix 命令的参数配置相似,采用 hystrix.command.<commandKey> 作为前缀。而 <commandKey> 默认情况下会采用 Feign 客户端中的方法名作为标识,所以,针对上一节介绍的重试机制中对 /showAppInfo 接口的熔断超时时间的配置可以通过其方法名作为 <commandKey> 来进行配置.
具体如下:
# 断路器的超时时间需要大于Ribbon的超时时间,不然不会触发重试。
hystrix:
showAppInfo: # @FeignClient中的方法名(Hystrix命令的键值)
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 5000
在使用指定命令配置的时候,需要注意,由于方法名很有可能重复,这个时候相同方法名的 Hystrix 配置会共用,所以在进行方法定义与配置的时候需要做好一定的规划。
服务降级配置
Hystrix 提供的服务降级是服务容错的重要功能, Spring Cloud Feign 提供了一种简单的定义方式,可以优雅的完成服务降级的配置。下面我们在之前创建的 feign-api 工程中进行改造。
- 服务降级逻辑的实现只需要为 Feign 客户端的定义接口编写一个具体的接口实现类。 比如为
UserClient接口实现一个服务降级类UserClientFallback,其中每个重写方法的实现逻辑都可以用来定义相应的服务降级逻辑,具体如下:
/**
* 服务降级实现类, 用于定义服务降级逻辑
* 该类需要实现Feign客户端接口拿到所有需要配置降级的方法
*/
public class UserClientFallback implements UserClient {
@Override
public User findUserById(String id) {
User user = new User();
user.setUsername("findUserById 调用失败");
user.setId(UUID.randomUUID().toString());
return user;
}
@Override
public String requestParam(String name) {
return "requestParam 调用失败";
}
@Override
public User headerInfo(String name, String address) {
User user = new User();
user.setUsername("headerInfo 调用失败");
user.setId(UUID.randomUUID().toString());
return user;
}
@Override
public String bodyInfo(User user) {
return "bodyInfo 调用失败";
}
}
- 在服务绑定接口 UserClient 中,通过
@FeignClient注解的fallback属性来指定对应的服务降级实现类。
@FeignClient(value = "userservice",fallback = UserClientFallback.class)
public interface UserClient {
...
}
-
在 order-service 服务的启动类上使用
@Bean注解声明服务降级类,解决 UserClientFallback 实例无法找到异常@MapperScan("com.niit.order.mapper") @SpringBootApplication @EnableCircuitBreaker //启用断路器功能 @ServletComponentScan(basePackages = {"com.niit.order.filter"}) @EnableFeignClients(clients = {UserClient.class}) //解决包扫描无法注入问题 public class OrderApplication { public static void main(String[] args) { SpringApplication.run(OrderApplication.class, args); } @Bean public UserClientFallback userClientFallback(){ return new UserClientFallback(); } }
上面的做法,是使用了 feign-api 提供的默认的统一服务降级配置类, 对每个接口都配置了对应的服务降级逻辑.
如果,order-service 想自定义某个服务降级逻辑, 只需在 order-service 中创建一个新的类,继承 UserClientFallback,重写其方法, 注入到 spring 容器里即可:
创建新的自定义服务降级类:
public class MyUserClientFallback extends UserClientFallback {
@Override
public String requestParam(String name) {
return "orderservice服务的自定义服务降级方法被触发了...";
}
}
重新注入到 Spring 容器:
@MapperScan("com.niit.order.mapper")
@SpringBootApplication
@EnableCircuitBreaker //启用断路器功能
@ServletComponentScan(basePackages = {"com.niit.order.filter"})
@EnableFeignClients(clients = {UserClient.class}) //解决包扫描无法注入问题
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
//注入order-service自定义的 MyUserClientFallback服务降级类
@Bean
public UserClientFallback userClientFallback(){
return new MyUserClientFallback();
}
}
测试验证
下面我们来验证一下服务降级逻辑的实现。启动服务注册中心 Nacos 和 order-service, 但是不启动 user-service 服务。发送 GET 请求到 http://localhost:8080/feign/order/request?param=world, 因为 user-service 服务没有启动,会直接触发服务降级.
如果没有降级发生, 检查order-service是否在springboot应用配置文件中启用hystrix:
feign:
hystrix:
enabled: true
注意 springcloud2020.0.1之后的版本改为
feign.circuitbreaker.enabled=true
正如我们在 MyUserClientFallback 类中实现的内容,每一个服务接口的断路器实际就是实现类中的重写函数的实现。
小问题:
在 Spring Cloud Feign 中,可以通过 _____ 来关闭 Hystrix 功能?
- feign.hystrix.enabled=false
- feign.hystrix.hystrixCommand=false
- feign.hystrix.disabled=false
- feign.hystrix.hystrix=false
答案
1. feign.hystrix.enabled=false
其他配置
请求压缩
Feign 是通过 http 调用的,那么就牵扯到一个数据大小的问题。如果不经过压缩就发送请求、获取响应,那么会因为流量过大导致浪费流量,这时就需要使用数据压缩,将大流量压缩成小流量。
Spring Cloud Feign 支持对请求与响应进行 GZIP 压缩,以减少通信过程中的性能损耗。
配置请求压缩支持, 找到需要调用 feign 的服务消费者 order-service 模块配置文件中添加如下配置,即可开启:
feign:
compression:
request:
enabled: true # 开启请求压缩
response:
enabled: true # 开启响应压缩
同时,我们也可以对请求的数据类型,以及触发压缩的大小下限进行设置:
feign:
compression:
request:
enabled: true # 开启请求压缩
mime-types: text/html,application/xml,application/json # 设置压缩的数据类型
min-request-size: 2048 # 设置触发压缩的大小下限
值得注意的是,上面的数据类型、压缩大小下限均为默认值。
日志配置
Spring Cloud Feign 在构建服务客户端时,会为每一个客户端都创建一个 feign. Logger 实例,我们可以利用该日志对象的 DEBUG 模式来帮助分析 Feign 的请求细节。
对于 Feign 的 Logger 级别主要有下面 4 类,可根据实际需要进行调整使用。
NONE:不记录任何信息。BASIC:仅记录请求方法、 URL 以及响应状态码和执行时间。HEADERS:除了记录 BASIC 级别的信息之外,还会记录请求和响应的头信息。FULL:记录所有请求与响应的明细,包括头信息、请求体、元数据等
配置 Feign 日志有两种方式:
- 配置文件方式
- Java 配置类方式
日志配置可以配置全局生效,也可以配置针对某个微服务生效,下面我们详细介绍两种方式是如何开启日志配置。
配置文件方式
通过修改服务消费方的 application.yml 文件,配置 Spring Cloud Feign 的日志级别。
全局 FeignClient 配置:
feign:
client:
config:
metaDataClient:
connect-timeout: 3000
read-timeout: 3000
default: #default 代表全局配置
loggerLevel: FULL
针对实例的 FeignClient 配置:
feign:
client:
config:
metaDataClient:
connect-timeout: 3000
read-timeout: 3000
userservice: #服务名称 针对某个微服务的配置
loggerLevel: BASIC
我们以 FULL 级别的日志配置为例,发送一次请求测试,查看控制台日志输出,可以看到日志信息非常完整:

通过 Java 配置类方式配置 FeignClient
需要先声明一个 Bean,用来定义日志的级别。
public class UserClientConfig {
@Bean
public Logger.Level feignLoggerLevel(){
return Logger.Level.FULL;
}
}
使用 Java 配置类进行全局 FeignClient 配置
全局配置,则需要在服务消费方的主启动类上进行配置指定:
@MapperScan("com.niit.order.mapper")
@SpringBootApplication
@EnableCircuitBreaker //启用断路器功能
@ServletComponentScan(basePackages = {"com.niit.order.filter"})
//解决包扫描无法注入, 指定feign的全局配置属性
@EnableFeignClients(clients = {UserClient.class},defaultConfiguration = UserClientConfig.class)
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
使用 Java 配置类进行局部 FeignClient 配置
局部配置,则放在相应的 feign 客户端的 @FeignClient 这个注解中:
@FeignClient(value = "userservice",configuration = UserClientConfig.class)
public interface UserClient {
...
}
值得注意的是,当使用配置文件和使用 Java 代码两种方式同时存在的时候,配置文件的优先级更高。当然,我们不推荐同时配置两种方式,只保留一种方式即可。
活动 6.1:
使用 Feign 实现服务远程调用
练习问题
-
在服务绑定接口 HelloService 中,通过 @FeignClient 注解的 _____ 属性来指定对应的服务降级实现类。
a. fallback
b. back
c. fullback
d. fall查看答案
fallback
-
对于 Hystrix 的全局配置同 Spring Cloud Ribbon 的全局配置一样,直接使用它的默认配置前缀 _____ 就可以进行设置
a. hystrix.command.index
b. hystrix.command.default
c. hystrix.default.command
d. hystrix.index.command查看答案
hystrix.command.default
-
Ribbon 的超时与 Hystrix 的超时是两个概念。为了让上述实现有效,我们需要让 Hystrix 的超时时间 _____ Ribbon 的超时时间?
a. 小于
b. 等于
c. 大于
d. 不等于查看答案
大于
-
我们在使用 Spring Cloud Ribbon 时,通常都会利用它对 _____ 的请求拦截来实现对依赖服务的接口调用。
a. FeignTemplate
b. SpringTemplate
c. CloudTemplate
d. RestTemplate查看答案
RestTemplate
小结
在本章中,您学习了:
- 我们在使用 Spring Cloud Ribbon 时,通常都会利用它对 RestTemplate 的请求拦截来实现对依赖服务的接口调用,而 RestTemplate 己经实现了对 HTTP 请求的封装处理,形成了一套模板化的调用方法。
- 在 Spring Cloud Feign 的实现下,我们只需创建一个接口并用注解的方式来配置它,即可完成对服务提供方的接口绑定,简化了在使用 Spring Cloud Ribbon 时自行封装服务调用客户端的开发量。
- Spring Cloud Feign 具备可插拔的注解支持, 包括 Feign 注解和 JAX-RS 注解。同时,为了适应 Spring 的广大用户,它在 NetflixFeign 的基础上扩展了对 Spring MVC 的注解支持。
- 由于 Spring Cloud Feign 的客户端负载均衡是通过 Spring Cloud Ribbon 实现的,所以我们可以直接通过配置 Ribbon 客户端的方式来自定义各个服务客户端调用的参数。
- 在 Spring Cloud Feign 中,除了引入了用于客户端负载均衡的 Spring Cloud Ribbon 之外,还引入了服务保护与容错的工具 Hystrix。
- 在对 Hystrix 进行配置之前,我们需要确认 feign.hystrix.enabled 参数没有被设置为 false, 否则该参数设置会关闭 Feign 客户端的 Hystrix 支持。
- Hystrix 提供的服务降级是服务容错的重要功能。
在微服务架构中,我们将系统拆分成了很多服务单元,各单元的应用间通过服务注册与订阅的方式互相依赖。由于每个单元都在不同 在本章中,您将学习: 微服务架构下,会存在服务之间相互依赖调用的情况,当某个服务不可用时,很容易因为服务之间的依赖关系使故障扩大,甚至造成整个系统不可用的情况,这种现象称为服务雪崩效应。 如上图所示,为服务雪崩效应发生的过程,首先是服务正常状态,当客户端对服务 A 发起请求,服务 A 依赖了服务 B,服务 B 又依赖了服务 C,当所有服务都处于正常状态时,整个请求链路是通畅的,结果会很快返回给客户端。 如果这时服务 C 发生故障或出现性能问题,就会出现延迟,刚开始时延迟较小,随着时间的推移,延迟会越来越大,服务 B 对服务 C 的调用就会堵塞,服务 C 此时已经疲惫不堪。 由于请求都堵在服务 C 上,服务 B 作为调用方,却迟迟等不到服务 C 的结果,服务 A 对服务 B 的请求又源源不断的发送过来,最终导致服务 B 的资源耗尽,从正常状态变成不正常状态,再也无法及时响应服务 A 的请求结果。 依此类推,最终服务 A 也会被拖垮,导致整个系统不可用,这个过程就是服务雪崩效应。如果能从最开始的小问题进行预防,就不会出现后面的级联效果,本章我们将讨论如何通过对服务进行容错降级来保证系统的可用性。 比如在一个电商网站中,我们可能会将系统拆分成用户、订单、库存、积分、 评论等一系列服务单元。用户创建一个订单的时候,客户端将调用订单服务的创建订单接口,此时创建订单接口又会向库存服务来请求出货(判断是否有足够库存来出货)。此时若库存服务因自身处理逻辑等原因造成响应缓慢,会直接导致创建订单服务的线程被挂起,以等待库存申请服务的响应,在漫长的等待之后用户会因为请求库存失败而得到创建订单失败的结果。如果在高并发情况之下,因这些挂起的线程在等待库存服务的响应而未能释放,使得后续到来的创建订单请求被阻塞,最终导致订单服务也不可用。 服务雪崩产生的原因肯定是服务提供者出了问题才导致后面的雪崩问题,在实际应用中无法预料服务提供者可能会出现什么样的问题,我们只能分析一些比较常见的问题。 服务提供者方面 服务消费者这方面 既然分析了一些比较常见的会导致服务雪崩的问题,那么就需要出对应的策略来解决这些问题。当然对于代码的 Bug 问题,我们可以通过测试、Code Review 等方式来避免,对于慢 SQL 这种问题,我们需要去做数据库性能优化。对于服务器硬件故障问题,我们可以加大运维粒度,通过监控等手段来提前预防。 而对于服务提供者方面,对于这种请求量超出承受能力的问题,我们可以进行扩容来支持高并发或者进行限流,自己能处理多少请求就处理多少,处理不了的请求直接拒绝,这样才不会将自己拖垮。服务消费者方面,我们需要做的就是资源隔离,快速失败,这也是最有效的方式,当我们发现被调用方迟迟不响应出现问题的时候,就不要再继续发起调用请求了,此时应该停止并返回一个友好的响应(服务降级),等待被调用方恢复后再发起调用。 类似实现限流、熔断以及降级等功能如果需要我们自己来实现的话,可能需要在每个调用的地方都要做一些逻辑处理并判断要不要发起调用,如果这样就太麻烦了。 好在 Spring Cloud Hystrix 就是专门处理此类场景问题的:核心功能有服务降级、服务熔断、服务限流、服务隔离、请求缓存、请求合并等。 断路器模式源于 Martin Fowler 的 CircuitBreaker 一文。“断路器”本身是一种开关装置, 用于在电路上保护线路过载,当线路中有电器发生短路时,“断路器”能够及时切断故障电 路,防止发生过载、发热甚至起火等严重后果。 在分布式架构中,断路器模式的作用也是类似的,当某个服务单元发生故障(类似用电器发生短路)之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个错误响应,而不是长时间的等待,这样就不会使得线程因调用故障服务而被长时间占用不释放,避免了故障在分布式系统中的蔓延。 Spring Cloud Netflix Hystrix 实现了断路器,线程隔离等一系列服务保护功能。该框架的目的在于通过控制那些访问远程系统、服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力。Hystrix 具备服务降级、服务熔断、线程和信号隔离、请求缓存、请求合并以及服务监控等的强大功能。 服务降级:当某个服务单元发生故障之后,通过断路器的故障监控,向调用方返回一个错误响应,而不是长时间的等待,这样就不会使得线程因调用故障服务而被长时间占用不释放,避免了故障在分布式系统中的蔓延。 线程和信号隔离:通过线程池的方式来为依赖服务调用提供隔离,避免因依赖服务故障导致的线程阻塞,从而导致整个系统的线程资源耗尽。还可以通过信号量(SEMAPHORE)的方式来实现, 信号量的方式相比于线程池的方式,它不会创建线程,而是通过计数器来实现线程的隔离。 SEMAPHORE: 信号量,是一种计数信号,用来控制同时访问特定资源的线程数量,或者同时执行某个指定操作的数量。读作: /ˈseməfɔːr/。 请求缓存: 在 Hystrix 中,请求缓存是通过请求命令的 请求合并:在 Hystrix 中,请求合并是通过请求命令的 服务监控:Hystrix 通过实现 HystrixCommandMetrics 和 HystrixThreadPoolMetrics 来实现对服务的监控,它们分别用于监控命令的执行情况和线程池的执行情况。 接下来,我们就从一个简单示例开始对 Spring Cloud Hystrix 的学习。 在消费侧启动类上添加 断路器的作用是当某个服务单元发生故障之后,通过断路器的故障监控,向调用方返回一个错误响应,而不是长时间的等待,这样就不会使得线程因调用故障服务而被长时间占用不释放,避免了故障在分布式系统中的蔓延。也叫做服务降级。 在消费端新增一个 Controller,使用 RestTemplate 模拟 HTTP 请求 上述代码中, 由于当前是快速入门的 demo,我们就只用简单定义一下 Controller 即可,提供一个新的对外开放的 API 即可。因此,可以声明一个新的 Controller,借由这个 Controller 来观察输出结果。 以本地(windows 系统)单机模式启动,在 Nacos 安装目录 bin 处打开命令窗口: 修改配置: 并分别启动三个服务实例,端口号分别为 9020,9021,9022 实际测试的时候, 此配置无效 我们已经掌握了负载均衡的相关知识,可启动多个服务端,本案例将启动三个服务实例进行演示。 所有项目启动成功后,端口号如下图所示: 本案例访问路径为: http://localhost:8080/consumer/hystrix/100 ,多次发送 Get 请求,可以看到如下三种可能的结果: 如果 实际情况是, 为了模拟降级的发生, 可以干脆停止所有的实例, 这样一定会出现服务降级了: 除了通过断开具体的服务实例来模拟某个节点无法访问的情况之外,我们还可以模拟一下服务阻塞(长时间未响应)的情况。我们对 userservice 的 通过 前面演示的是因为超时而触发服务降级的情况,那么如果是因为服务端抛出异常而触发服务降级呢?这里我们将userservice的 这样在测试的时候, 你会发现, 无论你发送多少次请求, 都会立即返回 注意:在该入门案例中,消费端的 通过上面的快速入门示例,我们对 Hystrix 的使用场景和使用方法已经有了一个基础的认识。接下来我们通过解读 Netflix Hystrix 官方的流程图来详细了解一下:当一个请求调用了相关服务依赖之后 Hystrix 是如何工作的。 第一步是构造一个 Hystrix 采用了命令模式来实现对服务调用操作的封装。而这两个 Command 对象分别针对不同的应用场景。 如何给一个接口构造一个 如何给一个接口构造一个 设计模式之命令模式介绍 Hystrix 利用命令模式来实现对服务调用操作的封装。命令模式是一种数据驱动的设计模式,它属于行为型模式。在命令模式中,我们可以将请求封装成一个对象,以便使用不同的请求、队列或日志来参数化其他对象。命令模式也支持可撤销的操作。通过使用命令模式可以实现“行为请求者”与“行为实现者”的解耦,以便使两者可以适应变化。 下面的示例是对命令模式的简单实现: 行为接收者类: 命令接口: ICommand, 它定义了一个命令对象应具备的一系列命令操作,比如 命令实现类: 命令调用者类: 客户端: 从上面的示例中,我们可以看到,调用者 Invoker 与操作者 Receiver 通过 Command 命令接口实现了解耦。对于调用者来说,我们可以为其注入多个命令操作,比如新建文件、 复制文件、删除文件这样三个操作,调用者只需在需要的时候直接调用即可,而不需要知道这些操作命令的具体对象是来自哪些操作者。 Invoker 和 Receiver 的关系非常类似于“请求 - 响应”模式,所以它比较适用于实现记录日志、撤销操作、队列请求等。 在下面这些情况下应考虑使用命令模式。 有四种方法可以执行 执行 fallbackMethod —— 回调方法,在服务调用异常、断路器打开、线程池/请求队列/信号量占满时会走回调逻辑。必须和服务方法定义在同一个类中,对修饰符没有特定的要求,定义为 private、 protected、 public 均可。 这里返回的 Observable 对象如何理解呢?我们可以把它理解为“事件源”或是“被观察者”,与其对应的 Subscriber 对象,可以理解为“订阅者”或是“观察者”。这两个对象是 RxJava 响应式编程的重要组成部分。 在 Hystrix 的底层实现中大量地使用了 RxJava, RxJava 是一个在 Java 虚拟机上使用可观测的序列来组成异步的、基于事件的程序的库。主要用于事件处理、数据集操作、异步任务以及基于回调的架构。 为了更容易地理解后续内容,在这里 对 RxJava 的观察者-订阅者模式做一个简单的入门介绍。 下面我们通过一个简单的例子来直观理解一下: 在该示例中,创建了一个简单的事件源 observable, —个对事件传递内容输出的订阅者 subscriber,通过 调用过程如下: 这在设计模式上一般称为观察者模式,也叫发布-订阅模式: 定义了对象之间的一对多的依赖关系,让多个观察者对象同时监听某一个主题对象(被观察者),这个主题对象在状态发生变化时,会通知所有观察者对象执行更新方法。 特点: 观察者和被观察者之间是松散耦合的,观察者和被观察者之间建立了一套触发机制。 在这里我们对于 Hystrix 的事件源 observable 提到了两个不同的概念:Hot Observable 和 Cold Observable, 别对应了上面 若当前命令的请求缓存功能是被启用的,并且该命令缓存命中,那么缓存的结果会立即以 Observable 对象的形式返回。 启用缓存的办法: 在创建 HystrixCommand 对象时,通过 在命令结果没有缓存命中的时候,Hystrix 在执行命令前需要检查断路器是否为打开状态. 如果与命令相关的线程池和请求队列,或者信号量(不使用线程池的时候)己经被占满,那么 Hystrix 也不会执行命令,而是转接到 fallback 处理逻辑(对应下面第 8 步)。需要注意的是,这里 Hystrix 所判断的线程池并非容器的线程池,而是每个依赖服务的专有线程池。Hystrix 为了保证不会因为某个依赖服务的问题影响到其他依赖服务而采用了“舱壁模式”(Bulkhead Pattern)来隔离每个依赖的服务。关于依赖服务的隔离与线程池相关的内容见后续详细介绍。 Hystrix 会根据我们编写的方法来决定采取什么样的方式去请求依赖服务。 如果 如果命令没有抛出异常并返回了结果,那么 Hystrix 在记录一些日志并采集监控报告之后将该结果返回。在使用 Hystrix 会将“成功”、“失败”、“拒绝”、“超时”等信息报告给断路器,而断路器会维护一组计数器来统计这些数据。断路器会使用这些统计数据来决定是否要将断路器打开,来对某个依赖服务的请求进 行“熔断/短路”,直到恢复期结束。若在恢复期结束后,根据统计数据判断如果还是未达到健康指标,就再次“熔断/短路”。 当命令执行失败的时候,Hystrix 会进入 fallback 尝试回退处理,我们通常也称该操作为“服务降级”。而能够引起服务降级处理的情况有下面几种: 在服务降级逻辑中,我们需要实现一个通用的响应结果,并且该结果的处理逻辑应当是从缓存或是根据一些静态逻辑来获取,而不是依赖网络请求获取。如果一定要在降级逻辑中包含网络请求,那么该请求也必须被包装在 HystrixCommand 或是 HystrixObservableCommand 中,从而形成级联的降级策略,而最终的降级逻辑一定不是一个依赖网络请求的处理,而是一个能够稳定地返回结果的处理逻辑。 在 HystrixCommand 和 HystrixObservableCommand 中实现降级逻辑时还略有不同: 当命令的降级逻辑返回结果之后,Hystrix 就将该结果返回给调用者。当使用 如果我们没有为命令实现降级逻辑或者降级处理逻辑中抛出了异常,Hystrix 依然会返回一个 Observable 对象,但是它不会发射任何结果数据,而是通过 降级执行失败时, Hystrix 会根据不同的执行方法做出不同的处理。 断路器在 HystrixCommand 和 HystrixObservableCommand 执行过程中起到了举足轻重的作用,它是 Hystrix 的核心部件。 断路器有三种状态: 默认触发熔断并造成断路器打开的条件是: 涉及断路器的三个参数:快照时间窗、请求总数阀值、错误百分比阀值。 快照时间窗:断路器确定是否打开需要统计一些请求和错误数据,而统计的时间范围就是快照时间窗,默认为最近的 10 秒。它是一个滚动时间窗. 每过一段时间,比如 10 秒,就会统计最近 10 秒内的请求总数,如果请求数量超过了阀值,就会根据错误百分比来判断是否需要打开断路器。 请求总数阀值:在快照时间窗内,必须满足请求总数阀值才有资格熔断。默认为 20,意味着在 10 秒内,如果该 hystrix 命令的调用次数不足 20 次,即使所有的请求都超时或其他原因失败,断路器都不会打开。 错误百分比阀值:当请求总数在快照时间窗内超过了阀值,比如发生了 30 次调用,如果在这 30 次调用中,有 15 次发生了超时异常,也就是超过 50%的错误百分比,在默认设定 50%阀值情况下,这时候就会将断路器打开。 配置断路器参数: 那么断路器是如何决策熔断和记录信息的呢? 下图是 Netflix Hystrix 官方文档中关于断路器的详细执行逻辑,可以帮助我们理解断路器的工作流程。 这个桶数可以通过 现在我们来看看断路器 可以看到它的接口定义并不复杂,主要定义了三个断路器的抽象方法。 另外还有三个静态类。 静态类 其中 String 类型的 key 通过 静态类 静态类 详细逻辑如下所示。 执行逻辑如下所示。 先根据配置对象 properties 中的断路器判断强制打开或关闭属性是否被设置。如果强制打开,就 直接返回 false,拒绝请求。如果强制关闭,它会允许所有请求,但是同时也会调用 在默认情况下,断路器并不会进入这两个强制打开或关闭的分支中去,而是通过 执行逻辑如下所示: Hystrix 提供了两种资源隔离策略:线程和信号量。 “舱壁模式”对于熟悉 Docker 的读者一定不陌生,Docker 通过“舱壁模式”实现进程 的隔离,使得容器与容器之间不会互相影响。而 Hystrix 则使用该模式实现线程池的隔离, 它会为每一个依赖服务创建一个独立的线程池,这样就算某个依赖服务出现延迟过高的情况,也只是对该依赖服务的调用产生影响,而不会拖慢其他的依赖服务。 虽然线程池隔离的方案带来如此多的好处,但是很多使用者可能会担心为每一个依赖服务都分配一个线程池是否会过多地增加系统的负载和开销。对于这一点,使用者不用过于担心,因为这些顾虑也是大部分工程师们会考虑到的,Netflix 在设计 Hystrix 的时候,认为线程池上的开销相对于隔离所带来的好处是无法比拟的。同时,Netflix 也针对线程池的 开销做了相关的测试,以用结果打消 Hystrix 实现对性能影响的顾虑。 下图是 Netflix Hystrix 官方提供的一个 Hystrix 命令的性能监控图,该命令以每秒 60 个请求的速度(QPS)对一个单服务实例进行访问,该服务实例每秒运行的线程数峰值为 350 个。 每种情况都有两个曲线,一个是未使用线程池隔离,一个是使用了线程池隔离。根据图表统计,不难得出如下结论: 对于大多数 Netflix 用例而言,使用线程池隔离的开销已被认为是可以接受的,因为可以实现弹性优势。 虽然对于大部分的请求我们可以忽略线程池的额外开销,而对于小部分延迟本身就非常小的请求(可能只需要 lms),那么 9ms 的延迟开销还是非常昂贵的。Hystrix 为此设计了另外的解决方案:信号量。 在 HystrixCommand 和 HystrixObservableCommand 中有两处支持信号量的使用: 这两种策略之间的选择取决于您的使用情况。如果您每秒有大量请求,则可以考虑使用信号量。其次,使用信号量时,命令将在调用者的线程内执行。这意味着并发调用并未完全与其他调用隔离(不像使用线程时)。 一般来说,在认为开销足够小的情况下,Netflix 在实践中通常更喜欢单独线程的隔离优势而选择线程的隔离策略。 在“快速入门”一节中我们己经使用过 Hystrix 中的核心注解@HystrixCommand 通过它创建了 HystrixCommand 的实现,同时利用 fallback 属性指定了服务降级的实现 方法。接下来我们将详细介绍 @HystrixCommand 注解的使用方法。 Hystrix 命令就是我们之前所说的 HystrixCommand,它用来封装具体的依赖服务调用逻辑。我们可以通过@HystrixCommand 注解来更为优雅地实现 Hystrix 命令的定义: fallbackMethod —— Hystrix 命令执行失败时使用的回退方法, 用来实现服务降级,在服务调用异常、断路器打开、线程池/请求队列/信号量占满时会走回调逻辑。必须和服务方法定义在同一个类中,对修饰符没有特定的要求,定义为 值得注意的是,定义服务降级方法时,最终要降级到一个逻辑稳定的方法上,避免这个方法再发生任何的异常情况。这样的设计对于系统来说才是有意义的,可以确保服务稳定性。 在实际使用时,我们需要为大多数执行过程中可能会失败的 Hystrix 命令实现服务降级逻辑,但是也有一些情况可以不去实现降级逻辑,如下所示。 执行写操作的命令:当 Hystrix 命令是用来执行写操作而不是返回一些信息的时候,通常情况下这类操作的返回类型是 void 或是为空的 Observable,实现服务降级的意义不是很大。当写入操作失败的时候,我们通常只需要通知调用者即可。 执行批处理或离线计算的命令:当 Hystrix 命令是用来执行批处理程序生成一份报告或是进行任何类型的离线计算时,那么通常这些操作只需要将错误传播给调用者,然后让调用者稍后重试而不是发送给调用者一个静默的降级处理响应。 不论 Hystrix 命令是否实现了服务降级,命令状态和断路器状态都会更新,并且我们可以由此了解到命令执行的失败情况。 在使用注册配置实现 Hystrix 命令时,它还支持忽略指定异常类型功能,只需要通过设置@HystrixCommand 注解的 ignoreExceptions 参数,比如: 如上面代码的定义,当 showAppInfo 方法抛出了类型为 BadRequestException 的异常时 Hystrix 会将它包装在 HystrixBadRequestException 中抛出,这样就不会触发后续的 fallback 逻辑。 当 Hystrix 命令因为异常(除了 HystrixBadRequestException 的异常)进入服务降级逻辑之后,往往需要对不同异常做针对性的处理,那么我们如何来获取当前抛出的异常呢? 当我们使用 HystrixCommand 注解的时候,通过设置 Hystrix 会根据组来组织和统计命令的告警、仪表盘等信息。 另外我们可以通过命令名称和线程池进行 Hystrix 的一些针对具体命令和线程池的相关配置,比如我们可以像下面这样进行设置: 当系统用户不断增长时,每个微服务需要承受的并发压力也越来越大。在分布式环境下,通常压力来自于对依赖服务的调用,因为请求依赖服务的资源需要通过通信来实现,这样的依赖方式比起进程内的调用方式会引起一部分的性能损失,同时 HTTP 相比于其他 高性能的通信协议在速度上没有任何优势,所以它有些类似于对数据库这样的外部资源进 行读写操作,在高并发的情况下可能会成为系统的瓶颈。既然如此,我们很容易地可以联想到,类似数据访问的缓存保护是否也可以应用到依赖服务的调用上呢? 答案显而易见,在高并发的场景之下,Hystrix 中提供了请求缓存的功能,我们可以方便地开启和使用请求缓存来优化系统,达到减轻高并发时的请求线程消耗、降低请求响应时间的效果。 Hystrix 的请求缓存可以通过注解的方式进行配置实现。注解配置的定义实现同 JSR 107 的定义非常相似,但由于 Hystrix 不需要独立外置的缓存系统来支持,所以没有 JSR 107 的定义那么复杂,它只提供了三个专用于请求 缓存的注解。 JSR 107 是 Java 缓存 API 的定义,也被称为 JCache。它定义了一系列开发人 员使用的标准化 Java 缓存 API 和服务提供商使用的标准 SPI。下面我们从几个方面的实例来看看这几个注解的具体使用方法。 缓存生命周期是从 1、在每个用到请求缓存的Controller方法的第一行加上如下代码: 2、使用Filter方式: 在启动类加入 创建 在应用启动类上开启包扫描: 有了过滤器后, 每个Controller方法中的 如果没有为 HystrixRequestContext 进行初始化,那么系统将报如下错误: 这样, Hystrix 的缓存设置是针对单次请求的: 因为每次请求来之前都执行了 Hystrix 的书上写的是:在同一用户请求的上下文中,相同依赖服务的返回数据始终保持一致。在当次请求内对同一个依赖进行重复调用,只会真实调用一次。在当次请求内数据可以保证一致性。 通过注解为请求命令开启缓存功能非常简单,如下例所示,我们只需添加@CacheResult 注解即可。当该依赖服务被调用并返回 User 对象时,Hystrix 会将该结果置入请求缓存中,而 它的缓存 Key 值默认会使用所有的参数,也就是这里 String 类型的 id 值。若要为请求命令指定具体的缓存 Key 生成规则,我们可以使用@CacheResult 的 cacheKeyMethod 属性来指定具体的生成函数;也可以通过使 @CacheKey 注解 在方法参数中指定用于组装缓存 Key 的元素。这里我们使用 @CacheKey 注解(由于只有一个参数, 这个注解其实也可以直接省略): 测试 发现所有调用都没有走缓存. 实践中发现, 必须通过实例来调用标记缓存的方法缓存才会生效, 也就是说必须通过new 或者自动装配的方式先获取标记缓存的方法所在类的实例, 然后通过实例调用缓存的方法, 缓存才可以生效. 修改如下: 把缓存方法放到service层: 测试方法: 测试接口: 输出: 当使用注解来定义请求缓存时,若要为请求命令指定具体的缓存 Key 生成规则,我们可以使用@CacheResult 的 cacheKeyMethod 属性来指定具体的生成函数;也可以通过使 @CacheKey 注解 在方法参数中指定用于组装缓存 Key 的元素。 需要注意,@CacheKey 注解的优先级比 cacheKeyMethod 的优先级低,如果己经使用 cacheKeyMethod 指定缓存 Key 的生成函数,那么@CacheKey 注解不会生效。 使用 cacheKeyMethod 方法的示例如下,它通过在Hystrix命令的同一个类中定义一个专门生成 Key 的方法,并用@CacheResult 注解的 cacheKeyMethod 方法来指定它即可。 在之前的例子中,我们己经通过 需要注意的是, 测试控制器 测试: 日志输出: 微服务架构中的依赖通常通过远程调用实现,而远程调用中最常见的问题就是通信消耗与连接数占用。在高并发的情况之下,因通信次数的增加,总的通信时间消耗将会变得不那么理想。同时,因为依赖服务的线程池资源有限,将出现排队等待与响应延迟的情况。所以在资源有限并且短时间内会产生高并发请求的时候,为避免连接不够用而引起的延迟可以考虑使用请求合并器的方式来处理和优化优化这两个问题,Hystrix 提供了 HystrixCollapser 来实现请求的合并,以减少通 信消耗和线程数的占用。 HystrixCollapser 实现了在 HystrixCommand 之前放置一个合并处理器,将处于一个很短的时间窗(默认 我们之前己经介绍过 而在请求 这样通过注解简单而又优雅地实现了在 测试: orderservice输出日志: 可见并没有发生请求合并. 需要注意: 修改如下: 测试, 可以使用线程池来模拟异步请求: orderservice输出日志: main方法输出日志: 虽然通过请求合并可以减少请求的数量以缓解依赖服务线程池的资源,但是在使用的时候也需要注意它所带来的额外开销:用于请求合并的延迟时间窗会使得依赖服务的请求延迟增高。比如,某个请求不通过请求合并器访问的平均耗时为 5ms,请求合并的延迟时 间窗为 10ms (默认值),那么当该请求设置了请求合并器之后,最坏情况下(在延迟时间 窗结束时才发起请求)该请求需要 15ms 才能完成。 由于请求合并器的延迟时间窗会带来额外开销,所以我们是否使用请求合并器需要根据依赖服务调用的实际情况来选择,主要考虑下面两个方面。 在之前介绍 Hystrix 的使用方法时,己经涉及过一些 Hystrix 属性的配置,我们可以根据实现 HystrixCommand 的不同方式将配置方法分为如下两类: 当通过继承的方式实现时,可使用 Setter 对象来对请求命令的属性进行设置 当通过注解的方法实现时,只需使用 实际上,Hystrix 为我们提供的配置内容和配置方式远不止上面这些,它提供了非常丰富和灵活的配置方法,下面我们将以注解为例,详解介绍各项配置属性。在具体说明这些属性之前,我们需要了解一下这些属性都存在下面 4 个不同优先级别的配置(优先级由低到高) 全局默认值:如果没有设置下面三个级别的属性,那么这个属性就是默认值。由于该属性通过代码定义,所以对于这个级别,我们主要关注它在代码中定义的默认值即可。 全局配置属性:通过在配置文件中定义全局属性值,在应用启动时或在与 SpringCloud Config 和 Spring Cloud Bus 实现的动态刷新配置功能配合下,可以实现对“全局默认值”的覆盖以及在运行期对“全局默认值”的动态调整。 实例默认值:通过代码为实例定义的默认值。通过代码的方式为实例设置属性值来覆盖默认的全局配置。 实例配置属性:通过配置文件来为指定的实例进行属性配置,以覆盖前面的三个默认值。 通过理解 Hystrix 4 个级别的属性配置,对设置 Hystrix 的默认值以及在线上如何根据 实际情况去调整配置非常有帮助,下面我们来具体看看它有哪些具体的属性配置。 Command 属性主要用来控制 HystrixCommand 命令的行为。 它主要有下面 5 种不同类型的属性配置。 下面这些属性用来控制 HystrixCommand.getFallback()的执行。这些属性同时适用于线程池的信号量的隔离策略。 fallback.enabled:该属性用来设置服务降级策略是否启用,如果设置为 false,那么当请求失败或者拒绝发生时,将不会调用 HystrixCommand.getFallback() 来执行服务降级逻辑。 下面这些是断路器的属性配置,用来控制 HystrixCircuitBreaker 的行为。 下面的属性均与 HystrixCommand 和 HystrixObservableCommand 执行中捕获的指标信息有关。 注意: 该属性从 Hystrix 1.4.2 版本开始(Brixton.SR5 版本中使用了 1.5.3 版本),只有在应用初始化的时候生效,通过动态刷新配置不会产生效果,这样做是为了避免出现运行期检测数据丢失的情况。 注意: 注意: 该属性从 Hystrix 1.4.12 版本开始(Brixton.SR5 版本中使用了 1.5.3 版本),只有在应用初始化的时候生效,通过动态刷新配置不会产生效果,这样做是为了避免出现运行期监测数据丢失的情况。 注意: 注意: 下面这些属性涉及 HystrixCommand 使用的 HystrixRequestContext 的设置。 下面这些属性用来控制命令合并相关的行为。 该属性除了在代码中用 set 和配置文件配置之外,还可使用注解进行配置。可使用 下面这些属性用来控制 Hystrix 命令所属线程池的配置。 注意: 该属性只有在初始化的时候才有用,无法通过动态刷新的方式来调整。 注意: 活动 5.1: Hystrix 容错保护 通过之前的内容,我们己经体验到了 Spring Cloud 对 Hystrix 的优雅整合。除此之外,SpringCloud 还完美地整合了它的仪表盘组件 Hystrix Dashboard,它主要用来实时监控 Hystrix 的各项指标信息。通过 Hystrix Dashboard 反馈的实时信息,可以帮助我们快速发现系统中存在的问题,从而及时地采取应对措施。 本节中我们将在 Hystrix 入门例子的基础上,构建一个 Hystrix Dashboard 来对 Order-Service 服务实现监控。 在 Spring Cloud 中构建一个 Hystrix Dashboard 非常简单,只需要下面 4 步: 创建一个标准的 Spring Boot 工程,命名为 hystrix-dashboard。 编辑 pom.xml,具体依赖内容如下所示: 为应用主类加上@EnableHystrixDashboard,启用 Hystrix Dashboard 功能。 根据实际情况修改 application.properties 配置文件,比如选择一个未被占用的端口等,此步不是必需的。 到这里我们己经完成了基本配置,接下来可以启动该应用,并访问 这是 Hystrix Dashboard 的监控首页,Hystrix Dashboard 共支持三种不同的监控方式,如下所示。 前两者都是对集群的监控,需要整合 Turbine 才能实现,这部分内容我们将在下一节中做详细介绍。在本节中,我们主要实现对单个服务实例的监控,这里我们先来实现单个服务实例的监控。 既然 Hystrix Dashboard 监控单实例节点需要通过访问实例的/hystrix. stream 接口 来实现,我们自然需要为服务实例添加这个端点,而添加该功能的步骤也同样简单,只需要下面三步,我们以 orderservice 服务(默认端口 8080)为例进行演示: 在服务实例 确保在服务实例的主类中己经使用 在配置文件中开启端点功能 这里使用了 到这里己经完成了所有的配置,在 Hystrix Dashboard 的首页还有另外两个参数。 输入 http://localhost:8080/actuator/hystrix.stream,填写 Title,单击 Monitor Stream 按钮,可以看到如下页面。 回到监控页面,我们来详细说说其中各元素的具体含义。 通过本节内容我们己经能够使用 Hystrix Dashboard 来对单个实例做信息监控了,但是在分布式系统中,往往有非常多的实例需要去维护和监控。到目前为止,我们能做的就是通过开启多个窗口来监控多个实例,很显然这样的做法并不合理。在下一节中,我们将介绍利用 Turbine 和 HystrixDashboard 配合实现对集群的监控。 活动 5.2 : 基于图形化的 DashBoard(仪表板)监控 在上一节介绍 Hystrix Dashboard 的首页时,我们提到过除了可以开启单个实例的监控 页面之外,还有一个监控端点/turbine . stream 是对集群使用的。从端点的命名中,可猜测到这里我们将引入 Turbine,通过它来汇集监控信息,并将聚合后的信息提供给 Hystrix Dashboard 来集中展示和监控。 下面我们将在上一节内容的基础上做一些扩展,通过引入 Turbine 来聚合多个服务的监控信息,并在 Hystrix Dashboard 上面展示。 具体实现步骤如下: 编辑 pom.xml,引入相关依赖。 创建应用主类 TurbineApplication,并使用 在 application.yml 配置文件中加入 Nacos 和 Turbine 的相关配置 在完成了上面的内容构建之后,我们来体验一下 Turbine 对集群的监控能力。分别启动 nacos-server、orderservice、userservice、Turbine 以及 Hystrix Dashboard。访问 HystrixDashboard,并开启对 在断路器原理的介绍中,我们多次提到关于请求命令的度量指标的判断。这些度量指标都是HystrixCommand 和 HystrixObservableCommand 实例在执行过程中记录的重要信息,它们除了在Hystrix 断路器实现中使用之外,对于系统运维也有非常大的帮助。 这些指标信息会以“滚动时间窗”与“桶”结合的方式进行汇总,并在内存中驻留一段时间,以供内部或外部进行查询使用,Hystrix 仪表盘就是这些指标内容的消费者之一。 通过之前的内容,我们己经体验到了 Spring Cloud 对 Hystrix 的优雅整合。除此之外,SpringCloud 还完美地整合了它的仪表盘组件 Hystrix Dashboard,它主要用来实时监控 Hystrix 的各项指标信息。通过 Hystrix Dashboard 反馈的实时信息,可以帮助我们快速发现系统中存在的问题,从而及时地采取应对措施。 本节中我们将在 Hystrix 入门例子的基础上,构建一个 Hystrix Dashboard项目 来对 Order-Service 服务实现监控。 微服务如果想要被监控, 需要暴露相应端点, 这里为了方便暴露了所有端点. 这里使用了 另外因为 springboot2.0起监控的默认路径不是 "/hystrix.stream", 需要在自己的项目里面手动配置映射路径: 以orderservice为例进行测试: userservice也是一样. 如果需要聚合多个微服务信息显示在面板上, 需要是创建一个turbine项目, 并进行统计信息的聚合: 由于turbine需要借助nacos获取服务信息, 需要使用 启动类: 配置: 访问聚合信息 新创建一个项目 my-dashboard , 用于展示单体项目或者聚合项目的统计信息: 启动类: 配置: 启动项目后, 访问:http://localhost:2022/hystrix 这是 Hystrix Dashboard 的监控首页,Hystrix Dashboard 共支持三种不同的监控方式,如下所示。 前者是对集群的监控,需要整合 Turbine 才能实现,后者是对单个服务实例的监控。 比如监控orderservice 在上面输入框输入 在上面输入框输入 一开始是没有监控信息的, 直到请求发生: 各个指标的含义: 熔断如果触发, 则可以看到红色圆圈: 实心圆:其有两种含义。通过颜色的变化代表了实例的健康程度,绿色表示正常,红色表示熔断。该实心圆除了颜色的变化之外, 它的大小也会根据实例的请求流量发生变化,流量越大该实心圆就越大。所以通过该实心圆的展示,我们可以在大量的实例中快速发现故障实例和高压力实例。 Circuit:表示熔断器的状态, Closed(绿字)表示熔断器闭合接收请求, Open(红字)表示熔断器打开走熔断降级流程. 正常的情况实心圆是淡绿色, 熔断器显示闭合; 当熔断发生时, 实心圆红色, 熔断器打开;当熔断器处于半开状态时, 实心圆回复淡绿色, 熔断器显示打开. 曲线:代表时间窗口内流量变化,可以通过它来观察流量的上升和下降趋势。 六个数字代表不同状态的请求数, 灰色百分号为失败率. 不同颜色代表的含义: 其他文字代表的含义: Cluster 集群每秒请求数 Host 主机每秒请求数 Hosts 主机数量 Median 响应时间的中位数, Mean 平均延迟, 90th 表示90百分位的延迟情况. 可以点击sort右边的排序字段进行排序: Views: 53 众所周知,随着用户量的增加,应用的访问量也会随之增加,单台服务器已经远不能满足高并发的业务需求,这时就需要多台服务器组成集群来应对高并发带来的业务压力,同时也需要负均衡器来对流量进行合理分配。 负载均衡是一种基础的网络服务,它的核心原理是按照指定的负载均衡算法,将请求分配到后端服务集群上,从而为系统提供并行处理和高可用的能力。 负载均衡的方式有很多种,在 Spring Cloud 体系中,Ribbon 就是负载均衡的组件,所有的请求都是通过 Ribbon 来选取对应的服务信息的。 在活动 3.1 的试验中, 我们已经学习了如何使用 Spring Cloud Alibaba Nacos 来实现服务注册与发现。在存在多个服务实例的情况下,使用固定主机名和 ip 的方式来访问服务,会存在单点故障的问题,因此,我们需要使用负载均衡的方式来访问服务。 如上面代码所示, 我们可以通过服务发现客户端 DiscoveryClient 来获取服务实例,然后通过服务实例的主机名和端口号来拼接访问服务的 url。这里我们始终使用第一个服务实例,这样就会存在单点故障的问题。当然,我们也可以在这里实现不同的算法来选择服务实例,但是这样会增加我们的开发工作量。 Spring Cloud Ribbon 是 Spring Cloud Netflix 项目下的一个子项目,它是一个基于 HTTP 和 TCP 的客户端负载均衡工具。它基于 Netflix Ribbon 实现,可以很好的控制 HTTP 和 TCP 的一些行为。Spring Cloud Ribbon 提供了一系列完善的配置项如连接超时、重试等。 Spring Cloud Ribbon 虽然只是一个工具类框架,它不需要独立部署,但是它几乎存在于每一个 Spring Cloud 构建的微服务和基础设施中。因为微服务间的调用, API 网关的请求转发等内容,实际上都是通过 Ribbon 来实现的。 所以,对 Spring Cloud Ribbon 的理解和使用,对于我们使用 Spring Cloud 来构建微服务非常重要。在这一章中,我们将具体介绍如何使用 Ribbon 来实现客户端的负载均衡,并且通过源码分析来了解 Ribbon 实现客户端负载均衡的基本原理。 在本章中,您将学习: 目前主流的负载方案分为两种: 服务端负载均衡(集中式负载均衡) 客户端负载均衡 集中式负载均衡的工作原理,负载均衡器负责维护需要负载的服务实例信息,如:192.168.1.1:8080 和 192.168.1.2:8080 这两个实例。 客户端不直接请求 192.168.1.1:8080 和 192.168.1.2:8080 这两个实例,而是通过负载均衡器来进行转发,客户端的请求到了负载均衡器这里,负载均衡器会根据配置的算法在 192.168.1.1:8080 和 192.168.1.2:8080 这两个实例中选取一个实例,转发到具体的实例上。 这样的好处是客户端不需要关心对应服务实例的信息,只需要跟负载均衡器进行交互,服务实例扩容或缩容,客户端不需要修改任何代码。 客户端负载均衡需要自己维护服务实例的信息,然后通过某些负载均衡算法,从实例中选取一个实例,直接进行访问。 负载均衡在系统架构中是一个非常重要,并且是不得不去实施的内容。因为负载均衡是对系统的高可用、网络压力的缓解和处理能力扩容的重要手段之一。我们通常所说的负载均衡都指的是服务端负载均衡,其中分为硬件负载均衡和软件负载均衡。 F5 是一家美国的网络设备厂商,主要生产网络负载均衡器、应用程序交付控制器、Web 应用程序防火墙、DNS 服务器、内容分发网络、SSL VPN、身份管理系统、网络安全系统等产品。 Nginx: 是一个高性能的 HTTP 和反向代理服务器,同时也提供了 IMAP/POP3/SMTP 服务。 硬件负载均衡的设备或是软件负载均衡的软件模块都会维护一个可用的服务端清单,通过心跳检测来剔除故障的服务端节点以保证清单中都是可以正常访问的服务端节点。当客户端发送请求到负载均衡设备的时候,该设备按某种算法(比如线性轮询、按权重负载、按流量负载等)从维护的可用服务端清单中取出一台服务端的地址,然后进行转发。 而客户端负载均衡和服务端负载均衡最大的不同点在于上面所提到的服务清单所存储的位置。 在客户端负载均衡中,所有客户端节点都维护着自己要访问的服务端清单,而这些服务端的清单来自于服务注册中心,比如上一章我们介绍的 Nacos 服务端。同服务端负载均衡的架构类似,在客户端负载均衡中也需要心跳去维护服务端清单的健康性,只是这个步骤需要与服务注册中心配合完成。 通过 Spring Cloud Ribbon 的封装,我们在微服务架构中使用客户端负载均衡调用非常简单,只需要如下两步: 这样,就可以将服务提供者的高可用以及服务消费者的负载均衡调用一起实现了。 注释: 小问题: 实现一个通用的负载均衡框架,则需要很多组件支持,Ribbon 中就提供了这些组件,有了这些组件,整个框架的扩展性便会更好,更灵活,我们可以根据业务需求,选择是否要自定义对应的组件来满足特定场景下的需求。 当我们需要通过 Ribbon 选择一个可用的服务实例信息,进行远程调用时,Ribbon 会根据指定的算法从服务列表中选择一个服务实例进行返回。 ServerList: ServerListFilter ServerListUpdater IPing IRule ILoadBalancer 总结 这些组件都支持自定义,扩展性很强。 负载均衡的流程,可以用一幅图来总结一下: 内置负载均衡策略 除了上面的内置负载均衡策略外,Ribbon 还提供了一种自定义负载均衡策略的方式,即继承 Ribbon 的使用方式主要分为下面这三种, 原生 API,Ribbon 是 Netflix 开源的,如果你没有使用 Spring Cloud,也可以在项目中单独使用 Ribbon,在这种场景下就需要使用 Ribbon 的原生 API。 Ribbon + RestTemplate,当我们项目整合了 Spring Cloud 时,就可以用 Ribbon 为 RestTemplate 提供负载均衡的服务。 Ribbon + Feign,关于 Feign 的使用方式会在后面的章节中进行详细的讲解。 Ribbon 原生 API 使用非常方便,首先我们需要配置一个服务列表,数据格式为 IP + PORT,你可以固定写几个服务列表,也可以从别处读取,比如注册中心。 下面来详细介绍 Ribbon 在使用时的各种配置方式。 由于 Ribbon 中定义的每一个接口都有多种不同的策略实现,同时这些接口之间又有一定的依赖关系,这使得第一次使用 Ribbon 的开发者很难上手,不知道如何选择具体的实现策略以及如何组织它们的关系。 Spring Cloud Ribbon 中的自动化配置恰恰能够解决这样的痛点,在引入 SpringCloud Ribbon 的依赖之后,就能够自动化构建下面这些接口的实现。 上面这些自动化配置内容仅在没有引入 Spring Cloud Alibaba Nacos 等服务治理框架时如此, 在引入 Nacos 依赖时,自动化配置会有一些不同,后续会做详细的介绍。 通过自动化配置的实现,我们可以轻松地实现客户端负载均衡。同时,针对一些个性化需求,我们也可以方便地替换上面的这些默认实现。只需在 Spring Boot 应用中创建对应的实现实例就能覆盖这些默认的配置实现。比如可以在启动类中添加如下的配置内容,由于创建了 NacosRule 实例,所以默认的 对于 Ribbon 的参数配置通常有两种方式:全局配置以及指定客户端配置。 注意:全局配置的参数名是不区分大小写的,但是值是区分大小写的。比如,下面这两种配置方式是等价的。 客户端所有可配置的 key 可通过此类查看 默认的客户端配置可以通过此类查看 全局配置可以作为默认值进行设置,当指定客户端配置了相应 key 的值时,将覆盖全局配置的内容。 指定客户端的配置方式采用 上面使用代码的方式可以实现修改负载均衡规则,不过在启动类中修改,属于是全局应用的范围.也就是说,无论调用哪个微服务,我们都将使用该负载均衡的规则.通过另一种方式,我们可以实现更加细粒度的配置. 在 application.yml 文件中,添加新的配置也可以修改规则,只针对某个微服务起效: Ribbon 默认采用懒加载,也就是在第一次访问时 当在 Spring Cloud 的应用中引入 Spring Cloud Alibaba Nacos 依赖时,会触发 Nacos 中实现的对 Ribbon 的自动化配置。 这时 ServerList 的维护机制实现将被 在与 Spring Cloud Alibaba Nacos 结合使用的时候,我们的配置将会变得更加简单。不再需要通过类似 Spring Cloud Ribbon 默认实现了区域亲和策略。此策略会优先请求同一个 Zone 区域的服务实例。但在一些情况下:比如当不存在同区域的服务、并且同区域的服务负载过高等,仍会自动切换成其他区域的服务。 从 Camden SR2 版本开始, Spring Cloud 整合了 Spring Retry 来增强 RestTemplate 的重试能力,对于开发者来说只需通过简单的配置,原来那些通过 RestTemplate 实现的服务访问就会自动根据配置来实现重试策略。重试机制是默认开启的,以之前对 userservice 服务的调用为例,可以在配置文件中增加如下内容: 根据如上配置,当访问到故障请求的时候,它会再尝试访问一次当前实例(次数由 MaxAutoRetries 配置),如果不行,就换一个实例进行访问,如果还是不行,再换一次实例访问(更换次数由 注意: 为了触发重试机制,断路器(第 5 章节讲解)的超时时间应该大于 Ribbon 的超时时间。 具体来说,当请求被发送到远程服务时,Ribbon 会开始计时。如果在超时时间内请求成功完成,则重试机制不会被触发。如果请求在超时时间内未能成功完成,则会尝试使用下一个可用实例进行重试。如果重试也未能成功,则断路器会被触发。 因此,为了触发重试机制,断路器的超时时间应该大于 Ribbon 的超时时间。这样,当重试机制被触发时,断路器才能够捕获到请求失败的事件,并执行相应的处理。 问题: 原因: Nacos 服务端的注册信息不一致 Spring Cloud Ribbon 提供了一些默认的负载均衡策略,比如轮询、随机、响应时间加权等。但是在实际的开发过程中,我们可能会需要一些自定义的负载均衡策略,比如:根据服务实例的元数据信息来选择服务实例、根据服务实例的运行状态来选择服务实例等。 那么如何实现自定义的负载均衡策略呢?Spring Cloud Ribbon 提供了一个抽象类 活动 4.1 : 使用 Ribbon 实现客户端负载均衡 附加活动 可以以配置文件方式指定自定义的负载均衡策略和 IPing 实现类. 也可以以代码方式指定自定义的负载均衡策略和 IPing 实现类, 优先级高于配置文件. 以下哪个不是负载均衡器? 下面哪个不是 ServerListFilter 接口的实现类? AbstractLoadBalancer 是什么接口的抽象实现? 对于 Ribbon 的参数配置通常有几种方法? 在本章中,您学习了: Views: 26 服务治理可以说是微服务架构中最为核心和基础的模块,它主要用来实现各个微服务实例的自动化注册与发现。为什么我们在微服务架构中那么需要服务治理模块呢? 在最初开始构建微服务系统的时候可能服务并不多,我们可以通过做一些静态配置来完成服务的调用。比如,有两个服务 A 和 B,其中服务 A 需要调用服务 B 来完成一个业务操作时,为了实现服务 B 的高可用,不论采用服务端负载均衡还是客户端负载均衡,都需要手工维护服务 B 的具体实例清单。 但是随着业务的发展,系统功能越来越复杂,相应的微服务应用也不断增加,我们的静态配置就会变得越来越难以维护。并且面对不断发展的业务,我们的集群规模、服务的位置、服务的命名等都有可能发生变化,如果还是通过手工维护的方式,那么极易发生错误或是命名冲突等问题。同时,对于这类静态内容的维护也必将消耗大量的人力。 为了解决微服务架构中的服务实例维护问题,产生了大量的服务治理框架和产品。这些框架和产品的实现都围绕着服务注册与服务发现机制来完成对微服务应用实例的自动化管理。 目标: 在本章中,您将学习: Spring Cloud Eureka,它既包含了服务端组件,也包含了客户端组件,并且服务端与客户端均采用 Java 编写,所以 Eureka 主要适用于通过 Java 实现的分布式系统,或是与 JVM 兼容语言构建的系统。但是,由于 Eureka 服务端的服务治理机制提供了完备的 RESTful API,所以它也支持将非 Java 语言构建的微服务应用纳入 Eureka 的服务治理体系中来。 ZooKeeper 是一个开放源码的分布式应用程序协调服务,是 Google 的 Chubby 一个开源的实现,是 Hadoop 和 Hbase 的重要组件。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。ZooKeeper 的目标就是封装好复杂易出错的关键服务,将简单易用的接口和性能高效、功能稳定的系统提供给用户。 Consul 是一个服务网格(微服务间的 TCP/IP,负责服务之间的网络调用、限流、熔断和监控)解决方案,它是一个分布式的,高度可用的系统,而且开发使用都很简便。它提供了一个功能齐全的控制平面,主要特点是:服务发现、健康检查、键值存储、安全服务通信、多数据中心。 Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理。 Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。 Nacos 是构建以“服务”为中心的现代应用架构 (例如微服务范式、云原生范式) 的服务基础设施。 服务治理的方案不一而论,现将主流的技术框架进行横向比对: 从上面的对比可以知道,Nacos 作为服务发现中心具备更多的功能支持,且从长远来看 Nacos 在以后的版本会支持 SpringCloud+K8s 的组合,填补两者的鸿沟,在两套体系下可以采用同一套服务发现和配置管理的解决方案,这将大大的简化使用和维护成本.本节课程将以 Nacos 为例,讲解服务治理的相关概念. 在服务治理框架中,通常都会构建一个注册中心,每个服务单元向注册中心登记自己提供的服务,将主机与端口号、版本号、通信协议等一些附加信息告知注册中心,注册中心按服务名分类组织服务清单。 Nacos 的安装和使用都非常简单.下面我们就在 Windows 系统上搭建一个 Nacos 服务吧. 在 Nacos 的 GitHub 页面,提供有下载链接,可以下载编译好的 Nacos 服务端或者源代码: GitHub 主页:https://github.com/alibaba/nacos GitHub 的 Release 下载页:https://github.com/alibaba/nacos/releases 如图所示: Windows 系统下载对应的 zip 压缩包即可. 将 nacos-server-2.1.0.zip 解压到任意非中文目录,此处将其解压到 D 盘的 develop 目录下: 目录说明: Nacos 的默认端口是 8848,如果你电脑上的其它进程占用了 8848 端口,请先尝试关闭该进程。如果无法关闭占用 8848 端口的进程,也可以进入 nacos 的 conf 目录,修改配置文件中的端口: 修改其中的内容: 启动非常简单,进入 bin 目录,结构如下: 然后执行命令即可: 当前目录下,打开 cmd 黑窗口,执行 windows 命令: (以单实例本机启动)。 也可以打开 startup.cmd, 修改 启动成功界面如下: 在浏览器中访问 Console 给出的 URL: http://192.168.1.4:8848/nacos/index.html ,Nacos 默认的账号密码都是 nacos 。 登录成功: 本章节,我们通过两个简单的微服务模块来了解 Nacos 是如何进行服务注册和发现. 现在我们已经成功构建了两个微服务业务模块,接下来我们尝试将这两个业务模块加入 Nacos 的服务治理体系中去.服务之间通过 HTTP 协议进行通信,所以当我们使用 Nacos 进行管理的时候,Nacos 也需要知道相关的信息,比如 IP,端口号等。 由于 Spring Cloud Alibaba 是新加入 Spring Cloud 的组件,所以没有在 spring-cloud-dependencies 里面,我们需要在项目的父工程 pom.xml 文件中加入如下依赖管理: 在上一节,我们已经搭建了 Nacos 服务,而想将我们的应用注册到 Nacos 中,我们还需要在模块中添加 Nacos 的客户端依赖。现在将依赖添加到其中一个模块的 pom.xml 文件中: 引入以上依赖之后,我们就可以在 application.yml 文件中配置相关信息,将服务注册到 Nacos 中。 在服务治理框架中,通常都会构建一个注册中心,每个服务单元向注册中心登记自己提供的服务,将主机与端口号、版本号、通信协议等一些附加信息告知注册中心,注册中心按服务名分类组织服务清单。 所以我们需要配置本服务的服务名以及 nacos 的服务端地址信息,如下: 重启当前应用,观察 nacos 控制台服务管理下的服务列表菜单,注册成功则会将该应用实例显示在列表中。 按照以上步骤,我们可以将另一个服务也注册到 Nacos 中,注册成功后,可以在 Nacos 中看到两条服务信息. 通过上面的内容介绍与实践,己经搭建起了微服务架构中的核心组件——服务注册中心。同时,还对两个业务模块做了改造。通过简单的配置,使该程序注册到 Nacos 注册中心上,成为该服务治理体系下的服务。 现在我们己经有了服务注册中心和服务提供者,服务消费者,下面就来尝试让服务消费者完成两个目标,发现服务以及消费服务。我们通过构建一个简单的示例,看看在 Nacos 的服务治理体系下如何实现服务的发现与消费。 在没有将服务交由 Nacos 管理之前,我们通过简单的 RestTemplate 对象的方法即可模拟 HTTP 请求,而现在我们将使用服务名代替传统的 IP+端口号的这种 URL,并且实现简单的负载均衡(负载均衡的内容我们将在之后的章节详细介绍). 在声明 RestTemplate 的地方添加注解: 在发送请求的时候,使用服务名替换掉原 IP+端口。服务消费方 Controller 关键代码如下所示: 当以上步骤完成之后,我们就构建了一个简单但完整的 如果测试通过,我们将得到包含两个模块数据的结果集,如下所示案例: 如果希望查询到的 User 实例包含配置文件中的端口信息, 可以这样修改 在上面的案例中,我们只启动了一个服务提供方,如果我们想要启动多个服务提供方,并且让服务消费方能够负载均衡的访问到这些服务提供方,那么我们需要做一些额外的配置. Q: Nacos 服务注册中心的作用是什么? A: 服务注册中心是一个服务的注册表, 用于存储服务提供方的信息, 以便服务消费方能够找到服务提供方, 从而实现服务的调用. pom.xml application.yml 运行 Eureka 服务, 查看可视化界面 user 和 order 模块: application.yml Q: Nacos 的服务注册中心和 Eureka 的服务注册中心有什么区别? A: Nacos 的服务注册中心和 Eureka 的服务注册中心都是用于存储服务提供方的信息, 但是 Nacos 的服务注册中心是一个更加完善的服务注册中心, 它不仅仅是一个服务注册中心, 还是一个配置中心, 服务发现中心, 以及服务管理中心. 活动 3.1 使用 Nacos 实现服务调用 d b a b 在本章中,您学习了: Views: 26 在展开 Spring Cloud 的微服务架构部署之前,我们先通过本章的内容来了解一下用于构建微服务的基础框架——Spring Boot。 在本章中我们将介绍下面这些与后续课程有密切联系的内容。 目标: Spring Boot 可以轻松创建独立的、生产级的基于 Spring 的应用程序,可以“直接运行”。Spring 平台可以轻松集成常用的第三方库。大多数 Spring Boot 应用程序只需要最少的 Spring 配置。 特征 选中 Spring Initializr 功能,选择项目 SDK(此处以 JDK 11 为例),最后点击下一步。 打开当前工程下的 pom.xml 文件,看看生成的项目都引入了哪些依赖来构建 Spring Boot 工程,内容大致如下所示。 在基础信息部分,groupld 和 artifactld 对应生成项目时页面上输入的内容。另外 Spring Boot 默认将该微服务应用打包为 jar 的形式,而非 war 的形式,因为默认的 Web 模块依赖会包含嵌入式的 Tomcat,这样使得我们的应用 jar 自身就具备了提供 Web 服务的能力,后续我们会演示如何启动它。 父项目 parent 配置指定为 spring-boot-starter-parent 的 2.7.1 版本,该父项目中定义了 Spring Boot 版本的基础依赖以及一些默认配置内容,比如,配置文件 application.properties 的位置等。在项目依赖 dependencies 配置中,包含了下面两项。 这里所引用的 web 和 test 模块,在 Spring Boot 生态中被称为 Starter POMs。Starter POMs 是一系列轻便的依赖包,是一套一站式的 Spring Spring Boot 的 官方 Starter POMs 采用 另外 文档: 在 Spring Boot 中创建一个 RESTful API 的实现代码同 Spring MVC 应用一样,只是不需要像 Spring MVC 那样先做很多配置,而是像下面这样直接开始编写 Controller 内容: 启动成功,默认端口 8080 功能实现之后,我们要养成随手写配套单元测试的习惯,这在微服务架构中尤为重要。 单元测试是在开发过程中用来验证代码正确性非常好的手段,并且这些单元测试将会很好地支持我们未来可能会进行的重构。 在 Spring Boot 中实现单元测试同样非常方便,下面我们编写一个简单的单元测试来模拟 HTTP 请求,测试之前实现的/hello 接口,该接口应返回 Hello World 字符串。 该单元测试使用 使用 RestTemplate 进行访问时,需要确保服务端保持活动状态,否则会测试不通过. 我们通过 IDEA 工具构建了一个 Spring Boot 的基础项目,并详细介绍了该基础项目的结构及 Maven 的依赖关系,接着在该项目中实现一个输出“Hello World”的 RESTful 接口以及针对该接口的单元测试用例,完成了一个虽然简单,但涵盖了项目构建、服务开发、单元测试的全套开发内容。 我们在后续使用 Spring Cloud 的时候会构建多个 Spring Boot 的项目来实现微服务架构中的基础设施以及微服务应用,通过本节的学习,我们己经具备了使用 Spring Boot 来构建简单微服务项目的基本能力。我们将对后续在 Spring Cloud 组件使用过程中会涉及的配置内容做一些介绍和解释。 我们已经轻松地实现了一个简单的 RESTful API 应用,体验了 Spring Boot 的诸多优点。在配置方面,除了 Maven 的配置之外,没有引入任何其他配置。这就是之前我们提到的,Spring Boot 针对常用的开发场景提供了一系列自动化配置来减少原本复杂而又几乎很少改动的模板化配置内容。 但是,我们还是需要了解如何在 Spring Boot 中修改这些自动化的配置内容,以应对一些特殊的场景需求。比如,我们在同一台主机上需要启动多个基于 Spring Boot 的 Web 应用,若不为每个应用指定特别的端口号,那么默认的 8080 端口必将导致冲突。 我们在使用 Spring Cloud 的各个组件的时候,其实有大量的工作都会是针对配置文件的。所以我们有必要深入了解一些关于 Spring Boot 中的配置文件的知识,比如配置方式、如何实现多环境配置、配置信息的加载顺序等 Spring Boot 的工程结构 src/main/ resources 目录是 Spring Boot 的配置目录,所以当要为应用创建个性化配置时,应在该目录下进行。Spring Boot 的默认配置文件位置为 src/main/resources/application.properties。 上图在配置文件中定义了应用的端口号以及上下文路径. 关于 Spring Boot 应用的配置内容都可以集中在该文件中,根据我们引入的不同 Starter 模块,可以在这里定义容器端口号、数据库连接信息、日志级别等各种配置信息。Spring Boot 的配置文件除了可以使用传统的 properties 文件之外,还支持现在被广泛推荐使用的 通过 YAML 的配置方式我们可以看到,配置信息利用阶梯化缩进的方式,其结构更为清晰易读,同时配置内容的字符量也得到显著减少。后续的配置文件,我们将逐步使用 YAML 方式进行编写. YAML(英语发音为/;jaem3l/,尾音类似 camel 骆驼)是一个可读性高,用来表达资料序列的格式。YAML 的意思其实是:Yet Another Markup Language (仍是一种标记语言),但为了强调这种语言以数据作为中心,而不是以标记语言为重点,而用反向缩略语重新命名。 YAML 的语法特别适合用来表达或编辑数据结构、各种设定文档、文件大纲(例如,许多电子邮件标题格式和 YAML 非常接近)。尽管它比较适合表达阶层式(hierarchical model)的数据结构,不过也有精致的语法可以表示关联性(relational model)的数据结构。 由于 YAML 使用空白符号和分行来分隔资料,其让人最容易上手的特色是巧妙避开各种封闭符号,如引号、各种括号等,这些符号在 xml 结构时会变得复杂而难以辨认。 除了可以在 Spring Boot 的配置文件中设置各个 Starter 模块中预定义的配置属性,也可以在配置文件中定义一些我们需要的自定义属性。比如在 application.yml 中添加: 自定义 Book 类,使用@Value 进行属性注入 @Value 注解加载属性值时可以支持两种表达式来进行配置,如下所示。 编写单元测试 启动测试,测试结果将在控制台输出: 在 application .yml 中的各个参数之间可以直接通过使用 PlaceHolder 的方式来进行引用,就像下面的设置 : 修改 Book 类,添加 desc 属性. 单元测试步骤同上.测试成功将在控制台打印: 我们在开发应用的时候,通常同一套程序会被应用和安装到几个不同的环境中,比如开发、测试、生产等。其中每个环境的数据库地址、服务器端口等配置都不同,如果在为不同环境打包时都要频繁修改配置文件的话,那必将是个非常烦琐且容易发生错误的事。 Spring Boot 通过配置多份不同环境的配置文件,实现多环境配置更加简单. 在 Spring Boot 中,多环境配置的文件名需要满足 至于具体哪个配置文件会被加载,需要在 application.yml 文件中通过 如下所示: 多环境的配置思路: 在上面的例子中,我们将 SpringBoot 应用需要的配置内容都放在了项目工程中,己经能够通过 spring.profiles.active 来实现多环境的支持。但是,当团队逐渐壮大,分工越来越细致之后,往往不需要让开发人员知道测试或是生产环境的细节,而是希望由每个环境各自的负责人(QA 或是运维)来集中维护这些信息。 那么如果还是以这样的方式存储配置内容,对于不同环境配置的修改就不得不去获取工程内容来修改这些配置内容,当应用非常多的时候就变得非常不方便。同时,配置内容对开发人员都可见,这本身也是一种安全隐患。对此,出现了很多将配置内容外部化的框架和工具,后续将要介绍的 Spring Cloud Config 和Spring Cloud Alibaba Nacos就是一些解决方案,为了后续能更好地理解 这些解决方案 的加载机制,我们需要对 Spring Boot 对数据文件的加载机制有一定的了解。 为了能够更合理地重写各属性的值,Spring Boot 使用了下面这种较为特别的属性加载顺序: 优先级按上面的顺序由高到低,数字越小优先级越高。 可以看到,其中第 7 项和第 9 项都是从应用 jar 包之外读取配置文件,所以,实现外部化配置的原理就是从此切入,为其指定外部配置文件的加载位置来取代 jar 包之内的配置内容。通过这样的实现,我们的工程在配置中就变得非常干净,只需在本地放置幵发需要的配置即可,而不用关心其他环境的配置,由其对应环境的负责人去维护即可。 配置加载优先级通用原理总结: 活动 2.1 创建 SpringBoot 应用,使用 RestTemplate 进行互相调用。 在微服务架构中各个应用的内部逻辑因分解而得以简化,但是由于部署应用的数量成倍增长,使得系统的维护复杂度大大提升。对于运维人员来说,为了能对这些成倍增长的应用做到高效运维,传统的运维方式显然是不合适的,所以我们需要实现一套自动化的监控运维机制,而这套机制的运行基础就是不间断地收集各个微服务应用的各项指标情况,并根据这些基础指标信息来制定监控和预警规则,更进一步甚至做到一些自动化的运维操作等。 当我们决定用 Spring Boot 来作为微服务框架时,除了它强大的快速开发功能之外,还因为它在 Starter POMs 中提供了一个特殊依赖模块 引入该模块能够自动为 SpringBoot 构建的应用提供一系列用于监控的端点。当然,它也并不是万能的,有时候也需要对其做一些简单的扩展来帮助我们实现自身系统个性化的监控需求。所以,在本节将详细介绍一些关于 下面,我们通过对“快速入门”小节中实现的 Spring Boot 应用增加 spring-boot- starter-actuator 模块功能,来对它有一个直观的认识。在现有的 Spring Boot 应用中引入该模块非常简单,只需要在 pom. xml 的 dependency 节点中,新增 spring-boot-starter-actuator 的依赖即可,具体如下: 增加该依赖之后,重新启动应用。此时输出: 上图显示了端点定义,这个端点并非我们自己在程序中创建的,而是由 比如访问 在没有引入其他依赖之前,该端点的内容较为简单,后续我们在使用 Spring Cloud 的各个组件之后,它的返回会变得非常丰富,这些内容将帮助我们制定更为个性化的监控策略。 通过在快速入门示例中添加 spring-boot-starter-actuator 模块,我们已经对它有了一个初步的认识。接下来,我们详细介绍一下 spring-boot-starter-actuator 模块中己经实现的一些原生端点。根据端点的作用,可以将原生端点分为以下三大类。 出于安全考虑,某些端点默认是没有对外暴露的.为方便测试,我们进行手动开启. 在 application.yml 中添加如下配置: 下面我们来详细了解一下这三类端点都分别可以为我们提供怎样的有用信息和强大功能,以及我们如何去扩展和配置它们。 由于 Spring Boot 为了改善传统 Spring 应用繁杂的配置内容,采用了包扫描和自动化配置的机制来加载原本集中于 XML 文件中的各项内容。虽然这样的做法让我们的代码变得非常简洁,但是整个应用的实例创建和依赖关系等信息都被离散到了各个配置类的注解上,这使我们分析整个应用中资源和实例的各种关系变得非常困难。而这类端点可以帮助我们轻松获取一系列关于 Spring 应用配置内容的详细报告,比如 Bean 创建的报告、环境属性的报告等。 该端点用来获取应用上下文中创建的所有 Bean。以本项目 dev 环境为例,启动后访问: http://localhost:8081/dev/actuator/beans ,可获取如下信息(部分展示): 在如上示例中,我们可以看到在每个 Bean 中都包含了下面这些信息 。 以本项目 dev 环境为例,启动后访问: http://localhost:8081/dev/actuator/configprops 。该端点用来获取应用中配置的属性信息报告。如下所示,prefix 属性代表了属性的配置前缀,properties 代表了各个属性的名称和值。 所以,我们可以通过该报告来看到各个属性的配置路径,比如我们要关闭该端点,就可以通过使用 以本项目 dev 环境为例,启动后访问: http://localhost:8081/dev/actuator/env。该端点与 以本项目 dev 环境为例,启动后访问: http://localhost:8081/dev/actuator/mappings。该端点用来返回所有 Spring MVC 的控制器映射关系报告。 上面我们所介绍的应用配置类端点可以说是一个静态报告。而度量指标类端点提供的报告内容则是动态变化的,这些端点提供了应用程序在运行过程中的一些快照信息,比如内存使用情况、HTTP 请求统计、外部资源指标等。 这些端点对于我们构建微服务架构中的监控系统非常有帮助,由于 Spring Boot 应用自身实现了这些端点,所以我们可以很方便地利用它们来收集我们想要的信息,以制定出各种自动化策略。下面,我们就来分别看看这些强大的端点功能。 以本项目 dev 环境为例,启动后访问: http://localhost:8081/dev/actuator/metrics。 该端点用来返回当前应用的各类重要度量指标,比如内存信息、线程信息、垃圾回收信息等。 如下所示(部分): 在当前路径后拼接上面路径即可查看对应详细信息. 如拼接"disk.free" ,访问: http://localhost:8081/dev/actuator/metrics/disk.free 可以获知当前磁盘剩余可用容量。 以本项目 dev 环境为例,启动后访问: http://localhost:8081/dev/actuator/health。该端点用来获取应用的各类健康指标信息。如下所示: 当前项目较为简单,只标识出应用的状态.后续集成 Spring Cloud 组件后,返回信息将变得丰富起来.状态码解释如下: 操作控制类端点拥有更强大的控制能力,如果要使用它们的话,需要通过属性来配置开启操作。 在原生端点中,只提供了一个用来关闭应用的端点: 在配置了上述属性之后,只需要访问该应用的/shutdown 端点就能实现关闭该应用的远程操作。由于开放关闭应用的操作本身是一件非常危险的事,所以真正在线上使用的时候,需要对其加入一定的保护机制,比如定制 actuator 的端点路径、整合 Spring Security 进行安全校验等。 该端点不支持 Get 请求,我们可以借助 PostMan 等类似工具发送 POST 请求. 活动 2.2 在 Spring Boot 项目中通过 actuator 端点查看应用的相关信息. 小问题 : 以下哪个内部类在定义时可以没有类名称? D IDEA 2021.2 版本 DevTools 热部署为例 首先引入依赖 修改 Settings 里 Compiler 选项下的配置,将下图中红色方框里的选项全部勾上 修改 Advanced Settings 里的配置,将下图的选项勾上 注意 Advanced Settings 选项只有在 File->Settings 里面才有,而在 File->New Projects Setup->Settings for New Projects 这个设置页面里是没有的,所以只能设置本项目,而不能自动设置新建的项目。 如果使用的是 IDEA 2021.2 之前版本的话还是使用快捷键 shift+Ctrl+Alt+/,选择 Registry…,将 compiler.automake.allow.when.app.running 选项勾上。 完成上面的步骤后重启下 IDEA 基本就没问题了,如果还是不行的话建议检查 pom 和 application 文件是否引入了 DevTools 依赖并且正确配置。 d a c 在 Spring Boot 应用中,不必引入 spring-boot-starter-actuator 的 Maven 即可实现端点监控? a. 正确 b Views: 55SpringCloud与微服务-第5章-服务容错(Hystrix)
Spring Cloud Hystrix断路器快速入门
的进程中运行,依赖通过远程调用的方式执行,这样就有可能因为网络原因或是依赖服务自身问题出现调用故障或延迟,而这些问题
会直接导致调用方的对外服务也出现延迟,若此时调用方的请求不断增加,最后就会因等待出现故障的依赖方响应形成任务积压,最
终导致自身服务的瘫痪。
目标:
服务雪崩

服务雪崩产生原因
由于某些代码问题导致 CPU 飙升,将资源耗尽等,比如服务器出现问题,磁盘出问题,导致数据读写特别慢,还比如说出现慢 SQL, 亦或请求量太大了已经超出了系统本身的承受能力, 又或者出现死循环, 死锁, 线程池配置等问题导致服务响应时间过长或报错。
比如同步调用等待结果导致资源耗尽,另外一些服务消费者可能同时也是服务提供者。
服务雪崩的解决方案
Hystrix 快速入门
execute() 和 queue() 方法来实现的,它们都会将请求结果缓存起来,当下次请求相同的命令时,就会直接从缓存中获取结果,而不是再次发送请求。queue() 方法来实现的,它会将多个请求合并为一个请求,然后再发送给依赖服务。
对消费端进行改造
1.在消费端引入 Hystrix 的启动依赖
<!--引入Hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!--Hystrix仪表盘-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
<!--actuator端点-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
2.改造启动类
@EnableCircuitBreaker 注解,启动断路器功能。@MapperScan("com.niit.order.mapper")
@SpringBootApplication
@EnableCircuitBreaker //启用断路器功能
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
2.新增 Controller
@RestController
@RequestMapping("/hystrix")
public class HystrixTestController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/{param}")
@HystrixCommand(fallbackMethod = "showAppInfoFallback")
public String showAppInfo(@PathVariable("param") String param) {
//通过RestTemplate查询用户信息 使用服务名代替ip和端口号
String url = "http://userservice/hystrix/"+param;
return restTemplate.getForObject(url, String.class);
}
//回调方法 - 服务降级 - 当前方法调用失败时,调用该方法
public String showAppInfoFallback(@PathVariable("param") String param) {
return "服务器繁忙,请稍后再试o(╥﹏╥)o";
}
}
@HystrixCommand 中的 fallbackMethod 属性代表着当前方法的回调方法,属性值就是该 Controller 下的方法名.
对服务端进行改造
@Slf4j
@RestController
@RequestMapping("/hystrix")
public class HystrixProviderController {
//自动注入端口号的值,方便区分查看应用
@Value("${server.port}")
private String port;
@GetMapping("/{param}")
public String showInfo(@PathVariable("param")String param){
return "当前应用的端口是: " + port+ "传递过来的参数是: "+ param;
}
}
启动要求
1.启动 Nacos
startup.cmd -m standalone
2.启动消费端
ribbon.staticServerList:
userservice:
- http://localhost:9020
- http://localhost:9021
- http://localhost:9022
3.启动服务端

测试及结果
1.正常访问
当前应用的端口是: 9020; 传递过来的参数是: 100;
当前应用的端口是: 9021; 传递过来的参数是: 100;
当前应用的端口是: 9022; 传递过来的参数是: 100;
2.服务器宕机
ribbon.staticServerList配置是有效的, 那么假如我们停掉 9022 端口的实例,那么刷新之后,将可能会出现以下这样的三种情境:当前应用的端口是: 9020; 传递过来的参数是: 100;
当前应用的端口是: 9021; 传递过来的参数是: 100;
服务器繁忙,请稍后再试o(╥﹏╥)oribbon.staticServerList配置是无效的, 估计这个配置已经被废弃了, 因为使用静态的服务列表是无法感知动态同步示例的变化的, 所以实际上也不推荐使用这个配置.服务器繁忙,请稍后再试o(╥﹏╥)o
3.服务端超时
/HystrixProviderController控制器并做一些修改,具体如下:
@Slf4j
@RestController
@RequestMapping("/hystrix")
public class HystrixProviderController {
/**
* 自动注入端口号的值,方便区分查看应用
*/
@Value("${server.port}")
private String port;
@GetMapping("/{param}")
public String showInfo(@PathVariable("param") String param) throws InterruptedException {
//让处理线程等待几秒钟
long begin = System.currentTimeMillis();
int sleepTime = new Random().nextInt(2000);
Thread.sleep(sleepTime);
long end = System.currentTimeMillis();
System.out.println("阻塞时间: " + (end-begin)+"毫秒");
return "当前应用的端口是: " + port
+ "; 传递过来的参数是: "
+ param + "; 共花费执行时间: "
+ (end - begin) + " 毫秒";
}
}
Thread.sleep()函数可让/hello 接口的处理线程不是马上返回内容,而是在阻塞几秒之后才返回内容。由于 Hystrix 默认超时时间为 1000 毫秒,所以这里采用了 0 至 2000 的随机数以让处理过程有一定概率发生超时来触发断路器:阻塞时间: 865毫秒
当前应用的端口是: 9020; 传递过来的参数是: 100; 共花费执行时间: 865 毫秒
阻塞时间: 1655毫秒
服务器繁忙,请稍后再试o(╥﹏╥)o
/hystrix/{param}接口逻辑改为抛出异常: @GetMapping("/{param}")
public String showInfo(@PathVariable("param")String param){
// 制造异常
int i = 1/0;
return "当前应用的端口是: " + port+ "传递过来的参数是: "+ param;
}
服务器繁忙,请稍后再试o(╥﹏╥)o, 并且没有任何停顿, 说明服务降级已经生效了。这时的服务降级是因为服务端抛出异常而触发的, 而不是因为超时而触发的。
application.yml 配置文件中不要设置自定义的超时时间。而是使用默认的超时时间.(1秒钟)
原理分析(How It Works)

1. 构造一个
HystrixCommand或HystrixObservableCommand对象HystrixCommand或HystrixObservableCommand对象来表示您对依赖项发出的请求。向构造函数传递发出请求时需要的任何参数。代码中我们可以简单的通过使用@HystrixCommand注解实现.
HystrixCommand:用在依赖的服务返回单个操作结果的时候。
observe() 一起使用,则会提供一个监听器模式的非阻塞实现。HystrixObservableCommand:用在依赖的服务返回多个操作结果的时候。
HystrixCommand相同, 不同的是此命令应用于纯非阻塞调用模式。此命令的调用方将订阅 run() 方法返回的 Observable。
HystrixCommand或HystrixObservableCommand对象呢?我们可以通过继承HystrixCommand类或HystrixObservableCommand来实现,也可以通过@HystrixCommand注解来实现。
HystrixObservableCommand对象呢?我们可以通过继承HystrixObservableCommand类来实现,也可以通过@HystrixCommand注解来实现。
Receiver, 负责具体的业务逻辑处理,它是真正执行命令的对象。public class Receiver {
public void action(){
//执行真正的业务逻辑
System.out.println("Receiver 执行命令...");
}
}
execute ()、undo()、 redo()等。当命令操作被调用的时候就会触发接收者去做具体命令对应的业务逻辑。public interface ICommand {
/**
* 执行方法
*/
void execute();
}
ConcreteCommand, 它是抽象命令类的具体实现类,它是一个具体的命令对象通过调用接收者的相关操作来实现业务逻辑。public class ConcreteCommand implements ICommand {
private Receiver receiver; // 组合接收者对象
public ConcreteCommand(Receiver receiver){
this.receiver = receiver;
}
@Override
public void execute() {
this.receiver.action();
}
}
Invoker, 它是命令模式的核心,它负责调用命令对象执行请求,相关的方法叫做行动方法。它可以持有一个或多个命令对象,它在需要的时候可以去调用命令对象的执行方法来执行请求,它通过这种方式来实现间接调用请求接收者的相关操作来实现业务操作。public class Invoker {
private ICommand command; // 组合命令对象
public void setCommand(ICommand command) {
this.command = command;
}
public void action(){
this.command.execute();
}
}
Client, 它是命令模式的使用者,它通过调用者来执行命令。public class Client {
public static void main(String[] args) {
Receiver receiver = new Receiver();
ConcreteCommand command = new ConcreteCommand(receiver);
Invoker invoker = new Invoker();
invoker.setCommand(command);
//客户端通过调用者来执行命令
invoker.action();
}
}

undo()方法,把命令所产生的效果撤销掉。redo()方法,以供客户端在需要时再重新实施命令效果。executed 方法一条一条执行这些命令,从而恢复系统在崩溃前所做的数据更新。
2. Hystrix 命令模式
HystrixCommand 或 HystrixObservableCommand,通过使用 Hystrix 命令对象的以下四种方法之一:
HystrixCommand
execute() — 同步执行,从依赖的服务返回一个单一的结果对象,或是在发生错误的时候抛出异常。queue() — 异步执行,直接返回一个 Future 对象,其中包含了服务执行结束时要返回的单一结果对象。HystrixObservableCommand,通过使用
observe() — 返回来自依赖项的响应的 Observable 对象 ,它代表了操作的多个结果,它是一个 hot Observable。toObservable() — 同样会返回 Observable 对象,也代表了操作的多个结果,但它返回的是一个 cold Observable。K value = command.execute();
Future<K> fValue = command.queue();
Observable<K> ohValue = command.observe(); //hot observable
Observable<K> ocValue = command.toObservable(); //cold observable
execute() - 同步执行@HystrixCommand(fallbackMethod = "fallbackMethod")
public String strConsumer() {
ResponseEntity<String> result = restTemplate.getForEntity("http://cloud-eureka-client/hello", String.class);
return result.getBody();
}
queue() - 异步执行@HystrixCommand(fallbackMethod = "fallbackMethod", ignoreExceptions = {IllegalAccessException.class})
public Future<String> asyncStrConsumer() {
Future<String> asyncResult = new AsyncResult<String>() {
@Override
public String invoke() {
ResponseEntity<String> result = restTemplate.getForEntity("http://cloud-eureka-client/hello", String.class);
return result.getBody();
}
};
return asyncResult;
}
ignoreExceptions 表示抛出该异常时不走降级回调逻辑,忽略此异常。
observe () 执行方式 @HystrixCommand(observableExecutionMode = ObservableExecutionMode.EAGER)
protected Observable<String> construct() {
ResponseEntity<String> result = restTemplate.getForEntity("http://cloud-eureka-client/hello", String.class);
return Observable.just(result.getBody());
}
ObservableExecutionMode.EAGER —— 代表使用 observe() 执行方式,返回的是一个 hot Observable,即便没有订阅者,它也会立即执行。
toObservable() 执行方式@HystrixCommand(observableExecutionMode = ObservableExecutionMode.LAZY)
protected Observable<String> construct() {
ResponseEntity<String> result = restTemplate.getForEntity("http://cloud-eureka-client/hello", String.class);
return Observable.just(result.getBody());
}
ObservableExecutionMode.LAZY —— 代表使用 toObservable() 执行方式,返回的是一个 cold Observable,只有在有订阅者时才会执行。
onNext()方法。Subscriber.onCompleted()或者 Subscriber.onError()来结束该事件的操作流。
// 1. 创建一个被观察者 Observable
Observable<String> observable = Observable.create(new Observable.OnSubscribe<String>() {
// 2. 在被观察者 Observable 被订阅时,回调该方法
@Override
public void call(Subscriber<? super String> subscriber) {
// 3. 发送事件:onNext()
subscriber.onNext("Hello RxJava");
subscriber.onNext("I am Jeff");
// 4. 发送事件:onCompleted()
subscriber.onCompleted();
}
});
// 5. 创建一个观察者 Observer
Observer<String> observer = new Observer<String>() {
// 6. 注册观察者时,回调该方法
@Override
public void onCompleted() {
// 7. 观察者接收完事件后,回调该方法
}
// 6. 注册观察者时,回调该方法
@Override
public void onError(Throwable e) {
// 7. 发生错误时,回调该方法
}
// 6. 注册观察者时,回调该方法
@Override
public void onNext(String str) {
// 7. 接收到事件时,回调该方法
System.out.println("Subscriber: " + str);
}
};
// 8. 通过订阅(Subscribe)连接观察者和被观察者
observable.subscribe(observer);
observable.subscribe (subscriber)来触发事件的发布。
Observable.create() 方法,创建一个被观察者 ObservableObservable.subscribe() 方法,订阅观察者 SubscriberSubscriber.onNext() 方法,让观察者接收到事件next() 方法输出事件内容Subscriber.onCompleted() 方法,用来通知观察者事件已经结束complete() 方法,用来结束事件流.
command.observe ()和 command.toObservable ()的返回对象。
Hot Observable 是指在订阅之前就开始发射数据的 Observable。observe()方法返回的 Hot Observable 代表了一个可观察的事件流,它可以被订阅,以便在事件流中接收数据。
和 Hot Observable 的区别是, Cold Observable 是 toObservable()方法返回的, 并且只有在订阅之后才开始发射数据。
3. 结果是否被缓存
HystrixCommandProperties.Setter().withRequestCacheEnabled(true)来启用缓存功能。
4. 断路器是否打开
5. 线程池/请求队列/信号量是否占满
6. 执行
HystrixObservableCommand.construct() 或 HystrixCommand.run()
HystrixCommand.run(): 返回一个单一的结果,或者抛出异常。HystrixObservableCommand.construct(): 返回一个 Observable 对象来发射多个结果,或通过 onError 发送错误通知。
run()或 construct()方法的执行时间超过了命令设置的超时阈值,当前处理线程将会抛出一个 TimeoutException (如果该命令不在其自身的线程中执行,则会通过单独的计时线程来抛出)。在这种情况下,Hystrix 会转接到 fallback 处理逻辑(第 8 步)。 同时,如果当前命令没有被取消或中断,那么它最终会忽略 run()或者 construct()方法的返回。run()的情况下,Hystrix 会返回一个 Observable,它发射单个结果并产生 onCompleted 的结束通知;而在使用 construct() 的情况下,Hystrix 会 直接返回该方法产生的 Observable 对象。
7. 计算断路器的健康度
8. fallback 处理
HystrixObservableCommand.construct() 和HystrixCommand.run() 抛出异常的时候。
HystrixCommand.getFallback()来实现服务降级逻辑。HystrixObservableCommand.resumeWithFallback()实现服务降级逻辑,该方法会返回一个 Observable 对象来发射一个或多个降级结果。
HystrixCommand.getFallback()的时候,它会返回一个 Observable 对象,该对象会发射 getFallback()的处理结果。而使用 HystrixObservableCommand.resumeWithFallback ()实现的时候,它会将 Observable 对象直接返回。
onError 方法通知命令立即中断请求,并通过 onError ()方法将引起命令失败的异常发送给调用者。实现一个有可能失败的降级逻辑是一种非常糟糕的做法,我们应该在实现降级策略时尽可能避免失败的情况。
execute(): 抛出异常。queue():正常返回 Future 对象,但是当调用 get()来获取结果的时候会抛出异常。observe():正常返回 Observable 对象,当订阅它的时候,将立即通过调用订阅者的 onError 方法来通知中止请求。toObservable(): 正常返回 Observable 对象,当订阅它的时候,将通过调用订阅者的 onError 方法来通知中止请求。
当 Hystrix 命令执行成功之后,它会将处理结果直接返回或是以 Observable 的形式 返回。而具体以哪种方式返回取决于之前第 2 步中我们所提到的对命令的 4 种不同执行方 式,下图中总结了这 4 种调用方式之间的依赖关系。我们可以将此图与在第 2 步中对前两 者源码的分析联系起来,并且从源头 toObservable ()来开始分析。

execute() - 以与 queue()相同的方式获取一个 Future,然后在这个 Future 上调用 get()来获取由 Observable 发出的单个值queue() - 将 Observable 转换为一个 BlockingObservable,以便它可以被转换为一个 Future,然后返回这个 Futureobserve() - 在 toObservable()产生原始 Observable 之后立即订阅它,让命令能够马上开始异步执行,并返回一个 Observable 对象,当调用它的 subscribe 时,将重新产生结果和通知给订阅者。toObservable() - 返回最原始的 Observable,必须通过订阅它才会真正触发命令的执行流程。
断路器原理

@HystricCommand(fallbackMethod = "fallback", commandProperties = {
@HystrixProperty(name = "circuitBreaker.enabled", value = "true"), // 是否开启断路器
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"), // 请求总数阀值
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000"), // 时间窗口期
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60"), // 错误百分比阀值
})
底层原理 (了解)

HystrixCommandProperties.circuitBreakerRequestVolumeThreshold() 的阈值(默认 20);或者在滚动时间窗口内,失败的请求数量占总请求数量的百分比超过了 HystrixCommandProperties.circuitBreakerErrorThresholdPercentage() 的阈值(默认 50%),那么断路器将会被打开,此时所有的请求都会被拒绝(失败), 熔断器将会从闭合状态变成打开状态;
在等待(冷却)的时间超过 HystrixCommandProperties.circuitBreakerSleepWindowInMilliseconds() 的值(默认 5 秒)后,断路器将会进入半开状态,此时断路器将会允许一个请求去调用依赖,如果这个请求调用成功,断路器将会闭合, 后续依赖调用可正常执行。,如果这个请求调用失败,断路器将会继续打开,继续等待下一次的半开状态(冷却)。
10 1-second "buckets" 的意思是,每 1 秒,会统计最近 10 秒内的请求总数,如果请求数量超过了阀值,就会根据错误百分比来判断是否需要打开断路器。HystrixCommandProperties.metricsRollingStatisticalWindowBuckets() 来配置,默认为 10 个桶,每个桶 1 秒,所以默认的快照时间窗为 10 秒。HystrixCircuitBreaker 的定义:public interface HystrixCircuitBreaker {
boolean allowRequest();
boolean isOpen();
void markSuccess();
public static class NoOpCircuitBreaker implements HystrixCircuitBreaker {...}
public static class HystrixCircuitBreakerImpl implements HystrixCircuitBreaker {...}
public static class Factory {...}
allowRequest()
每个 Hystrix 命令的请求都通过它判断是否被执行。isOpen()
返回当前断路器是否打开。markSuccess()
用来闭合断路器。
Factory 中维护了一个 Hystrix 命令与 HystrixCircuitBreaker 的关系集合:private static ConcurrentHashMap<String, HystrixCircuitBreaker> circuitBreakersByCommand = new ConcurrentHashMap();HystrixCommandKey 定义,每一个 Hystrix 命令需要有一个 key 来标识,同时一个 Hystrix 命令也会在该集合中 找到它对应的断路器 HystrixCircuitBreaker 实例。
NoOpCircuitBreaker 定义了一个什么都不做的断路器实现,它允许所有请求,并且断路器状态始终闭合(允许请求通过)。HystrixCircuitBreakerlmpl 是断路器接口 HystrixCircuitBreaker 的实现类,在该类中定义了断路器的 4 个核心对象。
HystrixCommandProperties properties:断路器对应 HystrixCommand 实例的属性对象,它的详细内容我们将在后续章节做具体的介绍。HystrixCommandMetrics metrics:用来让 HystrixCommand 记录各类度量指标的对象。AtomicBoolean circuitOpen:断路器是否打开的标志,默认为 false。AtomicLong circuitOpenedOrLastTestedTime:断路器打开或是上一次测试的时间 戳。
HystrixCircuitBreakerlmpl 对 HystrixCircuitBreaker 接口的各个方法实现如下所示。
isOpen ():判断断路器的打开/关闭状态。 public boolean isOpen() {
if (this.circuitOpen.get()) {
return true;
} else {
HealthCounts health = this.metrics.getHealthCounts();
if (health.getTotalRequests() < (long)(Integer)this.properties.circuitBreakerRequestVolumeThreshold().get()) {
return false;
} else if (health.getErrorPercentage() < (Integer)this.properties.circuitBreakerErrorThresholdPercentage().get()) {
return false;
} else if (this.circuitOpen.compareAndSet(false, true)) {
this.circuitOpenedOrLastTestedTime.set(System.currentTimeMillis());
return true;
} else {
return true;
}
}
}
10 秒)。false,表示断路器处于未打开状态。该阈值的酉己置参数为 circuitBreakerRequestVolumeThreshold, 默认值为 20。circuitBreakerErrorThresholdPercentage,默认值 为 50。circuitOpenedOrLastTestedTime 对象中。
public boolean allowRequest() {
if ((Boolean)this.properties.circuitBreakerForceOpen().get()) {
return false;
} else if ((Boolean)this.properties.circuitBreakerForceClosed().get()) {
this.isOpen();
return true;
} else {
return !this.isOpen() || this.allowSingleTest();
}
}
isOpen ()来执行断路器的计算逻辑,用来模拟断路器打开/关闭的行为。!isOpen()|| allowSingleTest()来判断是否允许请求访问。!isOpen ()之前己经介绍过,用来判断和计算当前断路器是否打开,如果是断开状态就不允许请求。那么 allowSingleTest()是用来做什么的呢?
public boolean allowSingleTest() {
long timeCircuitOpenedOrWasLastTested = this.circuitOpenedOrLastTestedTime.get();
return
this.circuitOpen.get()
&&
System.currentTimeMillis() >
timeCircuitOpenedOrWasLastTested
+ (long)(Integer)this.properties.circuitBreakerSleepWindowInMilliseconds().get()
&&
this.circuitOpenedOrLastTestedTime.compareAndSet(
timeCircuitOpenedOrWasLastTested,
System.currentTimeMillis()
);
}
allowSingleTest()的实现中我们可以看到,这里使用了在 isOpen()函数中当断路器从闭合到打开时候所记录的时间戳。当断路器在打开状态的时候,这里会判断 断开时的时间戳 + 配置中的 circuitBreakerSleepWindowInMilliseconds 时间是否小于当前时间,是的话,就将当前时间更新到记录断路器打开的时间对象 circuitOpenedOrLastTestedTime 中,并且允许此次请求。简单地说,通过 circuitBreakerSleepWindowInMilliseconds 属性设置了一个断路器打幵之后的休眠时间(默认为 5 秒),在该休眠时间到达之后,将再次允许请求尝试访问,此时断路器处于“半开”状态,若此时请求继续失败,断路器又进入打开状态,并继续等待下一个休眠窗口过去之后再次尝试;若请求成功,则将断路器重新置于关 闭状态。所以通过 allowSingleTest()与 isOpen()方法的配合,实现了断路器打开和关闭状态的切换。markSucces():该函数用来在“半开路”状态时使用。若 Hystrix 命令调用成功, 通过调用它将打开的断路器关闭,并重置度量指标对象。
依赖隔离


比较情况
未使用线程池隔离
使用了线程池隔离
耗时差距
中位数
2ms
2ms
2ms
90 百分位
5ms
8ms
3ms
99 百分位
28ms
37ms
9ms
在 Hystrix 中除了可使用线程池之外,还可以使用信号量来控制单个依赖服务的并发度,信号量的开销远比线程池的开销小,但是它不能设置超时和实现异步访问。所以,只有在依赖服务是足够可靠的情况下才使用信号量。
execution.isolation.strategy 设置为 SEMAPHORE, Hystrix 会使用信号量替代线程池来控制依赖服务的并发。(默认值为 THREAD)。
使用详解和属性详解
使用详解
创建请求命令
@GetMapping("/{param}")
@HystrixCommand(fallbackMethod = "showAppInfoFallback")
public String showAppInfo(@PathVariable("param") String param) {
String url = "http://userservice/hystrix/"+param;
return restTemplate.getForObject(url, String.class);
}
showAppInfoFallback的函数。
定义服务降级
private、 protected、 public 均可, 如:/**
* 服务降级方法
* @param param 源方法传入的参数
* @return 和源方法的返回值类型保持一致
*/
public String showAppInfoFallback(String param) {
return "服务器繁忙,请稍后再试o(╥﹏╥)o" + param;
}
@RestController
@RequestMapping("/hystrix")
public class HystrixConsumerController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/{param}")
@HystrixCommand(fallbackMethod = "showAppInfoFallback")
public String showAppInfo(@PathVariable("param") String param) {
String url = "http://userservice/hystrix/"+param;
return restTemplate.getForObject(url, String.class);
}
/**
* 服务降级方法
* @param param 源方法传入的参数
* @return 和源方法的返回值类型保持一致
*/
@HystrixCommand(fallbackMethod = "showAppInfoFallbackEnd")
public String showAppInfoFallback(String param) {
String url = "http://userservice/hystrix/"+param+"abc";
return restTemplate.getForObject(url, String.class);
}
/**
* 终极服务降级方法
* @param param 源方法传入的参数
* @return 和源方法的返回值类型保持一致
*/
public String showAppInfoFallbackEnd(String param) {
return "最终降级方法,逻辑稳定..." + param;
}
}
异常处理
异常传播
@HystrixCommand(fallbackMethod = "showAppInfoFallbackEnd",
ignoreExceptions = {BadRequestException.class})
public String showAppInfo(String param) {
String url = "http://userservice/hystrix/"+param+"abc";
return restTemplate.getForObject(url, String.class);
}
异常获取
注解配置方式的实现非常简单,只需要在 fallback 实现方法的参数中增加 Throwable e 对象的定义,这样在方法内部就可以获取触发服务降级的具体异常内容了,比如:
@RestController
@RequestMapping("/hystrix")
public class HystrixConsumerController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/{param}")
@HystrixCommand(fallbackMethod = "showAppInfoFallback")
public String showAppInfo(@PathVariable("param") String param) {
throw new RuntimeException("showAppInfo fail");
}
/**
* 服务降级方法
*
* @param param 源方法传入的参数
* @param exception 接收到的异常信息
* @return 和源方法的返回值类型保持一致
*/
public String showAppInfoFallback(String param,Throwable exception) {
return param+"====" + exception.getMessage();
}
}
命令名称、分组以及线程划分
@HystrixCommand 注解的 CommandKey、groupKey 以及 threadPoolKey 属性即可设置命令名称、分组以及线程池划分,比如我们可以像下面这样进行设置:@GetMapping("/{param}")
@HystrixCommand(fallbackMethod = "showAppInfoFallback",
ignoreExceptions = {IOException.class, FileNotFoundException.class},
commandKey = "showAppInfo",
groupKey = "userGroup",
threadPoolKey = "showAppInfoThread"
)
public String showAppInfo(@PathVariable("param") String param) {
//通过RestTemplate查询用户信息 使用服务名代替ip和端口号
String url = "http://userservice/hystrix/" + param;
return restTemplate.getForObject(url, String.class);
}
默认情况下,Hystrix 会让相同组名的命令使用同一个线程池。
通常情况下,尽量通过 HystrixThreadPoolKey 的方式来指定线程池的划分,而不是通过组名的默认方式实现划分,因为多个不同的命令可能从业务逻辑上来看属于同一个组,但是往往从实现本身上需要跟其他命令进行隔离。
hystrix:
command:
showAppInfo:
execution:
isolation:
thread:
timeoutInMilliseconds: 1000
threadpool:
showAppInfoThread:
coreSize: 10
maxQueueSize: 100
queueSizeRejectionThreshold: 50
请求缓存
注解
描述
属性
@CacheResult该注解用来标记请求命令返回的结果应该被缓存,它必须与
@HystrixCommand 注解结合使用cacheKeyMethod
@CacheRemove该注解用来让请求命令的缓存失效,失效的缓存根据定义的 Key 决定
commandKey,cacheKeyMethod
@CacheKey该注解用来在请求命令的参数上标记,使其作为缓存的 Key 值,如果没有标注则会使用所有参数。如果同时还使用了
@CacheResult 和@CacheRemove 注解的 cacheKeyMethod 方法指定缓存 Key 的生成,那么该注解将不会起作用value
缓存生命周期
HystrixRequestContext.initializeContext(); 初始化开始, 到HystrixRequestContext#shudown();结束。初始化HystrixRequestContext
//初始化Hystrix请求上下文
@Cleanup("shutdown") HystrixRequestContext.initializeContext();
@Cleanup 是 lombok注解, 可以自动生成释放资源的代码,默认是调用资源的 close() 方法,也可以自己指定释放的方法@ServletComponentScan注解, 指定HystrixRequestContextServletFilter.java,实现Filter接口,在doFilter方法中添加方法1中的那一行代码,并在一次请求结束后关掉这个上下文@WebFilter(filterName = "hystrixRequestContextServletFilter",urlPatterns = "/*",asyncSupported = true)
public class HystrixRequestContextServletFilter implements Filter {
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
//初始化Hystrix请求上下文
HystrixRequestContext context = HystrixRequestContext.initializeContext(); try {
chain.doFilter(request, response);//请求正常通过
} finally {
context.shutdown();//关闭Hystrix请求上下文
}
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
} @Override
public void destroy() {
}
}@ServletComponentScan(basePackages = {"com.niit.order.filter"})
HystrixRequestContext.initializeContext();代码就可以去掉了.
HystrixRequestContext.initializeContext();进行初始化,每请求一次 controller 就会走一次 filter,上下文又会初始化一次,前面缓存的就失效了,又得重新来。所以如果测试缓存,得在 Controller 请求中多次调用那个加了缓存的 HystrixCommand 命令。
测试请求缓存
@CacheResult
public String sayHello(@CacheKey String msg) {
log.info("未走缓存 ↓");
return msg;
}
// fallback
public String hiConsumerFallBack(String msg) {
return "hi," + msg + ",sorry,error!";
}
/**
* 使用注解请求缓存 方式1
*
* @CacheResult 标记这是一个缓存方法,结果会被缓存
*/
@HystrixCommand
@CacheRemove(commandKey = "testCommand")
public String sayGoodbye(@CacheKey String msg) {
log.info("清除缓存 ↓");
return msg;
}
// test say hello
@RequestMapping(value = "/hello/test", method = RequestMethod.GET)
String sayHelloTest() {
// @Cleanup HystrixRequestContext hystrixRequestContext = HystrixRequestContext.initializeContext();
log.info(sayHello("123"));
log.info(sayHello("123"));
log.info(sayHello("321"));
log.info(sayGoodbye("123"));
log.info(sayHello("123"));
log.info(sayHello("321"));
return "测试";
}未走缓存 ↓
123
未走缓存 ↓
123
未走缓存 ↓
321
清除缓存 ↓
123
未走缓存 ↓
123
未走缓存 ↓
321@Slf4j
@Service("orderServiceCache")
public class OrderServiceCacheImpl {
// LOGGER
private static final Logger LOGGER = LoggerFactory.getLogger(OrderServiceCacheImpl.class);
@Resource
private RestTemplate restTemplate;
@CacheResult(cacheKeyMethod = "getMsg")
public String sayHello(@CacheKey String msg) {
log.info("未走缓存 ↓");
return msg;
}
@HystrixCommand
@CacheRemove(commandKey = "testCommand")
public String sayGoodbye(@CacheKey String msg) {
log.info("清除缓存 ↓");
return msg;
}
}@Slf4j // lombok
@RestController
@RequestMapping("/consumer")
public class HystrixConsumerController03 {
...
@RequestMapping(value = "/hello/test", method = RequestMethod.GET)
String sayHelloTest() {
// @Cleanup HystrixRequestContext hystrixRequestContext = HystrixRequestContext.initializeContext();
log.info(orderServiceCache.sayHello("123")); // 首次调用123不走缓存
log.info(orderServiceCache.sayHello("123")); // 第二次调用123走缓存
log.info(orderServiceCache.sayHello("321")); // 首次调用312不走缓存
log.info(orderServiceCache.sayGoodbye("123")); // 清除123缓存
log.info(orderServiceCache.sayHello("123")); // 123缓存已清除,不走缓存
log.info(orderServiceCache.sayHello("321")); // 321走缓存
return "测试";
}GET http://localhost:9010/consumer/hello/test未走缓存 ↓
123
123
未走缓存 ↓
321
清除缓存 ↓
123
未走缓存 ↓
123
321
使用cacheKeyMethod 定义缓存 Key
@Slf4j
@Service("orderServiceCache")
public class OrderServiceCacheImpl {
@Resource
private RestTemplate restTemplate;
/**
* 使用注解请求缓存 方式1
*
* @CacheResult 标记这是一个缓存方法,结果会被缓存
*/
@CacheResult(cacheKeyMethod = "getCacheKey")
@HystrixCommand(commandKey = "commandKey1")
public Integer openCacheByAnnotation1(Long id) {
//此次结果会被缓存
return restTemplate.getForObject("http://orderservice/hystrix/cache/randomInt", Integer.class);
}
/**
* 第一种方法没有使用@CacheKey注解,而是使用这个方法进行生成cacheKey的替换办法
* 这里有两点要特别注意:
* 1、这个方法的入参的类型必须与缓存方法的入参类型相同,如果不同被调用会报这个方法找不到的异常
* 2、这个方法的返回值一定是String类型
*/
public String getCacheKey(Long id) {
return String.valueOf(id);
}
}缓存清理
@CacheResult 注解将请求结果置入 Hystrix 的请求缓存之中。如果对缓存的内容进行修改或者删除操作,那么此时请求缓存中的结果与实际结果就会产生不一致(缓存中的结果实际上己经过期失效了),所以我们需要在这类操作上对失效的缓存进行清理。在 Hystrix 的注解配置中, 可以通注解来实现失效缓存的清理,因此需要再加上这个方法:
/**
* 使用注解清除缓存 方式1
*
* @CacheRemove 必须指定commandKey才能进行清除指定缓存
*/
@CacheRemove(commandKey = "commandKey1", cacheKeyMethod = "getCacheKey")
@HystrixCommand
public void flushCacheByAnnotation1(Long id) {
log.info("请求缓存已清空!");
//这个@CacheRemove注解直接用在更新方法上效果更好
}
@CacheRemove 注解的 commandKey 属性是必须要指定的,它用来指明需要使用请求缓存的请求命令,因为只有通过该属性的配置,Hystrix 才能找到正确的请求命令缓存位置。@RestController
@RequestMapping("/hystrix/cache/")
public class HystrixCacheTest {
// LOGGER
private static final Logger LOGGER = LoggerFactory.getLogger(HystrixCacheTest.class);
@Resource
private OrderServiceCacheImpl service;
/**
* 为了请求测试Hystrix请求缓存提供的返回随机数的接口
*/
@GetMapping("randomInt")
public Integer getRandomInteger(){
Random random = new Random();
int randomInt = random.nextInt(99999);
return randomInt;
}
/**
* 注解方式请求缓存,第一种
*/
@GetMapping("/cacheAnnotation1")
public void openCacheByAnnotation1(){
//初始化Hystrix请求上下文, 也可以添加过滤器
// HystrixRequestContext.initializeContext();
//访问并开启缓存
Integer result1 = service.openCacheByAnnotation1(1L);
Integer result2 = service.openCacheByAnnotation1(1L);
LOGGER.info("first request result is:{} ,and secend request result is: {}", result1, result2);
//清除缓存
service.flushCacheByAnnotation1(1L);
//再一次访问并开启缓存
Integer result3 = service.openCacheByAnnotation1(1L);
Integer result4 = service.openCacheByAnnotation1(1L);
LOGGER.info("first request result is:{} ,and secend request result is: {}", result3, result4);
}
}
GET http://localhost:9010/hystrix/cache/cacheAnnotation1
请求合并
10 毫秒)内对同一依赖服务的多个请求进行整合并以批量方式 发起请求的功能(服务提供方也需要提供相应的批量实现接口)。通过 HystrixCollapser 的封装,开发者不需要关注线程合并的细节过程,只需关注批量化服务和处理。
在快速入门的例子中,我们使用@HystrixCommand 注解优雅地实现了 HystrixCommand 的定义,那么对于请求合并器是否也可以通过注解来定义呢?答案是肯定的!
使用注解进行请求合并
@GetMapping("/findUser/{id}")
@HystrixCollapser(
batchMethod = "findAll", collapserProperties = {
@HystrixProperty(name = "timerDelayInMilliseconds", value = "100")
})
public User findUser(@PathVariable("id") String id) {
return null;
}
@HystrixCommand()
public List<User> findAll(List<String> ids) {
User[] forObject = restTemplate.getForObject("http://userservice/user/findAll/{1}", User[].class, StringUtils.join(ids, ","));
assert forObject != null;
return Arrays.asList(forObject);
}
@HystrixCommand 了,可以看到,这里通过它定义了两个 Hystrix 命令,一个用于请求/users/{id} 接口,一个用于请求/users?ids={ids}接口。/users/{id}接口的方法上通过@HystrixCollapser 注解为其创建了合并请求器,通过 batchMethod 属性指定了批量请求的实现方法为 findAll 方法(即请求 /users?ids={ids}接口的命令),同时通过 collapserProperties 属性为合并请求器设置了相关属性,这里使用@HystrixProperty(name=”timerDelayInMilliseconds”, value = ”100”)将合并时间窗设置为 100 毫秒。/users/{id}依赖服务之前设置了一个批量请求合并器。GET http://localhost:9010/consumer/findUser/1
GET http://localhost:9010/consumer/findUser/2
GET http://localhost:9010/consumer/findUser/3
GET http://localhost:9010/consumer/findUser/4
GET http://localhost:9010/consumer/findUser/5
GET http://localhost:9010/consumer/findUser/6请求合并的ID: [1]
请求合并的ID: [2]
请求合并的ID: [3]
请求合并的ID: [4]
请求合并的ID: [5]
请求合并的ID: [6]为什么请求合并没有生效
@HystrixCollapser中有scope属性,scope的取值为REQUEST, GLOBAL。
REQUEST范围只对一个request请求内的多次服务请求进行合并GLOBAL是多单个应用中的所有线程的请求中的多次服务请求进行合并。@RestController
@RequestMapping("/consumer")
public class HystrixConsumerController04 {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/findUser/{id}")
@HystrixCollapser(
batchMethod = "findAll", scope = com.netflix.hystrix.HystrixCollapser.Scope.GLOBAL ,collapserProperties = {
@HystrixProperty(name = "timerDelayInMilliseconds", value = "3000"),
@HystrixProperty( name = "maxRequestsInBatch", value = "100")
})
public User findUser(@PathVariable("id") String id) {
return null;
}
@HystrixCommand
public List<User> findAll(List<String> ids) {
System.out.println("请求合并的ID: " + ids);
User[] forObject = restTemplate.getForObject("http://userservice/user/findAll/{1}", User[].class, StringUtils.join(ids, ","));
assert forObject != null;
return Arrays.asList(forObject);
} public static void main(String[] args) throws InterruptedException, IOException {
CloseableHttpClient httpClient = HttpClients.custom().setMaxConnPerRoute(100).build();
String url = "http://localhost:9010/consumer/findUser/";
ExecutorService executorService = Executors.newFixedThreadPool(10);
int requestCount = 6;
for(int i = 1;i <= requestCount;i++){
final int id = i;
executorService.execute(() -> {
try{
HttpGet httpGet = new HttpGet(url + id);
httpGet.addHeader("Content-Type", "application/json; charset=UTF-8");
HttpResponse response = httpClient.execute(httpGet);
HttpEntity entity = response.getEntity();
InputStream inputStream = entity.getContent();
InputStreamReader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
BufferedReader bufferedReader = new BufferedReader(reader);
String line;
StringBuilder stringBuilder = new StringBuilder();
while ((line = bufferedReader.readLine()) != null) {
stringBuilder.append(line);
}
String responseBody = stringBuilder.toString();
System.out.println(responseBody);
}catch (Exception e){
e.printStackTrace();
}
});
}
Thread.sleep(1000*10);
executorService.shutdown();
httpClient.close();
}请求合并的ID: [2, 6, 5, 3, 1, 4]{"id":6,"username":"范兵兵","address":"山东省青岛市","serverPort":"9020"}
{"id":2,"username":"文二狗","address":"陕西省西安市","serverPort":"9020"}
{"id":5,"username":"郑爽爽","address":"辽宁省沈阳市大东区","serverPort":"9020"}
{"id":1,"username":"柳岩","address":"湖南省衡阳市","serverPort":"9020"}
{"id":4,"username":"张必沉","address":"天津市","serverPort":"9020"}
{"id":3,"username":"华沉鱼","address":"湖北省十堰市","serverPort":"9020"}
...
请求合并的额外开销
属性详解
@HystrixCommand(fallbackMethod = "fallbackMethod", groupKey = "strGroupCommand", commandKey = "strCommand", threadPoolKey = "strThreadPool",
commandProperties = {
// 设置隔离策略,THREAD 表示线程池 SEMAPHORE:信号池隔离
@HystrixProperty(name = "execution.isolation.strategy", value = "THREAD"),
// 当隔离策略选择信号池隔离的时候,用来设置信号池的大小(最大并发数)
@HystrixProperty(name = "execution.isolation.semaphore.maxConcurrentRequests", value = "10"),
// 配置命令执行的超时时间
@HystrixProperty(name = "execution.isolation.thread.timeoutinMilliseconds", value = "10"),
// 是否启用超时时间
@HystrixProperty(name = "execution.timeout.enabled", value = "true"),
// 执行超时的时候是否中断
@HystrixProperty(name = "execution.isolation.thread.interruptOnTimeout", value = "true"),
// 执行被取消的时候是否中断
@HystrixProperty(name = "execution.isolation.thread.interruptOnCancel", value = "true"),
// 允许回调方法执行的最大并发数
@HystrixProperty(name = "fallback.isolation.semaphore.maxConcurrentRequests", value = "10"),
// 服务降级是否启用,是否执行回调函数
@HystrixProperty(name = "fallback.enabled", value = "true"),
// 是否启用断路器
@HystrixProperty(name = "circuitBreaker.enabled", value = "true"),
// 该属性用来设置在滚动时间窗中,断路器熔断的最小请求数。例如,默认该值为 20 的时候,如果滚动时间窗(默认10秒)内仅收到了19个请求, 即使这19个请求都失败了,断路器也不会打开。
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "20"),
// 该属性用来设置在滚动时间窗中,表示在滚动时间窗中,在请求数量超过 circuitBreaker.requestVolumeThreshold 的情况下,如果错误请求数的百分比超过50, 就把断路器设置为 "打开" 状态,否则就设置为 "关闭" 状态。
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "50"),
// 该属性用来设置当断路器打开之后的休眠时间窗。 休眠时间窗结束之后,会将断路器置为 "半开" 状态,尝试熔断的请求命令,如果依然失败就将断路器继续设置为 "打开" 状态,如果成功就设置为 "关闭" 状态。
@HystrixProperty(name = "circuitBreaker.sleepWindowinMilliseconds", value = "5000"),
// 断路器强制打开
@HystrixProperty(name = "circuitBreaker.forceOpen", value = "false"),
// 断路器强制关闭
@HystrixProperty(name = "circuitBreaker.forceClosed", value = "false"),
// 滚动时间窗设置,该时间用于断路器判断健康度时需要收集信息的持续时间
@HystrixProperty(name = "metrics.rollingStats.timeinMilliseconds", value = "10000"),
// 该属性用来设置滚动时间窗统计指标信息时划分"桶"的数量,断路器在收集指标信息的时候会根据设置的时间窗长度拆分成多个 "桶" 来累计各度量值,每个"桶"记录了一段时间内的采集指标。
// 比如 10 秒内拆分成 10 个"桶"收集这样,所以 timeinMilliseconds 必须能被 numBuckets 整除。否则会抛异常
@HystrixProperty(name = "metrics.rollingStats.numBuckets", value = "10"),
// 该属性用来设置对命令执行的延迟是否使用百分位数来跟踪和计算。如果设置为 false, 那么所有的概要统计都将返回 -1。
@HystrixProperty(name = "metrics.rollingPercentile.enabled", value = "false"),
// 该属性用来设置百分位统计的滚动窗口的持续时间,单位为毫秒。
@HystrixProperty(name = "metrics.rollingPercentile.timeInMilliseconds", value = "60000"),
// 该属性用来设置百分位统计滚动窗口中使用 “ 桶 ”的数量。
@HystrixProperty(name = "metrics.rollingPercentile.numBuckets", value = "60000"),
// 该属性用来设置在执行过程中每个 “桶” 中保留的最大执行次数。如果在滚动时间窗内发生超过该设定值的执行次数,
// 就从最初的位置开始重写。例如,将该值设置为100, 滚动窗口为10秒,若在10秒内一个 “桶 ”中发生了500次执行,
// 那么该 “桶” 中只保留 最后的100次执行的统计。另外,增加该值的大小将会增加内存量的消耗,并增加排序百分位数所需的计算时间。
@HystrixProperty(name = "metrics.rollingPercentile.bucketSize", value = "100"),
// 该属性用来设置采集影响断路器状态的健康快照(请求的成功、 错误百分比)的间隔等待时间。
@HystrixProperty(name = "metrics.healthSnapshot.intervalinMilliseconds", value = "500"),
// 是否开启请求缓存
@HystrixProperty(name = "requestCache.enabled", value = "true"),
// HystrixCommand的执行和事件是否打印日志到 HystrixRequestLog 中
@HystrixProperty(name = "requestLog.enabled", value = "true"),
},
threadPoolProperties = {
// 该参数用来设置执行命令线程池的核心线程数,该值也就是命令执行的最大并发量
@HystrixProperty(name = "coreSize", value = "10"),
// 该参数用来设置线程池的最大队列大小。当设置为 -1 时,线程池将使用 SynchronousQueue 实现的队列,否则将使用 LinkedBlockingQueue 实现的队列。
@HystrixProperty(name = "maxQueueSize", value = "-1"),
// 该参数用来为队列设置拒绝阈值。 通过该参数, 即使队列没有达到最大值也能拒绝请求。
// 该参数主要是对 LinkedBlockingQueue 队列的补充,因为 LinkedBlockingQueue 队列不能动态修改它的对象大小,而通过该属性就可以调整拒绝请求的队列大小了。
@HystrixProperty(name = "queueSizeRejectionThreshold", value = "5"),
}
)
public String strConsumer() {
ResponseEntity<String> result = restTemplate.getForEntity("http://cloud-eureka-client/hello", String.class);
return result.getBody();
}
@HystrixCommand 中的 commandProperties 属性来设置
commandProperties 属性
1.execution 配置
execution 配置控制的是 HystrixCommand.run()的执行。execution.isolation.strategy:该属性用来设置 HystrixCommand. run ()执行的隔离策略,它有如下两个选项:
THREAD:通过线程池隔离的策略。它在独立的线程上执行,并且它的并发限制受线程池中线程数量的限制。SEMAPHORE:通过信号量隔离的策略。它在调用线程上执行,并且它的并发限制受信号量计数的限制。
属性级别
默认值、配置方式、配置属性
全局默认值
THREAD
全局配置属性
hystrix.command.default.execution.isolation.strategy
实例默认值
可通过注解设置:
@HystrixProperty(name="execution.isolation.strategy",value="THREAD")
实例配置属性
hystrix.command.[HystrixCommandKey].execution.isolation.strategy
execution.isolation.thread,timeoutInMilliseconds:该属性用来配置 HystrixCommand 执行的超时时间,单位为毫秒。当 HystrixCommand 执行时间超过该配置值之后,Hystrix 会将该执行命令标记为 TIMEOUT 并进入服务降级处理逻辑。
属性级别
默认值、配置方式、配置属性
全局配置属性
hystrix.command.default.execution<br>.isolation.thread.timeoutInMilliseconds
实例默认值
可通过注解设置, 例如
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="1000")
实例配置属性
hystrix.command.[HystrixCommandKey].execution<br>.isolation.thread.timeoutInMilliseconds
execution.timeout.enabled:该属性用来配置 HystrixCommand.run()的执行是否启用超时时间。默认为 true,如果设置为 false,那么 execution.Isolation.thread.timeoutInMilliseconds 属性的配置将不再起作用。
属性级别
默认值、配置方式、配置属性
全局默认值
true
全局配置属性
hystrix.command.default.execution.timeout.enabled
实例默认值
可通过
@HystrixProperty(name="execution.timeout.enabled",value="false")注解设置
实例配置属性
hystrix.command.[HystrixCommandKey].execution.timeout.enabled
execution.isolation.thread.interruptOnTimeout:该属性用来配置当 HystrixCommand.run()执行超时的时候是否要将它中断
属性级别
默认值、配置方式、配置属性
全局默认值
true
全局配置属性
hystrix.command.default.execution.isolation.thread.interruptOnTimeout
实例默认值
可通过
@HystrixProperty(name="execution.isolation.thread.interruptOnTimeout",value="false")注解设置
实例配置属性
hystrix.command.[HystrixCommandKey].execution.isolation.thread.interruptOnTimeout
execution.Isolation.semaphore.maxConcurrentRequests: 当 HystrixCommand 的隔离策略使用信号量的时候,该属性用来配置信号量的大小(并发请求数)。当最大并发请求数达到该设置值时,后续的请求将会被拒绝。
属性级别
默认值、配置方式、配置属性
全局默认值
10
全局配置属性
hystrix.command.default.execution.isolation.semaphore.maxConcurrentRequests
实例默认值
可通过注解设置:
@HystrixProperty(name="execution.isolation.semaphore.maxConcurrentRequests",value="2")
实例配置属性
hystrix.command.[HystrixCommandKey].execution.isolation.semaphore.maxConcurrentRequests
2.fallback 配置
fallback.isolation.semaphore.maxConcurrentRequests:该属性用来设置从调用线程中允许 HystrixCommand.getFallback()方法执行的最大并发请求数。当达到最大并发请求数时,后续的请求将会被拒绝并抛出异常(因为它己经没有后续的 fallback 可以被调用了)。
属性级别
默认值、配置方式、配置属性
全局默认值
10
全局配置属性
hystrix.command.default.fallback.isolation.semaphore.maxConcurrentRequests
实例默认值
可通过注解设置:
@HystrixProperty(name="fallback.isolation.semaphore.maxConcurrentRequests",value="20")
实例配置属性
hystrix.command.HystrixCommandKey.fallback.isolation.semaphore.maxConcurrentRequests
属性级别
默认值、配置方式、配置属性
全局默认值
true
全局配置属性
hystrix.command.default.fallback.enabled
实例默认值
可通过注解设置
@HystrixProperty(name="fallback.enabled",value="false")
实例配置属性
hystrix.command.[HystrixCommandKey].fallback.enabled
3.circuitBreaker 配置
circuitBreaker.enabled:该属性用来确定当服务请求命令失败时,是否使用断路器来跟踪其健康指标和熔断请求。
属性级别
默认值、配置方式、配置属性
全局默认值
true
全局配置属性
hystrix.command.default.circuitBreaker.enabled
实例默认值
可通过注解设置:
@HystrixProperty(name="circuitBreaker.enabled",value="false")
实例配置属性
hystrix.command.[HystrixCommandKey].circuitBreaker.enabled
circuitBreaker.requestVolumeThreshold:该属性用来设置在滚动时间窗中,断路器熔断的最小请求数。例如,默认该值为 20 的时候,如果滚动时间窗(默 认 10 秒)内仅收到了 19 个请求,即使这 19 个请求都失败了,断路器也不会打开。
属性级别
默认值、配置方式、配置属性
全局默认值
20
全局配置属性
hystrix.command.default.circuitBreaker.requestVolumeThreshold
实例默认值
可通过注解设置:
@HystrixProperty(name="circuitBreaker.requestVolumeThreshold",value="30")
实例配置属性
hystrix.command.HystrixCommandKey.circuitBreaker.requestVolumeThreshold
circuitBreaker.sleepWindowInMilliseconds:该属性用来设置当断路器 打开之后的休眠时间窗。休眠时间窗结束之后,会将断路器置为“半幵”状态,尝试熔断的请求命令,如果依然失败就将断路器继续设置为“打开”状态,如果成功 就设置为“关闭”状态。
属性级别
默认值、配置方式、配置属性
全局默认值
5000
全局配置属性
实例默认值
可通过
@HystrixProperty(name="circuitBreaker.sleepWindowInMilliseconds",value="3000")注解设置
实例配置属性
hystrix.command.[HystrixCommandKey].circuitBreaker.sleepWindowInMilliseconds
circuitBreaker.errorThresholdPercentage:该属性用来设置断路器打开的错误百分比条件。例如,默认值为 50%的情况下,表示在滚动时间窗中,在请求数量超过 circuitBreaker.requestVolumeThreshold 阈值的前提下,如果错误请求数的百分比超过 50,就把断路器设置为“打开”状态,否则就设置为“关闭”状态。
属性级别
默认值、配置方式、配置属性
全局默认值
50
全局配置属性
hystrix.command.default.circuitBreaker.errorThresholdPercentage
实例默认值
可通过
@HystrixProperty(name="circuitBreaker.errorThresholdPercentage",value="40")注解设置
实例配置属性
hystrix.command.[HystrixCommandKey].circuitBreaker.errorThresholdPercentage
属性级别
默认值、配置方式、配置属性
全局默认值
false
全局配置属性
hystrix.command.default.circuitBreaker.forceOpen
实例默认值
可通过注解设置:
@HystrixProperty(name="circuitBreaker.forceOpen",value="true")
实例配置属性
hystrix.command.[HystrixCommandKey].circuitBreaker.forceOpen
circuitBreaker. forceClosed:如果将该属性设置为 true,断路器将强制进入 “关闭”状态,它会接收所有请求。如果 circuitBreaker.forceOpen 属性为 true,该属性不会生效。
属性级别
默认值、配置方式、配置属性
全局默认值
false
全局配置属性
hystrix.command.default.circuitBreaker.forceClosed
实例默认值
可通过注解设置:
@HystrixProperty(name="circuitBreaker.forceClosed",value="true")
实例配置属性
hystrix.command.[HystrixCommandKey].circuitBreaker.forceClosed
4.metrics 配置
metrics.rollingstats.timelnMilliseconds:该属性用来设置滚动时间窗的长度,单位为毫秒。该时间用于断路器判断健康度时需要收集信息的持续时间。断路器在收集指标信息的时候会根据设置的时间窗长度拆分成多个“桶”来累计各度量值,每个“桶”记录了一段时间内的采集指标。例如,当采用默认值 10000 毫 秒时,断路器默认将其拆分成 10 个桶(桶的数量也可通过 metrics.rollingStats.numBuckets 参数设置),每个桶记录 1000 毫秒内的指标信息。
属性级别
默认值、配置方式、配置属性
全局默认值
10000
全局配置属性
hystrix.command.default.metrics.rollingStats.timeInMilliseconds
实例默认值
可通过注解设置:
@HystrixProperty(name="metrics.rollingStats.timeInMilliseconds",value="20000")
实例配置属性
hystrix.command.[HystrixCommandKey].metrics.rollingStats.timeInMilliseconds
metrics.rollingStats.numBuckets:该属性用来设置滚动时间窗统计指标信息时划分“桶”的数量。
属性级别
默认值、配置方式、配置属性
全局默认值
10
全局配置属性
hystrix.command.default.metrics.rollingStats.numBuckets
实例默认值
可通过注解设置
@HystrixProperty(name="metrics.rollingStats.numBuckets",value="20")
实例配置属性
hystrix.command.[HystrixCommandKey].metrics.rollingStats.numBuckets
metrics.rollingStats.timeInMilliseconds 参数的设置必须能够被 metrics.rollingStats.numBuckets 参数整除,不然将拋出异常。该参数与 metrics.rollingStats.timelnMilliseconds —样,从 Hystrix 1.4.12 版本开始(Brixton.SR5 版本中使用了 1.5.3 版本),只有在应用初始化的时候生效,通过动态刷新配置不会产生效果,这样做是为了避免出现运行期监测数据丢失的情况。
metrics.rollingPercentile.enabled:该属性用来设置对命令执行的延迟是否使用百分位数来跟踪和计算。如果设置为 false,那么所有的概要统计都将返回-1
属性级别
默认值、配置方式、配置属性
全局默认值
true
全局配置属性
hystrix.command.default.metrics.rollingPercentile.enabled
实例默认值
可通过注解设置
@HystrixProperty(name="metrics.rollingPercentile.enabled",value="false")
实例配置属性
hystrix.command.[HystrixCommandKey].metrics.rollingPercentile.enabled
metrics.rollingPercentile.timelnMilliseconds:该属性用来设置百分位统计的滚动窗口的持续时间,单位为毫秒。
属性级别
默认值、配置方式、配置属性
全局默认值
60000
全局配置属性
hystrix.command.default.metrics.rollingPercentile.timelnMilliseconds
实例默认值
可通过注解设置
@HystrixProperty(name="metrics.rollingPercentile.timelnMilliseconds",value="50000")
实例配置属性
hystrix.command.[HystrixCommandKey].metrics.rollingPercentile.timelnMilliseconds
metrics.rollingPercentile.numBuckets:该属性用来设置百分位统计滚动窗口中使用“桶”的数量。
属性级别
默认值、配置方式、配置属性
全局默认值
6
全局配置属性
hystrix.command.default.metrics.rollingPercentile.numBuckets
实例默认值
可通过注解设置
@HystrixProperty(name="metrics.rollingPercentile.numBuckets",value="5")
实例配置属性
hystrix.command.[HystrixCommandKey].metrics.rollingPercentile.numBuckets
metrics.rollingPercentile.timelnMilliseconds 参数的设置必须能够被 metrics.rollingPercentile.numBuckets 参数整除,不然将会抛出异常。该属性从 Hystrix 1.4.12 版本开始(Brixton.SR5 版本中使用了 1.5.3 版本),只有在应用初始化的时候生效,通过动态刷新配置不会产生效果,这样做 是为了避免出现运行期监测数据丢失的情况。
metrics.rollingPercentile.bucketSize:该属性用来设置在执行过程中 每个“桶”中保留的最大执行次数。如果在滚动时间窗内发生超过该设定值的执行次数,就从最初的位置开始重写。例如,将该值设置为 100,滚动窗口为 10 秒,若在 10 秒内一个“桶”中发生了 500 次执行,那么该“桶”中只保留最后的 100 次执行的统计。另外,增加该值的大小将会增加内存量的消耗,并增加排序百分位数所需的计算时间。
属性级别
默认值、配置方式、配置属性
全局默认值
100
全局配置属性
hystrix.command.default.metrics.rollingPercentile.bucketSize
实例默认值
可通过注解设置
@HystrixProperty(name="metrics.rollingPercentile.bucketSize",value="120")
实例配置属性
hystrix.command.[HystrixCommandKey].metrics.rollingPercentile.bucketSize
该属性从 Hystrix 1.4.12 版本开始(Brixton.SR5 版本中使用了 L5.3 版本),只有在应用初始化的时候生效,通过动态刷新配置不会产生效果,这样做是为了避免出现运行期监测数据丢失的情况。
metrics.healthSnapshot.intervalInMilliseconds:该属性用来设置采集影响断路器状态的健康快照(请求的成功、错误百分比)的间隔等待时间。
属性级别
默认值、配置方式、配置属性
全局默认值
500
全局配置属性
hystrix.command.default.metrics.healthSnapshot.intervalInMilliseconds
实例默认值
可通过注解设置
@HystrixProperty(name="metrics.healthSnapshot.intervalInMilliseconds",value="600")
实例配置属性
hystrix.command.[HystrixCommandKey].metrics.healthSnapshot.intervalInMilliseconds
5.requestContext 配置
requestCache.enabled:此属性用来配置是否开启请求缓存。
属性级别
默认值、配置方式、配置属性
全局默认值
true
全局配置属性
hystrix.command.default.requestCache.enabled
实例默认值
可通过注解设置
@HystrixProperty(name="requestCache.enabled",value="false")
实例配置属性
h
ystrix.command.[HystrixCommandKey].requestCache.enabled
requestLog.enabled:该属性用来设置 HystrixCommand 的执行和事件是否打印日志到 HystrixRequestLog 中。@HystrixCollapser 中的 collapserProperties 属性来设置:@HystrixCollapser (batchMethod ="batch1",collapserProperties ={
@HystrixProperty(name="timerDelayInMilliseconds", value ="20")
})
maxRequestsInBatch:该参数用来设置一次请求合并批处理中允许的最大请求数
属性级别
默认值、配置方式、配置属性
全局默认值
Integer.MAX_VALUE
全局配置属性
hystrix.collapser.default.maxRequestsInBatch
实例默认值
可通过注解设置
@HystrixProperty(name="maxRequestsInBatch",value="100000")
实例配置属性
hystrix.collapser.[HystrixCollapserKey].maxRequestsInBatch
timerDelaylnMilliseconds:该参数用来设置批处理过程中每个命令延迟的时间,单位为毫秒。
属性级别
默认值、配置方式、配置属性
全局默认值
10
全局配置属性
hystrix.collapser.default.timerDelaylnMilliseconds
实例默认值
可通过注解设置
@HystrixProperty(name="timerDelaylnMilliseconds",value="20")
实例配置属性
hystrix.collapser.[HystrixCollapserKey].timerDelaylnMilliseconds
threadPool 属性
@HystrixCommand 中的 threadPoolProperties 属性来设置,比如:@HystrixCommand(fallbackMethod ="helloFallback" commandKey = "helloKey",threadPoolProperties = {
@HystrixProperty(name="coreSize",value ="20")
})
coreSize:该参数用来设置执行命令线程池的核心线程数,该值也就是命令执行的最大并发量。
属性级别
默认值、配置方式、配置属性
全局默认值
10
全局配置属性
hystrix.threadpool.default.coreSize
实例默认值
可通过注解设置
@HystrixProperty(name="coreSize",value="16")
实例配置属性
hystrix.threadpool.[hystrixThreadPoolKey].coreSize
maxQueueSize:该参数用来设置线程池的最大队列大小。当设置为-1 时,线程池将使用 SynchronousQueue 实现的队列,否则将使用 LinkedBlockingQueue 实现的队列。
属性级别
默认值、配置方式、配置属性
全局默认值
-1
全局配置属性
hystrix.threadpool.default.maxQueueSize
实例默认值
可通过注解设置
@HystrixProperty(name="maxQueueSize",value="10")
实例配置属性
hystrix.threadpool.[hystrixThreadPoolKey].maxQueueSize
queueSizeRejectionThreshold:该参数用来为队列设置拒绝阈值。通过该参数,即使队列没有达到最大值也能拒绝请求。该参数主要是对 LinkedBlocking- Queue 队列的补充,因为 LinkedBlockingQueue 队列不能动态修改它的对象大小,而通过该属性就可以调整拒绝请求的队列大小了。
属性级别
默认值、配置方式、配置属性
全局默认值
5
全局配置属性
hystrix.threadpool.default.queueSizeRejectionThreshold
实例默认值
可通过注解设置
@HystrixProperty(name="queueSizeRejectionThreshold",value="10")
实例配置属性
hystrix.threadpool.[hystrixThreadPoolKey].queueSizeRejectionThreshold
当 maxQueueSize 属性为-1 的时候,该属性不会生效。
Hystrix 仪表盘
<!--引入Hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!--Hystrix仪表盘-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
<!--actuator端点-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
server:
port: 2022
http://localhost:2022/hystrix。可以看到如下页面:

http://turbine-hostname:port/turbine.stream 开启,实现对默认集群的监控。http://turbine-hostname:port/turbine.stream?cluster=[clusterName]开启,实现对 clusterName 集群的监控。http://hystrix-app:port/hystrix.stream 开启,实现对具体某个服务实例的监控。
pom.xml 中的 dependencies 节点中新增 spring-boot-starter-actuator 监控模块以开启监控相关的端点,并确保己经引入断路器的依赖 spring-cloud-starter-hystrix:<!--引入Hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!--actuator端点-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
@EnableCircuitBreaker 注解,开启了断路器功能。#暴露全部的监控信息
management:
endpoints:
web:
exposure:
include: "*"
management.endpoints.web.exposure.include 属性来开启所有的端点,也可以通过 management.endpoints.web.exposure.exclude 属性来关闭某些端点。具体的端点信息可以参考 Spring Boot 的官方文档。"*" 表示开启所有端点,也可以使用具体的端点名称,比如 "hystrix.stream"。
Delay:该参数控制服务器上轮询监控信息的延迟时间,默认为 2000 毫秒,可以通过配置该属性来降低客户端的网络和 CPU 消耗。Title:该参数对应了下图头部标题 Hystrix Stream 之后的内容,默认会使用具体监控实例的 URL,可以通过配置该信息来展示更合适的标题。



Turbine 集群监控
构建监控聚合服务
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-turbine</artifactId>
<!--它默认依赖的是EurekaClient,需要排除,随后引入Nacos相关依赖即可-->
<exclusions>
<exclusion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
<!--引入Nacos Client即可-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
@EnableTurbine 注解幵启 Turbine。@SpringBootApplication
@EnableHystrixDashboard
@EnableTurbine
public class TurbineApplication {
public static void main(String[] args) {
SpringApplication.run(TurbineApplication.class,args);
}
}
server:
port: 2023
spring:
application:
name: turbine
cloud:
nacos:
server-addr: localhost:8848 #nacos 服务端地址
turbine:
app-config: orderservice,userservice #监控服务列表
aggregator:
cluster-config: default #聚合集群名称
cluster-name-expression: new String('default') #聚合集群名称
combine-host-port: true
hystrix:
dashboard:
proxy-stream-allow-list:
- "localhost"
#hystrix dashboard用,actuator暴露端口
management:
endpoints:
web:
exposure:
include: "hystrix.stream,turbine.stream"
cors:
allowed-origins: "*"
allowed-methods: "*"
turbine.app-config: 指定需要监控的服务列表,多个服务之间用逗号分隔。turbine.cluster-name-expression : 指定聚合集群名称, 该
参数指定了集群名称为 default,当服务数量非常多的时候,可以启动多个 Turbine 服务来构建不同的聚合集群,而该参数可以用来区分这些不同的聚合集群,同时该参数值可以在 Hystrix 仪表盘中用来定位不同的聚合集群,只需在 Hystrix Stream 的 URL 中通过 cluster 参数来指定;turbine.combine-host-port: 此参数设置为 true 可以让同一主机上的服务通过主机名与端口号的组合来进行区分,默认情况下会以 host 来区分不同的服务,这 会使得在本地调试的时候,本机上的不同服务聚合成一个服务来统计。
http://localhost:2023/turbine.stream 的监控,我们可以看到如下页面:

监控面板
通过actuator端点产生统计信息
#暴露全部的监控信息
management:
endpoints:
web:
exposure:
include: "*"
management.endpoints.web.exposure.include 属性来开启所有的端点,也可以通过 management.endpoints.web.exposure.exclude 属性来关闭某些端点。具体的端点信息可以参考 Spring Boot 的官方文档。"*" 表示开启所有端点,也可以使用具体的端点名称,比如 "hystrix.stream"。@Configuration
public class OrderServiceConfiguration {
@Bean
public ServletRegistrationBean getServlet(){
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean<HystrixMetricsStreamServlet> registrationBean = new ServletRegistrationBean<>(streamServlet);
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/actuator/hystrix.stream");
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}
}http://localhost:9010/actuator/hystrix.stream
创建turbine项目聚合统计信息
<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>my-turbine</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>my-turbine</name>
<description>my-turbine</description>
<parent>
<groupId>com.niit</groupId>
<artifactId>chapter05</artifactId>
<version>1.0</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-turbine</artifactId>
<!--它默认依赖的是EurekaClient,需要排除,随后引入Nacos相关依赖即可-->
<exclusions>
<exclusion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--引入Nacos Client即可-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
</project>spring-cloud-starter-alibaba-nacos-discovery依赖.@SpringBootApplication
@EnableHystrix
@EnableTurbine // 开启turbine, 用于聚合监控数据从而实现集群监控
public class MyTurbineApplication {
public static void main(String[] args) {
SpringApplication.run(MyTurbineApplication.class, args);
}
}
server:
port: 2023
spring:
application:
name: turbine
cloud:
nacos:
server-addr: localhost:8848 #nacos 服务端地址
discovery:
namespace: a52af3ff-ee9b-4c83-b6af-1e9fe53258e4 # 处于同一个命名空间,才能进行监控
ephemeral: false
turbine:
cluster-name-expression: new String('default') #聚合集群名称, default 为默认值
aggregator: #聚合器
# 指定聚合哪些集群,多个使用","分割,默认为default。可使用http://.../turbine.stream?cluster={clusterConfig之一}访问
cluster-config: default #聚合集群名称, default 为默认值
app-config: orderservice,userservice #监控服务列表, 需要从nacos中获取
combine-host-port: true #是否将主机名和端口号组合在一起来区分不同的实例, 默认仅使用主机名区分
http://localhost:2023/turbine.stream:
创建dashboard项目实现监控
<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>my-dashboard</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>my-dashboard</name>
<description>my-dashboard</description>
<parent>
<groupId>com.niit</groupId>
<artifactId>chapter05</artifactId>
<version>1.0</version>
</parent>
<dependencies>
<!--引入Hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!--Hystrix仪表盘-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
</dependencies>
</project>
@SpringBootApplication
@EnableHystrixDashboard
public class MyDashboardApplication {
public static void main(String[] args) {
SpringApplication.run(MyDashboardApplication.class, args);
}
}server:
port: 2022
hystrix:
dashboard:
proxy-stream-allow-list: '*' #允许任何 IP 地址访问仪表板

http://hystrix-app:port/hystrix.stream 开启,实现对具体某个服务实例的监控。http://turbine-hostname:port/turbine.stream?cluster=[clusterName]开启,实现对 clusterName 集群的监控。
监控单体项目:
http://localhost:9010/actuator/hystrix.stream
监控聚合项目:
http://localhost:2023/turbine.stream, 点击Monitor Stream按钮开始监控:
解读监控内容:




SpringCloud与微服务-第4章-客户端负载均衡(Ribbon)
第 4 章 客户端负载均衡(Ribbon)
负载均衡
@Resource
private DiscoveryClient discoveryClient;//查找服务
@GetMapping("/info/{sid}")
public ResponseEntity<List<Course>> getCourseBySid(@PathVariable Integer sid){
ServiceInstance serviceInstance = discoveryClient.getInstances("mycourse").get(0);
//拼接url
String url = "http://"+serviceInstance.getHost()+":"+serviceInstance.getPort();
Student student = this.studentService.queryById(sid);
//使用服务名方式获取
Subscribe[] subscribes = restTemplate.getForObject(url+"/subscribe/info/"+sid,Subscribe[].class);
List<Subscribe> subscribeList = Arrays.asList(subscribes);
//Subscribe subscribe = restTemplate.getForObject("http://localhost:8201/subscribe/info/"+sid,Subscribe.class);
//resutl list
List<Course> resultList = new ArrayList();
////使用服务名方式获取
for (Subscribe subscribe : subscribeList) {
Course course = restTemplate.getForObject(url+"/course/"+subscribe.getCid(),Course.class);
if (course != null) {
resultList.add(course);
}
}
//Course course = restTemplate.getForObject("http://localhost:8201/course/"+subscribe.getCid(),Course.class);
return ResponseEntity.ok(resultList);
}
目标:
负载均衡方案
服务端负载均衡(集中式负载均衡)

客户端负载均衡

Spring Cloud Ribbon 快速实现负载均衡效果
我们可以借助 IntelliJ IDEA 的配置复制功能,只用修改一下端口号即可很方便的启动多个实例.
在微服务分布式项目中, 我们通常所说的负载均衡都指的是?
查看答案
答案: 3. 服务端负载均衡
Ribbon 主要组件


--组件--
--职责--
ServerList
服务实例信息存储
ServerListFilter
服务实例信息过滤
ServerListUpdater
服务实例信息更新
IPing
服务实例信息检测
IRule
服务实例信息选择
ILoadBalancer
服务实例信息获取

内置负载均衡规则类
规则描述
RoundRobinRule
简单轮询服务列表来选择服务器。它是 Ribbon 默认的负载均衡规则。
AvailabilityFilteringRule
对以下两种服务器进行忽略: (1)在默认情况下,这台服务器如果 3 次连接失败,这台服务器就会被设置为“短路”状态。短路状态将持续 30 秒,如果再次连接失败,短路的持续时间就会几何级地增加。 (2)并发数过高的服务器。如果一个服务器的并发连接数过高,配置了 AvailabilityFilteringRule 规则的客户端也会将其忽略。
WeightedResponseTimeRule
为每一个服务器赋予一个权重值。服务器响应时间越长,这个服务器的权重就越小。这个规则会随机选择服务器,这个权重值会影响服务器的选择。

内置负载均衡规则类
规则描述
ZoneAvoidanceRule
以区域可用的服务器为基础进行服务器的选择。使用 Zone 对服务器进行分类,这个 Zone 可以理解为一个机房、一个机架等。而后再对 Zone 内的多个服务做轮询。(默认)
BestAvailableRule
忽略那些短路的服务器,并选择并发数较低的服务器。
RandomRule
随机选择一个可用的服务器。
RetryRule
重试机制的选择逻辑
WeightedResponseTimeRule
为每一个服务器赋予一个权重值。服务器响应时间越长,这个服务器的权重就越小。这个规则会随机选择服务器,这个权重值会影响服务器的选择。
NacosRule
Nacos 服务发现的负载均衡策略,根据 Nacos 中设置的权重随机访问。
AbstractLoadBalancerRule 类,然后实现 choose 方法,该方法的返回值就是选出的服务实例。这种方式的好处是可以根据自己的业务需求来实现自定义的负载均衡策略。
Ribbon 使用方式
原生 API
// 服务列表
List<Server> serverList = Arrays.asList(
new Server("localhost", 9020),
new Server("localhost", 9021),
new Server("localhost", 9022)
);
// 构建负载实例
BaseLoadBalancer loadBalancer = LoadBalancerBuilder.newBuilder().buildFixedServerListLoadBalancer(serverList);
loadBalancer.setRule(new RandomRule()); // 随机策略
// 调用10次来测试效果
for (int i = 0; i < 10; i++) {
String result = LoadBalancerCommand.<String>builder().withLoadBalancer(loadBalancer).build()
.submit(server -> {
try {
String addr = "http://" + server.getHost() + ":" + server.getPort() + "/user/1";
System.out.println("调用地址:" + addr);
return Observable.just("");
} catch (Exception e) {
return Observable.error(e);
}
}).toBlocking().first();
System.out.println("调用结果:" + result);
}
Ribbon + RestTemplate 配置详解
自动化配置
IClientConfig : Ribbon 的客户端配置,默认采用 com.netflix.client.config.DefaultClientConfiglmpl 实现。IRule: Ribbon 的负载均衡策略,默认采用 com.netflix.loadbalancer.ZoneAvoidanceRule 实现,该策略能够在多区域环境下选出最佳区域的实例进行访问。IPing: Ribbon 的实例检查策略,默认采用 com.netflix.loadbalancer.DummyPing 实现,该检查策略是一个特殊的实现,实际上它并不会检查实例是否可用,而是始终返回 true, 默认认为所有服务实例都是可用的。ServerList<Server> :服务实例清单的维护机制,默认采用 com.netflix.loadbalancer.ConfigurationBasedServerList 实现。
ServerListFilter<Server> : 服务实例清单过滤机制,默认采用 org.springframework.cloud.netflix.ribbon.ZonePreferenceServerListFilter实现,该策略能够优先过滤出与请求调用方处于同区域的服务实例。ILoadBalancer : 负载均衡器,默认采用 com.netflix.loadbalancer.ZoneAwareLoadBalancer 实现,它具备了区域感知的能力。
ZoneAvoidanceRule 就不会被创建。
@Bean
public IRule rule(){
return new NacosRule();
}
参数配置
ribbon.<key>=<value>格式进行配置即可。 其中,<key>代表了 Ribbon 客户端配置的参数名,<value>则代表了对应参数的值。比如,可以像下面这样全局配置 Ribbon 的连接超时时间。ribbon:
connectTimeout: 5000 # 默认2000
ribbon.ConnectTimeout=5000ribbon.connectTimeout=5000
com.netflix.client.config.CommonClientConfigKey
com.netflix.client.config.DefaultClientConfigImpl
<client>.ribbon.<key>=<value>的格式进行配置。其中,<key> 和<value>的含义同全局配置相同,而<client>代表了客户端的名称,也可以将它理解为是一个服务名。假设现在没有例如 Nacos 服务治理框架的帮助来得到动态服务列表,为了实现负载均衡, 我们可以进行如下配置来设置静态的服务列表:userservice.ribbon.listOfServers=localhost:8081,localhost:8082,localhost:8083
IRule 配置
userservice: #服务名
ribbon:
NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule #负载均衡规则的全类名
饥饿加载
LoadBalanceClient 才会创建,请求时间会很长。而饥饿加载则会在项目启动时创建,降低第一次访问的耗时,通过如下配置即可开启饥饿加载:#开启userservice服务的饥饿加载
ribbon:
eager-load:
enabled: true
clients:
- userservice #服务名
与 Nacos 结合
com.alibaba.cloud.nacos.ribbon.NacosServerList 的实例所覆盖,该实现会将服务清单列表交给 Nacos 的服务治理机制来进行维护。
userservice.ribbon.listOfServers 的参数来指定具体的服务实例清单,因为 Nacos 将会为我们动态维护所有服务的实例清单。而对于 Ribbon 的参数配置,依然可以采用之前的两种配置方式来实现,而指定客户端的配置方式可以直接使用 Nacos 中的服务名作为客户端名来完成针对各个微服务的个性化配置。
spring:
application:
name: userservice #注册到注册中心的服务名称
cloud:
nacos:
server-addr: localhost:8848 #nacos 服务端地址
discovery:
cluster-name: shanghai #区域(集群)名称
spring.cloud.nacos.discovery.cluster-name 属性用于指定当前服务所在的区域名称,当服务启动时,会将该属性的值作为服务的元数据信息,注册到 Nacos 服务注册中心中。当服务消费者调用服务提供者时,会根据服务提供者的元数据信息来选择服务实例。
重试机制
# 断路器的超时时间需要大于Ribbon的超时时间,不然不会触发重试。
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 10000
userservice: #服务名
ribbon:
ConnectTimeout: 250 #请求连接的超时时间
ReadTimeout: 1000 #请求处理的超时时间
OkToRetryOnAllOperations: true #对所有操作请求都进行重试
MaxAutoRetriesNextServer: 2 #切换实例的重试次数
MaxAutoRetries: 1 #对当前实例的重试次数。
MaxAutoRetriesNextServer 配置),如果依然不行,返回失败信息。
The Raft Group [naming_instance_metadata] did not find the Leader node
解决: 先停服务,再停 nacos,再删掉 data 目录下的 protocol 文件夹,再重启 nacos.最后再重启需要注册的那些服务.
自定义负载均衡策略
AbstractLoadBalancerRule,我们只需要继承该类并实现其中的 choose 方法即可。在 choose 方法中,我们可以根据自己的需求来选择服务实例。
public class MetadataRule extends AbstractLoadBalancerRule {
@Override
public Server choose(Object key) {
// 获取负载均衡器
ILoadBalancer loadBalancer = getLoadBalancer();
// 获取服务实例列表
List<Server> serverList = loadBalancer.getAllServers();
// 获取服务实例元数据信息
for (Server server : serverList) {
Map<String, String> metadata = server.getMetaInfo();
String version = metadata.get("version");
if (version.equals("1.0")) {
return server;
}
}
return null;
}
}
com.example.ribbon.rule.MyRule/**
* 自定义负载均衡策略 - 总是选择第一个可用的服务实例
*/
public class MyRule extends AbstractLoadBalancerRule {
@Override
public Server choose(Object key) {
List<Server> upList = getLoadBalancer().getReachableServers();
return upList.get(0);
}
@Override
public void initWithNiwsConfig(IClientConfig clientConfig) {
String clientName = clientConfig.getClientName();
System.out.println(clientName);
}
}
com.example.ribbon.rule.MyIPing/**
* 自定义我们如何“ping”服务器以检查其是否处于活动状态的接口
* 这里我们忽略任何判断直接返回true
*/
public class MyIPing implements IPing {
@Override
public boolean isAlive(Server server) {
return true;
}
}
# 指定userservice的负载策略
userservice.ribbon.NFLoadBalancerRuleClassName=com.example.ribbon.rule.MyRule
userservice.ribbon.NFLoadBalancerPingClassName=com.example.ribbon.rule.MyIPing
com.example.ribbon.config.RibbonClientConfig@RibbonClient(name="userservice", configuration=RibbonConfiguration.class)
public class RibbonClientConfig {
}
com.example.ribbon.config.RibbonConfiguration@Configuration
public class RibbonConfiguration {
@Bean
public IRule myRule() {
return new MyRule();
}
@Bean
public IPing myPing() {
return new MyIPing();
}
}
练习问题
a. DynamicServerListLoadBalancer
b. BaseLoadBalancer
c. AbstractLoadBalancer
d. ServerListSubsetFilter查看答案
答案: D
a. ZonePreferenceServerListFilter
b. DefaultNIWSServerListFilter
c. ZoneAwareLoadBalancer
d. ZoneAffinityServerListFilter查看答案
答案: C
a. ILoadBalancer
b. IloadBalancr
c. IoadBalancer
d. IloadBlancer查看答案
答案: A
a. 1
b. 2
c. 3
d. 4查看答案
答案: B
小结
SpringCloud与微服务-第3章-服务治理(Nacos/Eureka)
第 3 章 - 服务治理
常见方案
Netflix Eureka
zookeeper
Consult
Spring Cloud Alibaba Nacos
方案对比

服务治理
搭建 Nacos 服务
1.下载安装包

2.解压

3.修改端口


4.启动

startup.cmd -m standalone
set MODE="cluster为set MODE="standalone",然后执行startup.cmd。

5.访问


注册服务提供者
1. 添加依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.5.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
2. 添加 Nacos 的客户端依赖
<!-- nacos客户端依赖包 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
3. 添加 Nacos 地址
spring:
application:
name: orderservice #注册到注册中心的服务名称
cloud:
nacos:
server-addr: localhost:8848 #nacos 服务端地址
4. 启动测试


服务发现与消费
1.添加@LoadBalanced 注解
/**
* 在容器中注册 RestTemplate 方便项目内调用
* @return
*/
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
2.使用服务名
@RestController
@RequestMapping("order")
public class OrderController {
@Autowired
private OrderService orderService;
@Autowired
private RestTemplate restTemplate;
@GetMapping("{orderId}")
public Order queryOrderByUserId(@PathVariable("orderId") Long orderId) {
// 根据id查询订单并返回
Order order = orderService.queryOrderById(orderId);
//通过RestTemplate查询用户信息 使用服务名代替ip和端口号
String url = "http://userservice/user/"+order.getUserId();
User forObject = restTemplate.getForObject(url, User.class);
//封装返回的数据
order.setUser(forObject);
return order;
}
}
3.测试
服务消费方->注册中心->服务提供方
的这样一个简单体系。当我们使用浏览器访问一个服务的时候,该服务内部如何去调用其他服务能力接口的整个过程我们是感知不到的。所以我们像以前一样正常访问即可.

userservcie 模块 中的 UserController 类:@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@Value("${server.port}")
private String serverPort;
@GetMapping("/{id}")
public User queryById(@PathVariable("id") Long id) {
User user = userService.queryById(id);
user.setServerPort(serverPort);
return user;
}
}
4.如何启动多个服务提供方


直接修改运行配置中的 VM options,添加 -Dserver.port=XXXX,
其中 XXXX 为不同的端口号, 分别启动多个服务.

alt+8打开服务列表, 添加 SpringBoot 的配置类型, 可以方便的统一管理多个服务.


搭建 Eureka 服务
<!--eureka服务端-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
server:
port: 10086 # 服务端口
#将Eureka服务注册到Eureka注册中心上,方便以后做集群管理
spring:
application:
name: eurekaserver # eureka的服务名称
eureka:
client:
service-url: # eureka的地址信息, 如果搭建Eureka集群则需要配置多个地址, 以逗号分隔, 交叉注册
defaultZone: http://127.0.0.1:10086/eureka

启用 Eureka 客户端
@MapperScan("com.niit.user.mapper")
@SpringBootApplication
@EnableEurekaClient // for uereka
public class MainApplication {
...
}
# Eureka 的地址信息
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
练习问题
a. Netflix Eureka
b. Zookeeper
c. Spring Cloud Alibaba Nacos
d. Spring Boot Application
答案
a. 错误
b. 正确
答案
a. startup.cmd -m standalone
b. startup.cmd -f standalone
c. startup.exe -m standalone
d. startup.cmd
答案
a. spring-cloud-starter-alibaba-nacos-config
b. spring-cloud-starter-alibaba-nacos-discovery
c. spring-cloud-alibaba-starter-nacos-discovery
d. spring-cloud-alibaba-nacos-starter-discovery
答案
小结
SpringCloud与微服务-第2章-SpringBoot基础回顾
第 2 章 - SpringBoot 快速入门
Spring Boot 框架简介
项目构建与解析
系统及工具版本要求
使用 IDEA 快速创建应用
1.打开新建项目选项卡

2.填写版本信息和项目信息

3.选择相关初始化依赖

4.选择项目的本地路径

工程结构解析

src/main/java:源代码目录,该目录用来存放应用的源代码,其中 com.example.demo 包名是由我们在创建项目时指定的。src/main/resources:资源文件目录,该目录用来存放应用的配置文件,如 application.properties、application.yml 等。src/test:测试代码目录,该目录用来存放应用的测试代码。
Maven 配置分析
parent:
- spring-boot-starter-parent@2.7.1
dependencies:
- spring-boot-starter-web
- spring-boot-starter-jdbc
- mysql-connector-java(runtime)@5.1.47
- mybatis-spring-boot-starter
- spring-boot-devtools
- spring-boot-configuration-processor
- lombok(optional)
- spring-boot-starter-test
- spring-data-commons(分页支持)
相关技术的解决方案。开发者在使用和整合模块时,不必再去搜寻样例代码中的依赖配置来复制使用,只需要引入对应的模块包即可。
spring-boot-starter-xxx的命名方式,*代表一个特别的应用功能模块,比如这里的 web、test。xxx.sring-boot-starter 命名的模块是非官方的第三方模块,比如 mybatis-spring-boot-starter。
https://docs.spring.io/spring-boot/docs/2.3.12.RELEASE/reference/html/index.html
实现 RESTful API
HelloWorldController 类,内容如下所示。
package com.niit.quickstart.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 控制器类
*/
@RestController
@RequestMapping("/test")
public class HelloWorldController {
@RequestMapping("/hello")
public String showHello(){
return "Hello World!";
}
}




编写单元测试
RestTemplate 类模拟 发出 HTTP 请求.
1. 声明 RestTemplate 类
/**
*主配置类 项目启动入口
*/
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
/**
* 在bean容器中注册RestTemplate 方便在应用中使用
* @return
*/
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
2.编写单元测试类
/**
* @apiNote 测试类 用于测试controller 通过restTemplate调用接口 (需要启动项目)
*/
@SpringBootTest
class TestControllerTest {
@Autowired
RestTemplate restTemplate;
@Test
public void test() {
String url = "http://localhost:8080/test/hello";
String result = restTemplate.getForObject(url, String.class);
System.out.println(result);
}
}
3.点击测试方法左侧按钮,开始测试

4.测试结果

5.注意事项

快速入门小结
配置详解
配置文件

YAML 文件。
自定义参数
# 自定义属性
book:
name: SpringCloudInAction
author: Jeff
@Component
@Data
public class Book {
@Value("${book.name}")
private String name;
@Value("${book.author}")
private String author;
}
${ • • • },大括号内为 PlaceHolder。#{•••},大括号内为 SpEL 表达式
/**
* Book属性注入 单元测试类
*/
@SpringBootTest
class BookTest {
@Autowired
Book book;
@Test
public void testBook() {
System.out.println(book);
}
}

参数引用
# 自定义属性
book:
name: SpringCloudInAction
author: Jeff
desc: ${book.author} is writing <<${book.name}>>
@Component
@Data
public class Book {
@Value("${book.name}")
private String name;
@Value("${book.author}")
private String author;
@Value("${book.desc}")
private String desc;
}

多环境配置
application-{profile}.yml 的格式(我们以.yml 格式为例),其中{profile}对应你的环境标识,如下所示:
application-dev.yml:开发环境。application-test.yml:测试环境。application-prod.yml:生产环境。
spring.profiles.active 属性来设置,其值对应配置文件中的{profile}值。如 spring.profile.active=dev 就会加载 application-dev.yml 配置文件内容。

application.yml 中配置通用内容,并设置 spring.profiles.active=dev,以开发环境为默认配置。application-{profile}.yml 中配置各个环境不同的内容。
加载顺序
SPRING_APPLICATION_JSON 中的属性。SPRING_APPLICATION_JSON 是以 JSON 格式配置在系统环境变量中的内容。java:comp/env 中的 JNDI 属性。System.getProperties()获得的内容。random.* 配置的随机属性。
application-{profile}.properties 或是 YAML 定义的配置文件。application-{profile}.properties 或是 YAML 定义的配置文件。application.properties 和 YAML 配置内容。
application.properties 和 YAML 配置内容。@Configuration 注解修改的类中,通过 @PropertySource 注解定义的属性。SpringApplication.setDefaultProperties 定义的内容。
监控与管理
spring-boot-starter-actuator。spring-boot-starter-actuator 模块的内容,包括原生提供的端点以及一些常用的扩展和配置方式等。
初识 actuator
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

spring-boot-starter-actuator 模块根据应用依赖和配置自动创建出来的监控和管理端点。通过这个端点,我们可以实时获取应用的各项监控指标
/actuator 端点。我们可以获得如下类似信息:{
"_links": {
"self": {
"href": "http://localhost:8081/dev/actuator",
"templated": false
},
"health": {
"href": "http://localhost:8081/dev/actuator/health",
"templated": false
},
"health-path": {
"href": "http://localhost:8081/dev/actuator/health/{*path}",
"templated": true
}
}
}
原生端点
#主动暴露所有web端点
management:
endpoints:
web:
exposure:
include: "*"
应用配置类
/beans:
/configprops:
endpoints.configprops.enabled=false 来完成设置。如下所示,添加该配置后重启应用即可关闭 /configprops 端点management:
endpoint:
configprops:
enabled: false
/env:/configprops 不同,它用来获取应用所有可用的环境属性报告,其中还包括了应用还没有使用的配置,所以它可以帮助我们方便地看到当前应用可以加载的配置信息,并配合@ConfigurationProperties 注解将它们引入到我们的应用程序中来进行使用。另外,为了配置属性的安全,对于一些类似密码等敏感信息,该端点都会进行隐私保护,但是我们需要让属性名中包含 password、secret、key 这些关键词,这样该端点在返回它们的时候会使用 * 来替代实际的属性值。

/mappings:
度量指标类
/metrics:


/health:

UNKNOWN:未知状态,映射 HTTP 状态码为 503UP:正常,映射 HTTP 状态码为 200DOWN:失败,映射 HTTP 状态码为 503OUT_OF_SERVICE:不能对外提供服务,但是服务正常。映射 HTTP 状态码为 200
操作控制类
/shutdown(在后续我们引入了 Eureka 之后,会引入更多控制端点)。可以通过如下配置开启它:

答案
启用热部署以提升开发效率
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>


练习问题
答案
答案
答案
b. 错误答案
小结
