实战_Spring_Cloud(3)

简介: 实战_Spring_Cloud

实战_Spring_Cloud(2)https://developer.aliyun.com/article/1543983

微服务网关(Zuul)

Zuul是Netflix开源的微服务网关,可以和Eureka、Ribbon、Hystrix等组件配合使用,Spring Cloud对Zuul进行了整合与增强,Zuul默认使用的HTTP客户端是Apache HTTPClient,也可以使用RestClient或okhttp3.OkHttpClient。 Zuul的主要功能是路由转发和过滤器。zuul默认和Ribbon结合实现了负载均衡的功能

工作原理

zuul的核心是一系列的filters, 其作用类比Servlet框架的Filter,或者AOP。zuul把请求路由到用户处理逻辑的过程中,这些filter参与一些过滤处理,比如Authentication,Load Shedding等

Zuul使用一系列不同类型的过滤器,使我们能够快速灵活地将功能应用于我们的边缘服务。这些过滤器可帮助我们执行以下功能:

  • 身份验证和安全性 - 确定每个资源的身份验证要求并拒绝不满足这些要求的请求
  • 洞察和监控 - 在边缘跟踪有意义的数据和统计数据,以便为我们提供准确的生产视图
  • 动态路由 - 根据需要动态地将请求路由到不同的后端群集
  • 压力测试 - 逐渐增加群集的流量以衡量性能。
  • Load Shedding - 为每种类型的请求分配容量并删除超过限制的请求
  • 静态响应处理 - 直接在边缘构建一些响应,而不是将它们转发到内部集群

添加网关

  • 新建api-gateway子模块,作为服务网关、服务发现客户端、获取配置客户端,因此需要引入以下依赖。
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-config-client</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
    </dependency>
</dependencies>
  • 在启动类上增加EnableDiscoveryClient@EnableZuulProxy注解。
@EnableDiscoveryClient
@EnableZuulProxy
@SpringBootApplication
public class ApiGatewayApplication {
 
    public static void main(String[] args) {
        SpringApplication.run(ApiGatewayApplication.class, args);
    }
 
}
  • 启动服务,看看是否能正常获取配置,并注册到Eureka Server。
  • Zuul网关目前暴露的端口是8080,之前我们访问商品服务的api,是通过调用 http://localhost:11100/api/v1/product/productInfos来访问的,现在我们就可以通过Zuul,根据商品的服务名称shopping-produc来访问 http://localhost:8080/shopping-product//api/v1/product/productInfos,非常轻松的实现了路由的功能。

自定义路由

默认的路由规则是按照服务的名称来路由服务,当然我们也可以自定义。在zuul中,路由匹配的路径表达式采用ant风格定义

通配符 说明
匹配任意单个字符
* 匹配任意数量的字符
** 匹配任意数量的字符,支持多级目录
zuul:
  routes:
    # 简洁写法
    shopping-product: /product/**
  • 将命名为product的所有路径都映射到shopping-product服务中去,然后通过product名称来访问,依旧能访问成功。
  • 如果我们需要查看目前所有的路径映射呢,首先得引入actuator
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
  • 其次,需要放开actuator维护端口的权限
management:
  endpoints:
    web:
      exposure:
        include: "*"
  • 访问 http://localhost:8080/actuator/routes ,可以看到目前网关的所有路由映射
  • 如果需要定义哪些方法不能通过网关调用,还可以设置排除哪些路由的规则
zuul:
  routes:
    # 简洁写法
    shopping-product: /product/**
  # 排除某些路由
  ignored-patterns:
    - /**/productInfos

这样我们再访问这个接口时,就提示 Not Found 错误了

Cookie与头信息

默认情况下,spring cloud zuul在请求路由时,会过滤掉http请求头信息中一些敏感信息,防止它们被传递到下游的外部服务器。默认的敏感头信息通过zuul.sensitiveHeaders参数定义,默认包括cookie,set-Cookie,authorization三个属性。所以,我们在开发web项目时常用的cookie在spring cloud zuul网关中默认时不传递的,这就会引发一个常见的问题,如果我们要将使用了spring security,shiro等安全框架构建的web应用通过spring cloud zuul构建的网关来进行路由时,由于cookie信息无法传递,我们的web应用将无法实现登录和鉴权。有时候,针对某些路由,我们需要传递这个cookie。

