Sentinel dashboard Nacos dynamic data source - spring cloud gateway

Friendly tip: to download the source code for transformation, this paper is based on Sentinel-1.8.2.

The following contents are extracted from personal technical documents, and relevant materials are mainly from spring cloud Alibaba and GitHub sentinel. The text is part of the excerpts for reference only.

4.1.2. Gateway flow control

Gateway flow restriction rules are customized flow restriction rules for API Gateway scenarios. They can limit flow for different route s or customized API packets, and support customized flow restriction for parameters, headers, source IP, etc. in the request.

Similar to normal application flow control, gateway flow control will return such results after triggering.

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

4.1.2.1. Infrastructure

Front end code

  • Front end page: webapp / resources / APP / views / gateway / flow html
  • JS file: webapp / resources / APP / scripts / Controllers / gateway / flow js

Background code

  • Package: com.alibaba.csp.sentinel.dashboard.controller.gateway
  • Class: GatewayApiCountroller
    • GET /gateway/api/list.json - query flow control rules
    • POST /gateway/api/new.json - add flow control rule
    • PUT /gateway/api/save.json - modify flow control rules
    • DELETE /gateway/api/delete.json - delete flow control rule

4.1.2.2. Query flow control rules

Interface input parameter

Parameter nametypeRule constraintexplain
appStringString is not emptyGateway gateway ApplicationName, corresponding to spring in gateway configuration file application. The value of the name attribute
ipStringString is not emptyGateway server IP
portIntegerNon emptyGateway server port, corresponding to sentinel in gateway configuration file transport. Value of port

Internal logic

4.1.2.3. Add flow control rule

Interface input parameter

Parameter nametypeRule constraintexplain
appStringString is not emptyapply name
ipStringString is not emptyApplication IP
portIntegerApplication port number
resourceStringString is not emptyResources targeted by flow control
resourceModeIntegerNon empty, RESOURCE_MODE_ROUTE_ID=0; RESOURCE_MODE_CUSTOM_API_NAME=1Resource model
gradeIntegerNon empty, 0 is the number of threads, 1 is qpsThe current limit indicator dimension is the same as the grade field of the current limit rule
countDoubleGreater than or equal to 0Current limiting threshold
intervalLongGreater than 0Statistics interval: 1 by default
intervalUnitIntegerSecond, minute, hour, dayThe statistical interval unit is seconds by default
controlBehaviorIntegerNon empty, 0-fast failure, 2-uniform queuingThe control effect of traffic shaping is the same as that of the controlBehavior field of the flow restriction rule. At present, two modes of fast failure and uniform queuing are supported. The default is fast failure.
burstIntegerRequired for quick failure, greater than or equal to 0Number of additional requests allowed in response to sudden requests
maxQueueingTimeoutMsIntegerRequired when queuing at constant speed, greater than or equal to 0The longest queuing time in milliseconds under the uniform queuing mode. It only takes effect under the uniform queuing mode
paramItemGPFIVParameter current limiting configuration

GPFIV: GatewayParamFlowItemVo if not provided, it means that the current is not limited for the parameters, and the gateway rule will be converted into a common flow control rule; Otherwise, it will be converted to a hotspot rule.

Parameter nametypeRule constraintexplain
parseStrategyInteger0-ClientIP 1-Remote Host 2-Header 3-URL parameter4-cookiePolicy for extracting parameters from requests
fieldNameStringWhen the parameter property is 2-Header 3-URL 4-Cookie, the parameter name is requiredIf the extraction policy selects header mode or URL parameter mode, you need to specify the corresponding header name or URL parameter name
patternStringParameter value matching mode. Only the request attribute value matching the mode will be included in statistics and flow control; If it is blank, all values of the request attribute will be counted. (supported from version 1.6.2)
matchStrategyIntegerPARAM_MATCH_STRATEGY_EXACT, PARAM_MATCH_STRATEGY_CONTAINS, and PARAM_MATCH_STRATEGY_REGEXMatching strategy for parameter values (supported from version 1.6.2)

Internal logic

4.1.2.4. Modify flow control rules

