莫大人

Sentinel流控与熔断

参考: https://thinkwon.blog.csdn.net/article/details/103770879

项目结构

com.guo     
├── guo-sentinel             // sentinel限流熔断学习
│       └── guo-sentinel-base                         // [9204]消费端,限流、熔断在这里体现
│       └── guo-sentinel-provider                     // [9205]接口提供端

安装Sentinel控制台

Sentinel控制台是一个轻量级的控制台应用,它可用于实时查看单机资源监控及集群资源汇总,并提供了一系列的规则管理功能,如流控规则、降级规则、热点规则等。

  • 下载</br> 可以从https://github.com/alibaba/Sentinel/releases下载sentinel-dashboard-$version.jar包。我这里下载的是 sentinel-dashboard-1.8.0.jar 版本。可以从百度云盘下载 提取码:0708

  • 启动控制台

    java -Dserver.port=8718 -Dcsp.sentinel.dashboard.server=localhost:8718 -Dproject.name=sentinel-dashboard -Dcsp.sentinel.api.port=8719 -jar D:\bgy\install\微服务\sentinel-dashboard-1.8.0.jar

    其中-Dserver.port=8718用于指定Sentinel控制台端口为8718,D:\bgy\install\微服务\sentinel-dashboard-1.8.0.jar为下载的包路径地址。

  • 打开控制台</br> Sentinel提供了一个可视化的操作平台,安装好之后,在浏览器中输入(http://localhost:8718 (opens new window))就可以访问了,默认的用户名和密码都是sentinel(我使用的是1.8.0版本)

sentinel控制台

 

限流

添加依赖

<!-- 在guo-sentinel导入依赖 -->
<dependencyManagement>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud.alibaba}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencyManagement>
<!-- 在guo-sentinel-base添加依赖 -->
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
<version>${spring-cloud.alibaba}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>

添加bootstrap.yml配置

server:
port: 9100
address: localhost
 
 
spring:
application:
  name: guo-sentinel-base
cloud:
  sentinel:
    transport:
      dashboard: localhost:8718   #sentinel控制台的请求地址

按资源名称限流

  • 在guo-sentinel-base编写需要限流的资源接口


import java.util.HashMap;
import java.util.Map;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.guo.sentinel.handle.CustomBlockHandler;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@RestController
@RequestMapping("/rateLimit")
public class RateLimitController {

   /**
    * 按资源名称限流,需要指定限流处理逻辑
    *
    * @return
    */
   @GetMapping("/byResource")
   @SentinelResource(value = "byResource", blockHandler = "handleException")
   public Map<String,Object> byResource() {
  log.info("/rateLimit/byResource");
       Map<String,Object> result = new HashMap<>();
       result.put("name","按资源名称限流");
       result.put("code",200);
       return result ;
  }

   /**
    * 按url限流,有默认的限流处理逻辑
    *
    * @return
    */
   @GetMapping("byUrl")
   @SentinelResource(value = "byUrl", blockHandler = "handleException")
   public Map<String,Object> byUrl() {
  log.info("/rateLimit/byResource");
       Map<String,Object> result = new HashMap<>();
       result.put("name","按url限流");
       result.put("code",200);
       return result ;
  }

   public Map<String,Object> handleException(BlockException exception) {
       Map<String,Object> result = new HashMap<>();
       result.put("name",exception.getClass().getCanonicalName());
       result.put("code",200);
       return result ;
  }

   @GetMapping("/customBlockHandler")
   @SentinelResource(value = "customBlockHandler", blockHandler = "handleException", blockHandlerClass = CustomBlockHandler.class)
   public Map<String,Object> blockHandler() {
       Map<String,Object> result = new HashMap<>();
       result.put("name","限流成功");
       result.put("code",200);
       return result ;
  }


}
  • 在项目guo-sentinel-provider编写远程测试接口

controller


import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.guo.sentinel.service.UserService;

@RestController
@RequestMapping("/user")
public class UserController {

   @Autowired
   private UserService userService;

