spring-cloud-gateway动态路由的实现方法
概述
线上项目发布一般有以下几种方案:
- 机发布
- 蓝绿部署
- 滚动部署
- 灰度发布
停机发布这种发布一般在夜里或者进行大版本升级的时候发布,因为需要停机,所以现在大家都在研究Devops方案。
蓝绿部署需要准备两个相同的环境。一个环境新版本,一个环境旧版本,通过负载均衡进行切换与回滚,目的是为了减少服务停止时间。
滚动部署就是在升级过程中,并不一下子启动所有新版本,是先启动一台新版本,再停止一台老版本,然后再启动一台新版本,再停止一台老版本,直到升级完成。基于k8s的升级方案默认就是滚动部署。
灰度发布也叫金丝雀发布,灰度发布中,常常按照用户设置路由权重,例如90%的用户维持使用老版本,10%的用户尝鲜新版本。不同版本应用共存,经常与A/B测试一起使用,用于测试选择多种方案。
上边介绍的几种发布方案,主要是引出我们接下来介绍的spring-cloud-gateway动态路由,我们可以基于动态路由、负载均衡和策略加载去实现灰度发布。当然现在有很多开源的框架可以实现灰度发布,这里只是研究学习。
动态路由
spring-cloud-gateway默认将路由加载在内存中。具体可以参见InMemoryRouteDefinitionRepository类的实现。
这里我们基于Redis实现动态路由。基础项目见spring-cloud-gateway简介
1.将actuator的端点暴露出来。
management: endpoints: web: exposure: include:"*"
2.redis配置
@Configuration publicclassRedisConfig{ @Bean(name={"redisTemplate","stringRedisTemplate"}) publicStringRedisTemplatestringRedisTemplate(RedisConnectionFactoryfactory){ StringRedisTemplateredisTemplate=newStringRedisTemplate(); redisTemplate.setConnectionFactory(factory); returnredisTemplate; } }
3.将原内存路由持久化到redis
@Component publicclassRedisRouteDefinitionRepositoryimplementsRouteDefinitionRepository{ /** *hash存储的key */ publicstaticfinalStringGATEWAY_ROUTES="gateway_dynamic_route"; @Resource privateStringRedisTemplateredisTemplate; /** *获取路由信息 *@return */ @Override publicFluxgetRouteDefinitions(){ List routeDefinitions=newArrayList<>(); redisTemplate.opsForHash().values(GATEWAY_ROUTES).stream() .forEach(routeDefinition->routeDefinitions.add(JSON.parseObject(routeDefinition.toString(),RouteDefinition.class))); returnFlux.fromIterable(routeDefinitions); } @Override publicMono save(Mono route){ returnroute.flatMap(routeDefinition->{ redisTemplate.opsForHash().put(GATEWAY_ROUTES,routeDefinition.getId(),JSONObject.toJSONString(routeDefinition)); returnMono.empty(); }); } @Override publicMono delete(Mono routeId){ returnrouteId.flatMap(id->{ if(redisTemplate.opsForHash().hasKey(GATEWAY_ROUTES,id)){ redisTemplate.opsForHash().delete(GATEWAY_ROUTES,id); returnMono.empty(); } returnMono.defer(()->Mono.error(newNotFoundException("routedefinitionisnotfound,routeId:"+routeId))); }); } }
4.重写动态路由服务
@Service publicclassGatewayDynamicRouteServiceimplementsApplicationEventPublisherAware{ @Resource privateRedisRouteDefinitionRepositoryredisRouteDefinitionRepository; privateApplicationEventPublisherapplicationEventPublisher; /** *增加路由 *@paramrouteDefinition *@return */ publicintadd(RouteDefinitionrouteDefinition){ redisRouteDefinitionRepository.save(Mono.just(routeDefinition)).subscribe(); applicationEventPublisher.publishEvent(newRefreshRoutesEvent(this)); return1; } /** *更新 *@paramrouteDefinition *@return */ publicintupdate(RouteDefinitionrouteDefinition){ redisRouteDefinitionRepository.delete(Mono.just(routeDefinition.getId())); redisRouteDefinitionRepository.save(Mono.just(routeDefinition)).subscribe(); applicationEventPublisher.publishEvent(newRefreshRoutesEvent(this)); return1; } /** *删除 *@paramid *@return */ publicMono>delete(Stringid){ returnredisRouteDefinitionRepository.delete(Mono.just(id)).then(Mono.defer(()->Mono.just(ResponseEntity.ok().build()))) .onErrorResume(t->tinstanceofNotFoundException,t->Mono.just(ResponseEntity.notFound().build())); } @Override publicvoidsetApplicationEventPublisher(ApplicationEventPublisherapplicationEventPublisher){ this.applicationEventPublisher=applicationEventPublisher; } }
5.对外暴露接口
@RestController @RequestMapping("/gateway") publicclassGatewayDynamicRouteController{ @Resource privateGatewayDynamicRouteServicegatewayDynamicRouteService; @PostMapping("/add") publicStringcreate(@RequestBodyRouteDefinitionentity){ intresult=gatewayDynamicRouteService.add(entity); returnString.valueOf(result); } @PostMapping("/update") publicStringupdate(@RequestBodyRouteDefinitionentity){ intresult=gatewayDynamicRouteService.update(entity); returnString.valueOf(result); } @DeleteMapping("/delete/{id}") publicMono>delete(@PathVariableStringid){ returngatewayDynamicRouteService.delete(id); } }
测试
测试前删除我们配置的静态路由,因为静态路由和redis动态路由同时存在时取并集。
访问http://localhost:2000/actuator/gateway/routes,可以看到只有默认路由。
[ { "route_id":"CompositeDiscoveryClient_consul", "route_definition":{ "id":"CompositeDiscoveryClient_consul", "predicates":[ { "name":"Path", "args":{ "pattern":"/consul/**" } } ], "filters":[ { "name":"RewritePath", "args":{ "regexp":"/consul/(?.*)", "replacement":"/${remaining}" } } ], "uri":"lb://consul", "order":0 }, "order":0 }, { "route_id":"CompositeDiscoveryClient_idc-gateway", "route_definition":{ "id":"CompositeDiscoveryClient_idc-gateway", "predicates":[ { "name":"Path", "args":{ "pattern":"/idc-gateway/**" } } ], "filters":[ { "name":"RewritePath", "args":{ "regexp":"/idc-gateway/(? .*)", "replacement":"/${remaining}" } } ], "uri":"lb://idc-gateway", "order":0 }, "order":0 }, { "route_id":"CompositeDiscoveryClient_idc-provider1", "route_definition":{ "id":"CompositeDiscoveryClient_idc-provider1", "predicates":[ { "name":"Path", "args":{ "pattern":"/idc-provider1/**" } } ], "filters":[ { "name":"RewritePath", "args":{ "regexp":"/idc-provider1/(? .*)", "replacement":"/${remaining}" } } ], "uri":"lb://idc-provider1", "order":0 }, "order":0 }, { "route_id":"CompositeDiscoveryClient_idc-provider2", "route_definition":{ "id":"CompositeDiscoveryClient_idc-provider2", "predicates":[ { "name":"Path", "args":{ "pattern":"/idc-provider2/**" } } ], "filters":[ { "name":"RewritePath", "args":{ "regexp":"/idc-provider2/(? .*)", "replacement":"/${remaining}" } } ], "uri":"lb://idc-provider2", "order":0 }, "order":0 } ]
这个时候访问http://192.168.124.5:2000/idc-provider1/provider1/1根据结果可以推测能正确路由到provider1,测试结果一致。
创建provider1路由,将路径设置为/p1/**,测试是否生效。
POST请求http://localhost:2000/gateway/add
{ "id":"provider1", "predicates":[ { "name":"Path", "args":{ "_genkey_0":"/p1/**" } }, { "name":"RemoteAddr", "args":{ "_genkey_0":"192.168.124.5/16" } } ], "filters":[ { "name":"StripPrefix", "args":{ "_genkey_0":"1" } } ], "uri":"lb://idc-provider1", "order":0 }
查看redis存储,或者请求http://localhost:2000/actuator/gateway/routes,都可以看到配置成功。
访问
curlhttp://localhost:2000/p1/provider1/1
结果输出2001,与期望一致。
由此可见动态路由已经生效。
到此这篇关于spring-cloud-gateway动态路由的实现方法的文章就介绍到这了,更多相关spring-cloud-gateway动态路由内容请搜索毛票票以前的文章或继续浏览下面的相关文章希望大家以后多多支持毛票票!