Eureka注册中心
一、什么是注册中心
注册中心可以说是微服务架构中的“通讯录”,它记录了服务和服务地址的映射关系。在分布式架构中,服务会注册到这里,当服务需要调用其它服务时,就到这里找到服务的地址,进行调用。
总结:服务注册中心的作用就是服务的注册和服务的发现
二、常见的注册中心
- Netflix Eureka
- Alibaba Nacos
- HashiCorp Consul
- Apache ZooKeeper
特性 | Eureka | Nacos | Consul | Zookeeper |
---|---|---|---|---|
CAP | AP | CP+AP | CP | CP |
健康检查 | Client Beat | TCP/HTTP/MYSQL/Client Beat | TCP/HTTP/gRPC/Cmd | Keep Alive |
雪崩保护 | 有 | 有 | 无 | 无 |
自动注销实例 | 支持 | 支持 | 不支持 | 不支持 |
访问协议 | HTTP | HTTP/DNS | HTTP/DNS | TCP |
监听支持 | 支持 | 支持 | 支持 | 支持 |
多数据中心 | 支持 | 支持 | 支持 | 不支持 |
跨注册中心同步 | 不支持 | 支持 | 支持 | 不支持 |
SpringCloud集成 | 支持 | 支持 | 支持 | 支持 |
三、为什么需要注册中心
在分布式系统中,我们不仅仅是需要在注册中心找到服务和服务地址的映射关系这么简单,我们还需要考虑更多复杂的问题:
- 服务注册后,如何及时发现
- 服务宕机后,如何及时下线
- 服务如何有效的水平扩展
- 服务发现时,如何进行路由
- 服务异常时,如何进行降级
- 注册中心如何实现自身的高可用
四、注册中心解决了什么问题
- 服务管理
- 服务的依赖管理
五、什么是Eureka注册中心
Eureka是Netflix开发的服务发现组件,本身是一个基于REST的服务。Spring Cloud将它集成在子项目Spring Cloud Netflix中,实现Spring Cloud的注册与发现,同时还提供了负载均衡、故障转移等能力。
六、Eureka注册中心三种角色
-
Eureka Server
通过Register、Get、Renew等接口提供服务的注册和发现
-
Application Service(Service Provider)
服务提供方,把自身的服务实例注册到 Eureka Server 中
-
Application Client(Service Consumer)
服务调用方,通过 Eureka Server 获取服务列表,消费服务
-
Register(服务注册):把自己的IP和端口注册给Eureka
-
Renew(服务续约):发送心跳包,每30秒发送一次,告诉Eureka自己还活着,如果90秒还未发送心跳,宕机
-
Cancel(服务下线):当provider关闭时会向Eureka发送消息,把自己从服务列表中删除
-
Get Register(获取注册列表):获取其他服务列表
-
Replicate(数据同步):Eureka集群中的数据复制与同步
-
make remote call(远程调用):服务间的远程调用
七、HelloWorld
实例代码:https://gitee.com/junweihu/eureka-demo
-
创建父项目,添加依赖
<?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>org.example</groupId> <artifactId>eureka-demo</artifactId> <packaging>pom</packaging> <version>1.0-SNAPSHOT</version> <modules> <module>eureka-server</module> </modules> <!-- 集成spring-boot-starter-parent依赖 --> <!-- 使用集成方式了,实现复用 --> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.4.RELEASE</version> </parent> <!-- 集中定义依赖组件版本号,但不引入,在子工程中用到声明的依赖时,可以不加依赖的版本号,统一管理工程中用到的依赖版本号 --> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> <spring-cloud.version>Hoxton.SR1</spring-cloud.version> </properties> <!-- 项目依赖管理,父项目只是声明依赖,子项目需要写明需要的依赖(可以省略版本信息) --> <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> </project>
-
创建子项目,添加依赖
<?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"> <!-- 继承父依赖 --> <parent> <artifactId>eureka-demo</artifactId> <groupId>org.example</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>eureka-server</artifactId> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> </dependencies> </project>
-
配置文件
server: port: 8761 spring: application: name: eureka-server eureka: instance: hostname: localhost # 主机名,不配置的时候将根据操作系统的主机名获取 client: register-with-eureka: false # 是否将自己注册到注册中心 fetch-registry: false # 是否从注册中心获取注册信息 service-url: # 注册中心对外暴露的注册地址 defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
-
启动类
@EnableEurekaServer @SpringBootApplication public class EurekaServerApplication { public static void main( String[] args ) { SpringApplication.run(EurekaServerApplication.class); } }
-
访问
访问:http://localhost:8761/
八、高可用Eureka注册中心
注册中心
-
创建项目
在刚才的父工程下再创建一个 eureka-server02 注册中心的项目,如果是多台机器部署不用修改端口,通过IP区分服务,如果在一台机器上部署需要修改端口区分服务。
-
修改配置文件
eureka-server
server: port: 8761 spring: application: name: eureka-server eureka: instance: hostname: localhost # 主机名,不配置的时候将根据操作系统的主机名获取 client: # register-with-eureka: false # 是否将自己注册到注册中心 # fetch-registry: false # 是否从注册中心获取注册信息 # 注册中心多个节点相互注册 service-url: # 注册中心对外暴露的注册地址 defaultZone: http://localhost:8762/eureka/
eureka-server02
server: port: 8762 spring: application: name: eureka-server eureka: instance: hostname: localhost # 主机名,不配置的时候将根据操作系统的主机名获取 client: # register-with-eureka: false # 是否将自己注册到注册中心 # fetch-registry: false # 是否从注册中心获取注册信息 # 注册中心多个节点相互注册 service-url: # 注册中心对外暴露的注册地址 defaultZone: http://localhost:8761/eureka/
-
访问
访问http://localhost:8761/或者http://localhost:8762/显示如下说明互相注册成功。
-
显示IP+端口
修改配置文件
eureka: instance: hostname: localhost # 主机名,不配置的时候将根据操作系统的主机名获取 prefer-ip-address: true # 是否使用IP地址注册 instance-id: ${spring.cloud.client.ip-address}:${server.port} #ip:port
修改后结果
服务提供者&服务消费者
-
创建项目
创建子模块service-provider、service-consumer
-
配置文件
service-provider
server: port: 7070 spring: application: name: service-provider eureka: instance: hostname: localhost # 主机名,不配置的时候将根据操作系统的主机名获取 prefer-ip-address: true # 是否使用IP地址注册 instance-id: ${spring.cloud.client.ip-address}:${server.port} #ip:port client: # register-with-eureka: false # 是否将自己注册到注册中心 # fetch-registry: false # 是否从注册中心获取注册信息 # 注册中心多个节点相互注册 service-url: # 注册中心对外暴露的注册地址 defaultZone: http://localhost:8761/eureka/,http://localhost:8761/eureka/
service-consumer
server: port: 9090 spring: application: name: service-consumer eureka: instance: hostname: localhost # 主机名,不配置的时候将根据操作系统的主机名获取 prefer-ip-address: true # 是否使用IP地址注册 instance-id: ${spring.cloud.client.ip-address}:${server.port} #ip:port client: registry-fetch-interval-seconds: 10 #间隔多久拉取注册信息 service-url: # 注册中心对外暴露的注册地址 defaultZone: http://localhost:8761/eureka/,http://localhost:8761/eureka/
-
启动类
@EnableEurekaClient @SpringBootApplication public class ServiceProviderApplication { public static void main( String[] args ) { SpringApplication.run(ServiceProviderApplication.class); } }
-
访问注册中心
九、CAP原则
CAP原则,指的是在一个分布式系统中,Consistency(一致性)、Availability(可用性)、Partition tolerance(分区容错性),三者不可得兼。
特性 | 定理 |
---|---|
Consistency | 一致性,也叫数据原子性,系统在执行某项操作后仍然处于一致的状态。在分布式系统中,更新操作执行成功后所有的用户都应该读到最新的数据,这样的系统被认为是具有强一致性的。等同于所有节点访问同一份最新的数据副本。 |
Availability | 可用性,每一个操作总是能在一定的时间内返回结果,这里需要注意的是“一定时间内”和“返回结果”。一定时间内指的是在可以容忍的范围内返回结果,结果可以是成功或者失败,且不保证获取的数据是最新数据。 |
Partition tolerance | 分区容错性,分布式系统在遇到任何网络分区故障的时候,仍然能够对外提供满足一致性和可用性的服务,除非整个网络环境都发生了故障。 |
取舍策略
- CA:如果不要求P(不允许分区即单体应用),则强一致性和可用性是可以保证的。但是放弃P的同时也就意味着放弃了系统的扩展性,也就是分布式节点受限,没有办法部署子节点,这是违背分布式系统设计初衷的。
- CP:如果不要求A(可用性),相当于每个请求都需要在服务器之间保持一致,而P(分区)会导致同步时间无限长(也就是等待数据同步完成才能正常访问服务),一旦发送网络故障或者消息丢失等情况,就要牺牲用户的体验,等待所有数据全部一致了之后再让用户访问系统。
- AP:要高可用并允许分区,则需要放弃一致性。一旦分区发生,节点之间可能失去联系,为了高可用,每个节点只能使用本地数据提供服务,而这样会导致全局数据的不一致性。
总结
现如今,对于多数大型互联网应用的场景,主机众多、部署分散,而且现在的集群规模越来越大,节点只会越来越多,所以节点故障、网络故障是常态,因此分区容错也就成为了一个分布式系统必须要面对的问题。那么就只能在C和A之间进行取舍。但对于传统的项目就可能有所不同,比如银行的转账系统,涉及到金钱的对于数据一致性不能做出让步,C必须保证,出现网络故障的话,宁可停止服务。
总而言之,没有最好的策略,好的系统应该是根据业务场景来进行架构设计的,只有合适的才是最好的。
十、Eureka自我保护
启动自我保护条件
一般情况下,服务在Eureka上注册后,会每30秒发送心跳包,Eureka通过心跳来判断服务是否健康,同时会定期删除超过90秒没有发送心跳的服务。
有两种情况会导致Eureka Server收不到服务的心跳
- 服务自身的原因
- 微服务与Eureka之间的网络故障
自我保护模式
Eureka Server在运行期间会统计心跳失败比例在15分钟之内是否低于85%,如果低于85%,Eureka Server会将这些实例保护起来,让这些实例不会过期,同时提示一个警告。这种算法叫做Eureka Server的自我保护模式。
为什么要启动自我保护
- 因为同时保留“好数据”和“坏数据”总比丢掉任何数据要好,当网络故障恢复后,这个Eureka节点会退出“自我保护模式”
- Eureka还有客户端缓存功能,即便是Eureka集群中所有节点都宕机失效,微服务的provider和consumer都能正常通信
- 微服务的负载均衡策略会自动剔除死亡的服务节点
如何关闭自我保护
注册中心配置自我保护
eureka:
server:
enable-self-preservation: false #是否开启自我保护
十一、Eureka优雅停服
这里使用actuator实现。
-
添加依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
-
配置文件
服务提供者配置度量指标监控与健康检查
management: endpoints: web: exposure: include: shutdown #开启shutdown端点访问 endpoint: shutdown: enabled: true #开启shutdown实现优雅停服
-
优雅停服
使用POST请求访问:http://localhost:7070/actuator/shutdown 效果如下
shutdown之前
shutdown之后
十二、Eureka安全认证
通过用户名和密码访问Eureka Server。
-
添加依赖
注册中心添加security依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
-
配置文件
注册中心配置安全认证
spring: application: name: eureka-server security: #安全认证 user: name: root password: 123456
-
修改访问集群节点的url
eureka: server: enable-self-preservation: false #是否开启自我保护 eviction-interval-timer-in-ms: 60000 #清理间隔 instance: hostname: localhost # 主机名,不配置的时候将根据操作系统的主机名获取 prefer-ip-address: true # 是否使用IP地址注册 instance-id: ${spring.cloud.client.ip-address}:${server.port} #ip:port client: # 注册中心多个节点相互注册 service-url: # 注册中心对外暴露的注册地址 defaultZone: http://root:123456@localhost:8762/eureka/ #配置访问注册中心用户名和密码
-
效果
-
过滤CSRF
Eureka会自动化配置CSRF防御机制,Spring Security认为POST,PUT,DELETE http methods都是有风险的,如果这些method发送过程中没有带上CSRF token的话,会被拦截返回403 forbidden。
使CSRF忽略/eureka/**的所有请求
@EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { super.configure(http); // 忽略/eureka/**的所有请求 http.csrf().ignoringAntMatchers("/eureka/**"); } }