于是类似滚雪球,故障的范围越来越大。
处理思路:
用Sentinel来控制QPS。
| Sentinel | Hystrix | |
|---|---|---|
| 隔离策略 | 信号量隔离 | 线程池隔离/信号量隔离 |
| 熔断降级策略 | 基于慢调用比例或异常比例 | 基于失败比率 |
| 实时指标实现 | 滑动窗口 | 滑动窗口(基于RxJava) |
| 规则配置 | 支持多种数据源 | 支持多种数据源 |
| 扩展性 | 多个扩展点 | 插件的形式 |
| 基于注解的支持 | 支持 | 支持 |
| 限流 | 基于QPS,支持基于调用关系的限流 | 有限的支持 |
| 流量整形 | 支持慢启动、匀速排队模式 | 不支持 |
| 系统自适应保护 | 支持 | 不支持 |
| 控制台 | 开箱即用,可配置规则、查看秒级监控、机器发现等 | 支持查看,不支持动态配置,不完善 |
| 常见框架的适配 | Servlet、Spring Cloud、Dubbo、gRPC等 | Servlet、Spring Cloud Netflix |
alibaba/Sentinel
下载如下文件即可。

下载完成解压后,就可以直接用运行即可
账号密码都是sentinel
java -jar sentinel-dashboard-1.8.1.jar
在SpringBoot的消费中,导入如下依赖
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-sentinelartifactId>
dependency>
设置yml文件
spring:
sentinel:
transport:
dashboard: localhost:8080
如果启动不了或者显示循环依赖,请参照下面版本进行调整
版本说明
如果还是不行,添加如下语句,允许循环依赖
spring:
main:
allow-circular-references: true
配置完成后,我们使用消费者多次访问提供者后,再查看Sentinel,可以看到如下结果。


流量控制、熔断等都是针对簇点链路中的资源拉设置的。
用设置流控,单机阈值为5,然后用Jemeter测试


结果:


一般用于竞争关系,一个优先级较高,一优先级低。但优先级高的触发阈值时,优先级低的被限制。
我们为消费者控制层新增两个方法
@GetMapping("/query")
public String queryOrder(){
return "查询";
}
@GetMapping("/update")
public String updateOrder(){
return "更新";
}
}
访问一下,后

我们想让update达到一定次数后,query被限流。
我们想要给谁限流,就给谁加规则。

现在进行测试



在Service层添加一个查询的方法
/**
* 手动标记该方法为资源
* */
@SentinelResource("goods")
@Override
public void queryGoods() {
System.out.println("查询商品");
}
为控制层添加
@GetMapping("/query")
public String queryOrder(){
orderService.queryGoods();
return "查询";
}
@GetMapping("/save")
public String saveOrder(){
orderService.queryGoods();
return "新增完成";
}
修改
sentinel:
transport:
dashboard: localhost:8080
web-context-unify: false #关闭context整合,Sentinel默认会把Controller的方法做context。导致链路模式的流量失效
接下来访问一下,



分别请求

可以看到,query被限制,但是update没有被限制

query

update

分别统计参数值相同的请求,判断是否超过阈值。

参数例外项,也即是会有一些参数需要单独配置,可能它们需要高于或者低于其他参数。
如果没有高级选项,从左侧的热点规则一栏进去新增。

首先在配置中打开feign的sentinel功能。
feign:
client:
config:
user-service:
logger-level: BASIC
sentinel:
enabled: true
随后编辑要降级的逻辑,有两种方式
package com.config;
import com.model.User;
import com.service.UserClient;
import org.springframework.cloud.openfeign.FallbackFactory;
public class UserClientFallbackFactory implements FallbackFactory<UserClient> {
@Override
public UserClient create(Throwable cause) {
return new UserClient() {
@Override
public User getUserById(Long id) {
System.err.println("查询失败:" + cause);
return new User();
}
};
}
}
package com.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class Config {
@Bean
public UserClientFallbackFactory userClientFallbackFactory(){
return new UserClientFallbackFactory();
}
}
重启后,运行,访问,就可以看到
接下来就可以进行配置了

如下图设置即可,就完成了舱壁模式。

有三种:
慢调用:业务的响应时长(RT)大于指定时长的请求认定为慢调用请求。在指定时间内,如果请求数目超过设定的最小数量,慢调用比例大于设定阈值,则出发熔断。

异常比例:统计指定时间内的调用,如果调用次数超过指定请求数目,并且出现异常比例达到比例阈值,则触发熔断。

异常数:统计指定时间内的调用,如果调用次数超过指定请求数目,并且出现异常数目超过指定异常数目,则触发熔断。

为什么需要?避免服务地址暴露,被访问者绕过网关直接访问。
白名单:来源(origin)在白名单的调用者允许访问
黑名单:来源(origin)在黑名单的调用者不允许访问

