Spring-cloud微服務 Eureka學習教程-分散式搭建EurekaServer、EurekaClient(中級)
我們這裡只有一臺伺服器,所以我們先仿叢集搭建。
完整demo專案程式碼:https://github.com/wades2/EurekaDemo2
在這之前我們先分析分析Eureka相比其他註冊中心的好處。在一般的應用過程中,如果註冊中心service出現了問題,然而沒有備用的節點去替代這個主節點去分發服務,就會造成相關注冊服務的癱瘓,因此我們在分散式架構中,都會有備用節點。
我們先看看Doubel,在doubel中,zookeeper(以下簡稱zk)是作為服務註冊中心的,在叢集配置中,會有一個主從關係節點。如果Master掛了, zk就會啟動節點推舉,而在節點推舉的過程中,整個服務是不能使用的。但是zk強調的是CAP理論中的CP強調高的一致性,【注:什麼是CAP理論:Consistency(一致性)、 Availability(可用性)、Partition tolerance(分割槽容錯性),三者不可兼得】,在實際上線的站點或者網站中,30~60s的選舉過程導致站點不可用,損失是很大。
而我們再看看Eureka(如上圖),Eureka是去中心化的,每個Eureka server都是平級的。Eureka取CAP中的AP,注重可用性,Eureka內建了心跳服務(用於淘汰一些“瀕死”的伺服器),即使當你的應用服務某個server掛掉了,其他的服務是可以立即頂上去,保證客戶端向其發起請求有響應。
兩者的對比我們先大致分析到這裡。具體的分析可以看我轉的一篇部落格:
接下來我們嘗試自己搭建一個分散式的包含:Eureka Server,Application Service,Application Client的Eureka註冊中心。
通過上面這張圖我們可以看到,一個Eureka有三個角色:
1、Eureka Server(通過Register, Get,Renew等介面提供註冊和發現)
2、Application Service(服務提供方,把自身服務例項註冊到Eureka Server):
3、Application Client(Service Consumer):服務呼叫方,通過Eureka Server獲取服務例項,並呼叫Application Service
他們主要進行的活動如下:
每個Region有一個Eureka Cluster, Region中的每個Zone都至少有一個Eureka Server。
Service作為一個Eureka Client,通過register註冊到Eureka Server,並且通過傳送心跳的方式更新租約(renew leases)。如果Eureka Client到期沒有更新租約,那麼過一段時間後,Eureka Server就會移除該Service例項。
當一個Eureka Server的資料改變以後,會把自己的資料同步到其他Eureka Server。
Application Client也作為一個Eureka Client通過Get介面從Eureka Server中獲取Service例項資訊,然後直接呼叫Service例項。
Application Client呼叫Service例項時,可以跨可用區呼叫。(【引】)
在上面一篇文章中我們已經搭建好了Eureka Server和Application Client,如果不清楚的朋友建議可以先看看這篇博文(https://blog.csdn.net/asd529735325/article/details/84992538,git程式碼:https://github.com/wades2/EurekaDemo ),再來本篇中一起探討學習。
為了保護eureka安全性,在上一篇的基礎上,我加了service訪問密碼。因此在client端和叢集配置的service都需要加上相互的訪問密碼。
先看看我個人demo大致架構吧:
其中:EurekaServer模組和Eurekaserver_back是兩個相互平級依賴的server,EurekaClient是我們服務提供方,EurekaCaller是我們服務的呼叫方。大致的呼叫過程如下圖:
基於上一篇的基礎,我們從新看看pom檔案和相關配置。
首先是Eureka Server(此處我們是叢集配置,因此有兩個server)
首先修改電腦的hostname,新增:127.0.0.1 localhost server1 server2,作為兩個server的相互依賴。
兩個server的pom檔案都一樣:
<?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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.M3</spring-cloud.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Camden.SR5</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
<version>1.3.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</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>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</project>
相關注意點在上一篇部落格有提到,這裡就不贅述。
EurekaServer的配置檔案:
# 應用程式的名稱
spring.application.name=Eureka-server2
security.user.name=user
security.user.password=123456
#修改啟動埠
server.port=8083
# 是否將該例項的註冊資訊註冊到Eureka伺服器上,在只有一個Eureka伺服器的情況下沒必要,只是用於例項的發現
eureka.client.register-with-eureka=true
# 是否向Eureka伺服器獲取註冊資訊,在單例項的Eureka中共沒必要
eureka.client.fetch-registry=true
#Eureka Server能夠迅速有效地踢出已關停的節點,但是新手由於Eureka自我保護模式,以及心跳週期長的原因,常常會遇到Eureka Server不踢出已關停的節點的問題
# 設為false,關閉自我保護
eureka.server.enable-self-preservation=true
#清理間隔
eureka.server.eviction-interval-timer-in-ms=6000
eureka.client.serviceUrl.defaultZone=http://user:[email protected]:8082/eureka
eureka.instance.hostname=server1
EurekaServer_back的配置檔案:
# 應用程式的名稱
spring.application.name=Eureka-server2
security.user.name=user
security.user.password=123456
#修改啟動埠
server.port=8082
# 是否將該例項的註冊資訊註冊到Eureka伺服器上,在只有一個Eureka伺服器的情況下沒必要,只是用於例項的發現
eureka.client.register-with-eureka=true
# 是否向Eureka伺服器獲取註冊資訊,在單例項的Eureka中共沒必要
eureka.client.fetch-registry=true
#Eureka Server能夠迅速有效地踢出已關停的節點,但是新手由於Eureka自我保護模式,以及心跳週期長的原因,常常會遇到Eureka Server不踢出已關停的節點的問題
# 設為false,關閉自我保護
eureka.server.enable-self-preservation=true
#清理間隔
eureka.server.eviction-interval-timer-in-ms=60000
eureka.client.serviceUrl.defaultZone=http://user:[email protected]:8083/eureka
eureka.instance.hostname=server2
因為我們引入了security的jar包,保證訪問server頁面的安全性,因此,在client和其他節點配置的時候,都應該加上使用者名稱和密碼,把defaultZone改為:http://使用者名稱:密碼@hostname:埠號/eureka的形式。
【注意!!!!,這裡因為是相互依賴,所以在EurekaServer中defaultZone使用的是EurekaServer_back的使用者名稱,密碼和它對應的hostname+埠,同理EurekaSerer_back對應的是EurekaServer的。】
執行類都一樣:
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
然後我們可以啟動一個server訪問一下:
訪問需要使用者名稱密碼,就是配置檔案裡面的security.user.name,和security.user.password,我們可以看到介面如下:
這裡我們可以看到因為我們8082埠的server因為沒有啟動,所以無法依賴到,但是已經註冊進去了。我們再啟動server_back可以看到介面如下:
這樣的介面就是註冊成功了。然後我們開始第二部:
Application Service註冊到server裡面,也就是我們上一篇說的client端,也就是我這個demo的EurekaClient:
pom檔案:
<?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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.M3</spring-cloud.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Camden.SR5</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--<dependency>-->
<!--<groupId>org.springframework.cloud</groupId>-->
<!--<artifactId>spring-cloud-netflix-eureka-client</artifactId>-->
<!--<version>1.3.6.RELEASE</version>-->
<!--</dependency>-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<version>1.4.0.M1</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
<version>1.3.6.RELEASE</version>
</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>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</project>
application配置檔案:
spring.application.name=Eureka-client
#eureka.client.allow-redirects=false
#修改啟動埠
server.port=8084
eureka.client.serviceUrl.defaultZone=http://user:[email protected]:8083/eureka,http://user:[email protected]:8082/eureka
#eureka.client.register-with-eureka=true
eureka.instance.ip-address=true
啟動類:
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication
@EnableEurekaClient
//@EnableDiscoveryClient
@ComponentScan(basePackages = {"com.example.demo.controller"})
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
這裡我們設定了掃描controller包下面的java檔案,我的demo中contorller如下:
其中UserController放置在controller這個包下,還有一個作為測試的Contrller放置在啟動類平級目錄(你也可以隨便放一個,我們只是做後期實驗的)
我們的UserController
package com.example.demo.controller;
import com.netflix.appinfo.InstanceInfo;
import com.netflix.discovery.DiscoveryClient;
import com.netflix.discovery.EurekaClient;
import com.netflix.discovery.converters.Auto;
import com.netflix.discovery.shared.Applications;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("user")
public class UserController {
// private static final Logger logger = LoggerFactory.getLogger(UserController.class);
// @Autowired
// private IUserService userService;
// @GetMapping("getUser")
// public List<UserEntity> getUser() {
// UserEntity user = new UserEntity();
// return userService.getUser();
// }
@Autowired
private EurekaClient eurekaClients;
// @Autowired
// private DiscoveryClient discoveryClient;
@GetMapping("getUser")
public String getUser() {
//todo 得到eureka server的服務例項
InstanceInfo info=eurekaClients.getNextServerFromEureka("Eureka-client",false);
// return info.getHomePageUrl();
return "hello one";
}
}
我們的Controller:
package com.example.demo;
import com.netflix.appinfo.InstanceInfo;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("user")
public class Controller {
@GetMapping("getUser2")
public String getUser() {
return "hello tow....";
}
}
然後啟動。
我們可以看到如下介面:
這裡client已經註冊進來了,然後我們可以訪問一下http://localhost:8084/user/getUser
訪問沒問題。
訪問http://localhost:8084/user/getUser2,報錯,因為我們啟動類沒有掃描其他的包。
最後一步:Application Client(Service Consumer):服務呼叫方,通過Eureka Server獲取服務例項,並呼叫Application Service
也就是我們的EurekaCaller:
直接使用Eureka Client還是比較麻煩的,幸運的是,RestTemplate整合了EurekaClient,Ribbon為我們提供了多樣的負載均衡的功能,為我們提供了很多便利,我們所需要做的就是在Spring中註冊一個RestTemplate,並且新增@LoadBalanced
註解。
加入Ribbon的原因是服務呼叫者不關心服務提供者有多少個服務例項,它只關注它要呼叫這個服務ID,因而可能需要一定的負載均衡。
pom檔案如下:
<?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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.RC1</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</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>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
</repository>
</repositories>
</project>
application配置檔案:
spring.application.name=Eureka_caller
server.port=8086
eureka.client.serviceUrl.defaultZone=http://user:[email protected]:8083/eureka,http://user:[email protected]:8082/eureka
啟動類一樣掃描controller:
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication
@ComponentScan({"com.example.demo.controller"})
@EnableEurekaClient
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
我們這裡的controller開始通過Rest客戶端對映呼叫註冊的Eureka_client。
package com.example.demo.controller;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
@Configuration
public class Controller {
@Bean
@LoadBalanced
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
@GetMapping("getUser/routine")
public String routine() {
RestTemplate restTemplate = getRestTemplate();
String json = restTemplate.getForObject("http://Eureka-client/user/getUser", String.class);
return json;
}
@GetMapping("getUser/routine2")
public String routine2() {
RestTemplate restTemplate = getRestTemplate();
String json = restTemplate.getForObject("http://Eureka-client/user/getUser2", String.class);
return json;
}
}
restTemplate.getForObject方法請求特定的服務url,注意裡面使用的Eureka-client正是我們剛才在服務提供者客戶端專案中配置的服務名稱,它已經被註冊到Eureka服務上。
然後我們啟動,訪問http://localhost:8086/getUser/routine
我們可以看到內容和訪問http://localhost:8084/user/getUser一樣,但是依舊訪問http://localhost:8086/getUser/routine2訪問不到,其實要想訪問到很簡單,在EurekaClient掃描中帶上Conrtoller所在的包就可以了!
OK,全文結束,接下來我們講解,如何充分利用ribbon來動態呼叫服務消費者。