现在我们的服务注册中心以及服务提供者消费者都已经搭建完毕了,但是我们需要考虑一个问题就是 如果我们的服务注册中心Eureka挂了怎么办?
高可用的服务注册中心
服务注册中心Eureka Server,是一个实例,当成千上万个服务向它注册的时候,它的负载是非常高的,这在生产环境上是不太合适的,接下来主要介绍怎么将Eureka Server集群化。
这里我们创建三个注册中心项目
我们对每个项目的配置文件进行改造
之前我们是将eureka.client.serviceUrl.defaultZone
指向自己的ip以及端口,现在我们将自己注册在另外的注册中心上,这样彼此都作为微服务客户端进行注册,即实现了高可用。
#高可用服务注册中心
#服务名
spring:
application:
name: spring-cloud-eureka
#暴露的端口号
server:
port: 8761
eureka:
instance:
hostname: peer1
client:
serviceUrl:
defaultZone: http://peer2:8760/eureka/,http://peer3:8759/eureka/
# fetch-registry: false
# register-with-eureka: false
#本身也会被当做Client,所以false来表明自己是一个eureka server.
#做注册中心集群的时候可以设置为true,这样其他注册中心就会当作服务进行注册,而且会在available-replicas中
#但是(true,false)没看出有什么影响,我还在研究.....
spring:
application:
name: spring-cloud-eureka
server:
port: 8760
eureka:
instance:
hostname: peer2
client:
serviceUrl:
defaultZone: http://peer1:8761/eureka/,http://peer3:8759/eureka/
# fetch-registry: false
# register-with-eureka: false
spring:
application:
name: spring-cloud-eureka
server:
port: 8759
eureka:
instance:
hostname: peer3
client:
serviceUrl:
defaultZone: http://peer1:8761/eureka/,http://peer2:8760/eureka/
# fetch-registry: false
# register-with-eureka: false
当然因为是在一台电脑上所以我们要将自己的ip进行域名映射为peer1 peer2 peer3
,
我们分别启动三个项目,接下来访问 http://peer1/8761 http://peer2/8760 http://peer3/8759
可以看到每个注册中心都已经注册到了Eureka上(这里说一下fetch-registry register-with-eureka
这两个配置,在高可用情况下最好设置成true 不过默认值就是true所以可以不写,但是我发现设置成false 并没有什么区别,只是不在available-replicas
中 ,具体有什么影响我还在研究,也希望知道的朋友告诉我一下哈~~~)
我们前面服务提供者和消费者的配置文件中的url只需要配置成我们其中的一个注册中心地址即可(而不是本例子中的三个),这是因为Eureka Server的同步遵循着一个非常简单的原则:只要有一条边将节点连接,就可以进行信息传播与同步
现在我们分别启动我们前面的服务提供者与消费者,可以看到服务分别注册到了三个注册中心上了并且注册中心之间也实现集群
我们在断掉一个服务注册中心
接着访问服务消费者
可以看到我们的服务仍旧注册在服务中心上,从而实现了服务注册高可用,服务注册中心挂了我们也不怕了
服务注册发现简单分析
接下来我们简单分析一个服务注册到Eureka中经历了什么
一个客户端也可以说一个微服务注册到Eureka做了两步
- 在应用主类中配置了@EnableDiscoveryClient注解
- 在application.properties中用eureka.client.serviceUrl.defaultZone参数指定了服务注册中心的位置
在整个微服务注册过程中有了两个重要的对象region、Zone
客户端依次加载了两个内容,第一个是Region,第二个是Zone
通过getRegion函数,它从配置中读取了一个Region返回,所以一个微服务应用只可以属于一个Region,
如果不特别配置,就默认为default。若我们要自己设置,可以通过eureka.client.region
属性来定义。
当我们没有特别为Region配置Zone的时候,将默认采用defaultZone,
这也是我们之前配置参数eureka.client.serviceUrl.defaultZone
的由来。
若要为应用指定Zone,我们可以通过eureka.client.availability-zones
属性来进行设置。
从该函数的return内容,我们可以Zone是可以有多个的,并且通过逗号分隔来配置。由此,我们可以判断Region与Zone是一对多的关系。
在获取了Region和Zone信息之后,才开始真正加载Eureka Server的具体地址
当客户端在服务列表中选择实例进行访问时,对于Zone和Region遵循这样的规则:
优先访问同自己一个Zone中的实例,其次才访问其他Zone中的实例。
通过Region和Zone的两层级别定义,配合实际部署的物理结构,我们就可以有效的设计出区域性故障的容错集群。
服务注册 服务获取与服务续约(定时任务,REST的请求)
“服务获取”相对于“服务续约”更为独立,“服务续约”与“服务注册”在同一个if逻辑中,
这个不难理解,服务注册到Eureka Server后,自然需要一个心跳去续约,防止被剔除,所以他们肯定是成对出现的。
从源码中,我们可以清楚看到了,对于服务续约相关的时间控制参数:
eureka.instance.lease-renewal-interval-in-seconds=30
eureka.instance.lease-expiration-duration-in-seconds=90
“服务获取”的逻辑在独立的一个if判断中,其判断依据就是我们之前所提到的eureka.client.fetch-registry=true
参数,它默认是为true的,大部分情况下我们不需要关心
为了定期的更新客户端的服务清单,以保证服务访问的正确性,
“服务获取”的请求不会只限于服务启动,而是一个定时执行的任务
从源码中我们可以看到任务运行中的registryFetchIntervalSeconds参数对应eureka.client.registry-fetch-interval-seconds=30
配置参数,它默认为30秒。
服务注册中心处理
通过上面的源码分析,可以看到所有的交互都是通过REST
的请求来发起的。
在注册函数中,先调用publishEvent函数,将该新服务注册的事件传播出去,
然后调用com.netflix.eureka.registry.AbstractInstanceRegistry父类中的注册实现,
将InstanceInfo中的元数据信息存储在一个ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>>
对象中,
它是一个两层Map结构,第一层的key存储服务名:InstanceInfo中的appName属性,第二层的key存储实例名:InstanceInfo中的instanceId属性。