1 Eureka閉源的影響
1.1 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支持所有主流操作系統。
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的管理界面:
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
}
}
3.1.2 服務查詢
- 通過postman發送GET請求到http://192.168.32.100:8500/v1/catalog/services地址獲取所有的服務列表。
- 通過postman發送GET請求到http://192.168.32.100:8500/v1/catalog/service/mysql獲取具體的服務。
3.1.3 服務刪除
- 通過postman發送PUT請求到http://192.168.32.100:8500/v1/catalog/deregister刪除服務。
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的架構簡介
- 圖中的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協議中。
- 5️⃣raft協議:保證server集群的數據一致。
- Leader:處理所有客戶端交互、日志復制等,一般一次只有一個Leader。
- Follower:類似選民,完全被動。
- Candidate(候選人):可以被選為一個新的領導人。
- Leader全權負責所有客戶端的請求,以及將數據同步到Follower中(同一時刻系統中只存在一個Leader)。
- Follower被動響應請求RPC,從不主動發起RPC。
- Candidate由Follower向Leader轉換中間狀態。
5.2 Consul集群搭建
5.2.1 概述
- 首先一個正常的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
- 通過瀏覽器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節點的負載均衡來實現。