“没有最好的技术,只有最合适的技术。”我想这句话也同样适用于微服务领域, 没有最好的服务框架,只有最适合自己的服务改造 。在 Dubbo 的未来规划中,除了保持自身技术上的领先性,关注性能、大流量、大规模集群领域的挑战外,围绕 Dubbo 核心来发展生态,将 Dubbo 打造成一个服务化改造的整体方案也是重点之一。 从本期开始,我们将推出“服务化改造”系列文章,通过在一些外围系统和服务化基础组件上的开发实践,分享Dubbo 生态下的服务化改造收获和总结。
» 8月26日,Aliware Open Source 将首次在成都举办 Apache Dubbo 的meetup活动,Dubbo 、Sentinel和 Nacos 的小哥哥和小姐姐都会在现场进行技术分享,欢迎成都的朋友报名参加我们的活动喔,公众号后台发送“ 成都meetup ”,获取报名链接。
一、改造背景
在现代的分布式应用中,往往会出现节点和节点之间的协调问题,其中就包括了: 选主,集群管理,分布式锁,分布式配置管理,统一命名服务,状态同步 等诉求。Apache ZooKeeper,正如它的名字所暗示的那样,动物园管理员,就是为了解决这些诉求的一个分布式 协调 服务框架。
为了保证系统的高可用,ZooKeeper本身也可以部署成集群模式,称之为ZooKeeper Ensemble。ZooKeeper集群中始终确保其中的一台为leader的角色,并通过ZAB(Zookeeper Atomic Broadcast Protocol) [1] 协议确保所有节点上的信息的一致。客户端可以访问集群中的任何一台进行读写操作,而不用担心数据出现不一致的现象。
O'Reilly的ebook-Zookeeper-Distributed Process Coordination
Zookeeper中的数据存储方式与传统的UNIX文件系统相似,节点按照树状结构来组织,其中,节点被称之为znodes(ZooKeeper数据节点)
O'Reilly的ebook-Zookeeper-Distributed Process Coordination
二、基本用法
brew install zookeeper
来安装,考虑到通用性,本文采用docker的方式来运行ZooKeeper。如果没有安装docker,请先准备好docker环境 [4] 。 执行命令将 ZooKeeper,运行在docker容器中
docker run --rm --name zookeeper -p 2181:2181 zookeeper
docker exec -it zookeeper bash
在 bin
目录下有启动 ZooKeeper 的命令 zkServer 以及管理控制台 zkCli
bash-4.4# ls -l bintotal 36 -rwxr-xr-x 1 zookeepe zookeepe 232 Mar 27 04:32 README.txt -rwxr-xr-x 1 zookeepe zookeepe 1937 Mar 27 04:32 zkCleanup.sh -rwxr-xr-x 1 zookeepe zookeepe 1056 Mar 27 04:32 zkCli.cmd -rwxr-xr-x 1 zookeepe zookeepe 1534 Mar 27 04:32 zkCli.sh -rwxr-xr-x 1 zookeepe zookeepe 1759 Mar 27 04:32 zkEnv.cmd -rwxr-xr-x 1 zookeepe zookeepe 2696 Mar 27 04:32 zkEnv.sh -rwxr-xr-x 1 zookeepe zookeepe 1089 Mar 27 04:32 zkServer.cmd -rwxr-xr-x 1 zookeepe zookeepe 6773 Mar 27 04:32 zkServer.sh
由于是通过Docker启动, ZooKeeper 进程已经启动,并通过2181端口对外提供服务。
bash-4.4# psPID USER TIME COMMAND 1 zookeepe 0:02 /usr/lib/jvm/java-1.8-openjdk/jre/bin/java -Dzookeeper.log.dir=. -Dzookeeper.root 32 root 0:00 bash 42 root 0:00 ps
因此可以直接通过zkCli来访问 ZooKeeper 的控制台来进行管理。
bash-4.4# bin/zkCli.sh -server 127.0.0.1:2181Connecting to 127.0.0.1:2181... WATCHER:: WatchedEvent state:SyncConnected type:None path:null[zk: 127.0.0.1:2181(CONNECTED) 0] helpZooKeeper -server host:port cmd args stat path [watch] set path data [version] ls path [watch] delquota [-n|-b] path ls2 path [watch] setAcl path acl setquota -n|-b val path history redo cmdno printwatches on|off delete path [version] sync path listquota path rmr path get path [watch] create [-s] [-e] path data acl addauth scheme auth quit getAcl path close connect host:port
创建 /hello-zone
节点:
[zk: 127.0.0.1:2181(CONNECTED) 19] create /hello-zone 'world'Created /hello-zone
列出 /
下的子节点,确认 hello-zone
被创建:
[zk: 127.0.0.1:2181(CONNECTED) 19] create /hello-zone 'world'Created /hello-zone
列出 /hello-zone 的子节点,确认为空:
[zk: 127.0.0.1:2181(CONNECTED) 21] ls /hello-zone[]
获取存储在 /hello-zone
节点上的数据:
[zk: 127.0.0.1:2181(CONNECTED) 22] get /hello-zone world
三、在 Dubbo 中使用ZooKeeper
Dubbo使用 ZooKeeper 用于服务的注册发现和配置管理,在 ZooKeeper 中数据的组织由下图所示:
首先,所有Dubbo相关的数据都组织在 /duboo
的根节点下。
二级目录是服务名,如 com.foo.BarService
。
三级目录有两个子节点,分别 providers
和 consumers
, 表示该服务的提供者和消费者。
四级目录记录了与该服务相关的每一个应用实例的URL信息,在 providers
下的表示该服务的所有提供者,而在 consumers
下的表示该服务的所有消费者。举例说明, com.foo.BarService
的服务提供者在启动时将自己的URL信息注册到 /dubbo/com.foo.BarService/providers
下;同样的,服务消费者将自己的信息注册到相应的 consumers
下,同时,服务消费者会订阅其所对应的 providers
节点,以便能够感知到服务提供方地址列表的变化。
四、准备示例代码
本文代码可以在以下链接中找到。
https://github.com/dubbo/dubbo-samples/tree/master/dubbo-samples-zookeeper
一个定义简单的 GreetingService
接口,只有里面一个简单的方法 sayHello
向调用者问好。
public interface GreetingService { String sayHello(String name);}
实现 GreetingService
接口,并通过 @Service
来标注其为Dubbo的一个服务。
@Servicepublic class AnnotatedGreetingService implements GreetingService { public String sayHello(String name) { return "hello, " + name; }}
定义 ProviderConfiguration 来组装Dubbo服务。
@Configuration@EnableDubbo(scanBasePackages = "com.alibaba.dubbo.samples.impl")@PropertySource("classpath:/spring/dubbo-provider.properties")static class ProviderConfiguration {}
dubbo-provider.properties是在Spring应用中外置配置的方式,内容如下:
dubbo.application.name=demo-provider dubbo.registry.address=zookeeper://$DOCKER_HOST:2181 dubbo.protocol.name=dubbo dubbo.protocol.port=20880
由于 ZooKeeper 运行在Docker容器中,需要注意的是:
本文假定Dubbo应用运行在宿主机上,也就是Docker容器外,需要将 ZooKeeper 的地址替换成环境变量${DOCKER_HOST}所指定的IP地址,相关信息请查阅Docker官方文档;
如果Dubbo应用也是Docker化的应用,只需要用 ZooKeeper 的容器名,在本文中容器名是 ZooKeeper ;
当然,如果不用容器方式启动 ZooKeeper ,只需要简单的将这里的$ DOCKER_HOST换成localhost即可。
在 main
方法中通过启动一个Spring Context来对外提供Dubbo服务。
public class ProviderBootstrap { public static void main(String[] args) throws Exception { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ProviderConfiguration.class); context.start(); System.in.read(); }}
服务启动端的的 main
方法,将会看到下面的输出,代表服务端启动成功,并在注册中心( ZooKeeper Registry)注册上了 GreetingService
这个服务 :
[03/08/18 10:50:33:033 CST] main INFO zookeeper.ZookeeperRegistry: [DUBBO] Register: dubbo://192.168.99.1:20880/com.alibaba.dubbo.samples.api.GreetingService?anyhost=true&application=demo-provider&dubbo=2.6.2&generic=false&interface=com.alibaba.dubbo.samples.api.GreetingService&methods=sayHello&pid=12938&side=provider×tamp=1533264631849, dubbo version: 2.6.2, current host: 192.168.99.1
通过 ZooKeeper 管理终端观察服务提供方的注册信息:
$ docker exec -it zookeeper bash bash-4.4# bin/zkCli.sh -server localhost:218 Connecting to localhost:2181 ... Welcome to ZooKeeper! JLine support is enabled ... [zk: localhost:2181(CONNECTED) 0] ls /dubbo/com.alibaba.dubbo.samples.api.GreetingService/providers [dubbo%3A%2F%2F192.168.99.1%3A20880%2Fcom.alibaba.dubbo.samples.api.GreetingService%3Fanyhost%3Dtrue%26application%3Ddemo-provider%26dubbo%3D2.6.2%26generic%3Dfalse%26interface%3Dcom.alibaba.dubbo.samples.api.GreetingService%26methods%3DsayHello%26pid%3D12938%26side%3Dprovider%26timestamp%3D1533264631849]
可以看到刚刚启动的Dubbo的服务在 providers
节点下注册了自己的URL地址:dubbo://192.168.99.1:20880 /com.alibaba.dubbo.samples.api.GreetingService?anyhost = true&application = demo-provider&dubbo =2.6 0.2&通用=假接口=com.alibaba.dubbo.samples.api.GreetingService&方法= sayHello的&PID = 12938&侧=提供商时间戳=1533264631849
通过 @Reference
来在客户端声明服务的引用,运行时将会通过该引用发起全程调用,而服务的目标地址将会从 ZooKeeper 的 provider
节点下查询。
@Component("annotatedConsumer")public class GreetingServiceConsumer { @Reference private GreetingService greetingService; public String doSayHello(String name) { return greetingService.sayHello(name); }}
定义ConsumerConfiguration来组装Dubbo服务。
@Configuration@EnableDubbo(scanBasePackages = "com.alibaba.dubbo.samples.action")@PropertySource("classpath:/spring/dubbo-consumer.properties")@ComponentScan(value = {"com.alibaba.dubbo.samples.action"})static class ConsumerConfiguration {}
dubbo-consumer.properties是在Spring应用中外置配置的方式,内容如下:
dubbo.application.name=demo-consumer dubbo.registry.address=zookeeper://$DOCKER_HOST:2181 dubbo.consumer.timeout=3000
与服务端:组装相同,需要根据自己的运行环境来修改dubbo.registry.address中定义的$ DOCKER_HOST。请参阅步骤3的说明部分。
运行 main
向已经启动的服务提供方发起一次远程调用。Dubbo会先向 ZooKeeper 订阅服务地址,然后从返回的地址列表中选取一个,向对端发起调用:
public class ConsumerBootstrap { public static void main(String[] args) {public class ConsumerBootstrap { public static void main(String[] args) throws IOException { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConsumerConfiguration.class); context.start(); GreetingServiceConsumer greetingServiceConsumer = context.getBean(GreetingServiceConsumer.class); String hello = greetingServiceConsumer.doSayHello("zookeeper"); System.out.println("result: " + hello); System.in.read(); } }
运行结果如下:
[03/08/18 01:42:31:031 CST] main INFO zookeeper.ZookeeperRegistry: [DUBBO] Register: consumer://192.168.99.1/com.alibaba.dubbo.samples.api.GreetingService?application=demo-consumer&category=consumers✓=false&default.timeout=3000&dubbo=2.6.2&interface=com.alibaba.dubbo.samples.api.GreetingService&methods=sayHello&pid=82406&side=consumer×tamp=1533274951195, dubbo version: 2.6.2, current host: 192.168.99.1 #1[03/08/18 01:42:31:031 CST] main INFO zookeeper.ZookeeperRegistry: [DUBBO] Subscribe: consumer://192.168.99.1/com.alibaba.dubbo.samples.api.GreetingService?application=demo-consumer&category=providers,configurators,routers&default.timeout=3000&dubbo=2.6.2&interface=com.alibaba.dubbo.samples.api.GreetingService&methods=sayHello&pid=82406&side=consumer×tamp=1533274951195, dubbo version: 2.6.2, current host: 192.168.99.1 #2... result: hello, zookeeper
#说明:
a. 注册:消费者://192.168.99.1/...& category= consumers &:消费者向 ZooKeeper 注册自己的信息,并放在 consumers
节点下
b. 订阅:消费者://192.168.99.1/...& 类别=提供商,配置器,路由器:消费者同时向动物园管理员订阅了 providers
、 configurators
、 routers
节点,其中 configurations
与多宝配置相关, routers
与路由规则相关,值得注意的英文 providers
节点的订阅,当有新的服务提供方加入后,由于订阅的关系,新的地址列表会推送给订阅方,服务的消费者也因此动态感知到了地址列表的变化。
通过 ZooKeeper 管理终端观察服务提供方的注册信息:
$ docker exec -it zookeeper bash bash-4.4# bin/zkCli.sh -server localhost:218 Connecting to localhost:2181 ... Welcome to ZooKeeper! JLine support is enabled ... [zk: localhost:2181(CONNECTED) 4] ls /dubbo/com.alibaba.dubbo.samples.api.GreetingService/consumers [consumer%3A%2F%2F192.168.99.1%2Fcom.alibaba.dubbo.samples.api.GreetingService%3Fapplication%3Ddemo-consumer%26category%3Dconsumers%26check%3Dfalse%26default.timeout%3D3000%26dubbo%3D2.6.2%26interface%3Dcom.alibaba.dubbo.samples.api.GreetingService%26methods%3DsayHello%26pid%3D82406%26side%3Dconsumer%26timestamp%3D1533274951195]
可以看到Dubbo的服务消费者在 consumers
节点下注册了自己的URL地址:consumer://192.168.99.1/com.alibaba.dubbo.samples.api.GreetingService?application= demo-consumer&category= provider,configurators,routers&default。超时= 3000&达博= 2.6.2&接口= com.alibaba.dubbo.samples.api.GreetingService&方法=sayHello的&PID =82406&侧=消费者的时间戳=1533274951195
五、总结
本文侧重介绍了如何在Dubbo应用中使用Zookeeper做为注册中心,当然,本文也提到了Zookeeper在Dubbo的应用场景下还承担了配置中心和服务治理的职责。本文中的Zookeeper是单节点,Standalone的模式,在生产环境中为了高可用的诉求,往往会组件Zookeeper集群,也就是Zookeeper ensemble模式。
通过本文的学习,读者可以掌握到:
ZooKeeper 的基本概念和基本用法
ZooKeeper 在Dubbo应用中的作用
通过实战了解 ZooKeeper 与Dubbo的交互
Dubbo在 ZooKeeper 中服务注册,消费信息的存储方式
当然,自从阿里巴巴开源 Nacos 后, Dubbo生态中又多了一项动态服务发现的选项, Nacos + Dubbo的组合正进一步释放 Dubbo 在云原生及ServiceMesh时代中,在大规模微服务治理、流量治理、服务集成与服务共享等服务平台能力建设上的威力。详情请见文末 “今日推文”。
参考链接:
[1] https://www.ixiacom.com/company/blog/apache-zab-zookeeper-atomic-broadcast-protocol
[2] https://www.apache.org/dyn/closer.cgi/zookeeper/
[3] https: //brew.sh
[4] https://www.docker.com/community-edition
今日推文
点击下方图片即可阅读