使用Apache Zookeeper进行协调和服务发现

面向服务的设计已被证明是针对各种不同的分布式系统的成功解决方案。 如果使用得当,它会带来很多好处。 但是随着服务数量的增加,了解部署什么以及部署在何处变得更加困难。 而且,由于我们正在构建可靠且高度可用的系统,因此还需要问另一个问题:每个服务有多少实例可用?

在今天的帖子中,我想向您介绍Apache ZooKeeper的世界-一种高度可靠的分布式协调服务。 ZooKeeper提供的功能之多令人惊讶,因此让我们从一个非常简单的问题开始解决:我们有一个无状态的JAX-RS服务,我们可以根据需要在任意数量的JVM /主机上进行部署。 该服务的客户端应该能够自动发现所有可用实例,而只需选择其中一个(或全部)以执行REST调用即可。

听起来像是一个非常有趣的挑战。 有很多解决方法,但让我选择Apache ZooKeeper 。 第一步是下载Apache ZooKeeper (撰写本文时,当前的稳定版本是3.4.5)并解压缩。 接下来,我们需要创建一个配置文件。 做到这一点的简单方法是将conf / zoo_sample.cfg复制到conf / zoo.cfg中 。 要运行,只需执行:

Windows: bin/zkServer.cmd
Linux: bin/zkServer

太好了,现在Apache ZooKeeper已启动并正在运行,正在端口2181上侦听(默认)。 Apache ZooKeeper本身值得一本书来解释其功能。 但是简短的概述给出了一个非常高级的图片,足以使我们入门。

Apache ZooKeeper具有强大的Java API,但是它是一个很底层的工具,并且不容易使用。 这就是为什么Netflix开发并开源了一个很棒的库,称为Curator,用于将本机Apache ZooKeeper API包装到更方便,更易于集成的框架中(现在是Apache孵化器项目)。

现在,让我们做一些代码! 我们正在开发简单的JAX-RS 2.0服务,该服务返回人员列表。 由于它将是无状态的,因此我们能够在单个主机或多个主机中运行许多实例,例如,取决于系统负载。 出色的Apache CXF和Spring框架将支持我们的实现。 以下是PeopleRestService的代码段:

package com.example.rs;import java.util.Arrays;
import java.util.Collection;import javax.annotation.PostConstruct;
import javax.inject.Inject;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;import com.example.model.Person;@Path( PeopleRestService.PEOPLE_PATH ) 
public class PeopleRestService {public static final String PEOPLE_PATH = "/people";@PostConstructpublic void init() throws Exception {}@Produces( { MediaType.APPLICATION_JSON } )@GETpublic Collection< Person > getPeople( @QueryParam( "page") @DefaultValue( "1" ) final int page ) {return Arrays.asList(new Person( "Tom", "Bombadil" ),new Person( "Jim", "Tommyknockers" ));}
}

非常基本和天真的实现。 方法初始化有意为空,很快就会很有帮助。 同样,让我们​​假设我们正在开发的每个JAX-RS 2.0服务都支持某种版本控制概念,类RestServiceDetails可以达到这个目的:

package com.example.config;import org.codehaus.jackson.map.annotate.JsonRootName;@JsonRootName( "serviceDetails" )
public class RestServiceDetails {private String version;public RestServiceDetails() {}public RestServiceDetails( final String version ) {this.version = version;}public void setVersion( final String version ) {this.version = version;}public String getVersion() {return version;}    
}

我们的Spring配置类AppConfig使用People REST服务创建JAX-RS 2.0服务器的实例,该实例将由Jetty容器托管:

package com.example.config;import java.util.Arrays;import javax.ws.rs.ext.RuntimeDelegate;import org.apache.cxf.bus.spring.SpringBus;
import org.apache.cxf.endpoint.Server;
import org.apache.cxf.jaxrs.JAXRSServerFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;import com.example.rs.JaxRsApiApplication;
import com.example.rs.PeopleRestService;
import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;@Configuration
public class AppConfig {public static final String SERVER_PORT = "server.port";public static final String SERVER_HOST = "server.host";public static final String CONTEXT_PATH = "rest";@Bean( destroyMethod = "shutdown" )public SpringBus cxf() {return new SpringBus();}@Bean @DependsOn( "cxf" )public Server jaxRsServer() {JAXRSServerFactoryBean factory = RuntimeDelegate.getInstance().createEndpoint( jaxRsApiApplication(), JAXRSServerFactoryBean.class );factory.setServiceBeans( Arrays.< Object >asList( peopleRestService() ) );factory.setAddress( factory.getAddress() );factory.setProviders( Arrays.< Object >asList( jsonProvider() ) );return factory.create();} @Bean public JaxRsApiApplication jaxRsApiApplication() {return new JaxRsApiApplication();}@Bean public PeopleRestService peopleRestService() {return new PeopleRestService();}@Beanpublic JacksonJsonProvider jsonProvider() {return new JacksonJsonProvider();} 
}

