spring-boot:2.3.2
spring-cloud:Hoxton.SR8
spring-cloud-alibaba:2.2.3
dubbo:2.7.8
nacos:1.3.2
sentinel:1.8.0
com.alibaba.cloud spring-cloud-starter-alibaba-sentinel
spring-cloud-starter-alibaba-sentinel已经集成了sentinel-spring-webmvc-adapter和sentinel-spring-webflux-adapter适配器,其他适配器见Sentinel官方提供的适配器列表:https://github.com/alibaba/Sentinel/tree/master/sentinel-adapter
增加配置spring:
cloud:
sentinel:
transport:
dashboard: 127.0.0.1:8003 #控制台地址
client-ip: 127.0.0.1 #如果服务器有多个IP,或者使用了代理,则需要指定客户端IP地址
port: 8719 # 客户端端口,sentinel默认端口从8719开始,如果端口被占用则自动+1
Sentinel控制台
Sentinel控制台的限流规则默认是存储在内存中的,且5分钟失效。所以不适用于生产环境。如果需要再生产环境使用,需要改造。
下载地址:https://github.com/alibaba/Sentinel/releases/download/v1.8.0/sentinel-dashboard-1.8.0.jar
Linux环境下,执行以下命令后台启动Sentinel控制台:
nohup java -Dserver.port=8002 -Dcsp.sentinel.dashboard.server=localhost:8002 -Dproject.name=sentinel-dashboard -Dsentinel.dashboard.auth.username=admin -Dsentinel.dashboard.auth.password=admin -jar sentinel-dashboard-1.7.2.jar >>sentinel.log 2>&1 &Dubbo 使用Sentinel
引入Sentinel Dubbo适配器即可,再pom文件中增加以下依赖配置:
2、Application从nacos拉取sentinel规则com.alibaba.csp sentinel-apache-dubbo-adapter
SpringBoot应用可以将nacos作为数据源,拉取sentinel限流规则,nacos中的限流规则更该以后,会实时push到Springboot应用,这样就可以动态的修改的Sentinel规则。
引入依赖修改配置文件com.alibaba.csp sentinel-datasource-nacos
spring:
application:
name: dubbo-server
cloud:
sentinel:
transport:
dashboard: 127.0.0.1:8003
client-ip: 127.0.0.1
#port: 8729
enabled: true
datasource:
flow:
nacos:
server-addr: 127.0.0.1:8001
namespace: SENTINEL_NAMESPACE
dataId: ${spring.application.name}-flow-rules
groupId: SENTINEL_GROUP
data-type: json
rule-type: flow
username: username
password: password
degrade:
nacos:
server-addr: 127.0.0.1:8001
namespace: SENTINEL_NAMESPACE
dataId: ${spring.application.name}-degrade-rules
groupId: SENTINEL_GROUP
data-type: json
rule-type: degrade
username: username
password: password
3、Sentinel控制台改造:使用nacos作为数据源
Sentinel控制台使用nacos作为数据源,修改规则后将规则推送到nacos,nacos在推送到各个应用,使规则管理更加清晰和轻松。
从github下载源码:
git clone https://github.com/alibaba/Sentinel
修改子项目sentinel-dashboad的pom.xml文件,找到“sentinel-datasource-nacos”的依赖,去掉“test”
com.alibaba.csp sentinel-datasource-nacos
复制 src/test/java下的 com.alibaba.csp.sentinel.dashboard.rule.nacos包到src/main/java
修改com.alibaba.csp.sentinel.dashboard.controller.FlowControllerV1:
package com.alibaba.csp.sentinel.dashboard.controller;
import com.alibaba.csp.sentinel.dashboard.auth.AuthAction;
import com.alibaba.csp.sentinel.dashboard.auth.AuthService.PrivilegeType;
import com.alibaba.csp.sentinel.dashboard.client.SentinelApiClient;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity;
import com.alibaba.csp.sentinel.dashboard.domain.Result;
import com.alibaba.csp.sentinel.dashboard.repository.rule.InMemoryRuleRepositoryAdapter;
import com.alibaba.csp.sentinel.dashboard.rule.DynamicRuleProvider;
import com.alibaba.csp.sentinel.dashboard.rule.DynamicRulePublisher;
import com.alibaba.csp.sentinel.util.StringUtil;
import java.util.Date;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping(value = "/v1/flow")
public class FlowControllerV1 {
private final Logger logger = LoggerFactory.getLogger(FlowControllerV1.class);
@Autowired
private InMemoryRuleRepositoryAdapter repository;
@Autowired
private SentinelApiClient sentinelApiClient;
@Autowired
@Qualifier("flowRuleNacosProvider") //指定使用Nacos Provider
private DynamicRuleProvider> ruleProvider;
@Autowired
@Qualifier("flowRuleNacosPublisher") //指定使用Nacos Publisher
private DynamicRulePublisher> rulePublisher;
@GetMapping("/rules")
@AuthAction(PrivilegeType.READ_RULE)
public Result> apiQueryMachineRules(@RequestParam String app) {
if (StringUtil.isEmpty(app)) {
return Result.ofFail(-1, "app can't be null or empty");
}
try {
List rules = ruleProvider.getRules(app);
if (rules != null && !rules.isEmpty()) {
for (FlowRuleEntity entity : rules) {
entity.setApp(app);
if (entity.getClusterConfig() != null && entity.getClusterConfig().getFlowId() != null) {
entity.setId(entity.getClusterConfig().getFlowId());
}
}
}
return Result.ofSuccess(repository.findAllByApp(app));
} catch (Throwable throwable) {
logger.error("Error when querying flow rules", throwable);
return Result.ofThrowable(-1, throwable);
}
}
private Result checkEntityInternal(FlowRuleEntity entity) {
if (StringUtil.isBlank(entity.getApp())) {
return Result.ofFail(-1, "app can't be null or empty");
}
if (StringUtil.isBlank(entity.getIp())) {
return Result.ofFail(-1, "ip can't be null or empty");
}
if (entity.getPort() == null) {
return Result.ofFail(-1, "port can't be null");
}
if (StringUtil.isBlank(entity.getLimitApp())) {
return Result.ofFail(-1, "limitApp can't be null or empty");
}
if (StringUtil.isBlank(entity.getResource())) {
return Result.ofFail(-1, "resource can't be null or empty");
}
if (entity.getGrade() == null) {
return Result.ofFail(-1, "grade can't be null");
}
if (entity.getGrade() != 0 && entity.getGrade() != 1) {
return Result.ofFail(-1, "grade must be 0 or 1, but " + entity.getGrade() + " got");
}
if (entity.getCount() == null || entity.getCount() < 0) {
return Result.ofFail(-1, "count should be at lease zero");
}
if (entity.getStrategy() == null) {
return Result.ofFail(-1, "strategy can't be null");
}
if (entity.getStrategy() != 0 && StringUtil.isBlank(entity.getRefResource())) {
return Result.ofFail(-1, "refResource can't be null or empty when strategy!=0");
}
if (entity.getControlBehavior() == null) {
return Result.ofFail(-1, "controlBehavior can't be null");
}
int controlBehavior = entity.getControlBehavior();
if (controlBehavior == 1 && entity.getWarmUpPeriodSec() == null) {
return Result.ofFail(-1, "warmUpPeriodSec can't be null when controlBehavior==1");
}
if (controlBehavior == 2 && entity.getMaxQueueingTimeMs() == null) {
return Result.ofFail(-1, "maxQueueingTimeMs can't be null when controlBehavior==2");
}
if (entity.isClusterMode() && entity.getClusterConfig() == null) {
return Result.ofFail(-1, "cluster config should be valid");
}
return null;
}
@PostMapping("/rule")
@AuthAction(PrivilegeType.WRITE_RULE)
public Result apiAddFlowRule(@RequestBody FlowRuleEntity entity) {
Result checkResult = checkEntityInternal(entity);
if (checkResult != null) {
return checkResult;
}
entity.setId(null);
Date date = new Date();
entity.setGmtCreate(date);
entity.setGmtModified(date);
entity.setLimitApp(entity.getLimitApp().trim());
entity.setResource(entity.getResource().trim());
try {
entity = repository.save(entity);
publishRules(entity.getApp());
} catch (Throwable throwable) {
logger.error("Failed to add flow rule", throwable);
return Result.ofThrowable(-1, throwable);
}
return Result.ofSuccess(entity);
}
@PutMapping("/save.json")
@AuthAction(PrivilegeType.WRITE_RULE)
public Result apiUpdateFlowRule(Long id, String app,
String limitApp, String resource, Integer grade,
Double count, Integer strategy, String refResource,
Integer controlBehavior, Integer warmUpPeriodSec,
Integer maxQueueingTimeMs) {
if (id == null) {
return Result.ofFail(-1, "id can't be null");
}
FlowRuleEntity entity = repository.findById(id);
if (entity == null) {
return Result.ofFail(-1, "id " + id + " dose not exist");
}
if (StringUtil.isNotBlank(app)) {
entity.setApp(app.trim());
}
if (StringUtil.isNotBlank(limitApp)) {
entity.setLimitApp(limitApp.trim());
}
if (StringUtil.isNotBlank(resource)) {
entity.setResource(resource.trim());
}
if (grade != null) {
if (grade != 0 && grade != 1) {
return Result.ofFail(-1, "grade must be 0 or 1, but " + grade + " got");
}
entity.setGrade(grade);
}
if (count != null) {
entity.setCount(count);
}
if (strategy != null) {
if (strategy != 0 && strategy != 1 && strategy != 2) {
return Result.ofFail(-1, "strategy must be in [0, 1, 2], but " + strategy + " got");
}
entity.setStrategy(strategy);
if (strategy != 0) {
if (StringUtil.isBlank(refResource)) {
return Result.ofFail(-1, "refResource can't be null or empty when strategy!=0");
}
entity.setRefResource(refResource.trim());
}
}
if (controlBehavior != null) {
if (controlBehavior != 0 && controlBehavior != 1 && controlBehavior != 2) {
return Result.ofFail(-1, "controlBehavior must be in [0, 1, 2], but " + controlBehavior + " got");
}
if (controlBehavior == 1 && warmUpPeriodSec == null) {
return Result.ofFail(-1, "warmUpPeriodSec can't be null when controlBehavior==1");
}
if (controlBehavior == 2 && maxQueueingTimeMs == null) {
return Result.ofFail(-1, "maxQueueingTimeMs can't be null when controlBehavior==2");
}
entity.setControlBehavior(controlBehavior);
if (warmUpPeriodSec != null) {
entity.setWarmUpPeriodSec(warmUpPeriodSec);
}
if (maxQueueingTimeMs != null) {
entity.setMaxQueueingTimeMs(maxQueueingTimeMs);
}
}
Date date = new Date();
entity.setGmtModified(date);
try {
entity = repository.save(entity);
if (entity == null) {
return Result.ofFail(-1, "save entity fail");
}
publishRules(entity.getApp());
} catch (Throwable throwable) {
logger.error("Failed to update flow rule", throwable);
return Result.ofThrowable(-1, throwable);
}
return Result.ofSuccess(entity);
}
@DeleteMapping("/delete.json")
@AuthAction(PrivilegeType.WRITE_RULE)
public Result apiDeleteFlowRule(Long id) {
if (id == null || id <= 0) {
return Result.ofFail(-1, "Invalid id");
}
FlowRuleEntity oldEntity = repository.findById(id);
if (oldEntity == null) {
return Result.ofSuccess(null);
}
try {
repository.delete(id);
publishRules(oldEntity.getApp());
} catch (Exception e) {
logger.error("Error when deleting flow rules, app={}, ip={}, id={}", oldEntity.getApp(),
oldEntity.getIp(), id, e);
return Result.ofFail(-1, e.getMessage());
}
return Result.ofSuccess(id);
}
private void publishRules( String app) throws Exception {
List rules = repository.findAllByApp(app);
rulePublisher.publish(app, rules);
}
}
启动sentinel-dashbord,增加或修改限流规则后,控制台会将规则推送到nacos配置中心,Springboot应用监听到配置中心的变化后,会自动拉取更新限流规则。