zuul:
  routes:
    # 完全写法
    product-route:
      path: /product/**
      serviceId: shopping-product
      # 将指定路由的敏感头设置为空
      sensitiveHeaders:

动态路由

之前路由的配置都是写在配置文件中,如果路由规则变化以后,需要重启网关服务。但是实际生产环境,一般都需要动态的加载路由的配置,不能轻易重启网关服务。

  • 将配置信息集中到统一配置中心服务进行管理,具体实施参考前面章节-统一配置中心。
eureka:
  client:
    serviceUrl:
      defaultZone: http://eureka1:8761/eureka/,http://eureka2:8762/eureka/ #指定服务注册地址
 
spring:
  application:
    name: api-gateway  #应用名称
  cloud:
    config:
      discovery:
        enabled: true
        service-id: config-server
  • 将zuul配置属性定义成支持动态刷新,增加@RefreshScope注解
@Component
public class ZuulConfiguration {
 
    @ConfigurationProperties("zuul")
    @RefreshScope
    public ZuulProperties zuulProperties(){
        return new ZuulProperties();
    }
}

自定义Filter

设想以下场景:我们需要判断用户请求的参数是否包含认证信息,如果包含token信息,则可以访问,否则禁止访问。可以用Zuul Filter很方便的实现在网关端,统一进行认证。

  • 新建自定义的Filter,并继承ZuulFilter,默认需要实现4个接口
  • filterType():返回 filter 的类型,设置为PRE_TYPE
  • filterOrder():返回 filter 的顺序,设置为PRE_DECORATION_FILTER_ORDER-1
  • shouldFilter():是否启用 filter,设置为true
  • run():执行具体的过滤器逻辑
/**
 * 验证token 过滤器
 */
@Component
public class TokenFilter  extends ZuulFilter {
    @Override
    public String filterType() {
        return PRE_TYPE;
    }
 
    @Override
    public int filterOrder() {
        return 0;
    }
 
    @Override
    public boolean shouldFilter() {
        return true;
    }
 
    @Override
    public Object run() throws ZuulException {
 
        RequestContext currentContext = RequestContext.getCurrentContext();
        HttpServletRequest request = currentContext.getRequest();
 
        //测试在url参数中获取token
        String token = request.getParameter("token");
        if(StringUtils.isEmpty(token)){
            currentContext.setSendZuulResponse(false);
            currentContext.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
        }
 
        return null;
    }
}
  • 验证结果,如果url中添加了 token 参数,TokenFilter 验证通过,正确返回结果;如果没有 token 参数,则返回 401(UNAUTHORIZED)错误
  • 还可以在调用接口返回中,设置响应头
@Component
public class AddResHeaderFilter extends ZuulFilter{
    @Override
    public String filterType() {
        return POST_TYPE;
    }
 
    @Override
    public int filterOrder() {
        return SEND_RESPONSE_FILTER_ORDER - 1;
    }
 
    @Override
    public boolean shouldFilter() {
        return true;
    }
 
    @Override
    public Object run() {
        RequestContext requestContext = RequestContext.getCurrentContext();
        HttpServletResponse response = requestContext.getResponse();
        response.setHeader("X-Foo", UUID.randomUUID().toString());
        return null;
    }
}

限流

这里介绍一种限流的设计方案:

对于很多应用场景来说,除了要求能够限制数据的平均传输速率外,还要求允许某种程度的突发传输。这时候漏桶算法可能就不合适了,令牌桶算法更为适合。如图所示,令牌桶算法的原理是系统会以一个恒定的速度往桶里放入令牌,而如果请求需要被处理,则需要先从桶里获取一个令牌,当桶里没有令牌可取时,则拒绝服务。

Google公司已经实现了上述的令牌桶的算法,直接使用 RateLimiter 就可以通过Zuul实现限流的功能:

@Component
public class RateLimitFilter extends ZuulFilter {
 
    private static final RateLimiter RATE_LIMITER = RateLimiter.create(100);
 
 
    @Override
    public String filterType() {
        return PRE_TYPE;
    }
 
 
    @Override
    public int filterOrder() {
        return SERVLET_DETECTION_FILTER_ORDER - 1;
    }
 
    @Override
    public boolean shouldFilter() {
        return true;
    }
 
 
    @Override
    public Object run() {
        if (!RATE_LIMITER.tryAcquire()) {
            throw new RuntimeException("未能获取到令牌.");
        }
 
        return null;
    }
}

小结

整体项目结构如下:

spring-cloud-app

--api-gateway(服务网关)

--config-server(统一配置中心)

--eureka-server(服务注册中心)

--shopping-common(购物公共模块)

--shopping-product(商品服务模块)

--shopping-order(订单服务模块)

目前所有的客户端请求,首先被发送到统一网关服务处理,然后由网关进行限流、熔断、权限验证、记录日志等等,然后根据自定义的路由规则,再分发到不同的应用服务中去,应用服务器返回处理结果后,由网关统一返回给客户端。

