Click “like” to see, form a habit, the public account search [dime technology] pay attention to more original technical articles. This article has been included in GitHub org_Hejianhui /JavaStudy.

What is the avalanche effect?

Business scenarios,High concurrencycall

  1. Under normal conditions, microservices A, B, C, and D are normal.
  2. As time goes by, at A certain point in time, microservice A suddenly hangs up, and microservice B is still calling microservice A crazily. Since A has been hung up, B must wait for service invocation timeout when calling A. And we know that every time B -> A fits B, B creates threads (and threads are made of computer resources, such as CPU, memory, etc.). Because of the high concurrency scenario, B blocks a large number of threads. B’s machine over there will create threads, but computer resources are limited, and eventually B’s server will go down. (To put it bluntly, microservice B was dragged to death by his pig teammate Microservice A)
  3. Microservice A, A pig teammate, dragged microservice B to death, resulting in the breakdown of microservice B, and then A similar situation would occur in microservice C and D. Finally, our pig teammate A succeeded in dragging microservice B, C and D to death. This situation is also called service avalanche. There is also a technical term for cascading failures (Cascading failures).

Two, fault tolerance three axe

2.1 the timeout

Simply put is the timeout mechanism, configure the following timeout time, if 1 second — each request must be returned within 1 second, otherwise the thread will be strangled, free resources!

If a timeout occurs, the resource is released. Because resources are released faster, applications are less likely to be dragged to death.

Code demo :(for caller handling)

// Step 1: Set the timeout period for the RestTemplate
@Configuration
public class WebConfig {

    @Bean
    public RestTemplate restTemplate(a) {
        // Set the timeout period of the restTemplate
        SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
        requestFactory.setReadTimeout(1000);
        requestFactory.setConnectTimeout(1000);
        RestTemplate restTemplate = new RestTemplate(requestFactory);
        returnrestTemplate; }}// Step 2: Handle the timeout exception
try{
    ResponseEntity<ProductInfo> responseEntity= restTemplate.getForEntity(uri+orderInfo.getProductNo(), ProductInfo.class);
    productInfo = responseEntity.getBody();
}catch (Exception e) {
    log.info("Call timeout");
    throw new RuntimeException("Call timeout");
}


// Set global exception handling
@ControllerAdvice
public class NiuhExceptionHandler {

    @ExceptionHandler(value = {RuntimeException.class})
    @ResponseBody
    public Object dealBizException(a) {
        OrderVo orderVo = new OrderVo();
        orderVo.setOrderNo("1");
        orderVo.setUserName("Fault tolerant user");
        returnorderVo; }}Copy the code

2.2 Bulkhead isolation mode

If you are interested, you can learn about the cabin structure – generally, modern ships will be divided into many compartments, directly welded with steel plates, isolated from each other. In this way, if one or some of the cabins are flooded, the other cabins will not be flooded, and the ship will not sink if the buoyancy is enough.

Bulkhead isolation in code (Thread pool isolation mode)

Class M uses thread pool 1 and class N uses thread pool 2, with different thread pools and assigned a thread pool size for each class, such as coreSIze=10.

For example: Class M calls service B and class N calls service C. If class M and class N use the same thread pool, then if service B dies, the interface of class N calling service B is very concurrent, and you have no protection, your service is likely to be dragged to death by class M. If class M has its own thread pool and class N has its own thread pool, if service B fails, class M will fill up its own thread pool at best and not affect class N’s thread pool — so class N will still work.

You have your thread pool, I have my thread pool, it doesn’t matter if your thread pool is full, it doesn’t matter if you die.

2.3 Circuit Breaker Mode

The real world circuit breaker is no doubt well known. Everyone has one at home. The circuit breaker monitors the circuit in real time, and if it detects an abnormal current, it trips to prevent the circuit from being burned.

The circuit breaker in the software world can be understood as follows: real-time monitoring of the application, if it is found that the number of failures/failure rate within a certain period of time reaches a certain threshold, it “trips”, the circuit breaker opens — times, and the request is returned directly without invoking the logic originally called.

After tripping for a period of time (say 15 seconds), the circuit breaker will enter the half-open state, which is aInstantaneous state, a request is allowed to invoke the logic of the call. If successful, the circuit breaker is closed and the normal call is applied. If the call is still unsuccessful, the circuit breaker continues to return to the open state, and later enters the half-open state to try again – by “tripping”, the application can protect itself and avoid wasting resources; With a half-open design, the application can “repair itself.”

Sentinel flow control, fault tolerance and degradation

3.1 What is Sentinel?

A Lightweight powerful flow control component enabling reliability and monitoring for Microservices Github website address: github.com/alibaba/Sen… Wiki:github.com/alibaba/Sen…

Hystrix is the younger brother to Sentinel

A first look at Sentinel

niuh04-ms-alibaba-sentinel-helloworld

V1 version:

  • Step 1: Add dependency packages
<! <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-core</artifactId> <version>1.71.</version>
</dependency>
Copy the code
  • Step 2: Controller
@RestController
@Slf4j
public class HelloWorldSentinelController {

    @Autowired
    private BusiServiceImpl busiService;

    /** * Initialize the flow control rule */
    @PostConstruct
    public void init(a) {

        List<FlowRule> flowRules = new ArrayList<>();

        /** * Rules that define helloSentinelV1 protected resources */
        // Create a flow control rule object
        FlowRule flowRule = new FlowRule();
        // Set the flow control rule QPS
        flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        // Set the protected resource
        flowRule.setResource("helloSentinelV1");
        // Set the threshold of the protected resource
        flowRule.setCount(1);

        flowRules.add(flowRule);

        // Load the configured rule
        FlowRuleManager.loadRules(flowRules);
    }


