spring-cloud-netflix-core:1.3.0.RELEASE
原文:https://note.youdao.com/share/?id=fbd1d5bda015ae807a00c55edb2d3ed8&type=note#/
先说明两个概念:路由配置和路由规则,
路由配置是指配置某请求路径路由到指定的目的地址;
路由规则是指匹配到路由配置之后,再进行自定义的规则判断,规则判断可以更改路由目的地址
动态路由需要达到可持久化配置,动态刷新的效果。不仅要能满足从spring的配置文件properties加载路由信息,还需要从Redis加载我们的配置。另外一点是,路由信息在容器启动时就已经加载进入了内存,我们希望配置完成后,实施发布,动态刷新内存中的路由信息,达到不停机维护路由信息的效果。
为了避免Eureka的侵入性设计,这里没有使用spring-cloud的服务的注册与发现的Eureka,而直接使用了Zuul。
需要配置如下属性使Zuul不依赖Eureka:
#关闭zuul的eureka注册
eureka.client.enabled=false
CubeRouteLocator 自定义 动态路由
这里采取的方式修改路由定位器,借鉴DiscoveryClientRouteLocator去改造SimpleRouteLocator使其具备刷新能力。
DiscoveryClientRouteLocator比SimpleRouteLocator多了两个功能,第一是从DiscoveryClient(如Eureka)发现路由信息,我们不想使用eureka这种侵入式的网关模块,所以忽略它,第二是需要实现可刷新的路由定位器接口(RefreshableRouteLocator),并可以继承默认的实现(SimpleRouteLocator)再进行扩展,能够实现动态刷新。
实现动态路由主要关注两个方法
protected Map
public Route getMatchingRoute(String path):此方法是根据访问路径,获取匹配的路由配置,父类中已经匹配到路由,可以通过路由id查找自定义配置的路由规则,以达到根据自定义规则动态分流的效果(了解)
public class CubeRouteLocator extends SimpleRouteLocator implements RefreshableRouteLocator{
private LookupService lookupService;
public CubeRouteLocator(LookupService lookupService,ServerProperties server, ZuulProperties properties) {
super(server.getServletPrefix(), properties);
this.lookupService = lookupService;
}
protected Map locateRoutes() {
List serviceInfos = lookupService.getServiceInfos();//从配置文件中查询路由信息
LinkedHashMap routesMap = new LinkedHashMap();
for(ServiceInfo info : serviceInfos){
ZuulRoute zuulRoute = new ZuulRoute();
zuulRoute.setId( info.getServiceId() );
zuulRoute.setServiceId( info.getServiceId() );
zuulRoute.setPath( "/api/"+info.getServiceId() + info.getPath() +"/**" );
zuulRoute.setUrl( "http://" + info.getHostName() + ":" + info.getPort() + info.getPath() );
routesMap.put("/api/"+info.getServiceId()+ info.getPath() +"/**", zuulRoute);
}
return routesMap;
}
//刷新路由。
@Override
public void refresh() {
doRefresh();
}
}
在spring容器启动完成后就刷新了路由规则(重启)。因此我们如果要主动刷新路由规则,只需要发布一个RoutesRefreshedEvent事件即可(源码:ZuulRefreshListener 如下) , 由源码可知在发生ContextRefreshedEvent(Spring重启)和RoutesRefreshedEvent(可以手动发布)事件时会执行
@Service
public class RefreshRouteService {
@Autowired
ApplicationEventPublisher publisher;
@Autowired
RouteLocator routeLocator;
public void refreshRoute() {
RoutesRefreshedEvent routesRefreshedEvent = new RoutesRefreshedEvent(routeLocator);
publisher.publishEvent(routesRefreshedEvent);
}
}
看下源码,zuul是怎么做到转发,路由的。
@Configuration
@EnableConfigurationProperties({ ZuulProperties.class })
@ConditionalOnClass(ZuulServlet.class)
@Import(ServerPropertiesAutoConfiguration.class)
public class ZuulConfiguration {
@Autowired
protected ZuulProperties zuulProperties; //zuul的配置文件,对应了application.properties中的配置信息
@Autowired
protected ServerProperties server;
@Autowired(required = false)
private ErrorController errorController;
@Bean
public HasFeatures zuulFeature() {
return HasFeatures.namedFeature("Zuul (Simple)", ZuulConfiguration.class);
}
@Bean
@Primary
public CompositeRouteLocator primaryRouteLocator(
Collection routeLocators) {
return new CompositeRouteLocator(routeLocators);
}
//核心类,路由定位器,最最重要
@Bean
@ConditionalOnMissingBean(SimpleRouteLocator.class)
public SimpleRouteLocator simpleRouteLocator() {
//默认配置的实现是SimpleRouteLocator.class
return new SimpleRouteLocator(this.server.getServletPrefix(),
this.zuulProperties);
}
//zuul的控制器,负责处理链路调用
@Bean
public ZuulController zuulController() {
return new ZuulController();
}
@Bean
public ZuulHandlerMapping zuulHandlerMapping(RouteLocator routes) {
ZuulHandlerMapping mapping = new ZuulHandlerMapping(routes, zuulController());
mapping.setErrorController(this.errorController);
return mapping;
}
//注册了一个路由刷新监听器,默认实现是ZuulRefreshListener.class,这个是我们动态路由的关键
@Bean
public ApplicationListener zuulRefreshRoutesListener() {
return new ZuulRefreshListener();
}
//上面提到的路由刷新监听器
private static class ZuulRefreshListener
implements ApplicationListener {
@Autowired
private ZuulHandlerMapping zuulHandlerMapping;
private HeartbeatMonitor heartbeatMonitor = new HeartbeatMonitor();
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ContextRefreshedEvent
|| event instanceof RefreshScopeRefreshedEvent
|| event instanceof RoutesRefreshedEvent) {
//设置为脏,下一次匹配到路径时,如果发现为脏,则会去刷新路由信息
this.zuulHandlerMapping.setDirty(true); //setDirty方法源码如下
}
else if (event instanceof HeartbeatEvent) {
if (this.heartbeatMonitor.update(((HeartbeatEvent) event).getValue())) {
this.zuulHandlerMapping.setDirty(true);
}
}
}
}
}
public void setDirty(boolean dirty) {
this.dirty = dirty;
if (this.routeLocator instanceof RefreshableRouteLocator) {
((RefreshableRouteLocator) this.routeLocator).refresh();
}
}
分析下平台路由配置类
@Configuration
@EnableZuulProxy
public class CubeRouteConfiguration {
@Autowired
private ServerProperties server;
@Autowired
private ZuulProperties properties;
//映射规则写入到配置文件中
@Value("${cube.lookupservice.path?:file:config/cube-service.json}")
private Resource resouce;
public CubeRouteConfiguration() {
}
@Bean
public CubeRouteLocator getCubeRouteLocator() throws IOException {
return new CubeRouteLocator(this.getLookupService(), this.server, this.properties);
}
//读取配置文件中的路由规则
@Bean
public LookupService getLookupService() throws IOException {
if (this.resouce.exists()) {
FileLookupService lookupService = new FileLookupService(this.resouce.getInputStream());
return lookupService;
} else {
return new FileLookupService();
}
}
//路由过滤器
@Bean
public CubeZuulFilter getCubeZuulFilter() {
return new CubeZuulFilter();
}
// **** CubeRestClient 服务间调用工具 (平台自己使用(如记录系统日志))
@Bean
@ConditionalOnMissingBean
public CubeRestClient getCubeRestClient() throws IOException {
RestTemplateBuilder builder = new RestTemplateBuilder(new RestTemplateCustomizer[0]);
RestTemplate restTemplate = builder.additionalMessageConverters(new HttpMessageConverter[]{new FormHttpMessageConverter(), new FastJsonHttpMessageConverter()}).build();
return new CubeRestClient(this.getLookupService(), restTemplate);
// 请注意这块,this.getLookupService()参数,会把从配置文件读取到的路由规则传给CubeRestClient
//如果有兴趣 请接着阅读源码,这块不多介绍了
}
}
////////////////////////////////////////////////////我是分隔符////////////////////////////////////////////////////////////////////////////////
//服务间调用工具 我们目前使用的
@Bean
@ConditionalOnMissingBean
public CubeRestClient getCubeRestClient() throws IOException{
RestTemplateBuilder builder = new RestTemplateBuilder();
RestTemplate restTemplate = builder.additionalMessageConverters(new FormHttpMessageConverter(),new FastJsonHttpMessageConverter())
.additionalInterceptors(new ClientHttpRequestInterceptor(){
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
throws IOException {
CubeContext cubeContext = CubeContextManager.getCurrentContext();
if(null != cubeContext){
Locale locale = cubeContext.getLanguage();
String remoteAddress = cubeContext.getRemoteAddress();
String jwt = cubeContext.getHeaderString("Authorization");
request.getHeaders().add("x-forwarded-for", remoteAddress); //获取IP
if(locale != null){
request.getHeaders().add("Content-Language", locale.toString()); //获取local 本地化
}
if(StringUtils.isNotBlank(jwt)){
request.getHeaders().add("Authorization", jwt); //访问该系统的用户是谁 jwt里面有一个user实体(用户名等)
}
}
return execution.execute(request, body);
}
}).build();
return new CubeRestClient(getLookupService(), restTemplate);
}
////////////////////////////////////////////////////我是分隔符////////////////////////////////////////////////////////////////////////////////
//这个是WebUI里面 调用后台服务的时候 使用的 看区别
@Bean
@ConditionalOnMissingBean
public CubeRestClient getCubeRestClient() throws IOException {
RestTemplateBuilder builder = new RestTemplateBuilder(new RestTemplateCustomizer[0]);
RestTemplate restTemplate = builder.additionalMessageConverters(new HttpMessageConverter[]{new FormHttpMessageConverter(), new FastJsonHttpMessageConverter()}).additionalInterceptors(new ClientHttpRequestInterceptor[]{new ClientHttpRequestInterceptor() {
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
HttpServletRequest httpServletRequest = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();
Locale locale = httpServletRequest.getLocale();
String remoteAddress = httpServletRequest.getRemoteHost();
if (locale != null) {
request.getHeaders().add("Content-Language", locale.toString());
}
request.getHeaders().add("x-forwarded-for", remoteAddress);
String jwt = (String)RequestContextHolder.currentRequestAttributes().getAttribute("CUBE_AUTHORIZATION_TOKEN", 0);
if (StringUtils.isNotBlank(jwt) && !request.getHeaders().containsKey("CUBE_AUTHORIZATION_TOKEN")) {
request.getHeaders().add("Authorization", jwt);
}
return execution.execute(request, body);
}
}}).build();
return new CubeRestClient(this.getLookupService(), restTemplate);
}
回顾一下 自助审计日志
关注一下这个CUBE_AUTHORIZATION_TOKEN 参数