服务容错(Hystrix

在分布式环境中,许多服务依赖项中的一些必然会失败。Hystrix是一个库,通过添加延迟容忍和容错逻辑,帮助你控制这些分布式服务之间的交互。Hystrix通过隔离服务之间的访问点、停止级联失败和提供回退选项来实现这一点,所有这些都可以提高系统的整体弹性。

设计原则

  • 防止任何单个依赖项耗尽所有容器(如Tomcat)用户线程。
  • 甩掉包袱,快速失败而不是排队。
  • 在任何可行的地方提供回退,以保护用户不受失败的影响。
  • 使用隔离技术(如隔离板、泳道和断路器模式)来限制任何一个依赖项的影响。
  • 通过近实时的度量、监视和警报来优化发现时间。
  • 通过配置的低延迟传播来优化恢复时间。
  • 支持对Hystrix的大多数方面的动态属性更改,允许使用低延迟反馈循环进行实时操作修改。
  • 避免在整个依赖客户端执行中出现故障,而不仅仅是在网络流量中。

如何实现

  1. 用一个HystrixCommand 或者 HystrixObservableCommand (这是命令模式的一个例子)包装所有的对外部系统(或者依赖)的调用,典型地它们在一个单独的线程中执行
  2. 调用超时时间比你自己定义的阈值要长。有一个默认值,对于大多数的依赖项你是可以自定义超时时间的。
  3. 为每个依赖项维护一个小的线程池(或信号量);如果线程池满了,那么该依赖性将会立即拒绝请求,而不是排队。
  4. 调用的结果有这么几种:成功、失败(客户端抛出异常)、超时、拒绝。
  5. 在一段时间内,如果服务的错误百分比超过了一个阈值,就会触发一个断路器来停止对特定服务的所有请求,无论是手动的还是自动的。
  6. 当请求失败、被拒绝、超时或短路时,执行回退逻辑。
  7. 近实时监控指标和配置变化。

触发降级

在实际工作中,尤其是分布式、微服务越来越普遍的今天,一个服务经常需要调用其他的服务,即RPC调用,而调用最多的方式还是通过http请求进行调用,这里面就有一个问题了,如果调用过程中,因为网络等原因,造成某个服务调用超时,如果没有熔断机制,此处的调用链路将会一直阻塞在这里,在高并发的环境下,如果许多个请求都卡在这里的话,服务器不得不为此分配更多的线程来处理源源不断涌入的请求。

更恐怖的是,如果这是一个多级调用,即此处的服务的调用结果还被其他服务调用了,这就形成了所谓的雪崩效应,后果将不堪设想。因此,需要某种机制,在一定的异常接口调用出现的时候,能够自动发现这种异常,并快速进行服务降级。

  • 首先,shopping-order项目模拟一个远程调用shopping-product服务http请求
/**
 * Hystrix 测试
 */
@RestController
@RequestMapping("api/hystrix")
public class HystrixController {
 
    @GetMapping("/getProductEnv")
    public String getProductEnv() {
 
        RestTemplate restTemplate = new RestTemplate();
        return restTemplate.postForObject("http://localhost:11100/api/env", null, String.class);
 
    }
}
  • 如果此时将shopping-product服务关闭,则shopping-order调用远程服务不可用,进入等待,超时时返回 Error Page的错误页面。其实我们希望服务不可用的时候直接处理,返回通知服务的不可用状态。可以引入Hystrix。
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
  • 在启动类上增加@EnableCircuitBreaker注解,或者将@SpringBootApplication@EnableDiscoveryClient@EnableCircuitBreaker 三个合并成一个@SpringCloudApplication注解。
@EnableFeignClients(basePackages = "tech.lancelot.shoppingorder.client")
//@SpringBootApplication
//@EnableDiscoveryClient
//@EnableCircuitBreaker
@SpringCloudApplication
public class ShoppingOrderApplication {
 
    public static void main(String[] args) {
        SpringApplication.run(ShoppingOrderApplication.class, args);
    }
}
  • 修改 HystrixController,增加@HystrixCommand注解,并指定调用方法失败时的错误处理回调。也可以为整个类增加@DefaultProperties注解,定义一个默认的返回方法
/**
 * Hystrix 测试
 */
@RestController
@RequestMapping("api/hystrix")
public class HystrixController {
 
    @HystrixCommand(fallbackMethod = "defaultFallback")
    @GetMapping("/getProductEnv")
    public String getProductEnv() {
 
        RestTemplate restTemplate = new RestTemplate();
        return restTemplate.postForObject("http://localhost:11100/api/env", null, String.class);
 
    }
 
    // 默认服务不可达的返回信息
    private String defaultFallback() {
        return "太拥挤了, 请稍后再试~~";
    }
}
  • 重启启动后,再次访问接口,就会发现接口直接返回错误信息,不会阻塞在这里。

超时设置

如果我们没有配置默认的超时时间,Hystrix 将取 default_executionTimeoutInMilliseconds(1秒)作为默认超时时间,也可以自定义超时时间。

  • 代码中修改默认超时配置(改为3秒):
@HystrixCommand(commandProperties = {
    @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "3000")})