    / frequent request interface http://localhost:8080/helloSentinelV1 * * * * the disadvantage of this approach: invasive * 1) business is very big, need to write in your controoler non-business code.. * 2) The configuration is not flexible. If you need to add new protected resources, you need to manually add init method to add flow control rules *@return* /
    @RequestMapping("/helloSentinelV1")
    public String testHelloSentinelV1(a) {

        Entry entity =null;
        // Associate protected resources
        try {
            entity = SphU.entry("helloSentinelV1");
            // Start implementing your own business methods
            busiService.doBusi();
            // Stop executing your own business method
        } catch (BlockException e) {
            log.info("TestHelloSentinelV1 method is flow-controlled.");
            return "TestHelloSentinelV1 method is flow-controlled.";
        }finally {
            if(entity! =null) { entity.exit(); }}return "OK"; }}Copy the code

Test effect:http://localhost:8080/helloSentinelV1 The flaws of the V1 version are as follows:

  • The business is very intrusive and you need to write non-business code in your Controoler.
  • The configuration is not flexible. If you need to add new protected resources, manually add init to add flow control rules

V2: Based on V1, add a dependency

<dependency>
	<groupId>com.alibaba.csp</groupId>
	<artifactId>sentinel-annotation-aspectj</artifactId>
	<version>1.71.</version>
</dependency>
Copy the code
  • Writing the controller
// Configure a section
@Configuration
public class SentinelConfig {

    @Bean
    public SentinelResourceAspect sentinelResourceAspect(a) {
        return newSentinelResourceAspect(); }}/** * Initialize the flow control rule */
@PostConstruct
public void init(a) {

    List<FlowRule> flowRules = new ArrayList<>();

    /** * Defines the rules for the helloSentinelV2 protected resource */
    // Create a flow control rule object
    FlowRule flowRule2 = new FlowRule();
    // Set the flow control rule QPS
    flowRule2.setGrade(RuleConstant.FLOW_GRADE_QPS);
    // Set the protected resource
    flowRule2.setResource("helloSentinelV2");
    // Set the threshold of the protected resource
    flowRule2.setCount(1);

    flowRules.add(flowRule2);
}

/ frequent request interface http://localhost:8080/helloSentinelV2 * * * * advantage: the need to configure the aspectj aspects SentinelResourceAspect, add annotations@SentinelResource* Fixed sentinel business intrusion code issue in v1 version, using blockHandler to specify the method to be called after flow control. As the number of methods in our controller increases, the number of protected methods increases, which leads to a problem * blockHandler methods also increase, resulting in a method explosion. * blockHandler Specifies the name of the function that handles BlockException. * Optional. The blockHandler function access scope must be public, the return type must match the original method, the argument type must match the original method and an extra argument must be added at the end, and the * type must be BlockException. By default, the blockHandler function must be in the same class as the original method@return* /
@RequestMapping("/helloSentinelV2")
@SentinelResource(value = "helloSentinelV2",blockHandler ="testHelloSentinelV2BlockMethod")
public String testHelloSentinelV2(a) {
    busiService.doBusi();
    return "OK";
}

public String testHelloSentinelV2BlockMethod(BlockException e) {
    log.info("TestRt flow control");
    return "TestRt degraded flow control...."+e;
}
Copy the code

Test effect:http://localhost:8080/helloSentinelV2

The V3 version improves on V2’s shortcomings

/** * Initialize the flow control rule */
@PostConstruct
public void init(a) {

    List<FlowRule> flowRules = new ArrayList<>();

    /** * Rules that define helloSentinelV3 protected resources */
    // Create a flow control rule object
    FlowRule flowRule3 = new FlowRule();
    // Set the flow control rule QPS
    flowRule3.setGrade(RuleConstant.FLOW_GRADE_QPS);
    // Set the protected resource
    flowRule3.setResource("helloSentinelV3");
    // Set the threshold of the protected resource
    flowRule3.setCount(1);



    flowRules.add(flowRule3);
}

/ * * * we see the shortcomings in v2, we are specified by blockHandlerClass processing flow control class * specified by testHelloSentinelV3BlockMethod blockHandlerClass the method name * * * * this way of dealing with the abnormal flow control method must be static http://localhost:8080/helloSentinelV3 * * frequent request interface@return* /
@RequestMapping("/helloSentinelV3")
@SentinelResource(value = "helloSentinelV3",blockHandler = "testHelloSentinelV3BlockMethod",blockHandlerClass = BlockUtils.class)
public String testHelloSentinelV3(a) {
    busiService.doBusi();
    return "OK";
}

// Exception handling class
@Slf4j
public class BlockUtils {


    public static String testHelloSentinelV3BlockMethod(BlockException e){
        log.info("TestHelloSentinelV3 method is flow-controlled.");
        return "TestHelloSentinelV3 method is flow-controlled."; }}Copy the code

Test effect:http://localhost:8080/helloSentinelV3 disadvantages: Cannot dynamically add rules. How to solve the problem?

3.2 How to rapidly integrate Sentinel in engineering

<! - to join the sentinel -- -- >
<dependency>
	<groupId>com.alibaba.cloud</groupId>
	<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>


<! - join physical - >
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
Copy the code

After adding Sentinel, expose/physical/Sentinel endpoint http://localhost:8080/actuator/sentinel

Springboot does not expose this endpoint by default, so we need to configure it ourselves

server:
  port: 8080
management:
  endpoints:
    web:
      exposure:
        include: '*'
Copy the code

