Spring Cloud 是一个基于 Spring Boot 实现的云应用开发工具,它为基于 JVM 的云应用开发中的配置管理、服务发现、断路器、智能路由、微代理、控制总线、全局锁、决策竞选、分布式会话和集群状态管理等操作提供了一种简单的开发方式。
Spring Cloud 包含了多个子项目(针对分布式系统中涉及的多个不同开源产品),比如:Spring Cloud Config、Spring Cloud Netflix、Spring Cloud Data、Spring Cloud AWS、Spring Cloud Security、Spring Cloud Commons、Spring Cloud Gateway、Spring Cloud CLI 等项目。
下面分别从以下几块来开始搭建我们的 Spring Cloud 应用(基于 Greenwich.M3 版本,建议读者使用稳定版本,这里只是尝试新特性,文末有项目地址):
- 公共父项目(
spring-cloud-example) - 注册中心(
eureka) - 配置中心(
config-server) - 网关(
gateway) - 服务提供者(
feign-server) - 服务消费者(
feign-client)
公共父项目
开发 Java 项目一般使用 Intellij Idea 开发,创建项目的时候勾选 Spring Initializr,使用默认的服务 https://start.spring.io 即可。接下来一步步按照提示操作,选择 Spring Boot 版本为 2.1.1.RELEASE,接着选择需要的依赖,勾选 Ops 的 Actuator(可检测服务的健康状态等等,具体功能可查看 https://cloud.spring.io/spring-cloud-static/Greenwich.RELEASE/multi/multi__actuator_api.html)。生成的 pom.xml 文件如下:
1 | <?xml version="1.0" encoding="UTF-8"?> |



