SpringCloud微服务Zuul网关动态路由

SpringCloud微服务Zuul网关动态路由

  • zuul动态路由
      • 1. 网关层动态配置路由映射规则
      • 2. 将同一个请求根据自定义的规则,路由到不同服务中
  • FeignClient动态请求
      • 实现功能
      • 实现思路
      • 业务代码
  • 总结

这篇文章不止是普通的动态路由,他可以帮你将zuul网关由静态路由升级为动态路由,还可以在路由之后,再将利用自定义规则实现同一个URL请求根据用户(或者别的属性)访问到不同的服务中。

我在的公司是个半外包的公司,想做成一个产品但是又要满足不同客户的不同需求。采用微服务拆分所有业务模块,每一个业务模块根据不同的客户可能会有不同的场景。对于前端请求和后端服务间相互调用来说,不可能因为一个模块新增了一种业务场景就重写或者修改一次代码。于是就想,同一个模块的所有场景能否用同一个URL来请求,根据客户不同来请求不同的服务的接口。这样就有了这两个动态路由和场景选择。

源码在这里:动态路由源码 ,这个项目是我的微服务脚手架项目,基于eureka-zuul构建的。

zuul动态路由

1. 网关层动态配置路由映射规则

动态配置与网上其他文章的思路是一样的,继承org.springframework.cloud.netflix.zuul.filters.SimpleRouteLocator重写locateRoutes方法并实现org.springframework.cloud.netflix.zuul.filters.RefreshableRouteLocator接口的refresh方法。

SimpleRouteLocator是zuul基础路由加载类,初始化的时候自动将配置文件中配置的路由规则加载到内存中,重写locateRoutes方法将路由规则配置改为自定义的源。RefreshableRouteLocator接口仅提供刷新路由表的方法,实现很简单。

代码如下:

/**
 * 动态路由实现
 * @author 無痕剑
 * @date 2018/11/15 22:05
 */
public class DynamicRouteLocator extends SimpleRouteLocator implements RefreshableRouteLocator {

	/**
	 * 是否启用动态路由。
	 * 在配置文件中:saas.routes.dynamic.enabled,默认为false
	 */
	private boolean enabled;

	public DynamicRouteLocator(boolean enabled, String servletPath, ZuulProperties properties) {
		super(servletPath, properties);
		this.enabled = enabled;
	}

	/**
	 * 重载路由规则
	 */
	@Override
	protected Map locateRoutes() {
		if (!enabled) {
			return super.locateRoutes();
		}
		Map routeMap = new HashMap<>();
		// 从数据源获取路由配置
		// 先模拟几个配置
		String path = "/log/**";
		String serviceId = "service-log";

		// 任意一个为空,则不进行动态路由
		if (StringUtil.isBlank(path) || StringUtil.isBlank(serviceId)) {
			return super.locateRoutes();
		}
		// 生成ZuulRoute对象
		ZuulProperties.ZuulRoute zuulRoute = createZuulRoute(path, serviceId);

		routeMap.put(path, zuulRoute);

		String path1 = "/basedata/**";
		String serviceId2 = "service-basedata";
		ZuulProperties.ZuulRoute zuulRoute1 = createZuulRoute(path1, serviceId2);
		routeMap.put(path1, zuulRoute1);

		return routeMap;
	}

	/**
	 * 刷新路由
	 */
	@Override
	public void refresh() {
		super.doRefresh();
	}

	public boolean getEnabled() {
		return enabled;
	}

	public void setEnabled(boolean enabled) {
		this.enabled = enabled;
	}

	/**
	 * 生成ZuulRoute对象
	 * @param path 映射路径
	 * @param serviceId 服务Id
	 */
	private ZuulProperties.ZuulRoute createZuulRoute(String path, String serviceId) {
		ZuulProperties.ZuulRoute zuulRoute = new ZuulProperties.ZuulRoute();
		zuulRoute.setId(path);
		zuulRoute.setPath(path);
		zuulRoute.setServiceId(serviceId);
		return zuulRoute;
	}
}

完成自定义路由规则加载以后,创建Bean,让Spring帮我们管理它:

/**
 * 动态路由配置
 * @author: Overload
 * @review:
 * @date: 2018/11/22 9:29
 */
@Configuration
public class DynamicRouteConfig {

    @Value("${saas.routes.dynamic.enabled}")
    private boolean enabledDynamicRoute;

    /**
     * 使用自定义的路由策略代替默认路由策略
     */
    @Bean
    public SimpleRouteLocator routeLocator(ZuulProperties zuulProperties) {
        return new DynamicRouteLocator(enabledDynamicRoute, zuulProperties.getPrefix(), zuulProperties);
    }
}

ZuulProperties在zuul框架中已经创建了对应的Bean了,这里我们只需要注入它即可,不需要再创建了。

2. 将同一个请求根据自定义的规则,路由到不同服务中

这个思路是zuul路由是在filterType为route的过滤器之后实现的,实际上在进入第一个filter之前,zuul就已经将请求上下文RequestContext中的FilterConstants.SERVICE_ID_KEY已经设置好了(具体是根据1.中的配置进行设置的)。那么在这里,我们只需要替换这个SERVICE_ID_KEY为我们自定义规则匹配后的值即可。

