Spring Cloud和云计算没有关系,只是一个基于Spring Boot的快速构建分布式系统的工具集。
一 Spring Cloud特点
# 约定优于配置
# 开箱即用,快速启动
# 适用于各种环境,可以部署在PC server或者 云环境
# 轻量级的组件
# 组件的支持很丰富,功能齐全
# 选型中立
二 服务提供者和服务消费者
三 服务发现和注册
为什么需要服务注册与发现
# 服务重启或者升级后IP地址变化
# 水平伸缩后服务实例的变化
# 同一个节点运行多个服务
所以需要一种注册机制,帮助我们去获取响应。
核心机制:
将实例的信息注册到注册中心
调用者通过注册中心查找服务
调用者获取服务实例列表
调用者通过负载均衡通信
3.1 基本流程
首先:服务消费者和服务注册者向服务发现组件注册
其次:服务消费者要调用的时候会从服务发现组件中进行查询
需求:
# 每一个服务实例都会在启动的时候通过HTTP/REST或者Thrift等方式发布远程API
# 服务端实例的具体数量及位置会发生动态变化
# 虚拟机与容器通常会被分配动态IP地址
3.2 服务发现组件的功能
# 服务注册表: 是一个记录当前可用服务实例的网络信息的数据库,是服务发现机制的核心。服务注册表提供查询API和管理API,使用API 获得可用的服务实例,使用管理API实现注册和注销。
# 服务注册
# 健康检查
3.3 服务发现的方式
3.3.1 客户端发现
它的主要特点是客户端决定服务实例的网络位置,并且对请求进行负载均衡。客户端查询服务注册表(可用服务实例数据库),使用负载均衡算法选择一个实例,并发出请求。典型代表Eureka或者ZK
客户端发现模式的优缺点
优点:
不需要很多的网络跳转
缺点:
客户端和服务注册表耦合
需要为应用程序每一种编程语言、框架等建立客户端发现逻辑,比如 Netflix Prana就为非JVM客户端提供一套基于HTTP代理服务发现方案
# 服务器端发现
向某一服务发送请求,客户端会通过向运行位置已知的路由器或者负载均衡器发送请求。他们会查询服务注册表,并向可用的服务实例转发该请求。典型代表Consul + Nginx
服务器端发现模式优缺点:
优点:
客户端无需实现发现功能,只需要向路由器或者负载均衡器发送请求即可
缺点:
除非成为云环境的一部分,否则该路由机制必须作为另一系统组件进行安装与配置。为实现可用性和一定的接入能力,还需要为其配置一定数量的副本。
相较于客户端发现,服务器端发现机制需要更多的网络跳转。
3.4 服务发现组件Eureka
3.4.1 什么是Eureka
Eureka是Netflix开发的服务发现框架,本身是一个基于REST的服务,主要用于定位运行在AWS域中的中间层服务,以达到负载均衡和中间层服务故障转移的目的。Spring Cloud将它集成在其他子项目spring-cloud-netflix中,以实现spring cloud服务发现功能。
3.4.2 Eureka原理
先需要明白AWS几个概念:
Region: AWS云服务在全球不同的地方都有数据中心,比如北美、南美和欧洲亚洲等。与此对应,根据地理位置我们把某个地区的基础设施服务集合称为一个区域。不同区域之间是相互独立的。说白了就类似于不同地方的机房。
Available Zone: 基于容灾背景提出,简单而言,就是相同region区域不同的机房
# 首先是服务注册到Eureka
# 每30s发送心跳检测重新进行租约,如果客户端不能多次更新租约,它将在90s内从服务器注册中心移除。
# 注册信息和更新会被复制到其他Eureka 节点,来自任何区域的客户端科可以查找到注册中心信息,每30s发生一次复制来定位他们的服务,并进行远程调用
# 客户端还可以缓存一些服务实例信息,所以即使Eureka全挂掉,客户端也是可以定位到服务地址的
3.4.3 搭建Euraka Server
首先:搭建parent项目
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<modules>
<module>microservice-consumermodule>
<module>microservice-providermodule>
<module>microservice-discovery-eurekamodule>
modules>
<groupId>com.microservicegroupId>
<artifactId>microserviceartifactId>
<version>1.0-SNAPSHOTversion>
<packaging>pompackaging>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>1.5.6.RELEASEversion>
parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>Dalston.SR3version>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-configartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-eurekaartifactId>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>5.1.44version>
dependency>
dependencies>
<build>
<finalName>${project.artifactId}finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-resources-pluginartifactId>
<configuration>
<encoding>UTF-8encoding>
configuration>
plugin>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
<configuration>
<source>1.8source>
<target>1.8target>
<encoding>UTF-8encoding>
configuration>
plugin>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.tomcat.mavengroupId>
<artifactId>tomcat7-maven-pluginartifactId>
<version>2.2version>
plugin>
plugins>
pluginManagement>
build>
project>
其次:搭建Euraka Server
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<packaging>warpackaging>
<parent>
<artifactId>microserviceartifactId>
<groupId>com.microservicegroupId>
<version>1.0-SNAPSHOTversion>
parent>
<artifactId>microservice-discovery-eurekaartifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-eureka-serverartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
dependency>
dependencies>
project>
然后:在application.xml中配置application.yml文件
security:
basic:
enabled: true
user:
name: user
password: password
server:
port: 8761
eureka:
client:
register-with-eureka: false
fetch-registry: false
service-url:
defaultZone:http://user:password@localhost:8761/eureka
最后:创建EurekaApplication,并添加@SpringBootApplication
@EnableEurekaServer
public class EurakaApplication{
public static void main(String[] args) throws Exception {
SpringApplication.run(EurakaApplication.class, args);
}
}
就可以启动Eureka Server了
3.4 将微服务注册到Eureka上
首先:确认当前maven环境下是否引入了Eureka相关的配置,并且添加如下依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
其次:在微服务启动类上添加@EnableEurekaClient注解,使得他成为一个Eureka Client,在启动的 时候就向Eureka Server注册。
然后:在微服务的应用程序中application.yml配置文件中,我们不要添加如下配置,因为这表示是服务器,客户端我们是需要向Eureka注册的
eureka:
client:
register-with-eureka: false
fetch-registry: false
应该可以的配置有健康检查和路径配置
eureka:
client:
healthcheck:
enabled: true
service-url:
defaultZone:http://nicky:123abcABC@localhost:8761/eureka
最后:在启动类添加@EnableEurekaClient注解,表示这个类可以作为Eureka 客户端,启动之后可以向Eureka注册
@SpringBootApplication
@EnableEurekaClient
public class UserServiceRunner{
public static void main(String[] args) throws Exception {
SpringApplication.run(UserServiceRunner.class, args);
}
}
3.5 Eureka配置项
eureka.client.allow-redirects:是否允许重定向Eureka客户端请求到其他或者备份服务器,默认为fasle
eureka.client.eureka-connection-idle-timeout-seconds:HTTP连接到eureka服务器可以在关闭之前保持空闲的时间(几秒钟)。
eureka.client.eureka-server-connect-timeout-seconds:表示连接Eureka服务器,等待多长时间算超时
eureka.client.eureka-server-port: Eureka Server端口
eureka.client.eureka-server-d-n-s-name:获取要查询的DNS名称以获得eureka服务器的列表。
eureka.client.eureka-server-read-timeout-seconds:示在从eureka服务器读取数据之前需要等待多长时间(以秒为单位)
eureka.client.eureka-server-total-connections:从eureka客户端到所有eureka服务器的所允许连接总数。
eureka.client.eureka-server-total-connections-per-host:设置每一个主机所允许的到Eureka Server连接的数量
eureka.client.fetch-registry: 是否允许客户端向Eureka 注册表获取信息,一般服务器为设置为false,客户端设置为true
eureka.client.register-with-eureka:是否允许向Eureka Server注册信息,默认true,如果是服务器端,应该设置为false
eureka.client.fetch-remote-regions-registry:逗号分隔的区域列表,用于获取eureka注册信息
eureka.client.g-zip-content:从服务器端获取数据是否需要压缩
eureka.client.prefer-same-zone-eureka: 是否优先使选择相同Zone的实例,默认为true
eureka.client.registry-fetch-interval-seconds:多长时间从Eureka Server注册表获取一次数据,默认30s
eureka.client.service-url:可用区域映射,列出完全合格的url与eureka服务器通信。每个值可以是一个URL,也可以是一个逗号分隔的替代位置列表。
eureka.dashboard.enabled: 是否启用Eureka首页,默认为true
eureka.dashboard.path: 默认为/
eureka.instance.appname:在eureka注册的应用程序的名称。
eureka.instance.app-group-name:在eureka注册的应用程序的组名称
eureka.instance.health-check-url: 健康检查绝对路径
eureka.instance.health-check-url-path:健康检查相对路径
eureka.instance.hostname:设置主机名
eureka.instance.instance-id:设置注册实例的id
eureka.instance.lease-expiration-duration-in-seconds:设置多长时间意味着租约到期,默认90
eureka.instance.lease-renewal-interval-in-seconds:表示Eureka客户端需要发送心跳到eureka服务器的频率(以秒为单位),以表明它仍然存在。指定的期间内如果没有收到心跳leaseExpirationDurationInSeconds
eureka.instance.metadata-map:可以设置元数据
eureka.instance.prefer-ip-address: 实例名以IP,但是建议hostname,默认为false
四 负载均衡
4.1 Ribbon的介绍和架构
实现负载均衡,我们可以通过服务器端和客户端做负载均衡。服务器端做负载均衡,比如可以使用Nginx。而客户端做负载均衡,就是客户端有一个组件,知道有哪些可用的微服务,实现一个负载均衡的算法。
Ribbon工作流程主要分为两步:
第一:先选择Eureka Server,优先选择在同一个Zone且负载较少的Server;
第二:再根据用户指定的策略,再从server取到的服务注册列表中选择一个地址。其中Ribbon提供了很多种策略,例如轮询round bin,随机Random,根据响应时间加权。
4.2 使用Ribbon进行负载均衡
4.2.1 基本用法
要使用Ribbon进行负载均衡,那么就需要引入对应的依赖,如果已经因如果Eureka的依赖,那么就不需要再次引入Ribbon的依赖了。
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-ribbonartifactId>
dependency>
然后在启动类上加上注解@LoadBalanced,则负载均衡生效。
@SpringBootApplication
@EnableEurekaClient
public classMovieServiceRibbonRunner {
@Bean
@LoadBalanced
public RestTemplaterestTemplate(){
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(MovieServiceRibbonRunner.class, args);
}
}
我们可以修改application.yml文件的instance_id属性:
eureka:
client:
healthcheck:
enabled: true
service-url:
defaultZone:http://nicky:123abcABC@localhost:8761/eureka
instance:
instance-id: ${spring.application.name}:${spring.application.instance_id:${server.port}}
在消费者Controller中,访问虚拟的ip,即我们微服务的名称
@RestController
public class MovieController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/movie/{id}")
public User findById(@PathVariable Long id) {
returnthis.restTemplate.getForObject("http://microservice-provider-user/user/"+id, User.class);
}
}
最后启动两个服务提供者实例,可以修改端口实现。
4.2.2 通过代码自定义配置Ribbon
首先,该类需要加上@Configuration注解,但是注意,如果加上这个注解,他就不能包含在注解@ComponentScan或者@SpringBootApplication所指定包扫描路径。
@Configuration
public classRibbonTestConfiguration {
@Autowired
IClientConfig config;
@Bean
public IRuleribbonRule(IClientConfig config){
return new RandomRule();
}
}
其次:在启动类上加上@RibbonClient注解,指定名称和configuration文件类
@SpringBootApplication
@EnableEurekaClient
@RibbonClient(name="microservice-provider-user",configuration=RibbonTestConfiguration.class)
public classMovieServiceRunner {
@Bean
@LoadBalanced
public RestTemplaterestTemplate(){
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(MovieServiceRunner.class, args);
}
}
4.2.3 通过配置文件自定义Ribbon
即我们可以通过yml或者properties配置文件,进行配置,然后使用我们配置的选项。
注意:这里有些优先级的顺序问题:
配置文件定义的优先级大于通过使用Java代码@RibbonClient的优先级大于使用spring默认的优先级
支持以下属性:
NFLoadBalancerClassName:应该实现 ILoadBalancer
NFLoadBalancerRuleClassName:应该实现 IRule
NFLoadBalancerPingClassName:应该实现IPing
NIWSServerListClassName:应该实现 ServerList
NIWSServerListFilterClassName:应该实现 ServerListFilter
application.yml中配置如下:
microservice-provider-user:
ribbon:
NFLoadBalancerRuleClassName:com.netflix.loadbalancer.RandomRule
五 Feign (声明式的REST Client)
5.1 简介及基础使用
Feign是一个声明式的web服务客户端,它使得写web服务客户端更加容易。使用Feign创建一个接口并对其进行注解。它具有可插拔的注解支持,包括Feign自己的注解以及jax-rs注释。Feign还支持可插拔的的编码器和解码器。Spring Cloud增加了对Spring MVC注解的支持,并使用了在Spring Web中默认使用的相同的HttpMessageConverters。Spring Cloud集成了Ribbon和灵感,在使用时提供了负载均衡的http客户端。
加入Feign的依赖到Maven
然后创建接口,并且添加注解@EnableFeignClients
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public classMovieServiceRunner {
public static void main(String[] args) {
SpringApplication.run(MovieServiceRunner.class, args);
}
}
写真正的接口去调用其他微服务,以供本应用调用
创建接口:
首先在接口加上注解@FeignClient,指定需要调用微服务名字
其次:如果启动的时候,提示Http Method不正确我们需要使用以前的老的注解,即:
@RequestMapping(method= RequestMethod.GET, value = "/user/{id}"),而不能使用:
@GetMapping("/user/{id}")
然后:如果提示PathVariableannotation was empty on param 0,那么我们就需要:
还有就是:微服务提供方和消费者两边的HTTP 方法必须最好一致,如果一边是GET 请求,另外一边是POST请求,就会报错。而且如果传递的是一个负载对象,即使指定的是GET请求,也会作为POST请求,一般做法就是不传递对象,而是传递单个参数
比如@ReqquestParam(“name”) String name,@ReqquestParam(“age”) int age之类的
public UserfindById(@PathVariable("id") Long id); 而不是:
public UserfindById(@PathVariable Long id); 就可以
这相当于是Feign的坑吧
@FeignClient("microservice-provider-user")
public interface UserFeignClient {
@GetMapping("/user/{id}")
public User findById(@PathVariable Long id);
}
然后在其他类中就可以调用了:
@RestController
public class MovieController {
@Autowired
private UserFeignClient userFeignClient;
@GetMapping("/movie/{id}")
public User findById(@PathVariable Long id) {
return userFeignClient.findById(id);
}
}
5.2 覆写Feign的配置
Spring Cloud允许你完全控制Feign 客户端通过声明一些额外的注解,即@FeignClient(name = "stores",configuration = FooConfiguration.class)
一般来说,我们不需要使用在自定义的FooConfiguration上使用@Configuration组件,如果要使用我们需要把它从包含@ComponentScan或者@SpringBootApplication注解所扫描的包中排除掉。和Ribbon类似。
假设我们现在定义了一个Configuration1这个类,使用feign默认的契约,而不是使用SpringMvcContract,即:
@Configuration
public class Configuration1 {
@Bean
public ContractfeignContract() {
return newfeign.Contract.Default();
}
}
然后在Feign的接口处,指定@FeignClient的configuration,重写配置,使用刚才定义的Configuration1,即:
@FeignClient(name="microservice-provider-user",configuration=Configuration1.class)
public interface UserFeignClient {
@GetMapping("/user/{id}")
public User findById(@PathVariable("id") Long id);
}
但是在这儿我们依然用的是SpringMVC的注解,GetMapping,这里虽然没有报错,但是启动的时候就会报错了。比如java.lang.IllegalStateException: MethodfindById not annotated with HTTP method type (ex. GET, POST)
所以,接口处我们应该替换为Feign自己的注解。
@FeignClient(name="microservice-provider-user",configuration=Configuration1.class)
public interface UserFeignClient {
@RequestLine("GET /user/{id}")
public User findById(@Param("id") Long id);
}
Feign支持请求响应GZIP压缩:
feign.compression.request.enabled=true
feign.compression.response.enabled=true
5.3 feign的日志
Feign的默认日志级别是DEBUG 级别。
@Bean
Logger.Level feignLoggerLevel(){
return Logger.Level.FULL;
}
六 常见问题解决方案
6.1 Eureka环境以及cloud配置
Eureka可以运行AWS环境和非AWS环境上,Eureka也可以以测试环境和生产环境运行。
如果需要运行在AWS环境上,则需要通过-Deureka.datacenter=cloud指定运行在AWS上,在yml中我们可以通过eureka.datacenter:cloud指定。
eureka.datacenter:cloud
如果需要运行在测试或者生产环境,我们需要通过-Deureka.environment来指定。如果指定测试环境eureka.environment: test, 如果运行在生产环境,则指定eureka.environment:product
6.2 自我保护提示
6.3 Eureka注册服务慢的问题如何解决
作为实例还涉及到与注册中心的周期性心跳,默认持续时间为30秒(通过serviceUrl)。在实例、服务器、客户端都在本地缓存中具有相同的元数据之前,服务不可用于客户端发现(所以可能需要3次心跳)。你可以使用eureka.instance.leaseRenewalIntervalInSeconds配置,这将加快客户端连接到其他服务的过程。
在生产中,最好坚持使用默认值,因为在服务器内部有一些计算,他们对续约做出假设。
6.4如何解决Eureka Server不踢出已关停的节点的问题
服务器端:
# 关闭自我保护
eureka.server.enable-self-preservation:false
# 缩小清理间隔(单位毫秒,默认是60*1000)
eureka.server.eviction-interval-timer-in-ms: 5000
客户端:
开启健康检查(需要spring-boot-starter-actuator依赖)
eureka.client.healthcheck.enabled= true
租期更新时间间隔(默认30秒)
eureka.instance.lease-renewal-interval-in-seconds=10
租期到期时间(默认90秒)
eureka.instance.lease-expiration-duration-in-seconds=30
6.5 Eureka HA配置(假设三个节点)
6.5.1 在hosts文件中加入如下配置
127.0.0.1 peer1
127.0.0.1 peer2
127.0.0.1 peer3
6.5.2 在application.yml中加入以下配置
---
spring:
profiles: peer1
application:
name: EUREKA-HA
server:
port: 8761
eureka:
instance:
hostname: peer1
client:
serviceUrl:
defaultZone:http://peer2:8762/eureka/,http://peer3:8763/eureka/
---
spring:
profiles: peer2
application:
name: EUREKA-HA
server:
port: 8762
eureka:
instance:
hostname: peer2
client:
serviceUrl:
defaultZone:http://peer1:8761/eureka/,http://peer3:8763/eureka/
---
spring:
profiles: peer3
application:
name: EUREKA-HA
server:
port: 8763
eureka:
instance:
hostname: peer3
client:
serviceUrl:
defaultZone:http://peer1:8761/eureka/,http://peer2:8762/eureka/