Spring Cloud Alibaba service flow limiting downgrade Sentinel

Sentinel overview

Sentinel: traffic guard of distributed system

Sentinel takes traffic as the starting point to protect the stability of services from multiple dimensions such as traffic control, fuse degradation and system load protection.

Sentinel is divided into two parts:

Core library( Java Client) does not depend on any framework/Library, which can run on all Java Runtime environment, and Dubbo / Spring Cloud And other frameworks also have good support.

Console( Dashboard)be based on Spring Boot Development can be run directly after packaging without additional Tomcat And other application containers.

Sentinel's main features:

Sentinel's open source ecosystem:

GitHub address: https://github.com/alibaba/Sentinel

Official website: https://sentinelguard.io/zh-cn/

Project integration Sentinel

Correlation dependency

	<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.2.RELEASE</version>
        <relativePath/>
    </parent>

    <dependencyManagement>
        <dependencies>
            <!--integration spring cloud-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Hoxton.SR9</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!--integration spring cloud alibaba-->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>2.2.6.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

Configure Sentinel after the Sentinel console is successfully set up

spring:
  cloud:
    sentinel:
      transport:
        # Specifies the address of the sentinel console
        dashboard: IP:8858

View Sentinel endpoint

Exposure endpoint

management:
  endpoints:
    web:
      exposure:
        include: '*'

visit: http://localhost:8082/actuator/sentinel

{
  "blockPage": null,
  "appName": "pay-center",
  "consoleServer": [
    
  ],
  "coldFactor": "3",
  "rules": {
    "systemRules": [
      
    ],
    "authorityRule": [
      
    ],
    "paramFlowRule": [
      
    ],
    "flowRules": [
      
    ],
    "degradeRules": [
      
    ]
  },
  "metricsFileCharset": "UTF-8",
  "filter": {
    "order": -2147483648,
    "urlPatterns": [
      "/**"
    ],
    "enabled": true
  },
  "totalMetricsFileCount": 6,
  "datasource": {
    
  },
  "clientIp": "192.168.42.16",
  "clientPort": "8719",
  "logUsePid": false,
  "metricsFileSize": 52428800,
  "logDir": "C:\\Users\\Administrator\\logs\\csp\\",
  "heartbeatIntervalMs": 10000
}

Sentinel console

First, determine the Sentinel version corresponding to spring cloud Alibaba, and then build the Sentinel console of the corresponding version

		<!--click artifactId get into spring-cloud-alibaba View in dependency management nacos Version of-->
		<dependency>
             <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>2.2.6.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>


<sentinel.version>1.8.1</sentinel.version>

Docker deployment Sentine

https://blog.csdn.net/qq_38628046/article/details/106875278

Add Sentinel configuration and restart the project

spring:
  cloud:
    sentinel:
      transport:
        # Specifies the address of the sentinel console
        dashboard: IP:8858

