Spring Cloud Netflix Eureka集群配置方式主要用两种:Static servers list config和dns,本文主要介绍基于DNS的方式搭建Eureka集群。
client启动后从config中获取region和zone以及serviceUrl,进行服务注册、发现,这种静态集群模式无法动态扩容,一旦新增节点,只能挨个修改server和client端的配置文件,大致配置如下:
eureka:
server:
enable-self-preservation: false
instance:
prefer-ip-address: true
instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://10.25.102.174:8082/eureka/,http://10.25.102.174:8083/eureka/
基于DNS的Eureka Cluster架构图如下:
简单来说就是使用DNS服务器管理Eureka Server的地址,弹性伸缩对客户端没有影响。
大致流程如下:
use-dns-for-fetching-service-urls: true client.region "http://" + ec2Url + ":" + clientConfig.getEurekaServerPort() + "/" + clientConfig.getEurekaServerURLContext() + "/";
zts.local.zone区域配置文件中有关eureka的配置:
; eureka cluster配置 txt.shanghai IN TXT "defaultZone.zts.local" txt.defaultZone IN TXT "10.29.181.56" "10.29.181.57" "10.29.181.58" "10.29.181.60" ;通过查看Eureka的源码,它的实现是通过寻找txt.beijing-a.zts.local(txt.region.eureka-server-d-n-s-name)获取"beijing-a.zts.local" "beijing-b.zts.local"的zone:beijing-a和beijing-b, ;然后再去获取txt.beijing-a.zts.local和txt.beijing-b.zts.local中对应的ServiceUrls的数据,也就是IP地址: "10.29.181.61" "10.29.181.62" ;这样服务间就可以获取到固定端口下的不同IP的机器的注册中心服务地址,并相互注册 txt.beijing IN TXT "beijing-a.zts.local" "beijing-b.zts.local" txt.beijing-a IN TXT "10.29.181.61" "10.29.181.62" txt.beijing-b IN TXT "10.29.181.63" "10.29.181.64"
---
spring:
application:
name: eureka-server
server:
port: 8081
eureka:
server:
enable-self-preservation: false
instance:
prefer-ip-address: true
instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}
client:
## 是否将自己注册到eureka
register-with-eureka: true
## 是否获取注册信息到本地
fetch-registry: true
## 开启DNS方式获取serviceUrl,默认为false
use-dns-for-fetching-service-urls: true
## DNS域名,获取其他信息将以该域名为根域名
eureka-server-d-n-s-name: zts.local
## 当前应用所在区域,默认为us-east-1
region: beijing
## 配置中心的eureka目录
eureka-server-u-r-l-context: eureka
## 映射其他eurekaServer的端口,这里注意采用这种方式 server.port和这个端口最好一致
## 因为dns是控制地址的变化,端口不变,所以不像前面的哪些配置方式可以自己定义url和port。
## 所以每个IP都是用的相同的port进行注册中心服务的部署
eureka-server-port: 8081
## 获取serviceUrl时候是否优先获取相同zone的列表(如果获取为空则获取所在region第一个zone),如果为false则优先获取不在相同zone的列表
prefer-same-zone-eureka: true
修改客户端DNS服务器配置为10.29.181.61,DNS服务器的域名为zts.local
依次启动4台Eureka Server服务器,查看启动日志(删减版的日志),:
2018-11-20 16:17:14.962 INFO 49750 --- [ main] c.n.d.s.r.aws.ConfigClusterResolver : Resolving eureka endpoints via DNS: txt.beijing.zts.local (<------注意这里) 2018-11-20 16:17:15.012 INFO 49750 --- [ main] com.netflix.discovery.DiscoveryClient : Disable delta property : false 2018-11-20 16:17:15.262 INFO 49750 --- [ main] com.netflix.discovery.DiscoveryClient : Discovery Client initialized at timestamp 1542701835260 with initial instances count: 5 2018-11-20 16:17:15.326 INFO 49750 --- [ main] c.n.eureka.DefaultEurekaServerContext : Initializing ... 2018-11-20 16:17:15.559 INFO 49750 --- [ main] c.n.eureka.cluster.PeerEurekaNodes : Replica node URL: http://10.29.181.62:8081/eureka/ 2018-11-20 16:17:15.560 INFO 49750 --- [ main] c.n.eureka.cluster.PeerEurekaNodes : Replica node URL: http://10.29.181.64:8081/eureka/ 2018-11-20 16:17:15.560 INFO 49750 --- [ main] c.n.eureka.cluster.PeerEurekaNodes : Replica node URL: http://10.29.181.61:8081/eureka/ 2018-11-20 16:17:16.052 INFO 49750 --- [ Thread-14] o.s.c.n.e.server.EurekaServerBootstrap : isAws returned false 2018-11-20 16:17:16.053 INFO 49750 --- [ Thread-14] o.s.c.n.e.server.EurekaServerBootstrap : Initialized server context 2018-11-20 16:17:16.056 INFO 49750 --- [ main] c.z.e.server.EurekaServerApplication : Started EurekaServerApplication in 7.112 seconds (JVM running for 7.625) 2018-11-20 16:17:16.060 INFO 49750 --- [ Thread-14] c.n.e.registry.AbstractInstanceRegistry : Registered instance EUREKA-SERVER/10.29.181.61:eureka-server:8081 with status UP (replication=true) 2018-11-20 16:17:16.061 INFO 49750 --- [ Thread-14] c.n.e.registry.AbstractInstanceRegistry : Registered instance EUREKA-SERVER/10.29.181.62:eureka-server:8081 with status UP (replication=true) 2018-11-20 16:17:16.061 INFO 49750 --- [ Thread-14] c.n.e.registry.AbstractInstanceRegistry : Registered instance EUREKA-SERVER/10.29.181.64:eureka-server:8081 with status UP (replication=true) 2018-11-20 16:17:16.062 INFO 49750 --- [ Thread-14] c.n.e.registry.AbstractInstanceRegistry : Registered instance EUREKA-SERVER/10.29.181.63:eureka-server:8081 with status UP (replication=true) 2018-11-20 16:17:16.062 INFO 49750 --- [ Thread-14] c.n.e.registry.AbstractInstanceRegistry : Registered instance ACCOUNT-CENTER/10.25.102.174:account-center:8891 with status UP (replication=true) 2018-11-20 16:17:16.062 INFO 49750 --- [ Thread-14] c.n.e.r.PeerAwareInstanceRegistryImpl : Got 5 instances from neighboring DS node 2018-11-20 16:17:16.062 INFO 49750 --- [ Thread-14] c.n.e.r.PeerAwareInstanceRegistryImpl : Renew threshold is: 8 2018-11-20 16:17:16.062 INFO 49750 --- [ Thread-14] c.n.e.r.PeerAwareInstanceRegistryImpl : Changing status to UP 2018-11-20 16:17:16.070 INFO 49750 --- [nfoReplicator-0] com.netflix.discovery.DiscoveryClient : DiscoveryClient_EUREKA-SERVER/10.29.181.63:eureka-server:8081 - registration status: 204 2018-11-20 16:17:16.070 INFO 49750 --- [ Thread-14] e.s.EurekaServerInitializerConfiguration : Started Eureka Server 2018-11-20 16:17:16.310 INFO 49750 --- [nio-8081-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring FrameworkServlet 'dispatcherServlet' 2018-11-20 16:17:16.310 INFO 49750 --- [nio-8081-exec-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization started 2018-11-20 16:17:16.327 INFO 49750 --- [nio-8081-exec-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization completed in 17 ms 2018-11-20 16:17:16.578 INFO 49750 --- [nio-8081-exec-1] c.n.e.registry.AbstractInstanceRegistry : Registered instance EUREKA-SERVER/10.29.181.63:eureka-server:8081 with status UP (replication=true) 2018-11-20 16:17:17.457 INFO 49750 --- [nio-8081-exec-2] c.n.e.registry.AbstractInstanceRegistry : Registered instance EUREKA-SERVER/10.29.181.63:eureka-server:8081 with status UP (replication=true)
Account服务配置
logging:
level:
org.springframework.boot: info
org.springframework.cloud: debug
spring:
application:
name: account-center
server:
port: 8891
eureka:
server:
enable-self-preservation: false
instance:
prefer-ip-address: true
instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}
metadata-map:
configPath: ${server.servlet.context-path}
home-page-url-path: ${server.servlet.context-path}
## 服务过期时间配置,超过这个时间没有接收到心跳EurekaServer就会将这个实例剔除
lease-expiration-duration-in-seconds: 10
## 服务刷新时间配置,每隔这个时间会主动心跳一次
lease-renewal-interval-in-seconds: 5
client:
## 是否将自己注册到eureka
register-with-eureka: true
## 是否获取注册信息到本地
fetch-registry: true
## 开启DNS方式获取serviceUrl,默认为false
use-dns-for-fetching-service-urls: true
## DNS域名,获取其他信息将以该域名为根域名
eureka-server-d-n-s-name: zts.local
## 当前应用所在区域,默认为us-east-1
region: beijing
## 配置中心的eureka目录
eureka-server-u-r-l-context: eureka
## 映射其他eurekaServer的端口,这里注意采用这种方式 server.port和这个端口最好一致
## 因为dns是控制地址的变化,端口不变,所以不像前面的哪些配置方式可以自己定义url和port。
## 所以每个IP都是用的相同的port进行注册中心服务的部署
eureka-server-port: 8081
## 获取serviceUrl时候是否优先获取相同zone的列表(如果获取为空则获取所在region第一个zone),如果为false则优先获取不在相同zone的列表
prefer-same-zone-eureka: true
Account启动日志:
2018-11-20 16:11:05.373 INFO 5824 --- [ restartedMain] c.n.d.s.r.aws.ConfigClusterResolver : Resolving eureka endpoints via DNS: txt.beijing.zts.local (<------看这里) 2018-11-20 16:11:05.529 INFO 5824 --- [ restartedMain] com.netflix.discovery.DiscoveryClient : Disable delta property : false 2018-11-20 16:11:05.529 INFO 5824 --- [ restartedMain] com.netflix.discovery.DiscoveryClient : Single vip registry refresh property : null 2018-11-20 16:11:05.529 INFO 5824 --- [ restartedMain] com.netflix.discovery.DiscoveryClient : Force full registry fetch : false 2018-11-20 16:11:05.529 INFO 5824 --- [ restartedMain] com.netflix.discovery.DiscoveryClient : Application is null : false 201
打开Eureka console,查看注册效果:
版本:eureka-client:1.9.3
/**
* Get the list of all eureka service urls for the eureka client to talk to.
*
* @param clientConfig the clientConfig to use
* @param zone the zone in which the client resides
* @param randomizer a randomizer to randomized returned urls, if loading from dns
*
* @return The list of all eureka service urls for the eureka client to talk to.
*/
public static List<String> getDiscoveryServiceUrls(EurekaClientConfig clientConfig, String zone, ServiceUrlRandomizer randomizer) {
boolean shouldUseDns = clientConfig.shouldUseDnsForFetchingServiceUrls();
if (shouldUseDns) {
return getServiceUrlsFromDNS(clientConfig, zone, clientConfig.shouldPreferSameZoneEureka(), randomizer);
}
return getServiceUrlsFromConfig(clientConfig, zone, clientConfig.shouldPreferSameZoneEureka());
}
/**
* Get the list of all eureka service urls from DNS for the eureka client to
* talk to. The client picks up the service url from its zone and then fails over to
* other zones randomly. If there are multiple servers in the same zone, the client once
* again picks one randomly. This way the traffic will be distributed in the case of failures.
*
* @param clientConfig the clientConfig to use
* @param instanceZone The zone in which the client resides.
* @param preferSameZone true if we have to prefer the same zone as the client, false otherwise.
* @param randomizer a randomizer to randomized returned urls
*
* @return The list of all eureka service urls for the eureka client to talk to.
*/
public static List<String> getServiceUrlsFromDNS(EurekaClientConfig clientConfig, String instanceZone, boolean preferSameZone, ServiceUrlRandomizer randomizer) {
String region = getRegion(clientConfig);
// Get zone-specific DNS names for the given region so that we can get a
// list of available zones
Map<String, List<String>> zoneDnsNamesMap = getZoneBasedDiscoveryUrlsFromRegion(clientConfig, region);
Set<String> availableZones = zoneDnsNamesMap.keySet();
List<String> zones = new ArrayList<String>(availableZones);
if (zones.isEmpty()) {
throw new RuntimeException("No available zones configured for the instanceZone " + instanceZone);
}
int zoneIndex = 0;
boolean zoneFound = false;
for (String zone : zones) {
logger.debug("Checking if the instance zone {} is the same as the zone from DNS {}", instanceZone, zone);
if (preferSameZone) {
if (instanceZone.equalsIgnoreCase(zone)) {
zoneFound = true;
}
} else {
if (!instanceZone.equalsIgnoreCase(zone)) {
zoneFound = true;
}
}
if (zoneFound) {
logger.debug("The zone index from the list {} that matches the instance zone {} is {}",
zones, instanceZone, zoneIndex);
break;
}
zoneIndex++;
}
if (zoneIndex >= zones.size()) {
if (logger.isWarnEnabled()) {
logger.warn("No match for the zone {} in the list of available zones {}",
instanceZone, zones.toArray());
}
} else {
// Rearrange the zones with the instance zone first
for (int i = 0; i < zoneIndex; i++) {
String zone = zones.remove(0);
zones.add(zone);
}
}
// Now get the eureka urls for all the zones in the order and return it
List<String> serviceUrls = new ArrayList<String>();
for (String zone : zones) {
for (String zoneCname : zoneDnsNamesMap.get(zone)) {
List<String> ec2Urls = new ArrayList<String>(getEC2DiscoveryUrlsFromZone(zoneCname, DiscoveryUrlType.CNAME));
// Rearrange the list to distribute the load in case of multiple servers
if (ec2Urls.size() > 1) {
randomizer.randomize(ec2Urls);
}
for (String ec2Url : ec2Urls) {
StringBuilder sb = new StringBuilder()
.append("http://")
.append(ec2Url)
.append(":")
.append(clientConfig.getEurekaServerPort());
if (clientConfig.getEurekaServerURLContext() != null) {
if (!clientConfig.getEurekaServerURLContext().startsWith("/")) {
sb.append("/");
}
sb.append(clientConfig.getEurekaServerURLContext());
if (!clientConfig.getEurekaServerURLContext().endsWith("/")) {
sb.append("/");
}
} else {
sb.append("/");
}
String serviceUrl = sb.toString();
logger.debug("The EC2 url is {}", serviceUrl);
serviceUrls.add(serviceUrl);
}
}
}
// Rearrange the fail over server list to distribute the load
String primaryServiceUrl = serviceUrls.remove(0);
randomizer.randomize(serviceUrls);
serviceUrls.add(0, primaryServiceUrl);
if (logger.isDebugEnabled()) {
logger.debug("This client will talk to the following serviceUrls in order : {} ",
(Object) serviceUrls.toArray());
}
return serviceUrls;
}