微服务实践之SpringCloud

前言

 微服务架构是分布式服务的一种实施方案,近几年非常流行,关于微服务没有一个固定的标准,支撑微服务架构的各种组件,如服务注册和配置中心、服务网关、分布式事务、断路器等也都在不断更新迭代,当然并不是所有的软件项目都适合使用微服务,微服务架构的优点很多,缺点也不能忽略。本文将全面学习和讲解如何使用 Spring Cloud 的众多套件构建分布式微服务。

微服务

​ SOA基于面向服务的思想,将可重用功能抽取 为组件,以服务的方式进行调用,项目与服务之间采 用企业服务总线(ESB)进行通信。由于ESB在SOA 中仍处于核心位置,这种中心化致使SOA 难以实现 真正的面向服务和组件化[8]。

​ 而微服务是一组功能 职责单一、低内聚高耦合的服务组件化解决方案,组 件之间通过轻量级的通信机制进行通信,组件由注册 中心统一管理服务列表,通过服务调用的方式向其他 组件开放服务地址和服务功能。

​ 相比于SOA,微服 务架构强调去 ESB,去中心化,具有更大的灵活性, 其划分的细粒度更小,可重用性更高,数据独立存储。 基于微服务的 系统将系统功能微服务化, 通过 WEB API向外部提供服务。不同的微服务具 有平台无关性,微服务的调用与位置、编程语言、系统 配置无关,具有高度的可重用性,易于对业务功能与 模块水平垂直拆分、切割。

概念

 微服务是一种架构模式,它倡导将单一应用程序划分为多个小服务,每个服务运行在独立的进程中,服务于服务之间采用轻量级的通信机制互相协调和配合(通常是基于HTTP协议的RESTful API),每个服务都围绕着具体的业务进行构建。

 微服务架构中除了包含基本的业务服务外,还包含有很多中间件服务,如服务注册中心、配置中心 、网关、缓存、分布式事务、消息中间件等。

微服务架构

优点和缺点

优点:

  • 每个微服务足够内聚,足够小,业务代码容易理解和维护;
  • 开发简单,开发效率高;
  • 微服务是松耦合的;
  • 微服务可以使用不同的语言开发;

缺点:

  • 要额外处理分布式系统带来的复杂性;
  • 服务的运维成本增大;

Spring Cloud

 开发分布式服务本身具有不小的挑战性,复杂性已从应用程序层转移到网络层面,并要求服务之间进行更大的交互。Spring Cloud 为开发者提供了各类组件来快速的构建分布式系统。(服务发现、配置管理、网关路由、断路器、控制总线、全局锁、领导选举、分布式会话、集群状态)

 官网:https://spring.io/cloud

版本及兼容

Spring Cloud 在 2014年发布第一个版本,Spring Cloud 基于SpringBoot 进行开发, 因此与SpringBoot 有版本对应关系:

Release Train Version Boot Version
2022.0.3 3.0.7
2021.0.7 2.6.x
2020.0.6 2.4.x, 2.5.x
Hoxton 2.2.x, 2.3.x (Starting with SR5)
Greenwich 2.1.x
Finchley 2.0.x
Edgware 1.5.x

 微服务架构中会使用很多分布式组件,Spring Cloud 的工作更多的是组件的整合搬运,Spring Cloud 前期的大部分组件都是 Netflix 公司开发的。

 Netflix公司是早期微服务落地中最成功的公司。它开源了诸如Eureka、Hystrix、Zuul、Feign、Ribbon等等广大开发者所知微服务套件,统称为Netflix OSS。在当时Netflix OSS成为微服务组件上事实的标准。但是微服务兴起不久,也就是在 2018 年前后Netflix公司宣布其核心组件Hystrix、Ribbon、Zuul、Eureka等进入维护状态,不再进行新特性开发,只修 BUG。

 2019 年的 SpringOne 2019 大会中,Spring Cloud宣布 Spring Cloud Netflix 项目进入维护模式,并在 2020 年移除相关的Netflix OSS组件。

 后来国内的阿里巴巴公司也陆续开源自家的微服务组件套件,如配置中心和服务发现Nacos、服务治理Sentinel、服务调用Dubbo等组件,影响力日渐增大,在Spring Cloud 官网可以看到Spring Cloud Alibaba 相关的组件。

注:Spring Cloud 前期的版本号并不是用常规的数字来表示,而是用英文地名(英国伦敦火车站)作为版本号。

整体架构

至少有三个项目组成

  • 服务提供者
  • 服务消费者
  • 注册中心(服务发现与注册)
image-20210211084328683

主要组件

SpringCloud 核心技术栈如下:

组件 SpringCloud SpringCloud Netflix SpringCloud Alibaba 其它
注册中心 Service Registry
Service Discovery
Eureka Nacos Zookeeper
Consul
配置中心 SpringCloud Config Archaius Nacos Zookeeper
Consul
服务调用 SpringCloud OpenFeign
RestTemplate
Feign Dubbo
负载均衡 SpringCloud LoadBalancer Ribbon Dubbo
服务网关 SpringCloud Gateway Zuul Nginx + Lua
服务容错 SpringCloud Circuit Breaker Hystrix Sentinel
链路追踪 SpringCloud Sleuth Zipkin
分布式事务 Seata

注:Spring Cloud Alibaba 版本说明

