在微服务中网关的作用:
1.接收请求——与一般访问相同
2.转发请求
3.请求过滤
网关作用图示:
从上图可以看出,网关的作用实际上是对原始的请求方式插入了一层;在请求中间加入了一层网关,这样使得外部的所有请求都导向网关,再由网关来转发给具体服务处理。
加入网关的优势:
1.请求统一:原本的请求方式可能会因为访问的服务不同,出现访问地址也不同的问题,插入网关之后,访问地址统一指向网关;对于请求方而言,只需要记住一个访问地址就可以了,省事了;
2.通用功能的统一处理:与请求相关的各种操作都可以在网关这里先完成,例如请求过滤,权限控制等功能抽取出来进行统一处理;
3.负载均衡:网关本身是对请求进行了拦截的,拿到请求后自然就可以做一些处理,比如同一服务的多次请求就可以转发给不同的服务器处理;
缺点:
1.请求链路变长:从请求参与角色来看,加入网关的情况下,请求链路就增加了一个节点,提高了请求的复杂度,比如当一个请求出现问题时,就需要额外考虑是否网关出问题了,原本没有网关的时候自然不用考虑。
2.网关成为了微服务中的单点:通常分布式服务都会尽可能地避免服务单点现象,因为单点现象很容易造成个体影响整体的情况;比如单一网关的情况下,一旦网关服务挂掉,基本整个服务就瘫痪了;为了解决这个问题,可以设置网关集群,降低网关服务不可用的概率。
1.搭建网关服务
1.1SpringBoot项目
1.首先创建一个SpringBoot微服务(后面单独写,这里就不写了)
2.引入相关依赖
<!--zuul网关依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<!--springcloud核心依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix-core</artifactId>
<version>2.0.0.M2</version>
</dependency>
3.开启网关服务——启动类上加注解@EnableZuulProxy
开启网关注解后,网关服务可以使用
@EnableEurekaClient
@EnableZuulProxy //开启Zuul的API网关服务功能
@SpringBootApplication
public class GateOfZuul {
public static void main(String[] args){
SpringApplication.run(GateOfZuul.class,args);
}
}
4.配置文件——application.yml
server:
port: 5580 #端口号
spring:
application:
name: service-zuul #服务注册中心测试名
zuul:
routes:
api-a:
path: /ribbon/**
serviceId: service-ribbon
api-b:
path: /feign/**
serviceId: service-feign
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/ #服务注册中心
上述网关配置中;涉及网关的路由转发的配置是zuul.routes下的2个
其中api-a、api-b表示的是不同的微服务,可以自定义,但最好和微服务名称保持一致;path表示的是请求访问的路径;serviceId表示的则是微服务在注册中心的注册名称;path与serviceId是一 一对应关系,表示的是“ 如果是/ribbon/**路径下的请求,则跳转到service-ribbon”
该配置的主要作用就是定义好网关的路由规则,将不同的请求路由到不同的服务中;
2.网关服务功能探究
网关存在的意义是为了提供服务,那么身为一个网关,它所应该具有的能力有哪些呢?
1.接收请求:网关最终的能力就是接收请求,然后将请求转发出去;那么首先它就要有MVC的能力,则它需要实现servlet;
2.发出请求:网关需要将请求转发到其他服务,那么它就要有发送请求的能力,则它需要实现Http相关方法;
3.过滤请求:网关提供对请求的权限、日志等操作,那么他就要有过滤请求的能力,则它需要实现filter;
4.获取服务列表:网关提供路由功能,那么它就需要获取到路由地址,从微服务的架构设置来看,即它需要从注册中心拿到服务列表;
5.路由配置:网关实现路由操作,那么就需要设置请求路径与服务的对应关系;
2.1.网关接收请求
实际项目中,对于网关的开发,我们通常只需要配置过滤器以及配置文件中的路由转发,那么网关是如何做到接收请求的呢?
既然接收请求不需要我们来做,那么就肯定是已经有人帮我们做了,由此可以推断源码中应该已经写好了相关的处理。
那么现在就来分析源码中到底是如何做到的:
Servlet初始化
要实现网关访问,则首先要配置好servlet;
SpringBoot中通过ServletRegistrationBean 这个类实现了对servlet的注册;这里可能还是沿用的springMVC的那套DispatcherServlet;至于具体怎么注册的等分析SpringBoot时再研究!
下面通过源码来说明Zuul网关如何配置访问路径的
首先Zuul网关的配置类是ZuulServerAutoConfiguration;在该类中配置了Servlet及过滤规则
ZuulServerAutoConfiguration中配置了servlet及过滤规则;该类中通过ZuulServlet对根路径/进行过滤;
@Bean
@ConditionalOnMissingBean(
name = {"zuulServlet"}
)
public ServletRegistrationBean zuulServlet() {
ServletRegistrationBean<ZuulServlet> servlet = new ServletRegistrationBean(new ZuulServlet(), new String[]{this.zuulProperties.getServletPattern()});
servlet.addInitParameter("buffer-requests", "false");
return servlet;
}
/*其中拦截规则是在zuulProperties配置的*/
public String getServletPattern() {
String path = this.servletPath;
if (!path.startsWith("/")) {
path = "/" + path;
}
if (!path.contains("*")) {
path = path.endsWith("/") ? path + "*" : path + "/*";
}
return path;
}
处理器映射器与处理器适配器为默认配置,无需处理
继续分析这个配置类,可以看到有ZuulController这个配置
@Bean
public ZuulController zuulController() {
return new ZuulController();
}
到这里可以看出,在网关这里,zuul组件将所有的请求都会走到ZuulController中,由它来统一处理;处理方法为handleRequest方法
当一个url请求访问网关时,服务器会来到ZuulController这个类中:
public class ZuulController extends ServletWrappingController {
/*从这里可以看出,zuulController在初始化时,会传入zuulServlet这个实例*/
public ZuulController() {
this.setServletClass(ZuulServlet.class);
this.setServletName("zuul");
this.setSupportedMethods((String[])null);
}
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
ModelAndView var3;
try {
var3 = super.handleRequestInternal(request, response);
} finally {
RequestContext.getCurrentContext().unset();
}
return var3;
}
}
在zuulController中,只有handleRequest这一个方法来处理请求,而该方法最终调用的是其父类ServletWrappingController中的方法
public class ServletWrappingController extends AbstractController implements BeanNameAware, InitializingBean, DisposableBean {
@Nullable
private Class<? extends Servlet> servletClass;
@Nullable
private String servletName;
private Properties initParameters = new Properties();
@Nullable
private String beanName;
@Nullable
private Servlet servletInstance;
public ServletWrappingController() {
super(false);
}
public void setServletClass(Class<? extends Servlet> servletClass) {
this.servletClass = servletClass;
}
public void setServletName(String servletName) {
this.servletName = servletName;
}
public void setInitParameters(Properties initParameters) {
this.initParameters = initParameters;
}
public void setBeanName(String name) {
this.beanName = name;
}
public void afterPropertiesSet() throws Exception {
if (this.servletClass == null) {
throw new IllegalArgumentException("'servletClass' is required");
} else {
if (this.servletName == null) {
this.servletName = this.beanName;
}
this.servletInstance = (Servlet)ReflectionUtils.accessibleConstructor(this.servletClass, new Class[0]).newInstance();
this.servletInstance.init(new ServletWrappingController.DelegatingServletConfig());
}
}
/**zuulController处理请求最终调用的是这个方法
*其中servletInstance就是ZuulController实例化时一起实例化的zuulServlet
*可以看出最终请求做到了zuulServlet的service方法中
*/
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
Assert.state(this.servletInstance != null, "No Servlet instance");
this.servletInstance.service(request, response);
return null;
}
public void destroy() {
if (this.servletInstance != null) {
this.servletInstance.destroy();
}
}
private class DelegatingServletConfig implements ServletConfig {
private DelegatingServletConfig() {
}
@Nullable
public String getServletName() {
return ServletWrappingController.this.servletName;
}
@Nullable
public ServletContext getServletContext() {
return ServletWrappingController.this.getServletContext();
}
public String getInitParameter(String paramName) {
return ServletWrappingController.this.initParameters.getProperty(paramName);
}
public Enumeration<String> getInitParameterNames() {
return ServletWrappingController.this.initParameters.keys();
}
}
}
到这里位置,url请求最终走到了ZuulServlet的service中,现在来分析url请求在zuulServlet中时如何走下去的
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
try {
this.init((HttpServletRequest)servletRequest, (HttpServletResponse)servletResponse);
RequestContext context = RequestContext.getCurrentContext();
context.setZuulEngineRan();
try {
//前置过滤器pre
this.preRoute();
} catch (ZuulException var12) {
//
this.error(var12);
this.postRoute();
return;
}
try {
// route过滤器
this.route();
} catch (ZuulException var13) {
this.error(var13);
this.postRoute();
return;
}
try {
//后置过滤器
this.postRoute();
} catch (ZuulException var11) {
//错误处理方法
this.error(var11);
}
} catch (Throwable var14) {
this.error(new ZuulException(var14, 500, "UNHANDLED_EXCEPTION_" + var14.getClass().getName()));
} finally {
RequestContext.getCurrentContext().unset();
}
}
void init(HttpServletRequest servletRequest, HttpServletResponse servletResponse) {
this.zuulRunner.init(servletRequest, servletResponse);
}
从上面代码可以看出,url请求会被init方法处理,而init方法实际调用的是zuulRunner的init方法;
之后,代码会执行preRoute(),route(),postRoute()方法,而这三个方法实际就是我们做路由以及过滤等功能的切入点。
现在来分析这3个方法是如何让我们实现过滤以及路由功能的;
首先进行代码追踪,看看这3个方法最后到底是如何运作的
/*在ZuulServlet中*/
void postRoute() throws ZuulException {
this.zuulRunner.postRoute();
}
void route() throws ZuulException {
this.zuulRunner.route();
}
void preRoute() throws ZuulException {
this.zuulRunner.preRoute();
}
void error(ZuulException e) {
RequestContext.getCurrentContext().setThrowable(e);
this.zuulRunner.error();
}
/**可以看出最终这3个方法与init方法一样,最终都走到了zuulRunner中了*/
/**在ZuulRunner中*/
public void postRoute() throws ZuulException {
FilterProcessor.getInstance().postRoute();
}
public void route() throws ZuulException {
FilterProcessor.getInstance().route();
}
public void preRoute() throws ZuulException {
FilterProcessor.getInstance().preRoute();
}
public void error() {
FilterProcessor.getInstance().error();
}
/**可以看出方法统一由FilterProcessor来处理,而FilterProcessor是一个单例模式*/
/**在FilterProcessor类中*/
/**其他几个方法基本相似,只是参数不同而已,这里就不列出来了*/
public void postRoute() throws ZuulException {
try {
this.runFilters("post");
} catch (ZuulException var2) {
throw var2;
} catch (Throwable var3) {
throw new ZuulException(var3, 500, "UNCAUGHT_EXCEPTION_IN_POST_FILTER_" + var3.getClass().getName());
}
}
/** 这个方法通过参数判断filter的不同类型,返回不同的filter集合列表;并执行相应的filter方法*/
public Object runFilters(String sType) throws Throwable {
if (RequestContext.getCurrentContext().debugRouting()) {
Debug.addRoutingDebug("Invoking {" + sType + "} type filters");
}
// 根据过滤器类型,获取过滤器列表。
boolean bResult = false;
List<ZuulFilter> list = FilterLoader.getInstance().getFiltersByType(sType);
if (list != null) {
//依次调用过滤器
for(int i = 0; i < list.size(); ++i) {
ZuulFilter zuulFilter = (ZuulFilter)list.get(i);
//过滤器处理过程
Object result = this.processZuulFilter(zuulFilter);
if (result != null && result instanceof Boolean) {
bResult |= ((Boolean)result).booleanValue();
}
}
}
return bResult;
}
/**FilterLoader类是用来加载filter的*/
public List<ZuulFilter> getFiltersByType(String filterType) {
List<ZuulFilter> list = (List)this.hashFiltersByType.get(filterType);
if (list != null) {
return list;
} else {
//获取所有的过滤器;从filterRegistry
List<ZuulFilter> list = new ArrayList();
Collection<ZuulFilter> filters = this.filterRegistry.getAllFilters();
Iterator iterator = filters.iterator();
while(iterator.hasNext()) {
ZuulFilter filter = (ZuulFilter)iterator.next();
//按照类型提取过滤器
if (filter.filterType().equals(filterType)) {
list.add(filter);
}
}
//过滤器重排序,按照过滤器filterOrder中定义的值
Collections.sort(list);
this.hashFiltersByType.putIfAbsent(filterType, list); return list;
}
}
/**FilterRegistry类————该类是用来存放filter的,通过spring,系统中的filter最终都会存放在这里*/
/**从这里可以看出,只有实现了ZuulFilter的过滤器才会被调用*/
private final ConcurrentHashMap<String, ZuulFilter> filters = new ConcurrentHashMap();
public Collection<ZuulFilter> getAllFilters() {
return this.filters.values();
}
自定义的filter的方法执行是在FilterProcessor类中的processZuulFilter方法中执行的
public Object processZuulFilter(ZuulFilter filter) throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
boolean bDebug = ctx.debugRouting();
String metricPrefix = "zuul.filter-";
long execTime = 0L;
String filterName = "";
try {
long ltime = System.currentTimeMillis();
filterName = filter.getClass().getSimpleName();
RequestContext copy = null;
Object o = null;
Throwable t = null;
if (bDebug) {
Debug.addRoutingDebug("Filter " + filter.filterType() + " " + filter.filterOrder() + " " + filterName);
copy = ctx.copy();
}
// 本方法的核心代码就在这里,运行filter
ZuulFilterResult result = filter.runFilter();
ExecutionStatus s = result.getStatus();
execTime = System.currentTimeMillis() - ltime;
switch(s) {
case FAILED:
t = result.getException();
ctx.addFilterExecutionSummary(filterName, ExecutionStatus.FAILED.name(), execTime);
break;
case SUCCESS:
o = result.getResult();
ctx.addFilterExecutionSummary(filterName, ExecutionStatus.SUCCESS.name(), execTime);
if (bDebug) {
Debug.addRoutingDebug("Filter {" + filterName + " TYPE:" + filter.filterType() + " ORDER:" + filter.filterOrder() + "} Execution time = " + execTime + "ms");
Debug.compareContextState(filterName, copy);
}
}
if (t != null) {
throw t;
} else {
this.usageNotifier.notify(filter, s);
return o;
}
} catch (Throwable var15) {
if (bDebug) {
Debug.addRoutingDebug("Running Filter failed " + filterName + " type:" + filter.filterType() + " order:" + filter.filterOrder() + " " + var15.getMessage());
}
this.usageNotifier.notify(filter, ExecutionStatus.FAILED);
if (var15 instanceof ZuulException) {
throw (ZuulException)var15;
} else {
ZuulException ex = new ZuulException(var15, "Filter threw Exception", 500, filter.filterType() + ":" + filterName);
ctx.addFilterExecutionSummary(filterName, ExecutionStatus.FAILED.name(), execTime);
throw ex;
}
}
}
/**ZuulFilter类中的runFilter()方法*/
public ZuulFilterResult runFilter() {
ZuulFilterResult zr = new ZuulFilterResult();
if (!this.isFilterDisabled()) {
// 判断过滤器是否需要执行
if (this.shouldFilter()) {
Tracer t = TracerFactory.instance().startMicroTracer("ZUUL::" + this.getClass().getSimpleName());
try {
//执行自定义过滤器中的run方法
Object res = this.run();
zr = new ZuulFilterResult(res, ExecutionStatus.SUCCESS);
} catch (Throwable var7) {
t.setName("ZUUL::" + this.getClass().getSimpleName() + " failed");
zr = new ZuulFilterResult(ExecutionStatus.FAILED);
zr.setException(var7);
} finally {
t.stopAndLog();
}
} else {
zr = new ZuulFilterResult(ExecutionStatus.SKIPPED);
}
}
return zr;
}
processZuulFilter(ZuulFilter filter)
过滤器的分析就到此为止,现在来看看url在ZuulRunner类中是如何处理的
/**在ZuulRunner类中*/
/**在该方法中,url的请求与相应被封装在了RequestContext中
* 其中RequestContext是一个单例的,且使用ThreadLocal进行处理过,能够保证是线程安全的
*
*/
public void init(HttpServletRequest servletRequest, HttpServletResponse servletResponse) {
RequestContext ctx = RequestContext.getCurrentContext();
if (this.bufferRequests) {
ctx.setRequest(new HttpServletRequestWrapper(servletRequest));
} else {
ctx.setRequest(servletRequest);
}
ctx.setResponse(new HttpServletResponseWrapper(servletResponse));
}
2.2路由配置
第一步:解析配置文件,将路由配置读取到程序中