高可用eureka服务发现实例

Posted by zhida.liao on May 22, 2017

基于spring cloud 的案例进行自定义配置

简介

一个注册发现服务,类似于一个 Map数据结构保存了 实例名和 相应的域名信息,提供实例注册和返回。(保存在内存中 )

The Eureka server does not have a backend store, but the service instances in the registry all have to send heartbeats to keep their registrations up to date (so this can be done in memory). Clients also have an in-memory cache of eureka registrations (so they don’t have to go to the registry for every single request to a service).

实现

服务端配置

Application.java
@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {

	public static void main(String[] args) throws Exception {
		SpringApplication.run(EurekaApplication.class, args);
	}

} 
配置1(推荐): application.yml
spring:
  application:
    name: eureka-server-clustered1
  profiles: peer1				# 激活环境配置文件
server:
  port: 9761
  host: serverA
eureka:
  instance:
    hostname: eureka-peer1
    preferIpAddress: true
    instance-id: ${spring.cloud.client.ipAddress}:${server.port}:${spring.application.name}
  client:
    registerWithEureka: true	# 是否要发现服务中注册该程序	
    fetchRegistry: true			# 是否可以获取服务信息
    serviceUrl:					# 在该列表中注册该程序
      defaultZone: http://${server.host}:9762/eureka/,http://${server.host}:9763/eureka/

---
spring:
  application:
    name: eureka-server-clustered2
  profiles: peer2
server:
  port: 9762
  host: serverA
eureka:
  instance:
    hostname: eureka-peer2
    preferIpAddress: true
    instance-id: ${spring.cloud.client.ipAddress}:${server.port}:${spring.application.name}
  client:
    registerWithEureka: true
    fetchRegistry: true
    serviceUrl:
      defaultZone: http://${server.host}:9761/eureka/,http://${server.host}:9763/eureka/

---
spring:
  application:
    name: eureka-server-clustered3
  profiles: peer3
server:
  port: 9763
  host: serverA
eureka:
  instance:
    hostname: eureka-peer3
    preferIpAddress: true
    instance-id: ${spring.cloud.client.ipAddress}:${server.port}:${spring.application.name}
  client:
    registerWithEureka: true
    fetchRegistry: true
    serviceUrl:
      defaultZone: http://${server.host}:9761/eureka/,http://${server.host}:9762/eureka/
配置2: application.properties

application-peer1.properties

eureka.client.fetchRegistry=true
eureka.client.registerWithEureka=true
eureka.client.serviceUrl.defaultZone=http\://serverA\:9762/eureka/,http\://serverA\:9763/eureka/
eureka.instance.hostname=serverA
eureka.instance.metadataMap.instanceId=${spring.application.name}\:${spring.application.instance_id\:${random.value}}
eureka.server.waitTimeInMsWhenSyncEmpty=0
server.port=9761
spring.application.name=server_peer1

application-peer2.properties

eureka.client.fetchRegistry=true
eureka.client.registerWithEureka=true
eureka.client.serviceUrl.defaultZone=http\://serverA\:9761/eureka/,http\://serverA\:9763/eureka/
eureka.instance.hostname=serverA
eureka.server.waitTimeInMsWhenSyncEmpty=0
server.port=9762
eureka.instance.metadataMap.instanceId=${spring.application.name}\:${spring.application.instance_id\:${random.value}}
spring.application.name=server_peer2

application-peer3.properties

eureka.client.fetchRegistry=true
eureka.client.registerWithEureka=true
eureka.client.serviceUrl.defaultZone=http\://serverA\:9761/eureka/,http\://serverA\:9762/eureka/
eureka.instance.hostname=serverA
eureka.server.waitTimeInMsWhenSyncEmpty=0
server.port=9763
eureka.instance.metadataMap.instanceId=${spring.application.name}\:${spring.application.instance_id\:${random.value}}
spring.application.name=server_peer3

