Eureka注册中心(二)
更多内容参见个人技术博客,无广告欢迎关注
11.1 注册中心Eureka
1.1.1 注册中心
注意它的特点,结构类似于MessageQueue消息队列,服务(提供者、消费者)先都注册到注册中心。它的特点在于,不会每次都去注册中心获取,而是有本地缓存,加快访问性能。内部含有心跳机制,当注册中心信息改变,自动快速获取新的信息到本地。心跳机制还保证分布式环境下,某个服务失败后,自动列表从注册中心移除。注册中心中保证所有可用的链接。
1.1.2 拓展:CAP定理
CAP原则又称CAP定理,指的是在一个分布式系统中,Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性),三者不可得兼。它是分布式系统中最核心最重要的理论。
分布式系统的CAP理论:理论首先把分布式系统中的三个特性进行了如下归纳:
l 一致性(C):在分布式系统中的所有数据备份,在同一时刻是否同样的值。(等同于所有节点访问同一份最新的数据副本)
l 可用性(A):在集群中一部分节点故障后,集群整体是否还能响应客户端的读写请求。(对数据更新具备高可用性)
l 分区容错性(P):以实际效果而言,分区相当于对通信的时限要求。系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况,必须就当前操作在C和A之间做出选择。
CAP理论就是说在分布式系统中,最多只能实现上面的两点。而由于当前的网络硬件肯定会出现延迟丢包等问题,所以分区容忍性是我们必须需要实现的。所以我们只能在一致性和可用性之间进行权衡,要么选择CP要么选择AP,没有分布式系统能同时保证这三点。
1.1.3 ZooKeeper和Eureka对比
Eureka本身是Netflix开源的一款提供服务注册和发现的产品,并且提供了相应的Java封装。在它的实现中,节点之间相互平等,部分注册中心的节点挂掉也不会对集群造成影响,即使集群只剩一个节点存活,也可以正常提供发现服务。哪怕是所有的服务注册节点都挂了,Eureka Clients(客户端)上也会缓存服务调用的信息。这就保证了我们微服务之间的互相调用足够健壮。
Zookeeper主要为大型分布式计算提供开源的分布式配置服务、同步服务和命名注册。曾经是Hadoop项目中的一个子项目,用来控制集群中的数据,目前已升级为独立的顶级项目。很多场景下也用它作为Service发现服务解决方案。
对比
根据著名的CAP定理(C-数据一致性;A-服务可用性;P-服务对网络分区故障的容错性CAP这三个特性在任何分布式系统中不能同时满足,最多同时满足两个CP或者AP)。
ZooKeeper
Zookeeper是基于CP来设计的,即任何时刻对Zookeeper的访问请求能得到一致的数据结果,同时系统对网络分割具备容错性,但是它不能保证每次服务请求的可用性。从实际情况来分析,在使用Zookeeper获取服务列表时,如果zookeeper正在选主,或者Zookeeper集群中半数以上机器不可用,那么将无法获得数据。所以说,Zookeeper不能保证服务可用性。
诚然,在大多数分布式环境中,尤其是涉及到数据存储的场景,数据一致性应该是首先被保证的,这也是zookeeper设计成CP的原因。但是对于服务发现场景来说,情况就不太一样了:针对同一个服务,即使注册中心的不同节点保存的服务提供者信息不尽相同,也并不会造成灾难性的后果。因为对于服务消费者来说,能消费才是最重要的——拿到可能不正确的服务实例信息后尝试消费一下,也好过因为无法获取实例信息而不去消费。(尝试一下可以快速失败,之后可以更新配置并重试)所以,对于服务发现而言,可用性比数据一致性更加重要——AP胜过CP。
Eureka
而Spring Cloud Netflix在设计Eureka时遵守的就是AP原则。Eureka Server也可以运行多个实例来构建集群,解决单点问题,但不同于ZooKeeper的选举leader的过程,Eureka Server采用的是Peer to Peer对等通信。这是一种去中心化的架构,无master/slave区分,每一个Peer都是对等的。在这种架构中,节点通过彼此互相注册来提高可用性,每个节点需要添加一个或多个有效的serviceUrl指向其他节点。每个节点都可被视为其他节点的副本。
如果某台Eureka Server宕机,Eureka Client的请求会自动切换到新的Eureka Server节点,当宕机的服务器重新恢复后,Eureka会再次将其纳入到服务器集群管理之中。当节点开始接受客户端请求时,所有的操作都会进行replicateToPeer(节点间复制)操作,将请求复制到其他Eureka Server当前所知的所有节点中。
一个新的Eureka Server节点启动后,会首先尝试从邻近节点获取所有实例注册表信息,完成初始化。Eureka Server通过getEurekaServiceUrls()方法获取所有的节点,并且会通过心跳续约的方式定期更新。默认配置下,如果Eureka Server在一定时间内没有接收到某个服务实例的心跳,Eureka Server将会注销该实例(默认为90秒,通过eureka.instance.lease-expiration-duration-in-seconds配置)。当Eureka Server节点在短时间内丢失过多的心跳时(比如发生了网络分区故障),那么这个节点就会进入自我保护模式。
总结
ZooKeeper基于CP,不保证高可用,如果zookeeper正在选主,或者Zookeeper集群中半数以上机器不可用,那么将无法获得数据。Eureka基于AP,能保证高可用,即使所有机器都挂了,也能拿到本地缓存的数据。作为注册中心,其实配置是不经常变动的,只有发版(发布新的版本)和机器出故障时会变。对于不经常变动的配置来说,CP是不合适的,而AP在遇到问题时可以用牺牲一致性来保证可用性,既返回旧数据,缓存数据。
所以理论上Eureka是更适合作注册中心。而现实环境中大部分项目可能会使用ZooKeeper,那是因为集群不够大,并且基本不会遇到用做注册中心的机器一半以上都挂了的情况。所以实际上也没什么大问题。
1.1.4 拓展:分布式对关系型数据库的冲击
对于web网站来说,关系数据库的很多主要特性却往往无用武之地
l 数据库事务一致性需求
很多web实时系统并不要求严格的数据库事务,对读一致性的要求很低,有些场合对写一致性要求并不高。允许实现最终一致性。
l 数据库的写实时性和读实时性需求
对关系数据库来说,插入一条数据之后立刻查询,是肯定可以读出来这条数据的,但是对于很多web应用来说,并不要求这么高的实时性,比方说发一条消息之后,过几秒乃至十几秒之后,我的订阅者才看到这条动态是完全可以接受的。如:MQ消息队列机制,意义,可以解决瞬时的高并发,消峰填谷作用。
l 对复杂的SQL查询,特别是多表关联查询的需求
任何大数据量的web系统,都非常忌讳多个大表的关联查询,以及复杂的数据分析类型的报表查询,特别是SNS类型的网站,从需求以及产品设计角度,就避免了这种情况的产生。往往更多的只是单表的主键查询,以及单表的简单条件分页查询,SQL的功能被极大的弱化了。
SNS:全称Social Networking Services,专指社交网络服务,包括了社交软件和社交网站。例如:脸谱facebook、腾讯QQ、微信等。
1.1.5 自我保护模式
什么是自我保护模式?默认配置下,如果Eureka Server每分钟收到心跳续约的数量低于一个阈值(instance的数量(60/每个instance的心跳间隔秒数)自我保护系数),并且持续15分钟,就会触发自我保护。在自我保护模式中,Eureka Server会保护服务注册表中的信息,不再注销任何服务实例。当它收到的心跳数重新恢复到阈值以上时,该Eureka Server节点就会自动退出自我保护模式。它的设计哲学前面提到过,那就是宁可保留错误的服务注册信息,也不盲目注销任何可能健康的服务实例。该模式可以通过eureka.server.enable-self-preservation = false来禁用,同时eureka.instance.lease-renewal-interval-in-seconds可以用来更改心跳间隔。
1.1.6 调用关系图
常见的服务发现产品:Eureka/Consul/ZooKeeper,Eureka轻量级。
1.2 Eureka服务端
1.2.1 创建Maven工程
父工程spring-cloud
spring-cloud工程的pom.xml
<?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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.wood</groupId>
<artifactId>spring-cloud</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-cloud</name>
<description>Demo project for Spring Boot</description>
<!--Maven项目可以继承,三个子工程-->
<modules>
<module>eureka-server</module>
<module>provider-a</module>
<module>provider-b</module>
</modules>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.RELEASE</spring-cloud.version>
</properties>
<dependencies>
<!--公共依赖包-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</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>
</plugins>
</build>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
</repository>
</repositories>
</project>
子工程eureka-server
子工程服务提供者A provider-a
子工程服务提供者B provider-b
1.2.2 eureka-server服务的 pom.xml
<?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>
<!--父工程不在是spring-boot,而是spring-cloud-->
<parent>
<groupId>com.wood</groupId>
<artifactId>spring-cloud</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.wood</groupId>
<artifactId>eureka-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>eureka-server</name>
<description>Demo project for Spring Boot</description>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
</project>
1.2.3 application.properties
spring.application.name=eureka-server
server.port=6001
#eureka
# 注册中心配置
eureka.instance.preferIpAddress=true
eureka.instance.instance-id=${spring.cloud.client.ipAddress}:${server.port}
eureka.instance.lease-renewal-interval-in-seconds=5
eureka.instance.lease-expiration-duration-in-seconds=20
eureka.client.serviceUrl.defaultZone=http://localhost:6001/eureka
eureka.client.registry-fetch-interval-seconds=10
#不要向注册中心注册自己
eureka.client.register-with-eureka=false
#禁止检索服务
eureka.client.fetch-registry=false
#打印日志(开发模式)
logging.level.root=DEBUG
1.2.4 EurekaServerApplication.java
package com.wood.eurekaserver;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
1.2.5 监控地址
地址:http://localhost:6001
启动后形成一个控制台
1.3 服务提供者
1.3.1 创建Maven工程 provider-a
1.3.2 pom.xml
注意提供者是作为Eureka的客户端,这样启动后自动注册到EurekaServer中。
<?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>
<parent>
<groupId>com.wood</groupId>
<artifactId>spring-cloud</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.wood</groupId>
<artifactId>provider-a</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>provider-a</name>
<description>Demo project for Spring Boot</description>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
</project>
1.3.3 application.properties
注意多个提供端的差异在于端口不同,将来同一个业务服务多实例如何标识呢?就是通过端口来标识。
server.port=8081
spring.application.name=provider-user
eureka.client.serviceUrl.defaultZone=http://localhost:6001/eureka
1.3.4 HelloController.java
package com.wood.providera.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@GetMapping("/hello/{name}")
public String hello(@PathVariable String name) {
return "服务提供者A:"+name;
}
}
1.3.5 ProviderRunApp.java
package com.wood.providera;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
public class ProviderAApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderAApplication.class, args);
}
}
1.4 服务提供者2
1.4.1 创建Maven工程
创建项目,复制文件进行修改。主要
1.4.2 application.properties
server.port=8082
spring.application.name=provider-user
eureka.client.serviceUrl.defaultZone=http://localhost:6001/eureka
1.4.3 HelloController.java
修改打印的页面展现的值,方便观察是哪个提供者响应的。
package com.wood.providerb.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@GetMapping("/hello/{name}")
public String hello(@PathVariable String name) {
return "服务提供者B:"+name;
}
}
1.4.4 Provider2RunApp.java
修改类名
package com.wood.providerb;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
public class ProviderBApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderBApplication.class, args);
}
}
附上demo
spring-cloud.zip
https://pan.baidu.com/s/1aEg6SBim9xlPhSToBFOKrw