1 Eureka闭源的影响

1.1 Eureka闭源

Eureka闭源.png

  • 在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.4 Consul和Eureka的区别

2.4.1 一致性

  • Consul:强一致性(CP):
  • Eureka替换方案Consul(不推荐) - 图2服务注册相比Eureka会稍慢一些。因为Consul的Raft协议要求必须过半的节点都写入成功才认为注册成功。
  • Eureka替换方案Consul(不推荐) - 图3Leader挂掉后,重新选举期间整个Consul不可用。保证了强一致性,但牺牲了可用性。
  • Eureka:高可用性和最终一致性(AP):
  • Eureka替换方案Consul(不推荐) - 图4服务注册相对要快,因为不需要等注册信息复制到其他节点,也不保证注册信息是否复制成功。
  • Eureka替换方案Consul(不推荐) - 图5当数据出现不一致的时候,虽然A,B上的注册信息不完全相同,但是每个Eureka节点依然能够正常对外提供服务,这会出现查询服务信息时如果请求A查不到,但请求B就能查到。如果保证了可用性但牺牲了一致性。

2.4.2 开发语言和使用

  • Eureka是Servlet程序,跑在Servlet容器中。
  • Consul是Go语言编写而言的,安装启动即可。

2.5 Consul的下载和安装

Consul不同于Eureka需要单独安装,访问官网可以下载Consul的最新版本,目前使用的是Consul 1.8.4。根据不同的操作系统类型选择不同的安装包,Consul支持所有主流操作系统。 Consul支持所有主流操作系统.png

2.5.1 在linux中下载Consul

  1. # 从官网下载最新版本的consul服务
  2. wget https://releases.hashicorp.com/consul/1.8.4/consul_1.8.4_linux_amd64.zip
  3. # 使用unzip命令解压
  4. unzip consul_1.8.4_linux_amd64.zip
  5. # 将解压好的consul可执行命令赋值到/usr/local/bin目录下
  6. cp consul /usr/local/bin
  7. # 测试一下
  8. consul

2.5.2 启动Consul

  1. # 以开发者模式快速启动,-client指定客户端可以访问的IP地址
  2. consul agent -dev -client=0.0.0.0

Consul管理界面.png

3 Consul的基本使用

Consul支持健康检查,并提供了HTTP和DNS调用的API接口来完成服务的注册,服务发现以及KV存储这些功能。本人在VMWear中的Linux的IP地址是192.168.32.100。

3.1 服务注册和发现

3.1.1 注册服务

  1. {
  2. "Datacenter": "dc1",
  3. "Node": "node01",
  4. "Address": "192.168.1.57",
  5. "Service": {
  6. "ID": "mysql-01",
  7. "Service": "mysql",
  8. "tags": [
  9. "master",
  10. "v1"
  11. ],
  12. "Address": "192.168.1.57",
  13. "Port": 3306
  14. }
  15. }

Consul基本使用之注册服务.png

3.1.2 服务查询

Consul基本使用之查询所有服务.png

Consul获取具体的服务.png

3.1.3 服务删除

