sentinel 使用 apollo 作为数据源示例

This commit is contained in:
YunaiV
2020-01-31 11:26:37 +08:00
parent 3f55794dd2
commit f96c656852
10 changed files with 295 additions and 1 deletions

View File

@@ -0,0 +1,61 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>lab-46-sentinel-demo-apollo</artifactId>
<dependencies>
<!-- 实现对 SpringMVC 的自动化配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Sentinel 核心库 -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
<version>1.7.1</version>
</dependency>
<!-- Sentinel 接入控制台 -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-transport-simple-http</artifactId>
<version>1.7.1</version>
</dependency>
<!-- Sentinel 对 SpringMVC 的支持 -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-spring-webmvc-adapter</artifactId>
<version>1.7.1</version>
</dependency>
<!-- Sentinel 对【热点参数限流】的支持 -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-parameter-flow-control</artifactId>
<version>1.7.1</version>
</dependency>
<!-- Sentinel 对 Spring AOP 的拓展 -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-annotation-aspectj</artifactId>
<version>1.7.1</version>
</dependency>
<!-- Sentinel 对 Apollo 作为数据源的支持 -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-apollo</artifactId>
<version>1.7.1</version>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,17 @@
package cn.iocoder.springboot.lab46.sentineldemo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
// 设置系统属性 project.name提供给 Sentinel 读取
System.setProperty("project.name", "demo-application");
// 启动 Spring Boot 应用
SpringApplication.run(Application.class, args);
}
}

View File

@@ -0,0 +1,56 @@
package cn.iocoder.springboot.lab46.sentineldemo.config;
import com.alibaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect;
import com.alibaba.csp.sentinel.datasource.Converter;
import com.alibaba.csp.sentinel.datasource.apollo.ApolloDataSource;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Arrays;
import java.util.List;
@Configuration
public class SentinelConfiguration {
@Value("${spring.application.name}")
private String applicationName;
@Bean
public SentinelResourceAspect sentinelResourceAspect() {
return new SentinelResourceAspect();
}
@Bean
public ApolloDataSource apolloDataSource(ObjectMapper objectMapper) {
// Apollo 配置。这里先写死,推荐后面写到 application.yaml 配置文件中。
String appId = applicationName; // Apollo 项目编号。一般情况下,推荐和 spring.application.name 保持一致
String serverAddress = "http://localhost:8080"; // Apollo Meta 服务器地址
String namespace = "application"; // Apollo 命名空间
String flowRuleKey = "sentinel.flow-rule"; // Apollo 配置项的 KEY
// 创建 ApolloDataSource 对象
System.setProperty("app.id", appId);
System.setProperty("apollo.meta", serverAddress);
ApolloDataSource<List<FlowRule>> apolloDataSource = new ApolloDataSource<>(namespace, flowRuleKey, "",
new Converter<String, List<FlowRule>>() { // 转换器,将读取的 Apollo 配置,转换成 FlowRule 数组
@Override
public List<FlowRule> convert(String value) {
try {
return Arrays.asList(objectMapper.readValue(value, FlowRule[].class));
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
});
// 注册到 FlowRuleManager 中
FlowRuleManager.register2Property(apolloDataSource.getProperty());
return apolloDataSource;
}
}

View File

@@ -0,0 +1,53 @@
package cn.iocoder.springboot.lab46.sentineldemo.config;
import com.alibaba.csp.sentinel.adapter.spring.webmvc.SentinelWebInterceptor;
import com.alibaba.csp.sentinel.adapter.spring.webmvc.SentinelWebTotalInterceptor;
import com.alibaba.csp.sentinel.adapter.spring.webmvc.config.SentinelWebMvcConfig;
import com.alibaba.csp.sentinel.adapter.spring.webmvc.config.SentinelWebMvcTotalConfig;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class SpringMvcConfiguration implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// Add Sentinel interceptor
// addSentinelWebTotalInterceptor(registry);
addSentinelWebInterceptor(registry);
}
private void addSentinelWebInterceptor(InterceptorRegistry registry) {
// 创建 SentinelWebMvcConfig 对象
SentinelWebMvcConfig config = new SentinelWebMvcConfig();
config.setHttpMethodSpecify(true); // 是否包含请求方法。即基于 URL 创建的资源,是否包含 Method。
// config.setBlockExceptionHandler(new DefaultBlockExceptionHandler()); // 设置 BlockException 处理器。
// config.setOriginParser(new RequestOriginParser() { // 设置请求来源解析器。用于黑白名单控制功能。
//
// @Override
// public String parseOrigin(HttpServletRequest request) {
// // 从 Header 中,获得请求来源
// String origin = request.getHeader("s-user");
// // 如果为空,给一个默认的
// if (StringUtils.isEmpty(origin)) {
// origin = "default";
// }
// return origin;
// }
//
// });
// 添加 SentinelWebInterceptor 拦截器
registry.addInterceptor(new SentinelWebInterceptor(config)).addPathPatterns("/**");
}
private void addSentinelWebTotalInterceptor(InterceptorRegistry registry) {
// 创建 SentinelWebMvcTotalConfig 对象
SentinelWebMvcTotalConfig config = new SentinelWebMvcTotalConfig();
// 添加 SentinelWebTotalInterceptor 拦截器
registry.addInterceptor(new SentinelWebTotalInterceptor(config)).addPathPatterns("/**");
}
}

