Eureka替換方案Consul


1 Eureka閉源的影響

1.1 Eureka閉源

Eureka閉源

  • 在Eureka的GitHub上,宣布Eureka 2.x閉源。這意味着如果開發者繼續使用2.x分支上現有工作repo的一部分發布的代碼庫和組件,則自負風險。

1.2 Eureka的替換方案

1.2.1 Zookeeper

  • Zookeeper是一個分布式的,開放源代碼的分布式應用程序協調服務,是Hadoop和Hbase的重要組件。它是一個為分布式應用提供一致性服務的軟件,提供的功能包括:配置維護、域名服務、分布式同步、組服務等。

1.2.2 Consul

  • Consul是近幾年比較流行的服務發現工具。
  • Consul的三個主要應用場景:服務發現、服務隔離、服務配置。

1.2.3 Nacos

  • Nacos是阿里巴巴推出來的一個新開源項目,這是一個更易於構建雲原生應用的動態服務發現、配置管理和服務管理平台。Nacos致力於幫助您發現、配置和管理微服務。Nacos提供了一組簡單易用的特性集,幫助您快速實現動態服務發現、服務配置、服務元數據以及流量管理。Nacos幫助您更敏捷和容易的構建、交付和管理微服務平台。Nacos是構建以“服務”為中心的現代應用架構(例如微服務范式、雲原生范式)的服務基礎設施。

2 Consul簡介

2.1 概述

  • Consul是HashiCorp公司推出的開源工具,用於實現分布式系統的服務發現和配置。和其他分布式服務注冊和發現的方案,Consul的方案更“一站式”,內置了服務注冊和發現框架、分布式一致性協議實現、健康檢查、key/value存儲、多數據中心方案,不再需要依賴其他工具(比如Zookeeper等)。使用起來也比較簡單。Consul使用Go語言編寫,因此具有天然移植性(支持Linux、Windows和Mac OS X),安裝包僅包含一個可執行的文件,方便部署,和Docker等輕量級容器可以無縫配合。

2.2 Consul的優勢

  • 采用Raft算法來保證一致性,比服務的Paxos算法更直接。Zookeeper采用的是Paxos算法,而Consul以及etcd采用的是Raft算法。
  • 支持多數據中心,內外網的服務采用不同的端口進行監聽。多數據中心集群可以避免單數據中心的單點故障,而其部署則需要考慮網絡延遲、分片等情況。Zookeeper和etcd均不提供多數據中心功能的支持。
  • 支持健康檢查。etcd不提供此功能。
  • 支持http和dns協議接口。Zookeeper的集成較為復雜,etcd只支持http協議。
  • 官方提供web管理界面。etcd無此功能。

2.3 Consul的特性

  • 服務發現。
  • 健康檢查。
  • key/value存儲。
  • 多數據中心。

2.3 Consul和Eureka的區別

2.3.1 一致性

  • Consul:強一致性(CP):

  • 1️⃣服務注冊相比Eureka會稍慢一些。因為Consul的Raft協議要求必須過半的節點都寫入成功才認為注冊成功。

  • 2️⃣Leader掛掉后,重新選舉期間整個Consul不可用。保證了強一致性,但犧牲了可用性。

  • Eureka:高可用性和最終一致性(AP):

  • 1️⃣服務注冊相對要快,因為不需要等注冊信息復制到其他節點,也不保證注冊信息是否復制成功。

  • 2️⃣當數據出現不一致的時候,雖然A,B上的注冊信息不完全相同,但是每個Eureka節點依然能夠正常對外提供服務,這會出現查詢服務信息時如果請求A查不到,但請求B就能查到。如果保證了可用性但犧牲了一致性。

2.3.2 開發語言和使用

  • Eureka是Servlet程序,跑在Servlet容器中。
  • Consul是Go語言編寫而言的,安裝啟動即可。

2.4 Consul的下載和安裝

Consul不同於Eureka需要單獨安裝,訪問官網可以下載Consul的最新版本,目前使用的是Consul 1.8.4。根據不同的操作系統類型選擇不同的安裝包,Consul支持所有主流操作系統。

Consul支持所有主流操作系統

2.4.1 在linux中下載Consul