   @PostMapping("/insert")
   public Map<String,Object> insert(@RequestBody Map<String,Object> user) {
       return userService.insert(user);
  }

   @GetMapping("/{id}")
   public Map<String,Object> getUser(@PathVariable Long id) {
       return userService.getUser(id);
  }

   @GetMapping("/listUsersByIds")
   public Map<String,Object> listUsersByIds(@RequestParam List<Long> ids) {
       return userService.listUsersByIds(ids);
  }

   @GetMapping("/getByUsername")
   public Map<String,Object> getByUsername(@RequestParam String username) {
       return userService.getByUsername(username);
  }

   @PostMapping("/update")
   public Map<String,Object> update(@RequestBody Map<String,Object> user) {
       return userService.update(user);
  }

   @PostMapping("/delete/{id}")
   public Map<String,Object> delete(@PathVariable Long id) {
       return userService.delete(id);
  }

}

service

import java.util.List;
import java.util.Map;

public interface UserService {

   Map<String,Object> insert(Map<String,Object> user);

   Map<String,Object> getUser(Long id);

   Map<String,Object> listUsersByIds(List<Long> ids);

   Map<String,Object> getByUsername(String username);

   Map<String,Object> update(Map<String,Object> user);

   Map<String,Object> delete(Long id);

}

service.impl

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.stereotype.Service;

import com.guo.sentinel.service.UserService;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Service
public class UserServiceImpl implements UserService {

@Override
public Map<String, Object> insert(Map<String, Object> user) {
log.info("新增:{}", user);
return null;
}

@Override
public Map<String, Object> getUser(Long id) {
log.info("获取:{}", id);
if(Long.valueOf("1").equals(id)) {
           throw new RuntimeException("remote func is fail");
      }

       Map<String,Object> result = new HashMap<>();
       result.put("reqData",id);
       result.put("code","200");

       return result ;
}

@Override
public Map<String, Object> listUsersByIds(List<Long> ids) {
log.info("获取集合:{}", ids);
return null;
}

@Override
public Map<String, Object> getByUsername(String username) {
log.info("根据用户名获取:{}", username);
if(1==1) throw new RuntimeException("模拟异常");
return null;
}

@Override
public Map<String, Object> update(Map<String, Object> user) {
log.info("更新:{}", user);
return null;
}

@Override
public Map<String, Object> delete(Long id) {
log.info("删除:{}", id);
return null;
}

}
  • 在nacos配置guo-sentinel-base-dev.yml中添加远程调用接口地址

service-url:
user-service: http://localhost:9205

 

  • 在sentinel控制台给资源添加限流规则 image-20210709175218305

资源名称必须与流量控制定义的资源名称一致

image-20210709175308824

 

 

  • 快速访问接口发现资源被限流, 设置1QPS,连续访问2次以上发现,远程服务并未被调用,直接走了限流逻辑 image-20210709175337627

##按url限流

  • 在sentinel控制台给资源添加限流规则

image-20210709175359003

资源名称必须与流量控制定义的资源访问全路径一致

image-20210709175447627

 

 

自定义限流逻辑

用blockHandlerClass = CustomBlockHandler.class指定限流逻辑
import com.alibaba.csp.sentinel.slots.block.BlockException;

import java.util.HashMap;
import java.util.Map;

public class CustomBlockHandler {

   public static Map<String,Object> handleException(BlockException exception) {

       Map<String,Object> result = new HashMap<>();
       result.put("name","自定义限流信息");
       result.put("code",200);
       return result ;
  }

}

熔断

  • 在guo-sentinel-base编写需要熔断的资源接口


import com.alibaba.csp.sentinel.annotation.SentinelResource;

import lombok.extern.slf4j.Slf4j;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import java.util.HashMap;
import java.util.Map;


@Slf4j
@RestController
@RequestMapping("/breaker")
public class CircleBreakerController {

   private static final Logger LOGGER = LoggerFactory.getLogger(CircleBreakerController.class);

   @Autowired
   private RestTemplate restTemplate;

   @Value("${service-url.user-service}")
   private String userServiceUrl;