3.3 We need to integrate Sentinel-Dashboard

Download address:Github.com/alibaba/Sen…(My version: 1.6.3)

  • The first step: performJava jar sentinel - dashboard - 1.6.3. JarBoot (a SpringBoot project)
  • Step 2: Visit our Sentinel console (added to the landing page for version 1.6) http://localhost:8080/, default account password: sentinel/sentinel

  • Step 3: Our micro service NIUh04-MS-Alibaba-Sentinel-Order integrates sentinel. We have also built sentinel console and added sentinel console address for micro service
spring:
  cloud:
    sentinel:
      transport:
        dashboard: localhost:9999
Copy the code

Detailed explanation of Sentinel monitoring performance indicators

4.1 Real-time Monitoring Panel

In this panel we monitor the interfaceThrough the QPSRefuse QPSIn the absence of flow control rules, we will not see rejected QPS.

4.2 Cluster Point Link

A monitored API for online microservices

4.3 Flow control Settings

Cluster point link Select the specific API to access and click”Flow control button meaning:

  • Resource name: API /selectOrderInfoById/1 for our interface
  • For source: the default is default (the identity is not for source). There is also A case where microservice A needs to call this resource and microservice B also needs to call this resource. Then we can set thresholds for microservice A and microservice B separately.
  • A threshold type: is divided into QPS and number of threads, assuming a threshold of 2
    • QPS: Traffic limiting is performed when the number of interface accesses per second is greater than 2
    • Number of threads: In order to receive requests for this resource, the number of threads allocated is greater than 2

Flow control mode

  1. Direct: This is easy to understand, is the set threshold immediately after the flow control throws an exception

Crazy requests for this path

  1. associated

Business scenario: We now have two apis, the first is to save the order and the other is to query the order, assuming we want limited operations.”Save the order test:Write two read-write test interfaces

