多節點高可用Eureka集羣與服務註冊

1 簡介

Eureka是Netfilx開元的服務發現組件,本身是一個基於REST的服務。它包含EurekaServer(以下簡稱ES)和EurekaClient(以下簡稱EC)兩部分,SpringCloud將它集成在子項目SpringCloudNetfilx中,實現了微服務的註冊與發現

2 原理

我們可以直接看Eureka的架構圖
Eureka架構圖
上面說了 Eureka分爲Server和Client兩部分,解釋一下,我們拿us-east-1c來說:

  1. us-east-1c裏面的ApplicationService爲我們微服務的提供方,ApplicationClient爲服務的調用地方,他們通過MakeRemoteCall通訊,可以理解爲RESTful API行爲。
  2. 他們同時都與ES保持聯繫,通過EC向ES發送心跳來續約自己在ES中的註冊。
  3. ES提供服務發現的能力,會存儲各個微服務啓動時發送來的信息。
  4. 當ES在一定時間內沒有接收到某個微服務實例的心跳時,ES將會註銷該實例。
  5. ES同時也可以是EC,當有多個節點時,如上圖1d,1e,1c之間的ES通過互相複製來同步自己的服務註冊表。
  6. EC也會緩存服務註冊表中的信息,這樣不用每次請求都查詢ES,降低ES的壓力,同時當所有ES都宕了,消費者仍然可以根據緩存來完成調用。

3 代碼

示例代碼爲SpringBoot項目,採用Maven管理依賴,數據庫使用了H2,項目同時整合了Actuator

3.1 編寫單節點EurekaServer

創建一個Maven項目,完整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>

    <groupId>com.transientBa.cloud</groupId>
    <artifactId>microservice-discovery-eureka</artifactId>
    <version>1.0-SNAPSHOT</version>

    <!-- 引入spring boot的依賴 -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.4.3.RELEASE</version>
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <!-- 提供了SpringMVC的支持 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- 提供了Spring Data JpA -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <!--H2數據庫支持-->
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
        </dependency>
        <!--整合Spring Boot Actuator-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!--Eureka server依賴-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka-server</artifactId>
            <version>1.1.3.RELEASE</version>
        </dependency>
    </dependencies>

    <!-- 引入spring cloud的依賴 -->
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Camden.SR4</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <!-- 添加spring-boot的maven插件 -->
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>


</project>

在resource下新建一個application.yml文件作爲配置文件

server:
  port: 8761
eureka:
  client:
    #是否將自己這個服務註冊到EurekaServer中  默認True 此服務爲Server  所以爲False
    registerWithEureka: false   
    #是否從EurekaServer獲取註冊信息 默認True 單點ES不存在其他的EurkaServer 所以爲False      
    fetchRegistry: false              
    serviceUrl:
      #與ES交互的地址 查詢註冊都依賴此地址 默認爲http://localhost:8761/eureka 多個地址使用","分割
      defaultZone: http://localhost:8761/eureka  

編寫啓動類:

在啓動類上面加上@EnableEurekaServer註解來標識這是一個ES服務

@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaApplication.class,args);
    }
}

跑一下測試看看:

瀏覽器輸入:http://localhost:8761/
這裏寫圖片描述
我們可以看到Instances currently registered with Eureka下是沒有實例的 因爲現在只有ES,接下來我們來編寫EC的代碼

3.2 編寫微服務提供者

同樣新建一個Maven項目,此項目簡單的提供一個查詢用戶的接口

完整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>

    <groupId>com.TransientBa.cloud</groupId>
    <artifactId>microservice-provider-user</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <!-- 引入spring boot的依賴 -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.4.3.RELEASE</version>
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <!-- 提供了SpringMVC的支持 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- 提供了Spring Data JpA -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <!--整合Spring Boot Actuator-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!--Eureka server依賴-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka-server</artifactId>
            <version>1.1.3.RELEASE</version>
        </dependency>
        <!--H2數據庫支持-->
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
        </dependency>
    </dependencies>

    <!-- 引入spring cloud的依賴 -->
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Camden.SR4</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <!-- 添加spring-boot的maven插件 -->
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

resources下的application.yml如下:

server:
  port: 8000
  tomcat:
    uri-encoding: UTF-8

