简易智能识别服务2—使用zuul 的过滤器&动态路由

2018年10月16日

zuul是spring cloud的网关组件,可以构建动态路由、服务降级、负载均衡的服务网关,通过filter链式调用进行扩展,实现统一认证、调用监控、日志管理等等功能。

核心组件:

组件 介绍
ZuulFilter 是zuul的核心,分为pre、route、post三种类型,分别对应服务调用之前、之中、之后的处理
ZuulServlet 一个HttpServlet,执行所有ZuulFilter的入口
Route 路由的一个节点,通常对应某个微服务
RouteLocator 获取所有Route的接口,有SimpleRouteLocator和DiscoveryClientRouteLocator两种实现,后者继承自前者,前者通过配置文件配置获得,后者扩展了通过服务发现找到所有服务,并默认为所有服务生成path为/serviceId/**的Route。
ZuulController 需要路由的请求的统一处理器,继承自ServletWrappingController,把请求统一代理给ZuulServlet处理
ZuulHandlerMapping 通过RouteLocator 获取到所有Route之后把route的fullPath和ZuulController注册到到HandlerMap中,把网关需要路由的路径统一映射到ZuulController

目前主要用到请求过滤和路由功能,因此重点理解相关组件使用方法;

组件–zuulFilter:

Zuul中提供Filter的作用有哪些?
– 网关是暴露在外面的,必须要进行权限控制
– 可以针对服务做控制,在路由的时候处理,比如服务降级
– 防止爬虫,利用Filter对请求进行过滤
– 流量控制,只允许最高的并发量,保护后端的服务
– 灰度发布,可以针对不用的用户进行路由来实现灰度

怎么使用filter功能?

1.自定义新的filter类继承 ZuulFilterl基础抽象类

**
 * IP黑名单限制过滤器
 *
 **/
public class IpFilter extends ZuulFilter {

    @Autowired
    private BasicConf basicConf;

    public IpFilter() {
        super();
    }

    //shouldFilter是决定这个过滤器需不需要执行,返回false则不执行,这个也可以利用配置中心来做,达到动态的开启关闭效果, 
这边是通过上下文对象,获取一个isSuccess的值来决定要不要执行的,就是说由上个过滤器告诉你要不要执行,因为上面的流程没通过,所以下面也没必要继续执行了。
    @Override
    public boolean shouldFilter() {
        RequestContext ctx = RequestContext.getCurrentContext();
        Object success = ctx.get("isSuccess");
        return success == null ? true : Boolean.parseBoolean(success.toString());
    }

    //filterType就是过滤器的类型了,取值分别为pre,route,post,error
    @Override
    public String filterType() {
        return "pre";
    }

    //filterOrder是表示过滤器执行的顺序,数字越小,优先级越高
    @Override
    public int filterOrder() {
        return 1;
    }

     //run里面就是我们自己要执行的业务逻辑了,可以直接返回null,
    @Override
    public Object run() {
        RequestContext ctx = RequestContext.getCurrentContext();
        String ip = IpUtils.getIpAddr(ctx.getRequest());
        // 在黑名单中禁用
        if (StringUtils.isNotBlank(ip) && basicConf != null && basicConf.getIpStr().contains(ip)) {
            ctx.set("isSuccess", false);
            ctx.setSendZuulResponse(false);
            ResponseData data = ResponseData.fail("非法请求", ResponseCode.NO_AUTH_CODE.getCode());
            ctx.setResponseBody(JsonUtils.toJson(data));
            ctx.getResponse().setContentType("application/json; charset=utf-8");
            return null;
        }
        return null;
    }
}

2.  注册自定义的filter给SpringBoot

@Configuration
public class FilterConfig {

    @Bean
    public IpFilter ipFilter() {
        return new IpFilter();
    }

}

 

Zuul有哪些内置的filter? 这些filter了解一下即可

来着重看下route路由阶段的几个过滤器

1. RibbonRoutingFilter:是route阶段第一个过滤器,仅在context中存在serviceId的情况下运行。存在serviceId,就是说需要面向服务进行路由,服务的路由信息就是我们上面讲过的两种方式,配置文件(静态)及服务注册。具体就是创建RibbonCommandContext,然后交由ribbon和hystrix向下游服务进行请求

2. SimpleHostRoutingFilter:是route阶段第二个过滤器,仅在context中存在routeHost的情况下运行。存在routeHost,就是说我们配置了具体的http或者https url的请求信息。具体逻辑就是通过HttpClient直接向目标url发起请求,不再经过ribbon及hystrix,所以也就没有负载均衡以及熔断

3. SendForwardFilter:是route阶段第三个(最后一个)过滤器,仅在context中存在forward.to的情况下运行。存在forward.to,就是说我们配置了类似forward:/index的请求信息。具体就是通过RequestDispatcher进行forward(也就是local forward到本地子定义的controller上, 可以保证老的Http接口可以继续沿用)

 

组件–RouteLocator:

RouteLocator主要用于实现路由能力,包括 静态路由,动态路由;