/** * method implementation description: imitate flow control mode [association] read interface *@author:hejianhui
 * @param orderNo
 * @return:
 * @exception:
 * @date: 2019/11/24 22:06 * /
@RequestMapping("/findById/{orderNo}")
public Object findById(@PathVariable("orderNo") String orderNo) {
    log.info("orderNo:{}"."Perform query operation"+System.currentTimeMillis());
    return orderInfoMapper.selectOrderInfoById(orderNo);
}


/** * write interface (priority) *@author:hejianhui
 * @return:
 * @exception:
 * @date: 2019/11/24 22:07 * /
@RequestMapping("/saveOrder")
public String saveOrder(a) throws InterruptedException {
    //Thread.sleep(500);
    log.info("Perform save operation, imitate return order ID");
    return UUID.randomUUID().toString();
}
Copy the code

Test code: Write a for loop that keeps calling our write interface so that the write interface QPS reaches the threshold

public class TestSentinelRule {

    public static void main(String[] args) throws InterruptedException {
        RestTemplate restTemplate = new RestTemplate();
        for(int i=0; i<1000; i++) { restTemplate.postForObject("http://localhost:8080/saveOrder".null,String.class);
            Thread.sleep(10); }}}Copy the code

Access to our read interface at this time: the current is limited.

  1. link

The local experiment was not successful, using Alibaba ungraduated version 0.9.0 can test the effect, API level limit traffic

Code:

@RequestMapping("/findAll")
public String findAll(a) throws InterruptedException {
    orderServiceImpl.common();
    return "findAll";
}

@RequestMapping("/findAllByCondtion")
public String findAllByCondtion(a) {
    orderServiceImpl.common();
    return "findAllByCondition";
}

@Service
public class OrderServiceImpl {

    @SentinelResource("common")
    public String common(a) {
        return "common"; }}Copy the code

According to the flow control rules: only /findAll requests are restricted, not the /findAllByCondtion rule

Effect of flow control

  1. Fast failure (direct exception thrown) QPS operations per second after 1 direct exception thrown

Source: com. Alibaba. CSP. Sentinel. Slots. Block. Flow. Controller. DefaultController

  1. warmUp

Source: com. Alibaba. CSP. Sentinel. Slots. Block. Flow. Controller. WarmUpController >

When there is a sudden increase in traffic, it is tempting to expect the system to take longer to switch from idle to busy. That is, if the system is idle for a long time before this, we expect the number of requests to be processed to increase slowly and reach the maximum number of requests processed by the system after the expected time. Warm Up mode is for this purpose.

Cold loading factor: codeFacotr defaults to 3

  • The default coldFactor is 3, that is, the QPS request starts from threshold / 3 and gradually rises to the set QPS threshold after the preheating time.

Above Settings: QPS from 100/3=33, after 10 seconds, reach 100 QPS to limit the flow.

Detailed documentation: github.com/alibaba/Sen…

  1. Waiting in line

Source: com. Alibaba. CSP. Sentinel. Slots. Block. Flow. Controller. RateLimiterController

This approach is suitable for requests coming in spikes, when we do not want to pass all requests at once, which may overwhelm the system; We also expect the system to process these requests at a steady pace, so as to “fill the gap” rather than reject all requests.

The threshold type selected for queuing must be ****QPS In the figure above, the single-machine threshold is 10, indicating that the number of requests per second is 10, that is, the average interval of each request is constant 1000/10 = 100 ms, and the maximum waiting time of each request is 20 * 1000ms = 20s. After 20 seconds, the request is discarded.

Detailed documentation: github.com/alibaba/Sen…

4.4 Degradation Rules

Rt (Average response time)

Average response time (DEGRADE_GRADE_RT) : When five requests arrive within 1s and the average response time exceeds count (ms), the survival of each request degrades into a timeWindow in s. Calls to this method will automatically fuse (throw a DegradeException).

Note: Sentinel default RT at the upper limit is 4900 ms, beyond the threshold will appear 4900 ms, if need to change this limit can be through the startup configuration items: – Dcsp. Sentinel. Statistic. Max. RT = XXX to configure

Abnormal ratio (DEGRADE_GRADE_EXCEPTION_RATIO)

The total number of requests per second for a resource exceeds 5 and the total number of exceptions per second exceeds the survival threshold. The rate is reduced to s in the next timeWindow. Calls to this method are automatically returned. The threshold range of abnormal ratio is [0.0, 1.0], representing 0% ~ 100%.

Number of exceptions (DEGRADE_GRADE_EXCEPTION_COUNT)

When the number of anomalies in the resource near one thousand percent exceeds the threshold, a circuit breaker is performed. Notice the statistical timeWindow is minute. If the timeWindow is less than 60 seconds, the circuit breaker may enter the circuit breaker state again after the circuit breaker state ends.

4.5 Hotspot Parameters

The business scenario: Second kill business, for example, the shopping mall does promotion second kill and carries out 9.9 second kill activities for Apple 11 (product ID =1). At this time, the request flow to the order interface (product ID =1) is very large, so we can control the concurrent volume of requests for product ID =1 through the hot parameter rules. Requests for other normal goods are not restricted. Then this hot parameter rule is used.

The Sentinel-Dashboard console and our microservices communication principles

5.1 How can the Console Obtain monitoring Information about microservices?

5.2 How to push rules to microservices when Configuring rules on the console?

We observed the registration service microservice information by observing the sentinel-Dashboard machine list. Our console can then communicate with our specific microservices through the registration information of these microservices.

5.3 Some interface API addresses provided by Micro Service during sentinel integration:http://localhost:8720/api

5.4 We can set rules by code (here we use flow control rules as an example)

@RestController
public class AddFlowLimitController {

    @RequestMapping("/addFlowLimit")
    public String addFlowLimit(a) {
        List<FlowRule> flowRuleList = new ArrayList<>();

        FlowRule flowRule = new FlowRule("/testAddFlowLimitRule");

        // Set the QPS threshold
        flowRule.setCount(1);

        // Set the flow control model to QPS
        flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS);

        flowRuleList.add(flowRule);

        FlowRuleManager.loadRules(flowRuleList);

        return "success";

    }

    @RequestMapping("/testAddFlowLimitRule")
    public String testAddFlowLimitRule(a) {
        return "testAddFlowLimitRule"; }}Copy the code

Add effect screenshot:http://localhost:8080/addFlowLimit Sentinel configuration items:Github.com/alibaba/Sen…

5.5 Disable SpringMVC endpoint protection (disable SpringMVC endpoint protection when performing pressure test in general application scenarios)

spring:
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    sentinel:
      transport:
        dashboard: localhost:9999
      filter:
        enabled: true# Turn off Spring MVC endpoint protectionCopy the code

Then our type of interface will not be sentinel protectedOnly with the@SentinelResourceAnnotated resources are protected

6. Ribbon integrates Sentinel

6.1 Step 1: Add configurations

<! - join the ribbon - >
<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>

<! - to join the sentinel -- -- >
<dependency>
	<groupId>com.alibaba.cloud</groupId>
	<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>


<! - join physical - >
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
Copy the code

6.2 Step 2: Annotate

Add the @SentinelRestTemplate annotation to our RestTemplate component. We can also specify our blockHandlerClass, fallbackClass, blockHandler, and Fallback properties in @SentinelRestTemplate

@Configuration
public class WebConfig {

    @Bean
    @LoadBalanced
    @SentinelRestTemplate( blockHandler = "handleException",blockHandlerClass = GlobalExceptionHandler.class, fallback = "fallback",fallbackClass = GlobalExceptionHandler.class )
    public RestTemplate restTemplate(a) {
        return newRestTemplate(); }} ***************** Global exception handling class *****************@Slf4j
public class GlobalExceptionHandler {


    /** * Current limiting post-processing method *@param request
     * @param body
     * @param execution
     * @param ex
     * @return* /
    public static SentinelClientHttpResponse handleException(HttpRequest request,
                                                             byte[] body, ClientHttpRequestExecution execution, BlockException ex)  {

        ProductInfo productInfo = new ProductInfo();
        productInfo.setProductName("Pulled by restricted flow");
        productInfo.setProductNo("1");
        ObjectMapper objectMapper = new ObjectMapper();

        try {
            return new SentinelClientHttpResponse(objectMapper.writeValueAsString(productInfo));
        } catch (JsonProcessingException e) {
            e.printStackTrace();
            return null; }}/** * Fusing after processing method *@param request
     * @param body
     * @param execution
     * @param ex
     * @return* /
    public static SentinelClientHttpResponse fallback(HttpRequest request,
                                                      byte[] body, ClientHttpRequestExecution execution, BlockException ex) {
        ProductInfo productInfo = new ProductInfo();
        productInfo.setProductName("Demoted and pulled.");
        productInfo.setProductNo("1");
        ObjectMapper objectMapper = new ObjectMapper();

        try {
            return new SentinelClientHttpResponse(objectMapper.writeValueAsString(productInfo));
        } catch (JsonProcessingException e) {
            e.printStackTrace();
            return null; }}}Copy the code

6.3 Step 3: Add the configuration

When to turn this off: Do we normally turn this configuration off when we are testing business functions on our own

Resttemplate: sentinel: enabled: trueCopy the code

OpenFeign integrates our Sentinel

7.1 Step 1: Add configurations

Add the configuration in pom.xml on niuh05-Ms-Alibaba-feignwithSentinel-order

<! - to join the sentinel -- -- >
<dependency>
	<groupId>com.alibaba.cloud</groupId>
	<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>


<! - join physical - >
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

<dependency>
	<groupId>com.niuh</groupId>
	<artifactId>niuh03-ms-alibaba-feign-api</artifactId>
	<version>0.0.1 - the SNAPSHOT</version>
</dependency>
Copy the code

7.2 Step 2: Add the Fallback or fallbackFactory attribute to Feign’s declarative interface

  • API for adding fallback properties for us
@FeignClient(name = "product-center",fallback = ProductCenterFeignApiWithSentinelFallback.class)
public interface ProductCenterFeignApiWithSentinel {

    / * * * declarative interface, remote call http://product-center/selectProductInfoById/ {productNo} *@param productNo
     * @return* /
    @RequestMapping("/selectProductInfoById/{productNo}")
    ProductInfo selectProductInfoById(@PathVariable("productNo") String productNo) throws InterruptedException;
}
Copy the code

Our Feign traffic limiting degrade interface (there is no way to get exceptions through Fallback)

@Component
public class ProductCenterFeignApiWithSentinelFallback implements ProductCenterFeignApiWithSentinel {
    @Override
    public ProductInfo selectProductInfoById(String productNo) {
        ProductInfo productInfo = new ProductInfo();
        productInfo.setProductName("Default goods");
        returnproductInfo; }}Copy the code
  • API to add fallbackFactory property for us
package com.niuh.feignapi.sentinel;

import com.niuh.entity.ProductInfo;
import com.niuh.handler.ProductCenterFeignApiWithSentielFallbackFactoryasdasf;
import com.niuh.handler.ProductCenterFeignApiWithSentinelFallback;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

/** * Created by hejianhui on 2019/11/22. */

