1. Dashboard 监控仪表盘
Hystrix Dashboard 仪表盘用于以 GUI 的形式展示消费者的执行情况,包括其处理器方法与 Service 方法的调用执行情况,及熔断器 CircuitBreaker 的状态等。当然,这些显示出的数据都是在指定时间窗内的执行情况及状态信息。
总步骤
- 添加 hystrix-dashboard 与 actuator 依赖
- 配置文件中开启 actuator 的 hystrix.stream 监控终端
- 在启动类上添加@EnableHystrixDashboard 注解
1.1 定义消费者工程 04-consumer-dashboard-8080
Hystrix-dashboard 用于监控 Hystrix 服务降级情况,所以应添加在消费者工程中。
(1) 创建工程
本例完全可以直接在 04-consumer-fallbackfeign-8080 工程中进行修改,为了便于演示,这里又新建了一个工程。复制工程 04-consumer-fallbackfeign-8080,并重命名为04-consumer-dashboard-8080。
(2) 添加依赖
保证如下三个依赖都要有:
<!-- hystrix-dashboard依赖 -->
<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>
<!--hystrix依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
(3) 修改配置文件
在配置文件中添加如下内容,用于开启 actuator 的相关监控终端,并调整 Hystrix 隔离线程执行的超时时限。
spring:
application:
name: abcmsc-consumer-depart
eureka:
client:
service-url:
defaultZone: http://localhost:8000/eureka
feign:
client:
config:
default:
connectTimeout: 5000 # 指定Feign客户端连接提供者的超时时限
readTimeout: 5000 # 指定Feign客户端连接上提供者后,向提供者进行提交请求,从提交时刻开始,到接收到响应,这个时段的超时时限
# 开启Feign对Hystrix的支持
hystrix:
enabled: true
# 设置服务熔断时限
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 3000
# 开启actuator的hystrix.stream监控终端
management:
endpoints:
web:
exposure:
include: hystrix.stream
(4) 修改启动类
@EnableHystrixDashboard // 开启Dashboard功能
@EnableFeignClients // 开启Feign客户端
@SpringCloudApplication
public class ApplicationConsumer8080 {
public static void main(String[] args) {
SpringApplication.run(ApplicationConsumer8080.class, args);
}
}
1.2 定义提供者工程 04-provider-8081
(1) 创建工程
复制第三章的 03-provider-8081 工程,并重命名为 04-provider-8081。因为 03-provider-8081 工程的 Service 方法中我们实现了一个线程阻塞的功能,当时为了演示feign的调用超时,我们拿过来修改一下。
(2) 修改 Service 实现类
这里仅修改 getDepartById()方法。
为了测试方便,这里将 id 值设置为 sleep()的时间,这样设置没有业务上的意义,但可以方便控制请求的执行时间,达到超时、不超时的效果。
@Service
public class DepartServiceImpl implements DepartService {
@Autowired
private DepartRepository repository;
...
@Override
public Depart getDepartById(int id) {
int time = id;
try {
TimeUnit.SECONDS.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(repository.existsById(id)) {
// 在DB中指定的id若不存在,该方法会抛出异常
return repository.getOne(id);
}
Depart depart = new Depart();
depart.setName("no this depart");
return depart;
}
...
}
启动测试:
3秒是超时的:
1秒就是OK的:
1.3 仪表盘
原理是基于H5的SSE规范(Server Send Event,服务器发送事件)
操作:
通过访问localhost:8080/actuator/hystrix.stream可以直接获取到actuator的hystrix.stream监控终端的数据,Hystrix Dashboard实际上就是对这些数据进行解析转成可视化界面:
可以看到会动态显示很多数据,但是不直观,现在我们用Hystrix Dashboard对这些数据进行解析:
点击以后得到如下页面:
调用几次超时请求后,界面变化(红色):
调用几次成功请求后,界面变化(绿色):
GUI 介绍
官网的介绍:
1.4 使用postman进行批量访问测试
先创建一个集合,将要执行的请求保存到集合中:
找到Runner,执行集合中的请求:
选中集合,和集合中要执行的方法:
Iterations参数代表要循环执行几次,点击执行:
执行以后查看Dashboard监控页面,看到都是绿色成功的:
向集合中添加超时的请求:
看到集合中既有超时的,也有不超时的请求:
点击运行后直接看效果:
2. Turbine 聚合监控–监控默认组集群
关于 Turbine:
前面的方式是对单个消费者进行监控,我们也可以对集群进行整体监控。此时就需要使用 Turbine 技术了。Turbine 能够汇集监控信息,并将聚合后的信息提供给 Hystrix Dashboard来集中展示和监控。
【翻译】就系统的总体运行状况而言,查看单个实例的Hystrix数据并不是很有用。Turbine 是一个应用程序,它将所有相关的/hystrix.stream端点聚合成一个合并的/hystrix.stream,以便在Hystrix仪表板中使用。单个实例通过Eureka定位
。运行Turbine需要使用@EnableTurbine注释您的主类
(例如,通过使用spring-cloud-starter-netflix-turbine来设置类路径)。
2.1 整体架构示意图
2.2 总步骤
(1) Turbine Client
- 至少要有 actuator 与 neflix-hystrix 依赖
- 在配置文件中必须开启 acturator 的 hystrix.stream 监控终端
(2) Turbine Server
- 至少要有如下依赖:
- netflix-turbine 依赖
- netflix -hystrix-dashboard 依赖
- netflix -hystrix 依赖
- actuator 依赖
- eureka client 依赖
- 在配置文件中要配置 turbine:指定要监控的 group 及相应的微服务名称
2.3 创建消费者工程 04-consumer-turbine-client-8180
(1) 创建工程
复制 04-consumer-dashboard-8080 工程,并重命名为 04-consumer-turbine-client-8180。
(2) 修改依赖
由于当前应用无需使用 Dashboard,所以可以将 Dashboard 依赖去掉。但必须要保证 Turbine 所监控的应用中具有 actuator 与 hystrix 依赖。
<!-- hystrix-dashboard 依赖 -->
<!--<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>
<!--hystrix 依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
(3) 修改配置文件
server:
port: 8180
spring:
application:
name: abcmsc-consumer-depart
eureka:
client:
service-url:
defaultZone: http://localhost:8000/eureka
feign:
client:
config:
default:
connectTimeout: 5000 # 指定Feign客户端连接提供者的超时时限
readTimeout: 5000 # 指定Feign客户端连接上提供者后,向提供者进行提交请求,从提交时刻开始,到接收到响应,这个时段的超时时限
# 开启Feign对Hystrix的支持
hystrix:
enabled: true
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 3000
# 开启actuator的hystrix.stream监控终端
management:
endpoints:
web:
exposure:
include: hystrix.stream
(4) 修改启动类
作为被监控客户端,不需要开启监控仪表盘了:
//@EnableHystrixDashboard
@EnableFeignClients // 开启Feign客户端
@SpringCloudApplication
public class ApplicationConsumer8180 {//重命名改成8180
public static void main(String[] args) {
SpringApplication.run(ApplicationConsumer8180.class, args);
}
}
2.4 创建消费者工程 04-consumer-turbine-client-8280
(1) 创建工程
复制 04-consumer-turbine-client-8180 工程,并重命名为 04-consumer-turbine-client-8280。
(2) 修改配置文件
server:
port: 8280
spring:
application:
name: abcmsc-consumer-depart
eureka:
client:
service-url:
defaultZone: http://localhost:8000/eureka
feign:
client:
config:
default:
connectTimeout: 5000 # 指定Feign客户端连接提供者的超时时限
readTimeout: 5000 # 指定Feign客户端连接上提供者后,向提供者进行提交请求,从提交时刻开始,到接收到响应,这个时段的超时时限
# 开启Feign对Hystrix的支持
hystrix:
enabled: true
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 3000
# 开启actuator的hystrix.stream监控终端
management:
endpoints:
web:
exposure:
include: hystrix.stream
(3) 修改启动类
@EnableFeignClients // 开启Feign客户端
@SpringCloudApplication
public class ApplicationConsumer8280 {//启动类更名,方便区分
public static void main(String[] args) {
SpringApplication.run(ApplicationConsumer8280.class, args);
}
}
2.5 创建 Turbine 工程 00-hystrix-turbine-8888
(1) 创建工程
复制第二章中的 00-eurekaserver-8000 工程,并重命名为 00-hystrix-turbine-8888。
(2) 修改依赖
由于这是一个 Eureka Client,所以将原来的 Eureka Server 依赖删除,添加 Eureka Client 依赖。
- netflix-turbine 依赖
- netflix -hystrix-dashboard 依赖
- netflix -hystrix 依赖
- actuator 依赖
- eureka client 依赖
<!-- hystrix-turbine依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-turbine</artifactId>
</dependency>
<!-- hystrix-dashboard依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
<!--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>
<!--eureka客户端依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
(3) 修改启动类
@EnableTurbine // 开启Turbine聚合功能
@EnableHystrixDashboard // 开启Hystrix仪表盘功能
@SpringCloudApplication
public class ApplicationTurbine8888 { //重命名
public static void main(String[] args) {
SpringApplication.run(ApplicationTurbine8888.class, args);
}
}
(4) 修改配置文件
server:
port: 8888
eureka:
client:
service-url:
defaultZone: http://localhost:8000/eureka
spring:
application:
name: hystrix-turbine
# 配置turbine
turbine:
# 指定要监控的微服务名称
app-config: abcmsc-consumer-depart
aggregator:
# 指定要监控的微服务组,default指定要监控默认组
# 目前我们不需要给集群中的实例进行分组,所以这里配置default,代表监控默认组即可。
cluster-config: default
# 集群名称表达式,用来配置如何获取和判断集群中各个实例所属集群组的名称
# 目前我们不需要给集群中的实例进行分组
# 所以这里直接配置为简单字符串'default'即可,所有实例都属于默认组。
cluster-name-expression: "'default'"
2.6 效果
先直接通过路径访问:
在通过Dashboard 监控仪表盘监控 聚合后的数据:
看到该集群中有两台主机。
2.7 监控默认组两个集群(不同的微服务)
上面案例两个消费者是同一个微服务名称,现在我们改成不同的两个微服务:
(1) 分别将两个应用微服务名称改成不同的,对应两个微服务
04-consumer-turbine-client-8180:
04-consumer-turbine-client-8280:
(2) 修改Turbine 工程监控两个微服务
(3) 效果
看到虽然还是2,但是现在是两个微服务了,代表各自两个集群,将两个集群中的所有主机信息进行统计显示。
3. Turbine 聚合监控–监控多个组集群
为了更加方便对集群的运行状态的监控,Turbine 将集群进行了分组。前面我们监控了两个集群,这两个集群默认被分为了一个组,是默认组。我们也可以将集群划分到多个组中使用同一个 Turbine 进行分别监控。
3.1 整体架构
3.2 分组监控原理
Turbine 对于集群分组进行监控的原理是,在集群之上再添加一种分类:组。为每个集群中的 Sever 都添加一个 groupId,而 Turbine 是基于 groupId 进行监控的。
这个 groupId 是基于自定义的 Eureka 元数据实现的。
Eureka 元数据是指,Eureka Client 向 Eureka Server 注册时的描述信息,注册数据。其有两种类型:
- 标准元数据:Eureka 中已经定义好的客户端描述信息。
- 自定义元数据:在客户端配置文件中自己指定的 key-value 数据。
3.3 创建消费者工程 04-consumer-turbine-client-8380
(1) 创建工程
复制 04-consumer-turbine-client-8180 工程,并重命名为 04-consumer-turbine-client-8380。
启动类重命名,方便区分:ApplicationConsumer8380
(2) 修改配置文件
在instance配置信息里面添加自定义的元数据,key必须为cluster(Turbine要求的):
server:
port: 8380
spring:
application:
name: consumer-8380
eureka:
client:
service-url:
defaultZone: http://localhost:8000/eureka
instance:
metadata-map:
cluster: group1 # 自定义Eureka元数据
feign:
client:
config:
default:
connectTimeout: 5000 # 指定Feign客户端连接提供者的超时时限
readTimeout: 5000 # 指定Feign客户端连接上提供者后,向提供者进行提交请求,从提交时刻开始,到接收到响应,这个时段的超时时限
# 开启Feign对Hystrix的支持
hystrix:
enabled: true
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 3000
# 开启actuator的hystrix.stream监控终端
management:
endpoints:
web:
exposure:
include: hystrix.stream
3.4 创建消费者工程 04-consumer-turbine-client-8480
(1) 创建工程
复制 04-consumer-turbine-client-8380 工程,并重命名为 04-consumer-turbine-client-8480。
启动类重命名,方便区分:ApplicationConsumer8480
(2) 修改配置文件
server:
port: 8480
spring:
application:
name: consumer-8480
eureka:
client:
service-url:
defaultZone: http://localhost:8000/eureka
instance:
metadata-map:
cluster: group1 # 自定义Eureka元数据
...
3.5 创建消费者工程 04-consumer-turbine-client-8580
(1) 创建工程
复制 04-consumer-turbine-client-8480 工程,并重命名为 04-consumer-turbine-client-8580。
启动类重命名,方便区分:ApplicationConsumer8580
(2) 修改配置文件
server:
port: 8580
spring:
application:
name: consumer-8580
eureka:
client:
service-url:
defaultZone: http://localhost:8000/eureka
instance:
metadata-map:
cluster: group2 # 自定义Eureka元数据
...
3.6 创建消费者工程 04-consumer-turbine-client-8680
(1) 创建工程
复制 04-consumer-turbine-client-8580 工程,并重命名为 04-consumer-turbine-client-8680。
启动类重命名,方便区分:ApplicationConsumer8680
(2) 修改配置文件
server:
port: 8680
spring:
application:
name: consumer-8680
eureka:
client:
service-url:
defaultZone: http://localhost:8000/eureka
instance:
metadata-map:
cluster: group2 # 自定义Eureka元数据
...
3.7 创建 Turbine 工程 00-hystrix-turbine-7777
(1) 创建工程
复制 00-hystrix-turbine-8888 工程,并重命名为 00-hystrix-turbine-7777。
(2) 修改配置文件
指定监控四个微服务名称,同时代表了4个集群,并对所有集群中的实例进行了分组:
server:
port: 7777
eureka:
client:
service-url:
defaultZone: http://localhost:8000/eureka
spring:
application:
name: hystrix-turbine
# 配置turbine
turbine:
# 指定要监控的微服务名称
app-config: consumer-8380,consumer-8480,consumer-8580,consumer-8680
aggregator:
# 指定要监控的微服务组
cluster-config: group1,group2
# 指定要监控微服务组名称来自于Eureka元数据cluster
cluster-name-expression: metadata['cluster']
3.8 演示
之前的配置:
consumer-8380、consumer-8480 属于 group1
consumer-8580、consumer-8680 属于 group2
监控group1:
监控group2:
4. 服务降级报警机制
无论哪种原因(系统异常、超时、隔离、熔断)启用了服务降级,系统都应该向管理员发出警报通知管理员,例如向管理员发送短信。这种发生服务降级后向管理员发出警报的机制称为服务降级报警机制。
1.高并发情况下,避免同一个服务因为服务降级,导致该服务的每个请求都发告警信息,所以我们需要给报警添加一个报警标示,没报过警的才报警
2.同时避免发过一次报警以后忘记处理了,之后又不在报警,导致一直没人处理,所以需要给报警标示设置一个有效期时间
3.报警处理希望异步处理,不影响请求的响应。
针对以上情况,考虑将报警标识放Redis最为合适。
当然还需要考虑高并发情况下 Redis 存在的问题:
- 缓存穿透
- 缓存雪崩
- 热点缓存:双重检测锁(存在线程安全问题)
4.1 创建工程 04-consumer-alarm-8080
复制前面任意的服务熔断消费者工程,这里复制 04-consumer-fallbackmethod-8080工程,并重命名为 04-consumer-alarm-8080。
4.2 添加依赖
<!--Spring Boot与Redis整合依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
4.3 修改配置文件
spring:
application:
name: abcmsc-consumer-depart
# 连接Redis服务器
redis:
host: redis5OS
port: 6379
password: 111
eureka:
client:
service-url:
defaultZone: http://localhost:8000/eureka
4.4 修改处理器
修改处理器,首先创建一个线程池,包含 5 个线程,然后再修改服务降级方法。
修改服务降级方法,以当前主机ip和方法名作为key:
报警前先从redis缓存中查询报警暴击,判断在一定时间内是否已经报过警了:
报警,异步处理:
@RestController
@RequestMapping("/consumer/depart")
public class SomeController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private StringRedisTemplate redisTemplate;
// 创建一个线程池,包含5个线程
private ForkJoinPool pool = new ForkJoinPool(5);
...
@HystrixCommand(fallbackMethod = "getHystrixHandler")
@GetMapping("/get/{id}")
public Depart getByIdHandler(@PathVariable("id") int id, HttpServletRequest request) {
String url = SERVICE_PROVIDER + "/provider/depart/get/" + id;
return restTemplate.getForObject(url, Depart.class);
}
// 定义服务降级方法,即响应给客户端的备选方案
public Depart getHystrixHandler(@PathVariable("id") int id, HttpServletRequest request) {
// 向管理员发出警报
String ip = request.getLocalAddr();
String key = ip + "_getHystrixHandler";
// 指定存放到Redis中的key为“ip_发生降级的方法名”
alarm(key);
Depart depart = new Depart();
depart.setId(id);
depart.setName("no this depart");
return depart;
}
// 降级发生后的报警
private void alarm(String key) {
BoundValueOperations<String, String> ops = redisTemplate.boundValueOps(key);
String value = ops.get();
if (value == null) {
synchronized (this) {
value = ops.get();
if (value == null) {
// 发送短信
sendMsg(key);
value="已发生服务降级";
ops.set(value, 10, TimeUnit.SECONDS);
}
}
}
}
// 使用线程池实现异步短信发送
private void sendMsg(String key) {
pool.submit(() -> {
System.out.println("发送服务降级警报:" + key);
});
}
}