Since Sentinel is lazy loading, you need to access the( http://192.168.179.1:8082/test/1 )The corresponding service will appear only once for any interface

After accessing the interface for many times, it is found that there is no monitoring information

Check the Docker container log and find that it seems to be caused by IP, and the IP is strange.

 ERROR 1 --- [pool-2-thread-1] c.a.c.s.dashboard.metric.MetricFetcher   : Failed to fetch metric from <http://192.168.42.16:8719/metric?startTime=1638617140000&endTime=1638617146000&refetch=false> (ConnectionException: Connection timed out)


 ERROR 1 --- [pool-2-thread-1] c.a.c.s.dashboard.metric.MetricFetcher   : Failed to fetch metric from <http://192.168.42.16:8719/metric?startTime=1638617147000&endTime=1638617153000&refetch=false> (ConnectionException: Connection timed out)


 ERROR 1 --- [pool-2-thread-1] c.a.c.s.dashboard.metric.MetricFetcher   : Failed to fetch metric from <http://192.168.42.16:8719/metric?startTime=1638617154000&endTime=1638617160000&refetch=false> (ConnectionException: Connection timed out)


 ERROR 1 --- [pool-2-thread-1] c.a.c.s.dashboard.metric.MetricFetcher   : Failed to fetch metric from <http://192.168.42.16:8719/metric?startTime=1638617161000&endTime=1638617167000&refetch=false> (ConnectionException: Connection timed out)

Shut down the service and remove the wrong machine IP from the list of machines on the sentinel console

Modify Sentinel configuration

spring:
  cloud:
    sentinel:
      transport:
        # Specifies the address of the sentinel console
        dashboard: IP:8858
        #Specify the ip to communicate with the console. If it is not configured, an ip registration will be automatically selected, which may cause errors
        clientIp: localhost

Restart service

Multiple visits http://192.168.179.1:8082/test/1 Interface service

The test/{id} interface was requested. The interface was not found in the real-time monitoring. The reason was not found for a long time. Therefore, the Jar was directly downloaded and run locally.

Download address: https://github.com/alibaba/Sentinel/releases

java -jar sentinel-dashboard-1.8.1.jar

Modify Sentinel configuration (change Sentinel service address to local machine) and access http://localhost:8082/test/1 Service address, last access http://localhost:8080/

Sentinel's rules

Flow control rules

The principle of flow control is to monitor the QPS or the number of concurrent threads of application traffic, and control the traffic when the specified threshold is reached, so as to avoid being overwhelmed by the instantaneous traffic peak, so as to ensure the high availability of the application.

A current limiting rule is mainly composed of the following factors, which can be combined to achieve different current limiting effects:

resource: The resource name, that is, the object of the flow restriction rule

count: Current limiting threshold

grade: Current limiting threshold type( QPS Or concurrent threads)

limitApp: Call source for flow control, if yes default The call source is not distinguished

strategy: Call relation current limiting policy

controlBehavior: Flow control effect (direct rejection Warm Up,Uniform speed queue)


There are two main statistical types of flow control

Count the number of concurrent threads

Statistics QPS

Flow control based on call relation

direct    

relation    

link   

When the QPS exceeds a certain threshold, measures shall be taken to control the flow

Direct rejection

Warm Up

Uniform queue

Direct rejection

The direct reject (RuleConstant.CONTROL_BEHAVIOR_DEFAULT) method is the default flow control method. When the QPS exceeds the threshold of any rule, the new request will be rejected immediately. The rejection method is to throw a FlowException. This method is applicable when the processing capacity of the system is exactly known, such as when the accurate water level of the system is determined through pressure measurement.

Source class: com.alibaba.csp.sentinel.slots.block.flow.controller.DefaultController

Warm Up

Warm Up (RuleConstant.CONTROL_BEHAVIOR_WARM_UP) mode, i.e. preheating / cold start mode. When the system is at low water level for a long time, when the flow suddenly increases, directly pull the system to high water level, which may crush the system instantly. Through "cold start" , let the passing flow increase slowly and gradually increase to the upper limit of the threshold within a certain time to give the cold system a warm-up time to avoid the cold system being crushed.

According to the value of codeFactor (default 3), the set QPS threshold is reached after preheating from the threshold / codeFactor

Official principle analysis document
https://github.com/alibaba/Sentinel/wiki/%E9%99%90%E6%B5%81---%E5%86%B7%E5%90%AF%E5%8A%A8

Source code: com.alibaba.csp.sentinel.slots.block.flow.controller.WarmUpController

Uniform queue

The uniform queuing (ruleconstant. Control_behavior_rate_limit) method will strictly control the interval between requests, that is, let requests pass at a uniform speed, corresponding to the leaky bucket algorithm.

Queue at a uniform speed to allow requests to pass at a uniform speed, or the value type must be set to QPS, otherwise it is invalid

Official documents
https://github.com/alibaba/Sentinel/wiki/%E6%B5%81%E9%87%8F%E6%8E%A7%E5%88%B6-%E5%8C%80%E9%80%9F%E6%8E%92%E9%98%9F%E6%A8%A1%E5%BC%8F

Source code: com.alibaba.csp.sentinel.slots.block.flow.controller.RateLimiterController

Flow control test

Direct flow control mode

When Qps exceeds 1, an exception is reported and current limiting is performed.

Associated flow control mode

When the associated resource reaches the threshold, it limits itself


First call the / test2 interface in a loop, and then access the / test/{id} interface. The / test/{id} interface will always be in the current limiting state

Link flow control mode

Only the traffic on the specified link is recorded

@Component
public class TestComponent {
    @SentinelResource("common")
    public String common() {
        return "common";
    }
}
  @GetMapping("test-a")
    public String testA() {
        this.testService.common();
        return "test-a";
    }
    
    @GetMapping("test-b")
    public String testB() {
        this.testService.common();
        return "test-b";
    }


When accessing the test-a interface reaches the Qps time limit, the flow will not be limited by accessing test-b anyway

Degradation rule

A service often calls other modules, such as another remote service, database, or third-party API. For example, when paying, you may need to call the API provided by UnionPay remotely; when querying the price of a commodity, you may need to query the database. However, the stability of the dependent service cannot be guaranteed. If the dependent service appears In case of instability, if the response time of the request becomes longer, the response time of the method calling the service will also become longer, and the threads will accumulate. Finally, the thread pool of the business itself may be exhausted, and the service itself will become unavailable.

Therefore, it is necessary to fuse and downgrade the unstable weak dependent service calls, temporarily cut off the unstable calls, and avoid the overall avalanche caused by local unstable factors.


Sentinel fusing strategy:

Slow_request_ratio: select the slow call ratio as the threshold. You need to set the allowed slow call RT (i.e. the maximum response time). If the response time of the request is greater than this value, it will be counted as slow call. When the unit statistics time (statIntervalMs) If the number of internal requests is greater than the set minimum number of requests and the proportion of slow calls is greater than the threshold value, the internal requests in the next fusing time will be blown automatically. After the fusing time, the fuse will enter the detection recovery state (HALF-OPEN state) , if the response time of the next request is less than the set slow call RT, the fuse will end. If it is greater than the set slow call RT, it will be blown again.

Error_ratio: when the number of requests in the unit statistical duration (statIntervalMs) is greater than the set minimum number of requests, and the proportion of exceptions is greater than the threshold, the requests in the next fusing duration will be blown automatically. After fusing for a long time, the fuse will enter the detection recovery state (HALF-OPEN state). If the next request is successfully completed (no error), the fusing will be ended, otherwise it will be blown again. The threshold range of abnormal ratio is [0.0, 1.0], representing 0% - 100%.

Error_count: when the number of exceptions in the unit statistical time exceeds the threshold, it will automatically fuse. After fusing for a long time, the fuse will enter the detection recovery state (HALF-OPEN state). If the next request is successfully completed (no error), the fusing will be ended, otherwise it will be blown again.

Important attributes of the degradation rule:

FieldexplainDefault value
resourceResource name, that is, the object of the rule
gradeFusing strategy, supporting slow call proportion / exception proportion / different constant strategySlow call ratio
countIn slow call proportional mode, it is slow call critical RT (exceeding this value is counted as slow call); It is the corresponding threshold in the exception proportion / exception number mode
timeWindowFusing duration, unit: s
minRequestAmountThe minimum number of requests triggered by fusing. When the number of requests is less than this value, it will not fuse even if the abnormal ratio exceeds the threshold (introduced by 1.7.0)5
statIntervalMsStatistical duration (unit: ms), for example, 60 * 1000 represents minute level (introduced in 1.8.0)1000 ms
slowRatioThresholdSlow call proportional threshold. Only slow call proportional mode is valid (introduced in 1.8.0)

Reference source code: com alibaba. csp. sentinel. slots. block. degrade. DegradeRule

Hot spot parameter current limiting

Hotspot parameter current limiting will count the hotspot parameters in the incoming parameters, and limit the current of resource calls containing hotspot parameters according to the configured current limiting threshold and mode. Hotspot parameter current limiting can be regarded as a special flow control, which is only effective for resource calls containing hotspot parameters.

Add / testParam interface

   @GetMapping("/testParam")
    @SentinelResource("param")
    public String testParam(Integer a,String b){
        return a + "-------------" +b;
    }

request http://localhost:8082/testParam?a=1&b=Sentinel , due to the limitation on the first parameter, when Qps reaches 1, the current limit throws an exception


request http://localhost:8082/testParam?a=&b=Sentinel In any case, the request will not be throttled

When the first parameter value is not 2, Qps Current limit when 1 is reached

When the first parameter value is 2, Qps Current limit when reaching 100

Reference source code: com alibaba. csp. sentinel. slots. block. flow. param. ParamFlowChecker

System rules

The system protection rule is to control the entrance flow at the application level, and monitor the application indicators from the dimensions of load, CPU utilization, average RT, entrance QPS and the number of concurrent threads of a single machine, so as to make the system run at the maximum throughput and ensure the overall stability of the system.

Load adaptation (only valid for Linux / Unix like machines): load1 of the system is used as the heuristic index for adaptive system protection. When system load1 exceeds the set heuristic value and the current number of concurrent threads exceeds the estimated system capacity, system protection will be triggered (BBR stage). The system capacity is estimated from the maxQps * minRt of the system. The setting reference value is generally CPU cores * 2.5.

CPU usage (version 1.5.0 +): when the system CPU utilization exceeds the threshold, the system protection is triggered (value range 0.0-1.0), which is sensitive.

Average RT: when the average RT of all inlet flows on a single machine reaches the threshold, the system protection is triggered, and the unit is milliseconds.

Number of concurrent threads: system protection is triggered when the number of concurrent threads of all inlet traffic on a single machine reaches the threshold.

Inlet QPS: when the QPS of all inlet flows on a single machine reaches the threshold, the system protection is triggered.

Reference source code: com alibaba. csp. sentinel. slots. system. SystemRuleManager

Authorization rules

Whether the resource can pass is limited according to the request origin of the resource. If the white list is configured, it can pass only when the request source is in the white list; If the blacklist is configured, the request will not pass if the request source is in the blacklist, and the other requests will pass.

Sentinel's rule persistence

Pull mode:

The client actively polls and pulls rules from a rule management center, which 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 is pushed uniformly, and the client 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.

Specific implementation reference: https://github.com/alibaba/Sentinel/wiki/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%99%E6%89%A9%E5%B1%95

Sentinel configuration

Configuration of application side connection console

spring:
  cloud:
    sentinel:
      transport:
        # Specifies the address of the sentinel console
        dashboard: IP:8858
        #Specify the ip to communicate with the console. If it is not configured, an ip registration will be automatically selected, which may cause errors
        clientIp: localhost
        # Specifies the port to communicate with the console. The default value is 8719
        # If not set, it will automatically start scanning from 8719 and + 1 in turn until an unoccupied port is found
        port: 8719
        # Heartbeat sending cycle, the default value is null, but the default value is 10 seconds in SimpleHttpHeartbeatSender
        heartbeat-interval-ms: 10000

Console configuration

Configuration itemDefault valuedescribe
server.port8080Specify port
csp.sentinel.dashboard.serverlocalhost:8080Specify address
project.nameSpecifies the name of the program
sentinel.dashboard.auth.usernamesentinelDashboard login account
sentinel.dashboard.auth.passwordsentinelDashboard login password
server.servlet.session.timeout30 MinutesLogin Session expiration time
If it is configured as 7200, it means 7200 seconds
60m indicates 60 minutes

Use of Sentinel

API mode

Three core API s:

SphU : Define resources, make resources monitored, and protect resources

Tacer: Count the exceptions you want

ContextUtil:  Implement the call source and mark the call

Note that exception degradation is only for business exceptions, and does not take effect for the exception (BlockException) of Sentinel current limiting degradation itself. In order to count the proportion or number of exceptions, you need to use tracer Trace (Ex) records business exceptions.

    @GetMapping("/test-api")
    public String testSentinelAPI(@RequestParam(required = false) String name) {
        // Define resource name
        String resourceName = "test-api";
        //  Implement the call source and mark the call
        ContextUtil.enter(resourceName, "test-service");

        Entry entry = null;
        try {
            entry = SphU.entry(resourceName);
            // Protected business logic
            if (StringUtils.isBlank(name)) {
                throw new IllegalArgumentException("Illegal parameter");
            }
            return name;
        }
        // If the protected resource is restricted or degraded, BlockException will be thrown
        catch (BlockException blockException) {
            return "Current limiting or degradation";
        } catch (IllegalArgumentException illegalArgumentException) {
        	// Call tracer Trace (Ex) to record business exceptions
            Tracer.trace(illegalArgumentException);
            return "Illegal parameter";
        } finally {
            if (entry != null) {
                // Exit entry
                entry.exit();
            }
            ContextUtil.exit();
        }
    }

Annotation method

@SentinelResource is used to define resources and provide optional exception handling and fallback configuration items

Use details refer to: https://github.com/alibaba/Sentinel/wiki/%E6%B3%A8%E8%A7%A3%E6%94%AF%E6%8C%81

 @GetMapping("/test-sentinel-resource")
    @SentinelResource(
            value = "test-sentinel-api",
            blockHandler = "block",
            //blockHandlerClass = BlockHandlerTestClass.class,
            //fallbackClass = FallbackTestClass.class,
            fallback = "fallback"
    )
    public String testSentinelResource(@RequestParam(required = false) String name) {
        if (StringUtils.isBlank(name)) {
            throw new IllegalArgumentException("Illegal parameter");
        }
        return name;
    }

    public String block(String name, BlockException blockException) {
        return "Current limiting or degradation---------block";
    }

    public String fallback(String name, Throwable throwable) {
        return "Current limiting or degradation---------fallback";
    }

Separate block() and fallback(), and specify them respectively with blockHandlerClass and fallbackClass

 @GetMapping("/test-sentinel-resource")
    @SentinelResource(
            value = "test-sentinel-api",
            blockHandler = "block",
            blockHandlerClass = BlockHandlerTestClass.class,
            fallbackClass = FallbackTestClass.class,
            fallback = "fallback"
    )
    public String testSentinelResource(@RequestParam(required = false) String name) {
        if (StringUtils.isBlank(name)) {
            throw new IllegalArgumentException("Illegal parameter");
        }
        return name;
    }

blockHandlerClass = BlockHandlerTestClass.clas: the corresponding block function must be in the blockhandlertestclass class and must be a public static function

fallbackClass = FallbackTestClass.class: the corresponding fallback function must be in the fallbacktestclass class and must be a public static function

public class BlockHandlerTestClass{
    /**
     * Handle current limiting or degradation
     */
    public static String block(String name, BlockException blockException) {
        return "BlockHandlerTestClass----Current limiting or degradation-------block";
    }
}

Integration with RestTemplate and Feign

RestTemplate integration Sentinel

@SentinelRestTemplate annotation also provides fallback and blockHandler processing

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SentinelRestTemplate {
    String blockHandler() default "";

    Class<?> blockHandlerClass() default void.class;

    String fallback() default "";

    Class<?> fallbackClass() default void.class;

    String urlCleaner() default "";

    Class<?> urlCleanerClass() default void.class;
}

Define current limiting degradation exception handling class

public class BlockHandler {
    public static SentinelClientHttpResponse handleException(HttpRequest request, byte[] body, ClientHttpRequestExecution clientHttpRequestExecution, BlockException blockException) {
        return new SentinelClientHttpResponse("Current limiting or degradation---------BlockHandler Class");
    }
}
public class FallBackHandler {
 
    public static SentinelClientHttpResponse fallBackHandle(HttpRequest request, byte[] body, ClientHttpRequestExecution clientHttpRequestExecution, BlockException blockException){
        return new SentinelClientHttpResponse("Current limiting or degradation---------FallBackHandler Class");
    }
}

RestTemplate add @ SentinelRestTemplate annotation

    @Bean
    //Ribbon load balancing
    @LoadBalanced
    //Let RestTemplate support Sentinel current limiting
    @SentinelRestTemplate(blockHandler = "handleException",blockHandlerClass = BlockHandler.class,
            fallback = "fallBackHandle", fallbackClass = FallBackHandler.class)
    public RestTemplate restTemplate() {
        RestTemplate template = new RestTemplate();
        return template;
    }
}

