Spring Cloud Alibaba | Sentinel: Distributed System Flow Defender Advanced Practice

Before reading this article, it is recommended that you read it first Spring Cloud Alibaba | Sentinel: Distributed System Flow Defender Base Actual Warfare.

1. Sentinel integrates Feign and RestTemplate

Sentinel currently supports both Feign and RestTemplate, requiring us to introduce dependencies. When using Feign, we need to open Sentinel's support for Feign in the configuration file: feign.sentinel.enabled=true, and add an openfeign starter dependency to make the automated configuration class in sentinel starter effective.When using RestTemplate, you need to add the @SentinelRestTemplate annotation when constructing the Bean of RestTemplate to turn on Sentinel support for RestTemplate.

1.1 Create parent project sentinel-springcloud-high:

The parent project pom.xml is as follows:

Code List: 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>

Sentinel is introduced as a traffic control in public components and Nacos as a service center.

1.2 Create a subproject provider_server:

The configuration file application.yml is as follows:

List of codes: 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: '*'

The interface test class HelloController.java is as follows:

List of codes: 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 subproject consumer_server:

Subprojects rely on pom.xml as follows:

List of codes: Alibaba/sentinel-springcloud-high/consumer_server/pom.xml

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

The configuration file application.yml is as follows:

List of codes: 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

Sentinel support for Feign is turned on here using feign.sentinel.enabled=true.

Interface Test Class HelloController.java

List of codes: 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 already integrated this, and we don't need any additional notes where we use Feign.At the same time, Sentinel is compatible with all the properties in the @FeignClient comment.

Start the main class Ch122ConsumerServerApplication.java as follows:

Code List: 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, you need to add @SentinelRestTemplate to turn on Sentinel support for RestTemplate.

1.4 Test

Start the project provider_server and consumer_server. Provider_server modifies the startup configuration, starts two instances, and opens the browser for access: http://localhost : 9000/helloByFeign and http://localhost 9000/helloByRestTemplate, refresh a few times, you can see the page alternately shows Hello, port is: 8000 and Hello, port is: 8001, indicating that the load balancing is normal at present, now look at the Sentinel console, as shown in the following figure:

1.5 Flow Control Test

Then select the cluster point flow control on the left and click on the flow control, as shown in the figure:

Here we configure the simplest rule, configure QPS to be limited to 1, click Add New, as shown in Fig.

Explain what QPS is here, simply QPS is a number of visits per second, here we need to repeat a quick refresh when testing http://localhost : 9000/helloByFeign and http://localhost 9000/helloByRestTemplate, during the refresh process, we can see that the page will display an error message, such as: Blocked by Sentinel (flow limiting), indicating that we have successfully configured Sentinel to limit the current. Then we can look at Sentinel's console again to see the success and the number of limits we have just visited.Figure:

2. Service downgrade

In the last summary, we introduced the integration of Feign and RestTemplate with Sentinel usage, and QPS current limiting was done on the Sentinel console. After successful current limiting, by default Sentinel throws an exception directly to the current limiting treatment of control resources.This can be done without proper business acceptance or front-end docking, but normally for better user business, special handling after flow restriction will be achieved, and we don't want to show a hard error.In this section, we describe the service downgrade process.

2.1 Create subproject consumer_fallback

The Feign service downgrade class HelloRemoteFallBack.java is as follows:

Code List: 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, we need to do some configuration on HelloRemote.java to trigger the service downgrade to execute our service downgrade class after the current limit, code as follows:

List of codes: 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 specifies that the processing class for service degradation is HelloRemoteFallBack.class.

The RestTemplate service downgrade tool class ExceptionUtil.java is as follows:

List of codes: 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");
    }
}

This also requires modifying where RestTemplate is registered as a Bean so that the code executes the processing class we write for it after RestTemplate triggers the service downgrade. The Ch122 ConsumerFallbackApplication.java code is as follows:

List of codes: 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 is important to note here that the @SentinelRestTemplate annotation's properties support flow-limiting (blockHandler, blockHandlerClass) and downgrading (fallback, fallbackClass) processing.

The method corresponding to the blockHandler or fallback attribute must be a static method in the corresponding blockHandlerClass or fallbackClass attribute.

The @SentinelRestTemplate annotation's blockHandler (blockHandlerClass) and fallback (fallbackClass) properties do not force filling.

When a call using RestTemplate is broken by Sentinel, the RestTemplate request block by sentinel information is returned, or you can write a corresponding method to process the return information yourself.SentinelClientHttpResponse is provided here to construct the return information.

2.2 Test

Start the provider_server and consumer_fallback subprojects in sequence.Alternate access in browser first http://localhost : 9090/helloByFeign and http://localhost 9090/helloByRestTemplate, and then open the Sentinel Console to add the current limiting information on both interfaces. Note that you want to add the current limiting information to the resources here, as shown in the figure below:

Refresh the two links in the browser, both flow limiting information can be displayed in the normal browser, the test is successful, check the Sentinel console again, you can also see the rejected traffic statistics, as shown in the figure:

3. Sentinel Integrated Services Gateway Limit

Sentinel currently supports mainstream API Gateway restrictions such as Spring Cloud Gateway and Zuul.Take a look at the official structure diagram, as shown in the following figure:

