流量控制(flow control),其原理是监控应用流量的 QPS 或并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。
应用场景:
应对洪峰流量:秒杀、大促、下单、订单回流处理
消息型场景:削峰填谷,冷热启动
付费系统:根据使用流量付费
API Gateway:精准控制API流量
任何应用:探测应用中运行的慢程序块,时行限制
Provider端控制脉冲流量
针对不同调用来源进行流控
Web接口流控
配置规则:
梳理核心接口
通过事前压测评估核心接口的容量
配置QPS阈值
限流阈值类型
QPS(Query Per Second):每秒请求数。就是说服务器在一秒的时间内处理了多少个请求。
QPS
进入簇点链路选择具体的访问的API,然后点击流控按钮
package com.wsm.order.controller;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/order")
public class OrderController {
@RequestMapping("/add")
public String add(){
System.out.println("=========add===");
return "hello world";
}
@RequestMapping("/flow")
@SentinelResource(value = "flow",blockHandler = "flowBlockHandler")
public String flow(){
System.out.println("========flow====");
return "正常访问";
}
public String flowBlockHandler(BlockException e){
return "流控了";
}
}
并发线程数控制
并发数控制用于保护业务线程池不被慢调用耗尽。例如,当应用所依赖的下游应用由于某种原因导致服务不稳定、响应延迟增加,对于调用者来说,意味着吞吐量下降和更多的线程数占用,极端情况下甚至导致线程池耗尽。为应对太多线程占用的情况,业内有使用隔离的方案,比如通过不同业务逻辑使用不同线程池来隔离业务自身之间的资源争抢(线程池隔离)。这种隔离方案虽然隔离性比较好,但是代价就是线程数目太多,线程上下文切换的 overhead 比较大,特别是对低延时的调用有比较大的影响。Sentinel 并发控制不负责创建和管理线程池,而是简单统计当前请求上下文的线程数目(正在执行的调用数目),如果超出阈值,新的请求会被立即拒绝,效果类似于信号量隔离。并发数控制通常在调用端进行配置。
package com.wsm.order.controller;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.TimeUnit;
@RestController
@RequestMapping("/order")
public class OrderController {
@RequestMapping("/add")
public String add(){
System.out.println("=========add===");
return "hello world";
}
@RequestMapping("/flow")
@SentinelResource(value = "flow",blockHandler = "flowBlockHandler")
public String flow(){
System.out.println("========flow====");
return "正常访问";
}
public String flowBlockHandler(BlockException e){
return "流控了";
}
@RequestMapping("/flowThread")
@SentinelResource(value = "flowThread",blockHandler = "flowBlockHandler")
public String flowThread() throws InterruptedException {
TimeUnit.SECONDS.sleep(5);
System.out.println("========flowThread====");
return "正常访问";
}
}
server端统一限流降级返回值
原理
这里给出server端限流原理的源码查看流程,可以看出spring-cloud-starter-alibaba-sentinel中自动装配了拦截器来拦截所有http请求,最终的异常处理类是BlockExceptionHandler。
SentinelWebAutoConfiguration -SentinelWebInterceptor-AbstractSentinelInterceptor-BaseWebMvcConfig-BlockExceptionHandler
sentinel给了一个默认实现类,这也就是我们看到的"Blocked by Sentinel (flow limiting)"。
public class DefaultBlockExceptionHandler implements BlockExceptionHandler {
public DefaultBlockExceptionHandler() {
}
public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
response.setStatus(429);
StringBuffer url = request.getRequestURL();
if ("GET".equals(request.getMethod()) StringUtil.isNotBlank(request.getQueryString())) {
url.append("").append(request.getQueryString());
}
PrintWriter out = response.getWriter();
out.print("Blocked by Sentinel (flow limiting)");
out.flush();
out.close();
}
}
改造
定义自己的异常处理类并加入的spring容器中
package com.wsm.order.domain;
public class Result<T> {
private Integer code;
private String msg;
private T data;
public Result(Integer code, String msg, T data) {
this.code = code;
this.msg = msg;
this.data = data;
}
public Result(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public static Result error(Integer code, String msg){
return new Result(code,msg);
}
}
package com.wsm.order.exception;
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityException;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException;
import com.alibaba.csp.sentinel.slots.system.SystemBlockException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.wsm.order.domain.Result;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
public class MyBlockExceptionHandler implements BlockExceptionHandler {
Logger log = LoggerFactory.getLogger(this.getClass());
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws Exception {
// e.getRule() 资源 规则的详细信息
log.info("==BlockExceptionHandler ======"+ e.getRule());
Result result = null;
if(e instanceof FlowException){
result = Result.error(100,"接口被限流了");
}else if(e instanceof DegradeException){
result = Result.error(101,"服务降级了");
}else if(e instanceof ParamFlowException){
result = Result.error(102,"热点参数限流了");
}else if(e instanceof SystemBlockException){
result = Result.error(103,"触发系统保护规则了");
}else if(e instanceof AuthorityException){
result = Result.error(104,"授权规则不通过");
}
//返回JSON数据
httpServletResponse.setStatus(500);
httpServletResponse.setCharacterEncoding("utf-8");
httpServletResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);
new ObjectMapper().writeValue(httpServletResponse.getWriter(),result);
}
// private BlockExceptionUtil blockExceptionUtil;
//
// public MyBlockExceptionHandler(BlockExceptionUtil blockExceptionUtil) {
// this.blockExceptionUtil = blockExceptionUtil;
// }
// public MyBlockExceptionHandler() {
// }
//
// @Override
// public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws Exception {
// BaseDtoResponse baseDtoResponse = blockExceptionUtil.getResponseDto(e, null);
// httpServletResponse.setStatus(HttpStatus.OK.value());
// httpServletResponse.setCharacterEncoding("UTF-8");
// httpServletResponse.setContentType("application/json;charset=utf-8");
// httpServletResponse.setHeader("Content-Type","application/json;charset=utf-8");
// new ObjectMapper().writeValue(httpServletResponse.getWriter(),baseDtoResponse);
// }
}