本地跑测试的时候,需要编辑 etc/hosts
127.0.0.1 	serverA
Dockerfile 文件
FROM java:7
VOLUME /tmp
ADD eureka-0.0.1-SNAPSHOT.jar /app.jar
RUN bash -c 'touch /app.jar'
EXPOSE 9762
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]

pom.xml
<?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.demo</groupId>
	<artifactId>eureka</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<name>Eureka Server</name>
	<description>Eureka Server demo project</description>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>1.4.3.RELEASE</version>
		<relativePath />
	</parent>


	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<!-- springBoot 程序的主入口 -->
		<start-class>eurekademo.EurekaApplication</start-class> 
		<java.version>1.7</java.version>
		<docker.registry>docker.umiit.cn:5043</docker.registry>

	</properties>

	<profiles>
		<profile>
			<id>dev</id>
			<properties>
				<env>dev</env>
			</properties>
			<activation>
				<activeByDefault>true</activeByDefault>
			</activation>
		</profile>
		<profile>
			<id>peer1</id>
			<properties>
				<env>peer1</env>
				<docker>peer1</docker>
			</properties>
		</profile>
		<profile>
			<id>peer2</id>
			<properties>
				<env>peer2</env>
				<docker>peer2</docker>
			</properties>
		</profile>
		<profile>
			<id>peer3</id>
			<properties>
				<env>peer3</env>
				<docker>peer3</docker>
			</properties>
		</profile>
	</profiles>

	<dependencies>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-eureka-server</artifactId>
			<version>1.3.0.RELEASE</version>
		</dependency>
		<!-- <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> 
			</dependency> -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>org.springframework.cloud</groupId>
				<artifactId>spring-cloud-dependencies</artifactId>
				<version>Camden.SR6</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>



	<build>
		<plugins>
			<plugin>
				<groupId>com.spotify</groupId>
				<artifactId>docker-maven-plugin</artifactId>
				<version>0.2.11</version>
				<configuration>
					<pushImage>true</pushImage>
					<imageName>
						${docker.registry}/v3/${project.artifactId}:${docker}
					</imageName>

					<dockerDirectory>src/main/resources_${env}/docker</dockerDirectory>
					<resources>
						<resource>
							<targetPath>/</targetPath>
							<directory>${project.build.directory}</directory>
							<include>${project.build.finalName}.jar</include>
						</resource>
					</resources>
				</configuration>
			</plugin>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
				<!-- defined in spring-cloud-starter-parent pom (as documentation hint), 
					but needs to be repeated here -->
				<configuration>
					<requiresUnpack>
						<dependency>
							<groupId>com.netflix.eureka</groupId>
							<artifactId>eureka-core</artifactId>
						</dependency>
						<dependency>
							<groupId>com.netflix.eureka</groupId>
							<artifactId>eureka-client</artifactId>
						</dependency>
					</requiresUnpack>
				</configuration>
			</plugin>
			<plugin>
				<groupId>pl.project13.maven</groupId>
				<artifactId>git-commit-id-plugin</artifactId>
				<configuration>
					<failOnNoGitDirectory>false</failOnNoGitDirectory>
				</configuration>
			</plugin>
			<plugin>
				<!--skip deploy (this is just a test module) -->
				<artifactId>maven-deploy-plugin</artifactId>
				<configuration>
					<skip>true</skip>
				</configuration>
			</plugin>
			<plugin>
				<groupId>org.codehaus.mojo</groupId>
				<artifactId>build-helper-maven-plugin</artifactId>
				<executions>
					<execution>
						<id>add-resource</id>
						<phase>initialize</phase>
						<goals>
							<goal>add-resource</goal>
						</goals>
						<configuration>
							<resources>
								<resource>
									<directory>src/main/resources_${env}</directory>
								</resource>
							</resources>
						</configuration>
					</execution>
				</executions>
			</plugin>
		</plugins>
	</build>

	<repositories>
		<repository>
			<id>spring-snapshots</id>
			<name>Spring Snapshots</name>
			<url>https://repo.spring.io/libs-snapshot-local</url>
			<snapshots>
				<enabled>true</enabled>
			</snapshots>
		</repository>
		<repository>
			<id>spring-milestones</id>
			<name>Spring Milestones</name>
			<url>https://repo.spring.io/libs-milestone-local</url>
			<snapshots>
				<enabled>false</enabled>
			</snapshots>
		</repository>
		<repository>
			<id>spring-releases</id>
			<name>Spring Releases</name>
			<url>https://repo.spring.io/libs-release-local</url>
			<snapshots>
				<enabled>false</enabled>
			</snapshots>
		</repository>
	</repositories>
	<pluginRepositories>
		<pluginRepository>
			<id>spring-snapshots</id>
			<name>Spring Snapshots</name>
			<url>https://repo.spring.io/libs-snapshot-local</url>
			<snapshots>
				<enabled>true</enabled>
			</snapshots>
		</pluginRepository>
		<pluginRepository>
			<id>spring-milestones</id>
			<name>Spring Milestones</name>
			<url>https://repo.spring.io/libs-milestone-local</url>
			<snapshots>
				<enabled>false</enabled>
			</snapshots>
		</pluginRepository>
	</pluginRepositories>

