引入依赖
<!--Sentinel-->
<!--和sentinel starter 配合使用 ,集成sentinel相关自动化配置依赖项-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
<!--去除jackson-dataformat-xml,否则会返回xml文件,而不是JSON-->
<exclusions>
<exclusion>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--配置sentinel规则持久化至nacos依赖-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
<!--和sentinel starter 配合使用,集成sentinel网关控制相关自动化配置依赖项(仅网关模块才需要依赖)-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
</dependency>
关于依赖,普通服务引入 spring-cloud-starter-alibaba-sentinel
, sentinel-datasource-nacos
, 网关服务引入 spring-cloud-starter-alibaba-sentinel
, sentinel-datasource-nacos
, spring-cloud-alibaba-sentinel-gateway
,普通服务流控使用注解@SentinelResource,网关针对请求链路做流控
普通资源流控
自定义资源@SentinelResource
,我们只需要在相关方法上加上@SentinelResource
注解,让其可以成为sentinel
识别的资源
-
定义资源
以下远程服务接口实现为简化实现方式,可直接实现远程服务接口,以实现类的方式做具体业务实现类,不必单独写控制层接口实现类- 远程服务接口部分
@FeignClient(name = "user") public interface IUserRemote { @GetMapping("/user") R<UserVO> user(@RequestParam("userId") Integer userId); }
- 远程服务接口业务实现部分
@RestController @Slf4j public class UserRemote implements IUserRemote { @Resource private IUserService userService; @SentinelResource(value = "user") @Override public R<UserVO> user(@RequestParam("userId") Integer userId) { UserDetail user = userService.getUser(userId); UserVO userVO = new UserVO(); BeanUtils.copyProperties(user,userVO,UserVO.class); return R.data(userVO); } }
-
sentinel-dashboard 控制台流控配置
自定义资源流控配置@SentinelResource 如图
将user的QPS单机阈值设置成5,如果每秒QPS超过5,直接丢弃。这里的资源名就是我们使用@SentinelResource注解自定义的资源
触发流控会报出FlowException
异常,这说明我们的目的达到了,限流成功 -
我们大部分接口都是json形式的,此处对于流控部分做异常处理
@RestController
@Slf4j
public class UserRemote implements IUserRemote {
@Resource
private IUserService userService;
@SentinelResource(value = "user", blockHandler = "handlerException")
@Override
public R<UserVO> user(@RequestParam("userId") Integer userId) {
UserDetail user = userService.getUser(userId);
UserVO userVO = new UserVO();
BeanUtils.copyProperties(user,userVO,UserVO.class);
return R.data(userVO);
}
public R<UserVO> handlerException(Integer userId, BlockException exception){
log.info("flow exception {}",exception.getClass().getCanonicalName());
return R.fail(500,"请求过于频繁,稍后再试试!");
}
}
注意,自定义的异常方法的参数和返回值要跟目标方法一样,参数追加BlockException
- 使用持久化配置
Sentinel
配置默认是放在内存中的,每当应用重启或者sentinel重启都会丢失数据,这里使用Nacos作为配置中心持久化限流配置(见上一篇-sentinel之dashboard改造)
- 修改bootstrap.yml,配置sentinel的数据源
spring:
application:
name: user
cloud:
nacos:
config:
server-addr: ${star.nacos.ip}:${star.nacos.port}
file-extension: yml
namespace: ${star.nacos.namespace}
group: ${star.nacos.group}
sentinel:
filter:
enabled: false
transport:
dashboard: ${star.sentinel.ip}:${star.sentinel.port} # sentinel服务端地址
enabled: true
eager: true # 取消延迟加载
datasource:
# 名称随意
flow: # 流控规则
nacos:
server-addr: ${star.nacos.ip}:${star.nacos.port}
namespace: ${star.nacos.namespace}
data-id: ${spring.application.name}-flow-rules
group-id: ${star.sentinel.group}
# 规则类型,取值见:
# org.springframework.cloud.alibaba.sentinel.datasource.RuleType
rule-type: flow
degrade: # 降级规则
nacos:
server-addr: ${star.nacos.ip}:${star.nacos.port}
namespace: ${star.nacos.namespace}
data-id: ${spring.application.name}-degrade-rules
group-id: ${star.sentinel.group}
rule-type: degrade
system: # 系统规则
nacos:
server-addr: ${star.nacos.ip}:${star.nacos.port}
namespace: ${star.nacos.namespace}
data-id: ${spring.application.name}-system-rules
group-id: ${star.sentinel.group}
rule-type: system
authority: # 授权规则
nacos:
server-addr: ${star.nacos.ip}:${star.nacos.port}
namespace: ${star.nacos.namespace}
data-id: ${spring.application.name}-authority-rules
group-id: ${star.sentinel.group}
rule-type: authority
param-flow: # 热点参数规则
nacos:
server-addr: ${star.nacos.ip}:${star.nacos.port}
namespace: ${star.nacos.namespace}
data-id: ${spring.application.name}-param-flow-rules
group-id: ${star.sentinel.group}
rule-type: param-flow
配置项说明:
datasource 下的名称是我自己起的一个名字,这一层名字可以随便起,要具有辨识性
查看源码SentinelProperties
的datasource属性
datasource的类型为:Map<String, DataSourcePropertiesConfiguration> datasource
,此处map的key为上面的flow等,value继续查看
查看DataSourcePropertiesConfiguration
,发现sentinel配置支持的限流配置文件数据源支持(file,nacos,zk,apollo,redis,consul
)
我们使用nacos作为配置源,继续查看NacosDataSourceProperties
,对应上图nacos下的配置项都为NacosDataSourceProperties
里的属性
-
sentinel-dashboard控制台增加流控规则,如图
普通服务流控规则
- 运行查看结果生效
我是用JMeter做了测试,会发现,并发情况下,流控效果并不会很精准,当然流控肯定是生效了,只是控制的结果不是预设的值,会存在一些偏差,具体原因呢,可以参考Sentinel并发限流不精确-之责任链 的博文说明,主要问题还是并发的原因
附上流控过程中责任链的执行顺序
责任链执行顺序
网关流控
sentinel-dashboard各类规则分为两种类型,根据属性appType
确定:
- 0: 普通服务
- 1: Api Gateway 网关服务
服务集成sentinel时默认是作为普通服务,此处网关需设置为网关服务,网关服务启动参数增加:
-Dcsp.sentinel.app.type=1
Sentinel对Spring Cloud Gateway限流的支持其实是一个全局的过滤器,使用过程中我们将全局过滤器注入到容器中,然后全局过滤器就会生效
参数配置项
spring:
# 必须放在bootstrap中,放在application中不生效
application:
# 应用名
name: gateway
cloud:
nacos:
discovery:
# 服务注册地址
server-addr: ${star.nacos.ip}:${star.nacos.port}
namespace: ${star.nacos.namespace}
group: ${star.nacos.server.group}
cluster-name: ${star.nacos.cluster}
metadata:
target-version: ${star.nacos.metadata.version}
config:
# 配置中心地址
server-addr: ${star.nacos.ip}:${star.nacos.port}
# namespace 的ID,区分部署环境
namespace: ${star.nacos.namespace}
# 分组 区分业务项目
group: ${star.nacos.group}
prefix: ${spring.application.name} #缺省值
#文件后缀名 很根据本定文件名称拼接
file-extension: yml
gateway:
discovery:
locator:
enabled: true #开启从注册中心动态创建路由的功能,利用微服务名称进行路由
lower-case-service-id: true # 将服务名称转小写
# routeId的前缀,不配置的话,默认以(discoveryClient.getClass().getSimpleName() + "_". Service Id)格式,结合sentinel使用时,sentinel控制台请求链路中api名称就是网关注册的routeId(不看也不影响功能就是)
route-id-prefix: sisyphus_
routes:
- id: user #路由唯一标识
uri: lb://user #从nacos进行转发,lb: 负载均衡
predicates: #断言 配置哪个路径才转发
- Path=/user/**
- id: order
uri: lb://order
predicates:
- Path=/order/**
sentinel:
filter:
enabled: false # 网关流控控制台不展示URL资源,即不对gateway进行限流,我们只需要对网关下的服务进行限流
transport:
dashboard: ${star.sentinel.ip}:${star.sentinel.port}
eager: true # 是否提前触发 Sentinel 初始化(取消延迟加载) 默认false
datasource:
gw-flow:
nacos:
server-addr: ${star.nacos.ip}:${star.nacos.port}
namespace: ${star.nacos.namespace}
data-id: ${spring.application.name}-gateway-flow-rules # 在修改的sentinel 源码中定义的规则名
group-id: ${star.sentinel.group}
rule-type: gw-flow
gw-api-group:
nacos:
server-addr: ${star.nacos.ip}:${star.nacos.port}
namespace: ${star.nacos.namespace}
dataId: ${spring.application.name}-gateway-flow-rules # 在修改的sentinel 源码中定义的规则名
group-id: ${star.sentinel.group}
rule-type: gw-api-group
main:
#出现重名直接复写
allow-bean-definition-overriding: true
# Endpoint 支持(暴露的 endpoint 路径为 /actuator/sentinel),Sentinel Endpoint暴露的信息包括当前应用的所有规则信息、日志目录、当前实例的 IP,Sentinel Dashboard 地址,Block Page,应用与 Sentinel Dashboard 的心跳频率等等信息
management:
endpoints:
web:
exposure:
include: "*"
#开启Feign对Sentinel的支持
feign:
sentinel:
enabled: true
运行测试
请求相关网关接口,此时请求链路会看到记录,我们在对应链路增加流控规则,如图:
请求链路 流控规则
多次请求接口查看情况
以14:21:02数据观察接口,一秒钟内仅接收两个请求,其他全部拒绝,另外两个时间节点没有可比性,O了,达到我们要的效果
其他事项说明
请求网关任意接口,sentinel控制台,请求链路会出现如下
img.png
会发现带了一长串前缀 ReactiveCompositeDiscoveryClient_
,原因呢,查询源码 DiscoveryLocatorProperties
如下:
意思呢是说,routeId的前缀默认为 discoveryClient.getClass().getSimpleName() + "_". Service Id
,如果你不设置呢,就会给拼接这一大串前缀
解决方法呢,上面配置文件也说明过,即route-id-prefix
,routeId的前缀,不配置的话,默认以(discoveryClient.getClass().getSimpleName() + "_". Service Id)格式,结合sentinel使用时,sentinel控制台请求链路中api名称就是网关注册的routeId(不看也不影响功能就是), 新增流控规则的时候Api名称是需要和请求链路部分匹配一致的,建议更改哈
谢谢大家关注,点个赞呗~
如需转载请标明出处,谢谢~~
网友评论