@FeignClient(name = "product-center",fallbackFactory = ProductCenterFeignApiWithSentielFallbackFactoryasdasf.class)
public interface ProductCenterFeignApiWithSentinel {

    / * * * declarative interface, remote call http://product-center/selectProductInfoById/ {productNo} *@param productNo
     * @return* /
    @RequestMapping("/selectProductInfoById/{productNo}")
    ProductInfo selectProductInfoById(@PathVariable("productNo") String productNo) throws InterruptedException;
}

Copy the code

Our exception can be handled through the FallbackFactory property

@Component
@Slf4j
public class ProductCenterFeignApiWithSentielFallbackFactoryasdasf implements FallbackFactory<ProductCenterFeignApiWithSentinel> {
    @Override
    public ProductCenterFeignApiWithSentinel create(Throwable throwable) {
        return new ProductCenterFeignApiWithSentinel(){

            @Override
            public ProductInfo selectProductInfoById(String productNo) {
                ProductInfo productInfo = new ProductInfo();
                if (throwable instanceof FlowException) {
                    log.error("Flow control.... {}",throwable.getMessage());
                    productInfo.setProductName("I'm the default product of flow control.");
                }else {
                    log.error("Downgraded.... {}",throwable.getMessage());
                    productInfo.setProductName("I'm the default product to be downgraded.");
                }

                returnproductInfo; }}; }}Copy the code

8. Sentinel rule Persistence

The sentinel-Dashboard configuration rules were cleared when our microservices and console restarted because it was memory-based.

8.1 Native Mode

Dashboard pushes rules to clients through apis and updates them directly to memory. The advantages and disadvantages: The advantage of this approach is simple, no dependence; The downside is that the application of the restart rule disappears and is only used for simple testing, not production.

8.2 Pull Mode

The Sentinel console first pushes the rules to the client via the API and updates them to memory. The registered write data source then saves the new rules to a local file. There is generally no need to modify the Sentinel console when using pull mode data sources.

The advantage of this implementation method is that it is simple and does not introduce new dependencies, while the disadvantage is that it cannot guarantee the consistency of monitoring data

The transformation of Sentinel client (pull mode)

Extensions through the SPI extension mechanism, we write a la mode implementation class com. Niuh. Persistence. PullModeByFileDataSource, Directory at the factory and then create a meta-inf/services/com. Alibaba. CSP. The sentinel. Init. InitFun file.The content of the file is to write our pull pattern implementation class:

Code in niuh05 – ms – alibaba – sentinelrulepersistencepull – order the persistence of engineering.

8.3 Push mode (Taking Nacos as an example, recommended for production)

The principle is briefly

  • Console push rules:
    • Push rules to Nacos for other remote configuration centers
    • Sentinel client connected to Nacos to obtain the rule configuration. And listens for Nacos configuration changes and updates the local cache (so that it is always consistent with Nacos) if changes are sent
  • The console listens for Nacos configuration changes and updates the local cache if the changes are sent (to make the console local cache consistent with Nacos)

Retrofit scheme

Micro service transformation scheme

  • The first step: in niuh05 – ms – alibaba – sentinelrulepersistencepush – order engineering join dependency
<dependency>
	<groupId>com.alibaba.csp</groupId>
	<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
Copy the code
  • Step 2: Add the YML configuration
