Spring Cloud-Eureka实现服务的注册与发现(二)
说明
在Spring Cloud中是使用Eureka来实现服务的注册与发现的
请勿使用eureka2.x 用于生产 2.x已经停止开发了 使用1.x 最新版是1.9 我这里demo是使用1.9 详情:https://github.com/Netflix/eureka/wiki
基本概念
在Eureka的服务治理中,会涉及到下面一些概念:
服务注册:
Eureka Client会通过发送REST请求的方式向Eureka Server注册自己的服务,提供自身的元数据,比如ip地址、端口、运行状况指标的url、主页地址等信息。Eureka Server接收到注册请求后,就会把这些元数据信息存储在一个双层的Map中。
服务续约:
在服务注册后,Eureka Client会维护一个心跳来持续通知Eureka Server,说明服务一直处于可用状态,防止被剔除。Eureka Client在默认的情况下会每隔30秒发送一次心跳来进行服务续约。
服务同步:
Eureka Server之间会互相进行注册,构建Eureka Server集群,不同Eureka Server之间会进行服务同步,用来保证服务信息的一致性。
获取服务:
服务消费者(Eureka Client)在启动的时候,会发送一个REST请求给Eureka Server,获取上面注册的服务清单,并且缓存在Eureka Client本地,默认缓存30秒。同时,为了性能考虑,Eureka Server也会维护一份只读的服务清单缓存,该缓存每隔30秒更新一次。
服务调用:
服务消费者在获取到服务清单后,就可以根据清单中的服务列表信息,查找到其他服务的地址,从而进行远程调用。Eureka有Region和Zone的概念,一个Region可以包含多个Zone,在进行服务调用时,优先访问处于同一个Zone中的服务提供者。
服务下线:
当Eureka Client需要关闭或重启时,就不希望在这个时间段内再有请求进来,所以,就需要提前先发送REST请求给Eureka Server,告诉Eureka Server自己要下线了,Eureka Server在收到请求后,就会把该服务状态置为下线(DOWN),并把该下线事件传播出去。
服务剔除:
有时候,服务实例可能会因为网络故障等原因导致不能提供服务,而此时该实例也没有发送请求给Eureka Server来进行服务下线,所以,还需要有服务剔除的机制。Eureka Server在启动的时候会创建一个定时任务,每隔一段时间(默认60秒),从当前服务清单中把超时没有续约(默认90秒)的服务剔除。
自我保护:
既然Eureka Server会定时剔除超时没有续约的服务,那就有可能出现一种场景,网络一段时间内发生了异常,所有的服务都没能够进行续约,Eureka Server就把所有的服务都剔除了,这样显然不太合理。所以,就有了自我保护机制,当短时间内,统计续约失败的比例,如果达到一定阈值,则会触发自我保护的机制,在该机制下,Eureka Server不会剔除任何的微服务,等到正常后,再退出自我保护机制。
从这些概念中,就可以知道大体的流程,Eureka Client向Eureka Server注册,并且维护心跳来进行续约,如果长时间不续约,就会被剔除。Eureka Server之间进行数据同步来形成集群,Eureka Client从Eureka Server获取服务列表,用来进行服务调用,Eureka Client服务重启前调用Eureka Server的接口进行下线操作
项目骨架搭建
创建父工程
1.创建一个父工程 实现版本的统一管理 以及子项目的管理
然后下一步下一步
创建子工程
在对应的tab 勾选web和EurekaServer 创建项目后会自动生成pom依赖 注意右上角cloud版本
将eureka子工程的paren 节点改为指向父工程 这的各个节点需要改为 你的对应父工程的节点描述
<parent> <groupId>com.liqiang</groupId> <artifactId>spring-cloud-parent</artifactId> <version>0.0.1-SNAPSHOT</version> <relativePath/> <!-- lookup parent from repository --> </parent>
将工程的在父工程创建节点 并删除子工程的 没有的话就不管
<!--版本号的统一管理。 子类项目没有配置版本号的时候 会从子往上找 找到 dependencyManagement 找到对应的版本--> <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>
父工程model配置上eureka子工程
<modules> <module>spring-cloud-eureka</module> </modules>
最后将父工程的Src目录删除 这样我们项目的基础骨架就搭建好了 可以进行eureka的相关配置了
个人习惯 喜欢用yml配置 所以讲resource下面的application.properties 后缀改为yml
最后将parent pom文件的<packaging>jar</packaging> 改为<packaging>pom</packaging>
然后执行parent安装
配置Eureka服务端
1.增加pom依赖并在Application类添加@EnableEurekaServer注解 开启服务注册功能
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency>
@SpringBootApplication @EnableEurekaServer //开启服务注册的功能 public class SpringCloudEurekaApplication { public static void main(String[] args) { SpringApplication.run(SpringCloudEurekaApplication.class, args); } }
2.配置application.yml
server:
port: 1111
eureka:
instance:
hostname: localhost #主机名
client:
registerWithEureka: false #不注册自己 只维护服务 #集群情况下 是需要改成true 的因为 集群是注册中心 相互注册自己 同步服务
fetchRegistry: false #是否开启从 Eureka-Server 获取注册信息。默认值:true 集群情况下需要为true的 因为 会在别的注册中心 获取服务
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ #集群情况配置多注册中心,号隔开 单机情况下 不配置也没关系
3.访问http://127.0.0.1:1111
表示注册中心搭建成功。。registerWithEureka 我们设置了false 表示不注册 自己 我们可以尝试改成true 则可以看到 服务里面会出现服务
配置服务提供者
1.跟创建注册中心一样 创建一个名为provider的项目 pom配置文件 跟provider一致
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency>
2.配置 application.yml配置文件
spring:
application:
name: provider #服务名字
server:
port: 8081
eureka:
instance:
hostname: localhost #当前实例的主机名字
client:
serviceUrl:
defaultZone: http://127.0.0.1:1111/eureka/ #注册中心地址
3. @EnableDiscoveryClient 开启服务注册到注册中心的功能
@SpringBootApplication @EnableDiscoveryClient //开启将服务注册到注册中心 public class SpringCloudProviderApplication { public static void main(String[] args) { SpringApplication.run(SpringCloudProviderApplication.class, args); } }
4.创建对一个对外暴露的服务(注意 必须在application启动类所在目录或者下级目录)
@RestController public class HelloServerContorrler { @RequestMapping(value = "/hello") public String hello(){ return "hello"; } }
5.启动eureka 再启动provider 可以发现服务已经注册到注册中心
服务消费者配置
跟上面一样创建一个consumer
pom配置
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!--从注册中心获取服务的依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--rabbon客户端的负载均衡--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-ribbon</artifactId> </dependency> </dependencies>
启动类配置
@SpringBootApplication @EnableDiscoveryClient //开启服务发现 public class ConsumerApplication { public static void main(String[] args) { SpringApplication.run(ConsumerApplication.class, args); } //LoadBalanced 通过代理RestTemplate 实现客户端负载均衡的能力 @LoadBalanced @Bean RestTemplate restTemplate(){ return new RestTemplate(); } }
yml配置
spring:
application:
name: consumer
server:
port: 9001
eureka:
client:
serviceUrl:
defaultZone: http://127.0.0.1:1111/eureka #注册中心地址
调用服务
@Controller public class HelloContorller { //applicaton启动类配置的@LoadBalanced 的代理对象 @Autowired RestTemplate restTemplate; @RequestMapping(value = "/hello") @ResponseBody public String hell(){ //PROVIDER需要大写 这里没有配置物理地址 而是服务名 是去服务集群 获得集群服务地址 来实现负载均衡 return restTemplate.getForEntity("http://PROVIDER/hello",String.class).getBody(); } }
访问http://127.0.0.1:9001/hello
注册中心集群
1.配置host文件 模拟域名映射ip 比如我们通过http://peer1:8081/user 访问 实际上是访问peer1对应的ip 如http://127.0.0.1:8081/user
host文件在哪儿自行百度
127.0.0.1 peer1
127.0.0.1 peer2
2.copy2个配置文件 端口改为不一致
配置文件1
server: port: 1111 eureka: instance: hostname: localhost #主机名 client: registerWithEureka: true #将自己注册到 集群中的其他注册中心 fetchRegistry: true #从注册中心同步服务。 serviceUrl: defaultZone: http://peer1:1112/eureka/ #集群汇总的其他注册中心地址 多个,号隔开 spring: application: name: eureka
配置文件2
server: port: 1112 eureka: instance: hostname: localhost #主机名 client: registerWithEureka: true #将自己注册到 集群中的其他注册中心 fetchRegistry: true #从注册中心同步服务。 serviceUrl: defaultZone: http://peer1:1111/eureka/ #集群汇总的其他注册中心地址 多个,号隔开 spring: application: name: eureka
注册中心集群 是根据注册中心之间相互注册和相互订阅同步服务实现的 可以通过上面配置看出来
4.provider和cusumer的注册中心地址指向集群的注册中心所有
spring:
application:
name: consumer
server:
port: 9001
eureka:
client:
serviceUrl:
defaultZone: http://peer1:1111/eureka,http://peer2:1112/eureka
spring:
application:
name: provider #服务名字
server:
port: 8081
eureka:
instance:
hostname: localhost #当前实例的主机名字
client:
serviceUrl:
defaultZone: http://peer1:1111/eureka,http://peer2:1112/eureka #注册中心地址
3.打包安装 手动执行jar包 并分别指定配置文件路径替换成本地打包的路径
java -jar /Users/liqiang/Desktop/java开发环境/javadom/spring-cloud-parent/spring-cloud-eureka/target/spring-cloud-eureka-0.0.1-SNAPSHOT.jar --spring.profiles.active=peer1
java -jar /Users/liqiang/Desktop/java开发环境/javadom/spring-cloud-parent/spring-cloud-eureka/target/spring-cloud-eureka-0.0.1-SNAPSHOT.jar --spring.profiles.active=peer2
5.启动provider
6.启动cusumer
高可用集群的注册中心就搭建好了
eureka逻辑图
默认服务提供者启动会注册服务到注册中心的双层map 第一层key为服务名 第二层为key为实例信息 同时维持一个心跳方式注册中心认为自己挂了将自己从服务列表剔除
不知道怎么测试 正常关闭的话会通过REST通知到服务注册中心 注册中心将服务状态改为down 再通知下去
客户端配置:
# 多久未收到心跳,剔除instance 要比心跳时间大
eureka.instance.lease-expiration-duration-in-seconds:30
# 心跳时间间隔
eureka.instance.lease-renewal-interval-in-seconds: 5
服务端配置:
eureka.instance.eviction-interval-timer-in-ms: 5000 多久去剔除一次指定时间没有续约的服务
消费者启动会拉取注册中心服务。为了性能 eurekaServer会维护30秒的服务清单 该清单默认30秒更新一次 可以通过以下配置
eureka.client.registry-fetch-interval-seconds=30 确保开启 eureka.client.fetchRegistry: false #是否开启从 Eureka-Server 获取注册信息。默认值:true
服务保护机制
前面说了 注册中心是通过 服务提供者维护的心跳来控制服务是否有效 当15分钟内 心跳失败的比例在85%会开启保护机制 这个时候服务就不会因为续约过期而下线(个人觉得是为了防止 注册中心 网络出现问题 导致服务提供者通过心跳请求不能成功续约) 保护机制会导致客户端拿到 无效的服务 所以需要客户端启用启用重试和断路器机制
可以通过eureka.instance.enable-self-preservation=false 关闭保护机制(不建议开启 本地开发可以开启)
Eureka初始化过程
1.配置信息启动都会封装到EurekaClientConfig
2.com.netflix.discovery.DiscoveryClient 根据EurekaClientConfig会初始化3个定时任务 分别服务注册 服务获取 服务续约(心跳) 三个定时任务都是根据rest接口而来的
通过源码
com.netflix.discovery.DiscoveryClient的构造函数可以发现初始化定时任务的方法
private void initScheduledTasks() { //是否从注册中心订阅服务对应eureka.client.registerWithEureka配置 if (clientConfig.shouldFetchRegistry()) { //多久去注册中心拉取一次服务对应eureka.client.registry-fetch-interval-seconds int registryFetchIntervalSeconds = clientConfig.getRegistryFetchIntervalSeconds(); //服务拉取失败的重试时间系数 int expBackOffBound = clientConfig.getCacheRefreshExecutorExponentialBackOffBound(); scheduler.schedule( new TimedSupervisorTask( "cacheRefresh", scheduler, cacheRefreshExecutor, registryFetchIntervalSeconds, TimeUnit.SECONDS, expBackOffBound, new DiscoveryClient.CacheRefreshThread() ), registryFetchIntervalSeconds, TimeUnit.SECONDS); } //是否对注册中心注册服务 对应eureka.client.fetchRegistry if (clientConfig.shouldRegisterWithEureka()) { //心跳间隔时间 对应eureka.instance.lease-renewal-interval-in-seconds int renewalIntervalInSecs = instanceInfo.getLeaseInfo().getRenewalIntervalInSecs(); //心跳超时重试的系数对应 int expBackOffBound = clientConfig.getHeartbeatExecutorExponentialBackOffBound(); logger.info("Starting heartbeat executor: " + "renew interval is: {}", renewalIntervalInSecs); // Heartbeat timer scheduler.schedule( new TimedSupervisorTask( "heartbeat", scheduler, heartbeatExecutor, renewalIntervalInSecs, TimeUnit.SECONDS, expBackOffBound, new DiscoveryClient.HeartbeatThread() ), renewalIntervalInSecs, TimeUnit.SECONDS); // 服务注册定时任务 instanceInfoReplicator = new InstanceInfoReplicator( this, instanceInfo, clientConfig.getInstanceInfoReplicationIntervalSeconds(), 2); // burstSize statusChangeListener = new ApplicationInfoManager.StatusChangeListener() { @Override public String getId() { return "statusChangeListener"; } @Override public void notify(StatusChangeEvent statusChangeEvent) { if (InstanceInfo.InstanceStatus.DOWN == statusChangeEvent.getStatus() || InstanceInfo.InstanceStatus.DOWN == statusChangeEvent.getPreviousStatus()) { // log at warn level if DOWN was involved logger.warn("Saw local status change event {}", statusChangeEvent); } else { logger.info("Saw local status change event {}", statusChangeEvent); } instanceInfoReplicator.onDemandUpdate(); } }; if (clientConfig.shouldOnDemandUpdateStatusChange()) { applicationInfoManager.registerStatusChangeListener(statusChangeListener); } instanceInfoReplicator.start(clientConfig.getInitialInstanceInfoReplicationIntervalSeconds()); } else { logger.info("Not registering with Eureka server per configuration"); } }
配置参考
https://blog.csdn.net/cvntopuyef/article/details/78477724
Instance配置
服务注册会向注册中心发送一些用于描述自己的的元数据信息比如 服务名称、 实例名称、 实例IP、 实例端口等
在使用 Spring CloudEureka的时候, 所有的配置信息都通过org.springframework.cloud.netflix.eureka.EurekalnstianceConfigBean进行加载
但在真正进行服 务注册的时候, 还是会包装成com.netflix.appinfo.Instiancelnfo
可以通过eureka.instance.metadataMap.<key>=<value> 来进行自定发送数据
eureka.instance.instanceid 指定服务实例id 默认主机名+端口
指定服务info和heath端点路径 heath用于服务更新服务状态 down up info用于注册中心点击服务访问信息 保证这2类不要404 默认一般不用修改 当上下文修改 可以通过以下配置指定需要引入
spring-boot-starter-actuator模块
management.context-path=/hello
eureka.instance.statusPageUrlPath=${management.context-path}/info
eureka.instance.healthCheckUrlPath=${management.context-path}/health
instance其他配置