Consul服务删除.png

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脚本

  1. SET NAMES utf8mb4;
  2. SET FOREIGN_KEY_CHECKS = 0;
  3. -- ----------------------------
  4. -- Table structure for department
  5. -- ----------------------------
  6. DROP TABLE IF EXISTS `department`;
  7. CREATE TABLE `department` (
  8. `id` int(11) NOT NULL AUTO_INCREMENT,
  9. `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  10. PRIMARY KEY (`id`) USING BTREE
  11. ) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
  12. -- ----------------------------
  13. -- Records of department
  14. -- ----------------------------
  15. INSERT INTO `department` VALUES (1, '开发部');
  16. INSERT INTO `department` VALUES (2, '运维部');
  17. -- ----------------------------
  18. -- Table structure for employee
  19. -- ----------------------------
  20. DROP TABLE IF EXISTS `employee`;
  21. CREATE TABLE `employee` (
  22. `id` int(11) NOT NULL AUTO_INCREMENT,
  23. `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  24. `gender` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  25. PRIMARY KEY (`id`) USING BTREE
  26. ) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
  27. -- ----------------------------
  28. -- Records of employee
  29. -- ----------------------------
  30. INSERT INTO `employee` VALUES (1, 'zhangsan', '男');
  31. INSERT INTO `employee` VALUES (2, '李四', '女');
  32. -- ----------------------------
  33. -- Table structure for tb_order
  34. -- ----------------------------
  35. DROP TABLE IF EXISTS `tb_order`;
  36. CREATE TABLE `tb_order` (
  37. `id` bigint(11) NOT NULL AUTO_INCREMENT,
  38. `user_id` int(11) NULL DEFAULT NULL COMMENT '用户id',
  39. `product_id` int(11) NULL DEFAULT NULL COMMENT '商品id',
  40. `number` int(11) NULL DEFAULT NULL COMMENT '数量',
  41. `price` decimal(10, 2) NULL DEFAULT NULL COMMENT '单价',
  42. `amount` decimal(10, 2) NULL DEFAULT NULL COMMENT '总额',
  43. `product_name` varchar(40) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '商品名',
  44. `username` varchar(40) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '用户名',
  45. PRIMARY KEY (`id`) USING BTREE
  46. ) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
  47. -- ----------------------------
  48. -- Table structure for tb_product
  49. -- ----------------------------
  50. DROP TABLE IF EXISTS `tb_product`;
  51. CREATE TABLE `tb_product` (
  52. `id` bigint(20) NOT NULL AUTO_INCREMENT,
  53. `caption` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  54. `inventory` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  55. `price` decimal(19, 2) NULL DEFAULT NULL,
  56. `product_desc` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  57. `product_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  58. `status` int(11) NULL DEFAULT NULL,
  59. PRIMARY KEY (`id`) USING BTREE
  60. ) ENGINE = MyISAM AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
  61. -- ----------------------------
  62. -- Records of tb_product
  63. -- ----------------------------
  64. INSERT INTO `tb_product` VALUES (1, 'iPhone', '1', 5000.10, '苹果手机就是香', '苹果哇', 50);
  65. -- ----------------------------
  66. -- Table structure for tb_user
  67. -- ----------------------------
  68. DROP TABLE IF EXISTS `tb_user`;
  69. CREATE TABLE `tb_user` (
  70. `id` bigint(11) NOT NULL AUTO_INCREMENT,
  71. `username` varchar(40) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '用户名',
  72. `password` varchar(40) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '密码',
  73. `age` int(3) NULL DEFAULT NULL COMMENT '年龄',
  74. `balance` decimal(10, 2) NULL DEFAULT NULL COMMENT '余额',
  75. `address` varchar(80) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '地址',
  76. PRIMARY KEY (`id`) USING BTREE
  77. ) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
  78. SET FOREIGN_KEY_CHECKS = 1;

4.2.2 商品微服务

  • pom.xml
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <parent>
  6. <artifactId>spring_cloud_demo</artifactId>
  7. <groupId>org.sunxiaping</groupId>
  8. <version>1.0</version>
  9. </parent>
  10. <modelVersion>4.0.0</modelVersion>
  11. <artifactId>product_serivce-consul9003</artifactId>
  12. <dependencies>
  13. <!-- 服务监控 -->
  14. <dependency>
  15. <groupId>org.springframework.boot</groupId>
  16. <artifactId>spring-boot-starter-actuator</artifactId>
  17. </dependency>
  18. <dependency>
  19. <groupId>org.springframework.boot</groupId>
  20. <artifactId>spring-boot-starter-data-jpa</artifactId>
  21. </dependency>
  22. <dependency>
  23. <groupId>mysql</groupId>
  24. <artifactId>mysql-connector-java</artifactId>
  25. </dependency>
  26. </dependencies>
  27. </project>
  • application.yml
  1. server:
  2. port: 9003 # 微服务的端口号
  3. spring:
  4. application:
  5. name: service-product # 微服务的名称
  6. datasource:
  7. url: jdbc:mysql://192.168.1.57:3306/test?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true
  8. driver-class-name: com.mysql.cj.jdbc.Driver
  9. username: root
  10. password: 123456
  11. jpa:
  12. generate-ddl: true
  13. show-sql: true
  14. open-in-view: true
  15. database: mysql
  16. # 微服务info内容详细信息
  17. info:
  18. app.name: xxx
  19. company.name: xxx
  20. build.artifactId: $project.artifactId$
  21. build.version: $project.version$
  • Product.java
  1. package com.sunxiaping.product.domain;
  2. import lombok.AllArgsConstructor;
  3. import lombok.Getter;
  4. import lombok.NoArgsConstructor;
  5. import lombok.Setter;
  6. import javax.persistence.*;
  7. import java.io.Serializable;
  8. import java.math.BigDecimal;
  9. @Setter
  10. @Getter
  11. @AllArgsConstructor
  12. @NoArgsConstructor
  13. @Entity
  14. @Table(name = "tb_product")
  15. public class Product implements Serializable {
  16. @Id
  17. @GeneratedValue(strategy = GenerationType.IDENTITY)
  18. private Long id;
  19. @Column(name = "product_name")
  20. private String productName;
  21. @Column(name = "status")
  22. private Integer status;
  23. @Column(name = "price")
  24. private BigDecimal price;
  25. @Column(name = "product_desc")
  26. private String productDesc;
  27. @Column(name = "caption")
  28. private String caption;
  29. @Column(name = "inventory")
  30. private String inventory;
  31. }
  • ProductRepository.java
  1. package com.sunxiaping.product.dao;
  2. import com.sunxiaping.product.domain.Product;
  3. import org.springframework.data.jpa.repository.JpaRepository;
  4. import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
  5. import org.springframework.stereotype.Repository;
  6. @Repository
  7. public interface ProductRepository extends JpaRepository<Product, Long>, JpaSpecificationExecutor<Product> {
  8. }
  • ProductService.java
  1. package com.sunxiaping.product.service;
  2. import com.sunxiaping.product.domain.Product;
  3. public interface ProductService {
  4. /**
  5. * 根据id查询
  6. *
  7. * @param id
  8. * @return
  9. */
  10. Product findById(Long id);
  11. /**
  12. * 保存
  13. *
  14. * @param product
  15. */
  16. void save(Product product);
  17. /**
  18. * 更新
  19. *
  20. * @param product
  21. */
  22. void update(Product product);
  23. /**
  24. * 删除
  25. *
  26. * @param id
  27. */
  28. void delete(Long id);
  29. }
  • ProductServiceImpl.java
  1. package com.sunxiaping.product.service.impl;
  2. import com.sunxiaping.product.dao.ProductRepository;
  3. import com.sunxiaping.product.domain.Product;
  4. import com.sunxiaping.product.service.ProductService;
  5. import org.springframework.beans.factory.annotation.Autowired;
  6. import org.springframework.stereotype.Service;
  7. import javax.transaction.Transactional;
  8. @Service
  9. @Transactional
  10. public class ProductServiceImpl implements ProductService {
  11. @Autowired
  12. private ProductRepository productRepository;
  13. @Override
  14. public Product findById(Long id) {
  15. return productRepository.findById(id).orElse(new Product());
  16. }
  17. @Override
  18. public void save(Product product) {
  19. productRepository.save(product);
  20. }
  21. @Override
  22. public void update(Product product) {
  23. productRepository.save(product);
  24. }
  25. @Override
  26. public void delete(Long id) {
  27. productRepository.deleteById(id);
  28. }
  29. }
  • ProductController.java
  1. package com.sunxiaping.product.controller;
  2. import com.sunxiaping.product.domain.Product;
  3. import com.sunxiaping.product.service.ProductService;
  4. import org.springframework.beans.factory.annotation.Autowired;
  5. import org.springframework.web.bind.annotation.*;
  6. @RestController
  7. @RequestMapping(value = "/product")
  8. public class ProductController {
  9. @Autowired
  10. private ProductService productService;
  11. @PostMapping(value = "/save")
  12. public String save(@RequestBody Product product) {
  13. productService.save(product);
  14. return "新增成功";
  15. }
  16. @GetMapping(value = "/findById/{id}")
  17. public Product findById(@PathVariable(value = "id") Long id) {
  18. Product product = productService.findById(id);
  19. return product;
  20. }
  21. }
  • 启动类:
  1. package com.sunxiaping.product;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.autoconfigure.SpringBootApplication;
  4. @SpringBootApplication
  5. public class Product9003Application {
  6. public static void main(String[] args) {
  7. SpringApplication.run(Product9003Application.class, args);
  8. }
  9. }

4.2.3 订单微服务

  • pom.xml
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <parent>
  6. <artifactId>spring_cloud_demo</artifactId>
  7. <groupId>org.sunxiaping</groupId>
  8. <version>1.0</version>
  9. </parent>
  10. <modelVersion>4.0.0</modelVersion>
  11. <artifactId>order-service-consul9004</artifactId>
  12. <dependencies>
  13. <dependency>
  14. <groupId>org.springframework.boot</groupId>
  15. <artifactId>spring-boot-starter-data-jpa</artifactId>
  16. </dependency>
  17. <dependency>
  18. <groupId>mysql</groupId>
  19. <artifactId>mysql-connector-java</artifactId>
  20. </dependency>
  21. </dependencies>
  22. </project>
  • application.yml
  1. server:
  2. port: 9004 # 微服务的端口号
  3. spring:
  4. application:
  5. name: service-order # 微服务的名称
  6. datasource:
  7. url: jdbc:mysql://192.168.1.57:3306/test?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true
  8. driver-class-name: com.mysql.cj.jdbc.Driver
  9. username: root
  10. password: 123456
  11. jpa:
  12. generate-ddl: true
  13. show-sql: true
  14. open-in-view: true
  15. database: mysql
  16. jmx:
  17. unique-names: true
  18. # 微服务info内容详细信息
  19. info:
  20. app.name: xxx
  21. company.name: xxx
  22. build.artifactId: $project.artifactId$
  23. build.version: $project.version$
  24. # 开启日志debug
  25. logging:
  26. level:
  27. root: info
  • SpringConfig.java
  1. package com.sunxiaping.order.config;
  2. import org.springframework.context.annotation.Bean;
  3. import org.springframework.context.annotation.Configuration;
  4. import org.springframework.web.client.RestTemplate;
  5. @Configuration
  6. public class SpringConfig {
  7. @Bean
  8. public RestTemplate restTemplate() {
  9. return new RestTemplate();
  10. }
  11. }
  • Product.java
  1. package com.sunxiaping.order.domain;
  2. import lombok.AllArgsConstructor;
  3. import lombok.Getter;
  4. import lombok.NoArgsConstructor;
  5. import lombok.Setter;
  6. import javax.persistence.*;
  7. import java.io.Serializable;
  8. import java.math.BigDecimal;
  9. @Setter
  10. @Getter
  11. @AllArgsConstructor
  12. @NoArgsConstructor
  13. @Entity
  14. @Table(name = "tb_product")
  15. public class Product implements Serializable {
  16. @Id
  17. @GeneratedValue(strategy = GenerationType.IDENTITY)
  18. private Long id;
  19. @Column(name = "product_name")
  20. private String productName;
  21. @Column(name = "status")
  22. private Integer status;
  23. @Column(name = "price")
  24. private BigDecimal price;
  25. @Column(name = "product_desc")
  26. private String productDesc;
  27. @Column(name = "caption")
  28. private String caption;
  29. @Column(name = "inventory")
  30. private String inventory;
  31. }
  • OrderController.java
  1. package com.sunxiaping.order.controller;
  2. import com.sunxiaping.order.domain.Product;
  3. import org.springframework.beans.factory.annotation.Autowired;
  4. import org.springframework.web.bind.annotation.GetMapping;
  5. import org.springframework.web.bind.annotation.PathVariable;
  6. import org.springframework.web.bind.annotation.RequestMapping;
  7. import org.springframework.web.bind.annotation.RestController;
  8. import org.springframework.web.client.RestTemplate;
  9. @RestController
  10. @RequestMapping(value = "/order")
  11. public class OrderController {
  12. @Autowired
  13. private RestTemplate restTemplate;
  14. @GetMapping(value = "/buy/{id}")
  15. public Product buy(@PathVariable(value = "id") Long id) {
  16. Product product = restTemplate.getForObject("http://localhost:9003/product/findById/" + id, Product.class);
  17. return product;
  18. }
  19. }
  • 启动类:
  1. package com.sunxiaping.order;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.autoconfigure.SpringBootApplication;
  4. @SpringBootApplication
  5. public class Order9004Application {
  6. public static void main(String[] args) {
  7. SpringApplication.run(Order9004Application.class, args);
  8. }
  9. }

4.3 服务注册

4.3.1 在商品微服务中添加SpringCloud基于Consul的依赖

  • 修改部分:
  1. <!-- SpringCloud提供的基于Consul的服务发现 -->
  2. <dependency>
  3. <groupId>org.springframework.cloud</groupId>
  4. <artifactId>spring-cloud-starter-consul-discovery</artifactId>
  5. </dependency>
  • 完整部分:
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <parent>
  6. <artifactId>spring_cloud_demo</artifactId>
  7. <groupId>org.sunxiaping</groupId>
  8. <version>1.0</version>
  9. </parent>
  10. <modelVersion>4.0.0</modelVersion>
  11. <artifactId>product_serivce-consul9003</artifactId>
  12. <dependencies>
  13. <!-- 服务监控 -->
  14. <dependency>
  15. <groupId>org.springframework.boot</groupId>
  16. <artifactId>spring-boot-starter-actuator</artifactId>
  17. </dependency>
  18. <!-- SpringCloud提供的基于Consul的服务发现 -->
  19. <dependency>
  20. <groupId>org.springframework.cloud</groupId>
  21. <artifactId>spring-cloud-starter-consul-discovery</artifactId>
  22. </dependency>
  23. <dependency>
  24. <groupId>org.springframework.boot</groupId>
  25. <artifactId>spring-boot-starter-data-jpa</artifactId>
  26. </dependency>
  27. <dependency>
  28. <groupId>mysql</groupId>
  29. <artifactId>mysql-connector-java</artifactId>
  30. </dependency>
  31. </dependencies>
  32. </project>

4.3.2 在商品微服务的application.yml中配置服务注册

  • 修改部分:
  1. spring:
  2. # 开始配置Consul的服务注册
  3. cloud:
  4. consul:
  5. # ConsulServer的主机地址
  6. host: 192.168.32.100
  7. # ConsulServer端口
  8. port: 8500
  9. discovery:
  10. # 是否注册
  11. register: true
  12. # 服务实例id 必须填写 也可以写成 {spring.application.name}:${spring.cloud.client.ip-address}
  13. instance-id: ${spring.application.name}-1
  14. # 服务实例名称
  15. service-name: ${spring.application.name}
  16. # 服务实例端口
  17. port: ${server.port}
  18. # 健康检查路径
  19. health-check-path: /actuator/health
  20. # 健康检查时间间隔
  21. health-check-interval: 15s
  22. # 开启IP注册
  23. prefer-ip-address: true
  24. # 实例的请求IP
  25. ip-address: ${spring.cloud.client.ip-address}
  • 完整部分:
  1. server:
  2. port: 9003 # 微服务的端口号
  3. spring:
  4. application:
  5. name: service-product # 微服务的名称
  6. datasource:
  7. url: jdbc:mysql://192.168.1.57:3306/test?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true
  8. driver-class-name: com.mysql.cj.jdbc.Driver
  9. username: root
  10. password: 123456
  11. jpa:
  12. generate-ddl: true
  13. show-sql: true
  14. open-in-view: true
  15. database: mysql
  16. # 开始配置Consul的服务注册
  17. cloud:
  18. consul:
  19. # ConsulServer的主机地址
  20. host: 192.168.32.100
  21. # ConsulServer端口
  22. port: 8500
  23. discovery:
  24. # 是否注册
  25. register: true
  26. # 服务实例id 必须填写 也可以写成 {spring.application.name}:${spring.cloud.client.ip-address}
  27. instance-id: ${spring.application.name}-1
  28. # 服务实例名称
  29. service-name: ${spring.application.name}
  30. # 服务实例端口
  31. port: ${server.port}
  32. # 健康检查路径
  33. health-check-path: /actuator/health
  34. # 健康检查时间间隔
  35. health-check-interval: 15s
  36. # 开启IP注册
  37. prefer-ip-address: true
  38. # 实例的请求IP
  39. ip-address: ${spring.cloud.client.ip-address}
  40. # 微服务info内容详细信息
  41. info:
  42. app.name: xxx
  43. company.name: xxx
  44. build.artifactId: $project.artifactId$
  45. build.version: $project.version$

4.3.3 启动类上加上@EnableDiscoveryClient注解

  • 启动类:
  1. package com.sunxiaping.product;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.autoconfigure.SpringBootApplication;
  4. import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
  5. @SpringBootApplication
  6. @EnableDiscoveryClient
  7. public class Product9003Application {
  8. public static void main(String[] args) {
  9. SpringApplication.run(Product9003Application.class, args);
  10. }
  11. }

4.4 服务发现

4.4.1 在订单微服务中添加SpringCloud基于Consul的依赖

  • 修改部分:
  1. <!-- 服务监控 -->
  2. <dependency>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-starter-actuator</artifactId>
  5. </dependency>
  6. <!-- SpringCloud提供的基于Consul的服务发现 -->
  7. <dependency>
  8. <groupId>org.springframework.cloud</groupId>
  9. <artifactId>spring-cloud-starter-consul-discovery</artifactId>
  10. </dependency>
  • 完整部分:
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <parent>
  6. <artifactId>spring_cloud_demo</artifactId>
  7. <groupId>org.sunxiaping</groupId>
  8. <version>1.0</version>
  9. </parent>
  10. <modelVersion>4.0.0</modelVersion>
  11. <artifactId>order-service-consul9004</artifactId>
  12. <dependencies>
  13. <dependency>
  14. <groupId>org.springframework.boot</groupId>
  15. <artifactId>spring-boot-starter-data-jpa</artifactId>
  16. </dependency>
  17. <dependency>
  18. <groupId>mysql</groupId>
  19. <artifactId>mysql-connector-java</artifactId>
  20. </dependency>
  21. <!-- 服务监控 -->
  22. <dependency>
  23. <groupId>org.springframework.boot</groupId>
  24. <artifactId>spring-boot-starter-actuator</artifactId>
  25. </dependency>
  26. <!-- SpringCloud提供的基于Consul的服务发现 -->
  27. <dependency>
  28. <groupId>org.springframework.cloud</groupId>
  29. <artifactId>spring-cloud-starter-consul-discovery</artifactId>
  30. </dependency>
  31. </dependencies>
  32. </project>

4.4.2 在订单微服务的application.yml中配置服务注册

  • 修改部分:
  1. spring:
  2. # 开始配置Consul的服务注册
  3. cloud:
  4. consul:
  5. # ConsulServer的主机地址
  6. host: 192.168.32.100
  7. # ConsulServer端口
  8. port: 8500
  9. discovery:
  10. # 是否注册
  11. register: true
  12. # 服务实例id 必须填写 也可以写成 {spring.application.name}:${spring.cloud.client.ip-address}
  13. instance-id: ${spring.application.name}-1
  14. # 服务实例名称
  15. service-name: ${spring.application.name}
  16. # 服务实例端口
  17. port: ${server.port}
  18. # 健康检查路径
  19. health-check-path: /actuator/health
  20. # 健康检查时间间隔
  21. health-check-interval: 15s
  22. # 开启IP注册
  23. prefer-ip-address: true
  24. # 实例的请求IP
  25. ip-address: ${spring.cloud.client.ip-address}
  • 完整部分:
  1. server:
  2. port: 9004 # 微服务的端口号
  3. spring:
  4. application:
  5. name: service-order # 微服务的名称
  6. datasource:
  7. url: jdbc:mysql://192.168.1.57:3306/test?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true
  8. driver-class-name: com.mysql.cj.jdbc.Driver
  9. username: root
  10. password: 123456
  11. jpa:
  12. generate-ddl: true
  13. show-sql: true
  14. open-in-view: true
  15. database: mysql
  16. jmx:
  17. unique-names: true
  18. # 开始配置Consul的服务注册
  19. cloud:
  20. consul:
  21. # ConsulServer的主机地址
  22. host: 192.168.32.100
  23. # ConsulServer端口
  24. port: 8500
  25. discovery:
  26. # 是否注册
  27. register: true
  28. # 服务实例id 必须填写 也可以写成 {spring.application.name}:${spring.cloud.client.ip-address}
  29. instance-id: ${spring.application.name}-1
  30. # 服务实例名称
  31. service-name: ${spring.application.name}
  32. # 服务实例端口
  33. port: ${server.port}
  34. # 健康检查路径
  35. health-check-path: /actuator/health
  36. # 健康检查时间间隔
  37. health-check-interval: 15s
  38. # 开启IP注册
  39. prefer-ip-address: true
  40. # 实例的请求IP
  41. ip-address: ${spring.cloud.client.ip-address}
  42. # 微服务info内容详细信息
  43. info:
  44. app.name: xxx
  45. company.name: xxx
  46. build.artifactId: $project.artifactId$
  47. build.version: $project.version$
  48. # 开启日志debug
  49. logging:
  50. level:
  51. root: info

4.4.3 在商品微服务的RestTemplate上面标注@LoadBalanced注解

  • SpringConfig.java
  1. package com.sunxiaping.order.config;
  2. import org.springframework.cloud.client.loadbalancer.LoadBalanced;
  3. import org.springframework.context.annotation.Bean;
  4. import org.springframework.context.annotation.Configuration;
  5. import org.springframework.web.client.RestTemplate;
  6. @Configuration
  7. public class SpringConfig {
  8. /**
  9. * SpringCloud对Consul进行了进一步的处理,向其中集成了Ribbon的支持
  10. *
  11. * @return
  12. */
  13. @LoadBalanced
  14. @Bean
  15. public RestTemplate restTemplate() {
  16. return new RestTemplate();
  17. }
  18. }

4.4.4 修改Controller

  • OrderController.java
  1. package com.sunxiaping.order.controller;
  2. import com.sunxiaping.order.domain.Product;
  3. import org.springframework.beans.factory.annotation.Autowired;
  4. import org.springframework.web.bind.annotation.GetMapping;
  5. import org.springframework.web.bind.annotation.PathVariable;
  6. import org.springframework.web.bind.annotation.RequestMapping;
  7. import org.springframework.web.bind.annotation.RestController;
  8. import org.springframework.web.client.RestTemplate;
  9. @RestController
  10. @RequestMapping(value = "/order")
  11. public class OrderController {
  12. @Autowired
  13. private RestTemplate restTemplate;
  14. @GetMapping(value = "/buy/{id}")
  15. public Product buy(@PathVariable(value = "id") Long id) {
  16. Product product = restTemplate.getForObject("http://service-product/product/findById/" + id, Product.class);
  17. return product;
  18. }
  19. }

4.4.5 启动类上加@EnableDiscoveryClient注解

  • 启动类:
  1. package com.sunxiaping.order;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.autoconfigure.SpringBootApplication;
  4. import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
  5. @EnableDiscoveryClient
  6. @SpringBootApplication
  7. public class Order9004Application {
  8. public static void main(String[] args) {
  9. SpringApplication.run(Order9004Application.class, args);
  10. }
  11. }

5 Consul集群的搭建

5.1 Consul的架构简介

Consul架构.png

  • 图中的Server是Consul服务端高可用集群,Client是Consul的客户端。Consul客户端不保存数据,客户端将接收到的请求转发给响应的Server端。Server之间通过局域网或广域网通信实现数据一致性。每个Server或Client都是一个Consul agent。Consul集群间使用了gossip协议和raft一致性算法。
  • 上图涉及到的相关术语:
  • Eureka替换方案Consul(不推荐) - 图13agent:agent是用来启动一个consul的守护进程。
  • Eureka替换方案Consul(不推荐) - 图14client:是consul的代理,用来和consul server进行交互。一个微服务对应一个client,微服务和client部署到一台机器上。
  • Eureka替换方案Consul(不推荐) - 图15server:一个server是一个具有一组扩展功能的代理,这些功能包括参与raft选举、维护集群状态、响应RPC查询,和其他数据中心交互以及转发查询给Leader或者远程数据中心等。简而言之。server就是真正干活的consul服务。一般推荐3~5个。
  • Eureka替换方案Consul(不推荐) - 图16gossip协议:流言协议。所有的consul都会参与到gossip协议中。

Gossip协议.gif

  • Eureka替换方案Consul(不推荐) - 图18raft协议:保证server集群的数据一致。
    • Leader:处理所有客户端交互、日志复制等,一般一次只有一个Leader。
    • Follower:类似选民,完全被动。
    • Candidate(候选人):可以被选为一个新的领导人。
  • Leader全权负责所有客户端的请求,以及将数据同步到Follower中(同一时刻系统中只存在一个Leader)。
  • Follower被动响应请求RPC,从不主动发起RPC。
  • Candidate由Follower向Leader转换中间状态。

5.2 Consul集群搭建

5.2.1 概述

Consul集群搭建.png

  • 首先一个正常的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机器)上的防火墙:
  1. systemctl stop firewalld
  2. systemctl disable firewalld
  • 在每个consul节点上安装consul服务,下载安装过程和单节点一致:
  1. # 从官网下载最新版本的consul服务
  2. wget https://releases.hashicorp.com/consul/1.8.4/consul_1.8.4_linux_amd64.zip
  3. # 使用unzip命令解压
  4. unzip consul_1.8.4_linux_amd64.zip
  5. # 将解压好的consul可执行命令赋值到/usr/local/bin目录下
  6. cp consul /usr/local/bin
  7. # 测试一下
  8. consul
  • 启动每个consul server节点:
  1. 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 &
  1. 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 &
  1. 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:
  1. 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集群中。
  1. ## 加入到consul集群
  2. consul join 192.168.237.100

5.2.5 测试

  • 在任意一台服务器中输入consul members查看集群中的所有节点信息:
  1. # 查看consul集群节点信息
  2. consul members

查看consul集群节点信息.png

集群测试成功.png

5.3 微服务注册到Consul集群中

5.3.1 订单微服务注册到Consul集群中

  • application.yml
  1. server:
  2. port: 9003 # 微服务的端口号
  3. spring:
  4. application:
  5. name: service-product # 微服务的名称
  6. datasource:
  7. url: jdbc:mysql://192.168.237.100:3306/test?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true
  8. driver-class-name: com.mysql.cj.jdbc.Driver
  9. username: root
  10. password: 123456
  11. jpa:
  12. generate-ddl: true
  13. show-sql: true
  14. open-in-view: true
  15. database: mysql
  16. # 开始配置Consul的服务注册
  17. cloud:
  18. consul:
  19. # Consul Client的地址 修改的地方
  20. host: 192.168.237.1
  21. # ConsulServer端口
  22. port: 8500
  23. discovery:
  24. # 是否注册
  25. register: true
  26. # 服务实例id 必须填写 也可以写成 {spring.application.name}:${spring.cloud.client.ip-address}
  27. instance-id: ${spring.application.name}-1
  28. # 服务实例名称
  29. service-name: ${spring.application.name}
  30. # 服务实例端口
  31. port: ${server.port}
  32. # 健康检查路径
  33. health-check-path: /actuator/health
  34. # 健康检查时间间隔
  35. health-check-interval: 15s
  36. # 开启IP注册
  37. prefer-ip-address: true
  38. # 实例的请求IP
  39. ip-address: ${spring.cloud.client.ip-address}
  40. # 微服务info内容详细信息
  41. info:
  42. app.name: xxx
  43. company.name: xxx
  44. build.artifactId: $project.artifactId$
  45. build.version: $project.version$

5.3.2 商品微服务注册到Consul集群中

  • application.yml
  1. server:
  2. port: 9004 # 微服务的端口号
  3. spring:
  4. application:
  5. name: service-order # 微服务的名称
  6. datasource:
  7. url: jdbc:mysql://192.168.237.100:3306/test?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true
  8. driver-class-name: com.mysql.cj.jdbc.Driver
  9. username: root
  10. password: 123456
  11. jpa:
  12. generate-ddl: true
  13. show-sql: true
  14. open-in-view: true
  15. database: mysql
  16. jmx:
  17. unique-names: true
  18. # 开始配置Consul的服务注册
  19. cloud:
  20. consul:
  21. # Consul Client的地址 修改的地方
  22. host: 192.168.237.1
  23. # ConsulServer端口
  24. port: 8500
  25. discovery:
  26. # 是否注册
  27. register: true
  28. # 服务实例id 必须填写 也可以写成 {spring.application.name}:${spring.cloud.client.ip-address}
  29. instance-id: ${spring.application.name}-1
  30. # 服务实例名称
  31. service-name: ${spring.application.name}
  32. # 服务实例端口
  33. port: ${server.port}
  34. # 健康检查路径
  35. health-check-path: /actuator/health
  36. # 健康检查时间间隔
  37. health-check-interval: 15s
  38. # 开启IP注册
  39. prefer-ip-address: true
  40. # 实例的请求IP
  41. ip-address: ${spring.cloud.client.ip-address}
  42. # 微服务info内容详细信息
  43. info:
  44. app.name: xxx
  45. company.name: xxx
  46. build.artifactId: $project.artifactId$
  47. build.version: $project.version$
  48. # 开启日志debug
  49. logging:
  50. level:
  51. root: info

5.4 Consul的常见问题

5.4.1 节点和服务注销

  • 当服务或者节点失效,Consul不会对注册的信息进行剔除处理,仅仅进行标记而已(并且服务不可以使用)。如果担心失效节点和失效服务过多影响监控。可以通过调用HTTP API的形式进行处理。
  • 节注销任意节点和服务:
  1. # PUT请求
  2. http://192.168.32.100:8500/v1/catalog/deregister
  • 注销当前节点的服务:
  1. # PUT请求
  2. 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节点的负载均衡来实现。