spring: cloud: nacos: discovery: server-addr: localhost:8848 sentinel: transport: dashboard: localhost:9999 #namespace: Bc7613d2-2e22-4292-a748-48b78170f14c # specify namespace ID. Datasource: Degrade: dataId: ${spring.application. Name}-flow-rules groupId: SENTINEL_GROUP rule-type: flow nacos: server-addr: localhost:8848 dataId: ${spring.application.name}-degrade-rules groupId: SENTINEL_GROUP rule-type: degrade system: nacos: server-addr: localhost:8848 dataId: ${spring.application.name}-system-rules groupId: SENTINEL_GROUP rule-type: system authority: nacos: server-addr: localhost:8848 dataId: ${spring.application.name}-authority-rules groupId: SENTINEL_GROUP rule-type: authority param-flow: nacos: server-addr: localhost:8848 dataId: ${spring.application.name}-param-flow-rules groupId: SENTINEL_GROUP rule-type: param-flowCopy the code

Sentinel-dashboard transformation proposal

<! -- for Nacos rule publisher sample -->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
   <! -- <scope>test</scope>-->// we need to comment out test</dependency>
Copy the code

The console modification is mainly for the rule implementation:

  • DynamicRuleProvider: Reads configuration from Nacos
  • DynamicRulePublisher: Push rules to Nacis

In the sentinel – dashboard project directory com. Alibaba. CSP. Sentinel. Dashboard. Create a Nacos package under rule, and then write each configuration rules of our class under the package. We use the ParamFlowRuleController(hot parameter flow control class as modification) as a demonstration.

/ * * *@author Eric Zhao
 * @since0.2.1 * /
@RestController
@RequestMapping(value = "/paramFlow")
public class ParamFlowRuleController {

    private final Logger logger = LoggerFactory.getLogger(ParamFlowRuleController.class);

    @Autowired
    private SentinelApiClient sentinelApiClient;
    @Autowired
    private AppManagement appManagement;
    @Autowired
    private RuleRepository<ParamFlowRuleEntity, Long> repository;

    @Autowired
    @Qualifier("niuhHotParamFlowRuleNacosPublisher")
    private DynamicRulePublisher<List<ParamFlowRuleEntity>> rulePublisher;

    @Autowired
    @Qualifier("niuhHotParamFlowRuleNacosProvider")
    private DynamicRuleProvider<List<ParamFlowRuleEntity>> ruleProvider;

    @Autowired
    private AuthService<HttpServletRequest> authService;