Interface input parameter

Except that some parameters are replaced by id, other parameters are the same as the input parameters of the new interface

Parameter nametypeRule constraintexplain
idLongNon emptyRule id
gradeIntegerNon empty, 0 is the number of threads, 1 is qpsThe current limit indicator dimension is the same as the grade field of the current limit rule
countDoubleGreater than or equal to 0Current limiting threshold
intervalLongGreater than 0Statistics interval: 1 by default
intervalUnitIntegerSecond, minute, hour, dayThe statistical interval unit is seconds by default
controlBehaviorIntegerNon empty, 0-fast failure, 2-uniform queuingThe control effect of traffic shaping is the same as that of the controlBehavior field of the flow restriction rule. At present, two modes of fast failure and uniform queuing are supported. The default is fast failure.
burstIntegerRequired for quick failure, greater than or equal to 0Number of additional requests allowed in response to sudden requests
maxQueueingTimeoutMsIntegerRequired when queuing at constant speed, greater than or equal to 0The longest queuing time in milliseconds under the uniform queuing mode. It only takes effect under the uniform queuing mode
paramItemGPFIVParameter current limiting configuration

Internal logic (same as adding)

4.1.2.5. Delete flow control rule

Interface input parameter

Parameter nametypeRule constraintexplain
idLongNon emptyRule id

Internal logic (roughly the same as adding and modifying, and will not be repeated)

5. Dynamic data source

5.1. Pre knowledge

5.1.1. What is a dynamic data source

In the open source framework (at least as of version 1.8.2), all rules are stored in memory (in the form of Map in the JVM). However, in the production environment, we usually want to have a fixed configuration file or data source to complete the persistence and use of these rules, which is the dynamic data source.

This project is Sentinel Dashboard, not Sentinel. Since Dashboard provides relatively complete rule management functions, we expect to configure rules through Dashboard instead of modifying text through configuration centers such as Nacos. Then, in order to meet such requirements, it is necessary to integrate the dynamic data source into the Dashboard, so as to persist the rules configured in the Dashboard to the dynamic data source.

