使用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异步请求$.post,如何用PHP实现异步请求、忽略返回值

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

Linux NTP

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

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

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

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> }) 上面定义了一个名…

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之间有什么区别&…

基于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…

NetBeans 7.4的本机Java打包

成为“ NetBeans 74 NewAndNoteworthy ”页面的NetBeans 7.4的新功能之一是“本机打包 ”&#xff0c;在该页面上被描述为“ JavaSE项目现在支持使用JavaFX提供的本机打包技术来创建本机包。 ” 我将使用一个非常简单的示例来演示NetBeans 7.4中的本机打包功能。 下一个代码清…

基于Vue开发一个日历组件

最近在做一个类似课程表的需求&#xff0c;需要自制一个日历来支持功能及展现&#xff0c;就顺便研究一下应该怎么开发日历组件。 更新 2.23修复了2026年2月份会渲染多一行的bug&#xff0c;谢谢深蓝一人童鞋提出的bug&#xff0c;解决方案是给二月份的日历做特殊处理&#xf…

php 打开word乱码怎么办,如何解决php word 乱码问题

php word乱码的解决办法&#xff1a;首先打开“/Writer/Word2007/Base.php”文件&#xff1b;然后添加“$objWriter->writeAttribute(‘w:eastAsia’, $font)”内容&#xff1b;最后保存修改即可。PHPword解决中文乱码一、增加东亚字体支持打开并编辑路径/Writer/Word2007/B…

Java开发人员访谈的MindMap

多年来&#xff0c;我曾在许多Java开发人员访谈中担任小组成员。 之前&#xff0c;我曾写过一篇标题为“成功进行软件工程师技术面试的7大技巧”的文章&#xff0c;其中涵盖了很少的一般准则。 在本文中&#xff0c;我将分享一个思维导图&#xff0c;其中包含Java开发人员访谈中…

送给大家一个好看的简历神器

很多人看到里边有好看的东西就习惯性的点进来看看&#xff0c;还一边点一边想 —— 好看的简历我见多了&#xff0c;你这个又能好看到哪里去。我想差不多可以&#xff1a; 哪里吧因为最近有在准备简历&#xff0c;就习惯性的找一找有没有现成的简历模板。结果全是付费的&#x…

PHP简单实现单点登录功能示例

1.准备两个虚拟域名 127.0.0.1 www.openpoor.com127.0.0.1 www.myspace.com 2.在openpoor的根目录下创建以下文件 index.PHP 123456789101112131415161718<?phpsession_start();?><!DOCTYPE html><html><head><meta charset"UTF-8"/&…

JUNG 计算图属性,中心度,偏心率,直径,半径

本文介绍利用Java的第三方API JUNG 计算图中&#xff1a; closeness centrality&#xff1b;// 图中某节点的 接近中心性/亲密中心性 betweenness centrality&#xff1b;// 图中某节点的 中介中心性/介数中心性 distance; // 图中两节点的最短距离 eccentricity; // 图中某节…

Java VM –提防YoungGen空间

您可能从我们以前的面向性能的文章中看到&#xff0c;健康的JVM是实现最佳应用程序性能和稳定性的最重要目标之一。 这样的健康评估通常仅关注主要收集的频率&#xff08;避免&#xff09;或检测内存泄漏的存在。 年轻一代空间或短寿命物体的大小和足迹如何&#xff1f; 本文…

小程序绘图工具painter-json文件绘制保存分享图-可点击任意元素触发函数

Painter是由酷家乐移动前端团队打造的一款小程序绘图组件。 原项目地址&#xff1a;https://github.com/Kujiale-Mobile/Painter 新版地址&#xff1a;https://github.com/shesw/Painter 这款交互版原来是为了针对业务中的新需求而由我自己开发的&#xff0c;后来需求改动&a…

4 张动图解释为什么(什么时候)使用 Redux

dev-reading/fe 是一个阅读、导读、速读的 repo&#xff0c;不要依赖于 dev-reading/fe 学习知识。本 repo 只是一个快速了解文章内容的工具&#xff0c;并不提供全文解读和翻译。你可以通过本平台快速了解文章里面的内容&#xff0c;找到感兴趣的文章&#xff0c;然后去阅读全…

您正在使用什么垃圾收集器?

我们的研究实验室正全速前进。 随着最近的资金注入 &#xff0c;我们只能保证我们不断创新的步伐只会加快。 我们进行的部分研究与GC优化有关。 在处理这个有趣领域中的问题时&#xff0c;我们认为可以分享一些有关GC算法使用的见解。 为此&#xff0c;我们对使用特定GC算法的…