From this official diagram, you can see that Sentinel's restriction on Zuul is mainly done through three filters, and Spring Cloud Gateway is done through one SentinleGateway Filter and one BlockRequestHandler.

Sentinel 1.6.0 introduces the Sentinel API Gateway Adapter Common module, which contains rules for gateway flow limiting and entity and management logic for custom APIs:

  • Gateway FlowRule: Gateway flow restriction rules, custom flow restriction rules for API Gateway scenarios, flow restriction for different route s or custom API groupings, support flow restriction customized for parameters in requests, headers, source IP, etc.
  • ApiDefinition: User-defined API definition groupings, which can be seen as combinations of URL matches.For example, we can define an API called my_api, and all requests with path mode / foo / and / baz / are grouped under my_api.Flow limiting can be done for this custom API grouping dimension.

3.1 Zuul 1.x

Sentinel provides an adapter module for Zuul 1.x, which provides two resource dimensions for Zuul Gateway:

  • Route dimension: The route entry configured in the Spring configuration file with the resource name corresponding to the route ID (corresponding to the proxy field in the RequestContext)
  • Custom API dimension: Users can customize some API groupings using the APIs provided by Sentinel

3.1.1 Create subproject zuul_server

The project relies on pom.xml as follows:

Code List: 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 sentinel-zuul-adapter is not included in spring-cloud-starter-alibaba-sentinel, it needs to be introduced manually separately.

The 3.1.2 configuration file application.yml is as follows:

List of codes: 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 Defines the downgrade processing class ZuulFallbackProvider.java as follows:

Code List: 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 inject three Sentinel filters into Spring with the following configuration classes:

Code List: 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();
    }

    /**
     * Register ZuulFallbackProvider
     */
    @PostConstruct
    public void doInit() {
        ZuulBlockFallbackManager.registerProvider(new ZuulFallbackProvider());
    }

}

Ultimately, you need to configure the JVM startup parameters before starting by adding -Dcsp.sentinel.app.type=1 to tell the Sentinel console that the service we are starting is of type API Gateway.

3.1.5 Test

Sequential boot projects provider_server, consumer_fallback, zuul_server, open browser access: http://localhost : 18080/consumer/helloByFeign, and then we open the Sentinel console to view the zuul_server service, as shown in the following figure:

We customized the current limiting strategy, QPS is still 1, we refresh again http://localhost : 18080/consumer/helloByFeign page, at this time, the page is ready for abortion restriction, the content displayed after the restriction is:

{"code":429, "message":"Sentinel block exception", "route":"consumer-route"}

Note here that what defines flow restriction is resources. Never define anything wrong. Flow restriction is defined in the following figure:

3.2 Spring Cloud Gateway

Starting with version 1.6.0, Sentinel provides an adapter module for Spring Cloud Gateway, which provides two resource dimensions of throttling:

  • Route dimension: The route entry configured in the Spring configuration file with the resource name corresponding to routeId
  • Custom API dimension: Users can customize some API groupings using the APIs provided by Sentinel

3.2.1 Create subproject gateway_server

The project relies on pom.xml as follows:

List of codes: 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>

The 3.2.2 configuration file application.yml is as follows:

List of codes: 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 The global configuration class GatewayConfig.java is as follows:

As with Zuul described in the previous section, here we also need to inject two Sentinel filters about Spring Cloud Gateway into Spring: Sentinel Gateway Filter and entinel Gateway BlockExceptionHandler, because Spring Cloud Gateway support was added in Sentinel v1.6.0, many of which are not yet complete, different from each other.The SentinelGatewayBlockExceptionHandler, which handles SentinelGatewayBlockExceptionHandler routinely, currently returns only one exception information and does not combine well with the upstream and downstream in our system. Here the author redesigns the SentinelGatewayBlockExceptionHandler and names it JsonSentinelGatewayBlockExceptionHandler. The return parameter is defined as JSON.Instead of injecting SentinelGatewayBlockExceptionHandler provided by Sentinel, the author's own JsonSentinelGatewayBlockExceptionHandler is implemented.

Code List: 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 The demotion processing class JsonSentinelGatewayBlockExceptionHandler.java is as follows:

Code List: Alibaba/sentinel-springcloud-high/gateway_server/src/main/java/com/springcloud/gateway_server/exception/JsonSentinelGateway BlockExceptionHandler.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;
        }
    };
}

The author only rewrote the writeResponse() method here, and said that the returned information was simply changed to json format. Every reader can modify it according to their own needs.

3.2.5 Test

Start provider_server, consumer_server, and gateway_server sequentially, configure the gateway_server jvm startup parameter -Dcsp.sentinel.app.type=1, as shown in the following figure:

Open browser to access: http://localhost 28080/helloByFeign, refresh a few times, the page returns to Hello normally, port is: 8000, open Sentinel console, configure current limiting policy, QPS limit is 1, refresh the browser page, then we can see the browser returns flow limiting information:

{"code":403,"msg":"Sentinel block exception"}

The test was successful.

Tags: Java Spring xml JSON

Posted on Thu, 12 Sep 2019 18:06:20 -0700 by JPnyc