背景:
Netflix Eureka 2.x 官方宣告停止开发,但其实对国内的用户影响很小,一方面国内大都使用的是Eureka 1.x系列,并且官
方也在积极维护1.x 。另一方面,Spring Cloud支持很多服务发现的软件,Eureka只是其中之一,下面是 Spring Cloud 支
持的服务发现软件以及特性对比。
1、常见的服务注册中心:
- Netflix Eureka
- Alibaba Nacos
- HashiCorp Consul
- Apache Zookeeper
- CoreOS Etcd
- CNCF CoreDNS
特性 | Eureka | Nacos | Consul | Zookeeper |
---|---|---|---|---|
CAP | AP | CP + AP | CP | CP |
健康检查 | Client Beat | TCP/HTTP/MySQL/Client Beat | TCP/HTTP/gRPC/Cmd | Keep Alive |
雪崩保护 | 有 | 有 | 无 | 无 |
自动注销实例 | 支持 | 支持 | 不支持 | 支持 |
访问协议 | HTTP | HTTP/DNS | HTTP/DNS | TCP |
监听支持z | 支持 | 支持 | 支持 | 支持 |
多数据中心 | 支持 | 支持 | 支持 | 不支持 |
跨注册中心同步 | 不支持 | 支持 | 支持 | 不支持 |
Spring Cloud集成 | 支持 | 支持 | 支持 | 支持 |
2、Consul介绍:
Consul是HashoCorp公司推出的开源工具,用于实现分布式系统的服务发现与配置,与其它分布式服务注册于发现的方案
相比,Consul的方案更"一站式",内置了服务注册于发现框架、分布式一致性协议框架、健康检查、key/value存储、多
数据中心方案,不再需要依赖其它工具(比如Zookeeper等),使用起来也较为简单。
Consul使用Go语言编写,因此具有天然的可移植性(支持Linux、Windows和Mac OS),安装包仅包含一个可执行文件,方
便部署,与Docker等轻量级容器可以无缝配合。
3、Consul特性:
- Raft算法;
- 服务注册与发现;
- 健康检查;
- Key/Value存储;
- 多数据中心;
- 支持http和dns协议;
- 官方提供web管理界面;
4、Consul角色:
-
Client:客户端,无状态,将http和dns接口请求转发给局域网内的服务端集群。
-
Server:服务端,保存配置信息,高可用集群,每个数据中心的server数量推荐为3个到5个。
5、Consul工作原理:
5.1、服务发现与注册:
当服务Producer启动时,会将自己的ip/host等信息通过发送post请求告知Consul,Consul接收到Producer的注册信
息后,每隔10s(默认)会向Producer发送一个健康检查的请求,检查Producer是否健康。
5.2、服务调用:
当Consumer请求Producer时,会先从Consul中拿到存储Producer服务的ip和port的临时表(temp table),从temp
table表中任选一个Producer的ip和port,然后根据这个ip和port发送访问请求。temp table表只包含通过了健康检查
的Producer信息,并且每隔10s(默认)更新。
6、Consul安装:
Eureka其实就是个Servlet程序,运行在Servlet容器中;Consul则是用go语言编写的第三方工具需要单独安装使用。
6.1、下载:
wget https://releases.hashicorp.com/consul/1.9.4/consul_1.9.4_linux_amd64.zip
6.2、解压安装包:
unzip consul_1.9.4_linux_amd64.zip
6.3、单节点启动:
./consul agent -dev -client=0.0.0.0 # -dev表示开发模式运行,另外还有-server表示服务模式运行
- -dev:表示以开发模式运行Consul。
- -client:表示可以进行服务注册与拉取的客户端ip,设置成-client=0.0.0.0表示对ip不设限。
6.4、访问consul管理后台:
7、Consul入门案例:
7.1、创建项目:
我们创建聚合项目来讲解Consul,首先创建一个pom父工程:
7.2、查看Spring Cloud支持的Spring Boot版本:
7.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">
<modelVersion>4.0.0</modelVersion>
<!-- 项目坐标位置 -->
<groupId>org.zjg</groupId>
<!-- 项目模块名称 -->
<artifactId>consul-demo</artifactId>
<!-- 项目版本名称 快照版本SNAPSHOT、正式版本RELEASE -->
<version>1.0-SNAPSHOT</version>
<!-- 继承 spring-boot-starter-parent 依赖 -->
<!-- 使用继承方式,实现复用,符合继承的都可以被使用 -->
<parent>
<artifactId>spring-boot-starter-parent</artifactId>
<groupId>org.springframework.boot</groupId>
<version>2.4.3</version>
</parent>
<!--
集中定义以来组件版本号,但不引入
在子工程中用到声明的依赖时,可以不加依赖的版本号,
这样可以统一管理工程中用到的依赖版本
-->
<properties>
<spring-cloud.version>2020.0.2</spring-cloud.version>
</properties>
<!-- 项目依赖管理 父项目只是声明依赖 -->
<dependencyManagement>
<dependencies>
<!-- spring cloud 依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
7.4、服务提供者service-provider:
7.4.1、创建项目:
7.4.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">
<modelVersion>4.0.0</modelVersion>
<artifactId>service-provider</artifactId>
<!-- 集成父依赖 -->
<parent>
<artifactId>consul-demo</artifactId>
<groupId>org.zjg</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<!-- 项目依赖 -->
<dependencies>
<!-- spring cloud consul 依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<!-- spring boot actuator 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- spring boot web 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- lombok 依赖 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- spring boot test 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</project>
7.4.3、配置文件——application.yml:
server:
port: 7070 # 服务请求端口
spring:
application:
name: service-provider # 应用名称
# 配置consul注册中心
cloud:
consul:
host: 192.168.1.93 # 注册中心的访问地址
port: 8500 # 注册中心的访问端口
# 服务提供者信息(将自身注册到服务注册中心)
discovery:
register: true # 是否需要注册
instance-id: ${spring.application.name}-01 # 注册实例 id(必须唯一)
service-name: ${spring.application.name} # 服务名称
port: ${server.port} # 服务端口
prefer-ip-address: true # 是否使用ip地址注册
ip-address: ${spring.cloud.client.ip-address} # 服务请求ip
healthCheckInterval: 10s # 健康检查的间隔时间,默认10s
health-check-url: http://${spring.cloud.client.ip-
address}:${server.port}/actuator/health
7.4.4、实体类——Product.java:
package com.zjg.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Product {
private Integer id;
private String productName;
private Integer productNum;
private Double productPrice;
}
7.4.5、编写服务——ProductService.java:
package com.zjg.service;
import com.zjg.pojo.Product;
import org.springframework.stereotype.Service;
import java.util.Arrays;
import java.util.List;
/**
* 商品服务
*/
@Service
public class ProductService {
/**
* 查询商品列表
*/
public List<Product> selectProductAll(){
return Arrays.asList(
new Product(1,"华为手机",1,5800D),
new Product(2,",联想笔记本",1,6888D),
new Product(3,"小米平板",5,2020D)
);
}
}
7.4.6、控制层——ProductController.java:
package com.zjg.controller;
import com.zjg.pojo.Product;
import com.zjg.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping(value = "/product")
public class ProductController {
@Autowired
private ProductService productService;
/**
* 查询商品列表
*/
@GetMapping(value = "/selectProductAll")
public List<Product> selectProductAll(){
return productService.selectProductAll();
}
}
7.4.7、启动类——ServiceProviderApplication.java:
package com.zjg;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ServiceProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceProviderApplication.class, args);
}
}
7.4.8、访问:
7.5、服务消费者service-consumer:
7.5.1、创建项目:
7.5.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">
<modelVersion>4.0.0</modelVersion>
<artifactId>service-consumer</artifactId>
<!-- 集成父依赖 -->
<parent>
<artifactId>consul-demo</artifactId>
<groupId>org.zjg</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<!-- 项目依赖 -->
<dependencies>
<!-- spring cloud consul 依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<!-- spring boot actuator 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- spring boot web 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- lombok 依赖 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- spring boot test 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</project>
7.5.3、配置文件——application.yml:
server:
port: 7080 # 服务请求端口
spring:
application:
name: service-consumer # 应用名称
# 配置consul注册中心
cloud:
consul:
host: 192.168.1.93 # 注册中心的访问地址
port: 8500 # 注册中心的访问端口
# 服务提供者信息(将自身注册到服务注册中心)
discovery:
register: false # 是否需要注册
7.5.4、实体类——Order.java:
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Order {
private Integer id;
private String orderNo;
private String orderAddress;
private Double totalPrice;
private List<Product> productList;
}
7.5.5、编写服务——OrderService.java:
package com.zjg.service;
import com.zjg.pojo.Order;
import com.zjg.pojo.Product;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.util.List;
/**
* 订单服务
*/
@Service
public class OrderService {
@Autowired
private RestTemplate restTemplate;
/**
* 根据主键查询订单
*/
public Order selectOrderById(Integer id){
return new Order(id,"order-001","中国",22788D,
selectProductByLoadBalancerAnnotation());
}
/**
* 通过RestTemplate调用Consul服务注册中心的service-provider服务
*/
private List<Product> selectProductByLoadBalancerAnnotation(){
ResponseEntity<List<Product>> response = restTemplate.exchange(
"http://service-provider/product/selectProductAll",
HttpMethod.GET,
null,
new ParameterizedTypeReference<List<Product>>() {
});
return response.getBody();
}
}
7.5.6、控制层——ProductController.java:
package com.zjg.controller;
import com.zjg.pojo.Order;
import com.zjg.service.OrderService;
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;
@RestController
@RequestMapping(value = "/order")
public class OrderController {
@Autowired
private OrderService orderService;
/**
* 根据主键查询订单
*/
@GetMapping(value = "/{id}")
public Order selectOrderById(@PathVariable(value = "id") Integer id){
return orderService.selectOrderById(id);
}
}
7.5.7、启动类——ServiceConsumerApplication.java:
package com.zjg;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ServiceConsumerApplication {
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(ServiceConsumerApplication.class, args);
}
}
Spring
并不会自动注入RestTemplate
,如果要使用RestTemplate
需要通过@Bean
注解手动注入。
7.5.8、访问:
8、Consul集群:
上图是一个简单的Consul Cluster架构,Consul Cluster有Server和Client两种角色,无论是Server还是Client,统称为
Agent,Consul Client是相对无状态的,只负责转发RPC到Consul Server,所以Client资源开销很少。Consul
Server是一个有一组扩展功能的代理,这些功能包括参与Raft选举、维护集群状态、响应RPC查询,与其他数据中心交互
WAN Gossip、转发查询给Leader或者远程数据中心。
每个数据中心,Client和Server是混合的。一般建议有3~5台Server。这事基于有故障情况下的可用性和性能之间的权衡结
果,因为越多的机器加入达成共识越慢,Server之间会选举出一个Leader。然而并不限制Client的数量,一般建议一个微
服务对应一个Client,他们可以很容易的扩展到成千上万台,在开发时我们绑定一组服务注册中心中的客户端即可。
8.1、环境准备:
服务器IP | Consul类型 | Node节点 |
---|---|---|
192.168.1.94 | server | consul-server-01 |
192.168.1.95 | server | consul-server-02 |
192.168.1.96 | server | consul-server-03 |
192.168.1.97 | client | consul-client-01 |
8.2、将下载的安装包上传到各服务器:
scp consul root@192.168.1.94:/usr/local/dev/consul/
scp consul root@192.168.1.95:/usr/local/dev/consul/
scp consul root@192.168.1.96:/usr/local/dev/consul/
scp consul root@192.168.1.97:/usr/local/dev/consul/
8.3、解压安装包文件:
yum install -y unzip
unzip consul_1.9.4_linux_amd64.zip -d /usr/local/dev/consul/ # 解压到consul目录
8.4、创建Consul工作目录:
mkdir -p /usr/local/dev/consul/data
8.5、启动Consul Server端:
# consul-server-01
./consul agent -server -bind=192.168.1.94 -client=0.0.0.0 -ui -bootstrap-expect=3 -data-dir=/usr/local/dev/consul/data/ -node=consul-server-01
# consul-server-02
./consul agent -server -bind=192.168.1.95 -client=0.0.0.0 -ui -bootstrap-expect=3 -data-dir=/usr/local/dev/consul/data/ -node=consul-server-02
# consul-server-03
./consul agent -server -bind=192.168.1.96 -client=0.0.0.0 -ui -bootstrap-expect=3 -data-dir=/usr/local/dev/consul/data/ -node=consul-server-03
参数含义如下:
- -server:表示以服务模式启动。
- -bind:表示绑定到哪个ip。
- -client:表示指定客户端的ip,0.0.0.0表示不限客户端ip,放行所有客户端ip访问。
- -ui:表示开启web界面访问。
- -bootstrap-expect=3:表示server集群最低节点数为3,低于这个值集群将不能正常工作()。
- -data-dir:表示指定数据的存放目录(该目录必须存在,需提前创建好)。
- -node:表示节点名称。
8.6、启动consul Client端:
./consul agent -client=0.0.0.0 -bind=192.168.1.97 -data-dir=/usr/local/dev/consul/data/ -node=consul-client-01
8.7、关联集群:
# 在consul-server-02、consul-server-03、consul-client-01节点下分别执行以下命令
./consul join 192.168.1.94
8.8、查看集群状态:
./consul members