# 從官網下載最新版本的consul服務
wget https://releases.hashicorp.com/consul/1.8.4/consul_1.8.4_linux_amd64.zip
# 使用unzip命令解壓
unzip consul_1.8.4_linux_amd64.zip
# 將解壓好的consul可執行命令賦值到/usr/local/bin目錄下
cp consul /usr/local/bin
# 測試一下
consul

2.4.2 啟動Consul

# 以開發者模式快速啟動,-client指定客戶端可以訪問的IP地址
consul agent -dev -client=0.0.0.0
  • 啟動之后,返回http://localhost:8500,可以看到Consul的管理界面:

Consul管理界面

3 Consul的基本使用

Consul支持健康檢查,並提供了HTTP和DNS調用的API接口來完成服務的注冊,服務發現以及KV存儲這些功能。本人在VMWear中的Linux的IP地址是192.168.32.100。

3.1 服務注冊和發現

3.1.1 注冊服務

  • 通過postman發送PUT請求到http://192.168.32.100:8500/v1/catalog/register地址可以完成服務注冊。
{
    "Datacenter": "dc1",
    "Node": "node01",
    "Address": "192.168.1.57",
    "Service": {
        "ID": "mysql-01",
        "Service": "mysql",
        "tags": [
            "master",
            "v1"
        ],
        "Address": "192.168.1.57",
        "Port": 3306
    }
}

Consul基本使用之注冊服務

3.1.2 服務查詢

  • 通過postman發送GET請求到http://192.168.32.100:8500/v1/catalog/services地址獲取所有的服務列表。

Consul基本使用之查詢所有服務

  • 通過postman發送GET請求到http://192.168.32.100:8500/v1/catalog/service/mysql獲取具體的服務。

Consul獲取具體的服務

3.1.3 服務刪除

  • 通過postman發送PUT請求到http://192.168.32.100:8500/v1/catalog/deregister刪除服務。

Consul服務刪除

3.2 Consul的KV存儲

  • 可以參照Consul提供的KV存儲的API完成基於Consul的數據存儲。
含義 請求路徑 請求方式
查看key v1/kv/:key GET
保存或更新 v1/kv/:key put
刪除 /v1/kv/:key DELETE
  • key值中可以帶/,可以看做是不同的目錄結構。
  • value的值經過了base64編碼,獲取到數據后需要經過base64解碼才能獲取到原始值。數據不能大於521kb。
  • 不同的數據中心的kv存儲系統是獨立的,使用dc=?參數指定。

4 基於Consul的服務注冊

4.1 案例目標

  • 准備一個商品微服務和訂單微服務。
  • 將商品微服務注冊到Consul中。
  • 訂單微服務從consul中拉取所有的服務列表。

4.2 案例准備

4.2.1 sql腳本

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for department
-- ----------------------------
DROP TABLE IF EXISTS `department`;
CREATE TABLE `department`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of department
-- ----------------------------
INSERT INTO `department` VALUES (1, '開發部');
INSERT INTO `department` VALUES (2, '運維部');

-- ----------------------------
-- Table structure for employee
-- ----------------------------
DROP TABLE IF EXISTS `employee`;
CREATE TABLE `employee`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `gender` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of employee
-- ----------------------------
INSERT INTO `employee` VALUES (1, 'zhangsan', '男');
INSERT INTO `employee` VALUES (2, '李四', '女');

