微服务之间相互调用,如果有一个服务出现故障,无法对外提供服务,那么调用该服务的应用也会跟着陷入无线的等待,直至客户端出现链接超时,这种客户体验及其不好,而且严重影响系统的效率。熔断机制的出现,就是为了解决这个问题。微服务的熔断机制基本原理为当一个服务(A)调用另一个服务(B)的时候,如果后一个服务(B)出现故障,则不影响前一个服务(A),同时前者能够及时提供反馈到客户端,避免用户长久等待。
Spring Cloud 中的Hystrix 就是一个著名的微服务熔断组件,Sentinel 对其进行了适配,写法上与 Hystrix 保持基本一致,从而让开发者的迁移更加方便。
本文将介绍基于 Spring Cloud Alibaba 2.2 使用 Sentinel 实现服务熔断。
准备工作:
安装 Alibaba Sentinel
了解 Spring Cloud Alibaba Sentinel 流量限制控制台
Sentinel Gtihub: https://github.com/alibaba/Sentinel
Spring Cloud Alibaba Github: https://github.com/alibaba/spring-cloud-alibaba
./cloud-alibaba-consumer-sentinel/pom.xml
<dependencies>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
<version>${spring-cloud-alibaba.version}version>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-sentinelartifactId>
<version>${spring-cloud-alibaba.version}version>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
<version>${spring-cloud-openfeign.version}version>
dependency>
dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-alibaba-dependenciesartifactId>
<version>${spring-cloud-alibaba.version}version>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
其中 ${spring-cloud-alibaba.version}
的版本为 2.2.3.RELEASE
, ${spring-cloud-openfeign.version}
的版本为 2.2.5.RELEASE
注意事项: Spring Cloud Alibaba 2.2.3.RELEASE 版本支持的 Spring Boot 版本为 2.3.1.RELEASE
,建议在搭建项目时要保持版本的一致性,Spring Boot 版本过高或过低都可能导致不兼容问题
./cloud-alibaba-consumer-sentinel/src/main/resources/application.yml
## config
## server
server:
port: 8608
## spring
spring:
application:
name: cloud-alibaba-consumer-sentinel
cloud:
nacos:
discovery:
server-addr: 172.16.140.10:8688
sentinel:
transport:
dashboard: 172.16.140.10:8666
port: 8720
## feign
feign:
sentinel:
enabled: true
## endpoint
management:
endpoints:
web:
exposure:
include: "*"
配置简要说明:
spring.application.name
: 应用名称, Nacos 服务名称
spring.cloud.nacos.discovery.server-addr
: Nacos 服务注册中心地址
spring.cloud.sentinel.transport.dashboard
: Sentinel 控制台地址,包括 ip 和端口
spring.cloud.sentinel.transport.port
: 客户端与 Sentinel 控制台的通讯端口,是项目所在服务器的端口,不是 Sentinel 控制台所在服务器的端口
feign.sentinel.enabled
: 这里需要手动开启 Sentinel 对 Feign 的支持
./cloud-alibaba-consumer-sentinel/src/main/java/com/ljq/demo/springboot/alibaba/consumer/sentinel/service/NacosConsumerService.java
package com.ljq.demo.springboot.alibaba.consumer.sentinel.service;
import com.ljq.demo.springboot.alibaba.consumer.sentinel.common.constant.NacosConst;
import com.ljq.demo.springboot.alibaba.consumer.sentinel.model.param.HelloParam;
import com.ljq.demo.springboot.alibaba.consumer.sentinel.service.fallback.ConsumerFallBackFactory;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.cloud.openfeign.SpringQueryMap;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
/**
* @Description: Nacos 服务消费者业务层
* @Author: junqiang.lu
* @Date: 2020/12/2
*/
@FeignClient(name = NacosConst.CLOUD_ALIBABA_SENTINEL_DASHBOARD, fallbackFactory = ConsumerFallBackFactory.class)
public interface NacosConsumerService {
/**
* hello
* @param helloParam
* @return
*/
@GetMapping(value = "/api/cloud/alibaba/sentinel/dashboard/hello", produces = {MediaType.APPLICATION_JSON_VALUE})
ResponseEntity<String> hello(@SpringQueryMap HelloParam helloParam);
/**
*
* @param helloParam
* @return
*/
@PostMapping(value = "/api/cloud/alibaba/sentinel/dashboard/replay", produces = {MediaType.APPLICATION_JSON_VALUE})
ResponseEntity<String> replay(@RequestBody HelloParam helloParam);
}
./cloud-alibaba-consumer-sentinel/src/main/java/com/ljq/demo/springboot/alibaba/consumer/sentinel/service/fallback/ConsumerFallBack.java
package com.ljq.demo.springboot.alibaba.consumer.sentinel.service.fallback;
import com.ljq.demo.springboot.alibaba.consumer.sentinel.model.param.HelloParam;
import com.ljq.demo.springboot.alibaba.consumer.sentinel.service.NacosConsumerService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
/**
* @Description: 熔断回调方法
* @Author: junqiang.lu
* @Date: 2020/12/2
*/
@Slf4j
public class ConsumerFallBack implements NacosConsumerService {
private Throwable throwable;
public ConsumerFallBack(Throwable throwable) {
this.throwable = throwable;
}
/**
* hello
* @param helloParam
* @return
*/
@Override
public ResponseEntity<String> hello(HelloParam helloParam) {
String result = "Sorry," + helloParam.getName() + ",网络拥堵,稍后重试";
log.info("call back result: {}", result);
log.error("{}", throwable.getMessage());
return ResponseEntity.ok(result);
}
/**
* 回复
*
* @param helloParam
* @return
*/
@Override
public ResponseEntity<String> replay(HelloParam helloParam) {
String result = "Sorry," + helloParam.getName() + ",网络拥堵,稍后重试";
log.info("call back result: {}", result);
log.error("{}", throwable.getMessage());
return ResponseEntity.ok(result);
}
}
./cloud-alibaba-consumer-sentinel/src/main/java/com/ljq/demo/springboot/alibaba/consumer/sentinel/service/fallback/ConsumerFallBackFactory.java
package com.ljq.demo.springboot.alibaba.consumer.sentinel.service.fallback;
import feign.hystrix.FallbackFactory;
import org.springframework.stereotype.Component;
/**
* @Description: Nacos 熔断工厂
* @Author: junqiang.lu
* @Date: 2020/12/3
*/
@Component
public class ConsumerFallBackFactory implements FallbackFactory<ConsumerFallBack> {
@Override
public ConsumerFallBack create(Throwable cause) {
return new ConsumerFallBack(cause);
}
}
./cloud-alibaba-consumer-sentinel/src/main/java/com/ljq/demo/springboot/alibaba/consumer/sentinel/controller/NacosConsumerController.java
package com.ljq.demo.springboot.alibaba.consumer.sentinel.controller;
import com.ljq.demo.springboot.alibaba.consumer.sentinel.model.param.HelloParam;
import com.ljq.demo.springboot.alibaba.consumer.sentinel.service.NacosConsumerService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
/**
* @Description: nacos 服务消费者控制层
* @Author: junqiang.lu
* @Date: 2020/12/2
*/
@Slf4j
@RestController
@RequestMapping("/api/nacos/consumer")
public class NacosConsumerController {
@Autowired
private NacosConsumerService consumerService;
@GetMapping(value = "/hello", produces = {MediaType.APPLICATION_JSON_VALUE})
public ResponseEntity<String> hello(HelloParam helloParam) {
ResponseEntity<String> response = consumerService.hello(helloParam);
log.info("response: {}", response.getBody());
return response;
}
@PostMapping(value = "/replay", produces = {MediaType.APPLICATION_JSON_VALUE})
public ResponseEntity<String> replay(@RequestBody HelloParam helloParam) {
ResponseEntity<String> response = consumerService.replay(helloParam);
log.info("response: {}", response.getBody());
return response;
}
}
./cloud-alibaba-consumer-sentinel/src/main/java/com/ljq/demo/springboot/alibaba/consumer/sentinel/CloudAlibabaConsumerSentinelApplication.java
package com.ljq.demo.springboot.alibaba.consumer.sentinel;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
/**
* @author junqiang.lu
*/
@EnableDiscoveryClient
@EnableFeignClients
@SpringBootApplication
public class CloudAlibabaConsumerSentinelApplication {
public static void main(String[] args) {
SpringApplication.run(CloudAlibabaConsumerSentinelApplication.class, args);
}
}
@EnableDiscoveryClient
: spring cloud 服务发现注解,使用该注解可以在服务注册中心发现、管理该服务
@EnableFeignClients
:启用 Feign 的注解
请求参数类
./cloud-alibaba-consumer-sentinel/src/main/java/com/ljq/demo/springboot/alibaba/consumer/sentinel/model/param/HelloParam.java
package com.ljq.demo.springboot.alibaba.consumer.sentinel.model.param;
import lombok.Data;
import java.io.Serializable;
/**
* @Description: 用户实体类
* @Author: junqiang.lu
* @Date: 2020/12/2
*/
@Data
public class HelloParam implements Serializable {
private static final long serialVersionUID = -686738885661126900L;
/**
* 用户名
*/
private String name;
}
常量类
./cloud-alibaba-consumer-sentinel/src/main/java/com/ljq/demo/springboot/alibaba/consumer/sentinel/common/constant/NacosConst.java
package com.ljq.demo.springboot.alibaba.consumer.sentinel.common.constant;
/**
* @Description: Nacos 相关常量
* @Author: junqiang.lu
* @Date: 2020/12/2
*/
public class NacosConst {
private NacosConst() {
}
public static final String CLOUD_ALIBABA_SENTINEL_DASHBOARD = "cloud-alibaba-sentinel-dashboard";
}
Sentinel 提供的注解 @SentinelResource
用于标记资源,其 value
属性即为资源识别编号,被标记后可以在 Sentinel 控制台对其进行流量监控和限制等操作,该注解不仅可以用在服务调用者上,还可以用在服务提供者上。本文主要介绍了 Sentinel 和 Feign 结合使用的示例,因此并没有用到该注解。在本示例中 @SentinelResource
可以再添加到 cloud-alibaba-sentinel-dashboard
项目 Controller 里提供的两个接口上。这样就可以做到更好地服务熔断。
更多信息可参考: 官方文档: Spring Cloud Alibaba Sentinel
依次启动 Sentinel 控制台项目、待调用服务 cloud-alibaba-sentinel-dashboard
、当前项目服务 cloud-alibaba-consumer-sentinel
请求当前项目的两个接口(可暂不传参数,主要为了让 Sentinel 控制台显示当前服务)
打开 Sentinel 控制台
接口一:
接口地址与请求参数:
http://127.0.0.1:8608/api/nacos/consumer/hello?name=%E8%92%99%E5%A8%9C%E4%B8%BD%E8%8E%8E
请求方式: GET
返回结果:
Hello,蒙娜丽莎
服务消费者(cloud-alibaba-consumer-sentinel
)日志:
2020-12-17 13:34:31.130 INFO 7339 --- [nio-8608-exec-1] c.l.d.s.a.c.s.c.NacosConsumerController : response: Hello,蒙娜丽莎
服务提供者(cloud-alibaba-sentinel-dashboard
)日志:
2020-12-17 13:34:31.014 INFO 7346 --- [nio-8606-exec-1] .d.s.a.s.d.c.SentinelDashboardController : serverPort: 8606
2020-12-17 13:34:31.015 INFO 7346 --- [nio-8606-exec-1] .d.s.a.s.d.c.SentinelDashboardController : result: Hello,蒙娜丽莎
接口二:
接口地址:
http://127.0.0.1:8608/api/nacos/consumer/replay
请求参数:
{
"name": "马保国"
}
请求方式: POST
返回结果:
Hi,马保国,I'm fine,Thank you.
服务消费者(cloud-alibaba-consumer-sentinel
)日志:
2020-12-17 13:34:35.425 INFO 7339 --- [nio-8608-exec-2] c.l.d.s.a.c.s.c.NacosConsumerController : response: Hi,马保国,I'm fine,Thank you.
服务提供者(cloud-alibaba-sentinel-dashboard
)日志:
2020-12-17 13:34:35.422 INFO 7346 --- [nio-8606-exec-2] .d.s.a.s.d.c.SentinelDashboardController : result: Hi,马保国,I'm fine,Thank you.
Sentinel 控制台界面
对服务提供者的接口进行限流,QPS 上限为 5
操作步骤:
点击左侧「cloud-alibaba-sentinel-dashboard」,点击「簇点链路」,在hello
和 replay
接口右侧点击「流控」按钮,将 QPS 设置为 5
添加成功后,点击左侧「流控规则」,展示设置的限流信息
使用测试工具对接口进行测试
这里以 Postman 的 Runner 功能为例
请求的接口为服务消费者(cloud-alibaba-consumer-sentinel
) 的两个接口
总共请求 1000 次,每间隔 10 毫秒请求一次,理论 QPS 为 100/2 = 50(Runner 是单线程串行请求接口的)
启动 Runner
Sentinel 控制台
服务消费者部分后台日志:
2020-12-17 14:12:02.061 INFO 7339 --- [nio-8608-exec-8] c.l.d.s.a.c.s.c.NacosConsumerController : response: Hello,蒙娜丽莎
2020-12-17 14:12:02.103 INFO 7339 --- [nio-8608-exec-9] c.l.d.s.a.c.s.c.NacosConsumerController : response: Hi,马保国,I'm fine,Thank you.
2020-12-17 14:12:02.144 INFO 7339 --- [io-8608-exec-10] c.l.d.s.a.c.s.c.NacosConsumerController : response: Hello,蒙娜丽莎
2020-12-17 14:12:02.190 INFO 7339 --- [nio-8608-exec-1] c.l.d.s.a.c.s.s.f.ConsumerFallBack : call back result: Sorry,马保国,网络拥堵,稍后重试
2020-12-17 14:12:02.190 ERROR 7339 --- [nio-8608-exec-1] c.l.d.s.a.c.s.s.f.ConsumerFallBack : [429] during [POST] to [http://cloud-alibaba-sentinel-dashboard/api/cloud/alibaba/sentinel/dashboard/replay] [NacosConsumerService#replay(HelloParam)]: [Blocked by Sentinel (flow limiting)]
2020-12-17 14:12:02.190 INFO 7339 --- [nio-8608-exec-1] c.l.d.s.a.c.s.c.NacosConsumerController : response: Sorry,马保国,网络拥堵,稍后重试
2020-12-17 14:12:02.227 INFO 7339 --- [nio-8608-exec-2] c.l.d.s.a.c.s.s.f.ConsumerFallBack : call back result: Sorry,蒙娜丽莎,网络拥堵,稍后重试
2020-12-17 14:12:02.227 ERROR 7339 --- [nio-8608-exec-2] c.l.d.s.a.c.s.s.f.ConsumerFallBack : [429] during [GET] to [http://cloud-alibaba-sentinel-dashboard/api/cloud/alibaba/sentinel/dashboard/hello?serialVersionUID=-686738885661126900&name=%E8%92%99%E5%A8%9C%E4%B8%BD%E8%8E%8E] [NacosConsumerService#hello(HelloParam)]: [Blocked by Sentinel (flow limiting)]
2020-12-17 14:12:02.227 INFO 7339 --- [nio-8608-exec-2] c.l.d.s.a.c.s.c.NacosConsumerController : response: Sorry,蒙娜丽莎,网络拥堵,稍后重试
2020-12-17 14:12:02.276 INFO 7339 --- [nio-8608-exec-3] c.l.d.s.a.c.s.s.f.ConsumerFallBack : call back result: Sorry,马保国,网络拥堵,稍后重试
2020-12-17 14:12:02.276 ERROR 7339 --- [nio-8608-exec-3] c.l.d.s.a.c.s.s.f.ConsumerFallBack : [429] during [POST] to [http://cloud-alibaba-sentinel-dashboard/api/cloud/alibaba/sentinel/dashboard/replay] [NacosConsumerService#replay(HelloParam)]: [Blocked by Sentinel (flow limiting)]
2020-12-17 14:12:02.276 INFO 7339 --- [nio-8608-exec-3] c.l.d.s.a.c.s.c.NacosConsumerController : response: Sorry,马保国,网络拥堵,稍后重试
2020-12-17 14:12:02.324 INFO 7339 --- [nio-8608-exec-4] c.l.d.s.a.c.s.s.f.ConsumerFallBack : call back result: Sorry,蒙娜丽莎,网络拥堵,稍后重试
2020-12-17 14:12:02.324 ERROR 7339 --- [nio-8608-exec-4] c.l.d.s.a.c.s.s.f.ConsumerFallBack : [429] during [GET] to [http://cloud-alibaba-sentinel-dashboard/api/cloud/alibaba/sentinel/dashboard/hello?serialVersionUID=-686738885661126900&name=%E8%92%99%E5%A8%9C%E4%B8%BD%E8%8E%8E] [NacosConsumerService#hello(HelloParam)]: [Blocked by Sentinel (flow limiting)]
2020-12-17 14:12:02.324 INFO 7339 --- [nio-8608-exec-4] c.l.d.s.a.c.s.c.NacosConsumerController : response: Sorry,蒙娜丽莎,网络拥堵,稍后重试
2020-12-17 14:12:02.374 INFO 7339 --- [nio-8608-exec-5] c.l.d.s.a.c.s.s.f.ConsumerFallBack : call back result: Sorry,马保国,网络拥堵,稍后重试
2020-12-17 14:12:02.374 ERROR 7339 --- [nio-8608-exec-5] c.l.d.s.a.c.s.s.f.ConsumerFallBack : [429] during [POST] to [http://cloud-alibaba-sentinel-dashboard/api/cloud/alibaba/sentinel/dashboard/replay] [NacosConsumerService#replay(HelloParam)]: [Blocked by Sentinel (flow limiting)]
2020-12-17 14:12:02.374 INFO 7339 --- [nio-8608-exec-5] c.l.d.s.a.c.s.c.NacosConsumerController : response: Sorry,马保国,网络拥堵,稍后重试
2020-12-17 14:12:02.411 INFO 7339 --- [nio-8608-exec-6] c.l.d.s.a.c.s.s.f.ConsumerFallBack : call back result: Sorry,蒙娜丽莎,网络拥堵,稍后重试
2020-12-17 14:12:02.411 ERROR 7339 --- [nio-8608-exec-6] c.l.d.s.a.c.s.s.f.ConsumerFallBack : [429] during [GET] to [http://cloud-alibaba-sentinel-dashboard/api/cloud/alibaba/sentinel/dashboard/hello?serialVersionUID=-686738885661126900&name=%E8%92%99%E5%A8%9C%E4%B8%BD%E8%8E%8E] [NacosConsumerService#hello(HelloParam)]: [Blocked by Sentinel (flow limiting)]
2020-12-17 14:12:02.411 INFO 7339 --- [nio-8608-exec-6] c.l.d.s.a.c.s.c.NacosConsumerController : response: Sorry,蒙娜丽莎,网络拥堵,稍后重试
2020-12-17 14:12:02.454 INFO 7339 --- [nio-8608-exec-7] c.l.d.s.a.c.s.s.f.ConsumerFallBack : call back result: Sorry,马保国,网络拥堵,稍后重试
2020-12-17 14:12:02.454 ERROR 7339 --- [nio-8608-exec-7] c.l.d.s.a.c.s.s.f.ConsumerFallBack : [429] during [POST] to [http://cloud-alibaba-sentinel-dashboard/api/cloud/alibaba/sentinel/dashboard/replay] [NacosConsumerService#replay(HelloParam)]: [Blocked by Sentinel (flow limiting)]
2020-12-17 14:12:02.454 INFO 7339 --- [nio-8608-exec-7] c.l.d.s.a.c.s.c.NacosConsumerController : response: Sorry,马保国,网络拥堵,稍后重试
2020-12-17 14:12:02.505 INFO 7339 --- [nio-8608-exec-8] c.l.d.s.a.c.s.c.NacosConsumerController : response: Hello,蒙娜丽莎
2020-12-17 14:12:02.557 INFO 7339 --- [nio-8608-exec-9] c.l.d.s.a.c.s.c.NacosConsumerController : response: Hi,马保国,I'm fine,Thank you.
2020-12-17 14:12:02.619 INFO 7339 --- [io-8608-exec-10] c.l.d.s.a.c.s.c.NacosConsumerController : response: Hello,蒙娜丽莎
2020-12-17 14:12:02.678 INFO 7339 --- [nio-8608-exec-1] c.l.d.s.a.c.s.c.NacosConsumerController : response: Hi,马保国,I'm fine,Thank you.
2020-12-17 14:12:02.759 INFO 7339 --- [nio-8608-exec-2] c.l.d.s.a.c.s.c.NacosConsumerController : response: Hello,蒙娜丽莎
2020-12-17 14:12:02.846 INFO 7339 --- [nio-8608-exec-3] c.l.d.s.a.c.s.c.NacosConsumerController : response: Hi,马保国,I'm fine,Thank you.
Postman 部分日志:
测试结果可以看出服务提供者端的QPS 在限流之后最高只能达到5,超过的部分请求均被拒绝。在后台日志中可以看出请求被拒绝之后走了熔断方法。从Postman日志中可以看出一个完整请求的时间很短(50毫秒以内),并没有处于长时间的等待,从而提升了用户体验。
至此,已经实现了使用 Sentinel 对服务进行限流,以及集成 Feign 对服务进行熔断控制的功能。
官方文档: Spring Cloud Alibaba Sentinel
Spring Cloud Alibaba系列教程 - Spring Cloud Alibaba 熔断(Sentinel)
Gtihub 源码地址 : https://github.com/Flying9001/springBootDemo
个人公众号:404Code,分享半个互联网人的技术与思考,感兴趣的可以关注.