实战_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

相关文章
|
5月前
|
XML Java 测试技术
Spring5入门到实战------17、Spring5新功能 --Nullable注解和函数式注册对象。整合JUnit5单元测试框架
这篇文章介绍了Spring5框架的三个新特性:支持@Nullable注解以明确方法返回、参数和属性值可以为空;引入函数式风格的GenericApplicationContext进行对象注册和管理;以及如何整合JUnit5进行单元测试,同时讨论了JUnit4与JUnit5的整合方法,并提出了关于配置文件加载的疑问。
Spring5入门到实战------17、Spring5新功能 --Nullable注解和函数式注册对象。整合JUnit5单元测试框架
|
5月前
|
Java
Spring5入门到实战------9、AOP基本概念、底层原理、JDK动态代理实现
这篇文章是Spring5框架的实战教程,深入讲解了AOP的基本概念、如何利用动态代理实现AOP,特别是通过JDK动态代理机制在不修改源代码的情况下为业务逻辑添加新功能,降低代码耦合度,并通过具体代码示例演示了JDK动态代理的实现过程。
Spring5入门到实战------9、AOP基本概念、底层原理、JDK动态代理实现
|
3月前
|
自然语言处理 Java API
Spring Boot 接入大模型实战:通义千问赋能智能应用快速构建
【10月更文挑战第23天】在人工智能(AI)技术飞速发展的今天,大模型如通义千问(阿里云推出的生成式对话引擎)等已成为推动智能应用创新的重要力量。然而,对于许多开发者而言,如何高效、便捷地接入这些大模型并构建出功能丰富的智能应用仍是一个挑战。
369 6
|
3月前
|
缓存 NoSQL Java
Spring Boot与Redis:整合与实战
【10月更文挑战第15天】本文介绍了如何在Spring Boot项目中整合Redis,通过一个电商商品推荐系统的案例,详细展示了从添加依赖、配置连接信息到创建配置类的具体步骤。实战部分演示了如何利用Redis缓存提高系统响应速度,减少数据库访问压力,从而提升用户体验。
177 2
|
3月前
|
Java 数据库连接 Spring
【2021Spring编程实战笔记】Spring开发分享~(下)
【2021Spring编程实战笔记】Spring开发分享~(下)
37 1
|
3月前
|
XML Java 数据格式
Spring IOC容器的深度解析及实战应用
【10月更文挑战第14天】在软件工程中,随着系统规模的扩大,对象间的依赖关系变得越来越复杂,这导致了系统的高耦合度,增加了开发和维护的难度。为解决这一问题,Michael Mattson在1996年提出了IOC(Inversion of Control,控制反转)理论,旨在降低对象间的耦合度,提高系统的灵活性和可维护性。Spring框架正是基于这一理论,通过IOC容器实现了对象间的依赖注入和生命周期管理。
88 0
|
3月前
|
XML Java 数据库连接
【2020Spring编程实战笔记】Spring开发分享~(上)
【2020Spring编程实战笔记】Spring开发分享~
61 0
|
5月前
|
NoSQL Java Redis
Redis6入门到实战------ 八、Redis与Spring Boot整合
这篇文章详细介绍了如何在Spring Boot项目中整合Redis,包括在`pom.xml`中添加依赖、配置`application.properties`文件、创建配置类以及编写测试类来验证Redis的连接和基本操作。
Redis6入门到实战------ 八、Redis与Spring Boot整合
|
5月前
|
SQL 数据库
Spring5入门到实战------13、使用JdbcTemplate操作数据库(批量增删改)。具体代码+讲解 【下篇】
这篇文章是Spring5框架的实战教程,深入讲解了如何使用JdbcTemplate进行数据库的批量操作,包括批量添加、批量修改和批量删除的具体代码实现和测试过程,并通过完整的项目案例展示了如何在实际开发中应用这些技术。
Spring5入门到实战------13、使用JdbcTemplate操作数据库(批量增删改)。具体代码+讲解 【下篇】
|
5月前
|
XML Java 数据格式
Spring5入门到实战------11、使用XML方式实现AOP切面编程。具体代码+讲解
这篇文章是Spring5框架的AOP切面编程教程,通过XML配置方式,详细讲解了如何创建被增强类和增强类,如何在Spring配置文件中定义切入点和切面,以及如何将增强逻辑应用到具体方法上。文章通过具体的代码示例和测试结果,展示了使用XML配置实现AOP的过程,并强调了虽然注解开发更为便捷,但掌握XML配置也是非常重要的。
Spring5入门到实战------11、使用XML方式实现AOP切面编程。具体代码+讲解