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 | "feign-server", fallback = FeignServiceClientFallBack.class) (value = |
@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。