这是运行嵌入式Jetty服务器的ServerStarter类。 由于我们希望每个主机托管许多这样的服务器,因此端口不应该硬编码,而应作为参数提供:

package com.example;import org.apache.cxf.transport.servlet.CXFServlet;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;import com.example.config.AppConfig;public class ServerStarter {public static void main( final String[] args ) throws Exception {if( args.length != 1 ) {System.out.println( "Please provide port number" );return;}final int port = Integer.valueOf( args[ 0 ] );final Server server = new Server( port );System.setProperty( AppConfig.SERVER_PORT, Integer.toString( port ) );System.setProperty( AppConfig.SERVER_HOST, "localhost" );// Register and map the dispatcher servletfinal ServletHolder servletHolder = new ServletHolder( new CXFServlet() );final ServletContextHandler context = new ServletContextHandler();   context.setContextPath( "/" );context.addServlet( servletHolder, "/" + AppConfig.CONTEXT_PATH + "/*" );  context.addEventListener( new ContextLoaderListener() );context.setInitParameter( "contextClass", AnnotationConfigWebApplicationContext.class.getName() );context.setInitParameter( "contextConfigLocation", AppConfig.class.getName() );server.setHandler( context );server.start();server.join(); }
}

好的,此刻无聊的部分结束了。 但是, Apache ZooKeeper和服务发现在哪里适合呢? 答案是:只要部署了新的PeopleRestService服务实例,它就会将自身发布(或注册)到Apache ZooKeeper注册表中,包括可访问的URL和所托管的服务版本。 客户端可以查询Apache ZooKeeper以获得所有可用服务的列表并调用它们。 服务及其客户唯一需要了解的是Apache ZooKeeper的运行位置。 当我在本地计算机上部署所有内容时,实例在localhost上 。 让我们将此常量添加到AppConfig类中:

private static final String ZK_HOST = "localhost";

每个客户端都维护与Apache ZooKeeper服务器的持久连接。 每当客户端死亡时,连接也会断开, Apache ZooKeeper可以决定此特定客户端的可用性。 要连接到Apache ZooKeeper ,我们必须创建一个CuratorFramework类的实例:

@Bean( initMethod = "start", destroyMethod = "close" )
public CuratorFramework curator() {return CuratorFrameworkFactory.newClient( ZK_HOST, new ExponentialBackoffRetry( 1000, 3 ) );
}

下一步是创建ServiceDiscovery类的实例,该实例将允许使用刚刚创建的CuratorFramework实例将服务信息发布到Apache ZooKeeper中以供发现(我们还希望将RestServiceDetails作为附加元数据与每个服务注册一起提交):

@Bean( initMethod = "start", destroyMethod = "close" )
public ServiceDiscovery< RestServiceDetails > discovery() {JsonInstanceSerializer< RestServiceDetails > serializer = new JsonInstanceSerializer< RestServiceDetails >( RestServiceDetails.class );return ServiceDiscoveryBuilder.builder( RestServiceDetails.class ).client( curator() ).basePath( "services" ).serializer( serializer ).build();        
}

在内部, Apache ZooKeeper像标准文件系统一样,将其所有数据存储为分层名称空间。 服务路径将成为我们所有服务的基本(根)路径。 每个服务还需要弄清楚它正在运行哪个主机和端口。 我们可以通过构建JaxRsApiApplication类中包含的URI规范来做到这一点( {port}{scheme}将在服务注册时由Curator框架解析):

package com.example.rs;import javax.inject.Inject;
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;import org.springframework.core.env.Environment;import com.example.config.AppConfig;
import com.netflix.curator.x.discovery.UriSpec;@ApplicationPath( JaxRsApiApplication.APPLICATION_PATH )
public class JaxRsApiApplication extends Application {public static final String APPLICATION_PATH = "api";@Inject Environment environment;public UriSpec getUriSpec( final String servicePath ) {return new UriSpec( String.format( "{scheme}://%s:{port}/%s/%s%s",environment.getProperty( AppConfig.SERVER_HOST ),AppConfig.CONTEXT_PATH,APPLICATION_PATH, servicePath) );   }
}

最后一个难题是在服务发现中注册PeopleRestService ,并且init方法在这里起作用:

@Inject private JaxRsApiApplication application;
@Inject private ServiceDiscovery< RestServiceDetails > discovery; 
@Inject private Environment environment;@PostConstruct
public void init() throws Exception {final ServiceInstance< RestServiceDetails > instance = ServiceInstance.< RestServiceDetails >builder().name( "people" ).payload( new RestServiceDetails( "1.0" ) ).port( environment.getProperty( AppConfig.SERVER_PORT, Integer.class ) ).uriSpec( application.getUriSpec( PEOPLE_PATH ) ).build();discovery.registerService( instance );
}

这是我们所做的:

  • 创建了一个名称为people的服务实例(完整名称为/ services / people
  • 端口设置为该实例正在运行的实际值
  • 设置此特定REST服务端点的URI规范
  • 此外,还附加了带有服务版本的有效负载( RestServiceDetails )(尽管未使用,但它演示了传递更多详细信息的能力)

我们正在运行的每个新服务实例都将在以下位置发布
/ services / people路径
Apache ZooKeeper 。 要查看实际情况,让我们构建并运行几个人服务实例。

mvn clean package
java -jar jax-rs-2.0-service\target\jax-rs-2.0-service-0.0.1-SNAPSHOT.one-jar.jar 8080
java -jar jax-rs-2.0-service\target\jax-rs-2.0-service-0.0.1-SNAPSHOT.one-jar.jar 8081

在Apache ZooKeeper中,它可能看起来像这样(请注意,会话UUID将有所不同):

动物园管理员

让两个服务实例启动并运行,让我们尝试使用它们。 从服务客户端的角度来看,第一步是完全相同的:应该按照上面的方法创建CuratorFrameworkServiceDiscovery的实例(配置类ClientConfig声明那些bean),而无需进行任何更改。 但是,除了注册服务,我们将查询可用的服务:

package com.example.client;import java.util.Collection;import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;import org.springframework.context.annotation.AnnotationConfigApplicationContext;import com.example.config.RestServiceDetails;
import com.netflix.curator.x.discovery.ServiceDiscovery;
import com.netflix.curator.x.discovery.ServiceInstance;public class ClientStarter {public static void main( final String[] args ) throws Exception {try( final AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( ClientConfig.class ) ) { @SuppressWarnings("unchecked")final ServiceDiscovery< RestServiceDetails > discovery = context.getBean( ServiceDiscovery.class );final Client client = ClientBuilder.newClient();final Collection< ServiceInstance< RestServiceDetails > > services = discovery.queryForInstances( "people" );for( final ServiceInstance< RestServiceDetails > service: services ) {final String uri = service.buildUriSpec();final Response response = client.target( uri ).request( MediaType.APPLICATION_JSON ).get();System.out.println( uri + ": " + response.readEntity( String.class ) );System.out.println( "API version: " + service.getPayload().getVersion() );response.close();}}}
}

一旦检索到服务实例, 就将进行REST调用(使用很棒的JAX-RS 2.0客户端API),并另外询问服务版本(因为有效负载包含RestServiceDetails类的实例)。 让我们针对之前部署的两个实例构建并运行客户端:

mvn clean package
java -jar jax-rs-2.0-client\target\jax-rs-2.0-client-0.0.1-SNAPSHOT.one-jar.jar

控制台输出应显示对两个不同端点的两次调用:

http://localhost:8081/rest/api/people: [{"email":null,"firstName":"Tom","lastName":"Bombadil"},{"email":null,"firstName":"Jim","lastName":"Tommyknockers"}]
API version: 1.0http://localhost:8080/rest/api/people: [{"email":null,"firstName":"Tom","lastName":"Bombadil"},{"email":null,"firstName":"Jim","lastName":"Tommyknockers"}]
API version: 1.0

如果我们停止一个或所有实例,则它们将从Apache ZooKeeper注册表中消失。 如果任何实例崩溃或变得无响应,则同样适用。

优秀的! 我想我们使用Apache ZooKeeper这样强大的工具实现了我们的目标。 感谢其开发人员以及馆长们,使您可以轻松地在应用程序中使用Apache ZooKeeper 。 我们只是简单介绍了使用Apache ZooKeeper可以完成的工作,我强烈建议大家探索其功能(分布式锁,缓存,计数器,队列等)。

值得一提的是来自LinkedIn的Apache ZooKeeper上另一个名为Norbert的出色项目。 对于Eclipse开发人员,还可以使用Eclipse插件 。

  • 所有资源都可以在GitHub上找到 。

参考:在Andriy Redko {devmind}博客上,我们的JCG合作伙伴 Andrey Redko 与Apache Zookeeper进行了协调和服务发现 。

翻译自: https://www.javacodegeeks.com/2013/11/coordination-and-service-discovery-with-apache-zookeeper.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/366887.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

微信小程序无埋点数据采集方案

作者&#xff1a;lxj&#xff0c;点餐终端团队成员前言 相信业务团队对这样的场景不会太陌生&#xff1a;打点需求&#xff1a; 每新上一个功能&#xff0c;数据产品便会同步加上打点需求&#xff0c;当数据打点上线后一段时间&#xff0c;数据产品/业务产品便会针对数据的转化…

php 状态码302,HTTP状态码302、303和307的故事

今日读书&#xff0c;无法理解HTTP302、303、307状态码的来龙去脉&#xff0c;决定对其做深究并总结于本文。《HTTP权威指南》第3章在讲解30X状态码时&#xff0c;完全没有讲清楚为什么要有302、303、307&#xff0c;以及他们的关系&#xff0c;一句“问题出在HTTP/1/1”让我一…

Codeforces 1070A Find a Number(BFS) 2018-2019 ICPC, NEERC, Southern Subregional Contest Problem A

Description You are given two positive integers ddd and sss. Find minimal positive integer nnn which is divisible by ddd and has sum of digits equal to sss.Input The first line contains two positive integers ddd and sss(1≤d≤500,1≤s≤5000)(1≤d≤500,1≤s…

luogu3807 【模板】 卢卡斯定理

题目大意 对于一个很大的$n,m,p$如何求$C_{nm}^m\mod p$&#xff1f; Lucas定理 若$n_i,m_i$分别是$n,m$在$p$进制下第$i$位的数字&#xff0c;则有 $$C_n^m\mod p\prod_{i0}^{\log_p m}C_{n_i}^{m_i}\mod p$$ 求法 按照定理式一个一个求组合数即可。 组合数并不用批量求。故预…

防止System.exit调用

在开发运行其他开发人员编写的代码的容器时&#xff0c;请谨慎防范System.exit调用。 如果开发人员无意间调用了System.exit并将其代码部署为由您的容器运行&#xff0c;则它将完全降低容器进程。 可以使用SecurityManager中的checkExit函数调用来控制。 根据SecurityManager …

php异步请求$.post,如何用PHP实现异步请求、忽略返回值

本篇文章的主要内容是用PHP实现异步请求、忽略返回值&#xff0c;具有一定的参考价值&#xff0c;有需要的朋友可以看看&#xff0c;希望能对你有帮助。项目需要&#xff0c;场景如下&#xff1a;某个条件下需要调用接口发送多个请求执行脚本&#xff0c;但是由于每个请求下的脚…

JavaScript函数式编程学习

本文是我在函数式编程学习过程中&#xff0c;总结的笔记&#xff0c;也分享给想学函数式编程的同学。 函数式编程可能对于初学者来说&#xff0c;概念难以理解&#xff0c;入门比较难。所以本文从两部分去学习。 在你身边你左右 --函数式编程别烦恼 第一部分&#xff0c;你身…

读书印记 - 《沟通的艺术:一本手把手教你社交沟通的书》

一个教训是没上过榜也没什么好推荐的书有挺大概率不是本好书。我现在不太能耐心的去读这种指导手册&#xff0c;一是谈话的技巧需要在实战中才能有效提升&#xff0c;二是这方面对我来说不是关注的重点。所以几乎是刷刷的翻完了整本书。当然也不能说这本书就是烂书&#xff0c;…

Linux NTP

ntpd服务的设置: ntpd服务的相关设置文件如下&#xff1a; /etc/ntp.conf&#xff1a;这个是NTP daemon的主要设文件&#xff0c;也是 NTP 唯一的设定文件。/usr /share/zoneinfo/:在这个目录下的文件其实是规定了各主要时区的时间设定文件&#xff0c;例如北京地区的时区设定文…

Java Web项目的保存和刷新

如何配置计算机以保存并刷新Java Web项目&#xff1f; 当您向开发人员提出此问题时&#xff0c;答案以“哦&#xff0c;好吧……”开头&#xff0c;并以某种可行的方式继续描述。 每个人都有自己的方式。 首先–为什么需要这个&#xff1f; 因为执行复制和重新启动服务器是生产…

mysql alter怎么用,mysql alter话语用法

mysql alter语句用法//主键ALTER TABLE tablename add new_field_id int(5) UNSIGNED DEFAULT 0 NOT NULL anto_increment,ADD PRIMARY KEY (new_field_id);//增加一个新列mysql>ALTER TABLE tablename ADD fieldname fieldtype如&#xff1a;ALTER TABLE t2 ADD d TIMESTAM…

《React 学习之道》The Road to learn React (简体中文版)

通往 React 实战大师之旅&#xff1a;掌握 React 最简单&#xff0c;且最实用的教程。 前言 《React 学习之道》这本书使用路线图中的精华部分用于传授 React&#xff0c;并将其融入一个独具吸引力的真实世界 App的具体代码实现。 如何掌握 React 存在特别多的路线图。本书提…

Linux下设置和查看环境变量

Linux的变量种类 按变量的生存周期来划分&#xff0c;Linux变量可分为两类&#xff1a; 1 永久的&#xff1a;需要修改配置文件&#xff0c;变量永久生效。 2 临时的&#xff1a;使用export命令声明即可&#xff0c;变量在关闭shell时失效。 设置变量的三种方法 1 在/etc/profi…

vue.js(三)

这里该记到vue的组件了&#xff0c;组件基础篇 1.vue组件的基本书写方式 Vue.component(button-counter, {data: function () {return {count: 0}},template: <button v-on:click"count">You clicked me {{ count }} times.</button> }) 上面定义了一个名…

RIP GlassFish –感谢所有的鱼。

我们都听说过它的到来。 昨天&#xff0c;JavaEE和GlassFish的官方路线图更新已发布 。 从标题开始&#xff0c;整个帖子基本上都是关于一件事的&#xff1a;今天我们知道的GlassFish Server已从完整的产品转为玩具产品。 从Sun到Oracle的漫长道路 从一开始&#xff0c;GlassF…

Nuxt中如何使用Vuex-Store异步获取数据

Nuxt是一个基于Vue.js的通用型框架&#xff0c;它集成了使用Vue开发的绝大数组件/框架。 长话短说如何在Vuex-store中获取异步数据呢&#xff1f; 在Nuxt中由于集合了Vuex还有其他的一些配置&#xff0c;大大的方便了我们使用Vuex&#xff1b;在Nuxt官方文档中写到&#xff1a;…

Struts2面试问答

Struts2是用Java开发Web应用程序的著名框架之一。 最近&#xff0c;我写了很多Struts2教程 &#xff0c;在这篇文章中&#xff0c;我列出了一些重要的Struts2面试问题以及答案&#xff0c;以帮助您进行面试。 什么是Struts2&#xff1f; Struts1和Struts2之间有什么区别&…

php怎么改标题,PHP – 如何更改包含header.php的页面标题?

扩展Dainis Abols答案,以及关于输出处理的问题,考虑以下&#xff1a;你的header.php标题标签设置为< title>&#xff05;TITLE&#xff05;< / title&gt ;;“&#xff05;”很重要,因为几乎没有人输入&#xff05;TITLE&#xff05;所以你可以在以后使用str_repla…

基于canvas的骨骼动画

最近学习到了一种关于canvas的骨骼动画&#xff0c;听这个名字就知道他和canvas之前的动画不同&#xff0c;不知道你有没有兴趣了解一下呢&#xff1f;关于骨骼动画最初是无意间在腾讯团队上看到的&#xff0c;但是由于他官网的教程是在是少之又少&#xff0c;也就仅有一个小de…

Python APSchedule安装使用与源码分析

我们的项目中用apschedule作为核心定时调度模块。所以对apschedule进行了一些调查和源码级的分析。 1、为什么选择apschedule&#xff1f; 听信了一句话&#xff0c;apschedule之于python就像是quartz之于java。实际用起来还是不错的。 2、安装 # pip安装方式 $ pip install ap…