服务发现

 服务发现也叫注册中心,目前流行的解决方案有 Alibaba NacosSpring Cloud Consul、Netflix Eureka、ZooKeeper、etcd等。

 在真实的系统里,注册中心的地位是特殊的,不能为完全视其为一个普通的服务。注册中心不依赖其他服务,但被所有其他服务共同依赖,是系统中最基础的服务。(现在服务发现框架也开始同时提供配置中心的功能,以避免配置中心又去专门摆弄出一集群的节点来)

 这意味着服务注册中心一旦崩溃,整个系统都不再可用,因此,必须尽最大努力保证服务发现的可用性。实际用于生产的分布式系统,服务注册中心都是以集群的方式进行部署的,通常使用三个或者五个节点来保证高可用。

Nacos Discovery

阿里巴巴公司开源的服务注册中心和配置中心。

官网地址: https://nacos.io/zh-cn/

  • 通过 Nacos Server 和 spring-cloud-starter-alibaba-nacos-config 实现配置的动态变更。
  • 通过 Nacos Server 和 spring-cloud-starter-alibaba-nacos-discovery 实现服务的注册与发现。

版本

时间 版本
2019-04 1.0.0
2020-08 1.3.2
2021-01 1.4.1
2022-08 2.1.1

Nacos 支持AP 和CP 模式的切换

服务发现

image-20210209215149292

文档:https://github.com/alibaba/spring-cloud-alibaba/wiki/Nacos-discovery

Nacos 中已经集成了 Netflix 的 Ribbon:

image-20210208180650892

在 Nacos 中使用 Ribbon 非常简单,同样是使用 @LoadBalanced 注解:

@Configuration
public class RestTemplateConfig {

    @LoadBalanced
    @Bean
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }
}

注意:

  @LoadBalanced 注解生效后,restTemplate 不能再继续使用 ip:port 的形式访问其他服务,如169.254.247.135:9201,127.0.0.1:9201;否则报错:

java.lang.IllegalStateException: No instances available for 127.0.0.1

只能使用服务名的方式访问:

@RestController
@RequestMapping("/purchase")
public class PurchaseController {

    @Resource
    private RestTemplate restTemplate;
    
    @PostMapping("/updatePurchase/{goods}/{purchaseNum}")
    public String addOrder(@PathVariable String goods,@PathVariable Integer purchaseNum){
        //修改采购信息  TODO
        //更新订单
        restTemplate.postForObject(
            "http://jmember-order/order/updateOrder/"+goods+"/"+purchaseNum, null, String.class);
        //更新库存 TODO
        return "success";
    }
}
image-20210208194629858

服务配置

官方文档:https://github.com/alibaba/spring-cloud-alibaba/wiki/Nacos-config

命名空间

命名空间 Namespace 用于隔离数据,用于区分环境 测试,开发,生产环境

默认情况下:

Namespace = public
Group = DEFAULT_GROUP 

配置持久化方案

 Nacos 是⼀个需要存储数据的组件,Nacos 默认使用内嵌的数据库 Apache Derby存储数据,Derby 是使用Java编写的数据库,非常小巧。默认启动日志如下:

INFO Nacos started successfully in stand alone mode. use embedded storage