这样的话,shopping-order调用远程服务,超过3s之后,立刻返回错误处理,不会再阻塞。

  • 可以在配置文件中定义HystrixCommand属性
hystrix:
  command:
    default:        # 方法默认属性
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 1000
    getProductEnv:  # 该名称方法属性
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 3000

熔断机制

如果某个目标服务调用慢或者有大量超时,此时,熔断该服务的调用,对于后续调用请求,不在继续调用目标服务,直接返回,快速释放资源。如果目标服务情况好转则恢复调用。

熔断器有三个状态 CLOSEDOPENHALF_OPEN 熔断器默认关闭状态,当触发熔断(至少有 circuitBreaker.requestVolumeThreshold 个请求,错误率达到 circuitBreaker.errorThresholdPercentage)后状态变更为 OPEN,在等待到指定的时间(circuitBreaker.sleepWindowInMilliseconds),Hystrix会放请求检测服务是否开启,这期间熔断器会变为HALF_OPEN 半开启状态,熔断探测服务可用则继续变更为 CLOSED关闭熔断器。

  • 在方法上增加熔断属性的相关设置
@HystrixCommand(commandProperties = {
    @HystrixProperty(name = "circuitBreaker.enabled", value = "true"), //设置熔断
    @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),//请求数达到后才计算
    @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000"), //休眠时间窗
    @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60"),    //错误率
})

实战_Spring_Cloud(4)https://developer.aliyun.com/article/1543990

相关文章
|
7天前
|
存储 Java Maven
Spring Boot WebFlux 增删改查完整实战 demo
Spring Boot WebFlux 增删改查完整实战 demo
|
1月前
|
Java 应用服务中间件 测试技术
深入探索Spring Boot Web应用源码及实战应用
【5月更文挑战第11天】本文将详细解析Spring Boot Web应用的源码架构,并通过一个实际案例,展示如何构建一个基于Spring Boot的Web应用。本文旨在帮助读者更好地理解Spring Boot的内部工作机制,以及如何利用这些机制优化自己的Web应用开发。
54 3
|
1月前
|
安全 Java 开发者
深入理解Spring Boot配置绑定及其实战应用
【4月更文挑战第10天】本文详细探讨了Spring Boot中配置绑定的核心概念,并结合实战示例,展示了如何在项目中有效地使用这些技术来管理和绑定配置属性。
29 1
|
1月前
|
Java 关系型数据库 数据库
Spring Boot多数据源及事务管理:概念与实战
【4月更文挑战第29天】在复杂的企业级应用中,经常需要访问和管理多个数据源。Spring Boot通过灵活的配置和强大的框架支持,可以轻松实现多数据源的整合及事务管理。本篇博客将探讨如何在Spring Boot中配置多数据源,并详细介绍事务管理的策略和实践。
93 3
|
1月前
|
安全 Java 测试技术
Spring Boot集成支付宝支付:概念与实战
【4月更文挑战第29天】在电子商务和在线业务应用中,集成有效且安全的支付解决方案是至关重要的。支付宝作为中国领先的支付服务提供商,其支付功能的集成可以显著提升用户体验。本篇博客将详细介绍如何在Spring Boot应用中集成支付宝支付功能,并提供一个实战示例。
113 2
|
4天前
|
JSON 安全 Java
Spring Security 6.x 微信公众平台OAuth2授权实战
上一篇介绍了OAuth2协议的基本原理,以及Spring Security框架中自带的OAuth2客户端GitHub的实现细节,本篇以微信公众号网页授权登录为目的,介绍如何在原框架基础上定制开发OAuth2客户端。
24 4
Spring Security 6.x 微信公众平台OAuth2授权实战
|
6天前
|
存储 数据可视化 关系型数据库
|
6天前
|
消息中间件 Java Kafka
|
6天前
|
负载均衡 Java API
|
1月前
|
开发框架 监控 Java
深入探索Spring Boot的监控、管理和测试功能及实战应用
【5月更文挑战第14天】Spring Boot是一个快速开发框架,提供了一系列的功能模块,包括监控、管理和测试等。本文将深入探讨Spring Boot中监控、管理和测试功能的原理与应用,并提供实际应用场景的示例。
35 2