1. 程式人生 > >Spring-cloud微服務 Eureka學習教程-分散式搭建EurekaServer、EurekaClient(中級)

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掛掉了,其他的服務是可以立即頂上去,保證客戶端向其發起請求有響應。

       兩者的對比我們先大致分析到這裡。具體的分析可以看我轉的一篇部落格:

       

https://blog.csdn.net/asd529735325/article/details/85049662

      接下來我們嘗試自己搭建一個分散式的包含: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來動態呼叫服務消費者。