修改Nacos 的持久化方案非常简单,只需要两步:

  1. 初始化mysql数据库,数据库初始化文件:nacos-mysql.sql (官方提供
  2. 修改conf/application.properties文件,增加支持mysql数据源配置,添加mysql数据源的url、用户名和密码。

再次启动 Nacos,启动日志如下说明配置生效:

INFO Nacos started successfully in stand alone mode. use external storage

数据一致性协议

 Nacos 会在单个集群中同时运行 CP 协议以及 AP 协议,Nacos 是⼀个集服务注册发现以及配置管理于⼀体的组件,因此对于集群下,各个节点之间 的数据⼀致性保障问题,需要拆分成两个方面:

从服务注册发现来看(最终一致性和强一致性并施)

 前面提到,服务发现注册中心是分布式系统中最重要的一个组件,它的不可用会导致整个系统不可用,因此对于服务注册发现中心组件的可用性,提出了很高的要求,需要在任何场景下,尽最大可能保证服务注册发现能力可以对外提供服务;

 因此,为了满足服务发现注册中心的可用性,强⼀致性的共识算法这里就不太合适了,因为强⼀致性共识算法能否对外提供服务是有要求的,如果当前集群可用的节点数没有过半的话,整个算法直接“罢工”,而最终⼀致共识算法的话,更多保障服务的可用性,并且能够保证在⼀定的时间内各个节点之间的数据能够达成⼀致。

 上述的都是针对于 Nacos 服务发现注册中的非持久化服务而言(即需要客户端上报心跳进行服务实例续约)。而对于 Nacos 服务发现注册中的持久化服务,因为所有的数据都是直接使用调用 Nacos 服务端直接创建,因此需要由 Nacos 保障数据在各个节点之间的强⼀致性,故而针对此类型的服务数据,选择了强⼀致性共识算法来保障数据的⼀致性。

从配置管理来看 (强一致性共识算法)

 配置数据,是直接在 Nacos 服务端进行创建并进行管理的,必须保证大部分的节点都保存了此配置数据才能认为配置被成功保存了,否则就会丢失配置的变更,如果出现这种情况,问题是很严重的,如果是发布重要配置变更出现了丢失变更动作的情况,那多半就要引起严重的现网故障了,因此对于配置数据的管理,是必须要求集群中大部分的节点是强⼀致的,而这里的话只能使用强⼀致性共识算法。

Raft 和 Distro

 在一致性算法实现方面,Nacos 采用了Raft 实现强一致性共识算法,采用自研的 Distro 实现最终一致性共识算法。

 Distro 协议是阿里巴巴自研的⼀个最终⼀致性协议,而最终⼀致性协议有很多,比如 Gossip、Eureka 内的数据同步算法。而 Distro 算法是集 Gossip 以及 Eureka 协议的优点并加以优化而出来的。

集群部署

直接执行启动命令,Nacos 会以集群模式启动,如果只想单机部署,需要加入参数:

# linux
sh startup.sh -m standalone
# windows
startup.cmd -m standalone

注册中心在生产环境下一般都会采用集群部署,保证高可用,3个及以上Nacos节点才能构成集群。

# Clone 项目
git clone https://github.com/nacos-group/nacos-docker.git
cd nacos-docker
# 集群模式:3台nacos,一台mysql5.7
docker-compose -f example/cluster-hostname.yaml up

其他替代方案

Eureka

 Eureka 是 Netflix 公司2014年开源的服务注册中心组件 。Eureka Server 本身也是个Java Web项目,Eureka client 通过jar包的形式引入各服务中。

数据一致性设计

 Eureka的设计原则是AP,即可用性和分区容错性。他保证了注册中心的可用性,但舍弃了数据一致性,各节点上的数据有可能是不一致的(会最终一致)。

停止更新

 目前 Eureka 已经停止更新, 如果继续使用 Eureka 2.0, 官方称后果自负

Zookeeper

统一配置管理、统一命名服务、分布式锁、集群管理。

数据一致性设计

 ZK的设计原则是CP,即强一致性和分区容错性。他保证数据的强一致性,但舍弃了可用性,如果出现网络问题可能会影响ZK的选举,导致ZK注册中心的不可用。

Consul

 Consul 是 HashiCorp 公司推出的开源组件,用于实现分布式系统的服务发现与配置。Consul 使用 Go 语言编写,因此具有天然可移植性。安装包仅包含一个可执行文件,方便部署。

​ Consul 官网:consul.io

​ Consul 会提供Web管理界面,访问地址:localhost:8500

功能特性

  • 服务发现
  • 健康检查
  • Key/Value 存储
  • 多数据中心

工作原理

image-20221014112155906
  1. 当 Producer 启动的时候,会向 Consul 发送一个 post 请求,告诉 Consul 自己的 IP 和 Port
  2. Consul 接收到 Producer 的注册后,每隔10s(默认)会向 Producer 发送一个健康检查的请求,检验Producer是否健康
  3. 当 Consumer 发送 GET 方式请求 /api/address 到 Producer 时,会先从 Consul 中拿到一个存储服务 IP 和 Port 的临时表,从表中拿到 Producer 的 IP 和 Port 后再发送 GET 方式请求 /api/address
  4. 该临时表每隔10s会更新,只包含有通过健康检查的 Producer。

数据一致性设计

 Consul 的设计原则是CP,即强一致性和分区容错性。Consul 的 raft 协议要求必须过半数的节点都写入成功才认为注册成功,Leader 挂掉时,重新选举期间整个 Consul 不可用。保证了强一致性但牺牲了可用性。

配置中心

注意,并不是所有的配置数据都适合放在配置中心,每个服务应有自己的自定义配置。

目前流行的解决方案有:

  • Alibaba Nacos
  • Spring Cloud Config
  • Consul
  • Etcd

在 Nacos Spring Cloud 中,dataId 的完整格式如下:

${prefix}-${spring.profiles.active}.${file-extension}
  • prefix 默认为 spring.application.name 的值,也可以通过配置项 spring.cloud.nacos.config.prefix来配置。
  • spring.profiles.active 即为当前环境对应的 profile。 注意:当 spring.profiles.active 为空时,对应的连接符 - 也将不存在,dataId 的拼接格式变成 ${prefix}.${file-extension} ;
  • file-exetension 为配置内容的数据格式,可以通过配置项 spring.cloud.nacos.config.file-extension 来配置。目前只支持 propertiesyaml 两种类型。

工程项目的resources 目录下新增 bootstrap.yaml 或 bootstrap.properties 配置文件, 以 yaml 格式举例,内容如下:

spring:
  application:
    name: jmember-purchase
  profiles:
    active: dev
  cloud:
    nacos:
      config:
        server-addr: 127.0.0.1:8848
        file-extension: yaml

 这样项目启动时会去配置中心读取配置数据, 上面的配置会去 DEFAULT_GROUP 分组下读取dataid 为 jmember-purchase-dev.yamljmember-purchase.yaml 的配置信息,优先读取带有${spring.profiles.active} 的配置文件,也就是dataId为 jmember-purchase-dev.yaml的配置,如果配置中心没有该 dataId或在该dataId中找不到对应的配置信息,再去读取 jmember-purchase.yaml 的配置信息。

配置中心的数据配置如下:

image-20210209211751648

配置自动更新

@RefreshScope 注解 //支持nacos 的配置动态刷新

@Service
@RefreshScope       //支持nacos 的配置动态刷新
public class MemberPurchaseService {

	@Value("${config.info:my-config}")
    private String configInfo;
    
}

注:推荐 @Value 注解的value 都以冒号 : 的形式补上默认值,这样可以防止因找不到配置信息而报错。

Nacos Config

服务调用

Ribbon

Ribbon 是Netflix 公司开源的一款用于进程间通信的,内置负载均衡调用的软件库。

基本使用

使用Ribbon 的方法很简单:

1.引入Ribbon依赖。

2.配置负载均衡:(声明式配置)

@Configuration
public class Configuration {

	@LoadBalanced
	@Bean
	public RestTemplate restTemplate() {
		return new RestTemplate();
	}
}

3.客户端负载均衡调用:

@RestController
public class OrderController {
    
    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/order/completeOrder/{orderId}")
    public String completeOrder(@PathVariable String orderId) {
        return restTemplate.getForObject("http://service-stock/echo/" + orderId, String.class);
    }
}

注:客户端负载均衡调用一定要配合注册中心使用,需要使用服务名调用服务。

负载均衡策略

Ribbon 默认的负载调用规则是轮询。可以通过配置修改Ribbon的负载调用规则。

Ribbon的负载调用规则实现类都实现了 com.netflix.loadbalancer.IRule 接口:

其他负载调用规则有:

  • RoundRobinRule 轮询调用
  • RandomRule 随机调用
  • RetryRule 重试调用(先按照轮询调用,如果失败则在指定时间内重试,获取可用的服务)
  • WeightedResponseTimeRule 响应时间越快的实例选择权重越大

Ribbon 内置的负载均衡策略

更换负载策略

自定义负载均衡策略

集成 AbstractLoadBalancerRule 接口实现自己的负载均衡策略

设置范围分为两种:

  • 全局设置负载调用规则
  • 局部设置负载调用规则(针对某个服务设置负载调用规则)

OpenFeign

 OpenFeign 全称 Spring Cloud OpenFeign,它是 Spring 官方推出的一种声明式服务调用与负载均衡组件,它的出现就是为了替代进入停更维护状态的 Feign。

 OpenFeign 是 Spring Cloud 官方对Netflix Feign 的二次封装,它具有 Feign 的所有功能,并在 Feign 的基础上增加了对 Spring MVC 注解的支持,例如 @RequestMapping、@GetMapping 和 @PostMapping 等。OpenFeign 包里面集成了 Netflix的 Ribbon,将Ribbon作为负载均衡器。

基本使用

OpenFeign 的使用也非常简单,需要两步:

  1. 在启动类上标注 @EnableFeignClients 注解,表示启用 OpenFeign;
  2. 在业务接口上标注 @FeignClient 注解指定要调用的服务名称,配合SpringMVC的注解指定要调用的路径;

下面是一个示例:

@SpringBootApplication
@EnableFeignClients
public class WebApplication {

	public static void main(String[] args) {
		SpringApplication.run(WebApplication.class, args);
	}

	@FeignClient("jmember-order")
	public interface NameService {
        
		@PostMapping("/updateOrder/{goods}/{orderNum}")
    	String addOrder(@PathVariable String goods,@PathVariable Integer orderNum)
	}
}

注:因为OpenFeign 集成了 Ribbon,所以天然支持客户端负载调用。调用该业务接口的方法就会调用指定服务的相关接口,而且是负载轮询调用。

超时控制

 OpenFeign 默认等待一秒钟,超时后就会报错,OpenFeign底层是由Ribbon实现的,所以我们只要修改Ribbon的配置即可:(单位:毫秒)

ribbon:
  ConnectTimeout: 5000
  ReadTimeout: 5000

接口调用日志

有时我们需要查看调用接口的日志详情,查看OpenFeign 的调用日志非常方便:

@Configuration
public class OpenFeignLogConfig {

    @Bean
    Logger.Level feignLoggerLevel() {
        return Logger.Level.FULL;
    }
}

配置文件配置:

logging:
  level:
    com.jackpot.purchase.feign.OrderService: debug

输出结果:

[OrderService#updateOrder] ---> POST http://jmember-order/order/updateOrder/001/777 HTTP/1.1
[OrderService#updateOrder] ---> END HTTP (0-byte body)
[OrderService#updateOrder] <--- HTTP/1.1 200 (1046ms)
[OrderService#updateOrder] connection: keep-alive
[OrderService#updateOrder] content-length: 14
[OrderService#updateOrder] content-type: text/plain;charset=UTF-8
[OrderService#updateOrder] date: Wed, 10 Feb 2021 12:15:35 GMT
[OrderService#updateOrder] keep-alive: timeout=60
[OrderService#updateOrder] 
[OrderService#updateOrder] order-9101true
[OrderService#updateOrder] <--- END HTTP (14-byte body)

更换负载均衡策略

同Ribbon 一致。

RPC

 服务之间除了使用OpenFeign 进行通信外,还可以使用RPC 方式进行远程通信,并且RPC通常可以带来更高的通信效率,RPC 即远程过程调用框架,流行解决方案有 Apache Dubbo、Google gRPC 和 Java RMI 等,本文不对RPC框架进行详细讲解。

​ 严格意义上来说,OpenFeign 并不属于RPC框架,OpenFeign 虽然这次会了服务间的调用,并且还支持负载均衡,但

服务网关

网关(Gateway)在计算机网络中很常见,它用来表示位于内部区域边缘,与外界进行交互的某个物理或逻辑设备。

API 网关

 微服务中网关的首要职责就是作为统一的出口对外提供服务,将外部访问网关地址的流量,根据适当的规则路由到内部集群中正确的服务节点之上,因此,微服务中的网关,也常被称为“服务网关”或者“API 网关”,微服务中的网关首先应该是个路由器,在满足此前提的基础上,网关还可以根据需要作为流量过滤器来使用,提供某些额外的可选的功能,譬如安全、认证、授权、限流、监控、缓存,等等(这部分内容在后续章节中有专门讲解,这里不会涉及)。简而言之:

 网关 = 路由器(基础职能) + 过滤器(可选职能)

先来看没有网关的微服务架构会遇到哪些问题:

  • 如果需要添加鉴权功能,则需要对每个微服务进行改造。
  • 如果需要对流量进行控制,则需要对每个微服务进行改造。
  • 跨域问题,需要对每个微服务进行改造。
  • 存在安全问题,每个微服务需要暴露自己的 Endpoint 给客户端。(如http://jackpot.cn:8000)
  • 灰度发布、动态路由需要对每个微服务进行改造。

 以上问题的痛点是每个微服务都是一个单独的入口,需要逐个改造。为了解决以上痛点,API 网关应运而生。服务网关的流行解决方案有 Spring Cloud Gateway 、Netflix Zuul 、Nginx 等。本篇只讲解 Spring Cloud Gateway 网关组件。

Spring Cloud GateWay

 Spring Cloud GateWay 构建于 Spring Boot 2 和 Spring Framework 5 版本之上,使用 Spring WebFluxProject Reactor 开发,底层使用 Netty 开发的网络应用服务。

官方参考手册:https://docs.spring.io/spring-cloud-gateway/docs/3.1.4/reference/html

基本术语

服务网关3个基本术语:

  • 路由 route
  • 断言 predicate
  • 过滤 filter

工作流程

路由判断:客户端的请求到达网关后,先经过 Handler Mapping 处理,这里面会做断言(Predicate)判断,看下符合哪个路由规则,这个路由映射后端的某个服务。

请求过滤:然后请求到达 Web Handler,这里面有很多过滤器,组成过滤器链(Filter Chain),这些过滤器可以对请求进行拦截和修改,比如添加请求头、参数校验等,。然后将请求转发到实际的后端服务。这些过滤器被称作 Pre-Filters,表示业务处理之前的过滤器。

服务处理:后端服务会对请求进行处理。

响应过滤:后端处理完成后,返回给 Gateway 的过滤器再次做处理,这些过滤器被称作 Post-Filters,表示业务处理之后的过滤器。

响应返回:响应经过过滤处理后,返回给客户端。

两种配置方式

  • 配置文件方式(推荐)
  • Java Config 编码方式

下面是配置文件的示例:(application.yaml)

server:
  port: 8080
spring:
  application:
    name: jmember-gateway
  cloud:
    nacos:
      server-addr: 127.0.0.1:8848   # 服务注册中心
      password: nacos
      username: nacos
      discovery:
        group: jmember
    gateway:
      enabled: true
      discovery:
        locator:
          enabled: true   # 开启从注册中心动态创建路由的功能,使用服务名称路由
      routes:
        - id: purchase   # 路由ID(唯一标识)
          uri: lb://jmember-purchase  # 目标服务地址(服务名称方式负载调用)
          predicates:
            - Path=/purchase/**
        - id: order
          uri: lb://jmember-order
          predicates:
            - Path=/order/**
        - id: stock
          uri: lb://jmember-stock
          predicates:
            - Path=/stock/**

Java Config 编码方式:

@Configuration
public class GatewayConfig {
    @Bean
    public RouteLocator routes(RouteLocatorBuilder builder) {
        return builder.routes()
                .route("route1", r -> r.path("/order").uri("http://news.order.com"))
                .build();
    }
}

断言和路由

断言 Predicate

​ Predicate 是用于匹配 HTTP 请求中相关内容的判断条件,当满足判断条件后才会进行路由转发,如果设置多个Predicate,则全部满足的情况下被转发。

常见断言配置如下:

断言 示例 说明
Path - Path=/api/order/** 匹配 /api/order/** 的请求路径
Method - Method=GET 匹配 GET请求
Query - Query=name, zhangsan 【Query=参数名,参数值】
Header - Header=h, [a-h] 【Header=header名, header值的正则表达式规则】
Cookie - Cookie=id, [0-9] 【Cookie=cookie名, cookie值的正则表达式规则】
Before - Before=2020-04-25T16:40:58.215+08:00[Asia/Shanghai]
After - After=2020-04-25T16:30:58.215+08:00[Asia/Shanghai]
Between
Host - Host=*.a.com,**.b.cn 【Host=主机名(可配置多个,也可以使用通配符)】
RemoteAddr - RemoteAddr=192.168.1.1/24 如果请求的远程地址是例如 192.168.1.10,则此路由匹配。
Weight 权重路由

断言与路由的关系

  • 一个路由规则可以包含多个断言,如果一个路由规则中有多个断言,则需要同时满足才能匹配。
  • 如果一个请求可以匹配多个路由,则映射第一个匹配成功的路由。(权重路由特殊)

示例:

spring:
  cloud:
    gateway:
      routes:
      - id: after_route
        uri: https://example.org
        predicates:
        - After=2021-01-20T17:42:47.789-07:00[Asia/Shanghai]

路由 Route

动态路由

 uri 直接配置 ip:port 的方式是配置死了,如果服务的ip 和端口发生变化,或者微服务是集群部署,这种方式就很不方便。通过配置 locator 选项可以开启动态路由功能,网关从注册中心获取服务名的地址进行访问。

spring:
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true   # 开启从注册中心动态创建路由的功能,使用服务名称路由

示例:

spring:
  cloud:
    gateway:
      routes:
        - id: purchase   
          uri: lb://jmember-purchase  # 目标服务地址(服务名称方式负载调用)
          predicates:
            - Path=/purchase/**

uri: lb://jmember-purchase 表示将请求转发给 jmember-purchase 微服务,且支持负载均衡。

权重路由

spring:
  cloud:
    gateway:
      routes:
      - id: weight_high
        uri: https://weighthigh.org
        predicates:
        - Weight=group1, 8
      - id: weight_low
        uri: https://weightlow.org
        predicates:
        - Weight=group1, 2

该路由会将约 80% 的流量转发到 weighthigh.org,将约 20% 的流量转发到 weightlow.org

过滤 Filter

 Getaway 的过滤器使用分为两种,一种是官方提供的Gateway Filter,官方已经实现好了,只需要我们在配置文件配置即可,目前提供几十种 Filter 可以直接使用;另一种是通过编码的方式自定义过滤器。

生命周期

Pre 类型:在请求被转发到微服务之前,对请求进行拦截和修改,例如参数校验、权限校验、流量监控、日志输出以及协议转换等操作。

Post 类型:微服务处理完请求后,返回响应给网关,网关可以再次进行处理,例如修改响应内容或响应头、日志输出、流量监控等。

  • 官方提供的 Gateway Filter
  • 自定义全局过滤器

GatewayFilter 局部过滤器

官方GatewayFilter 提供了很多,这里只举例其中一种,更多Gateway Filter的种类的使用请在官网查看。

application.yml 配置信息:

spring:
  cloud:
    gateway:
      routes:
      - id: add_request_header_route
        uri: https://example.org
        filters:
        - AddRequestHeader=X-Request-red, blue

官方内置的GatewayFilter

Filter 示例 说明
AddRequestHeader - AddRequestHeader=X-Request-red, blue 将 X-Request-red:blue 标头添加到所有匹配请求的下游请求标头中
AddRequestParameter - AddRequestParameter=red, blue 将 red=blue 添加到所有匹配请求的查询参数上
AddResponseHeader - AddResponseHeader=X-Response-Red, Blue
DedupeResponseHeader
PrefixPath - PrefixPath=/mypath 为所有匹配请求的路径添加前缀 /mypath
RequestRateLimiter
RemoveRequestHeader - RemoveRequestHeader=X-Request-Foo
RemoveResponseHeader - RemoveResponseHeader=X-Response-Foo
RemoveRequestParameter - RemoveRequestParameter=red
RewritePath - RewritePath=/red(?/?.*), ${segment}
SetRequestHeader - SetRequestHeader=X-Request-Red, Blue
SetPath - SetPath=/{segment}
RedirectTo - RedirectTo=302, https://acme.org
SetStatus - SetStatus=401
Default Filters

Default Filters

要添加过滤器并将其应用于所有路由,可以使用 spring.cloud.gateway.default-filters。 此属性采用过滤器列表。 以下清单定义了一组默认过滤器:

spring:
  cloud:
    gateway:
      default-filters:
      - AddResponseHeader=X-Response-Default-Red, Default-Blue
      - PrefixPath=/httpbin

全局过滤器 GlobalFilter

ForwardRoutingFilter
ReactiveLoadBalancerClientFilter 
GatewayMetricsFilter

负载均衡过滤器

​ uri 中有个关键字 lb,用到了全局过滤器 LoadBalancerClientFilter,当匹配到这个路由后,会将请求转发到 passjava-member 服务,且支持负载均衡转发,也就是先将 passjava-member 解析成实际的微服务的 host 和 port,然后再转发给实际的微服务。

自定义全局过滤器

编写自定义过滤器非常简单,只需要让过滤器被Spring容器管理,然后实现两个接口即可:

@Component
public class MyGolbalFilter implements GlobalFilter, Ordered {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        HttpMethod method = exchange.getRequest().getMethod();
        ServerHttpResponse response = exchange.getResponse();
        if (method!=HttpMethod.GET && method!=HttpMethod.POST){
            logger.error("只接受 GET 和 POST 请求!");
            response.setStatusCode(HttpStatus.NOT_ACCEPTABLE);
            return exchange.getResponse().setComplete();
        }
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return 0;
    }
}

Ordered 接口的 getOrder() 方法定义过滤器的优先级,数值越小,优先级越高。

关闭网关

配置文件中的 spring.cloud.gateway.enabled 选项用于开启或关闭网关。

spring.cloud.gateway.enabled=true

服务治理

服务与服务之间并不总是能成功调用,总会有出现错误的时候,比如调用失败,调用超时等异常情况出现.

  • 服务熔断

  • 服务降级

  • 服务限流

流量控制

熔断降级

系统保护

来源访问控制

热点限流

服务熔断

 当一个服务因为各种原因停止响应时,调用方通常会等待一段时间,然后超时或者收到错误返回。如果调用链路比较长,可能会导致请求堆积,整条链路占用大量资源一直在等待下游响应。所以当多次访问一个服务失败时,应熔断,标记该服务已停止工作,直接返回错误。直至该服务恢复正常后再重新建立连接。

image-20221011105955689

服务降级

 当下游服务停止工作后,如果该服务并非核心业务,则上游服务应该降级,以保证核心业务不中断。比如网上超市下单界面有一个推荐商品凑单的功能,当推荐模块挂了后,下单功能不能一起挂掉,只需要暂时关闭推荐功能即可。

服务限流

 一个服务挂掉后,上游服务或者用户一般会习惯性地重试访问。这导致一旦服务恢复正常,很可能因为瞬间网络流量过大又立刻挂掉,在棺材里重复着仰卧起坐。因此服务需要能够自我保护——限流。限流策略有很多,最简单的比如当单位时间内请求数过多时,丢弃多余的请求。另外,也可以考虑分区限流。仅拒绝来自产生大量请求的服务的请求。例如商品服务和订单服务都需要访问促销服务,商品服务由于代码问题发起了大量请求,促销服务则只限制来自商品服务的请求,来自订单服务的请求则正常响应。

Spring Cloud Circuit Breaker

​ Spring Cloud Circuit Breaker 是 Spring Cloud 提供的一套 容错机制抽象层,旨在通过断路器模式(Circuit Breaker Pattern)保护微服务架构中的服务调用,防止级联故障并提升系统弹性。


核心功能

  1. 故障隔离
    当依赖服务调用失败或超时时,自动切断请求链路(”熔断”),避免资源耗尽和雪崩效应。
  2. 降级处理(Fallback)
    提供备选逻辑(如返回默认值、缓存数据或友好提示),确保主逻辑失败时系统仍可提供有限服务。
  3. 自动恢复
    周期性检测依赖服务状态,若恢复则自动闭合断路器,逐步恢复流量。
  4. 监控与指标集成
    支持对接 Micrometer、Prometheus 等监控工具,实时跟踪熔断状态和性能指标。

支持的实现库

Spring Cloud Circuit Breaker 是一个抽象层,支持多种底层断路器实现:

  • Resilience4J(官方推荐,替代 Hystrix)
  • Sentinel(阿里开源)
  • Hystrix(旧版,已停止维护)

Resilience4j

Spring Cloud Circuit Breaker + Resilience4j(推荐)

Resilience4j 是 Spring Cloud 官方推荐的熔断库(替代已停更的 Hystrix),轻量且功能强大,支持熔断、限流、重试、降级等功能。

注:Resilience4j 2 requires Java 17.

实现步骤

1.添加依赖

<!-- Resilience4j 核心依赖 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
</dependency>
<!-- 若需要与 OpenFeign 集成 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

2.启用熔断和 Feign 客户端

在启动类添加注解:

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

(3) 配置熔断策略

application.yml 中配置:

resilience4j:
  circuitbreaker:
    instances:
      myServiceA:  # 熔断器实例名(与Feign客户端名一致)
        register-health-indicator: true  # 暴露健康状态
        failure-rate-threshold: 50       # 触发熔断的失败率阈值(50%)
        minimum-number-of-calls: 5       # 最小请求数(统计窗口内)
        sliding-window-type: COUNT_BASED  # 统计窗口类型(基于请求数)
        sliding-window-size: 10          # 统计窗口大小(10次请求)
        wait-duration-in-open-state: 5s  # 熔断后等待恢复时间
        permitted-number-of-calls-in-half-open-state: 3  # 半开状态允许的请求数

4. 定义 Feign 客户端并添加熔断降级

@FeignClient(
    name = "myServiceA",
    url = "http://service-a:8080",
    fallback = ServiceAFeignClientFallback.class  // 降级实现类
)
public interface ServiceAFeignClient {
    @GetMapping("/api/data")
    String getData();
}

// 降级类(需实现 Feign 接口)
@Component
public class ServiceAFeignClientFallback implements ServiceAFeignClient {
    @Override
    public String getData() {
        return "Fallback: Service A is unavailable.";
    }
}

(5) 监控熔断状态

通过 Actuator 端点查看熔断器状态:

management:
  endpoints:
    web:
      exposure:
        include: health,circuitbreakers

访问 http://localhost:8080/actuator/health 查看熔断器健康状态。

Sentinel

 Sentinel 是面向分布式、多语言异构化服务架构的流量治理组件,主要以流量为切入点,从流量控制、流量路由、熔断降级、系统自适应保护等多个维度来帮助用户保障微服务的稳定性。

GitHub:https://github.com/alibaba/Sentinel

使用手册:https://github.com/alibaba/spring-cloud-alibaba/wiki/Sentinel

快速开始

1 引入依赖

<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-core</artifactId>
</dependency>

2.定义资源

try (Entry entry = SphU.entry("HelloWorld")) {
    // Your business logic here.
    System.out.println("hello world");
} catch (BlockException e) {
    // Handle rejected request.
    e.printStackTrace();
}
// try-with-resources auto exit

3.定义规则

List<FlowRule> rules = new ArrayList<>();
FlowRule rule = new FlowRule();
rule.setResource("HelloWorld");
// set limit qps to 20
rule.setCount(20);
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
rules.add(rule);
FlowRuleManager.loadRules(rules);

4.测试结果

5.启动仪表盘

Sentinel 与Spring Cloud 各个组件深度适配:如openfeign、restTemplate、Spring Cloud Gateway

控制台

配置控制台信息

application.yml

spring:
  cloud:
    sentinel:
      transport:
        port: 8719
        dashboard: localhost:8080

 这里的 spring.cloud.sentinel.transport.port 端口配置会在应用对应的机器上启动一个 Http Server,该 Server 会与 Sentinel 控制台做交互。比如 Sentinel 控制台添加了一个限流规则,会把规则数据 push 给这个 Http Server 接收,Http Server 再将规则注册到 Sentinel 中。

Hystrix

Hystrix 不再处于积极开发阶段,目前处于维护模式。

Hystrix(版本 1.5.18)足够稳定,可以满足 Netflix 对我们现有应用程序的需求。与此同时,我们的重点已转向更具适应性的实现,这些实现会对应用程序的实时性能做出反应,而不是预先配置的设置(例如,通过自适应并发限制)。对于像 Hystrix 这样的东西有意义的情况,我们打算继续将 Hystrix 用于现有应用程序,并将 resilience4j 等开放和活跃的项目用于新的内部项目。我们开始建议其他人也这样做。

注意:Hystrix 从2018年不再继续开发,进入维护状态。最后一个稳定版本是 Hystrix1.5.18;

Hystrix 是 Netflix开源的一款分布式系统的延迟和容错框架。

第一步:引入依赖

<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>

第二步:启动类上加 @EnableHystrix 注解表示启用 Hystrix。

服务降级

在Controller 层编写代码:

@RestController
@RequestMapping("/")
public class OrderController {

    @HystrixCommand(commandKey = "/order/updateOrder",
            commandProperties = {
                    @HystrixProperty(name="execution.timeout.enabled",value="true"),
                    @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="5000")
            },
            fallbackMethod = "createOrderFallbackMethodTimeout")
    @PostMapping("/updateOrder/{goods}/{orderNum}")
    public String addOrder(@PathVariable String goods,@PathVariable Integer orderNum) 
        throws InterruptedException{
        Thread.sleep(6000);	//模拟超时
        return "order";
    }

    /**
     * 超时降级策略
     */
    public String createOrderFallbackMethodTimeout(String goods,Integer orderNum){
        System.out.println("超时降级策略执行..."+goods+"---"+orderNum);
        return "Hystrix time out";
    }
}

注:@HystrixCommand 的参数和默认值全部记录在 HystrixCommandProperties 类里;

服务熔断

Hystrix 的熔断器默认是打开的, HystrixCircuitBreaker 接口定义了熔断器, 如果失败超过定义的阀值,将不允许执行。然后它将在定义的 sleepWindow 之后单次尝试,直到成功为止,此时运行请求继续执行。

参数 描述 默认值
circuitBreaker.enabled 是否启用断路器 true
circuitBreaker.requestVolumeThreshold 请求量的阀值,默认情况下,必须先在10秒内发出20个请求,才能统计 20次
circuitBreaker.errorThresholdPercentage 失败率 50%
circuitBreaker.sleepWindowInMilliseconds 失败后再次重试的时间间隔 5000ms

默认配置下,必须先在10秒内发出20个请求,熔断器才开始统计失败率,如果10秒内50%以上的请求失败,断路器将会打开;(即10秒内请求量达到20次并且失败次数达到10次以上,才会触发断路器)跳闸后,等待5秒后再次尝试是否恢复,如果请求成功,则关闭断路器,恢复正常;

服务熔断和服务降级是有关系的;

服务限流

Hystrix 支持两种限流策略:

  • Thread 线程池隔离策略(默认)
  • Semaphore 信号量隔离策略

一个 @HystrixCommand 标记,就初始化一个线程池,断路器之间线程池隔离。

//线程池方式
@HystrixCommand(commandKey = "/orders/add",
                    commandProperties = {
                    	@HystrixProperty(name="execution.isolation.strategy",value="THREAD")},
                    threadPoolKey = "addOrderThreadPool",
                    threadPoolProperties = {
                        @HystrixProperty(name="coreSize",value="10"),
                        @HystrixProperty(name="maxQueueSize",value="2000"),
                        @HystrixProperty(name="queueSizeRejectionThreshold",value="30")
                    },
                    fallbackMethod = "addOrderFallbackMethodThread")
image-20210225103825933
//信号量方式                      
    @HystrixCommand(commandKey = "/orders/insert",
                    commandProperties = {
                        @HystrixProperty(name="execution.isolation.strategy",value="SEMAPHORE"),
                     @HystrixProperty(name="execution.isolation.semaphore.maxConcurrentRequests",value="3"),
                    },
                    fallbackMethod = "insertOrderFallbackMethodSemaphore")

链路追踪

 以往单体应用,排查问题通常是看一下日志,研究错误信息和调用堆栈。而微服务架构将整个应用分散成多个服务,定位故障点比单体架构要困难。通常需要一个台机器一台机器地查看日志,一个服务一个服务地手工调用。可能需要十几分钟或者更长时间的排找才可以定位到故障点。

 为了解决这样的痛点,分布式链路追踪应运而生。

流行的解决方案有 Spring Cloud Sleuth

zipkin

skywalking

完整的解决方案

上手容易

服务鉴权

最佳实践

Mall4cloud

可以去看看这个开源的商城项目,维护了很多年了,一直在更新,内容也是非常完善。

Mall4cloud微服务商城系统、一个基于Spring Cloud、Nacos、Seata、Mysql、Redis、RocketMQ、canal、ElasticSearch、minio的微服务B2B2C电商商城系统。

README

作者:沙律君

版权声明:本文遵循知识共享许可协议3.0(CC 协议): 署名-非商业性使用-相同方式共享 (by-nc-sa)

参考:

 Spring Cloud Alibaba 官方参考手册

 Nacos 官网

 Spring Cloud GateWay 官网

 分布式链路追踪设计:Google Dapper

 《Nacos架构与原理》


微服务实践之SpringCloud
http://jackpot-lang.online/2020/01/27/系统技术架构设计/分布式-微服务实践之SpringCloud/
作者
Jackpot
发布于
2020年1月27日
许可协议