</project>

.gitlab-ci.yml
image: docker.umiit.cn:5043/maven_docker:latest

stages:
  - build

run_build_peer1:
  stage: build
  tags:
    - docker
  only:
    - develop
  script:   
    - mvn clean package docker:build -Ppeer1

run_build_peer2:
  stage: build
  tags:
    - docker
  only:
    - develop
  script:   
    - mvn clean package docker:build -Ppeer2

run_build_peer3:
  stage: build
  tags:
    - docker
  only:
    - develop
  script:   
    - mvn clean package docker:build -Ppeer3

docker-compose.yml
eureka_1:
  image: docker.umiit.cn:5043/v3/eureka:peer1
  volumes:
    - /etc/localtime:/etc/localtime:ro
    - /etc/timezone:/etc/timezone:ro
    - /mnt/docker-data/logstash/eureka_1:/usr/local/tomcat/logs
  environment:
    spring.profiles.active: peer1
  ports:
    - "公网IP:9761:9761"
eureka_2:
  image: docker.umiit.cn:5043/v3/eureka:peer2
  volumes:
    - /etc/localtime:/etc/localtime:ro
    - /etc/timezone:/etc/timezone:ro
    - /mnt/docker-data/logstash/eureka_2:/usr/local/tomcat/logs
  environment:
    spring.profiles.active: peer2
  ports:
    - "公网IP:9762:9762"
eureka_3:
  image: docker.umiit.cn:5043/v3/eureka:peer3
  volumes:
    - /etc/localtime:/etc/localtime:ro
    - /etc/timezone:/etc/timezone:ro
    - /mnt/docker-data/logstash/eureka_2:/usr/local/tomcat/logs
  environment:
    spring.profiles.active: peer3
  ports:
    - "公网IP:9763:9763"
项目结构
|-- src/main/resources_peer1 
|-- src/main/resources_peer2 
|-- src/main/resources_peer3

# 放置 Dockerfile & application.yml
本地测试
mvn package

java -jar eureka-server-0.0.1-SNAPSHOT.jar --spring.profiles.active=peer1
java -jar eureka-server-0.0.1-SNAPSHOT.jar --spring.profiles.active=peer2
java -jar eureka-server-0.0.1-SNAPSHOT.jar --spring.profiles.active=peer3

客户端配置

application.yml
server:
  port: ${vcap.application.port:9099}
  
eureka:
  instance:
    preferIpAddress: true
  client:
    serviceUrl:
      defaultZone: http://${serverA}:9761/eureka/,http://${serverA}:9762/eureka/,http://${serverA}:9763/eureka/
    registerWithEureka: true
    fetchRegistry: true