@SentinelRestTemplate annotation configuration can be turned off

resttemplate:
  sentinel:
  	# The development environment can be temporarily shut down: degraded, current limited
    # The default value is true. If it is set to false, it means that the @ SentinelRestTemplate annotation is closed
    enabled: false

Feign integrated Sentinel

Enable Feign's support for Sentinel

feign:
  sentinel:
    # Integrate sentinel for feign
    enabled: true

Create current limiting degraded fault tolerance class

Implement the fault-tolerant interface directly, and implement the fault-tolerant scheme for each method

realization FallbackFactory Interface
@Component
public class UserCenterFeignClientFallback implements UserCenterFeignClient {
    @Override
    public UserDTO selectUserById(Integer id) {
        UserDTO userDTO = new UserDTO();
        userDTO.name("Flow control or degradation-------implements UserCenterFeignClient");
        return userDTO;
    }
}
@Component
public class UserCenterFeignClientFallbackFactory implements FallbackFactory<UserCenterFeignClient> {
    @Override
    public UserCenterFeignClient create(Throwable cause) {
        return new UserCenterFeignClient() {
            @Override
            public UserDTO selectUserById(Integer id) {
                UserDTO userDTO = new UserDTO();
                userDTO.name("Flow control or degradation-------implements FallbackFactory");
                return userDTO;
            }
        };
    }
}

