本文位址:https://blog.csdn.net/m0_37595562/article/details/80646063
代碼位址:https://gitee.com/sunnymore/high_availability_eureka
grpc實戰文章:https://blog.csdn.net/m0_37595562/article/details/80474037
之前Sunny有聊過grpc架構,那是用于遠端服務調用的架構。今天我們來搭建一個eureka服務治理架構。本文所做的demo代碼,Sunny已經上傳,歡迎大家根據上面位址fork實踐。另外,之前grpc的文章位址也在上面。
關于eureka,從eureka的角度來看,主要分為兩個角色:eureka server和eureka client。而從整個系統的角度來看,可以分為三個角色。eureka server叫做服務注冊中心,eureka client中可以分為服務提供者和服務調用者。
今天要做的系統大緻架構如下,Sunny畫的比較簡陋:

服務注冊中心有三個,內建一個高可用叢集,且三個eureka server之間互相注冊自己,三者之間會進行互現通信。另外有兩個服務提供者,以實作服務的高可用性,他們在某台服務注冊中心注冊服務,三台服務注冊中心互相之間會同步服務資訊,是以服務消費者在任何一台服務注冊中心都能發現所有已注冊的服務——事實上,通常會找較近zone的服務注冊中心發現服務。服務消費者通過服務注冊中心獲得目前已經注冊的服務清單,通過用戶端負載均衡選擇某個服務提供者,然後發起遠端調用。事實上,包括注冊服務、續約、終止服務或者發現服務都是通過restful的形式。服務具體的存儲形式是雙層map,外層key為服務名,内層key為執行個體名。
正式開始
一、建立總項目
本項目還是用多子產品來做。為了更簡潔明了一些,我們真的來建立多個實際子產品,而不采取同一個子產品多個配置啟動多個執行個體的方式。感興趣的小夥伴可以自己嘗試一下或者和我交流。總項目名為eureka-demo,其中包括eureka-server、service-provider和service-consumer等多個子產品。項目目錄如下圖所示:
二、建立服務注冊中心
首先,我們需要設定總的項目的pom.xml檔案,各子產品的一個基礎。總的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>com.sunny</groupId>
<artifactId>eureka-demo</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>eurekaserver1</module>
<module>eurekaserver2</module>
<module>eurekaserver3</module>
<module>serviceprovider2</module>
<module>serviceprovider1</module>
<module>serviceconsumer</module>
</modules>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-parent</artifactId>
<version>1.5.9.RELEASE</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
這裡我們Spring boot的版本用1.5.9.RELEASE。其實之前Sunny想嘗試用Spring boot2,但是和Spring cloud多次版本比對都出現了問題。是以還是選擇用Spring boot的1.5.9.RELEASE版本,Spring cloud版本選擇Edgware.SR3版本。
然後我們建立立eureka server1的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">
<parent>
<artifactId>eureka-demo</artifactId>
<groupId>com.sunny</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>eureka-server1</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Edgware.SR3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
這裡我們以整個項目的pom.xml為基礎,另外導入spring-cloud-starter-eureka-server依賴。當然不要忘了spring cloud依賴。
然後我們寫application.yml檔案:
server:
port: 1111
eureka:
instance:
hostname: peer1
client:
serviceUrl:
defaultZone: http://peer2:1112/eureka/,http://peer3:1113/eureka/
spring:
application:
name: eureka-server
這裡,我們設定端口為1111,目前執行個體主機名為peer1。同時,設定eureka.client.serviceUrl.defaultZone為另外兩台eureka server的服務注冊位址。如果是單eureka server的環境,就将eureka.client.serviceUrl.defaultZone設定為本機的服務注冊位址,同時需要設定eureka.client.registerWithEureka和eureka.client.fetchRegistry為false,以防止自己注冊自己。同時設定應用名為eureka-server,另外兩台服務注冊中心也需要設定相同的名稱。值得一提的是,這裡peer1、peer2和peer3需要在hosts檔案中設定為127.0.0.1,在生産環境中可以寫真實的域名。
接下來,我們寫一個啟動類,加上@EnableEurekaServer注解即可。
@EnableEurekaServer
@SpringBootApplication
public class Server1Application {
public static void main(String[] args) {
new SpringApplicationBuilder(Server1Application.class).web(true).run(args);
}
}
另外兩台eureka server的pom.xml檔案一樣,當然除了端口,通過eureka server1中的application.yml檔案中的配置就可以發現eureka server2的域名為peer2,其端口為1112;eureka server3的域名為peer3,端口為1113。application.yml檔案中的eureka.client.serviceUrl.defaultZone分别設定為另外兩個eureka server的服務位址。具體代碼可參見傳送門源碼連結。
三、建立服務提供者
還是從pom.xml檔案說起,服務提供者作為一個eureka client導入的依賴有所不同,不需要導入spring-cloud-starter-eureka-server依賴,需要導入的是spring-cloud-starter-eureka依賴。
<?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">
<parent>
<artifactId>eureka-demo</artifactId>
<groupId>com.sunny</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>service-provider1</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Edgware.SR3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
然後寫配置檔案,也與服務注冊中心有所不同。
server:
port: 8081
eureka:
client:
serviceUrl:
defaultZone: http://peer1:1111/eureka/
spring:
application:
name: hello-service
這裡,端口設定為8081;在peer1上注冊服務,如前面所說,隻需向一台eureka server注冊即可,其他服務注冊中心會進行服務同步——當然,這裡想要向多台服務注冊中心注冊也行,位址之間以逗号隔開即可;并且設定應用名為hello-service。
然後我們寫一個啟動類,ServiceProvider1Application.class,加上EnableEurekaClient注解。
@SpringBootApplication
@EnableEurekaClient
public class ServiceProvider1Application {
public static void main(String[] args) {
SpringApplication.run(ServiceProvider1Application.class,args);
}
}
接着就是寫實際的服務了,我們就寫最簡單的hello world式的restful接口。
@RestController
public class HelloController {
@RequestMapping("/hello")
public String hello(){
return "hello world";
}
}
現在服務提供者service provider1基本完成了,我們用同樣的代碼再寫一個service provider2。在配置上,我們将端口設定為8082,其他都可一樣,當然也可以将eureka.client.serviceUrl.defaultZone設定為其他兩台eureka server的服務注冊位址。具體配置如下:
server:
port: 8082
eureka:
client:
serviceUrl:
defaultZone: http://peer1:1111/eureka/
spring:
application:
name: hello-service
四、建立服務消費者
建立服務消費者,還是先看相關的依賴。關于eureka client的依賴和服務提供者一樣,另外還需要添加spring-cloud-starter-ribbon的依賴用于用戶端負載均衡,預設是輪詢的方式。
<?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">
<parent>
<artifactId>eureka-demo</artifactId>
<groupId>com.sunny</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>service-consumer</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Edgware.SR3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
配置和服務提供者基本一樣,但是這裡,我們讓服務消費者的eureka.client.serviceUrl.defaultZone為eureka server2,這裡是特意不設定成eureka server1的,喜歡的童鞋可以按自己的喜好設定,甚至是設定多個,用逗号隔開。
spring:
application:
name: hello-consumer
server:
port: 9090
eureka:
client:
serviceUrl:
defaultZone: http://peer2:1112/eureka/
然後就是啟動類,這裡一樣加上EnableEurekaClient的注解。同時建立RestTemplate的bean,加上LoadBalanced注解用于負載均衡。具體如下:
@SpringBootApplication
@EnableEurekaClient
public class ConsumerApplication {
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class,args);
}
}
然後就是具體的服務消費類了,這裡我們注入restTemplate執行個體,并用它來調用服務。同樣,我們寫成一個restful接口,可以用于驗證結果。具體如下:
@RestController
public class ConsumerController {
@Autowired
private RestTemplate restTemplate;
@RequestMapping("/helloConsumer")
public String helloConsumer(){
return restTemplate.getForEntity("http://hello-service/hello",String.class).getBody();
}
}
五、啟動并驗證
首先我們啟動eureka server1、eureka server2和eureka server3。啟動過程中控制台中可能會有warn或者error資訊,如下面的資訊,但是這時候不要慌,這是因為peer2和peer3還未啟動,是以出現連接配接失敗的情況,當三個eureka server全部啟動後,系統就是完全正常運作的。
2018-06-05 21:27:39.233 ERROR 10164 --- [-target_peer2-2] c.n.e.cluster.ReplicationTaskProcessor : Network level connection to peer peer2; retrying after delay
com.sun.jersey.api.client.ClientHandlerException: java.net.SocketTimeoutException: Read timed out
at com.sun.jersey.client.apache4.ApacheHttpClient4Handler.handle(ApacheHttpClient4Handler.java:187) ~[jersey-apache-client4-1.19.1.jar:1.19.1]
at com.netflix.eureka.cluster.DynamicGZIPContentEncodingFilter.handle(DynamicGZIPContentEncodingFilter.java:48) ~[eureka-core-1.7.2.jar:1.7.2]
at com.netflix.discovery.EurekaIdentityHeaderFilter.handle(EurekaIdentityHeaderFilter.java:27) ~[eureka-client-1.7.2.jar:1.7.2]
at com.sun.jersey.api.client.Client.handle(Client.java:652) ~[jersey-client-1.19.1.jar:1.19.1]
at com.sun.jersey.api.client.WebResource.handle(WebResource.java:682) ~[jersey-client-1.19.1.jar:1.19.1]
at com.sun.jersey.api.client.WebResource.access$200(WebResource.java:74) ~[jersey-client-1.19.1.jar:1.19.1]
at com.sun.jersey.api.client.WebResource$Builder.post(WebResource.java:570) ~[jersey-client-1.19.1.jar:1.19.1]
at com.netflix.eureka.transport.JerseyReplicationClient.submitBatchUpdates(JerseyReplicationClient.java:116) ~[eureka-core-1.7.2.jar:1.7.2]
at com.netflix.eureka.cluster.ReplicationTaskProcessor.process(ReplicationTaskProcessor.java:71) ~[eureka-core-1.7.2.jar:1.7.2]
at com.netflix.eureka.util.batcher.TaskExecutors$BatchWorkerRunnable.run(TaskExecutors.java:187) [eureka-core-1.7.2.jar:1.7.2]
at java.lang.Thread.run(Thread.java:748) [na:1.8.0_161]
Caused by: java.net.SocketTimeoutException: Read timed out
at java.net.SocketInputStream.socketRead0(Native Method) ~[na:1.8.0_161]
at java.net.SocketInputStream.socketRead(SocketInputStream.java:116) ~[na:1.8.0_161]
at java.net.SocketInputStream.read(SocketInputStream.java:171) ~[na:1.8.0_161]
at java.net.SocketInputStream.read(SocketInputStream.java:141) ~[na:1.8.0_161]
at org.apache.http.impl.io.AbstractSessionInputBuffer.fillBuffer(AbstractSessionInputBuffer.java:161) ~[httpcore-4.4.8.jar:4.4.8]
at org.apache.http.impl.io.SocketInputBuffer.fillBuffer(SocketInputBuffer.java:82) ~[httpcore-4.4.8.jar:4.4.8]
at org.apache.http.impl.io.AbstractSessionInputBuffer.readLine(AbstractSessionInputBuffer.java:278) ~[httpcore-4.4.8.jar:4.4.8]
at org.apache.http.impl.conn.DefaultHttpResponseParser.parseHead(DefaultHttpResponseParser.java:138) ~[httpclient-4.5.3.jar:4.5.3]
at org.apache.http.impl.conn.DefaultHttpResponseParser.parseHead(DefaultHttpResponseParser.java:56) ~[httpclient-4.5.3.jar:4.5.3]
at org.apache.http.impl.io.AbstractMessageParser.parse(AbstractMessageParser.java:259) ~[httpcore-4.4.8.jar:4.4.8]
at org.apache.http.impl.AbstractHttpClientConnection.receiveResponseHeader(AbstractHttpClientConnection.java:286) ~[httpcore-4.4.8.jar:4.4.8]
at org.apache.http.impl.conn.DefaultClientConnection.receiveResponseHeader(DefaultClientConnection.java:257) ~[httpclient-4.5.3.jar:4.5.3]
at org.apache.http.impl.conn.AbstractClientConnAdapter.receiveResponseHeader(AbstractClientConnAdapter.java:230) ~[httpclient-4.5.3.jar:4.5.3]
at org.apache.http.protocol.HttpRequestExecutor.doReceiveResponse(HttpRequestExecutor.java:273) ~[httpcore-4.4.8.jar:4.4.8]
at org.apache.http.protocol.HttpRequestExecutor.execute(HttpRequestExecutor.java:125) ~[httpcore-4.4.8.jar:4.4.8]
at org.apache.http.impl.client.DefaultRequestDirector.tryExecute(DefaultRequestDirector.java:684) ~[httpclient-4.5.3.jar:4.5.3]
at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:486) ~[httpclient-4.5.3.jar:4.5.3]
at org.apache.http.impl.client.AbstractHttpClient.doExecute(AbstractHttpClient.java:835) ~[httpclient-4.5.3.jar:4.5.3]
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:118) ~[httpclient-4.5.3.jar:4.5.3]
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:56) ~[httpclient-4.5.3.jar:4.5.3]
at com.sun.jersey.client.apache4.ApacheHttpClient4Handler.handle(ApacheHttpClient4Handler.java:173) ~[jersey-apache-client4-1.19.1.jar:1.19.1]
... 10 common frames omitted
我們可以檢視服務注冊中心的相關資訊,通路peer1:1111或者peer2:1112或者peer3:1113可以檢視。
可以看到服務已經注冊上了:
同時可以檢視相關的可用節點:
然後我們啟動兩個服務提供者,同樣在此檢視服務注冊中心的資訊,可以發現服務已經注冊上了:
然後在啟動服務消費者,再次檢視服務注冊中心的資訊:
最後,我們來驗證服務是否調用成功,通路localhost:9090/helloConsumer,成功得到如下結果:
最後的最後,我們驗證負載均衡的效果。為了驗證負載均衡,我們在服務提供者的restful接口中加入一個列印資訊,service provider1中為:
@RestController
public class HelloController {
@RequestMapping("/hello")
public String hello(){
System.out.println("1");
return "hello world";
}
}
service provider2中為:
@RestController
public class HelloController {
@RequestMapping("/hello")
public String hello(){
System.out.println("2");
return "hello world";
}
}
然後多次調用服務消費者的restful接口,發現兩個服務提供者的控制台資訊中依次列印1和2,這也說明了确實是輪詢的。
至此,本篇文章到此結束,喜歡的童鞋可以點個贊。
歡迎轉載,轉載時請注明原文位址:https://blog.csdn.net/m0_37595562/article/details/80646063
童鞋們如果有疑問或者想和我交流的話有兩種方式:
第一種
評論留言
第二種
郵箱聯系:[email protected]