docker-compose.yml
ticketservice:
  image: docker.umiit.cn:5043/v3/ticket-service:prep
  volumes:
    - /etc/localtime:/etc/localtime:ro
    - /etc/timezone:/etc/timezone:ro
    - /mnt/docker-data/logstash/ticketservice:/logs
  external_links:
    - common_mysql_1:mysqlDb
    - common_redis_1:redisDb
    - common_mongo_1:mongoDb
  environment:
    serverA: localhost
    JVM_ARGS: -Xmx1024m
  ports:
    - "0.0.0.0:9099:9099"
yeaservice:
  image: docker.umiit.cn:5043/v3/yea-service:ipc
  volumes:
    - /etc/localtime:/etc/localtime:ro
    - /etc/timezone:/etc/timezone:ro
    - /mnt/docker-data/logstash/yeaservice:/logs
  external_links:
    - common_mysql_1:mysqlDb
    - common_redis_1:redisDb
    - common_mongo_1:mongoDb
  environment:
    serverA: localhost
    JVM_ARGS: -Xmx1024m
  ports:
    - "0.0.0.0:8081:8081"
hystrix-dashboard:
  image: kennedyoliveira/hystrix-dashboard
  ports:
    - "0.0.0.0:7979:7979"
  environment:
    JVM_ARGS: -Xmx512m
yeaservice - Application.java

@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
@EnableDiscoveryClient
@EnableCircuitBreaker
@EnableHystrixDashboard
@RibbonClient(name = "yea-ribbon", configuration = YeaRibbonConfiguration.class)
@ComponentScan(basePackages = { "com.**" })
public class Application {

	public static void main(String[] args) {
		SpringApplication.run(Application.class, args);
	}

}

yeaservice - 调用 ticketservice

@FeignClient(value = "ticket-service")
interface TicketClient {
	
}

这里使用了Feign的框架演示。

查看结果

curl -i IP:9761/  # 在浏览器中输入网址

官方知识点

Authenticating with the Eureka Server

HTTP basic authentication will be automatically added to your eureka client if one of the eureka.client.serviceUrl.defaultZone URLs has credentials embedded in it (curl style, like http://user:password@localhost:8761/eureka). For more complex needs you can create a @Bean of type DiscoveryClientOptionalArgs and inject ClientFilter instances into it, all of which will be applied to the calls from the client to the server.

Zones

希望在不同的Zone中配置你的客户端,首先要确认你的Eureka servers 部署在不同的Zone中,并且互相连接了,接下来要告诉Server你的服务实例在哪里,你可以使用metadataMap属性,例如像下面这样配置:

Service 1 in Zone 1
eureka.instance.metadataMap.zone = zone1
eureka.client.preferSameZoneEureka = true
Service 1 in Zone 2
eureka.instance.metadataMap.zone = zone2
eureka.client.preferSameZoneEureka = true

NOTE Because of a limitation in Eureka it isn’t possible to support per-server basic auth credentials, so only the first set that are found will be used.

单例模式

如果是单例模式的话,不必开启下面的选项,因为心跳检测会不断的进行,会报错连接不上。

eureka:
  instance:
    hostname: localhost
  client:
    registerWithEureka: false			## this
    fetchRegistry: false				## this	
    serviceUrl:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

参考链接

如何实现微服务架构中的服务发现

Microservice Registration and Discovery with Spring Cloud and Netflix’s Eureka

Spring Cloud构建微服务架构(一)服务注册与发现

Spring Cloud Netflix

QA

使用serverA 的域名配置,修改宿主机的 /etc/hosts ,不能集群连接

需要在docker容器中配置 /etc/hosts, 所以 vim Dockerfile

RUN echo '10.45.189.178 serverA' >> /etc/hosts

重新启动,发现还是无效,cat /etc/hosts 没有写进serverA 的映射,后面推测应该是容器的/etc/hosts是动态生成的,所以这个语句无效。 从环境变量开始入手 docker-compose.yml

environment:
serverA: 内网IP