Specify the fault-tolerant class for the interface degraded by current limiting

@FeignClient(name = "user-center",
//    fallback = UserCenterFeignClientFallback.class,
    fallbackFactory = UserCenterFeignClientFallbackFactory.class
)
public interface UserCenterFeignClient {
    @GetMapping("/users/{id}")
    UserDTO selectUserById(@PathVariable Integer id);
}

Sentinel exception handling

Uniformly handle returned exceptions: implement the BlockExceptionHandler interface and override the handle() method.

@Component
public class MyUrlBlockHandler implements BlockExceptionHandler {
    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse response, BlockException e) throws Exception {
        String msg = null;
        if (e instanceof FlowException) {
            msg = "Current limiting---------FlowException";
        } else if (e instanceof DegradeException) {
            msg = ("Demotion---------DegradeException";
        } else if (e instanceof ParamFlowException) {
            msg = "Hot spot parameter current limiting---------ParamFlowException";
        } else if (e instanceof SystemBlockException) {
            msg = "System rules---------SystemBlockException";
        } else if (e instanceof AuthorityException) {
            msg = "Authorization rule failed---------AuthorityException";
        }

        response.setStatus(500);
        response.setCharacterEncoding("utf-8");
        response.setHeader("Content-Type", "application/json;charset=utf-8");
        response.setContentType("application/json;charset=utf-8");
        new ObjectMapper().writeValue(response.getWriter(), msg);
    }

Sentinel implements source differentiation

code implementation

@Component
public class MyRequestOriginParser implements RequestOriginParser {
    @Override
    public String parseOrigin(HttpServletRequest request) {
        // Get the parameter named origin from the request header / request parameters
//        String origin = request.getHeader("request");
        String origin = request.getParameter("origin");

        // Throw an exception if the origin parameter cannot be obtained
        if (StringUtils.isBlank(origin)) {
            throw new IllegalArgumentException("Illegal request");
        }
        return origin;
    }
}

Current limiting test for source


Request: http://localhost:8082/test?origin=web

Current limiting---------FlowException

Request: http://localhost:8082/test?origin=app : casual access

Authorization test for source


Request: http://localhost:8082/test?origin=app

Authorization rule failed---------AuthorityException

Request: http://localhost:8082/test?origin=web : passed

Keywords: Java Spring Boot Spring Cloud Microservices

Added by JohnnyLearningPHP on Mon, 13 Dec 2021 03:21:46 +0200