Sentinel (including Dashboard) provides two dynamic data source schemes:

  • In pull mode, the client (integrated Sentinel application) actively polls a rule management center for pull rules regularly. This rule center can be RDBMS, files, or even VCS. The way to do this is simple, but the disadvantage is that changes cannot be obtained in time;

  • Push mode, the rule center pushes uniformly, and the client (integrating Sentinel's application) monitors changes at any time by registering a listener, such as using Nacos, Zookeeper and other configuration centers. This method has better real-time and consistency guarantee.

Sentinel currently supports the following data source extensions:

5.1.2. Pull mode expansion

The simplest way to implement a pull pattern data source is inheritance AutoRefreshDataSource Abstract class, and then implement the readSource() method, in which the configuration data in string format is read from the specified data source. such as File based data source.

5.1.3. Push mode expansion

The simplest way to implement a data source in push mode is inheritance AbstractDataSource Abstract class, add a listener to its constructor, and implement readSource() to read configuration data in string format from the specified data source. such as Nacos based data source.

The console usually needs to be modified to directly push the rules of the application dimension to the configuration center. Function examples can be referred to Rule push function of AHAS Sentinel console . Modification guidelines can be referred to Using Sentinel console in production environment.

5.1.4. Register data source

This section describes the steps you need to do when using Sentinel alone. If you use spring cloud Alibaba to integrate Sentinel, you don't need to.

You usually need to call the following method to register the data source in the specified rule manager:

ReadableDataSource<String, List<FlowRule>> flowRuleDataSource = new NacosDataSource<>(remoteAddress, groupId, dataId, parser);
FlowRuleManager.register2Property(flowRuleDataSource.getProperty());

If you do not want to register the data source manually, you can use Sentinel's InitFunc SPI extension interface. You only need to implement your own InitFunc interface and write the logic to register the data source in the init method. For example:

package com.test.init;

public class DataSourceInitFunc implements InitFunc {

    @Override
    public void init() throws Exception {
        final String remoteAddress = "localhost";
        final String groupId = "Sentinel:Demo";
        final String dataId = "com.alibaba.csp.sentinel.demo.flow.rule";

        ReadableDataSource<String, List<FlowRule>> flowRuleDataSource = new NacosDataSource<>(remoteAddress, groupId, dataId,
            source -> JSON.parseObject(source, new TypeReference<List<FlowRule>>() {}));
        FlowRuleManager.register2Property(flowRuleDataSource.getProperty());
    }
}

Then add the corresponding class name to com.inf under META-INF/services directory under resource directory (usually resource directory) alibaba. csp. sentinel. init. Initfunc file, for example:

com.test.init.DataSourceInitFunc

In this way, Sentinel can automatically register the corresponding data source when accessing any resource for the first time.

5.1.5. Official Expansion example

Sentinel's official source code provides an example of dynamic data source expansion for the application of flow control rules. Sentinel open source version provides a memory based rule engine. In order to use the data source for persistence, it is necessary to transform it.

The official has written a special sample code for Nacos as a dynamic data source, which is aimed at flow control rules.

Review the background code of flow control rules:

  • Package: com.alibaba.csp.sentinel.dashboard.controller
  • Class: FlowControllerV1
    • GET /v1/flow/rules - query flow control rules
    • POST /v1/flow/rule - add flow control rule
    • PUT /v1/flow/save.json - modify flow control rules
    • DELETE /v1/flow/delete.json - delete flow control rule

The sample code is:

  • Package: com.alibaba.csp.sentinel.dashboard.controller.v2
  • Class: FlowControllerV2
    • GET /v2/flow/rules - query flow control rules
    • POST /v2/flow/rule - add flow control rule
    • PUT /v2/flow/save.json - modify flow control rules
    • DELETE /v2/flow/delete.json - delete flow control rule

Sentinel official provides key samples of Nacos (test/com.alibaba.csp.sentinel.dashboard.rule.nacos) in the dashboard test package, including Apollo and Zookeeper. The following mainly introduces how to operate based on Nacos as a dynamic data source.

5.1.5.1. Copy sample class

At test / com alibaba. csp. sentinel. dashboard. rule. Under Nacos, there are four classes:

  • FlowRuleNacosProvider
  • FlowRuleNacosPublisher
  • NacosConfig
  • NacosConfigUtil

Copy them to SentinelDashboard. Since the Nacos service address filled in NacosConfig is localhost, we need to change it to the format of ip:port. Usually, we will define a configuration item, move it to the configuration file for definition or specify it with startup parameters.

5.1.5.2. Add data source dependency

Since the official set the data source dependency to the test range, we need to open it for use, as long as POM Just comment out the following tags in XML.

		<!-- for Nacos rule publisher sample -->
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-datasource-nacos</artifactId>
            <scope>test</scope>
        </dependency>

5.1.5.3. Modify front-end route

The default front-end access back-end interfaces mentioned above are:

  • GET /v1/flow/rules - query flow control rules
  • POST /v1/flow/rule - add flow control rule
  • PUT /v1/flow/save.json - modify flow control rules
  • DELETE /v1/flow/delete.json - delete flow control rule

Now I want to change it to / v2 /... So I need to transform the front end. Although the front end is developed by Angular, you don't have to worry about the whole stack to see what's going on.

Angular is similar to Vue and needs node JS to compile and package, so you need to install node JS, it is recommended to use 11.15.0, otherwise you may encounter some strange problems. If you want to manage multiple node versions at the same time, you can refer to this article Blog.

node related commands:

# Installation package
npm -install

# Operation (Development)
npm start

# Packaging (production)
npm run build

The structure of the front-end project is roughly as follows (only the necessary structures are excerpted, others have been omitted):

  • webapp/resources/

    • app

      • scripts
        • controllers
          • flow_v1.js --------------------------------- original flow control front end js
          • flow_v2.js --------------------------------- dynamic data source front end js
        • services
          • flow_service_v1.js -------------------------- original flow control backend API definition
          • flow_service_v2.js -------------------------- dynamic data source backend API definition
        • directives
          • sidebar
            • sidebar.html ----------------------- define route
        • app.js ---------------------------------------------- defines the global control, which is the core configuration
      • views
        • flow_v1.html --------------------------------- original flow control rule configuration page
        • flow_v2.html --------------------------------- dynamic data source flow control rule configuration page
    • dist ------------------------------------------------------- store the files compiled and packaged in the production environment

    • tmp -------------------------------------------------------- store the compiled and packaged files of the development environment

    • gulpfile. JS ------------------------------------------------------- the place defined in the service file (to be used for subsequent extension of new code)

There are two key here, app JS and sidebar HTML, in app JS defines two state s that we can use directly. The contents are as follows:

      .state('dashboard.flowV1', {
        templateUrl: 'app/views/flow_v1.html',
        url: '/flow/:app',
        controller: 'FlowControllerV1',
        resolve: {
          loadMyFiles: ['$ocLazyLoad', function ($ocLazyLoad) {
            return $ocLazyLoad.load({
              name: 'sentinelDashboardApp',
              files: [
                'app/scripts/controllers/flow_v1.js',
              ]
            });
          }]
        }
      })

      .state('dashboard.flow', {
          templateUrl: 'app/views/flow_v2.html',
          url: '/v2/flow/:app',
          controller: 'FlowControllerV2',
          resolve: {
              loadMyFiles: ['$ocLazyLoad', function ($ocLazyLoad) {
                  return $ocLazyLoad.load({
                      name: 'sentinelDashboardApp',
                      files: [
                          'app/scripts/controllers/flow_v2.js',
                      ]
                  });
              }]
          }
      })

Correspondingly, the connection in the navigation on the left side of the console is in the sidebar Defined in HTML:

<!-- Original flow control rule navigation -->
<li ui-sref-active="active" ng-if="!entry.isGateway">
	<a ui-sref="dashboard.flowV1({app: entry.app})">
	<i class="glyphicon glyphicon-filter"></i>&nbsp;&nbsp;Flow control rules</a>
</li>
<!-- Change to dynamic data source flow control rule navigation -->
<li ui-sref-active="active" ng-if="!entry.isGateway">
	<a ui-sref="dashboard.flow({app: entry.app})">
	<i class="glyphicon glyphicon-filter"></i>&nbsp;&nbsp;Flow control rules</a>

Obviously, the dashboard in the tag Flowv1 is app JS.

After the front end is modified, you can start it directly or start it after packaging to debug to see whether it is successful.

5.2. Based on spring cloud gateway

Spring cloud gateway is relatively simple to integrate because Sentinel officials have made adaptation.

The gateway related codes are basically in the gateway package or directory, which is easy to find.

In order to retain the original code, basically new classes, pages, js, etc. are created for extension, and the original code will not be changed directly. The following is only for flow control process extension for reference.

5.2.1. New Publisher and Provider

According to the original GatewayFlowRuleController, you can know that the entity objects used in the interface are GatewayFlowRuleEntity, so you can directly provide a converter for it and write Publisher and Provider.

Provider

import com.alibaba.csp.sentinel.dashboard.config.NacosConfigUtil;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway.GatewayFlowRuleEntity;
import com.alibaba.csp.sentinel.datasource.Converter;
import com.alibaba.csp.sentinel.util.StringUtil;
import com.alibaba.nacos.api.config.ConfigService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

@Component("gatewayFlowRuleNacosProvider")
public class GatewayFlowRuleNacosProvider implements DynamicRuleProvider<List<GatewayFlowRuleEntity>> {
    @Autowired
    private ConfigService configService;
    // For the converter of rule objects, the obtained data needs to be converted by different converters according to the different data types used
    @Autowired
    private Converter<String, List<GatewayFlowRuleEntity>> converter;

    @Override
    public List<GatewayFlowRuleEntity> getRules(String appName) throws Exception {
        String rules = configService.getConfig(appName + NacosConfigUtil.FLOW_DATA_ID_POSTFIX,
                NacosConfigUtil.GROUP_ID, 3000);
        if (StringUtil.isEmpty(rules)) {
            return new ArrayList<>();
        }
        return converter.convert(rules);
    }
}

Publisher

import com.alibaba.csp.sentinel.dashboard.config.NacosConfigUtil;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway.GatewayFlowRuleEntity;
import com.alibaba.csp.sentinel.datasource.Converter;
import com.alibaba.csp.sentinel.util.AssertUtil;
import com.alibaba.nacos.api.config.ConfigService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.List;

/**
 * <h3>Sentinel Gateway flow control rule publisher for dynamic data source</h3>
 *
 * <p>Publish the rules configured through the console to the dynamic data source (nacos). Currently, the push mode is used,
 * After the rules are pushed to nacos, the gateway will automatically obtain the latest rules through the listener and update them to the local buffer of the gateway
 * In storage.
 * </p>
 */
@Component("gatewayFlowRuleNacosPublisher")
public class GatewayFlowRuleNacosPublisher implements DynamicRulePublisher<List<GatewayFlowRuleEntity>> {
    // Configuration service of data source
    @Autowired
    private ConfigService configService;
    /**
     * <p>Converter for data communication.
     * <p>The Spring Bean object declared in the NacosConfig class under the config package.
     * <p>Responsible for converting entity objects to json formatted strings</p>
     */
    @Autowired
    private Converter<List<GatewayFlowRuleEntity>, String> converter;

    @Override
    public void publish(String app, List<GatewayFlowRuleEntity> rules) throws Exception {
        AssertUtil.notEmpty(app, "app name cannot be empty");
        if (rules == null) {
            return;
        }
        /*
         * Publish the rules to the dynamic data source for persistence. The first parameter is app + suffix. Here, the suffix of - flow rules is used;
         * The second parameter is the nacos group id, which can use the sentinel reservation provided by default; The last parameter is data conversion
         * After the object is converted into a unified format, it is transmitted to nacos through the network.
         */
        configService.publishConfig(app + NacosConfigUtil.FLOW_DATA_ID_POSTFIX,
                NacosConfigUtil.GROUP_ID, converter.convert(rules));
    }
}

5.2.2. Add Converter

Add a converter < list < gatewayflowruleentity >, string > type converter, which can be directly added to the NacosConfig copied earlier:

@Bean
public Converter<List<GatewayFlowRuleEntity>, String> gatewayFlowRuleEntityEncoder() {
	return JSON::toJSONString;
}

@Bean
public Converter<String, List<GatewayFlowRuleEntity>> gatewayFlowRuleEntityDecoder() {
	return s -> JSON.parseArray(s, GatewayFlowRuleEntity.class);
}

5.2.3. GatewayFlowRuleControllerV2

  1. A new GatewayFlowRuleControllerV2 is added, and the content can be directly copied to the GatewayFlowRuleController.

  2. Change the original interface address: / gateway / flow - > / V2 / gateway / flow

  3. Remove the original sentinelApiClient

    @Autowired
    private SentinelApiClient sentinelApiClient;
    
  4. Join Provider and Publisher

    @Autowired
    @Qualifier("gatewayFlowRuleNacosProvider")
    private DynamicRuleProvider<List<GatewayFlowRuleEntity>> ruleProvider;
    @Autowired
    @Qualifier("gatewayFlowRuleNacosPublisher")
    private DynamicRulePublisher<List<GatewayFlowRuleEntity>> rulePublisher;
    
  5. Modify publishRules() method

    private boolean publishRules(String app, String ip, Integer port) {
    	List<GatewayFlowRuleEntity> rules = repository.findAllByMachine(MachineInfo.of(app, ip, port));
        return sentinelApiClient.modifyGatewayFlowRules(app, ip, port, rules);
    }
    // Change to
    
        /**
         * <h3>Unified logic of Publishing Rules</h3>
         *
         * <p>Rules are stored in local memory. First, get all the rules applied by the rules to be published from memory, which is a list</p>
         * <p>Publish the full amount of rules to the data source in a certain format for unified update</p>
         *
         * @param app apply name
         * @param ip Application IP
         * @param port Application port
         * @throws Exception If you publish remotely, an exception will occur. You need to handle the exception
         */
        private void publishRules(String app, String ip, Integer port) throws Exception {
            List<GatewayFlowRuleEntity> rules = repository.findAllByMachine(MachineInfo.of(app, ip, port));
            rulePublisher.publish(app, rules);
        }
    
  6. Modify the logic in the controller, mainly for reading rules and saving rules:

    • queryFlowRules

      List<GatewayFlowRuleEntity> rules = sentinelApiClient.fetchGatewayFlowRules(app, ip, port).get();
      // Change to
      List<GatewayFlowRuleEntity> rules = ruleProvider.getRules(app);
      
    • addFlowRule

      try {
      	entity = repository.save(entity);
      } catch (Throwable throwable) {
      	logger.error("add gateway flow rule error:", throwable);
      	return Result.ofThrowable(-1, throwable);
      }
      
      if (!publishRules(app, ip, port)) {
      	logger.warn("publish gateway flow rules fail after add");
      }
      // Change to
      try {
          entity = repository.save(entity);
          publishRules(entity.getApp(), entity.getIp(), entity.getPort());
      } catch (Throwable throwable) {
          logger.error("add gateway flow rule error:", throwable);
          return Result.ofThrowable(-1, throwable);
      }
      
    • updateFlowRule (similar to addFlowRule)

    • deleteFlowRule (similar to addFlowRule)

5.2.4. Front end transformation

  1. On app JS to define a new gateway state

    // New state
    	.state('dashboard.gatewayFlowV2', {
              templateUrl: 'app/views/gateway/flow_v2.html',
              url: '/gateway/flow/:app',
              controller: 'GatewayFlowCtlV2',
              resolve: {
                  loadMyFiles: ['$ocLazyLoad', function ($ocLazyLoad) {
                      return $ocLazyLoad.load({
                          name: 'sentinelDashboardApp',
                          files: [
                              'app/scripts/controllers/gateway/flow_v2.js',
                          ]
                      });
                  }]
              }
          })
    
    // This is the original, at the bottom
          .state('dashboard.gatewayFlow', {
              templateUrl: 'app/views/gateway/flow.html',
              url: '/gateway/flow/:app',
              controller: 'GatewayFlowCtl',
              resolve: {
                  loadMyFiles: ['$ocLazyLoad', function ($ocLazyLoad) {
                      return $ocLazyLoad.load({
                          name: 'sentinelDashboardApp',
                          files: [
                              'app/scripts/controllers/gateway/flow.js',
                          ]
                      });
                  }]
              }
          });
    
  2. Retrofit sidebar HTML, add new navigation to the new controller, and comment out the old one

    <li ui-sref-active="active" ng-if="entry.isGateway">
    	<a ui-sref="dashboard.gatewayFlowV2({app: entry.app})">
    	<i class="glyphicon glyphicon-filter"></i>&nbsp;&nbsp;Gateway flow control rules</a>
    </li>
    
  3. Add a new gateway flow control page, views/gateway/flow_v2.html

  4. Copy Controllers / gateway / flow js -> controllers/gateway/flow_ V2. JS, just change the place with V2

    app.controller('GatewayFlowCtlV2', ['$scope', '$stateParams', 'GatewayFlowServiceV2', 'GatewayApiService', 'ngDialog', 'MachineService',
      function ($scope, $stateParams, GatewayFlowServiceV2, GatewayApiService, ngDialog, MachineService) {
    
  5. Copy services / gateway / flow_ service_ v1. js -> services/gateway/flow_ service_ v2. js

    app.service('FlowServiceV2', ['$http', function ($http) {
    

    Change FlowService to FlowServiceV2, and then prefix all request paths with / v2

  6. gulpfile.js to add the definition of FlowServiceV2:

    const JS_APP = [
    	......,
    	'app/scripts/services/gateway/flow_service_v2.js',
    ]
    
  7. Execute npm run build, empty the browser cache, and then test.

Keywords: Spring Cloud architecture sentinel

Added by JCF22Lyoko on Sun, 09 Jan 2022 10:43:06 +0200