   @GetMapping("/fallback/{id}")
    @SentinelResource(value = "fallback", fallback = "handleFallback")
   public Map<String,Object> fallback(@PathVariable Long id) {
       Map<String,Object> forObject = restTemplate.getForObject(userServiceUrl + "/user/{1}", Map.class, id);
       System.out.println(forObject);
       return forObject;
  }

   @GetMapping("/fallbackException/{id}")
   @SentinelResource(value = "fallbackException", fallback = "handleFallback2", exceptionsToIgnore = {NullPointerException.class})
   public Map<String,Object> fallbackException(@PathVariable Long id) {
  log.info("/breaker/fallbackException");
       if (id == 1) {
           throw new IndexOutOfBoundsException();
      } else if (id == 2) {
           throw new NullPointerException();
      }

       return restTemplate.getForObject(userServiceUrl + "/user/{1}", Map.class, id);
  }

   public Map<String,Object> handleFallback(Long id) {
       Map<String,Object> result = new HashMap<>();
       result.put("name","服务熔断");
       result.put("code",200);
       return result ;
  }

   public Map<String,Object> handleFallback2(Long id, Throwable e) {
       LOGGER.error("handleFallback2 id:{},throwable class:{}", id, e.getClass());
       Map<String,Object> result = new HashMap<>();
       result.put("name","服务熔断");
       result.put("code",200);

       return result ;
  }
}

在feign接口上的限流与熔断

guo-sentinel-base添加feign接口相关依赖

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

guo-sentinel-base中nacos添加配置项

# 打开sentinel对feign的支持
feign:
sentinel:
  enabled: true

在应用启动类上添加启用feign的注解@EnableFeignClients

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
public class SentinelBaseApplication {

   public static void main(String[] args) {
       SpringApplication.run(SentinelBaseApplication.class, args);
  }
}

创建feign接口

controller


import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.guo.sentinel.service.UserService;

@RestController
@RequestMapping("/user")
public class UserFeignController {

   @Autowired
   private UserService userService;

   @PostMapping("/insert")
   public Map<String,Object> insert(@RequestBody Map<String,Object> user) {
       return userService.insert(user);
  }

   @GetMapping("/{id}")
   public Map<String,Object> getUser(@PathVariable Long id) {
       return userService.getUser(id);
  }

   @GetMapping("/listUsersByIds")
   public Map<String,Object> listUsersByIds(@RequestParam List<Long> ids) {
       return userService.listUsersByIds(ids);
  }

   @GetMapping("/getByUsername")
   public Map<String,Object> getByUsername(@RequestParam String username) {
       return userService.getByUsername(username);
  }

   @PostMapping("/update")
   public Map<String,Object> update(@RequestBody Map<String,Object> user) {
       return userService.update(user);
  }

   @PostMapping("/delete/{id}")
   public Map<String,Object> delete(@PathVariable Long id) {
       return userService.delete(id);
  }

}

service

import java.util.List;
import java.util.Map;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;

import com.guo.sentinel.service.impl.UserFallbackService;

// 注意value = "guo-sentinel-provider"值与服务提供方的服务注册名一致
@FeignClient(value = "guo-sentinel-provider", fallback = UserFallbackService.class)
public interface UserService {

   @PostMapping("/user/insert")
   Map<String,Object> insert(@RequestBody Map<String,Object> user);

   @GetMapping("/user/{id}")
   Map<String,Object> getUser(@PathVariable(value = "id") Long id);

   @GetMapping("/user/listUsersByIds")
   Map<String,Object> listUsersByIds(@RequestParam(value = "ids") List<Long> ids);

   @GetMapping("/user/getByUsername")
   Map<String,Object> getByUsername(@RequestParam(value = "username") String username);

   @PostMapping("/user/update")
   Map<String,Object> update(@RequestBody Map<String,Object> user);

   @PostMapping("/user/delete/{id}")
   Map<String,Object> delete(@PathVariable(value = "id") Long id);

}

fallback熔断逻辑UserFallbackService.java


import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.stereotype.Service;

import com.guo.sentinel.service.UserService;

