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

在今天的帖子中,我想向您介绍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

使用Apache Zookeeper进行协调和服务发现相关推荐

  1. 为什么Eureka比ZooKeeper更适合做服务发现?

    Eureka的优势 1.在Eureka平台中,如果某台服务器宕机,Eureka不会有类似于ZooKeeper的选举leader的过程:客户端请求会自动切换到新的Eureka节点:当宕机的服务器重新恢复 ...

  2. Apache APISIX 携手 CoreDNS 打开服务发现新大门

    Apache APISIX 是一个动态.实时.高性能的云原生 API 网关,提供了负载均衡.动态上游.灰度发布.服务熔断.身份认证.可观测性等丰富的流量管理功能.作为云原生 API 网关,Apache ...

  3. API 网关 Apache APISIX 集成 Eureka 作为服务发现

    微服务架构中,大型复杂的系统按功能或者业务需求垂直切分成更小的子系统,这些子系统以独立部署的子进程存在,它们之间通过网络调用进行通信.这些独立部署的服务如何发现对方成为了首先要解决的问题,所以在微服务 ...

  4. Netty游戏服务器实战开发(6):Netty整合Zookeeper实现分布式服务发现与注册

    1:Zookeeper基础 安装zookeeper.当然,很多时候我们会在Windows上开发,所以,我们需要在本地搭建一个zookeeper环境.方便开发过程中的测试. 首先我们去Apache上下载 ...

  5. 服务发现框架选型,Consul还是Zookeeper还是etcd

    https://www.servercoder.com/2018/03/30/consul-vs-zookeeper-etcd/ 背景 本文并不介绍服务发现的基本原理.除了一致性算法之外,其他并没有太 ...

  6. RPC Demo(二) 基于 Zookeeper 的服务发现

    RPC Demo(二) 基于 Zookeeper 的服务发现 简介     基于上篇的:RPC Demo(一) Netty RPC Demo 实现     第二部分来实现使用Zookeeper作为服务 ...

  7. Nacos 在 Apache APISIX API 网关中的服务发现实践

    Apache APISIX 是一个动态.实时.高性能的 API 网关,提供负载均衡.动态上游.灰度发布.服务熔断.身份认证.可观测性等丰富的流量管理功能.它不仅拥有众多实用的插件,而且支持插件动态变更 ...

  8. Docker学习笔记之在开发环境中使用服务发现

    0x00 概述 服务发现应用是很多服务化系统的组成部分,所以在开发.测试环境中也就有必要配备一套服务发现体系来配合我们的开发.测试工作.在这一小节里,我们就来谈谈如何在 Docker 环境下部署服务发 ...

  9. 漏洞“Apache Zookeeper 授权问题漏洞(CVE-2019-0201)”详情

    扫描漏洞详情如下: 目录 1.漏洞详情 漏洞名称 Apache Zookeeper 授权问题漏洞(CVE-2019-0201) 发现日期 2019-05-23 CVSS评分 5.9 漏洞描述 Apac ...

最新文章

  1. linux 下 php 安装 Gearman
  2. iOS-改变UITextField的Placeholder颜色的三种方式
  3. 【已解决】[Error] cannot pass objects of non-trivially-copyable type ‘std::string {aka class std::basic_s
  4. 18.8.20 考试总结
  5. php xdebug 中文手册,Xdebug文档(四)函数跟踪
  6. GDAL中MEM格式的简单使用示例
  7. vasp544编译安装
  8. [网络安全自学篇] 八十五.《Windows黑客编程技术详解》之注入技术详解(全局钩子、远线程钩子、突破Session 0注入、APC注入)
  9. VS挤房间探索(含源码)
  10. Github TOP100 Android开源,android开发环境搭建实验报心得
  11. PayPal的钱如何提现?PayPal商家终于可以松口气了!
  12. 计算关联系数matlab,matlab相关性系数【excel中的相关系数是如何计算出来的?】
  13. 什么是物联网技术
  14. JavaWeb的体育用品商店的设计与实现
  15. 《假装情侣匿名聊天室2.0》源码及搭建教程
  16. A+B的各种写法(不是couta+b;)
  17. 【实例】python 使用beautifulSoup 抓取网页正文 以淘宝商品价格为例
  18. C++流式输入输出加速 给 cin/cout 装个小火箭
  19. 80X86汇编—存储器的变址寻址
  20. 交叉验证(Cross-Validation)

热门文章

  1. 时间格式转换2020-04-09T23:00:00.000+08:00
  2. jsp连接mysql数据库 例子_JSP 连接 MySQL 数据库的例子
  3. MySQL的自然联结+外部联结(左外连接,右外连接)+内部联结
  4. 普罗米修斯使用es数据库_用普罗米修斯和格拉法纳仪法来豪猪
  5. jax-rs配置_具有MicroProfile配置的可配置JAX-RS ExceptionMapper
  6. spring-retry_使用Spring-Retry重试处理
  7. jboss eap 7_EAP 7 Alpha和Java EE 7入门
  8. 绩效工作流_流绩效–您的想法
  9. web ua检测_UA Web挑战会议:针对初创公司的SpringIO
  10. rx.observable_在Spring MVC流中使用rx-java Observable