Zookeeper如何确保每位工人都能愉快地从工作委托经理那里得到一些工作。
Apache ZooKeeper是注册,管理和发现在不同计算机上运行的服务的工具。 当我们必须处理具有许多节点的分布式系统时,它是技术堆栈中必不可少的成员,这些节点需要知道其依赖关系从何处启动。
但是ZooKeeper的级别很低,即使标准用例也需要很多行代码。 这就是Apache Curator诞生的原因–一个比ZooKeeper更友好,更易于使用的包装器库。 使用Curator,我们可以用更少的代码和更简洁的方式交付更多内容。
“ Guava对于Java来说就像Curator对ZooKeeper一样” – ZooKeeper提交者Patrick Hunt
使用ZooKeeper负载均衡微服务
我们习惯于在应用程序前面部署负载均衡器的情况。 它的作用是确保每个单个节点获得或多或少相同的流量。
在微服务世界中,情况是相同的,但是与单片方法相比,我们具有显着的优势:当我们需要扩展应用程序时,我们不必复制整个系统并将其部署在功能强大的服务器上即可平稳运行。 我们只能将精力集中在需要扩展的小模块上,因此扩展成本要低得多(无论是在服务器成本还是在很多情况下为部署模块做准备所需的开发方面)。
但是随着我们系统的发展,我们最终会得到许多可扩展的模块,每个模块都需要单独的负载平衡器。 这似乎很麻烦,因为即使没有基础架构,我们的基础架构也非常复杂。 幸运的是,如果我们将ZooKeeper用作服务编排和发现工具,则可以使用内置的负载平衡功能,而不会在我们的微服务架构中引入任何其他复杂性。
为了展示ZooKeeper中开箱即用的负载平衡工作方式,我们需要两项服务:将多次部署的工作人员,以及将任务委派给已注册工作人员的经理。
简单工人
让我们从创建一个简单的工作程序开始,该工作程序将侦听给定的端口并在要求执行其工作时返回一些结果。 为了实现这个微小的微服务,我们将使用Groovy, Undertow轻量级servlet容器,当然还要使用ZooKeeper和Curator。
我们的工作人员将由一个小类组成,该小类的主要方法可以完成三件事:
class Main {static final void main(String[] args) {// Step 1: Extract name and port number to launch worker// Step 2: Configure and start Rest server on given port// Step 3: Register worker in ZooKeeper}
}
为简便起见,我将在此处省略步骤1和2,您可以在GitHub project上查看完整的源代码。 我们的工作人员只有一个端点GET / work,它会返回一个响应,并带有被叫工作人员的名字:
@Path("/")
class Main {@GET@Path("/work")public String work() {String response = "Work done by $workerName"println responsereturn response}}
步骤3: ZooKeeper中的注册工作人员是最有趣的地方,因此我将对其进行详细说明:
private static void registerInZookeeper(int port) {CuratorFramework curatorFramework = CuratorFrameworkFactory.newClient("localhost:2181", new RetryNTimes(5, 1000))curatorFramework.start()ServiceInstance<Void> serviceInstance = ServiceInstance.builder().uriSpec(new UriSpec("{scheme}://{address}:{port}")).address('localhost').port(port).name("worker").build()ServiceDiscoveryBuilder.builder(Void).basePath("load-balancing-example").client(curatorFramework).thisInstance(serviceInstance).build().start()
}
- 第2-3行:我们创建并启动CuratorFramework客户端,该客户端包装了我们要在ZooKeeper实例上执行的所有操作。 为简单起见,我们将localhost与默认端口一起使用(通常,它应该是ZooKeeper的运行实例的URL)
- 第4-9行创建了代表我们的工作人员的ServiceInstance。 我们传递了从其他微服务呼叫此工作人员所需的所有“联系方式”
- 第11-16行在由CuratorFramework客户端表示的ZooKeeper中注册我们的实例。
出发工人
现在,Worker已经准备就绪,因此我们可以创建胖子罐(使用Gradle fatJar任务),然后使用以下命令启动它:
java -jar simple-worker/build/libs/simple-worker-1.0-shadow.jar Worker_1 18005
在启动worker之前,请记住您需要在默认2181端口上运行的ZooKeeper实例!
要检查worker是否正在运行,您应该在http:// localhost:18005 / work上打开浏览器,并在其中看到“ Worker_1完成的工作”文本。 要验证工作人员是否已在ZooKeeper中正确注册了自己,请启动命令行客户端:
cd/bin
./zkCli.sh
然后执行ls命令,查看在/ load-balancing-example / worker路径下注册的一个节点:
[zk: localhost:2181(CONNECTED) 1] ls /load-balancing-example/worker <enter>
[f69545e8-8466-40c0-93e9-f493eb7496b4]
简单的经理
现在,由于我们有工作人员在监听/ work的请求,因此我们可以创建简单的经理服务委托任务给其下属。 主要方法看上去与简单工作人员项目中的方法非常相似,主要区别在于我们没有在ZooKeeper中注册,我们仅创建角色(给惊喜)为我们提供工作人员实例的ServiceProvider 。 所以基本的工作流程是:
- 等待/ delegate上的请求
- 从ZooKeeper的ServiceProvider获取工作程序服务的实例
- 调用工作人员的/工作并返回其执行结果
要创建ServiceProvider,我们必须创建CuratorFramework客户端,连接到ZooKeeper,然后获取具有给定名称的服务的ServiceProvider,在本例中为worker :
CuratorFramework curatorFramework = CuratorFrameworkFactory.newClient("localhost:2181", new RetryNTimes(5, 1000))
curatorFramework.start()ServiceDiscovery<Void> serviceDiscovery = ServiceDiscoveryBuilder.builder(Void).basePath("load-balancing-example").client(curatorFramework).build()
serviceDiscovery.start()serviceProvider = serviceDiscovery.serviceProviderBuilder().serviceName("worker").build()
serviceProvider.start()
- 第1-2行,创建ZooKeeper客户端,方法与简单工作者相同
- 第4-8行,创建ServiceDiscovery,它将能够为我们提供服务提供商。
- 第10-11行。 创建并启动ServiceProvider,将用于获取工作程序节点的工作实例。
最后,我们需要一个Rest端点,管理器在其上等待任务委派:
@GET
@Path("/delegate")
public String delegate() {def instance = serviceProvider.getInstance()String address = instance.buildUriSpec()String response = (address + "/work").toURL().getText()println responsereturn response
}
起始经理
Manager已准备就绪,执行fatJar任务后,我们可以使用以下命令启动它:
java -jar simple-manager/build/libs/simple-manager-1.0-shadow.jar 18000
为了验证它是否正常工作,我们可以在浏览器中打开( http:// localhost:18000 / delegate )以查看消息“ Worker_1完成的工作”。
经理对其员工一无所知。 他唯一知道的是服务已在ZooKeeper中的特定路径下注册。 这很简单,无论我们是在本地启动多个工作人员还是在不同国家/地区的不同服务器上分布。
使用ZooKeeper开箱即用的负载平衡
想象一下这样的情况:经理从首席执行官那里获得了太多任务,以至于他需要多个工人来委派工作。 在标准情况下,我们将被迫扩大规模并在他们前面放置负载平衡器。 但是ZooKeeper无需任何其他工作即可为我们提供此功能。
让我们添加更多在不同端口上侦听的工作者:
java -jar simple-worker/build/libs/simple-worker-1.0-shadow.jar Worker_1 18005 &java -jar simple-worker/build/libs/simple-worker-1.0-shadow.jar Worker_2 18006 &java -jar simple-worker/build/libs/simple-worker-1.0-shadow.jar Worker_3 18007 &java -jar simple-worker/build/libs/simple-worker-1.0-shadow.jar Worker_4 18008 &
诀窍是所有工作程序都在ZooKeeper中的同一路径下注册,因此当我们在/ load-balancing-example / worker下列出节点时,将看到四个实例:
[zk: localhost:2181(CONNECTED) 0] ls /load-balancing-example/worker <enter>
[d5bc4eb9-8ebb-4b7c-813e-966a25fdd843, 13de9196-bfeb-4c1a-b632-b8b9969b9c0b, 85cd1387-2be8-4c08-977a-0798017379b1, 9e07bd1d-c615-430c-8dcb-bf228e9b56fc]
这里最重要的是,要利用这四个新工作人员,经理不需要对代码进行任何更改。 我们可以在流量增加时启动新的工作程序实例,或者在无事可做时将其关闭。 Manager与这些操作脱钩,它仍然调用ServiceProvider来获取worker的实例并将工作传递给他。
所以现在当我们打开http:// localhost:18000 / delegate并按几次刷新时,我们将看到:
Work done by Worker_1
Work done by Worker_2
Work done by Worker_3
Work done by Worker_4
Work done by Worker_1
Work done by Worker_2
Work done by Worker_3
它是如何实现的? 默认情况下,ServiceProvider使用Rounded-robin ProviderStrategy实现,该实现旋转给定路径下可用的实例,从而使每个实例都有一些工作要做。 如果默认机制不符合我们的需求,我们当然可以实施自定义策略。
摘要
今天就这些。 如您所见,通过使用Apache ZooKeeper和Curator,我们可以在无需部署,监视和管理单独负载均衡器的情况下生存。 即使没有微服务架构,基础架构也非常复杂。
翻译自: https://www.javacodegeeks.com/2014/07/zookeeper-curator-and-how-microservices-load-balancing-works.html