Setinel 通过 RequestOriginParser 这个接口的parseOrigin来获取请求来源,但无论是走网关还是直接访问默认都是default,无法区分。
因此我们需要自己去实现它。
public interface RequestOriginParser{
/**
* 从请求request对象中获取origin,获取方式自定义
*/
String parseOrigin(HttpServletRequest request);
}
为消费者实现该方法。
package com.config;
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser;
import com.alibaba.nacos.common.utils.StringUtils;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
@Component
public class HeaderOriginParser implements RequestOriginParser {
@Override
public String parseOrigin(HttpServletRequest httpServletRequest) {
/**
* 如果浏览器或者网关获取的origin头不一样,那么就可以区分
* */
String origin = httpServletRequest.getHeader("origin");
if(StringUtils.isEmpty(origin)){
return "blank";
}
return origin;
}
}
为网关添加过滤器,将我们上面的来源加上
server:
port: 10010
spring:
application:
name: gateway
cloud:
nacos:
server-addr: localhost:8848
gateway:
routes: # 网关路由配置
- id: user-service #路由id,自定义,但需要唯一
uri: lb://user-service #路由目标地址,lb即负载均衡,后面跟着服务名称
# uri: http://127.0.0.1:8001 # 也可以这样写,但是地址固定,不推荐
predicates: # 路由断言,即判断是否符合要求
- Path=/user/** # 这个规则是,只要以/user/开头,就算符合要求
- id: order-service
uri: lb://order-service
predicates:
- Path=/order/**
default-filters:
- AddRequestHeader=origin,gateway # , 是等于的意思

重启后


所有异常默认的都是限流异常,我们接下来自定义异常,避免抛异常时不知道是哪里出错
public interface BlockExceptionhanlder{
/**
* 处理请求被限流、降级、授权拦截时抛出的异常
*/
void handle(HttpServletRequest request,HttpServletResponse response,BlockException e) throws Exception;
}
BlockException包含很多子类
| 异常 | 说明 |
|---|---|
| FlowException | 限流异常 |
| ParamFlowExcetion | 热点参数限流异常 |
| DegradeException | 降级异常 |
| AuthorityException | 授权规则异常 |
| SystemBlockException | 系统规则异常 |
package com.config;
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 org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
public class SentinelBolckhandler implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws Exception {
String msg = "未知异常";
int status = 429;
if(e instanceof FlowException){
msg = "请求被限流了!";
}
else if(e instanceof DegradeException){
msg = "请求被降级了!";
}
else if(e instanceof ParamFlowException){
msg = "热点参数被限流了!";
}
else if(e instanceof AuthorityException){
msg = "请求没有权限!";
status = 401;
}
httpServletResponse.setContentType("application/json;charset=utf-8");
httpServletResponse.setStatus(status);
httpServletResponse.getWriter().println("{\"message\":\""+msg+"\",\"status\":"+status+"}");
}
}

有三种模式:
我们为消费者引入如下依赖
<dependency>
<groupId>com.alibaba.cspgroupId>
<artifactId>sentinel-datasource-nacosartifactId>
dependency>
spring:
application:
name: order-service
profiles: # 环境
active: dev
cloud:
nacos:
config:
enabled: true
server-addr: localhost:8848 #nacos地址
file-extension: yaml #文件后缀名
sentinel:
transport:
dashboard: localhost:8080
web-context-unify: false #关闭context整合,Sentinel默认会把Controller的方法做context。导致链路模式的流量失效
datasource:
flow:
nacos:
server-addr: localhost:8848
data-id: orderservice-flow-rules
group-id: SENTINEL_GROUP
rule-type: flow #此处我们选择限流,也可以选:degrade、authority、param-flow
随后重启服务
下载源码
alibaba/Sentinel
接下来我们修改源码:
在源码中找到dashboard

在其中如下图进行注释

将该类的test中的nacos,复制到main中对应的位置


在main中,修改nacosConfig类
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.dashboard.rule.nacos;
import java.util.List;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity;
import com.alibaba.csp.sentinel.datasource.Converter;
import com.alibaba.fastjson.JSON;
import com.alibaba.nacos.api.config.ConfigFactory;
import com.alibaba.nacos.api.config.ConfigService;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author Eric Zhao
* @since 1.4.0
*/
@Configuration
@ConfigurationProperties(prefix = "nacos")
public class NacosConfig {
private String addr;
@Bean
public Converter<List<FlowRuleEntity>, String> flowRuleEntityEncoder() {
return JSON::toJSONString;
}
@Bean
public Converter<String, List<FlowRuleEntity>> flowRuleEntityDecoder() {
return s -> JSON.parseArray(s, FlowRuleEntity.class);
}
@Bean
public ConfigService nacosConfigService() throws Exception {
return ConfigFactory.createConfigService(addr);
}
public String getAddr(){
return addr;
}
public void setAddr(String addr){
this.addr = addr;
}
}
随后在Properties文件中,添加Nacos地址
nacos.addr=localhost:8848
接着修改数据源
修改如下
@Autowired
@Qualifier("flowRuleNacosProvider")
private DynamicRuleProvider<List<FlowRuleEntity>> ruleProvider;
@Autowired
@Qualifier("flowRuleNacosPublisher")
private DynamicRulePublisher<List<FlowRuleEntity>> rulePublisher;
接着修改前端页面

取消该部分注释

<li ui-sref-active="active" ng-if="entry.appType==0">
<a ui-sref="dashboard.flow({app: entry.app})">
<i class="glyphicon glyphicon-filter">i> 流控规则-NACOSa>
li>
随后打包

启动后如下:

点击流控规则Nacos,并添加一个规则

可以看到多出来了一个规则

同理,如果要改其他的,也是需要把页面进行修改。
[1]黑马程序员Java微服务