Netflix Eureka 2.x官方宣告停止开发,但其对国内用户影响很小,一方面国内大都使用的是Eureka 1.x系列,并且官方也在积极维护1.x 。
另一方面,Spring Cloud支持很多服务发现的软件,Eureka只是其中之一,下面是Spring Cloud支持的服务发现软件以及特性对比。
特性 | 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集成 | 支持 | 支持 | 支持 | 支持 |
Consul是HashoCorp公司推出的开源工具,用于实现分布式系统的服务发现与配置,与其它分布式服务注册于发现的方案
相比,Consul的方案更"一站式",内置了服务注册于发现框架、分布式一致性协议框架、健康检查、key/value存储、多
数据中心方案,不再需要依赖其它工具(比如Zookeeper等),使用起来也较为简单。
Consul使用Go语言编写,因此具有天然的可移植性(支持Linux、Windows和Mac OS),安装包仅包含一个可执行文件,方
便部署,与Docker等轻量级容器可以无缝配合。
Client:客户端,无状态,将http和dns接口请求转发给局域网内的服务端集群。
Server:服务端,保存配置信息,高可用集群,每个数据中心的server数量推荐为3个到5个。
首先,上图中有两个数据中心,分别是Datacenter1和Datacenter2。Consul非常好的支持多个数据中心,每个数据中
心内,有客户端和服务端,服务器一般为3~5个,这样可以在稳定和性能上达到平衡,因为更多的机器会使数据同步
变慢,不过客户端是没有限制的,可以有成千上万个。
数据中心内的所有节点都会加入到Gossip(流言)协议,这就意味着有一个Gossip池,其中包含这个数据中心所有的节
点。客户端不需要去配置服务器地址信息,发现服务工作会自动完成。故障检测节点的工作不是放在服务器端,而是
分布式的,这使得失败检测相对于本地化的心跳机制而言更具可扩展性。在选择Leader这种重要的事情发生的时候,
数据中心被用作消息层来做消息广播。
每个数据中心内的服务器都是单个Raft中节点集的一部分,这意味着他们一起工作,选择一个单一的领导者—一个具
有额外职责的选定的服务器。Leader负责处理所有查询和事物,事物也必须作为同步协议的一部分复制到节点集中的
所有节点。由于这个要求,当非Leader服务器接收到RPC请求时,就会将请求转发给集群Leader。
服务端节点同时也作为WAN Gossip池的一部分,WAN池和LAN池不同的是,它针对网络高延迟做了优化,而且只包
含其他Consul服务器的节点。这个池的目的是允许数据中心以最少的消耗方式发现对方。启动新的数据中心与加入现
有的WAN Gossip一样简单。因为这些服务器都在这个池中运行,它还支持跨数据中心请求。当服务器收到不同数据
中心的请求时,它会将其转发到正确数据中心中的随机服务器,那个服务器可能会转发给本地的Leader。
服务发现与注册:
当服务Producer启动时,会将自己的ip/host等信息通过发送post请求告知Consul,Consul接收到Producer的注册信
息后,每隔10s(默认)会向Producer发送一个健康检查的请求,检查Producer是否健康。
服务调用:
当Consumer请求Producer时,会先从Consul中拿到存储Producer服务的ip和port的临时表(temp table),从temp
table表中任选一个Producer的ip和port,然后根据这个ip和port发送访问请求。temp table表只包含通过了健康检查
的Producer信息,并且每隔10s(默认)更新。
Eureka其实就是个Servlet程序,运行在Servlet容器中;Consul则是用go语言编写的第三方工具需要单独安装使用。
下载:
wget https://releases.hashicorp.com/consul/1.9.4/consul_1.9.4_linux_amd64.zip
解压安装包:
unzip consul_1.9.4_linux_amd64.zip
单节点启动:
./consul agent -dev -client=0.0.0.0 # -dev表示开发模式运行,另外还有-server表示服务模式运行
访问consul管理后台:
创建项目:
我们创建聚合项目来讲解Consul,首先创建一个pom父工程:
项目创建成功后,删除src目录。
查看Spring Cloud支持的Spring Boot版本:
添加依赖——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>
服务提供者service-provider:
[ ] 创建项目:
[ ] 添加依赖——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>
[ ] 配置文件——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
[ ] 实体类——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;
}
[ ] 编写服务——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)
);
}
}
[ ] 控制层——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();
}
}
[ ] 启动类——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);
}
}
[ ] 访问:
服务消费者service-consumer:
[ ] 创建项目:
[ ] 添加依赖——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>
[ ] 配置文件——application.yml:
server:
port: 7080 # 服务请求端口
spring:
application:
name: service-consumer # 应用名称
# 配置consul注册中心
cloud:
consul:
host: 192.168.1.93 # 注册中心的访问地址
port: 8500 # 注册中心的访问端口
# 服务提供者信息(将自身注册到服务注册中心)
discovery:
register: false # 是否需要注册
[ ] 实体类——Order.java:
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Order {
private Integer id;
private String orderNo;
private String orderAddress;
private Double totalPrice;
private List<Product> productList;
}
[ ] 编写服务——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();
}
}
[ ] 控制层——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);
}
}
[ ] 启动类——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
注解手动注入。
[ ] 访问:
上图是一个简单的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,他们可以很容易的扩展到成千上万台,在开发时我们绑定一组服务注册中心中的客户端即可。
环境准备:
服务器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 |
将下载的安装包上传到各服务器:
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/
解压安装包文件:
yum install -y unzip
unzip consul_1.9.4_linux_amd64.zip -d /usr/local/dev/consul/ # 解压到consul目录
创建Consul工作目录:
mkdir -p /usr/local/dev/consul/data
启动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
参数含义如下:
启动consul Client端:
./conusl agent -client=0.0.0.0 -bind=192.168.1.97 -data-dir=/usr/local/dev/consul/data/ -node=consul-client-01
关联集群,在consul-server-02、consul-server-03、consul-client-01节点下分别执行以下命令:
./conusl join 192.168.1.94
查看集群状态:
./consul members
访问集群中的任意节点,如果看到如下界面说明集群搭建成功:
原文:https://www.cnblogs.com/xiaoshuzhagen/p/14673811.html