1. 简介
本文将介绍如何使用Zookeeper
在微服务框架中实现服务发现,该服务发现机制可作为云服务的注册中心。通过Spring Cloud Zookeeper
为应用程序提供一种Spring Boot集成,将Zookeeper通过自动配置和绑定 的方式集成到Spring环境中。
在本例子中我们将创建两个应用程序:
- 提供服务的应用程序(称为
服务提供者
) - 使用此服务的应用程序(称为
服务消费者
)
2. 安装Zookeeper
2.1 下载
Apache官方最新版本为:3.4.8
下载地址:http://mirrors.cnnic.cn/apache/zookeeper/zookeeper-3.4.8/zookeeper-3.4.8.tar.gz
2.2 安装
解压到指定目录下 D:\soft\zookeeper-3.4.8
修改zoo_sample.cfg 文件名(D:\soft\zookeeper-3.4.8\conf) 为 zoo.cfg
主要修改一下日志位置,具体配置文件如下:
# The number of milliseconds of each tick
tickTime=2000
# The number of ticks that the initial
# synchronization phase can take
initLimit=10
# The number of ticks that can pass between
# sending a request and getting an acknowledgement
syncLimit=5
# the directory where the snapshot is stored.
# do not use /tmp for storage, /tmp here is just
# example sakes.
dataDir=D:\\zookeeper\\data
dataLogDir=D:\\zookeeper\\log
# the port at which the clients will connect
clientPort=2181
# the maximum number of client connections.
# increase this if you need to handle more clients
#maxClientCnxns=60
#
# Be sure to read the maintenance section of the
# administrator guide before turning on autopurge.
#
# http://zookeeper.apache.org/doc/current/zookeeperAdmin.html#sc_maintenance
#
# The number of snapshots to retain in dataDir
#autopurge.snapRetainCount=3
# Purge task interval in hours
# Set to "0" to disable auto purge feature
#autopurge.purgeInterval=1
tickTime
:这个时间是作为 Zookeeper 服务器之间或客户端与服务器之间维持心跳的时间间隔,也就是每个 tickTime 时间就会发送一个心跳。dataDir
:顾名思义就是 Zookeeper 保存数据的目录,默认情况下,Zookeeper 将写数据的日志文件也保存在这个目录里。dataLogDir
:顾名思义就是 Zookeeper 保存日志文件的目录clientPort
:这个端口就是客户端连接 Zookeeper 服务器的端口,Zookeeper 会监听这个端口,接受客户端的访问请求。
2.3 启动
进入到bin目录,并且启动zkServer.cmd,这个脚本中会启动一个java进程,双击该文件即可启动:
3. 搭建服务提供者
我们将创建一个服务提供者,通过增加pring-cloud-starter-zookeeper-discovery依赖和在主程序中引入@EnableDiscoveryClient注释。服务的具体内容是通过GET请求返回“Hello World!”字符串。
整个项目结构如下:
3.1 导入依赖
<?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>com.yiidian</groupId> <artifactId>spring-cloud-zookeeper-proivder</artifactId> <version>1.0-SNAPSHOT</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.1.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <!-- SpringBoot 对Web支持 SpringMVC相关功能,json转换的功能等等 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId> </dependency> </dependencies> <!-- 定义SpringCloud版本 --> <repositories> <repository> <id>spring-snapshots</id> <name>Spring Snapshots</name> <url>https://repo.spring.io/snapshot</url> <snapshots> <enabled>true</enabled> </snapshots> </repository> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Finchley.M9</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> </project>
3.2 启动类
使用@EnableDiscoveryClient注释我们的主类,这将使HelloWorld 应用程序自动发布。
package com.yiidian.provider; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; /** * 服务提供者启动类 */ @SpringBootApplication @EnableDiscoveryClient public class ProviderStarter { public static void main(String[] args) { SpringApplication.run(ProviderStarter.class,args); } }
3.3 Controller
package com.yiidian.provider.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; /** * 一点教程网: http://www.yiidian.com * 控制器 */ @RestController public class HelloController { @GetMapping("/hello") public String hello(){ return "Hello World"; } }
3.4 配置application.yml
在application.yml中定义服务名称用来被客户端调用,同时配置Zookeeper信息用来注册服务。
server: port: 9001 spring: application: name: Provider cloud: zookeeper: discovery: enabled: true connect-string: localhost:2181 logging: level: org.apache.zookeeper.ClientCnxn: WARN
4. 搭建服务消费者
现在我们来创建一个REST服务消费者,它使用spring Netflix Feign Client来调用服务。先看看整个项目结构:
4.1 导入依赖
在pom文件里增加需要依赖的一些组件包。
<?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>com.yiidian</groupId> <artifactId>spring-cloud-zookeeper-consumer</artifactId> <version>1.0-SNAPSHOT</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.1.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <!-- SpringBoot 对Web支持 SpringMVC相关功能,json转换的功能等等 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> </dependencies> <!-- 定义SpringCloud版本 --> <repositories> <repository> <id>spring-snapshots</id> <name>Spring Snapshots</name> <url>https://repo.spring.io/snapshot</url> <snapshots> <enabled>true</enabled> </snapshots> </repository> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Finchley.M9</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> </project>
4.2 启动类
同服务提供者一样在主程序中增加@EnableDiscoveryClient 注解。
package com.yiidian.consumer; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.openfeign.EnableFeignClients; /** *服务消费者启动类 */ @SpringBootApplication @EnableDiscoveryClient @EnableFeignClients public class ConsumerStarter { public static void main(String[] args) { SpringApplication.run(ConsumerStarter.class,args); } }
4.3 定义Feign远程接口
通过引入spring-cloud-starter-openfeign
组件包使用声明式服务调用方式调用远程服务,使用@FeignClient(“service-name”
)注解一个接口并将它自动连接到我们的应用程序中,以便我们以编程方式访问此服务。
package com.yiidian.consumer.client; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; /** * Feign远程接口 */ @FeignClient("Provider") public interface HelloClient { @GetMapping("/hello") public String hello(); }
4.4 Controller
以下是一个简单的服务控制器类,它通过注入接口helloWorldClient对象调用服务提供者的接口来消费该服务,并在响应中显示它的返回值。
package com.yiidian.consumer.controller; import com.yiidian.consumer.client.HelloClient; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; /** * 消费者控制器 */ @RestController public class GreetingController { @Autowired private HelloClient helloClient; @GetMapping("/get-greeting") public String greeting() { return helloClient.hello(); } }
4.5 配置application.yml
server: port: 9002 spring: application: name: Consumer cloud: zookeeper: discovery: enabled: true connect-string: localhost:2181 logging: level: org.apache.zookeeper.ClientCnxn: WARN
5. 测试
HelloWorld REST服务在Zookeeper中注册了自己,Greeting服务通过声明式客户端发现和调用HelloWorld 服务。
现在我们可以运行这两个服务,然后在浏览器中访问 http://localhost:9002/get-greeting
,将返回以下信息:
6. 总结
在本文中我们看到了如何使用Spring Cloud Zookeeper
实现服务发现,并且在Zookeeper中注册了一个名为Hello World的服务。然后通过声明式服务调用方式实现了一个服务消费者Greeting来发现和使用该服务。
顺便介绍下Zookeeper
与Eureka
这两种服务治理框架的区别。Spring Cloud Eureka
实现的服务治理机制强调了CAP原理中的AP,即可用性
与可靠性
,而Zookeeper
强调CP(一致性
、可靠性
)。Eureka为了实现更高的服务可用性,牺牲了一定的一致性,在极端情况下它宁愿接受故障实例也不要丢掉“健康”实例,比如,当服务注册中心的网络发生故障断开时,由于所有的服务实例无法维持续约心跳,在强调CP的服务治理中将会把所有服务实例都剔除掉,而Eureka则会触发保护机制,保留此时的所有节点,以实现服务间依然可以进行互相调用的场景。