点击上方
“码农沉思录”
,选择“设为星标”
优质文章,及时送达
导语
随着车金融业务的快速发展,单体架构的系统已经不能满足业务的快速发展的需要,在这种情况下,本文主要介绍微服务网关在金融的实践与演进过程。
背景 随着车金融业务的快速发展,单体架构的系统已经不能满足业务的快速发展的需要,因此在2018年初,我们对车金融业务进行了微服务架构的升级改造, 整个系统拆分出40多个微服务。在重构过程中我们发现以下几个问题:- 每一个访问微服务系统的客户端都需要维护一份服务路由关系;
- 一些通用的如身份鉴权、权限控制等功能,微服务中重复开发。
@Override
public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException {
...
// 路由预处理(pre阶段)
preRoute();
...
// 路由阶段(route阶段)
route();
...
// 请求响应阶段(post阶段)
postRoute();
}
在路由阶段(route阶段)
请求会先经过RibbonRoutingFilter,然后经过SimpleHostRoutingFilter
以下代码分别是两个filter的执行条件
//RibbonRoutingFilter
ctx.getRouteHost() == null && ctx.get(SERVICE_ID_KEY) != null&& ctx.sendZuulResponse());
// SimpleHostRoutingFilter
RequestContext.getCurrentContext().getRouteHost() != null
&& RequestContext.getCurrentContext().sendZuulResponse();
通过以上代码,结合application.yml配置文件
zuul:
routes:
service1:
path: /service1/**
url: http://127.0.0.1:8080
service2:
path: /service2/**
serviceId: service2
当调用到RibbonRoutingFilter时会去判断serviceId是否为空(执行路由条件),当调用到SimpleHostRoutingFilter时会校验host是否为空。
由此推断路由信息是在pre阶段确定下来的,然后定位到PreDecorationFilter会根据请求URI匹配相应的路由信息,然后获取静态配置中的路由信息解析出相应的RouteHost和serviceId。其
源码
(由于源码过长,请同学们自行查看)中RouteLocator即为我们的路由定位器,也就是我们要重写的部分。
(3) 路由定位器
PreDecorationFilter通过RouteLocator根据URI获取Route,因此可以通过对RouteLocator的扩展来完成动态路由工作。Spring Cloud默认的路由定位器由SimpleRouteLocator来实现。
主要功能包含:
通过properties获取所有路由;
根据请求URI获取路由信息;
public class SimpleRouteLocator implementsRouteLocator, Ordered {
// routes 用于存储路由信息
private AtomicReference> routes = new AtomicReference<>();
// 查找路由信息
protected Map locateRoutes() {
LinkedHashMaproutesMap = new LinkedHashMap<>();
// 提取ZuulProperties中的ZuulRoute
for (ZuulRoute route :this.properties.getRoutes().values()) {
routesMap.put(route.getPath(), route);
}
return routesMap;
}
// 根据请求匹配路由
protected Route getSimpleMatchingRoute(final Stringpath) {
// 确认初始化路由map完成
getRoutesMap();
// 对URI处理
String adjustedPath = adjustPath(path);
// 获取匹配路由
ZuulRoute route = getZuulRoute(adjustedPath);
return getRoute(route, adjustedPath);
}
}
所以这里继承SimpleRouteLocator并重写了locateRoutes函数,由properties获取路由信息改为通过DB获取我们的路由信息。
@Override
public MaploadLocateRoute() {
List zuulRouteDtos =getZuulRoutes();
// 把DB获取的路由信息转为Map
Map handle =handle(zuulRouteDtos);
return handle;
}
/**
* @authorpenghb
* @description 获取所有路由
* @date 8:37PM 2019/6/3
* @return 路由列表
**/
private List getZuulRoutes() {
String cloudClusterGroup =System.getenv(SYSTEM_CLOUD_GROUP);
APIResponse> all = zuulRouteService.findByCloudGroupCode(cloudClusterGroup);
return APIResponseUtils.getResultData(all);
}
(4) 路由动态刷新
由于Spring Cloud默认的SimpleRouteLocator是不支持路由刷新的,但是自定义的动态路由是要支持路由的刷新功能的(当配置中心路由信息修改后,网关要实时的刷新路由信息),因此在继承SimpleRouteLocator的基础上,还要实现Zuul提供的RefreshableRouteLocator来支持动态路由刷新能力。
zuul内部提供了ZuulRefreshListener,它会监听ApplicationEventPublisher发布的事件,如果事件为RoutesRefreshedEvent,则会调用routeLocator的refresh函数,在自定义的路由定位器中可以直接调用SimpleRouteLocator的doRefresh函数:
protected void doRefresh() {
this.routes.set(locateRoutes());
}
当路由信息在配置中心发生变化的时候,就通过ApplicationEventPublisher发布一个RoutesRefreshedEvent事件:
RoutesRefreshedEventroutesRefreshedEvent = new RoutesRefreshedEvent(routeLocator);
publisher.publishEvent(routesRefreshedEvent);
这样动态刷新路由也实现了。
最后向IOC容器中注入自定义的路由定位器,去替换Spring Cloud的路由定位器。
@Bean
@ConditionalOnMissingBean(ZuulRouteDatabaseLocator.class)
public ZuulRouteDatabaseLocator zuulRouteDatabaseLocator() {
return newZuulRouteDatabaseLocator(this.server.getServletPrefix(), this.zuulProperties);
}
这样完整的动态路由就实现完成了。
引入consul作为注册中心
经过上面的改造后,发现在应用过程中新接入服务必须要经过人工配置,并且新服务都需要为接入网关而申请内网域名,为了解决人工配置和申请域名的人工介入,注册中心就粉墨登场了。
Consul是一种
服务网络解决方案,可跨任何运行时平台以及公共或私有云连接和保护服务。
金融网关借助于集团的云平台,在每一个业务实例所在的docker中,启动一个consul的agent进程(即consul client),这个agent会收集业务实例进程的相关信息(如:容器ip、业务进程端口等)上报给consul server集群,该agent也负责做服务的健康检查相关的工作,并且随服务一起启动,一起销毁;然后网关会通过consulserver获取服务路由信息进行路由。通过引入consul彻底解决了服务的人工配置,做到了自动化的服务发现与路由。
网关内部线程模型
目前我们使用的zuul版本为1.x,该版本中对一次请求的拦截与路由使用的是同步阻塞线程;
1.优势
首先在设计层面上架构设计简单,其次源码阅读上代码易于理解,最后是链路追踪比较方便,出现问题时易于排查。
2. 缺点
zuul内部本质上是一个同步的servlet,这样每一个请求servlet都会为其分配一个线程来处理这个请求,但是容器中的线程是有限的,一般会使用线程池,当后端服务响应缓慢时,线程资源会被持续占用,当线程被大量占用导致连接池满之后,新请求会被拒绝。
未来展望
对于网关目前存在的问题,首先在未来会基于Netty去改造金融网关;同时网关也是所有服务的入口,也会对服务的性能分析以及健康指标做一些相关的分析工作。
参考文献:
1.zuul github(https://github.com/Netflix/zuul/wiki)
2.zuul源码(https://github.com/Netflix/zuul/tree/1.x)
祝大家在2020年工作顺路,家庭幸福,合家团圆