写在前面
本文参照Spring官方文档,并实现了代码,在此做下笔记;
随着微服务的流行,服务调用的稳定性变得越来越重要。Sentinel 是面向分布式服务架构的流量控制组件,主要以“流量”为切入点,从流量控制、断路和负载保护等多个维度来保障服务可靠性。
哨兵拥有以下特性:
- 控制突发的流量,使其处于系统容量可接受的范围内,消息削峰填谷,集群流量控制、实时熔断下游不可用应用等。
- 全面实时监控:Sentinel 提供实时监控能力。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500台以下规模的集群的汇总运行情况。
- 广泛的开源生态系统:Sentinel 提供了开箱即用的模块,可以很容易地与其他开源框架/库集成,比如 Spring Cloud、Dubbo 和 gRPC,您只需要引入相应的依赖项并进行一些简单的配置。
- SPI 拓展:Sentinel 提供了易用和可靠的 SPI 拓展接口。您可以使用 SPI 拓展快速定制逻辑,例如,您可以定义自己的规则管理,或者适应动态数据源等。
这里附上一张官网总结的特性图:
Sentinel 入门
新建 sentinel-app
子模块。如果想要在项目中使用 Sentinel ,需要导入如下依赖:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!-- 项目需要使用到web相关的 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
下面是一个关于如何使用 Sentinel 的用例:
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(ServiceApplication.class, args);
}
}
@RestController
public class TestController {
@GetMapping(value = "/hello")
@SentinelResource("hello")
public String hello() {
return "Hello Sentinel";
}
}
@SentinelResource
注解是用于标识资源是否受到速率限制或降级。在上面的用例中,注解中的 ‘hello’ 属性值指的是资源名。
资源 是 Sentinel 中的核心概念之一。最常用的资源是我们代码中的 Java 方法。当然,也可以更灵活的定义资源,例如,将需要控制流量的代码用 Sentinel API SphU.entry("helloWorld")
和 entry.exit()
包围起来。
@GetMapping("/helloWorld")
public String helloWorld(){
try(Entry entry = SphU.entry("helloWorld")){
//被保护的业务逻辑
return "helloWorld";
}catch (BlockException exception){
//资源访问阻止,被限流或被降级
System.out.println("blocked!");
return "blocked!";
}
}
Sentinel Dashboard
在我们运行上面的测试用例之前,先让我们来搭建好 Sentinel Dashboard。
Sentinel Dashboard 是一种轻量级控制台,它提供机器发现、单服务器资源监视、集群资源数据概览以及规则管理等功能。要使用这些特性,您需要完成以下几个步骤:
集群统计只支持节点少于 500 个的集群,延迟大约 1 到 2 秒。
-
下载稳定的 dashboard JAR 包;
-
启动:
java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar
访问地址
http://<ip>:8080
:
3. 客户端访问 dashboard:在上面的测试用例中添加如下配置:
spring:
cloud:
sentinel:
transport:
port: 8720
dashboard: localhost:8080
使用如上的配置,你需要在程序启动后,手动访问用例的 http://192.168.3.18:18085/hello
地址,才能看如下界面:
不过,你也可以通过新增 eager
配置,在启动后就能初始化:
spring:
application:
name: sentinel-app
cloud:
sentinel:
transport:
dashboard: localhost:8080
heartbeat-interval-ms: 10000
port: 8720
eager: true
现在,启动我们的应用程序,可以尝试不断地访问接口地址,来观察 Dashboard 实时监控栏的变化!
OpenFeign 支持
实现 openFeign
支持,项目需要做比较大的改动;
-
引入依赖:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
不过由于这个依赖是由
spring-cloud-dependencies
所管理的,所以我们需要先在顶层模块下加入如下:<dependencyManagement> <dependencies> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>2.1.0.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Greenwich.SR2</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
-
引入
nacos-discovery
来支持远程调用:<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency>
-
修改启动类,使其支持
nacos-discovery
以及feign
:@SpringBootApplication @EnableDiscoveryClient @EnableFeignClients public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
-
添加
openFeign
测试用例:@FeignClient(name = "provider-app", fallback = EchoServiceFallback.class, configuration = FeignConfiguration.class) public interface EchoService { @GetMapping("/echo/{str}") String echo(@PathVariable("str") String str); } class FeignConfiguration{ @Bean public EchoServiceFallback echoServiceFallback(){ return new EchoServiceFallback(); } } class EchoServiceFallback implements EchoService{ @Override public String echo(String str) { return "echo fallback"; } }
-
修改当前的测试用例,使其使用
feign
的测试用例:@RestController public class TestController { @Resource private EchoService echoService; @GetMapping("/hello") @SentinelResource("hello") public String hello(){ return "Hello Sentinel"; } @GetMapping("/echo/{str}") public String echo(@PathVariable String str){ return echoService.echo(str); } }
-
修改配置文件:
server: port: 18085 spring: application: name: sentinel-app cloud: sentinel: transport: dashboard: localhost:8080 heartbeat-interval-ms: 10000 port: 8720 eager: true nacos: discovery: server-addr: 127.0.0.1:8848 network-interface: eth9 namespace: ecbbf883-5744-42e3-b448-09aabee90d7f feign: sentinel: enabled: true management: endpoints: web: exposure: include: nacos-discovery
现在,启动 provider-app
子模块(服务提供者)以及当前的 sentinel-app
项目;查看 Nacos
控制台,两个服务都注册成功。我们可以尝试不断访问上面的测试用例地址:http://192.168.3.18:18085/echo/hello
,然后观察 Sentinel Dashboard 的实时监控一栏的变化。
RestTemplate 支持
Sentinel 也支持保护 RestTemplate
的服务调用。我们只需要在构造 RestTemplate
bean 时添加 @SentinelRestTemplate
注解:
@Bean
@SentinelRestTemplate(blockHandler = "handleException", blockHandlerClass = ExceptionUtil.class)
public RestTemplate restTemplate() {
return new RestTemplate();
}
该注解的属性支持流量控制(blockHandler
,blockHandlerClass
) 和断路(fallback
,fallbackClass
)。并且,这个 blockHandler
或者 fallback
指定的方法必须是 blockHandlerClass
或者 fallbackClass
的静态方法。
方法的入参和返回应该和 org.springframework.http.client.ClientHttpRequestInterceptor#interceptor
一样,但是,可以多一个 BlockException
来通过 Sentinel 捕获异常。所以,handleException
方法如下:
public class ExceptionUtil {
public static ClientHttpResponse handleException(HttpRequest request, byte[] body, ClientHttpRequestExecution execution, BlockException exception) throws IOException {
return execution.execute(request, body);
}
}
现在,让我们来完善这个测试用例。除了上面的两处变动外,我们需要在 TestController
类中做些改动来测试。TestController
新增如下代码:
@Autowired
private LoadBalancerClient loadBalancerClient;
@Autowired
private RestTemplate restTemplate;
@Value("${spring.application.name}")
private String appName;
@GetMapping("/echo/app-name")
public String echoAppName(){
ServiceInstance serviceInstance = loadBalancerClient.choose("provider-app");
String path = String.format("http://%s:%s/echo/%s", serviceInstance.getHost(), serviceInstance.getPort(), appName);
System.out.println("request path: " + path);
return restTemplate.getForObject(path, String.class);
}
重启应用程序,访问上面的接口地址,观察 dashboard 实时监控栏变化!(关于流控的具体介绍,我打算重新写一篇博客来介绍)。
Sentinel 也提供了对 Zuul
以及 Gateway
的支持,这里就不再介绍了!
Sentinel 端点
Sentinel 提供了一个 /sentinel
端点,该端点暴露的主要内容如下:
- appName: 应用程序名;
- logDir: 日志目录;
- logUsePid: 日志名是否使用 Pid;
- blockPage: sentinel 阻塞后直接跳转的页面;
- metricsFileSize: 度量的文件大小;
- metricsFileCharset: 度量的文件编码;
- totalMetricsFileCount: 度量文件的总文件计数;
- consoleServer: 哨兵的仪表盘地址;
- clientIp: client ip;
- heartbeatIntervalMs: 客户端与仪表盘的心跳间隔;
- clientPort: 客户端需要公开端口以与仪表板交互;
- coldFactor: cold factor;
- filter: CommonFilter related properties, such as order, urlPatterns and enable;
- datasource: datasource configuration info by client;
- rules: the rule that the client takes effect internally contains flowRules, degradeRules, systemRules, authorityRule, paramFlowRule;
末尾有几个内容不太懂,我们都将在后续一一弄明白。注意,端点都需要在配置文件中进行暴露以后,才可以访问。
Sentinel 配置
当 ApplicationContext
中存在下列相应的 bean 时,会产生相应的行为:
-
UrlCleaner
: 处理 url,对一些路径变量进行统一处理,例如collect_item_relation--10200012121-.html
将被转换为collect_item_relation.html
。 -
UrlBlockHandler
: 在 URL 被 sentinel 阻塞时,应执行的处理程序; -
RequestOriginParser
: 解析给定请求的来源;
上面的 bean 都将通过 WebCallbackManager
统一管理调用。
Sentinel 的配置可以查看相应的文档或者 SentinelProperties
类。
我与风来
认认真真学习,做思想的产出者,而不是文字的搬运工。
但行善事,莫问前程。