    private boolean checkIfSupported(String app, String ip, int port) {
        try {
            return Optional.ofNullable(appManagement.getDetailApp(app))
                .flatMap(e -> e.getMachine(ip, port))
                .flatMap(m -> VersionUtils.parseVersion(m.getVersion())
                    .map(v -> v.greaterOrEqual(version020)))
                .orElse(true);
            // If error occurred or cannot retrieve machine info, return true.
        } catch (Exception ex) {
            return true; }}@GetMapping("/rules")
    public Result<List<ParamFlowRuleEntity>> apiQueryAllRulesForMachine(HttpServletRequest request,
                                                                        @RequestParam String app,
                                                                        @RequestParam String ip,
                                                                        @RequestParam Integer port) {
        AuthUser authUser = authService.getAuthUser(request);
        authUser.authTarget(app, PrivilegeType.READ_RULE);
        if (StringUtil.isEmpty(app)) {
            return Result.ofFail(-1."app cannot be null or empty");
        }
        if (StringUtil.isEmpty(ip)) {
            return Result.ofFail(-1."ip cannot be null or empty");
        }
        if (port == null || port <= 0) {
            return Result.ofFail(-1."Invalid parameter: port");
        }
        if(! checkIfSupported(app, ip, port)) {return unsupportedVersion();
        }
        try {
/* return sentinelApiClient.fetchParamFlowRulesOfMachine(app, ip, port) .thenApply(repository::saveAll) .thenApply(Result::ofSuccess) .get(); * /
            List<ParamFlowRuleEntity> rules = ruleProvider.getRules(app);
            rules = repository.saveAll(rules);
            return Result.ofSuccess(rules);
        } catch (ExecutionException ex) {
            logger.error("Error when querying parameter flow rules", ex.getCause());
            if (isNotSupported(ex.getCause())) {
                return unsupportedVersion();
            } else {
                return Result.ofThrowable(-1, ex.getCause()); }}catch (Throwable throwable) {
            logger.error("Error when querying parameter flow rules", throwable);
            return Result.ofFail(-1, throwable.getMessage()); }}private boolean isNotSupported(Throwable ex) {
        return ex instanceof CommandNotFoundException;
    }

    @PostMapping("/rule")
    public Result<ParamFlowRuleEntity> apiAddParamFlowRule(HttpServletRequest request,
                                                           @RequestBody ParamFlowRuleEntity entity) {
        AuthUser authUser = authService.getAuthUser(request);
        authUser.authTarget(entity.getApp(), PrivilegeType.WRITE_RULE);
        Result<ParamFlowRuleEntity> checkResult = checkEntityInternal(entity);
        if(checkResult ! =null) {
            return checkResult;
        }
        if(! checkIfSupported(entity.getApp(), entity.getIp(), entity.getPort())) {return unsupportedVersion();
        }
        entity.setId(null);
        entity.getRule().setResource(entity.getResource().trim());
        Date date = new Date();
        entity.setGmtCreate(date);
        entity.setGmtModified(date);
        try {
            entity = repository.save(entity);
            //publishRules(entity.getApp(), entity.getIp(), entity.getPort()).get();
            publishRules(entity.getApp());
            return Result.ofSuccess(entity);
        } catch (ExecutionException ex) {
            logger.error("Error when adding new parameter flow rules", ex.getCause());
            if (isNotSupported(ex.getCause())) {
                return unsupportedVersion();
            } else {
                return Result.ofThrowable(-1, ex.getCause()); }}catch (Throwable throwable) {
            logger.error("Error when adding new parameter flow rules", throwable);
            return Result.ofFail(-1, throwable.getMessage()); }}private <R> Result<R> checkEntityInternal(ParamFlowRuleEntity entity) {
        if (entity == null) {
            return Result.ofFail(-1."bad rule body");
        }
        if (StringUtil.isBlank(entity.getApp())) {
            return Result.ofFail(-1."app can't be null or empty");
        }
        if (StringUtil.isBlank(entity.getIp())) {
            return Result.ofFail(-1."ip can't be null or empty");
        }
        if (entity.getPort() == null || entity.getPort() <= 0) {
            return Result.ofFail(-1."port can't be null");
        }
        if (entity.getRule() == null) {
            return Result.ofFail(-1."rule can't be null");
        }
        if (StringUtil.isBlank(entity.getResource())) {
            return Result.ofFail(-1."resource name cannot be null or empty");
        }
        if (entity.getCount() < 0) {
            return Result.ofFail(-1."count should be valid");
        }
        if(entity.getGrade() ! = RuleConstant.FLOW_GRADE_QPS) {return Result.ofFail(-1."Unknown mode (blockGrade) for parameter flow control");
        }
        if (entity.getParamIdx() == null || entity.getParamIdx() < 0) {
            return Result.ofFail(-1."paramIdx should be valid");
        }
        if (entity.getDurationInSec() <= 0) {
            return Result.ofFail(-1."durationInSec should be valid");
        }
        if (entity.getControlBehavior() < 0) {
            return Result.ofFail(-1."controlBehavior should be valid");
        }
        return null;
    }

    @PutMapping("/rule/{id}")
    public Result<ParamFlowRuleEntity> apiUpdateParamFlowRule(HttpServletRequest request,
                                                              @PathVariable("id") Long id,
                                                              @RequestBody ParamFlowRuleEntity entity) {
        AuthUser authUser = authService.getAuthUser(request);
        if (id == null || id <= 0) {
            return Result.ofFail(-1."Invalid id");
        }
        ParamFlowRuleEntity oldEntity = repository.findById(id);
        if (oldEntity == null) {
            return Result.ofFail(-1."id " + id + " does not exist");
        }
        authUser.authTarget(oldEntity.getApp(), PrivilegeType.WRITE_RULE);
        Result<ParamFlowRuleEntity> checkResult = checkEntityInternal(entity);
        if(checkResult ! =null) {
            return checkResult;
        }
        if(! checkIfSupported(entity.getApp(), entity.getIp(), entity.getPort())) {return unsupportedVersion();
        }
        entity.setId(id);
        Date date = new Date();
        entity.setGmtCreate(oldEntity.getGmtCreate());
        entity.setGmtModified(date);
        try {
            entity = repository.save(entity);
            //publishRules(entity.getApp(), entity.getIp(), entity.getPort()).get();
            publishRules(entity.getApp());
            return Result.ofSuccess(entity);
        } catch (ExecutionException ex) {
            logger.error("Error when updating parameter flow rules, id=" + id, ex.getCause());
            if (isNotSupported(ex.getCause())) {
                return unsupportedVersion();
            } else {
                return Result.ofThrowable(-1, ex.getCause()); }}catch (Throwable throwable) {
            logger.error("Error when updating parameter flow rules, id=" + id, throwable);
            return Result.ofFail(-1, throwable.getMessage()); }}@DeleteMapping("/rule/{id}")
    public Result<Long> apiDeleteRule(HttpServletRequest request, @PathVariable("id") Long id) {
        AuthUser authUser = authService.getAuthUser(request);
        if (id == null) {
            return Result.ofFail(-1."id cannot be null");
        }
        ParamFlowRuleEntity oldEntity = repository.findById(id);
        if (oldEntity == null) {
            return Result.ofSuccess(null);
        }
        authUser.authTarget(oldEntity.getApp(), PrivilegeType.DELETE_RULE);
        try {
            repository.delete(id);
            /*publishRules(oldEntity.getApp(), oldEntity.getIp(), oldEntity.getPort()).get(); * /
            publishRules(oldEntity.getApp());
            return Result.ofSuccess(id);
        } catch (ExecutionException ex) {
            logger.error("Error when deleting parameter flow rules", ex.getCause());
            if (isNotSupported(ex.getCause())) {
                return unsupportedVersion();
            } else {
                return Result.ofThrowable(-1, ex.getCause()); }}catch (Throwable throwable) {
            logger.error("Error when deleting parameter flow rules", throwable);
            return Result.ofFail(-1, throwable.getMessage()); }}private CompletableFuture<Void> publishRules(String app, String ip, Integer port) {
        List<ParamFlowRuleEntity> rules = repository.findAllByMachine(MachineInfo.of(app, ip, port));
        return sentinelApiClient.setParamFlowRuleOfMachine(app, ip, port, rules);
    }

    private void publishRules(String app) throws Exception {
        List<ParamFlowRuleEntity> rules = repository.findAllByApp(app);
        rulePublisher.publish(app, rules);
    }

    private <R> Result<R> unsupportedVersion(a) {
        return Result.ofFail(4041."Sentinel client not supported for parameter flow control (unsupported version or dependency absent)");
    }

    private final SentinelVersion version020 = new SentinelVersion().setMinorVersion(2);
}

Copy the code

8.4 AHAS of Aliyun

  • Open at ahas.console.aliyun.com/
  • Opening rules: help.aliyun.com/document_de…

The first step: visitHelp.aliyun.com/document_de… The second step: Free access The third stepOpening: The fourth step: Access applications Step 5: Click to access SDK Step 6: Join our app

Take niUH05-MS-Alibaba – SentinelRulePersistence – Ahas-Order project as an example

  • Join aHAS dependencies
<dependency>
	<groupId>com.alibaba.csp</groupId>
	<artifactId>Spring ‐ boot ‐ starter ‐ ahas ‐ sentinel ‐ client</artifactId> 4 <version>1.5.0</version>
</dependency>
Copy the code
  • Add configuration: yML configuration
ahas.namespace: default
project.name: order-center
ahas.license: b833de8ab5f34e4686457ecb2b60fa46
Copy the code
  • The test interface
@SentinelResource("hot-param-flow-rule")
@RequestMapping("/testHotParamFlowRule")
public OrderInfo testHotParamFlowRule(@RequestParam("orderNo") String orderNo) {
    return orderInfoMapper.selectOrderInfoById(orderNo);
}
Copy the code

First access interface:Our microservice appears on the AHas consoleAdd our direct flow control rulesRefresh our test interface crazily:

Optimization of Sentinel online environment

9.1 Optimizing the Error Page

  • Flow control error page

  • Degraded error page

It turns out that both of these errors are hospitals, so obviously we need to optimize hereUrlBlockHandlerProvides an interface that we need to implement

/ * * *@vlogHigher than life, from life *@descClass description: Handles flow control, degradation rules *@author: hejianhui
* @createDate: 2019/12/3 they *@version: 1.0 * /
@Component
public class NiuhUrlBlockHandler implements UrlBlockHandler {

    public static final Logger log = LoggerFactory.getLogger(NiuhUrlBlockHandler.class);

    @Override
    public void blocked(HttpServletRequest request, HttpServletResponse response, BlockException ex) throws IOException {

        if(ex instanceof FlowException) {
            log.warn("Triggered the flow control.");
            warrperResponse(response,ErrorEnum.FLOW_RULE_ERR);
        }else if(ex instanceof ParamFlowException) {
            log.warn("Triggered parameter flow control.");
            warrperResponse(response,ErrorEnum.HOT_PARAM_FLOW_RULE_ERR);
        }else if(ex instanceof AuthorityException) {
            log.warn("Triggered the authorization rule.");
            warrperResponse(response,ErrorEnum.AUTH_RULE_ERR);
        }else if(ex instanceof SystemBlockException) {
            log.warn("Triggered a system rule.");
            warrperResponse(response,ErrorEnum.SYS_RULE_ERR);
        }else{
            log.warn("Triggered the downgrade rule."); warrperResponse(response,ErrorEnum.DEGRADE_RULE_ERR); }}private void warrperResponse(HttpServletResponse httpServletResponse, ErrorEnum errorEnum) throws IOException {
        httpServletResponse.setStatus(500);
        httpServletResponse.setCharacterEncoding("UTF-8");
        httpServletResponse.setHeader("Content-Type"."application/json; charset=utf-8");
        httpServletResponse.setContentType("application/json; charset=utf-8");

        ObjectMapper objectMapper = new ObjectMapper();
        String errMsg =objectMapper.writeValueAsString(newErrorResult(errorEnum)); httpServletResponse.getWriter().write(errMsg); }}Copy the code

After the optimization:

  • Flow control rule tips:

  • Demotion Rule Tips:

9.2 Source coding implementation

Sentinel provides oneRequestOriginParserInterface, where we can implement code to distinguish sources from request headers

/ * * *@vlogHigher than life, from life *@desc: Class description: distinguish the source interface *@author: hejianhui
* @createDate: 2019/12/4 "*@version: 1.0 * /
/*@Component*/
@Slf4j
public class NiuhRequestOriginParse implements RequestOriginParser {

    @Override
    public String parseOrigin(HttpServletRequest request) {
        String origin = request.getHeader("origin");
        if(StringUtils.isEmpty(origin)) {
            log.warn("origin must not null");
            throw new IllegalArgumentException("request origin must not null");
        }
        returnorigin; }}Copy the code

The source of the configuration Settings is yijiaoqian

9.3 Resolving RestFul Requests

For example, /selectOrderInfoById/2, /selectOrderInfoById/1 needs to be converted to /selectOrderInfoById/{number}.

/ * * *@vlogHigher than life, from life *@descResolve restfule-style requests * eg: /selectOrderInfoById/2 /selectOrderInfoById/1 needs to be converted to /selectOrderInfoById/{number} *@author: hejianhui
* @createDate: 2019/12/4 13:28
* @version: 1.0 * /
@Component
@Slf4j
public class NiuhUrlClean implements UrlCleaner {
    @Override
    public String clean(String originUrl) {
        log.info("originUrl:{}",originUrl);

        if(StringUtils.isEmpty(originUrl)) {
            log.error("originUrl not be null");
            throw new IllegalArgumentException("originUrl not be null");
        }
        return replaceRestfulUrl(originUrl);
    }

    /** * replace /selectOrderInfoById/2 with /{number} *@author:hejianhui
     * @paramSourceUrl Destination URL *@return: Indicates the new URL *@exception:
     * @date: 2019/12/4 13:46 * /
    private String replaceRestfulUrl(String sourceUrl) {
        List<String> origins = Arrays.asList(sourceUrl.split("/"));
        StringBuffer targetUrl = new StringBuffer("/");

        for(String str:origins) {
            if(NumberUtils.isNumber(str)) {
                targetUrl.append("/{number}");
            }else{ targetUrl.append(str); }}returntargetUrl.toString(); }}Copy the code

PS: The above code is submitted to Github: github.com/Niuh-Study/…

GitHub Org_Hejianhui /JavaStudy GitHub Hejianhui /JavaStudy