spring:
  application:
    name: microserviece-provider-user           # 用於指定註冊到Eureka Server上的應用名稱
  jpa:
    generate-ddl: false           # 是否生成ddl語句
    show-sql: true          # 是否打印sql語句
    hibernate:
      ddl-auto: none
  datasource:                 # 指定數據源
    platform: h2              # 指定數據源類型
    schema: classpath:schema.sql    # 指定h2數據庫的建表腳本
    data: classpath:data.sql        # 指定h2數據庫的數據腳本
  http:
    encoding:
      charset: UTF-8
      enabled: true
      force: true
  messages:
    encoding: UTF-8

logging:            # 配置日誌級別,讓hibernate打印執行的SQL
  level:
    root: INFO
    org.hibernate: DEBUG
    org.hibernate.type.descriptor.sql.BasicBinder: TRACE
    org.hibernate.type.descriptor.sql.BasicExtractor: TRACE
    com.TransientBa: DEBUG

eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/
  instance:
    prefer-ip-address: true     # 將自己的ip註冊到Eureka Server上   如果不配置該屬性  則默認爲false且表示註冊微服務所在操作系統的hostname到Eureka Server

創建兩個sql文件扔在resource下作爲我們的數據庫

data.sql:

insert into user(id,username,name,age,balance) values(1,'account1','張三',20,100.00);
insert into user(id,username,name,age,balance) values(2,'account2','李四',28,180.00);
insert into user(id,username,name,age,balance) values(3,'account3','王五',32,280.00);

schema.sql:

drop table user if exists;
create table user(id bigint generated by default as identity,username varchar(40),name varchar(20) ,age int(3),balance decimal(10,2),primary key(id));

有了表我們來創建User類

package com.TransientBa.cloud.entity;

import javax.persistence.*;
import java.math.BigDecimal;

/**
 * User class
 *
 * @author TransientBa
 * @date 2018/5/4
 */
@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column
    private String username;
    @Column
    private String name;
    @Column
    private Integer age;
    @Column
    private BigDecimal balance;
    // 省略getset
}

創建dao

package com.TransientBa.cloud.dao;

import com.TransientBa.cloud.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

/**
 * UserRepository class
 *
 * @author TransientBa
 * @date 2018/5/4
 */
@Repository
public interface  UserRepository extends JpaRepository<User,Long> {
}

創建controller

package com.TransientBa.cloud.controller;

import com.TransientBa.cloud.dao.UserRepository;
import com.TransientBa.cloud.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

/**
 * UserController class
 *
 * @author TransientBa
 * @date 2018/5/4
 */
@RestController
public class UserController {
    @Autowired
    private UserRepository userRepository;

    @GetMapping("/{id}")
    public User findById(@PathVariable Long id){
        User findOne = this.userRepository.findOne(id);
        return findOne;
    }
}

編寫啓動類

package com.TransientBa.cloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

/**
 * ProviderUserApplication class
 *
 * @author TransientBa
 * @date 2018/5/4
 */
@EnableDiscoveryClient
@SpringBootApplication
public class ProviderUserApplication {
    public static void main(String[] args) {
        SpringApplication.run(ProviderUserApplication.class,args);
    }
}

完整的項目結構如下:

這裏寫圖片描述

同樣我們測試下

這裏寫圖片描述
可以看到Instances currently registered with Eureka下有了一個叫做MICROSERVIECE-PROVIDER-USER的實例
同樣我們在創建一個微服務消費者

3.3 編寫消費者微服務

POM

POM和提供者一樣 只需要改一下artifactId換成消費者的ID,添加一個依賴

  <groupId>com.transientBa.cloud</groupId>
  <artifactId>microservice-simple-consumer-movie</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>
   <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-web</artifactId>
   </dependency>

寫一個POJO用戶類

package com.TransientBa.cloud.pojo;

import java.math.BigDecimal;

/**
 * User class
 *
 * @author TransientBa
 * @date 2018/5/5
 */
public class User {
    private Long id;
    private String username;
    private String name;
    private Integer age;
    private BigDecimal balance;
    //GetSet..
}

Controller

package com.TransientBa.cloud.controller;

import com.TransientBa.cloud.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

/**
 * MovieController class
 *
 * @author TransientBa
 * @date 2018/5/5
 */
@RestController
public class MovieController {
    //使用restTemplate請求User服務
    @Autowired
    private RestTemplate restTemplate;

    //讀取配置文件Url路徑
    @Value("${user.userServiceUrl}")
    private String userServiceUrl;