注册中心
微服务框架的核心就是注册中心,有了注册中心后各个服务可以将多个实例都注册到注册中心,需要调用时可根据需要选择对应的服务实例,比如实现负载均衡等。
1.右键项目新建新的 module,同样选择 Spring initializr,选择需要的依赖,注册中心我们勾选 Cloud Discovery 中的 EurekaServer(当然也可以选择其他注册中心,如 Zookeeper、Consul 等)和 Cloud Core 中的 Cloud Security(需要验证才能注册到注册中心,否则泄漏注册中心地址后会有危险服务注册从而被攻击),当然也可以手动新建模块,具体读者可参考 maven 的文档 https://maven.apache.org/guides/mini/guide-multiple-modules.html。生成的 pom.xml 文件如下:
1 | <?xml version="1.0" encoding="UTF-8"?> |
2.在启动类上加上注解,如下:
1 |
|
3.配置 Security,新建类 WebSecurityConfig 继承 WebSecurityConfigurerAdapter,设置 csrf 关闭,否则服务不能正常注册:
1 |
|
4.配置文件 application.yml。
eureka.instance.prefer-ip-address设置为true使服务都是以ip地址形式提供的;eureka.client.register-with-eureka表示是否将自己注册到服务中心,默认为true,因为这里就是注册中心,所以设置为false;eureka.client.fetch-registry表示是否从其他注册中心同步信息,这里我们是单点的注册中心,所以不需要同步,设置为false;eureka.client.service-url.defaultZone设置与Eureka Server交互的地址,查询服务和注册服务都需要依赖这个地址;在开发过程中,我们常常希望Eureka Server能够迅速有效地踢出已关停的节点,但是由于Eureka自我保护模式,以及心跳周期长的原因,常常会遇到Eureka Server不踢出已关停的节点的问题,解决方法如下:Eureka Server端:配置关闭自我保护,并按需配置Eureka Server清理无效节点的时间间隔。1
2eureka.server.enable-self-preservation # 设为false,关闭自我保护
eureka.server.eviction-interval-timer-in-ms # 清理间隔(单位毫秒,默认是60*1000)Eureka Client端:配置开启健康检查,并按需配置续约更新时间和到期时间。1
2
3eureka.client.healthcheck.enabled # 开启健康检查(需要spring-boot-starter-actuator依赖)
eureka.instance.lease-renewal-interval-in-seconds # 续约更新时间间隔(默认30秒)
eureka.instance.lease-expiration-duration-in-seconds # 续约到期时间(默认90秒)spring.security.user配置鉴权有关信息,这里使用简单的用户名密码的形式鉴权,更复杂的安全机制可查看Spring Security的文档。配置用户名为user,密码为123456,则客户端的注册地址需要写成http://user:123456@localhost:8761/eureka/。
示例:
服务端配置:
1 | server: |
客户端配置:
1 | spring: |
注意:更改 Eureka 更新频率将打破服务器的自我保护功能,生产环境下不建议自定义这些配置。详见:https://github.com/spring-cloud/spring-cloud-netflix/issues/373
配置中心
配置管理在分布式项目中有着非常重要的作用,在 Spring Cloud 应用的开发过程中,我们通常使用配置文件 application.yml 或 bootstrap.yml 来管理项目配置,对于单体应用来说,一次配置的修改并不会耗费多少的时间,但是对于许多个微服务都需要更新某项配置时,就需要更改每个服务的配置并重启服务完成更新。而有了配置中心统一管理项目配置后,则无需变动服务本身,只需更改配置中心对应配置即可,提高系统的可扩展性。Spring Cloud Config 提供了这样一种配置中心服务,并且能够通过消息总线实时更新已启动的服务的配置(除在项目启动时就已经读取并不能再更新的配置,如 port)。
Spring Cloud Config 可以使用 Git 仓库作为配置仓库,也可以使用本地的文件系统作为仓库(本质上是在本地创建了 Git 仓库并管理),提供服务端和客户端的支持。
1.右键项目新建新的 module,同样选择 Spring initializr,选择需要的依赖,配置服务端勾选 Cloud Config 中的 Config Server,配置客户端勾选 Cloud Config 中的 Config Client。
2.配置中心服务端配置。生成的依赖如下:
1 | <dependencies> |
启动类添加 @EnableConfigServer:
1 |
|
编写配置文件:
1 | server.port=8888 |
3.配置中心客户端配置。生成的依赖如下:
1 | <dependency> |
编写配置文件(注意这里一定要为 bootstrap.yml 而不能是 application.yml,因为需要从引导上下文中读取配置中心的配置数据,作为应用上下文的配置,两者有先后关系):
1 | spring: |
如果需要配置重试,则需要添加依赖:
1 | <dependency> |
4.动态刷新配置。有时动态更新了 Git 仓库的配置,需要在不重启服务的情况下完成配置的动态更新,这需要引入另一个依赖 actuator:
1 | <dependency> |
假设客户端在 Git 仓库中的配置文件名为 config-client-dev(config-client 为服务名,dev 为活跃的 profile,这些在 bootstrap.yml 中配置,则可将在仓库中的对应的文件结合仓库中默认的 application.yml 作为客户端的配置文件 appilcation.yml 使用),为实现动态更新需要暴露 refresh 端点,这里简单起见开放所有端点,在 bootstrap.yml 或 Git 仓库中的 config-client-dev.yml 中配置:
1 | management: |
在修改了远程仓库的配置文件(如 application-client.yml)后,需要使用 POST 请求访问服务的 /actuator/refresh 端点完成更新。
动态刷新配置对使用配置的方式有限制,客户端需通过 @ConfigurationProperties、@Value("${…}") 或 Environment 使用配置中心的配置,这些配置在服务启动时已经加载,而为了强制刷新这些已读取的配置需要加上 @RefreshScope 注解,该注解可对类或者方法作用。
一般配置中心会配合消息总线一起使用,以便通知所有服务某项配置的更新。需要引入消息总线的依赖,具体如何使用可参考 https://github.com/1016990109/spring-cloud-example 中的
config-server和feign-server的相关配置及代码,这里的例子基于RabbitMQ消息队列实现。
网关
服务网关是微服务架构中一个不可或缺的部分。通过服务网关统一向外系统提供 REST API 的过程中,除了具备服务路由、均衡负载功能之外,它还具备了权限控制等功能。Spring 官方有推出自己的组件 Spring Cloud Gateway,相比于之前的 Zuul(1.x) 有许多优势:Zuul 基于 servlet 2.5(使用 3.x),使用阻塞 API。 它不支持任何长连接,如 websockets。而 Gateway 建立在 Spring Framework 5,Project Reactor 和 Spring Boot 2 之上,使用非阻塞 API。Websockets 得到支持,并且由于它与 Spring 紧密集成,所以将会是一个更好的开发体验。Spring Cloud Gateway 为微服务架构提供了前门保护的作用,同时将权限控制这些较重的非业务逻辑内容迁移到服务路由层面,使得服务集群主体能够具备更高的可复用性和可测试性。
1.右键项目新建新的 module,同样选择 Spring initializr,选择需要的依赖,勾选 Cloud Routing 中的 Gateway。生成的依赖如下所示:
1 | <dependency> |
2.application.yml 配置示例:
1 | server: |
上述的配置可以达到以下效果:凡是以 /feignapi/ 开头的请求都会路由到注册中心的 feign-server 服务,并自动实现负载均衡,转发后的请求不再有 /feignapi 前缀但是会添加 /feign-server 作为前缀,且每个用户请求频率不得超过每秒 10 个,总请求数不得超过 20。转发请求时,不再携带头部 dummy、Connection,响应附带头部 default-src 'self' https:; font-src 'self' https: data:; img-src 'self' https: data:; object-src 'none'; script-src https:; style-src 'self' https: 'unsafe-inline'。
注意:实现网关限流需额外安装依赖以及配置 key-resolver,详情可移步作者 Github 仓库 https://github.com/1016990109/spring-cloud-example:
1 | <dependency> |
1 |
|
除了根据请求路径路由外,
Spring Cloud Gateway还可以根据时间、Cookie、Header、Host、请求方式、请求参数、请求ip地址,或者将它们组合使用。具体使用方式读者可以自行查询Predicate的介绍。
服务提供者(基于 Fiegn 服务调用中的服务端)
服务的提供者,提供接口供调用者调用,是服务调用中的“服务端”。
1.服务端依赖。包括 web、open-feign、注册中心:
1 | <dependency> |
2.application.yml 配置:
1 | feign: |
目的是为了启动服务间调用的压缩(GZIP),减少通信中的性能损耗。
3.启动类配置:需加上 @EnableFeignClients 注解
1 |
|
4.提供接口。
1 |
|
以上即完成了基于 Spring Cloud OpenFeign 的服务间调用的服务端配置。
服务调用者(基于 Fiegn 服务调用中的客户端)
服务的调用者,是服务间调用的发起者。
1.客户端依赖。包括 web、注册中心、熔断器等等。
1 | <dependency> |
2.application.yml 配置:
1 | feign: |
上述出了配置 Feign 的相关配置、熔断外,还配置了 hystrix 监控即 hystrix.stream(访问 http://localhost:port/actuator/hystrix.stream 可查看相关统计数据,当然也可以配合 turbine 统一监控应用,这需要加入 hystrix-dashboard 依赖,又在上文提到,本项目采用单体应用的监控,可访问 http://localhost:post/hystrix 进入 Hystrix DashBoard 界面)。
3.启动类配置:
1 |
|
使用 EnableCircuitBreaker 配置 Feign 的熔断,使用 EnableHystrixDashBoard 启用熔断监控,使用 @EnableFeignClients 启动 Open Fiegn。
4.声明调用的服务接口:
1 | (value = "feign-server", fallback = FeignServiceClientFallBack.class) |
@FeignClient(value = "feign-server", fallback = FeignServiceClientFallBack.class) 配置调用服务的名称(在注册中心的名称),以便像注册中心获取相应的服务实例地址(负载均衡的),接口主体部分保持和服务端相同即可,即 Test getInstanceByServiceId(..)。
而 fallback 则是断路器的配置,当服务(服务端)的错误率超过一定阀值时,Hystrix 可以触发断路机制,停止向该服务请求一段时间,而直接使用 fallback 指定的类作为接口实现,直接返回数据。
阀值有几个指标: 1.一定时间(默认 10s)内错误一定数量(默认 20 次); 2.请求错误数量超过一定百分比(默认 50%)。
当某个服务的断路器打开后,Hystrix 将不会请求至该服务,直接 fallback,这样对于已经确定的故障在一定时间内不会再尝试。当断路器打开一段时间后,Hystrix 会进入”半开”状态,断路器会允许一个请求尝试对服务进行请求,如果该服务可以调用成功,则关闭断路器,否则将继续保持断路器打开,并进入倒计时,倒计时结束后继续尝试自动恢复。
5.开始服务调用。使用非常简单,像使用普通的本地接口一般使用即可:
1 |
|
注意:这里声明的接口返回数据类型的名称可以不相同,也可以只保留部分字段,如服务端提供的是
InstanceDTO而客户端调用时声明的为TestDTO,Feign会自动地序列化与反序列化(JSON),不像Java自带的序列化工具那般要求类完全相同(序列号即成员变量等等都要求相同)。具体使用可参考 https://github.com/1016990109/spring-cloud-example。