微服务系列(一)聊聊服务网关
前几年随着分布式架构的演变,微服务开始兴起,自然也产生了一系列支持微服务的框架,例如本文要聊到的Spring Cloud。
Spring 相信做Java的小伙伴们已经耳熟能详了,也正是应该这个Spring生态获得广大的关注,在Spring之上开发的新兴框架如Spring Boot、Spring Cloud也很快让大家熟知。
下面主要针对Spring Cloud聊聊它所实现的服务网关,并针对市面上常用的Nginx做一些分析和比较。
由于笔者比较熟悉和擅长的语言是Java,所以仅针对Java框架来做一些源码层面的分析。
服务网关的角色
常见微服务架构
可以看到,网关层是最外层,浏览器与服务器交互时经过的第一个服务节点,它主要起屏蔽下游业务服务的作用,对于浏览器而言,只需要跟网关交互就相当于在与下游多个业务服务节点交互,让浏览器觉得他在和一台服务器交互。
这样的好处显而易见,不管是下游业务服务、支撑服务、基础服务,都对于浏览器屏蔽,与服务器的交互变的非常简单,浏览器无需关心各个节点的依赖关系、如何协同工作,浏览器只会了解到本次请求是否成功;开发者可以灵活的增加业务服务模块;可以在网关层做一些最上层的公用的操作,如过滤恶意请求、设置ip黑白名单、做身份认证、限流、负载均衡等。
换个角度考虑一下,如果去掉网关层,浏览器交互的最外层服务是业务服务层,由于需要解决单点登陆问题,必须在每个业务服务节点上多扮演一个auth client的角色,从开发的角度上看,明显增加了复杂度,试问本可以只需要在一个网关服务上构建auth client,为何要选择在多个(并且可能还会增加)的业务服务上构建auth client呢?
另外,从开发上讲可能还需要解决跨域请求的问题,前后端分离架构中后端api的展示也会是一个问题,也不便于管理;对于同一服务多节点的负载均衡也不好实现,难道需要浏览器每次访问前都去访问一次注册中心?
通过分析发现,微服务架构中,对于再小的业务量的项目,服务网关都是必不可少的。
Spring Cloud Netflix Zuul和Spring Cloud Gateway
Zuul在早期微服务架构中用的非常广泛,如今Spring Cloud推出了Spring Cloud Gateway,那么作为开发者,应该考虑以下问题:该如何选择?他们之间的差异有哪些?各有什么优势呢?
下面从源码入手,探索Zuul的工作原理,尝试理解他的设计理念。
为了图方便,就不去github上download源码了,直接在pom引入依赖,开干…
进入com.netflix.zuul.http.ZuulServlet
public class ZuulServlet extends HttpServlet {
private ZuulRunner zuulRunner;
public void init(ServletConfig config) throws ServletException {
...
}
public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException {
...
}
}
它继承了HttpServlet,熟悉吗?不熟悉的话,可以打开你熟悉的DispatchServlet,看看它继承了谁?
也就是说,它本质上用了java.servlet API,实现了一个有网关功能的servlet。
那么继续观察一下它的com.netflix.zuul.http.ZuulServlet#service
方法:
try {
init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);
// Marks this request as having passed through the "Zuul engine", as opposed to servlets
// explicitly bound in web.xml, for which requests will not have the same data attached
RequestContext context = RequestContext.getCurrentContext();
context.setZuulEngineRan();
try {
preRoute();
} catch (ZuulException e) {
error(e);
postRoute();
return;
}
try {
route();
} catch (ZuulException e) {
error(e);
postRoute();
return;
}
try {
postRoute();
} catch (ZuulException e) {
error(e);
return;
}
} catch (Throwable e) {
error(new ZuulException(e, 500, "UNHANDLED_EXCEPTION_" + e.getClass().getName()));
} finally {
RequestContext.getCurrentContext().unset();
}
可以看到,它做了以下几件事:
- 前置路由
- 路由
- 后置路由
- 异常处理
不管是preRoute()
、route()
、postRoute()
、error()
,它们最终调用了com.netflix.zuul.FilterProcessor#runFilters
public Object runFilters(String sType) throws Throwable {
if (RequestContext.getCurrentContext().debugRouting()) {