    @GetMapping("/user/{id}")
    public User findById(@PathVariable Long id){
        return this.restTemplate.getForObject(userServiceUrl+id,User.class);
    }
}

application啓動類

package com.TransientBa.cloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

/**
 * ConsumerMovieApplication class
 *
 * @author TransientBa
 * @date 2018/5/5
 */
@SpringBootApplication
public class ConsumerMovieApplication {

    //以下寫法等價於 RestTemplate restTemplate = new RestTemplate();
    @Bean
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }

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

application.ymi

server:
  port: 8010
user:
  userServiceUrl: http://localhost:8000/
spring:
  application:
    name: microserviece-consumer-movie           # 用於指定註冊到Eureka Server上的應用名稱
eureka:
  client:
    serviceUrl:
#      defaultZone: http://peer1:8761/eureka/
      defaultZone: http://localhost:8761/eureka/
  instance:
    prefer-ip-address: true     # 將自己的ip註冊到Eureka Server上   如果不配置該屬性  則默認爲false且表示註冊微服務所在操作系統的hostname到Eureka Server

測試一下

這裏寫圖片描述

這樣在Instances currently registered with Eureka下 ,我們就有了兩個服務

高可用Eureka集羣

通過上面我們完成了一個單節點的Eureka,消費者和提供者與ES保持聯繫,ES提供發現註冊的功能,看起來很美好,但很明顯並不適用生產環境,同樣不高可用
我們可以思考一個問題,當Eureka Server宕掉之後會發生什麼?消費服務仍然靠着緩存來消費遠程API,若提供者正常服務這當然沒有問題,但如果提供者也出現了不可用的情況,整個系統就會受到了影響,我們採用EurekaServer集羣來解決這個問題

我們修改Eureka Server這個服務

通過用spring.profiles來配置兩個環境,模擬兩個Server

首先修改系統的hosts文件

windows系統的hosts文件在:C:\Windows\System32\drivers\etc下
修改爲127.0.0.1 peer1 peer2

修改EurekaServer下的application.yml文件如下

這樣來讓兩個Server啓動後相互註冊

spring:
  application:
    name: microservice-discovery-eureka-ha
---
spring:
  #指定profile = peer1
  profiles: peer1
server:
  port: 8761
eureka:
  instance:
    #指定當profile=peer1時,主機名是peer1
    hostname: peer1
  client:
    serviceUrl:
      #將自己註冊到peer2這個Eureka上去
      defaultZone: http://peer2:8762/eureka/
---
spring:
  profiles: peer2
server:
  port: 8762
eureka:
  instance:
    hostname: peer2
  client:
    serviceUrl:
      defaultZone: http://peer1:8761/eureka/

修改消費者和提供者的application.yml文件

defaultZone: http://peer1:8761/eureka/,http://peer2:8762/eureka/

將Eureka Server打包後,我們用兩個命令啓動兩個Server:

java -jar microservice-discovery-eureka-ha-1.0-SNAPSHOT.jar --spring.profiles.active=peer1
java -jar microservice-discovery-eureka-ha-1.0-SNAPSHOT.jar --spring.profiles.active=peer2

然後再啓動消費者和服務者兩個微服務,然後訪問peer1:8761或者peer2:8762
這裏寫圖片描述
能看到Instances currently registered with Eureka下分別有:

MICROSERVICE-DISCOVERY-EUREKA-HA    n/a (2) (2) UP (2) - DESKTOP-TBLJVIP:microservice-discovery-eureka-ha:8762 , DESKTOP-TBLJVIP:microservice-discovery-eureka-ha:8761
MICROSERVIECE-CONSUMER-MOVIE    n/a (1) (1) UP (1) - DESKTOP-TBLJVIP:microserviece-consumer-movie:8010
MICROSERVIECE-PROVIDER-USER n/a (1) (1) UP (1) - DESKTOP-TBLJVIP:microserviece-provider-user:8000

這三個實例

4 總結

兩個EurekaServer通過複製的方式實現註冊表的同步,這樣我們就完成了一個高可用的EurekaServer集羣了,多節點Server也是一個道理。當EurekaServer集羣中某個服務宕掉了,消費服務就可以通過其他節點來同步註冊表信息,如果你要問我所有的Server全部宕掉了怎麼辦,很簡單,像bilibili一樣請個道士做個法吧。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章