View File

@@ -0,0 +1,82 @@
package cn.iocoder.springboot.lab46.sentineldemo.controller;
import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/demo")
public class DemoController {
@GetMapping("/echo")
public String echo() {
return "echo";
}
@GetMapping("/test")
public String test() {
return "test";
}
@GetMapping("/sleep")
public String sleep() throws InterruptedException {
Thread.sleep(100L);
return "sleep";
}
// 测试热点参数限流
@GetMapping("/product_info")
@SentinelResource("demo_product_info_hot")
public String productInfo(Integer id) {
return "商品编号:" + id;
}
// 手动使用 Sentinel 客户端 API
@GetMapping("/entry_demo")
public String entryDemo() {
Entry entry = null;
try {
// 访问资源
entry = SphU.entry("entry_demo");
// ... 执行业务逻辑
return "执行成功";
} catch (BlockException ex) {
return "被拒绝";
} finally {
// 释放资源
if (entry != null) {
entry.exit();
}
}
}
// 测试 @SentinelResource 注解
@GetMapping("/annotations_demo")
@SentinelResource(value = "annotations_demo_resource",
blockHandler = "blockHandler",
fallback = "fallback")
public String annotationsDemo(@RequestParam(required = false) Integer id) throws InterruptedException {
if (id == null) {
throw new IllegalArgumentException("id 参数不允许为空");
}
return "success...";
}
// BlockHandler 处理函数,参数最后多一个 BlockException其余与原函数一致.
public String blockHandler(Integer id, BlockException ex) {
return "block" + ex.getClass().getSimpleName();
}
// Fallback 处理函数,函数签名与原函数一致或加一个 Throwable 类型的参数.
public String fallback(Integer id, Throwable throwable) {
return "fallback" + throwable.getMessage();
}
}

View File

@@ -0,0 +1,17 @@
package cn.iocoder.springboot.lab46.sentineldemo.web;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
@ControllerAdvice(basePackages = "cn.iocoder.springboot.lab46.sentineldemo.controller")
public class GlobalExceptionHandler {
@ResponseBody
@ExceptionHandler(value = BlockException.class)
public String blockExceptionHandler(BlockException blockException) {
return "请求过于频繁";
}
}

View File

@@ -0,0 +1,6 @@
spring:
application:
name: demo-application
server:
port: 18080 # 避免和 Apollo 使用到的 8080 端口冲突

View File

@@ -0,0 +1 @@
csp.sentinel.dashboard.server=127.0.0.1:7070

View File

@@ -8,7 +8,7 @@ public class Application {
public static void main(String[] args) {
// 设置系统属性 project.name提供给 Sentinel 读取
System.setProperty("project.name", "demo-application-nacos");
System.setProperty("project.name", "demo-application");
// 启动 Spring Boot 应用
SpringApplication.run(Application.class, args);

View File

@@ -14,6 +14,7 @@
<modules>
<module>lab-46-sentinel-demo</module>
<module>lab-46-sentinel-demo-nacos</module>
<module>lab-46-sentinel-demo-apollo</module>
</modules>