@Service
public class UserFallbackService implements UserService {

@Override
public Map<String, Object> insert(Map<String, Object> user) {
return this.installFallBack();
}

@Override
public Map<String, Object> getUser(Long id) {
return this.installFallBack();
}

@Override
public Map<String, Object> listUsersByIds(List<Long> ids) {
return this.installFallBack();
}

@Override
public Map<String, Object> getByUsername(String username) {
return this.installFallBack();
}

@Override
public Map<String, Object> update(Map<String, Object> user) {
return this.installFallBack();
}

@Override
public Map<String, Object> delete(Long id) {
return this.installFallBack();
}

private Map<String, Object> installFallBack() {
Map<String, Object> r = new HashMap<>();
r.put("code", 500);
r.put("msg", "调用失败,服务被降级");
return r;
}

}

在sentinel控制台配置熔断逻辑

image-20210712093022290

 

统一限流处理

参考: https://zhuanlan.zhihu.com/p/150058613

 

使用nacos存储限流、熔断规则

默认情况下,当我们在Sentinel控制台中配置规则时,控制台推送规则方式是通过API将规则推送至客户端并直接更新到内存中。一旦我们重启应用,规则将消失。下面我们介绍下如何将配置规则进行持久化,以存储到Nacos为例。

先在guo-sentinel-base pom.xml中添加相关依赖:

<dependency>
   <groupId>com.alibaba.csp</groupId>
   <artifactId>sentinel-datasource-nacos</artifactId>
</dependency>

修改bootstrap.yml添加规则配置

server:
port: 9204
 
spring:
application:
  name: guo-sentinel-base
profiles:
  active: dev    
cloud:
  sentinel:
    transport:
      # 配置Sentinel dashborad地址
      dashboard: http://localhost:8718
      port: 8719
    # 添加Nacos数据源配置
    datasource:
      - nacos:
          server-addr: localhost:8848
          # guo-sentinel-base-flow-rules.json
          data-id: ${spring.application.name}-flow-rules
          group-id: DEFAULT_GROUP
          data-type: json
          rule-type: flow
  nacos:
    discovery:
      server-addr: localhost:8848
    config:
      server-addr: localhost:8848
      file-extension: yml
      ext-config:
      # guo-sentinel-base-dev.yml
        - data-id: ${spring.application.name}-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
          group: DEFAULT_GROUP
          refresh: true
           

在nacos中新建配置guo-sentinel-base-flow-rules.json

[
  {
       "resource": "/rateLimit/byUrl",
       "limitApp": "default",
       "grade": 1,
       "count": 1,
       "strategy": 0,
       "controlBehavior": 0,
       "clusterMode": false
  }
]

限流相关参数解释:

  • resource:资源名称;

  • limitApp:来源应用;

  • grade:阈值类型,0表示线程数,1表示QPS;

  • count:单机阈值;

  • strategy:流控模式,0表示直接,1表示关联,2表示链路;

  • controlBehavior:流控效果,0表示快速失败,1表示Warm Up,2表示排队等待;

  • clusterMode:是否集群。

熔断相关参数解释

  • resource:资源名,即规则的作用对象

  • grade:熔断策略,支持慢调用比例/异常比例/异常数策略,默认为 慢调用比例

  • count:慢调用比例模式下为慢调用临界 RT(超出该值计为慢调用);异常比例/异常数模式下为对应的阈值

  • timeWindow:熔断时长,单位为 s

  • minRequestAmount:熔断触发的最小请求数,请求数小于该值时即使异常比率超出阈值也不会熔断(1.7.0 引入),默认为5

  • statIntervalMs:统计时长(单位为 ms),如 60*1000 代表分钟级(1.8.0 引入),默认为1000ms

  • slowRatioThreshold:慢调用比例阈值,仅慢调用比例模式有效(1.8.0 引入)

 

代码地址

https://gitee.com/laiba_yun/test20210702

 

 

 

 

 

 

 

 

 

 

 

 

 

posted on 2021-07-12 09:34  莫大人  阅读(220)  评论(0编辑  收藏  举报

导航