spring cloud 服务治理 - Eureka
前言
在分布式系统领域有个著名的CAP定理:
C——数据一致性;
A——服务可用性;
P——服务对网络分区故障的容错性。
这三个特性在任何分布式系统中不能同时满足,最多同时满足两个。
Zookeeper是著名Hadoop的一个子项目,很多场景下Zookeeper也作为Service发现服务解决方案。Zookeeper保证的是CP,即任何时刻对Zookeeper的访问请求能得到一致的数据结果,同时系统对网络分割具备容错性,但是它不能保证每次服务请求的可用性。
而Spring Cloud Netflix在设计Eureka时遵守的就是AP原则。因为对于服务消费者来说,能消费才是最重要的——拿到可能不正确的服务实例信息后尝试消费一下,也好过因为无法获取实例信息而不去消费。所以,对于服务发现而言,可用性比数据一致性更加重要——AP胜过CP。
1、服务注册与服务发现
服务治理是微服务架构中最为核心和基础的模块,主要为各个微服务实例提供自动化注册与发现功能。
服务注册:服务治理框架会构建一个注册中心,每个服务向注册中心注册登记自己提供的服务,将服务名、主机、端口号、版本号、通信协议等告知注册中心,注册中心按服务名分类组织成一个服务清单。
例如:
有三个提供订单服务的实例分别运行在 192.168.0.100:8081、192.168.0.101:8082、192.168.0.102:8083;
还有四个提供用户服务的实例分别运行在 192.168.1.100:8091、192.168.1.101:8092、192.168.1.102:8093、192.168.1.103:8094;
当这些服务全部启动并成功注册到注册中心上后,注册中心会维护一份类似下面的一个服务清单:
服务名 | 服务地址 |
订单服务 | 192.168.0.100:8081、192.168.0.101:8082、192.168.0.102:8083 |
用户服务 | 192.168.1.100:8091、192.168.1.101:8092、192.168.1.102:8093、192.168.1.103:8094 |
服务发现:在服务治理框架下,服务之间不再通过具体实例地址来相互调用,而是通过向注册中心咨询并获取服务清单,以实现(通过服务名)对具体服务的调用。
例如:前台用户下单需要调用订单服务,首先向注册中心发起咨询订单服务的请求,注册中心返回订单服务位置清单,然后调用方通过负载策略选择其中某一个位置进行服务调用即可。
2、单点注册中心
创建一个空项目,pom.xml如下(后面都基于这个pom):
<?xml version="1.0" encoding="UTF-8"?> <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.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.springcloud.demo</groupId> <artifactId>springcloud-demo</artifactId> <packaging>pom</packaging> <version>1.0-SNAPSHOT</version> <modules> <module>eureka-server</module> <module>eureka-service-provider</module> </modules> <name>Maven</name> <!-- FIXME change it to the project's website --> <url>http://maven.apache.org/</url> <inceptionYear>2001</inceptionYear> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.8.RELEASE</version> <relativePath/> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <java.version>1.8</java.version> <spring-cloud.version>Finchley.RELEASE</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> <plugin> <artifactId>maven-surefire-plugin</artifactId> <configuration> <failIfNoTests>true</failIfNoTests> </configuration> </plugin> </plugins> </build> </project>
1、服务注册中心
首先搭建一个注册中心,命名为eureka-server,引入eureka依赖包
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency>
在 Spring Boot 启动入口文件上加注解 @EnableEurekaServer
@SpringBootApplication @EnableEurekaServer public class EurekaServerApplication { public static void main(String[] args) { SpringApplication.run( EurekaServerApplication.class, args ); } }
然后配置application.yml:
server: port: 8081 eureka: instance: hostname: eurekaserver client: registerWithEureka: false fetchRegistry: false serviceUrl: defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ spring: application: name: eurka-server
其中:
eureka.client.registerWithEureka = false 设置为false表示不向注册中心注册自己,默认为true
eureka.client.fetchRegistry = false 设置为false表示不需要去检索服务,默认为true
启动应用并访问 http://localhost:8081/ ,如下图所示:
可以看到注册中心还没有注册任何服务,至此注册中心已经搭建完毕。
2、服务提供者
搭建好注册中心后,我们再来创建一个服务提供者,然后将其注册到注册中心。新建一个项目eureka-service-provider,添加eureka依赖,在 Spring Boot 启动入口文件上加注解 @EnableEurekaClient
@SpringBootApplication @EnableEurekaClient public class EurekaServiceApplication { public static void main(String[] args) { SpringApplication.run( EurekaServiceApplication.class, args ); } }
配置application.yml:
server: port: 8082 eureka: instance: hostname: service-provider client: serviceUrl: defaultZone: http://localhost:8081/eureka/ spring: application: name: service-1
因为需要向注册中心注册,所以 eureka.client.registerWithEureka、eureka.client.fetchRegistry 不要配置为 false
启动eureka-service-provider,刷新注册中心,可以看到服务已经注册成功了:
同时eureka-server控制台会打印一行日志:Registered instance SERVICE-1/xxxx:service-1:8082 with status UP (replication=false)
3、高可用注册中心
上面只是一个单点注册中心,微服务场景下应用通过注册中心来访问真实服务,如果注册中心挂了会造成服务无法调用,所以注册中心也得要保持高可用,Eureka 不可能没有想到这一点,其实在Eureka里,所有结点既是服务提供方,又是服务消费方,注册中心也不例外。
大家还记得在单节点配置里讲到的这两个配置吗:
eureka.client.registerWithEureka = false
eureka.client.fetchRegistry = false
实际上 Eureka 高可用方案就是将自己作为服务向其他服务注册中心注册自己,这样就可以形成一组相互注册的服务注册中心,在这组服务注册中心内部相互同步注册清单,形成一个高可用集群
注册中心集群
创建以下三个Eureka Server:peer1、peer2、peer3
peer1的配置如下(peer2、peer3配置类似):
server: port: 8081 eureka: instance: hostname: peer1 client: serviceUrl: defaultZone: http://peer2:8082/eureka/,http://peer3:8083/eureka/ spring: application: name: eurka-server
分别启动peer1、peer2、peer3,访问peer1:8081
单点故障
这时候如果peer2突然挂了,访问peer1和peer3仍然可以看到peer2,这是因为Eureka默认开启了自我保护机制(peer2挂了没有剔除),可以通过设置 eureka.server.enable-self-preservation=false 来禁用保护
peer1的配置文件里新增eureka.server.enable-self-preservation=false设置,然后模拟peer3挂了,peer1注册中心如下:
大概两分钟左右,从peer1注册中心可以看到 registered-replicas 有3个,unavailable-replicas 里有 peer3,available-replicas 里只有 peer1 和 peer2 可用,
而从 peer2 里看 peer3 仍然可用(过比较长的时间,peer2 仍然会将 peer3 从 available-replicas 里剔除)
将serverce-1注册到注册中心peer1,然后在peer1 和 peer2里都可以看到service-1。
故障恢复
peer3 经过运维处理恢复了,重新启动peer3自动加入了peer1和peer2集群,可用看到service-1也自动注册到peer3上了:
4、配置
服务注册相关配置:eureka.instance 为前缀对应的Class
org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean
服务实例相关配置:eureka.client 为前缀对应的Class
org.springframework.cloud.netflix.eureka.EurekaClientConfigBean
服务配置属性以eureka.server为前缀对应的Class
org.springframework.cloud.netflix.eureka.server.EurekaServerConfigBean
client常见配置
# 启用Eureka客户端
eureka.client.enabled=true
# 从Eureka服务端获取注册信息的间隔时间(单位:秒)
eureka.client.registry-fetch-interval-seconds=30
# 是否从Eureka服务端获取注册信息
eureka.client.fetch-registry=true
# 是否要将自身的实例信息注册到Eureka服务端
eureka.client.register-with-eureka=true
# 初始化实例信息的变化到Eureka服务端的间隔时间(单位:秒)
eureka.client.instance-info-replication-interval-seconds=40
# 从Eureka客户端到Eureka服务端的连接总数
eureka.client.eureka-server-total-connections=200
# 获取实例时是否过滤,仅保留UP状态的实例
eureka.client.filter-only-up-instances=true
server常见配置
#设为false,关闭自我保护
eureka.server.enable-self-preservation=false
#表示是否将自己注册到Eureka Server,默认为true
eureka.client.register-with-eureka=false
#表示是否从Eureka Server获取注册信息,默认为true
eureka.client.fetch-registry=false
# 扫描失效服务的间隔时间(单位毫秒,默认是60*1000)即60秒
eureka.server.eviction-interval-timer-in-ms=5000
#设置 eureka server同步失败的等待时间 默认 5分
#在这期间,它不向客户端提供服务注册信息
eureka.server.wait-time-in-ms-when-sync-empty=5
#设置 eureka server同步失败的重试次数 默认为 5 次
eureka.server.number-of-replication-retries=5
#自我保护系数(默认0.85)
eureka.server.renewal-percent-threshold=0.85
instance常见配置
#是否优先使用IP地址作为主机名的标识,如果不配置就是机器的主机名
eureka.instance.prefer-ip-address=false
#Eureka客户端向服务的发送心跳的时间间隔(单位:秒)
eureka.instance.lease-renewal-interval-in-seconds=30
#Eureka服务的收到最后一次心跳之后等待的时间上限(单位:秒),
#超过这个时间之后会将该服务实例从注册中心清单里剔除
eureka.instance.lease-expiration-duration-in-seconds=90