-- ----------------------------
-- Table structure for tb_order
-- ----------------------------
DROP TABLE IF EXISTS `tb_order`;
CREATE TABLE `tb_order`  (
  `id` bigint(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) NULL DEFAULT NULL COMMENT '用戶id',
  `product_id` int(11) NULL DEFAULT NULL COMMENT '商品id',
  `number` int(11) NULL DEFAULT NULL COMMENT '數量',
  `price` decimal(10, 2) NULL DEFAULT NULL COMMENT '單價',
  `amount` decimal(10, 2) NULL DEFAULT NULL COMMENT '總額',
  `product_name` varchar(40) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '商品名',
  `username` varchar(40) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '用戶名',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for tb_product
-- ----------------------------
DROP TABLE IF EXISTS `tb_product`;
CREATE TABLE `tb_product`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `caption` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `inventory` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `price` decimal(19, 2) NULL DEFAULT NULL,
  `product_desc` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `product_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `status` int(11) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = MyISAM AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of tb_product
-- ----------------------------
INSERT INTO `tb_product` VALUES (1, 'iPhone', '1', 5000.10, '蘋果手機就是香', '蘋果哇', 50);

-- ----------------------------
-- Table structure for tb_user
-- ----------------------------
DROP TABLE IF EXISTS `tb_user`;
CREATE TABLE `tb_user`  (
  `id` bigint(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(40) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '用戶名',
  `password` varchar(40) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '密碼',
  `age` int(3) NULL DEFAULT NULL COMMENT '年齡',
  `balance` decimal(10, 2) NULL DEFAULT NULL COMMENT '余額',
  `address` varchar(80) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '地址',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;

4.2.2 商品微服務

  • 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>spring_cloud_demo</artifactId>
        <groupId>org.sunxiaping</groupId>
        <version>1.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>product_serivce-consul9003</artifactId>

    <dependencies>
        <!--    服務監控    -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
    </dependencies>
</project>
  • application.yml
server:
  port: 9003 # 微服務的端口號

spring:
  application:
    name: service-product # 微服務的名稱
  datasource:
    url: jdbc:mysql://192.168.1.57:3306/test?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: 123456
  jpa:
    generate-ddl: true
    show-sql: true
    open-in-view: true
    database: mysql


# 微服務info內容詳細信息
info:
  app.name: xxx
  company.name: xxx
  build.artifactId: $project.artifactId$
  build.version: $project.version$
  • Product.java
package com.sunxiaping.product.domain;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

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

@Setter
@Getter
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Table(name = "tb_product")
public class Product implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "product_name")
    private String productName;

    @Column(name = "status")
    private Integer status;

    @Column(name = "price")
    private BigDecimal price;

    @Column(name = "product_desc")
    private String productDesc;

    @Column(name = "caption")
    private String caption;

    @Column(name = "inventory")
    private String inventory;   
}
  • ProductRepository.java
package com.sunxiaping.product.dao;

import com.sunxiaping.product.domain.Product;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.stereotype.Repository;

@Repository
public interface ProductRepository extends JpaRepository<Product, Long>, JpaSpecificationExecutor<Product> {
}
  • ProductService.java
package com.sunxiaping.product.service;

import com.sunxiaping.product.domain.Product;

public interface ProductService {

    /**
     * 根據id查詢
     *
     * @param id
     * @return
     */
    Product findById(Long id);


    /**
     * 保存
     *
     * @param product
     */
    void save(Product product);


    /**
     * 更新
     *
     * @param product
     */
    void update(Product product);


    /**
     * 刪除
     *
     * @param id
     */
    void delete(Long id);
}
  • ProductServiceImpl.java
package com.sunxiaping.product.service.impl;

import com.sunxiaping.product.dao.ProductRepository;
import com.sunxiaping.product.domain.Product;
import com.sunxiaping.product.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.transaction.Transactional;

@Service
@Transactional
public class ProductServiceImpl implements ProductService {

    @Autowired
    private ProductRepository productRepository;

    @Override
    public Product findById(Long id) {
        return productRepository.findById(id).orElse(new Product());
    }

    @Override
    public void save(Product product) {
        productRepository.save(product);
    }

    @Override
    public void update(Product product) {
        productRepository.save(product);
    }

    @Override
    public void delete(Long id) {
        productRepository.deleteById(id);
    }
}
  • ProductController.java
package com.sunxiaping.product.controller;

import com.sunxiaping.product.domain.Product;
import com.sunxiaping.product.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping(value = "/product")
public class ProductController {

    @Autowired
    private ProductService productService;

    @PostMapping(value = "/save")
    public String save(@RequestBody Product product) {
        productService.save(product);
        return "新增成功";
    }

    @GetMapping(value = "/findById/{id}")
    public Product findById(@PathVariable(value = "id") Long id) {
        Product product = productService.findById(id);

        return product;
    }
}
  • 啟動類:
package com.sunxiaping.product;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

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

4.2.3 訂單微服務

  • 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>spring_cloud_demo</artifactId>
        <groupId>org.sunxiaping</groupId>
        <version>1.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>order-service-consul9004</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
    </dependencies>

</project>
  • application.yml
server:
  port: 9004 # 微服務的端口號

spring:
  application:
    name: service-order # 微服務的名稱
  datasource:
    url: jdbc:mysql://192.168.1.57:3306/test?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: 123456
  jpa:
    generate-ddl: true
    show-sql: true
    open-in-view: true
    database: mysql
  jmx:
    unique-names: true


# 微服務info內容詳細信息
info:
  app.name: xxx
  company.name: xxx
  build.artifactId: $project.artifactId$
  build.version: $project.version$

# 開啟日志debug
logging:
  level:
    root: info
  • SpringConfig.java
package com.sunxiaping.order.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class SpringConfig {

    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}
  • Product.java
package com.sunxiaping.order.domain;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

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

@Setter
@Getter
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Table(name = "tb_product")
public class Product implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "product_name")
    private String productName;

    @Column(name = "status")
    private Integer status;

    @Column(name = "price")
    private BigDecimal price;

    @Column(name = "product_desc")
    private String productDesc;

    @Column(name = "caption")
    private String caption;

    @Column(name = "inventory")
    private String inventory;
    
}
  • OrderController.java
package com.sunxiaping.order.controller;


import com.sunxiaping.order.domain.Product;
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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
@RequestMapping(value = "/order")
public class OrderController {
    @Autowired
    private RestTemplate restTemplate;

    @GetMapping(value = "/buy/{id}")
    public Product buy(@PathVariable(value = "id") Long id) {
        Product product = restTemplate.getForObject("http://localhost:9003/product/findById/" + id, Product.class);
        return product;
    }
}
  • 啟動類:
package com.sunxiaping.order;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;


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

4.3 服務注冊

4.3.1 在商品微服務中添加SpringCloud基於Consul的依賴

  • 修改部分:
<!--   SpringCloud提供的基於Consul的服務發現     -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
  • 完整部分:
<?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>spring_cloud_demo</artifactId>
        <groupId>org.sunxiaping</groupId>
        <version>1.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>product_serivce-consul9003</artifactId>

    <dependencies>
        <!--    服務監控    -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!--   SpringCloud提供的基於Consul的服務發現     -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-consul-discovery</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
    </dependencies>
</project>

4.3.2 在商品微服務的application.yml中配置服務注冊

  • 修改部分:
spring:  
  # 開始配置Consul的服務注冊
  cloud:
    consul:
      # ConsulServer的主機地址
      host: 192.168.32.100
      # ConsulServer端口
      port: 8500
      discovery:
        # 是否注冊
        register: true
        # 服務實例id 必須填寫 也可以寫成 {spring.application.name}:${spring.cloud.client.ip-address}
        instance-id: ${spring.application.name}-1
        # 服務實例名稱
        service-name: ${spring.application.name}
        # 服務實例端口
        port: ${server.port}
        # 健康檢查路徑
        health-check-path: /actuator/health
        # 健康檢查時間間隔
        health-check-interval: 15s
        # 開啟IP注冊
        prefer-ip-address: true
        # 實例的請求IP
        ip-address: ${spring.cloud.client.ip-address}
  • 完整部分:
server:
  port: 9003 # 微服務的端口號

spring:
  application:
    name: service-product # 微服務的名稱
  datasource:
    url: jdbc:mysql://192.168.1.57:3306/test?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: 123456
  jpa:
    generate-ddl: true
    show-sql: true
    open-in-view: true
    database: mysql
  # 開始配置Consul的服務注冊
  cloud:
    consul:
      # ConsulServer的主機地址
      host: 192.168.32.100
      # ConsulServer端口
      port: 8500
      discovery:
        # 是否注冊
        register: true
        # 服務實例id 必須填寫 也可以寫成 {spring.application.name}:${spring.cloud.client.ip-address}
        instance-id: ${spring.application.name}-1
        # 服務實例名稱
        service-name: ${spring.application.name}
        # 服務實例端口
        port: ${server.port}
        # 健康檢查路徑
        health-check-path: /actuator/health
        # 健康檢查時間間隔
        health-check-interval: 15s
        # 開啟IP注冊
        prefer-ip-address: true
        # 實例的請求IP
        ip-address: ${spring.cloud.client.ip-address}


# 微服務info內容詳細信息
info:
  app.name: xxx
  company.name: xxx
  build.artifactId: $project.artifactId$
  build.version: $project.version$

4.4 服務發現

4.4.1 在訂單微服務中添加SpringCloud基於Consul的依賴

  • 修改部分:
<!--    服務監控    -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--   SpringCloud提供的基於Consul的服務發現     -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
  • 完整部分:
<?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>spring_cloud_demo</artifactId>
        <groupId>org.sunxiaping</groupId>
        <version>1.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>order-service-consul9004</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!--    服務監控    -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!--   SpringCloud提供的基於Consul的服務發現     -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-consul-discovery</artifactId>
        </dependency>
    </dependencies>

</project>

4.4.2 在訂單微服務的application.yml中配置服務注冊

  • 修改部分:
spring:  
  # 開始配置Consul的服務注冊
  cloud:
    consul:
      # ConsulServer的主機地址
      host: 192.168.32.100
      # ConsulServer端口
      port: 8500
      discovery:
        # 是否注冊
        register: true
        # 服務實例id 必須填寫 也可以寫成 {spring.application.name}:${spring.cloud.client.ip-address}
        instance-id: ${spring.application.name}-1
        # 服務實例名稱
        service-name: ${spring.application.name}
        # 服務實例端口
        port: ${server.port}
        # 健康檢查路徑
        health-check-path: /actuator/health
        # 健康檢查時間間隔
        health-check-interval: 15s
        # 開啟IP注冊
        prefer-ip-address: true
        # 實例的請求IP
        ip-address: ${spring.cloud.client.ip-address}
  • 完整部分:
server:
  port: 9004 # 微服務的端口號

spring:
  application:
    name: service-order # 微服務的名稱
  datasource:
    url: jdbc:mysql://192.168.1.57:3306/test?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: 123456
  jpa:
    generate-ddl: true
    show-sql: true
    open-in-view: true
    database: mysql
  jmx:
    unique-names: true
  # 開始配置Consul的服務注冊
  cloud:
    consul:
      # ConsulServer的主機地址
      host: 192.168.32.100
      # ConsulServer端口
      port: 8500
      discovery:
        # 是否注冊
        register: true
        # 服務實例id 必須填寫 也可以寫成 {spring.application.name}:${spring.cloud.client.ip-address}
        instance-id: ${spring.application.name}-1
        # 服務實例名稱
        service-name: ${spring.application.name}
        # 服務實例端口
        port: ${server.port}
        # 健康檢查路徑
        health-check-path: /actuator/health
        # 健康檢查時間間隔
        health-check-interval: 15s
        # 開啟IP注冊
        prefer-ip-address: true
        # 實例的請求IP
        ip-address: ${spring.cloud.client.ip-address}

# 微服務info內容詳細信息
info:
  app.name: xxx
  company.name: xxx
  build.artifactId: $project.artifactId$
  build.version: $project.version$

# 開啟日志debug
logging:
  level:
    root: info

4.4.3 在商品微服務的RestTemplate上面標注@LoadBalanced注解

  • SpringConfig.java
package com.sunxiaping.order.config;

import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class SpringConfig {

    /**
     * SpringCloud對Consul進行了進一步的處理,向其中集成了Ribbon的支持
     *
     * @return
     */
    @LoadBalanced
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

4.4.4 修改Controller

  • OrderController.java
package com.sunxiaping.order.controller;


import com.sunxiaping.order.domain.Product;
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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
@RequestMapping(value = "/order")
public class OrderController {
    @Autowired
    private RestTemplate restTemplate;

    @GetMapping(value = "/buy/{id}")
    public Product buy(@PathVariable(value = "id") Long id) {
        Product product = restTemplate.getForObject("http://service-product/product/findById/" + id, Product.class);
        return product;
    }
}

5 Consul集群的搭建

5.1 Consul的架構簡介

Consul架構

  • 圖中的Server是Consul服務端高可用集群,Client是Consul的客戶端。Consul客戶端不保存數據,客戶端將接收到的請求轉發給響應的Server端。Server之間通過局域網或廣域網通信實現數據一致性。每個Server或Client都是一個Consul agent。Consul集群間使用了gossip協議和raft一致性算法。
  • 上圖涉及到的相關術語:
  • 1️⃣agent:agent是用來啟動一個consul的守護進程。
  • 2️⃣client:是consul的代理,用來和consul server進行交互。一個微服務對應一個client,微服務和client部署到一台機器上。
  • 3️⃣server:一個server是一個具有一組擴展功能的代理,這些功能包括參與raft選舉、維護集群狀態、響應RPC查詢,和其他數據中心交互以及轉發查詢給Leader或者遠程數據中心等。簡而言之。server就是真正干活的consul服務。一般推薦3~5個。
  • 4️⃣gossip協議:流言協議。所有的consul都會參與到gossip協議中。

ossip協議

  • 5️⃣raft協議:保證server集群的數據一致。
    • Leader:處理所有客戶端交互、日志復制等,一般一次只有一個Leader。
    • Follower:類似選民,完全被動。
    • Candidate(候選人):可以被選為一個新的領導人。
  • Leader全權負責所有客戶端的請求,以及將數據同步到Follower中(同一時刻系統中只存在一個Leader)。
  • Follower被動響應請求RPC,從不主動發起RPC。
  • Candidate由Follower向Leader轉換中間狀態。

5.2 Consul集群搭建

5.2.1 概述

Consul集群搭建

  • 首先一個正常的Consul集群,有Server,有Client。這里的服務器Server1、Server2和Server3上分別部署了Consul Server(這些服務器上最好只部署Consul程序,以盡量維護Consul Server的穩定)。
  • 服務器Server4和Server5上通過Consul Client分別注冊Service A、B、C,這里每個Service分別注冊在了兩個服務器上,這樣可以避免Service的單點問題(一般而言微服務和Client綁定)。
  • 在服務器Server6中Service D需要訪問ServiceB,這個時候Service D首先訪問本機Consul Client提供的HTTP API,本機Client會將請求轉發到Consul Server,Consul Server查詢到Service B當前的信息返回。

5.2.2 准備環境

服務器IP Consul類型 Node節點名稱 序號
192.168.237.100 Server server-1 s1
192.168.237.101 Server server-2 s2
192.168.237.102 Server server-3 s3
192.168.237.1 Client client-1 s4

簡而言之,就是通過VMWear新建3個CentOS7環境,然后每個CentOS7環境中的網絡模式都是NAT。

  • agent以client模式啟動的節點:在該模式下,該節點會采集相關信息,通過RPC的方式向server發送。client模式節點有無數個,官方建議搭配微服務配置。
  • agent以server模式啟動的節點:一個數據中心至少包含1個server節點。不過官網建議使用3~5個server節點組成集群,以保證高可用且不失效率。server節點參與raft、維護微服務信息、注冊服務、健康檢查等功能。

5.2.3 安裝consul並啟動

  • 關閉每個consul節點上(Linux機器)上的防火牆:
systemctl stop firewalld
systemctl disable firewalld
  • 在每個consul節點上安裝consul服務,下載安裝過程和單節點一致:
# 從官網下載最新版本的consul服務
wget https://releases.hashicorp.com/consul/1.8.4/consul_1.8.4_linux_amd64.zip
# 使用unzip命令解壓
unzip consul_1.8.4_linux_amd64.zip
# 將解壓好的consul可執行命令賦值到/usr/local/bin目錄下
cp consul /usr/local/bin
# 測試一下
consul
  • 啟動每個consul server節點:
consul agent -server -bootstrap-expect 3 -data-dir=/etc/consul.d -node=server-1 -bind=192.168.237.100 -ui -client 0.0.0.0 &
consul agent -server -bootstrap-expect 3 -data-dir=/etc/consul.d -node=server-2 -bind=192.168.237.101 -ui -client 0.0.0.0 &
consul agent -server -bootstrap-expect 3 -data-dir=/etc/consul.d -node=server-3 -bind=192.168.237.102 -ui -client 0.0.0.0 &

-server:以server身份啟動

-bootstrap-expect:集群要求的最少Server數量,當低於這個數量,集群將失效

-data-dir:data存放的目錄。

-node:節點id,在同一集群中不能重復。

-bind:監聽的IP地址。

-client:客戶端的IP地址(0.0.0.0)表示不限制。

&:在后台運行,Linux腳本語法

  • 在本地電腦中使用client形式啟動consul:
consul agent  -data-dir /etc/consul.d -node=client-1 -bind=192.168.237.1 -client=0.0.0.0

5.2.4 每個節點加入到集群中

  • 在s2(192.168.237.101),s3(192.168.237.102)和s4(192.168.237.1)服務行通過consul join命令加入s1中的consul集群中。
## 加入到consul集群
consul join 192.168.237.100

5.2.5 測試

  • 在任意一台服務器中輸入consul members查看集群中的所有節點信息:
# 查看consul集群節點信息
consul members

查看consul集群節點信息

  • 通過瀏覽器http://192.168.237.100:8500/ui/dc1/nodes查看:

集群測試成功

5.3 微服務注冊到Consul集群中

5.3.1 訂單微服務注冊到Consul集群中

  • application.yml
server:
  port: 9003 # 微服務的端口號

spring:
  application:
    name: service-product # 微服務的名稱
  datasource:
    url: jdbc:mysql://192.168.237.100:3306/test?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: 123456
  jpa:
    generate-ddl: true
    show-sql: true
    open-in-view: true
    database: mysql
  # 開始配置Consul的服務注冊
  cloud:
    consul:
      # Consul Client的地址  修改的地方
      host: 192.168.237.1
      # ConsulServer端口
      port: 8500
      discovery:
        # 是否注冊
        register: true
        # 服務實例id 必須填寫 也可以寫成 {spring.application.name}:${spring.cloud.client.ip-address}
        instance-id: ${spring.application.name}-1
        # 服務實例名稱
        service-name: ${spring.application.name}
        # 服務實例端口
        port: ${server.port}
        # 健康檢查路徑
        health-check-path: /actuator/health
        # 健康檢查時間間隔
        health-check-interval: 15s
        # 開啟IP注冊
        prefer-ip-address: true
        # 實例的請求IP
        ip-address: ${spring.cloud.client.ip-address}



# 微服務info內容詳細信息
info:
  app.name: xxx
  company.name: xxx
  build.artifactId: $project.artifactId$
  build.version: $project.version$

5.3.2 商品微服務注冊到Consul集群中

  • application.yml
server:
  port: 9004 # 微服務的端口號

spring:
  application:
    name: service-order # 微服務的名稱
  datasource:
    url: jdbc:mysql://192.168.237.100:3306/test?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: 123456
  jpa:
    generate-ddl: true
    show-sql: true
    open-in-view: true
    database: mysql
  jmx:
    unique-names: true
  # 開始配置Consul的服務注冊
  cloud:
    consul:
      # Consul Client的地址  修改的地方
      host: 192.168.237.1
      # ConsulServer端口
      port: 8500
      discovery:
        # 是否注冊
        register: true
        # 服務實例id 必須填寫 也可以寫成 {spring.application.name}:${spring.cloud.client.ip-address}
        instance-id: ${spring.application.name}-1
        # 服務實例名稱
        service-name: ${spring.application.name}
        # 服務實例端口
        port: ${server.port}
        # 健康檢查路徑
        health-check-path: /actuator/health
        # 健康檢查時間間隔
        health-check-interval: 15s
        # 開啟IP注冊
        prefer-ip-address: true
        # 實例的請求IP
        ip-address: ${spring.cloud.client.ip-address}

# 微服務info內容詳細信息
info:
  app.name: xxx
  company.name: xxx
  build.artifactId: $project.artifactId$
  build.version: $project.version$

# 開啟日志debug
logging:
  level:
    root: info

5.4 Consul的常見問題

5.4.1 節點和服務注銷

  • 當服務或者節點失效,Consul不會對注冊的信息進行剔除處理,僅僅進行標記而已(並且服務不可以使用)。如果擔心失效節點和失效服務過多影響監控。可以通過調用HTTP API的形式進行處理。
  • 節注銷任意節點和服務:
# PUT請求
http://192.168.32.100:8500/v1/catalog/deregister
  • 注銷當前節點的服務:
# PUT請求
http://192.168.32.100:8500/v1/agent/service/deregister/[service-id]
  • 如果某個節點不繼續使用,也可以在本機使用consul leave命令,或者在其他節點使用consul force-level 節點id

5.4.2 健康檢查和故障轉移

  • 在集群環境下,健康檢查是由服務注冊到的agent來處理的,如果這個agent掛掉了,那么此節點的健康檢查就處於無人管理的狀態了。
  • 從實際應用看,節點上的服務可能既要被發現,又要發現別的服務,如果節點掛掉了,僅提供被發現的功能實際上服務還是不可用的。當然發現別的服務也可以不使用本機節點,可以通過訪問一個Nginx實現的若干Consul節點的負載均衡來實現。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM