2021-07-06

Spring Cloud 专题之四:Zuul网关

书接上回:

SpringCloud专题之一:Eureka

Spring Cloud专题之二:OpenFeign

Spring Cloud专题之三:Hystrix

经过前面三章对Spring Cloud的基本组件的介绍,我们可以构建一个简单的微服务架构系统了。比如,通过使用Spring Cloud Eureka实现高可用的服务注册中心以及实现微服务的注册与发现;通过Spring Cloud OpenFeign 实现服务间负载均衡的接口调用;同时,为了使分布式系统更为健壮,对于依赖的服务调用使用SpringCloud Hystrix来进行包装,实现线程隔离并加入熔断机制,以避免在微服务架构中因个别服务出现异常而引起级联故障蔓延。

上面的架构实现系统功能是完全没有问题,但是还可以进一步思考,这样的架构还有不足的地方会使运维人员或开发人员感到很痛苦。

​ 首先,我们从运维人员的角度来看看,他们平时都需要做一些什么工作来支持这样的架构。当客户端应用单击某个功能的时候往往会发出一些对微服务获取资源的请求到后端,这些请求通过F5、Nginx等设施的路由和负载均衡分配后,被转发到各个不同的服务实例上。而为了让这些设施能够正确路由与分发请求,运维人员需要手工维护这些路由规则与服务实例列表,当有实例增减或是地址变动等情况发生的时候,也需要手工地去同步修改这些信息以保持实例信息与中间件配置内容的一致性。在系统规模不大的时候,维护这些信息的工作还不会太过复杂,但是如果当系统规模不断增大,那么这些看似简单的维护任务会变得越来越难,并且出现配置错误的概率也会逐渐增加。很显然,这样的做法并不可取,所以我们需要一套机制来有效降低维护路由规则与服务实例列表的难度。

​ 其次,我们再从开发人员的角度来看看,在这样的架构下,会产生一些怎样的问题呢?大多数情况下,为了保证对外服务的安全性,我们在服务端实现的微服务接口,往往都会有一定的权限校验机制,比如对用户登录状态的校验等;同时为了防止客户端在发起请求时被篡改等安全方面的考虑,还会有一些签名校验的机制存在。这时候,由于使用了微服务架构的理念,我们将原本处于一个应用中的多个模块拆成了多个应用,但是这些应用提供的接口都需要这些校验逻辑,我们不得不在这些应用中都实现这样一套校验逻辑。随着微服务规模的扩大,这些校验逻辑的冗余变得越来越多,突然有一天我们发现这套校验逻辑有个BUG需要修复,或者需要对其做一些扩展和优化,此时我们就不得不去每个应用里修改这些逻辑,而这样的修改不仅会引起开发人员的抱怨,更会加重测试人员的负担。所以,我们也需要一套机制能够很好地解决微服务架构中,对于微服务接口访问时各前置校验的冗余问题。

为了解决上面的架构问题,API网关应运而生,而Spring Cloud Zuul就是Spring Colud 提供的这样的一个API网关。Zuul提供了动态路由、监控、弹性负载和安全功能。Zuul底层利用各种filter实现如下功能:

  • 认证和安全:识别每个需要认证的资源,拒绝不符合要求的请求。
  • 性能监测:在服务边界追踪并统计数据,提供精确的生产视图。
  • 动态路由:根据需要将请求动态路由到后端集群。
  • 压力测试:逐渐增加对集群的流量以了解其性能。
  • 负载卸载:预先为每种类型的请求分配容量,当请求超过容量时自动丢弃。
  • 静态资源处理:直接在边界返回某些响应。

代码实践

本次的代码实践还是在前几篇文章的代码的基础上所作的。

1.创建zuul-gateway的工程并引入依赖

<!--zuul的依赖--><dependency>	<groupId>org.springframework.cloud</groupId>	<artifactId>spring-cloud-starter-netflix-zuul</artifactId></dependency><!--eureka-client--><dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId></dependency>

2.创建应用主类,使用@EnableZuulProxy注解开启Zuul的API网关服务功能

@SpringBootApplication@EnableZuulProxypublic class ZuulGatewayApplication {	public static void main(String[] args) {		SpringApplication.run(ZuulGatewayApplication.class, args);	}}

3.在配置文件中配置Zuul应用的基础信息,这里不像之前的服务使用properties作为配置文件,而是菜用yaml作为配置(后面会讲)

server: port: 9010spring: application: name: zuul-gateway# 指定Eureka server的注册中心的位置,出来将Zuul的注册成服务之外,也让Zuul能够获取注册中心的实例清单eureka: client: service-url:  defaultZone: id="传统的路由方式">传统的路由方式

使用Zuul实现路由的功能非常简单,之需要对api-gateway服务增加关于路由规则的配置即可。

#Zuul实现的传统的路由配置zuul: routes: hello-server-url:  path: /hello-server/**  url: 这个地址上。也就是:我们在访问 src="https://img2020.cnblogs.com/blog/1459011/202107/1459011-20210706233837244-1303076482.png" alt="" loading="lazy">

这种方式直观容易理解,API网关直接根据请求的URL路径找到最匹配的path表达式,直接转发给该表达式对应的url以实现外部请求的路由。

面向服务的路由

在properties配置文件中配置路由

# Zuul面向服务的配置服务zuul: routes: api-hello-server:  path: /hello-server/**  service-id: hello-server api-customer-server:  path: /customer-server/**  service-id: customer-server

在这里分别使用了api-hello-server和pi-customer-server来映射服务提供者(hello-server)和服务消费者(customer-server)的路由。通过上面的配置方式,我们不足要再为每个路由维护微服务的具体实例的位置,而是通过path和service-id的映射,使得维护工作变得非常简单。

这种方式,整合了Eureka来实现。将API网关看作Eureka的一个应用服务,除了将自己注册到Eureka服务注册中心上之外,也会从注册中心获取所有的服务以及他们的实例清单。在Eureka的帮助下,API网关服务就已经维护了所有serviceId与实例地址的映射关系,那么只需要通过Ribbon的负载均衡策略,直接在这些清单种选择一个具体的实例进行转发就能完成路由工作了。

为啥选择yaml作为配置文件

