Spring Cloud Alibaba | Sentinel: Traffic Defense Advanced combat soldiers distributed systems
Before reading this article, it is recommended to read the "Spring Cloud Alibaba | Sentinel: Traffic Distributed Systems defensive combat soldiers basis" .
1. Sentinel integration Feign and RestTemplate
Sentinel now supports both Feign and RestTemplate, we need to introduce corresponding dependence, need to open Sentinel Feign support in the configuration file when using Feign: the feign.sentinel.enabled=true
same time need to join openfeign starter
rely on the sentinel starter
automated configuration class effect. When using RestTemplate you need to add at construction RestTemplate of Bean's @SentinelRestTemplate
comment, open the Sentinel's support for RestTemplate.
1.1 Creating a parent project sentinel-springcloud-high:
Parent project pom.xml as follows:
Listing: Alibaba / Sentinel-springcloud-High / pom.xml
***
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
Public Sentinel introduced in components do flow control, the introduction of Nacos do service center.
1.2 create a sub-project provider_server:
Profile application.yml as follows:
Listing: Alibaba / Sentinel-springcloud-High / provider_server / pom.xml
***
server:
port: 8000
spring:
application:
name: spring-cloud-provider-server
cloud:
nacos:
discovery:
server-addr: 192.168.44.129:8848
sentinel:
transport:
dashboard: localhost:8080
port: 8720
management:
endpoints:
web:
cors:
allowed-methods: '*'
Interface test class HelloController.java follows:
代码清单:Alibaba/sentinel-springcloud-high/provider_server/src/main/java/com/springcloud/provider_server/controller/HelloController.java
***
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello(HttpServletRequest request) {
return "Hello, port is: " + request.getLocalPort();
}
}
1.3 create a sub-project consumer_server:
Sub-project relies pom.xml as follows:
Listing: Alibaba / Sentinel-springcloud-High / consumer_server / pom.xml
***
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
Profile application.yml as follows:
代码清单:Alibaba/sentinel-springcloud-high/consumer_server/src/main/resources/application.yml
***
server:
port: 9000
spring:
application:
name: spring-cloud-consumer-server
cloud:
nacos:
discovery:
server-addr: 192.168.44.129:8848
sentinel:
transport:
dashboard: localhost:8080
port: 8719
management:
endpoints:
web:
cors:
allowed-methods: '*'
feign:
sentinel:
enabled: true
Here the use of feign.sentinel.enabled=true
open support for the Sentinel's Feign.
Interface Test class HelloController.java
代码清单:Alibaba/sentinel-springcloud-high/consumer_server/src/main/java/com/springcloud/consumer_server/controller/HelloController.java
***
@RestController
public class HelloController {
@Autowired
HelloRemote helloRemote;
@Autowired
RestTemplate restTemplate;
@GetMapping("/helloByFeign")
public String helloByFeign() {
return helloRemote.hello();
}
@GetMapping("/helloByRestTemplate")
public String helloByRestTemplate() {
return restTemplate.getForObject("http://spring-cloud-provider-server/hello/", String.class);
}
}
Sentinel has done for integration, we use Feign place without additional comment. At the same time, @FeignClient
notes of all properties, Sentinel are made compatible.
Start main class Ch122ConsumerServerApplication.java follows:
代码清单:Alibaba/sentinel-springcloud-high/consumer_server/src/main/java/com/springcloud/consumer_server/ConsumerServerApplication.java
***
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class Ch122ConsumerServerApplication {
public static void main(String[] args) {
SpringApplication.run(Ch122ConsumerServerApplication.class, args);
}
@Bean
@LoadBalanced
@SentinelRestTemplate
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
When using RestTemplate need to be increased @SentinelRestTemplate
to support RestTemplate of open Sentinel.
1.4 Test
Start engineering provider_server and consumer_server, provider_server modify the startup configuration, start two instances, open the browser to access: http: // localhost: 9000 / helloByFeign and http: // localhost: 9000 / helloByRestTemplate , refresh a few times, you can see the page alternately display Hello, port is: 8000
and Hello, port is: 8001
, indicating the current load balancing normal, now view Sentinel console, as shown:
1.5 Flow Control Test
At this point in the stream select a cluster control left, click flow control as shown:
Here we configure a simple rule, configured QPS limit of 1, click Add, as shown:
Here to explain what the QPS, QPS is it simply a number of visits per second, fast refresh needs to be repeated when we tested here http: // localhost: 9000 / helloByFeign and http: // localhost: 9000 / helloByRestTemplate , in the course of refreshing we can see that the page will display an error message such as: Blocked by Sentinel (flow limiting)
, that we have to configure Sentinel limiting success, then let's look at Sentinel console, you can see the number of successful and we have just limiting access, as Figure:
2. Service downgraded
On a summary, we introduced the use Feign and RestTemplate integrate Sentinel, and made QPS current limit on the Sentinel console and limiting success, after the success of current limiting, by default, Sentinel process of limiting resources is controlled direct throw. In no reasonable business undertaking or docking at the front end of the case may be so, but normal circumstances in order to better service users, will achieve some special treatment after being limiting, we do not want to show a blunt error. This section, we introduce the service degradation process.
2.1 create a sub-project consumer_fallback
Feign service degradation class HelloRemoteFallBack.java as follows:
代码清单:Alibaba/sentinel-springcloud-high/consumer_fallback/src/main/java/com/springcloud/consumer_fallback/fallback/HelloRemoteFallBack.java
***
@Component
public class HelloRemoteFallBack implements HelloRemote {
@Override
public String hello() {
return "Feign FallBack Msg";
}
}
Correspondingly, there needs to be done on the part of HelloRemote.java configured such that the current limit, triggering the implementation of our service degradation of service degradation classes, as follows:
代码清单:ch12_2/ch12_2_consumer_fallback/src/main/java/com/springcloud/book/ch12_2_consumer_fallback/remote/HelloRemote.java
***
@FeignClient(name = "spring-cloud-provider-server", fallback = HelloRemoteFallBack.class)
public interface HelloRemote {
@GetMapping("/hello")
String hello();
}
fallback = HelloRemoteFallBack.class
Designated service degradation handling class HelloRemoteFallBack.class
.
RestTemplate service degradation Tools ExceptionUtil.java as follows:
代码清单:Alibaba/sentinel-springcloud-high/consumer_fallback/src/main/java/com/springcloud/consumer_fallback/remote/HelloRemote.java
***
public class ExceptionUtil {
private final static Logger logger = LoggerFactory.getLogger(ExceptionUtil.class);
public static SentinelClientHttpResponse handleException(HttpRequest request, byte[] body, ClientHttpRequestExecution execution, BlockException ex) {
logger.error(ex.getMessage(), ex);
return new SentinelClientHttpResponse("RestTemplate FallBack Msg");
}
}
Here, too, need to modify RestTemplate registered as Bean's place, making RestTemplate trigger handler class service degradation code execution after we wrote it, Ch122ConsumerFallbackApplication.java code is as follows:
代码清单:Alibaba/sentinel-springcloud-high/consumer_fallback/src/main/java/com/springcloud/consumer_fallback/ConsumerFallbackApplication.java
***
@Bean
@LoadBalanced
@SentinelRestTemplate(blockHandler = "handleException", blockHandlerClass = ExceptionUtil.class)
public RestTemplate restTemplate() {
return new RestTemplate();
}
It should be noted, @SentinelRestTemplate
annotation property supports limiting ( blockHandler
, blockHandlerClass
) and downgrading ( fallback
, fallbackClass
) processing.
Wherein blockHandler
or fallback
corresponding to the attribute must be a corresponding method blockHandlerClass
or fallbackClass
attribute static method.
@SentinelRestTemplate
Notes limiting ( blockHandler
, blockHandlerClass
) and downgrading ( fallback
, fallbackClass
) property is not mandatory to fill in.
When using the Sentinel is fused RestTemplate call, returns RestTemplate request block by sentinel
the method information, or may be prepared by the process returns information corresponding to itself. Here provides SentinelClientHttpResponse
for constructing return information.
2.2 Test
Sequential start provider_server and consumer_fallback two sub-projects. First alternate browser to http: // localhost: 9090 / helloByFeign and http: // localhost: 9090 / helloByRestTemplate, and then open the Sentinel console, increase the current limit information on the two interfaces, note that you want to limit here information added to the stream of resources, particularly as shown:
Refresh in the browser two links, two limiting information can be displayed properly in the browser, the test is successful, see the Sentinel console again, we can see-rejected traffic statistics, as shown:
3. Sentinel Integrated Service Gateway limiting
Sentinel currently supports mainstream Spring Cloud Gateway, Zuul, etc. API Gateway be limiting. Look at the official chart, as shown:
From the official view of this, we can see, Sentinel of Zuul limiting mainly through three Filter accomplished, for Spring Cloud Gateway is through a SentinleGatewayFilter
and a BlockRequestHandler
to complete.
Sentinel 1.6.0 introduced Sentinel API Gateway Adapter Common module, this module contains rules limiting the gateway API and custom entities and management logic:
- GatewayFlowRule: Gateway limiting rules for the API Gateway scene customized limiting rules can be limiting for API grouping different route or custom supports customized current limiting for the request parameters, Header, Source IP, etc. .
- ApiDefinition: API defines user-defined packets, it can be seen as some combination of matching the URL. For example, we can define an API called my_api, requested path mode / foo / ** and / baz / ** returned into my_api the API grouped below. When the current limit can be limiting for this API custom grouping dimension.
3.1 Zuul 1.x
Sentinel provides Zuul 1.x adapter module, can provide current limiting and resources dimensions of Zuul Gateway:
- Dimensions route: i.e., disposed in a routing entry Spring configuration file, the resource name corresponding route ID (corresponding to the proxy field RequestContext)
- Custom API dimensions: the user can use the Sentinel API provided from the API to define some packets
3.1.1 create a sub-project zuul_server
Project relies pom.xml as follows:
Listing: Alibaba / Sentinel-springcloud-High / zuul_server / pom.xml
***
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-zuul-adapter</artifactId>
</dependency>
Because here sentinel-zuul-adapter
it is not included in spring-cloud-starter-alibaba-sentinel
necessary to introduce a separate manually.
3.1.2 Profile application.yml as follows:
Listing: Alibaba / Sentinel-springcloud-High / zuul_server / src / main / Resources / application.yml
***
server:
port: 18080
spring:
application:
name: spring-cloud-zuul-server
cloud:
nacos:
discovery:
server-addr: 192.168.44.129:8848
sentinel:
transport:
dashboard: localhost:8080
port: 8720
zuul:
routes:
consumer-route:
path: /consumer/**
serviceId: spring-cloud-consumer-fallback
3.1.3 downgrade defined class ZuulFallbackProvider.java follows:
代码清单:Alibaba/sentinel-springcloud-high/zuul_server/src/main/java/com/springcloud/zuul_server/fallback/ZuulFallbackProvider.java
***
public class ZuulFallbackProvider implements ZuulBlockFallbackProvider {
@Override
public String getRoute() {
return "*";
}
@Override
public BlockResponse fallbackResponse(String route, Throwable cause) {
RecordLog.info(String.format("[Sentinel DefaultBlockFallbackProvider] Run fallback route: %s", route));
if (cause instanceof BlockException) {
return new BlockResponse(429, "Sentinel block exception", route);
} else {
return new BlockResponse(500, "System Error", route);
}
}
}
3.1.4 At the same time, we need to 3 injection of Sentinel Filter Spring, configuration class as follows:
代码清单:Alibaba/sentinel-springcloud-high/zuul_server/src/main/java/com/springcloud/zuul_server/config/ZuulConfig.java
***
@Configuration
public class ZuulConfig {
@Bean
public ZuulFilter sentinelZuulPreFilter() {
// We can also provider the filter order in the constructor.
return new SentinelZuulPreFilter();
}
@Bean
public ZuulFilter sentinelZuulPostFilter() {
return new SentinelZuulPostFilter();
}
@Bean
public ZuulFilter sentinelZuulErrorFilter() {
return new SentinelZuulErrorFilter();
}
/**
* 注册 ZuulFallbackProvider
*/
@PostConstruct
public void doInit() {
ZuulBlockFallbackManager.registerProvider(new ZuulFallbackProvider());
}
}
Ultimately, you need to configure JVM startup parameters before starting to increase -Dcsp.sentinel.app.type=1
, to tell us start the Sentinel Console service for the API Gateway type.
3.1.5 Testing
Sequential engineering promoter provider_server, consumer_fallback, zuul_server, open the browser to access: http: // localhost: 18080 / consumer / helloByFeign, then we open the Sentinel console to see zuul_server services as:
We customize limiting policy, is still QPS 1, we once again set http: // localhost: 18080 / consumer / helloByFeign page, then, the page already produced positive current limit, and after limiting the content of the display is as follows:
{"code":429, "message":"Sentinel block exception", "route":"consumer-route"}
Note here that the definition of limiting resources, do not define the wrong place, limiting the definition as shown:
3.2 Spring Cloud Gateway
From the start of version 1.6.0, Sentinel offers Spring Cloud Gateway adapter module, two resources can provide current limiting dimensions:
- Dimensions route: i.e., disposed in a routing entry Spring configuration file, a corresponding resource named routeId
- Custom API dimensions: the user can use the Sentinel API provided from the API to define some packets
3.2.1 create a sub-project gateway_server
Project relies pom.xml as follows:
Listing: Alibaba / Sentinel-springcloud-High / gateway_server / pom.xml
***
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
</dependency>
3.2.2 Profile application.yml as follows:
Listing: Alibaba / Sentinel-springcloud-High / gateway_server / src / main / Resources / application.yml
***
server:
port: 28080
spring:
application:
name: spring-cloud-gateway-server
cloud:
nacos:
discovery:
server-addr: 192.168.44.129:8848
sentinel:
transport:
dashboard: localhost:8080
port: 8720
gateway:
enabled: true
discovery:
locator:
lower-case-service-id: true
routes:
- id: consumer_server
uri: lb://spring-cloud-consumer-fallback
predicates:
- Method=GET
3.2.3 Global GatewayConfig.java following categories:
Zuul with the previous subsections, where we will also need about two Sentinel Spring Cloud Gateway Filter injection of Spring: SentinelGatewayFilter
and SentinelGatewayBlockExceptionHandler
, because here was adding support for Spring Cloud Gateway in the Sentinel v1.6.0 version, in many places is not perfect, abnormal treatment SentinelGatewayBlockExceptionHandler
can only return an exception message, not a good combination in our system and downstream, where the author himself re-implemented SentinelGatewayBlockExceptionHandler
, and the name JsonSentinelGatewayBlockExceptionHandler
, return parameters are defined to be JSON, this is no longer inject Sentinel provided SentinelGatewayBlockExceptionHandler
, but instead I own implementation JsonSentinelGatewayBlockExceptionHandler
.
代码清单:Alibaba/sentinel-springcloud-high/gateway_server/src/main/java/com/springcloud/gateway_server/config/GatewayConfig.java
***
@Configuration
public class GatewayConfig {
private final List<ViewResolver> viewResolvers;
private final ServerCodecConfigurer serverCodecConfigurer;
public GatewayConfig(ObjectProvider<List<ViewResolver>> viewResolversProvider, ServerCodecConfigurer serverCodecConfigurer) {
this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
this.serverCodecConfigurer = serverCodecConfigurer;
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public JsonSentinelGatewayBlockExceptionHandler jsonSentinelGatewayBlockExceptionHandler() {
// Register the block exception handler for Spring Cloud Gateway.
return new JsonSentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
}
@Bean
@Order(-1)
public GlobalFilter sentinelGatewayFilter() {
return new SentinelGatewayFilter();
}
}
3.2.4 Class JsonSentinelGatewayBlockExceptionHandler.java downgrade follows:
代码清单:Alibaba/sentinel-springcloud-high/gateway_server/src/main/java/com/springcloud/gateway_server/exception/JsonSentinelGatewayBlockExceptionHandler.java
***
public class JsonSentinelGatewayBlockExceptionHandler implements WebExceptionHandler {
private List<ViewResolver> viewResolvers;
private List<HttpMessageWriter<?>> messageWriters;
public JsonSentinelGatewayBlockExceptionHandler(List<ViewResolver> viewResolvers, ServerCodecConfigurer serverCodecConfigurer) {
this.viewResolvers = viewResolvers;
this.messageWriters = serverCodecConfigurer.getWriters();
}
private Mono<Void> writeResponse(ServerResponse response, ServerWebExchange exchange) {
ServerHttpResponse serverHttpResponse = exchange.getResponse();
serverHttpResponse.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
byte[] datas = "{\"code\":403,\"msg\":\"Sentinel block exception\"}".getBytes(StandardCharsets.UTF_8);
DataBuffer buffer = serverHttpResponse.bufferFactory().wrap(datas);
return serverHttpResponse.writeWith(Mono.just(buffer));
}
@Override
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
if (exchange.getResponse().isCommitted()) {
return Mono.error(ex);
}
// This exception handler only handles rejection by Sentinel.
if (!BlockException.isBlockException(ex)) {
return Mono.error(ex);
}
return handleBlockedRequest(exchange, ex)
.flatMap(response -> writeResponse(response, exchange));
}
private Mono<ServerResponse> handleBlockedRequest(ServerWebExchange exchange, Throwable throwable) {
return GatewayCallbackManager.getBlockHandler().handleRequest(exchange, throwable);
}
private final Supplier<ServerResponse.Context> contextSupplier = () -> new ServerResponse.Context() {
@Override
public List<HttpMessageWriter<?>> messageWriters() {
return JsonSentinelGatewayBlockExceptionHandler.this.messageWriters;
}
@Override
public List<ViewResolver> viewResolvers() {
return JsonSentinelGatewayBlockExceptionHandler.this.viewResolvers;
}
};
}
I just to rewrite the writeResponse()
method returns information speak simple change has become a json format, you need the reader can modify according to their needs.
3.2.5 Testing
Sequentially starting provider_server, consumer_server and gateway_server, arranged gateway_server jvm startup parameters -Dcsp.sentinel.app.type=1
, as shown:
Open your browser and visit: http: // localhost: 28080 / helloByFeign, refresh a few times, the page returns to normal Hello, port is: 8000
, open Sentinel console, configure traffic limiting policies, QPS limit is 1, then refresh the browser page, then, we can see limiting the return to the browser information:
{"code":403,"msg":"Sentinel block exception"}
testing successfully.