重庆市建设工程信息网站诚信分软件编程培训学校排名
sentinel集成网关支持restful接口进行url粒度的流量治理
- 前言
- 使用网关进行总体流量治理(sentinel版本:1.8.6)
- 1、cloud gateway添加依赖:
- 2、sentinel配置
- 3、网关类型项目配置
- 4、通过zk事件监听刷新上报api分组信息
- 1、非网关项目上报api分组信息
- 2、网关添加监听事件
- 3、网关监听事件处理
- 5、sentinel控制台启动
前言
sentinel作为开源的微服务、流量治理组件,在对restful接口的支持上,在1.7之后才开始友好起来,对于带有@PathVariable的restful接口未作支持,在sentinel中/api/{id}这样的接口,其中/api/1与/api/2会被当做两个不同的接口处理,因此很难去做类似接口的流量治理,但在之后,sentinel团队已经提供了响应的csp扩展依赖,下文将会逐步讲述如何通过sentinel扩展来支持相应的服务流量治理
使用网关进行总体流量治理(sentinel版本:1.8.6)
这里选型为spring cloud gateway,而sentinel也对spring cloud gateway做了特殊照顾
1、cloud gateway添加依赖:
<!-- alibaba封装的sentinel的starter --><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-sentinel</artifactId><version>2021.1</version></dependency><!-- alibaba封装的sentinel支持网关的starter --><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId><version>2021.1</version></dependency><!-- 此包即为sentinel提供的扩展支持restful接口的依赖 --><dependency><groupId>com.alibaba.csp</groupId><artifactId>sentinel-spring-webmvc-adapter</artifactId><version>1.8.0</version></dependency>
上述需要重点关注的是sentinel-spring-webmvc-adapter包,此依赖是支持restful接口的关键,不需要我们自己改造。
2、sentinel配置
spring:cloud:sentinel:transport:#sentinel控制台地址dashboard: 1.1.1.1:8600#sentinel通信端口,默认为8179,被占用会继续扫描,一般固定起来port: 8700#项目所在服务器ipclient-ip: 2.2.2.2#心跳启动eager: true
client-ip在某些情况下不配置会出现sentinl控制台页面只有首页,服务一直注册不上去的情况,如果出现这种情况一定要配置上,如果没有这种情况,client-IP可以不配置,同时上述配置的这些ip端口都需要连通。
3、网关类型项目配置
/*** @classDesc:* @author: cyjer* @date: 2023/1/30 9:53*/
@SpringBootApplication
@EnableCaching
@Slf4j
public class SiriusApplication {public static void main(String[] args) {System.getProperties().setProperty("csp.sentinel.app.type", "1");SpringApplication.run(SiriusApplication.class, args);log.info("<<<<<<<<<<启动成功>>>>>>>>>>");}}
如果是网关类型的项目,需要配置csp.sentinel.app.type= 1,普通项目与网关项目,在控制台上所展示和可使用的功能是不同的
4、通过zk事件监听刷新上报api分组信息
通过将接口分组按照不同粒度,如controller粒度,和具体api接口粒度,通过zookeeper修改数据监听的方式,通过网关监听该事件,实现将api分组信息写入到sentinel中。
1、非网关项目上报api分组信息
/*** @classDesc: 扫描项目接口上报api* @author: cyjer* @date: 2023/2/10 13:46*/
@Configuration
@Slf4j
@Order(1)
@RequiredArgsConstructor
public class ApiDefinitionReporter implements BeanPostProcessor, CommandLineRunner, Constraint {private final List<ApiSiriusDefinition> apiSiriusDefinitionList = new ArrayList<>();private final GatewayServiceProperties gatewayServiceProperties;private final Environment environment;private final static char JTR = '/';private final static String PASS = "/**";private final static String APPLICATION_NAME = "spring.application.name";private final static String CONTEXT_PATH = "server.servlet.context-path";private final static List<String> PASS_LIST = Arrays.asList("swaggerWelcome", "basicErrorController", "swaggerConfigResource", "openApiResource");private final ZookeeperService zookeeperService;@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {// url访问路径为:访问基地址basePath+classMappingPath+methodPathif (!gatewayServiceProperties.isAutoReportAndRegister() || PASS_LIST.contains(beanName)) {return bean;}Class<?> beanClass = bean.getClass();Class<?> targetClass = AopUtils.getTargetClass(bean);//判断类上有无controller注解 spring代理类需用spring的注解扫描工具类查找RestController restController = AnnotationUtils.findAnnotation(beanClass, RestController.class);Controller controller = AnnotationUtils.findAnnotation(beanClass, Controller.class);//没有注解直接跳过扫描if (null == controller && null == restController) {return bean;}String applicationName = this.getApplicationName();//项目访问基地址String basePath = this.getBasePath();//如果类上有controller注解再查找requestMapping注解RequestMapping requestMapping = AnnotationUtils.findAnnotation(beanClass, RequestMapping.class);String classMappingPath = this.getClassMappingPath(requestMapping);//按照controller分组上报if (StringUtils.isNotBlank(classMappingPath)) {String controllerGroupPath = basePath + classMappingPath + PASS;ApiSiriusDefinition controllerGroup = new ApiSiriusDefinition();controllerGroup.setGatewayId(gatewayServiceProperties.getGatewayId());controllerGroup.setResource("服务:" + applicationName + ",控制器:" + targetClass.getSimpleName() + ",路径:" + controllerGroupPath);controllerGroup.setUrlPath(controllerGroupPath);apiSiriusDefinitionList.add(controllerGroup);}//查找类中所有方法,进行遍历Method[] methods = targetClass.getMethods();for (Method method : methods) {//查找方法上RequestMapping注解String methodPath = "";String requestType = "";RequestMapping methodRequestMapping = AnnotationUtils.findAnnotation(method, RequestMapping.class);if (methodRequestMapping != null) {String[] value = methodRequestMapping.value();RequestMethod[] requestMethods = methodRequestMapping.method();if (value.length == 0) {if (requestMethods.length == 0) {return bean;}RequestMethod requestMethod = requestMethods[0];requestType = requestMethod.name();if (requestMethod.equals(RequestMethod.POST)) {PostMapping postMapping = AnnotationUtils.findAnnotation(method, PostMapping.class);methodPath = this.joinMethodPath(postMapping.value());} else if (requestMethod.equals(RequestMethod.GET)) {GetMapping getMapping = AnnotationUtils.findAnnotation(method, GetMapping.class);methodPath = this.joinMethodPath(getMapping.value());} else if (requestMethod.equals(RequestMethod.DELETE)) {DeleteMapping deleteMapping = AnnotationUtils.findAnnotation(method, DeleteMapping.class);methodPath = this.joinMethodPath(deleteMapping.value());} else if (requestMethod.equals(RequestMethod.PATCH)) {PatchMapping patchMapping = AnnotationUtils.findAnnotation(method, PatchMapping.class);methodPath = this.joinMethodPath(patchMapping.value());} else if (requestMethod.equals(RequestMethod.PUT)) {PutMapping putMapping = AnnotationUtils.findAnnotation(method, PutMapping.class);methodPath = this.joinMethodPath(putMapping.value());}}ApiSiriusDefinition apiSiriusDefinition = new ApiSiriusDefinition();String urlPath = basePath + classMappingPath + methodPath;apiSiriusDefinition.setUrlPath(urlPath);apiSiriusDefinition.setRequestType(requestType);apiSiriusDefinition.setGatewayId(gatewayServiceProperties.getGatewayId());apiSiriusDefinition.setResource("服务:" + applicationName + ",请求类型:" + requestType + ",路径:" + urlPath);apiSiriusDefinitionList.add(apiSiriusDefinition);}}return bean;}private String joinMethodPath(String[] value) {if (value.length != 0) {String str = this.trimStrWith(value[0], JTR);return JTR + str;}return "";}private String getContextPath() {String contextPath = environment.getProperty(CONTEXT_PATH);contextPath = this.trimStrWith(contextPath, JTR);return StringUtils.isBlank(contextPath) ? "" : contextPath;}public String getApplicationName() {String applicationName = environment.getProperty(APPLICATION_NAME);applicationName = this.trimStrWith(applicationName, JTR);return StringUtils.isBlank(applicationName) ? "" : applicationName;}private String getBasePath() {String contextPath = this.getContextPath();String applicationName = this.getApplicationName();if (StringUtils.isBlank(contextPath)) {return JTR + applicationName;}return JTR + applicationName + JTR + contextPath;}private String getClassMappingPath(RequestMapping requestMapping) {if (null != requestMapping) {String requestMappingUrl = requestMapping.value().length == 0 ? "" : requestMapping.value()[0];requestMappingUrl = this.trimStrWith(requestMappingUrl, JTR);return JTR + requestMappingUrl;}return "";}public String trimStrWith(String str, char trimStr) {if (StringUtils.isBlank(str)) {return str;}int st = 0;int len = str.length();char[] val = str.toCharArray();while ((st < len) && (val[st] <= trimStr)) {st++;}while ((st < len) && (val[len - 1] <= trimStr)) {len--;}return ((st > 0) || (len < str.length())) ? str.substring(st, len) : str;}@Overridepublic void run(String... args) {if (StringUtils.isBlank(this.getApplicationName())) {throw new RuntimeException(APPLICATION_NAME + " should not be null");}if (!apiSiriusDefinitionList.isEmpty()) {log.info("<<<<< start to report api information to api governance platform >>>>>");try {zookeeperService.create(API_DEFINITION + SPLIT + getApplicationName(), JSONArray.toJSONString(apiSiriusDefinitionList));zookeeperService.update(API_DEFINITION + SPLIT + getApplicationName(), JSONArray.toJSONString(apiSiriusDefinitionList));} catch (Exception e) {log.error("reported api information failed,stack info:", e);}log.info("<<<<< successfully reported api information >>>>>");}}}
通过扫描项目下的controller和相应的mapping注解中的属性拼接出url来,通过zk来更新节点数据
2、网关添加监听事件
zk操作查看另一篇文章zookeeper相关操作
/*** @classDesc: 网关核心应用* @author: cyjer* @date: 2023/1/30 9:53*/
@Component
@Slf4j
@RequiredArgsConstructor
public class GatewayApplication implements ApplicationListener<ContextRefreshedEvent> {private final GatewayServiceProperties properties;private final ApiDefinitionService apiDefinitionService;private final ZookeeperService zookeeperService;private final ApiGroupProcesser apiGroupProcesser;@Overridepublic void onApplicationEvent(ContextRefreshedEvent event) {//拉取api governance platform 信息apiDefinitionService.refreshApiGovernanceInfo(properties.getGatewayId());log.info("<<<<<<<<<<刷新api分组信息完成>>>>>>>>>>");zookeeperService.create(Constraint.API_DEFINITION, "init");zookeeperService.addWatchChildListener(Constraint.API_DEFINITION, apiGroupProcesser);log.info("<<<<<<<<<<api上报监听器配置完成>>>>>>>>>>");}
}
通过事件处理,首先启动时刷新api信息,同时尝试初始化zk节点,然后注册监听watch。
3、网关监听事件处理
/*** @classDesc: api分组上报* @author: cyjer* @date: 2023/2/10 11:13*/
@Slf4j
@Component
public class ApiGroupProcesser extends AbstractChildListenerProcess implements ApiDefinitionConstraint {@Resourceprivate RedisTemplate<String, Object> redisTemplate;@Resourceprivate GatewayServiceProperties gatewayServiceProperties;@Overridepublic void process(CuratorFramework curatorFramework, PathChildrenCacheEvent cacheEvent) {ChildData data = cacheEvent.getData();if (Objects.nonNull(data) && cacheEvent.getType().equals(PathChildrenCacheEvent.Type.CHILD_UPDATED)) {log.info("<<<<<<<<<<上报api分组到sentinel>>>>>>>>>>");String path = data.getPath();String content = new String(data.getData(), StandardCharsets.UTF_8);Set<ApiDefinition> definitions = GatewayApiDefinitionManager.getApiDefinitions();List<ApiSiriusDefinition> list = JSONArray.parseArray(content, ApiSiriusDefinition.class);for (ApiSiriusDefinition apiGroup : list) {ApiDefinition api = new ApiDefinition(apiGroup.getResource()).setPredicateItems(new HashSet<ApiPredicateItem>() {{add(new ApiPathPredicateItem().setPattern(apiGroup.getUrlPath()).setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));}});definitions.add(api);}GatewayApiDefinitionManager.loadApiDefinitions(definitions);redisTemplate.opsForHash().put(API_INFO_REDIS_PREFIX + gatewayServiceProperties.getGatewayId(), path, JSONArray.toJSONString(list));log.info("<<<<<<<<<<上报api分组到sentinel成功>>>>>>>>>>");}}
}
5、sentinel控制台启动
java -Dserver.port=8600 -Dcsp.sentinel.dashboard.server=localhost:8600 -Dproject.name=sentinel-dashboard -Xms512m -Xmx512m -Xmn256m -XX:MaxMetaspaceSize=100m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/oom/log -Dfile.encoding=UTF-8 -XX:+UseG1GC -jar sentinel-dashboard-1.8.6.jar
打开sentinel控制台,请求几次接口后
可以看到相应的api分组信息和url路径匹配都已加载,在进行流量治理的时候就可以支持restful接口和controller粒度的治理了