在上个月我们知道 Eureka 2.0 闭源了,但其实对国内的用户影响甚小,一方面国内大都使用的是 Eureka 1.X 系列,另一方面 Spring Cloud 支持很多服务发现的软件,Eureka 只是其中之一,下面是 Spring Cloud 支持的服务发现软件以及特性对比:
Feature | euerka | Consul | zookeeper | etcd |
---|---|---|---|---|
服务健康检查 | 可配支持 | 服务状态,内存,硬盘等 | (弱)长连接,keepalive | 连接心跳 |
多数据中心 | — | 支持 | — | — |
kv 存储服务 | — | 支持 | 支持 | 支持 |
一致性 | — | raft | paxos | raft |
cap | ap | ca | cp | cp |
使用接口(多语言能力) | http(sidecar) | 支持 http 和 dns | 客户端 | http/grpc |
watch 支持 | 支持 long polling/大部分增量 | 全量/支持long polling | 支持 | 支持 long polling |
自身监控 | metrics | metrics | — | metrics |
安全 | — | acl /https | acl | https 支持(弱) |
spring cloud 集成 | 已支持 | 已支持 | 已支持 | 已支持 |
在以上服务发现的软件中,Euerka 和 Consul 使用最为广泛。如果大家对注册中心的概念和 Euerka 不太了解的话, 可以参考我前期的文章: springcloud(二):注册中心Eureka ,本篇文章主要给大家介绍 Spring Cloud Consul 的使用。
Consul 是 HashiCorp 公司推出的开源工具,用于实现分布式系统的服务发现与配置。与其他分布式服务注册与发现的方案,Consul 的方案更“一站式”,内置了服务注册与发现框 架、分布一致性协议实现、健康检查、Key/Value 存储、多数据中心方案,不再需要依赖其他工具(比如 ZooKeeper 等)。使用起来也较 为简单。Consul 使用 Go 语言编写,因此具有天然可移植性(支持Linux、windows和Mac OS X);安装包仅包含一个可执行文件,方便部署,与 Docker 等轻量级容器可无缝配合。
Consul 客户端、服务端还支持夸中心的使用,更加提高了它的高可用性。
Spring Cloud Consul 项目是针对 Consul 的服务治理实现。Consul 是一个分布式高可用的系统,它包含多个组件,但是作为一个整体,在微服务架构中为我们的基础设施提供服务发现和服务配置的工具。
Eureka 是一个服务发现工具。该体系结构主要是客户端/服务器,每个数据中心有一组 Eureka 服务器,通常每个可用区域一个。通常 Eureka 的客户使用嵌入式 SDK 来注册和发现服务。对于非本地集成的客户,使用功能区边框等透过 Eureka 透明地发现服务。
Eureka 提供了一个弱一致的服务视图,使用尽力而为复制。当客户端向服务器注册时,该服务器将尝试复制到其他服务器,但不提供保证。服务注册的生存时间(TTL)较短,要求客户端对服务器心存感激。不健康的服务或节点将停止心跳,导致它们超时并从注册表中删除。发现请求可以路由到任何服务,由于尽力而为的复制,这些服务可能会导致陈旧或丢失数据。这个简化的模型允许简单的群集管理和高可扩展性。
领事提供了一套超级功能,包括更丰富的健康检查,关键/价值存储以及多数据中心意识。Consul 需要每个数据中心都有一套服务器,以及每个客户端的代理,类似于使用像 Ribbon 这样的边车。Consul 代理允许大多数应用程序成为 Consul 不知情者,通过配置文件执行服务注册并通过 DNS 或负载平衡器 sidecars 发现。
Consul 提供强大的一致性保证,因为服务器使用Raft协议复制状态 。Consul 支持丰富的健康检查,包括 TCP,HTTP,Nagios / Sensu 兼容脚本或基于 Eureka 的 TTL。客户端节点参与基于八卦的健康检查,该检查分发健康检查工作,而不像集中式心跳检测那样成为可扩展性挑战。发现请求被路由到选举出来的领事领导,这使他们默认情况下强烈一致。允许陈旧读取的客户端使任何服务器都可以处理他们的请求,从而实现像Eureka这样的线性可伸缩性。
Consul 强烈的一致性意味着它可以作为领导选举和集群协调的锁定服务。Eureka 不提供类似的保证,并且通常需要为需要执行协调或具有更强一致性需求的服务运行 ZooKeeper。
Consul 提供了支持面向服务的体系结构所需的一系列功能。这包括服务发现,还包括丰富的运行状况检查,锁定,密钥/值,多数据中心联合,事件系统和 ACL。Consul 和 consul-template 和 envconsul 等工具生态系统都试图尽量减少集成所需的应用程序更改,以避免需要通过SDK进行本地集成。Eureka是一个更大的 Netflix OSS 套件的一部分,该套件预计应用程序相对均匀且紧密集成。因此,Eureka只解决了一小部分问题,希望ZooKeeper等其他工具可以一起使用。 在 CAP 中,Consul 使用 CP 体系结构,有利于实现可用性的一致性。
Consul 强一致性(C)带来的是:
服务注册相比 Eureka 会稍慢一些。因为 Consul 的 raft 协议要求必须过半数的节点都写入成功才认为注册成功 Leader 挂掉时,重新选举期间整个 Consul 不可用。保证了强一致性但牺牲了可用性。
Eureka 保证高可用(A)和最终一致性:
服务注册相对要快,因为不需要等注册信息 replicate 到其他节点,也不保证注册信息是否 replicate 成功 当数据出现不一致时,虽然A, B上的注册信息不完全相同,但每个Eureka节点依然能够正常对外提供服务,这会出现查询服务信息时如果请求A查不到,但请求B就能查到。如此保证了可用性但牺牲了一致性。
其他方面,eureka 就是个 servlet 程序,跑在 servlet 容器中; Consul 则是 go 编写而成。
Consul 不同于 Eureka 需要单独安装,访问 Consul 官网 下载 Consul 的最新版本,我这里是 consul_1.2.1。
根据不同的系统类型选择不同的安装包,从下图也可以看出 Consul 支持所有主流系统。
我这里以 Windows 为例,下载下来是一个 consul_1.2.1_windows_amd64.zip 的压缩包,解压是是一个 consul.exe 的执行文件。
cd 到对应的目录下,使用 cmd 启动 Consul
cd D:/Common Files/consul #cmd启动: consul agent -dev # -dev表示开发模式运行,另外还有-server表示服务模式运行
为了方便期间,可以在同级目录下创建一个 run.bat 脚本来启动,脚本内容如下:
consul agent -dev pause
启动结果如下:
启动成功之后访问: http://localhost:8500
,可以看到 Consul 的管理界面
这样就意味着我们的 Consul 服务启动成功了。
接下来我们开发 Consul 的服务端,我们创建一个 spring-cloud-consul-producer 项目
依赖包如下:
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-consul-discovery</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
spring-boot-starter-actuator spring-cloud-starter-consul-discovery
Spring Boot 版本使用的是 2.0.3.RELEASE,Spring Cloud 最新版本是 Finchley.RELEASE 依赖于 Spring Boot 2.x.
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.3.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <dependencyManagement> <dependencies> <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>
完整的 pom.xml 文件大家可以参考示例源码。
配置文件内容如下
spring.application.name=spring-cloud-consul-producer server.port=8501 spring.cloud.consul.host=localhost spring.cloud.consul.port=8500 #注册到consul的服务名称 spring.cloud.consul.discovery.serviceName=service-producer
Consul 的地址和端口号默认是 localhost:8500 ,如果不是这个地址可以自行配置。 spring.cloud.consul.discovery.serviceName
是指注册到 Consul 的服务名称,后期客户端会根据这个名称来进行服务调用。
@SpringBootApplication @EnableDiscoveryClient public class ConsulProducerApplication { public static void main(String[] args) { SpringApplication.run(ConsulProducerApplication.class, args); } }
添加了 @EnableDiscoveryClient
注解表示支持服务发现。
我们在创建一个 Controller,推文提供 hello 的服务。
@RestController public class HelloController { @RequestMapping("/hello") public String hello() { return "helle consul"; } }
为了模拟注册均衡负载复制一份上面的项目重命名为 spring-cloud-consul-producer-2 ,修改对应的端口为 8502,修改 hello 方法的返回值为:”helle consul two”,修改完成后依次启动两个项目。
这时候我们再次在浏览器访问地址:http://localhost:8500,显示如下:
我们发现页面多了 service-producer 服务,点击进去后页面显示有两个服务提供者:
这样服务提供者就准备好了。
我们创建一个 spring-cloud-consul-consumer 项目,pom 文件和上面示例保持一致。
配置文件内容如下
spring.application.name=spring-cloud-consul-consumer server.port=8503 spring.cloud.consul.host=127.0.0.1 spring.cloud.consul.port=8500 #设置不需要注册到 consul 中 spring.cloud.consul.discovery.register=false
客户端可以设置注册到 Consul 中,也可以不注册到 Consul 注册中心中,根据我们的业务来选择,只需要在使用服务时通过 Consul 对外提供的接口获取服务信息即可。
@SpringBootApplication public class ConsulConsumerApplication { public static void main(String[] args) { SpringApplication.run(ConsulConsumerApplication.class, args); } }
我们先来创建一个 ServiceController ,试试如果去获取 Consul 中的服务。
@RestController public class ServiceController { @Autowired private LoadBalancerClient loadBalancer; @Autowired private DiscoveryClient discoveryClient; /** * 获取所有服务 */ @RequestMapping("/services") public Object services() { return discoveryClient.getInstances("service-producer"); } /** * 从所有服务中选择一个服务(轮询) */ @RequestMapping("/discover") public Object discover() { return loadBalancer.choose("service-producer").getUri().toString(); } }
Controller 中有俩个方法,一个是获取所有服务名为 service-producer
的服务信息并返回到页面,一个是随机从服务名为 service-producer
的服务中获取一个并返回到页面。
添加完 ServiceController 之后我们启动项目,访问地址: http://localhost:8503/services
,返回:
[{"serviceId":"service-producer","host":"windows10.microdone.cn","port":8501,"secure":false,"metadata":{"secure":"false"},"uri":"http://windows10.microdone.cn:8501","scheme":null},{"serviceId":"service-producer","host":"windows10.microdone.cn","port":8502,"secure":false,"metadata":{"secure":"false"},"uri":"http://windows10.microdone.cn:8502","scheme":null}]
发现我们刚才创建的端口为 8501 和 8502 的两个服务端都存在。
多次访问地址: http://localhost:8503/discover
,页面会交替返回下面信息:
http://windows10.microdone.cn:8501 http://windows10.microdone.cn:8502 ...
说明 8501 和 8501 的两个服务会交替出现,从而实现了获取服务端地址的均衡负载。
大多数情况下我们希望使用均衡负载的形式去获取服务端提供的服务,因此使用第二种方法来模拟调用服务端提供的 hello 方法。
创建 CallHelloController :
@RestController public class CallHelloController { @Autowired private LoadBalancerClient loadBalancer; @RequestMapping("/call") public String call() { ServiceInstance serviceInstance = loadBalancer.choose("service-producer"); System.out.println("服务地址:" + serviceInstance.getUri()); System.out.println("服务名称:" + serviceInstance.getServiceId()); String callServiceResult = new RestTemplate().getForObject(serviceInstance.getUri().toString() + "/hello", String.class); System.out.println(callServiceResult); return callServiceResult; } }
使用 RestTemplate 进行远程调用。添加完之后重启 spring-cloud-consul-consumer 项目。在浏览器中访问地址: http://localhost:8503/call
,依次返回结果如下:
helle consul helle consul two ...
说明我们已经成功的调用了 Consul 服务端提供的服务,并且实现了服务端的均衡负载功能。通过今天的实践我们发现 Consul 提供的服务发现易用、强大。
示例代码-github
示例代码-码云