背景分析
在一些节日中,例如京东、淘宝、拼多多、格物等平台参与商品秒杀、抢购以及一些优惠活动,也会在节假日使用车票等等都会引起服务器流量的暴涨,导致网页无法显示,app反应慢,甚至引起整个网站的崩溃。
当业务的负载过重时,为了保证各种业务安全运营,就需要限流、熔断、降级等措施来保护的系统,需要控制流量适用,需要sentinel技术
sentinel概述
sentinel(分布式系统的流量方卫兵)是阿里开源的一套用于服务容错的综合性解决方案。随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 是面向分布式服务架构的流量控制组件,主要以流量为切入点,从流量控制、熔断降级、系统自适应保护等多个维度来帮助您保障微服务的稳定性。
#启动sentinel
一、下载jar包
https://github.com/alibaba/Sentinel/releases
二、在cmd中运行代码
java -Dserver.port=8180 -Dcsp.sentinel.dashboard.server=localhost:8180 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.0.jar
三、访问sentinel,通过浏览器进行访问
http://localhost:8180
sentinel使用
一、添加依赖
<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>
二、配置application.yml文件,添加sentinel配置
spring:
cloud:
sentinel:
transport:
port: 8099 #跟sentinel控制台交流的端口,随意指定一个未使用的端口即可
dashboard: localhost:8180 # 指定sentinel控制台地址。
三、访问路径以及运行结果
sentinel限流策略
阈值类型分析
-
QPS(Queries Per Second):当调用相关url对应的资源时,QPS达到单机阈值时,就会限流。
-
线程数:当调用相关url对应的资源时,线程数达到单机阈值时,就会限流。
QPS: 每秒请求次数
单机阈值:允许每秒请求的次数
注:下图处理过的数据
二、关联限流
当关联的资源达到阈值,就限流自己 (操作:连续输出doRestEcho2,快速点击doRestEcho1,就会造成现在的状况)
简述:当关联资源doRestEcho2的访问量过大时,doRestEcho1就被限流了
三、链路限流
链路模式只记录指定链路入口的流量。也就是当多个服务对指定资源调用时,假如流量超出了指定阈值,则进行限流。被调用的方法用@SentinelResource进行注解,然后分别用不同业务方法对此业务进行调用,假如A业务设置了链路模式的限流,在B业务中是不受影响的。例如现在设计一个业务对象,代码如下(为了简单,可以直接写在启动类内部):
@Service
public class ConsumerService {
/**@SentinelResource
* */
@SentinelResource
public String doGetResource() {
return "Get resource";
}
}
接下来我们在/consumer/doRestEcho1对应的方法中对ConsumerService中的doGetResource方法进行调用(应用consumerService对象之前,要先在doRestEcho01方法所在的类中进行consumerService值的注入)。例如:
//http://ip:port/consumer/doRestEcho1
@GetMapping("/consumer/doRestEcho1")
public String doRestEcho1() {
consumerService.doGetResource();
System.out.println("==doRestEcho1()==");
//调用服务提供方API(http://ip:port/path)
//1.定义要调用的API
String url =
"http://localhost:8081/provider/echo/" + appName;
//2.谁去访问这个API? restTemplate;
return restTemplate
.getForObject(url,
String.class);
}
说明,流控模式为链路模式时,假如是sentinel 1.7.2以后版本,Sentinel Web过滤器默认会聚合所有URL的入口为sentinel_spring_web_context,因此单独对指定链路限流会不生效,需要在application.yml添加如下语句来关闭URL PATH聚合,例如:
sentine降级策略
一、慢调用比例
慢调用指耗时大于阈值RT(Response Time)的请求称为慢调用,阈值RT由用户设置。
慢调用逻辑中的状态分析如下:
熔断(OPEN):请求数大于最小请求数并且慢调用的比率大于比例阈值则发生熔断,熔断时长为用户自定义设置。
探测(HALFOPEN):当熔断过了定义的熔断时长,状态由熔断(OPEN)变为探测(HALFOPEN)。
关闭(CLOSED):如果接下来的一个请求小于最大RT,说明慢调用已经恢复,结束熔断,状态由探测(HALF_OPEN)变更为关闭(CLOSED),如果接下来的一个请求大于最大RT,说明慢调用未恢复,继续熔断,熔断时长保持一致
注意:Sentinel默认统计的RT上限是4900ms,超出此阈值的都会算作4900ms,若需要变更此上限可以通过启动配置项-Dcsp.sentinel.statistic.max.rt=xxx来配置
代码:
//AtomicLong 类支持线程安全的自增自减操作
private AtomicLong along = new AtomicLong(1);
@GetMapping("/consumer/doRestAlong")
public String doRestAlong() throws InterruptedException {
//获取自增对象的值,然后再加1
long num = along.getAndIncrement();
//模拟50%的慢调用比例
if (num%2 ==0) {
Thread.sleep(200);
}
String url="http://localhost:8081/provider/echo/"+appName;
//远程过程调用-RPC
return restTemplate.getForObject(url,String.class);//String.class调用服务响应数据类型
}
熔断策略为慢调用比例,表示链路请求数超过3时,假如平均响应时间假如超过200毫秒的有50%,则对请求进行熔断,熔断时长为10秒钟,10秒以后恢复正常。
二、异常比例
系统提供了默认的异常处理机制,假如默认处理机制不满足我们需求,我们可以自己进行定义。定义方式上可以直接或间接实现BlockExceptionHandler接口,并将对象交给spring管理。
@Component
public class ServiceBlockExceptionHandler implements BlockExceptionHandler {
/**
* 一旦服务被限流或者降级了,sentinel系统的提供拦截器会
* 调用此方法对异常
*/
@Override
public void handle(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
BlockException e) throws Exception {
//设置响应数据的编码
// httpServletResponse.setCharacterEncoding("UTF-8");
//告诉客户端要响应的数据类型以及客户端以什么编码呈现数据
// httpServletResponse.setContentType("text/html;charset=utf8");
httpServletResponse.setContentType("application/json;charset=utf-8");
PrintWriter out = httpServletResponse.getWriter();
Map<String,Object> map = new HashMap<>();
map.put("status", 429);
if (e instanceof DegradeException) {//降级、熔断
// out.println("服务暂时不可用");
map.put("Message", "服务不可用");
} else {
// out.println("<h2>访问太频繁,稍等片刻</h2>");
map.put("message", "访问太频繁");
}
//将map对象转换为json格式字符串
String jsonStr = new ObjectMapper().writeValueAsString(map);
out.println(jsonStr);
out.flush();
}
}
原理:
异常比例中的状态分析如下:
熔断(OPEN):当请求数大于最小请求并且异常比例大于设置的阈值时触发熔断,熔断时长由用户设置。
探测(HALFOPEN):当超过熔断时长时,由熔断(OPEN)转为探测(HALFOPEN)
关闭(CLOSED):如果接下来的一个请求未发生错误,说明应用恢复,结束熔断,状态由探测(HALF_OPEN)变更为关闭(CLOSED)。如果接下来的一个请求继续发生错误,说明应用未恢复,继续熔断,熔断时长保持一致。
三、异常数量
当资源近1分钟的异常数目超过阈值(异常数)之后会进行服务降级。注意,由于统计时间窗口是分钟级别的,若熔断时长小于60s,则结束熔断状态后仍可能再次进入熔断状态。其属性说明如下:
sentinel热点策略
热点参数限流会统计传入参数中的热点数据,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。其中,Sentinel会利用 LRU 策略统计最近最常访问的热点参数,结合令牌桶算法来进行参数级别的流控。
//http://ip:port/consumer/doFindById?id=10
@GetMapping("/consumer/findById")
@SentinelResource("res")
public String doFindById(@RequestParam("id") Integer id){
return "resource id is "+id;
}
热点规则的限流模式只有QPS模式(这才叫热点)。参数索引为@SentinelResource注解的方法参数下标,0代表第一个参数,1代表第二个参数。单机阈值以及统计窗口时长表示在此窗口时间超过阈值就限流。
系统所报的异常:
com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException: 2
sentinel特定参数策略
这里表示参数值为5时阈值为100,其它参数值阈值为1,例如当我们访问http://ip:port/consumer/doRestEcho1?id=5时的限流阈值为100。
sentinel系统保护规则
Sentinel的系统保护规则是从应用级别的入口流量进行控制,从单台机器的总体 Load、RT、入口 QPS 、线程数和CPU使用率五个维度监控应用数据,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。如图所示:
Load(仅对 Linux/Unix-like 机器生效):当系统 load1 超过阈值,且系统当前的并发线程数超过系统容量时才会触发系统保护。系统容量由系统的 maxQps * minRt 计算得出。设定参考值一般是 CPU cores * 2.5。
CPU使用率:当系统 CPU 使用率超过阈值即触发系统保护(取值范围 0.0-1.0)。
RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。
线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。
系统保护规则是应用整体维度的,而不是资源维度的,并且仅对入口流量生效。入口流量指的是进入应用的流量(EntryType.IN),比如 Web 服务。
sentinel授权规则
在业务需要时限制资源是否通过,这时候可以使用 Sentinel 的黑白名单控制的功能。黑白名单根据资源的请求来源(origin)限制资源是否通过,若配置白名单则只有请求来源位于白名单内时才可通过;若配置黑名单则请求来源位于黑名单时不通过,其余的请求通过。例如微信中的黑名单。
资源名:即限流规则的作用对象
流控应用:对应的黑名单/白名单中设置的规则值,多个值用逗号隔开.
授权类型:白名单,黑名单(不允许访问)
案例实现:
定义请求解析器,用于对请求进行解析,并返回解析结果,sentinel底层 在拦截到用户请求以后,会对请求数据基于此对象进行解析,判定是否符合黑白名单规则
可以通过参数名、请求ip、请求头来授权黑白名单
@Component
public class DefaultRequestOriginParser implements RequestOriginParser {
@Override
public String parseOrigin(HttpServletRequest httpServletRequest) {
//获取请求参数数据,参数名可以自己写 例如origin,然后基于参数值做黑白名单
//http://ip:port/path?origin=app
// String origin = httpServletRequest.getParameter("origin");
// return origin;
//获取访问请求中的ip地址,基于ip地址进行黑白名单设计
// String ip = httpServletRequest.getRemoteAddr();
// System.out.println("ip"+ip);
// return ip;
//获取请求头中的数据,基于请求头中token值进行限流统计
String token = httpServletRequest.getHeader("token");
return token;
}//授权规则中的黑白名单的值,来自此方法的返回值
}