随着版本的迭代,可能会对服务做一个功能的拆分,将原本属于hello-service的某些共鞥你拆分到了另一个全新的hello-service-ext服务中。而这些拆分的外部调用URL路径希望能够符合规则/hello-service/ext/**。所以需要做如下配置:

zuul.routes.hello-service.path=/hello-service/**zuul.routes.hello-service.serviceId=hello-servicezuul.routes.hello-service-ext.path=/hello-service/ext/**zuul.routes.hello-service-ext.serviceId=hello-service-ext

此时,调用hello-service-ext服务的 URL路径实际上会同时被/hello-service/** 和/hello-service/ext/** 两个表达式所匹配。在逻辑上,API网关服务需要优先选择/hello-service/ext/** 路由,然后再匹配/hello-service/** 路由才能实现上述需求。但是如果使用上面的配置方式,实际上是无法保证这样的路由优先顺序的。

由于properties的配置内容无法保证有序,所以为了保证路由的优先顺序,需要使用yaml文件来配置,这也是为啥配置zuul的时候要选择使用yaml作为配置文件。

请求过滤

在实现了请求路由功能之后,我们的微服务应用提供的接口就可以通过统一的API网关入口被客户端访问到了。但是每个客户端用户请求微服务应用提供的接口时,他们的访问权限往往都有一定的限制,系统并不会将所有的微服务接口都对他们开放。

为了实现对客户端请求的安全校验和权限控制,最简单的方法就是为每个微服务应用都实现一套用于检验签名和鉴别权限的过滤器或者拦截器。但是,因为同一个系统中的各种检验逻辑很多情况下都是相同或者类似的,这样做的话会出现代码冗余,后期维护异常麻烦。所以比较好的做法时将这些校验逻辑剥离出去,构建出一个独立的鉴权服务。

Zuul允许开发者在API网关上通过定义过滤器来实现对请求的拦截与过滤,实现的方法非常简单,只需要继承ZuulFilter抽象类并实现他定义的4个抽象函数就可以完成对请求的过滤和拦截了。

在这里我们实现一个简单的请求过滤功能:登录系统检验token,如果token不为空,则不可以访问。

/** * @className: LoginFilter * @description: 实现登录过滤校验 * @author: charon * @create: 2021-07-04 22:46 */public class LoginFilter extends ZuulFilter { private static Logger log = LoggerFactory.getLogger(LoginFilter.class); /**  * 过滤器的类型,它决定了过滤器在请求的那个生命周期执行,  * 主要有四种类型:  * pre: 可以在请求被路由之前调用  * routing: 在路由请求时被调用  * post: 在routing和error过滤器之后被调用  * error: 处理请求时发生错误时被调用  * @return  */ @Override public String filterType() {  return "pre"; } /**  * 过滤器的执行顺序,当请求在一个阶段中存在多个过滤器时,需要根据该方法返回的值来过滤依次执行,数值越小优先级越高  * @return  */ @Override public int filterOrder() {  return 0; } /**  * 判断该过滤器市够需要被执行  * @return  */ @Override public boolean shouldFilter() {  return true; } /**  * 过滤器的具体逻辑这里通过context.setSendZuulResponse(false);令zuul过滤该请求,不对其进行路由。  *  * @return  * @throws ZuulException  */ @Override public Object run() throws ZuulException {  RequestContext context = RequestContext.getCurrentContext();  HttpServletRequest request = context.getRequest();  Object token = request.getHeader("token");  if (Objects.isNull(token)) {   log.error("token为空,不允许访问");   context.setSendZuulResponse(false);   // 防止返回给前端时出现中文乱码   context.getResponse().setContentType("text/html;charset=utf-8");   context.setResponseStatusCode(401);   context.setResponseBody("当前状态未登录,请重新登录。");   return null;  }  log.error("token不为空,允许正常访问");  return null; }}

为自定义的过滤器创建具体的bean才能启动该过滤器。

@Beanpublic LoginFilter loginFilter(){ return new LoginFilter();}

在完成了上面的改造之后,重启服务,并使用下面两种请求对其进行验证:

源码分析

在使用zuul的时候,最主要的就是在启动类上添加@EnableZuulProxy的注解,所以我们先从注解开始看。

@EnableCircuitBreaker@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Import(ZuulProxyMarkerConfiguration.class)public @interface EnableZuulProxy {}

可以看到,这个注解类引入了ZuulProxyMarkerConfiguration这个类。跟进这个类:

@Configuration(proxyBeanMethods = false)public class ZuulProxyMarkerConfiguration {	@Bean	public Marker zuulProxyMarkerBean() {		return new Marker();	}	class Marker {	}}

发现这个类与Eureka的EurekaServerMarkerConfiguration类一样(作者是同一人),主要就是把Marker类变成了Spring的Bean。作为自动配置Zuul的开关。又了MEurekaServerMarkerConfiguration.Marker这个bean之后,Zuul代理的自动配置类(ZuulProxyAutoConfiguration)就能加载了。

在ZuulProxyAutoConfiguration这个类里注入了一些Filters。

@Bean@ConditionalOnMissingBean(PreDecorationFilter.class)public PreDecorationFilter preDecorationFilter(RouteLocator routeLocator,            ProxyRequestHelper proxyRequestHelper) { return new PreDecorationFilter(routeLocator,         this.server.getServlet().getContextPath(), this.zuulProperties,         proxyRequestHelper);}// route filters@Bean@ConditionalOnMissingBean(RibbonRoutingFilter.class)public RibbonRoutingFilter ribbonRoutingFilter(ProxyRequestHelper helper,            RibbonCommandFactory<?> ribbonCommandFactory) { RibbonRoutingFilter filter = new RibbonRoutingFilter(helper, ribbonCommandFactory,               this.requestCustomizers); return filter;}@Bean@ConditionalOnMissingBean({ SimpleHostRoutingFilter.class,       CloseableHttpClient.class })public SimpleHostRoutingFilter simpleHostRoutingFilter(ProxyRequestHelper helper,              ZuulProperties zuulProperties,              ApacheHttpClientConnectionManagerFactory connectionManagerFactory,              ApacheHttpClientFactory httpClientFactory) { return new SimpleHostRoutingFilter(helper, zuulProperties,          connectionManagerFactory, httpClientFactory);}@Bean@ConditionalOnMissingBean({ SimpleHostRoutingFilter.class })public SimpleHostRoutingFilter simpleHostRoutingFilter2(ProxyRequestHelper helper,              ZuulProperties zuulProperties, CloseableHttpClient httpClient) { return new......

原文转载:http://www.shaoqun.com/a/851306.html

跨境电商:https://www.ikjzd.com/

聚贸:https://www.ikjzd.com/w/1305

amazon go:https://www.ikjzd.com/w/67

贸发局:https://www.ikjzd.com/w/1621


书接上回:SpringCloud专题之一:EurekaSpringCloud专题之二:OpenFeignSpringCloud专题之三:Hystrix经过前面三章对SpringCloud的基本组件的介绍,我们可以构建一个简单的微服务架构系统了。比如,通过使用SpringCloudEureka实现高可用的服务注册中心以及实现微服务的注册与发现;通过SpringCloudOpenFeign实现服务间负
外贸圈:https://www.ikjzd.com/w/1083
巴克莱:https://www.ikjzd.com/w/2775
亚马逊FBA卖家如何提高利润?先用这5种方式降低成本!:https://www.ikjzd.com/articles/100996
产品被亚马逊"二次"销售,Listing 惨遭下架!:https://www.ikjzd.com/articles/100997
速卖通货物纠纷的解决办法:https://www.ikjzd.com/articles/100998
跨境电商新增试点城市,这几大省份有望入列!:https://www.ikjzd.com/articles/101002
没有穿内裤被同桌摸一天 同桌摸出水来了好爽:http://lady.shaoqun.com/a/247350.html
张柏芝自曝拍艳照原因 大爆陈冠希另类癖好:http://lady.shaoqun.com/m/a/46137.html
第一次性生活需要注意什么:http://lady.shaoqun.com/a/405816.html
射精太快是怎么造成的?这四个激励不可忽视:http://lady.shaoqun.com/a/405817.html
男性射精是高潮吗?:http://lady.shaoqun.com/a/405818.html
深圳锦绣中华7月门票价格及优惠:http://www.30bags.com/a/476640.html

No comments:

Post a Comment