1.准备
1.首先,在C:\WINDOWS\System32\drivers\etc\hosts文件里面添加一下映射,如果不添加也没关系,只是如果是单机环境,在eureka首页中的replicas那一项看到的其它注册中心都是localhost,我这里为了方便理解就添加了映射。
2.为了方便理解,我这里是单个application用一个module,没有采用通过多个profile开启多个application的做法,而且这样做一会儿验证起来也比较清晰。
3.必要的一些概念
先看官方的这张图:
springcloud中eureka的默认region是us-east-1,一个region下可以有多个zone。比如zone1内有服务A,zone2内也有服务A,消费者A(这里指调用服务A的client)如果属于zone1,他就会优先调用zone1,如果zone1内的服务都不可用了,就会调用zone2中的服务A,这种调用方式就是服务分区。分区其实就是建立在集群之上,zone1和zone2构成了集群,但是消费者A调用zone1的时候可能开销会小一些,所以可以让他优先调用zone1内的服务A。
2.搭建
2个注册中心
region1-zone1(region1区域内的zone1),region1-zone2(region1区域内的zone2)
region1-zone1:
application.properties:
#端口
server.port=8761
#主机名
eureka.instance.hostname=region1-zone1
#应用名称
spring.application.name=eureka-server
#是否注册自身到eureka服务器
eureka.client.register-with-eureka=true
#是否获取eureka服务器注册表上的注册信息
eureka.client.fetch-registry=true
#false表示在此eureka服务器中关闭自我保护模式,所谓自我保护模式,默认true
eureka.server.enableSelfPreservation=false
#配置这个eureka server注册中心所属的region,默认值us-east-1
eureka.client.region=region1
#region1内的所有zone,提取第一个作为自己的zone
eureka.client.availability-zones.region1=region1-zone1,region1-zone2
#region1内的所有其它zone的注册中心
eureka.client.service-url.region1-zone2=http://region1-zone2:8764/eureka/
启动类:
@SpringBootApplication
@EnableEurekaServer
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
region1-zone2:
server.port=8764
eureka.instance.hostname=region1-zone2
spring.application.name=eureka-server
eureka.client.register-with-eureka=true
eureka.client.fetch-registry=true
eureka.server.enableSelfPreservation=false
eureka.client.region=region1
eureka.client.availability-zones.region1=region1-zone2,region1-zone1
eureka.client.service-url.region1-zone1=http://region1-zone1:8761/eureka/
启动类:同region1-zone1
3个service-hi服务
我这里用前缀用来区分他们分别属于哪个zone。
region1-zone1-service-hi-1:
server.port=8762
spring.application.name=test-zone
eureka.client.region=region1
eureka.client.availability-zones.region1=region1-zone1,region1-zone2
eureka.client.service-url.region1-zone1=http://region1-zone1:8761/eureka/
eureka.client.service-url.region1-zone2=http://region1-zone2:8764/eureka/
eureka.client.prefer-same-zone-eureka=true
eureka.instance.prefer-ip-address=true
#属于哪一个zone
eureka.instance.metadata-map.zone=region1-zone1
#自定义信息,验证的时候用
info=region1-zone1-service-hi-1
启动类:
@EnableEurekaClient
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
controller:
@RestController
public class HelloController {
@Value("${info}")
private String info;
@RequestMapping(value="/hi")
public String hi() {
return info;
}
}
region1-zone1-service-hi-2:
server.port=8763
spring.application.name=test-zone
eureka.client.region=region1
eureka.client.availability-zones.region1=region1-zone1,region1-zone2
eureka.client.service-url.region1-zone1=http://region1-zone1:8761/eureka/
eureka.client.service-url.region1-zone2=http://region1-zone2:8764/eureka/
eureka.client.prefer-same-zone-eureka=true
eureka.instance.prefer-ip-address=true
eureka.instance.metadata-map.zone=region1-zone1
info=region1-zone1-service-hi-2
启动类、controller都和region1-zone1-service-hi-1相同
region1-zone2-service-hi-1:
server.port=8765
spring.application.name=test-zone
eureka.client.region=region1
eureka.client.availability-zones.region1=region1-zone2,region1-zone1
eureka.client.service-url.region1-zone1=http://region1-zone1:8761/eureka/
eureka.client.service-url.region1-zone2=http://region1-zone2:8764/eureka/
eureka.client.prefer-same-zone-eureka=true
eureka.instance.prefer-ip-address=true
eureka.instance.metadata-map.zone=region1-zone2
info=region1-zone2-service-hi-1
启动类、controller都和region1-zone1-service-hi-1相同
1个consumer
application.properties:
server.port=8888
spring.application.name=region1-consumer
eureka.client.region=region1
eureka.client.availability-zones.region1=region1-zone1,region1-zone2
eureka.client.service-url.region1-zone1=http://region1-zone1:8761/eureka/
eureka.client.service-url.region1-zone2=http://region1-zone2:8764/eureka/
eureka.client.prefer-same-zone-eureka=true
eureka.instance.prefer-ip-address=true
eureka.instance.metadata-map.zone=region1-zone1
logging.level.root=debug
controller:
@RestController
public class HiController {
@Autowired
private RestTemplate restTemplate;
@RequestMapping(value="/hello")
public String hi() {
return restTemplate.getForObject("http://test-zone/hi", String.class);
}
}
3.验证
先启动两个注册中心:eureka-server-region1-zone1、eureka-server-region1-zone2。
然后将4个服务启动。
再启动消费者服务。
访问zone1的注册中心:
访问zone2的注册中心:
可以看到每个注册中心都有4个服务,这说明,3个service-hi和一个consumer注册到了集群中所有注册中心上。
打开浏览器访问:http://ip:8888/hello,ip自行改成consumer服务的ip即可,连续访问这个url若干次,可以看到consumer调用的service的始终是zone1中的service-hi-1和service-hi-2,并且由于ribbon的默认负载均衡规则,service-hi-1和service-hi-2被轮询调用。
然后我们关闭zone1中的service-hi-1,再访问若干次,可以发现此时始终只有service-hi-2提供服务了。
然后我们再关闭zone1中的service-hi-2,再访问若干次url,此时zone1中的注册中心已经没有服务提供者可以给consumer消费了,zone2中的service-hi-1开始给consumer提供服务。
4.解释:
1.有的人可能会发现在关闭服务后立即调用会报错,这一点下面做下解释:
打开debug
logging.level.root=debug
过程解释:
1.此时region1-zone1-service-hi-1、region1-zone1-service-hi-2、region1-zone2-service-hi-1都还存在于ribbon的服务列表中。由于服务分区的原因,ribbon只会轮询使用zone1中的服务即region1-zone1-service-hi-1和region1-zone1-service-hi-2服务。
2.此时region1-zone1-service-hi-1已经下线,但是region1-zone1-service-hi-2和region1-zone2-service-hi-1还存在于ribbon的服务列表中。由于服务分区的原因,ribbon只会使用region1-zone1-service-hi-2服务。
3.此时zone1中的服务:region1-zone1-service-hi-1和region1-zone1-service-hi-2都已经下线,ribbon只能使用region1-zone2-service-hi-1服务
因为存在心跳机制,默认情况下,服务的超时时间是90s,服务和注册中心的心跳间隔是30s,我们可以看下spring cloud的文档:
大概意思是:eureka将等待一定的时间(默认90秒)保持实例的存活,过了这个时间如果没有收到最新的心跳,那么eureka将会把这个实例从自己的视图中移除,并且拒绝他的流量。
这个是在讲心跳的时间间隔:指示eureka client隔多少时间发送心跳给server,过了这个时间如果没收到心跳,eureka server就会移除超时的eureka client。
所以说,当一个服务下线,eureka server要90s后才能知道这个服务已经不存在了。
2.为什么我这里调试的时候要禁用保护模式?
当保护模式被触发的时候,实例就永远不会过期,如果zone1中的服务都下线了,由于这个保护模式,eureka并不会让这些实例过期,因此,zone2中的服务就永远调用不到了。
注册中心选择逻辑:
- 如果prefer-same-zone-eureka为false,按照service-url下的 list取第一个注册中心来注册,并和其维持心跳检测。不会再向list内的其它的注册中心注册和维持心跳。只有在第一个注册失败的情况下,才会依次向其它的注册中心注册,总共重试3次,如果3个service-url都没有注册成功,则注册失败。每隔一个心跳时间,会再次尝试。
- 如果prefer-same-zone-eureka为true,先通过region取availability-zones内的第一个zone,然后通过这个zone取service-url下的list,并向list内的第一个注册中心进行注册和维持心跳,不会再向list内的其它的注册中心注册和维持心跳。只有在第一个注册失败的情况下,才会依次向其它的注册中心注册,总共重试3次,如果3个service-url都没有注册成功,则注册失败。每隔一个心跳时间,会再次尝试。
所以说,为了保证服务注册到同一个zone的注册中心,一定要注意availability-zones的顺序,必须把同一zone写在前面
我稍微修改了一下,您也可以去看原文spring cloud之eureka高可用集群和服务分区