RouteLocator 获取所有Route的接口,有SimpleRouteLocator和DiscoveryClientRouteLocator两种实现,后者继承自前者,前者通过配置文件配置获得,后者扩展了通过服务发现找到所有服务,并默认为所有服务生成path为/serviceId/**的Route。

如何实现动态路由?一个基于URL(不是service id)路由的样例:

  1.  继承SimpleRouteLocator类,实现 RefreshableRouteLocator, 重新定义相关路由逻辑(比如从DB,Zookeeper中读取路由信息)
public class CustomRouteLocator extends SimpleRouteLocator implements RefreshableRouteLocator {
 
    public final static Logger logger = LoggerFactory.getLogger(CustomRouteLocator.class);
 
    private JdbcTemplate jdbcTemplate;
 
    private ZuulProperties properties;
 
    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }
 
    public CustomRouteLocator(String servletPath, ZuulProperties properties) {
        super(servletPath, properties);
        this.properties = properties;
        logger.info("servletPath:{}", servletPath);
    }
 
    @Override
    public void refresh() {
        doRefresh();
    }
 
    @Override
    protected Map<String, ZuulProperties.ZuulRoute> locateRoutes() {
        LinkedHashMap<String, ZuulProperties.ZuulRoute> routesMap = new LinkedHashMap<>();
        //从application.properties中加载路由信息
        routesMap.putAll(super.locateRoutes());
        //从db中加载路由信息
        routesMap.putAll(locateRoutesFromDB());
        //优化一下配置
        LinkedHashMap<String, ZuulProperties.ZuulRoute> values = new LinkedHashMap<>();
        for (Map.Entry<String, ZuulProperties.ZuulRoute> entry : routesMap.entrySet()) {
            String path = entry.getKey();
            // Prepend with slash if not already present.
            if (!path.startsWith("/")) {
                path = "/" + path;
            }
            if (StringUtils.hasText(this.properties.getPrefix())) {
                path = this.properties.getPrefix() + path;
                if (!path.startsWith("/")) {
                    path = "/" + path;
                }
            }
            values.put(path, entry.getValue());
        }
        return values;
    }
 
    private Map<String, ZuulProperties.ZuulRoute> locateRoutesFromDB() {
        Map<String, ZuulProperties.ZuulRoute> routes = new LinkedHashMap<>();
        List<ZuulRouteVO> results = jdbcTemplate.query("select * from gateway_api_define where enabled = true ", new
                BeanPropertyRowMapper<>(ZuulRouteVO.class));
        for (ZuulRouteVO result : results) {
            if (StringUtils.isEmpty(result.getPath()) ) {
                continue;
            }
            if (StringUtils.isEmpty(result.getServiceId()) && StringUtils.isEmpty(result.getUrl())) {
                continue;
            }
            ZuulProperties.ZuulRoute zuulRoute = new ZuulProperties.ZuulRoute();
            try {
                BeanUtils.copyProperties(result, zuulRoute);
            } catch (Exception e) {
                logger.error("=============load zuul route info from db with error==============", e);
            }
            routes.put(zuulRoute.getPath(), zuulRoute);
        }
        return routes;
    }
 
    public static class ZuulRouteVO {
 
        /**
         * The ID of the route (the same as its map key by default).
         */
        private String id;
 
        /**
         * The path (pattern) for the route, e.g. /foo/**.
         */
        private String path;
 
        /**
         * The service ID (if any) to map to this route. You can specify a physical URL or
         * a service, but not both.
         */
        private String serviceId;
 
        /**
         * A full physical URL to map to the route. An alternative is to use a service ID
         * and service discovery to find the physical address.
         */
        private String url;
 
        /**
         * Flag to determine whether the prefix for this route (the path, minus pattern
         * patcher) should be stripped before forwarding.
         */
        private boolean stripPrefix = true;
 
        /**
         * Flag to indicate that this route should be retryable (if supported). Generally
         * retry requires a service ID and ribbon.
         */
        private Boolean retryable;
 
        private Boolean enabled;
 
        public String getId() {
            return id;
        }
 
        public void setId(String id) {
            this.id = id;
        }
 
        public String getPath() {
            return path;
        }
 
        public void setPath(String path) {
            this.path = path;
        }
 
        public String getServiceId() {
            return serviceId;
        }
 
        public void setServiceId(String serviceId) {
            this.serviceId = serviceId;
        }
 
        public String getUrl() {
            return url;
        }
 
        public void setUrl(String url) {
            this.url = url;
        }
 
        public boolean isStripPrefix() {
            return stripPrefix;
        }
 
        public void setStripPrefix(boolean stripPrefix) {
            this.stripPrefix = stripPrefix;
        }
 
        public Boolean getRetryable() {
            return retryable;
        }
 
        public void setRetryable(Boolean retryable) {
            this.retryable = retryable;
        }
 
        public Boolean getEnabled() {
            return enabled;
        }
 
        public void setEnabled(Boolean enabled) {
            this.enabled = enabled;
        }
    }
}

2. 把自定义的路由类注册给SpringBoot

@Configuration
public class CustomZuulConfig {
 
    @Autowired
    ZuulProperties zuulProperties;
    @Autowired
    ServerProperties server;
    @Autowired
    JdbcTemplate jdbcTemplate;
 
    @Bean
    public CustomRouteLocator routeLocator() {
        CustomRouteLocator routeLocator = new CustomRouteLocator(this.server.getServletPrefix(), this.zuulProperties);
        routeLocator.setJdbcTemplate(jdbcTemplate);
        return routeLocator;
    }
 
}

 

没有评论

发表评论

邮箱地址不会被公开。 必填项已用*标注