复杂分布式体系结构中的应用程序往往由多个服务组成,这些服务之间相互依赖,依赖关系错综复杂,每个依赖关系在某些时候将不可避免的失败!
通常情况下,一个用户请求往往需要多个服务配合才能完成。如图 1 所示,在所有服务都处于可用状态时,请求 a 需要调用 S1、S3、S5、S7 四个服务才能完成,请求 b 需要调用 S4、S5 、S72个服务才能完成,请求 s 需要调用服务S3、S5、S7 、S8 四个服务才能完成。
当服务 s5发生故障或网络延迟时,会出现以下情况:
即使其他所有服务都可用,由于服务s5的不可用,那么用户请求 a、b、c 都会处于阻塞状态,等待服务s5 的响应。在高并发的场景下,会导致整个服务器的线程资源在短时间内迅速消耗殆尽。
所有依赖于服务s5的其他服务,例如服务 S3、S4、S6、S7 以及s5 也都会处于线程阻塞状态,等待服务 s5 的响应,导致这些服务的不可用。
所有依赖服务S3、S7 的服务,例如服务 s1 和服务 s8 也会处于线程阻塞状态,以等待S3、S7的响应,导致 s1、s8 也不可用。
服务降级:在某个服务奔溃后调用备选的服务,若不用Hystrix实现自己可以使用异步加载,异常来实现
在微服务系统中,Hystrix 能够帮助我们实现以下目标:
Hystrix 提供了服务降级功能,能够保证当前服务不受其他服务故障的影响,提高服务的健壮性。
服务降级的使用场景有以下 2 种:
我们可以通过重写 HystrixCommand 的 getFallBack() 方法或 HystrixObservableCommand 的 resumeWithFallback() 方法,使服务支持服务降级。
这个降级方法可以是返回一个友好提示,也可以是执行一个和原方法同样的逻辑方法,由于导致故障的原因不同,也许第二次执行 可以通过。这两种方案视具体情况而定。
Hystrix 会在以下场景下进行服务降级处理:
接着我们测试下 Hystrix 服务端服务降级和客户端服务降级
spring-cloud-microservice
org.example
1.0-SNAPSHOT
4.0.0
microservice-cloud-provider-dept-hystrix-8004
org.example
microservice-cloud-api
1.0-SNAPSHOT
org.springframework.boot
spring-boot-starter-data-jdbc
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-tomcat
org.springframework.boot
spring-boot-devtools
runtime
true
mysql
mysql-connector-java
runtime
com.alibaba
druid
org.projectlombok
lombok
true
org.mybatis.spring.boot
mybatis-spring-boot-starter
org.springframework.boot
spring-boot-starter-test
test
junit
junit
test
org.springframework
springloaded
1.2.8.RELEASE
ch.qos.logback
logback-core
org.apache.logging.log4j
log4j-core
org.springframework.boot
spring-boot-starter-jetty
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
2.2.10.RELEASE
org.springframework.boot
spring-boot-starter-actuator
org.springframework.cloud
spring-cloud-starter-netflix-hystrix
2.2.10.RELEASE
org.springframework.boot
spring-boot-maven-plugin
org.projectlombok
lombok
server:
port: 8004
spring:
application:
name: microServiceCloudProviderDeptHystrix #微服务名称,对外暴漏的微服务名称,十分重要
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 123456
url: jdbc:mysql://localhost:3306/springcloud_db2?useSSL=true&useUnicode=true&characterEncoding=utf-8&serverTimeZone=UTC
type: com.alibaba.druid.pool.DruidDataSource
#SpringBoot默认是不注入这些的,需要自己绑定
#druid数据源专有配置
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
#配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入
#如果允许报错,java.lang.ClassNotFoundException: org.apache.Log4j.Priority
#则导入log4j 依赖就行
filters: stat,wall,log4j2
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
#整合mybatis
mybatis:
#type-aliases-package: com.zk.springcloud.entity
#mapper-locations: classpath:mybatis/mapper/*.xml
#config-location: classpath:mybatis/mybatis-config.xml
configuration:
map-underscore-to-camel-case: true #默认开启驼峰命名法,可以不用设置该属性
#eureka配置,Spring cloud 自定义服务名称和 ip 地址
eureka:
client:
fetch-registry: true
service-url:
#defaultZone: http://localhost:7001/eureka/ #这个地址是 7001注册中心在 application.yml 中暴露出来额注册地址 (单机版)
defaultZone: http://eurekaserver7001.com:7001/eureka/,http://eurekaserver7002.com:7002/eureka/,http://eurekaserver7003.com:7003/eureka/ #将服务注册到 Eureka Server 集群
instance:
instance-id: microservice-cloud-provider-dept-hystrix-8004 #自定义服务名称描述信息
prefer-ip-address: true #显示访问路径的 ip 地址
#zookeeper需要配置那些服务service被扫描,eureka则是将整个服务包注册进去了
# spring cloud 使用 Spring Boot actuator 监控完善信息
# Spring Boot 2.50对 actuator 监控屏蔽了大多数的节点,只暴露了 heath 节点,本段配置(*)就是为了开启所有的节点
management:
endpoints:
web:
exposure:
include: health,info #应包含的端点 ID 或 '' 全部,* 在yaml 文件属于关键字,所以需要加双引号
#include的值也可以改成*,但建议还是最小暴露原则,用啥开启啥
info:
env:
enabled: true #启用配置里的info开头的变量
info: #配置一些服务描述信息,监控信息页面显示,,可以判断服务是否正常
app.name: microservice-cloud-provider-dept-hystrix-8004
company.name: cloud.zk.com
auth: zk
email: [email protected]
build.aetifactId: @project.artifactId@
build.version: @project.version@
package com.example.service;
public interface DeptService {
// hystrix 熔断器示例 ok
String dept_200(Integer id);
//hystrix 熔断器超时案例
String dept_timeout_500(Integer id);
}
package com.example.service;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
/**
* @author CNCLUKZK
* @create 2022/9/20-13:26
*/
@Service("deptService")
public class DeptServiceImpl implements DeptService{
@Override
public String dept_200(Integer id) {
return "服务端当前线程:"+Thread.currentThread().getName()+"请求处理成功200。ID+"+id;
}
//一旦该方法失败并抛出了异常信息后,会自动调用 @HystrixCommand 注解标注的 fallbackMethod 指定的方法
@HystrixCommand(fallbackMethod = "dept_timeout_handler",
规定 5 秒钟以内就不报错,正常运行,超过 5 秒就报错,调用指定的方法
commandProperties = {@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "5000")})
@Override
public String dept_timeout_500(Integer id) {
try {
//Thread.sleep(6000);
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "服务端当前线程:"+Thread.currentThread().getName()+"请求超时500。ID+"+id;
}
// 当服务出现故障后,调用该方法给出友好提示
public String dept_timeout_handler(Integer id){
return "服务端因当前请求超时,服务降级,返回提示信息!当前线程:"+Thread.currentThread().getName()+"请求超时500。ID+"+id;
}
}
dept_timeout_500() 方法上使用 @HystrixCommand 注解,该注解说明如下:
在 com.example.controller包下创建一个名为 DeptController的控制类提供服务,代码如下
package com.example.controller;
import com.example.service.DeptService;
import com.zk.springcloud.entity.Dept;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.core.env.Environment;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* @author CNCLUKZK
* @create 2022/9/7-1:43
*/
@RestController //提供restful服务,@RestController需要spring-boot-starter-web
@Slf4j
@RequestMapping("/dept")
public class DeptController {
@Value("${server.port}")
private String serverPort;
@Autowired
Environment environment;
@Autowired
private DeptService deptService;
//获取一些配置的信息,得到具体的微假务
@GetMapping("/getInfo/Hystrix/200/{id}")
public String dept_200(@PathVariable("id") Integer id) {
String info = deptService.dept_200(id);
log.info("request result"+info+"port:"+serverPort);
return "request result"+info+"port:"+serverPort;
}
//超时测试,该服务的响应时间为 3 秒
@GetMapping("/testTimeOut/Hystrix/{id}")
public String testTimeOut(@PathVariable("id") Integer id){
String info = deptService.dept_timeout_500(id);
//当Application.yml中没有配置当前端口号,只能使用此方式获取端口
String serverPort = environment.getProperty("server.port");
log.info("request result"+info+"port:"+serverPort);
return "request result"+info+"port:"+serverPort;
}
}
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
@SpringBootApplication
@EnableEurekaClient //在服务启动后自动注册到eureka中
//@EnableCircuitBreaker //激活熔断器功能
@EnableHystrix //启用 Hystrix
public class MicroserviceCloudProviderDeptHystrix8004Application {
public static void main(String[] args) {
SpringApplication.run(MicroserviceCloudProviderDeptHystrix8004Application.class,args);
}
}