随着微服务的流行,服务和服务之间的稳定性变得越来越重要。 https://github.com/alibaba/Sentinel[Sentinel] 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。
https://github.com/alibaba/Sentinel[Sentinel] 具有以下特征:
服务限流/熔断
在高并发情况下,客户端请求达到了一定的极限,也就是我们所设定的阈值,服务就会自动开启自我保护机制,直接走我们的服务降级fallback方法,给客户端一个友好提示。
服务降级
在高并发情况下,为了防止用户一直等待,给用户一个友好提示。
服务的雪崩效应
默认情况下,tomcat/jetty服务器只有一个线程池去处理用户请求。在高并发情况下,如果客户端的所有请求都堆积在同一个接口上,线程池中的所有线程都用来处理这些请求,就会导致其他接口无法访问。
服务隔离机制
服务隔离机制分为两种:信号量隔离和线程池隔离
信号量隔离:最多只能有一定的阈值的线程数来处理我们的请求,超过阈值就会拒绝请求
线程池隔离:每个服务接口都有独立的线程池来处理请求,接口之间互不影响,缺点:占用CPU资源较大
Sentinel和Hytrix区别
下载对应Sentinel-Dashboard
https://github.com/alibaba/Sentinel/releases/tag/1.7.1 运行即可。
默认账号密码:sentinel/sentinel
运行执行命令
java -Dserver.port=8718 -Dcsp.sentinel.dashboard.server=localhost:8718 -Dproject.name=sentinel-dashboard -Dcsp.sentinel.api.port=8719 -jar sentinel-dashboard-1.7.1.jar
maven依赖
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-alibaba-sentinelartifactId>
<version>0.2.2.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
private static final String GETORDER_KEY = "getOrder";
@RequestMapping("/getOrder")
public String getOrders() {
Entry entry = null;
try {
entry = SphU.entry(GETORDER_KEY);
// 执行我们服务需要保护的业务逻辑
return "getOrder接口";
} catch (Exception e) {
e.printStackTrace();
return "该服务接口已经达到上线!";
} finally {
// SphU.entry(xxx) 需要与 entry.exit() 成对出现,否则会导致调用链记录异常
if (entry != null) {
entry.exit();
}
}
}
限流配置放到项目自动加载
@Component
@Slf4j
public class SentinelApplicationRunner implements ApplicationRunner {
private static final String GETORDER_KEY = "getOrder";
@Override
public void run(ApplicationArguments args) throws Exception {
List<FlowRule> rules = new ArrayList<FlowRule>();
FlowRule rule1 = new FlowRule();
rule1.setResource(GETORDER_KEY);
// QPS控制在2以内
rule1.setCount(1);
// QPS限流
rule1.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule1.setLimitApp("default");
rules.add(rule1);
FlowRuleManager.loadRules(rules);
log.info(">>>限流服务接口配置加载成功>>>");
}
}
private static final String GETORDER_KEY = "getOrder";
/***
* @SentinelResource 流量规则资源名
* - blockHandler 限流/熔断 出现异常 执行的方法
* - fallback 服务降级执行的方法
* -QPS=并发数/平均响应时间
* @return
*/
@SentinelResource(value = GETORDER_KEY,blockHandler = "getOrderQpsException")
@RequestMapping("/getOrder")
public String getOrders() {
return "getOrder接口";
}
/**
* 被限流后返回的提示
*
* @param e
* @return
*/
public String getOrderQpsException(BlockException e) {
e.printStackTrace();
return "该接口已经被限流啦!";
}
application.yml
server:
port: 8080
spring:
cloud:
sentinel:
transport:
port: 8719
dashboard: 192.168.75.137:8718
eager: true
application:
name: Sentinel-02
Sentinel控制台中添加限流规则
/***
* 基于并发数量处理限流
* 并发数 = QPS*平均响应时间
* 每次最多只会有一个线程处理该业务逻辑,超出该阈值的情况下,直接拒绝访问。
* @return
*/
@SentinelResource(value = "getOrderThrad", blockHandler = "getOrderQpsException")
@RequestMapping("/getOrderThrad")
public String getgetOrderThread(){
System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "getOrderThrad";
}
Sentinel控制台中添加限流规则
如果要在您的项目中引入 Sentinel,使用 group ID 为 com.alibaba.cloud
和 artifact ID 为 spring-cloud-starter-alibaba-sentinel
的 starter。
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-sentinelartifactId>
dependency>
下面这个例子就是一个最简单的使用 Sentinel 的例子:
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(ServiceApplication.class, args);
}
}
@RestController
public class TestController {
@GetMapping(value = "/hello")
@SentinelResource("hello")
public String hello() {
return "Hello Sentinel";
}
}
@SentinelResource 注解用来标识资源是否被限流、降级。上述例子上该注解的属性 ‘hello’ 表示资源名。
@SentinelResource 还提供了其它额外的属性如 blockHandler
,blockHandlerClass
,fallback
用于表示限流或降级的操作,更多内容可以参考 https://github.com/alibaba/Sentinel/wiki/%E6%B3%A8%E8%A7%A3%E6%94%AF%E6%8C%81[Sentinel注解支持]。
以上例子都是在 WebServlet 环境下使用的,Sentinel 目前已经支持 WebFlux,需要配合 spring-boot-starter-webflux
依赖触发 sentinel-starter 中 WebFlux 相关的自动化配置。
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(ServiceApplication.class, args);
}
}
@RestController
public class TestController {
@GetMapping("/mono")
@SentinelResource("hello")
public Mono<String> mono() {
return Mono.just("simple string")
.transform(new SentinelReactorTransformer<>("otherResourceName"));
}
}
添加maven依赖:
<dependency>
<groupId>com.alibaba.cspgroupId>
<artifactId>sentinel-parameter-flow-controlartifactId>
<version>1.6.3version>
<scope>compilescope>
dependency>
将限流规则,手动放入到项目启动自动加载:
@Component
public class SentinelApplicationRunner implements ApplicationRunner {
private static final String GETORDER_KEY = "getOrder";
@Override
public void run(ApplicationArguments args) throws Exception {
// 定义热点限流的规则,对第一个参数设置 qps 限流模式,阈值为5
ParamFlowRule rule = new ParamFlowRule(GETORDER_KEY)
//热点参数的索引
.setParamIdx(0)
//限流模式
.setGrade(RuleConstant.FLOW_GRADE_QPS)
//阈值
.setCount(2);
ParamFlowRuleManager.loadRules(Collections.singletonList(rule));
}
}
controller层:
@RestController
public class IndexController {
private static final String GETORDER_KEY = "getOrder";
@GetMapping("getOrder")
public String getOrder(int id){
Entry entry = null;
try {
//参数1:资源名称,参数三:计数,参数四:限流参数
entry = SphU.entry(GETORDER_KEY, EntryType.IN, 1, id);
// Your logic here.
} catch (BlockException ex) {
return "该用户服务已经限流,id="+id;
}finally {
if (entry != null) {
entry.exit();
}
}
return GETORDER_KEY+",id="+id;
}
}
java代码:
@RestController
public class IndexController {
private static final String GETORDER_KEY = "getOrder";
@GetMapping("getOrder")
@SentinelResource(GETORDER_KEY)
public String getOrder(int id) {
return GETORDER_KEY + ".id=" + id;
}
}
application.yml
server:
port: 8080
spring:
cloud:
sentinel:
transport:
port: 8719
dashboard: 192.168.75.137:8718
eager: true
java代码:
/***
* 基于平均响应时间
* @return
*/
@SentinelResource(value = "getERFallBack",fallback = "getException")
@GetMapping("getERFallBack")
public String getERFallBack(){
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "getERFallBack";
}
/***
* 基于平均响应时间 的降级方法
* @return
*/
public String getException(){
return "服务降级啦,当前服务器请求次数过多,请稍后重试!";
}
Sentinel控制台:
每秒平均响应时间超过阈值200ms,走服务降级,并且5秒内无法访问服务,继续走服务降级方法。
java代码:
/***
* 基于异常比例
* @param i
* @return
*/
@SentinelResource(value = "getErroPer",fallback = "getException")
@GetMapping("/getErroPer")
public String getErroPer(int i){
int j=1/i;
return "getErroPer";
}
/***
* 参数必须与被降级的方法中的参数一致,才能实现降级
* 异常比例和异常次数 的降级方法
* @param i
* @return
*/
public String getException(int i){
return "服务降级啦,请稍后重试!";
}
Sentinel控制台:
java代码:
/***
* 基于异常次数
* @param i
* @return
*/
@SentinelResource(value = "getErroCout",fallback = "getException")
@GetMapping("/getErroCout")
public String getErroCout(int i){
int j=1/i;
return "getErroCout";
}
/***
* 参数必须与被降级的方法中的参数一致,才能实现降级
* 异常比例和异常次数 的降级方法
* @param i
* @return
*/
public String getException(int i){
return "服务降级啦,请稍后重试!";
}
Sentinel控制台:
Spring Cloud Alibaba Sentinel 支持对 RestTemplate
的服务调用使用 Sentinel 进行保护,在构造 RestTemplate
bean的时候需要加上 @SentinelRestTemplate
注解。
@Bean
@SentinelRestTemplate(blockHandler = "handleException", blockHandlerClass = ExceptionUtil.class)
public RestTemplate restTemplate() {
return new RestTemplate();
}
@SentinelRestTemplate
注解的属性支持限流(blockHandler
, blockHandlerClass
)和降级(fallback
, fallbackClass
)的处理。
其中 blockHandler
或 fallback
属性对应的方法必须是对应 blockHandlerClass
或 fallbackClass
属性中的静态方法。
该方法的参数跟返回值跟 org.springframework.http.client.ClientHttpRequestInterceptor#interceptor
方法一致,其中参数多出了一个 BlockException
参数用于获取 Sentinel 捕获的异常。
比如上述 @SentinelRestTemplate
注解中 ExceptionUtil
的 handleException
属性对应的方法声明如下:
public class ExceptionUtil {
public static ClientHttpResponse handleException(HttpRequest request, byte[] body, ClientHttpRequestExecution execution, BlockException exception) {
...
}
}
NOTE: 应用启动的时候会检查 @SentinelRestTemplate
注解对应的限流或降级方法是否存在,如不存在会抛出异常
@SentinelRestTemplate
注解的限流(blockHandler
, blockHandlerClass
)和降级(fallback
, fallbackClass
)属性不强制填写。
当使用 RestTemplate
调用被 Sentinel 熔断后,会返回 RestTemplate request block by sentinel
信息,或者也可以编写对应的方法自行处理返回信息。这里提供了 SentinelClientHttpResponse
用于构造返回信息。
Sentinel RestTemplate 限流的资源规则提供两种粒度:
httpmethod:schema://host:port/path
:协议、主机、端口和路径
httpmethod:schema://host:port
:协议、主机和端口
NOTE: 以 https://www.taobao.com/test
这个 url 并使用 GET 方法为例。对应的资源名有两种粒度,分别是 GET:https://www.taobao.com
以及 GET:https://www.taobao.com/test
Sentinel中提供四种数据源实现数据持久化:File、Nacos、Zookeeper、Apolle
配置案例:
spring.cloud.sentinel.datasource.ds1.file.file=classpath: degraderule.json
spring.cloud.sentinel.datasource.ds1.file.rule-type=flow
#spring.cloud.sentinel.datasource.ds1.file.file=classpath: flowrule.json
#spring.cloud.sentinel.datasource.ds1.file.data-type=custom
#spring.cloud.sentinel.datasource.ds1.file.converter-class=org.springframework.cloud.alibaba.cloud.examples.JsonFlowRuleListConverter
#spring.cloud.sentinel.datasource.ds1.file.rule-type=flow
spring.cloud.sentinel.datasource.ds2.nacos.server-addr=localhost:8848
spring.cloud.sentinel.datasource.ds2.nacos.data-id=sentinel
spring.cloud.sentinel.datasource.ds2.nacos.group-id=DEFAULT_GROUP
spring.cloud.sentinel.datasource.ds2.nacos.data-type=json
spring.cloud.sentinel.datasource.ds2.nacos.rule-type=degrade
spring.cloud.sentinel.datasource.ds3.zk.path = /Sentinel-Demo/SYSTEM-CODE-DEMO-FLOW
spring.cloud.sentinel.datasource.ds3.zk.server-addr = localhost:2181
spring.cloud.sentinel.datasource.ds3.zk.rule-type=authority
spring.cloud.sentinel.datasource.ds4.apollo.namespace-name = application
spring.cloud.sentinel.datasource.ds4.apollo.flow-rules-key = sentinel
spring.cloud.sentinel.datasource.ds4.apollo.default-flow-rule-value = test
spring.cloud.sentinel.datasource.ds4.apollo.rule-type=param-flow
每种数据源都有两个共同的配置项: data-type
、 converter-class
以及 rule-type
。
data-type
:表示数据类型,Sentinel 默认提供两种内置的值,分别是 json
和 xml
(默认是json)。 若不想使用这两种,可以添加 custom
自定义数据类型,再配置 converter-class
配置项,该配置项需要写类的全路径名(比如 spring.cloud.sentinel.datasource.ds1.file.converter-class=org.springframework.cloud.alibaba.cloud.examples.JsonFlowRuleListConverter
)。
rule-type
:表示数据规则,(flow
,degrade
,authority
,system
, param-flow
, gw-flow
, gw-api-group
)。
添加maven依赖:
<dependency>
<groupId>com.alibaba.cspgroupId>
<artifactId>sentinel-datasource-nacosartifactId>
<version>1.5.2version>
dependency>
application.yml
server:
port: 8080
spring:
cloud:
sentinel:
transport:
port: 8719
dashboard: 192.168.75.137:8718
eager: true
datasource:
ds:
nacos:
### nacos连接地址
server-addr: 192.168.75.137:8848
## nacos连接的分组
group-id: DEFAULT_GROUP
###路由存储规则
rule-type: flow
### 读取配置文件的 data-id
data-id: Sentinel-03
### 读取培训文件类型为json
data-type: json
log:
dir: D:\Code\study\Java_Learning\01 Sentinel\03 基于Nacos实现数据持久化\logs\
application:
name: Sentinel-03
java代码:
@SentinelResource(value = "getNacosInfo",blockHandler = "getQpsException")
@GetMapping("/getNacosInfo")
public String getNacosInfo(){
return "getNacosInfo";
}
public String getQpsException(){
return "该接口已被限流,请稍后再试";
}
Nacos控制台:
resource:资源名,即限流规则的作用对象
limitApp:流控针对的调用来源,若为 default 则不区分调用来源
grade:限流阈值类型(QPS 或并发线程数);0代表根据并发数量来限流,1代表根据QPS来进行流量控制
count:限流阈值
strategy:调用关系限流策略
https://github.com/alibaba/Sentinel/wiki/%E7%BD%91%E5%85%B3%E9%99%90%E6%B5%81 [参考 Sentinel 网关限流]
注:github源码