这里有个小提示,放置这个自定义路由过滤器的位置很关键,如果你有前置处理请求参数或者用户信息鉴权等filter的话,记得把这个filter放置在他们后面配置filterOrder的值即可。

/**
 * 

场景选择过滤器。

*

查询一个静态常量,匹配到请求用户的对应场景的 serviceId ,并设置到请求上下文中的属性{@link FilterConstants#SERVICE_ID_KEY}中。

*

该过滤器放置在 {@link PreDecorationFilter} 之后, * 相当于替换 PreDecorationFilter 已经设置好的 serviceId

*

如果放置在 PreDecorationFilter 之前, PreDecorationFilter 会不进行过滤。就需要重写 PreDecorationFilter 的功能。

* @author 無痕剑 * @date 2018/11/23 23:40 */ @Slf4j @Component public class SceneSelectorFilter extends ZuulFilter { @Override public String filterType() { return FilterConstants.PRE_TYPE; } /** * 将该过滤器放置在PreDecorationFilter之后 */ @Override public int filterOrder() { return FilterConstants.PRE_DECORATION_FILTER_ORDER + 1; } /** * 所有请求都要适配场景 */ @Override public boolean shouldFilter() { return true; } @Override public Object run() throws ZuulException { // 获取当前请求上下文 RequestContext currentContext = RequestContext.getCurrentContext(); // 获取URI String uri = currentContext.getRequest().getRequestURI(); // 获取服务名称 String serviceName = RequestUtil.matchServiceName(uri); // 获取用户信息 // 根据serviceName和user.companyId获取场景 // 将请求头中的serviceId设置为对应场景 currentContext.set(FilterConstants.SERVICE_ID_KEY, "service-basedata"); return null; } }

FeignClient动态请求

实现功能

需要将一个FeignClient的请求根据客户信息,发送到不同的服务上去。

例如,客户C1-C3,订单服务O1-O3,商品服务P。客户均访问同一个商品服务,商品服务中一个接口会直接创建订单,请求接口为/service-order/create。现在要求C1只能访问O1,C2只能访问O2,C3只能访问O3,在不改变服务P的情况下,需要进行此业务处理。

实现思路

思路大体上与zuul层修改请求路由路径类似,都是修改serviceId来达到对请求的修改。FeignClient默认采用的是org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient进行FeignClient请求处理,execute方法有两个参数,我们需要的是feign.Request这个参数,将里面对应的URL中的serviceId替换为我们自定义规则匹配的serviceId即可。

业务代码

继承LoadBalancerFeignClient,重写execute方法即可,最后再调用super.execute()来进行请求。

/**
 * 动态路由FeignClient
 * @author 無痕剑
 * @date 2018/11/27 22:49
 */
public class DynamicRouteFeignClient extends LoadBalancerFeignClient {

	public DynamicRouteFeignClient(Client delegate, CachingSpringLoadBalancerFactory lbClientFactory, SpringClientFactory clientFactory) {
		super(delegate, lbClientFactory, clientFactory);
	}

	/**
	 * 重写这个方法,在前面加上动态路由。
	 * 其实就是修改URL
	 */
	@Override
	public Response execute(Request request, Request.Options options) throws IOException {
		/*
		 获取远程调用的URL
		 格式:http://service-log/operation/remote/listByTypeAndId?mappingType=asd&mappingId=12&companyId=11
		  */
		String url = request.url();
		// 获取请求接口的serviceId
        String serviceId = StringUtil.urlServiceId(url);
        // 看获取场景是通过本地服务连缓存系统还是连配置中心服务获取
        // 连接配置中心的请求,需要跳过下面几步,直接return
        //TODO 获取用户购买的场景对应的serivceId
        String nServiceId = "";
        // 修改URL中serviceId为对应场景serviceId
		String newUrl = StringUtil.changeServiceId(url, nServiceId);
		//生成新请求
		Request newRequest = Request.create(request.method(), newUrl, request.headers(), request.body(), request.charset());
		// 执行请求
		return super.execute(newRequest, options);
	}
}

这里由于构造器为含参构造器,创建Bean的时候,需要注入这三个参数。CachingSpringLoadBalancerFactory cachingFactorySpringClientFactory clientFactory在feign框架中已经声明为Bean了,只是使用了@ConditionalOnMissingBean注解,可能IDEA之类的编辑器会提示无法找到对应的Bean,但是没有关系,依然可以自动注入。

创建Bean配置文件如下:

/**
 * @author 無痕剑
 * @date 2018/11/27 23:38
 */
@Configuration
public class DynamicRouteFeignConfig {

	@Bean
	public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory, SpringClientFactory clientFactory) {
		return new DynamicRouteFeignClient(new Client.Default(null, null), cachingFactory, clientFactory);
	}
}

总结

这样就整体实现了动态路由与请求。阅读源码可以很方便的找到你想要的东西。

你可能感兴趣的:(Java,微服务,SpringCloud)