zookeeper
面向服务的设计SOA已被证明是一个针对各种分布式系统的巨大的成功的解决方案。如果使用得当,它有一个很大的好处。但是,随着服务数量的增加,它变得更加难以明白什么服务部署在哪里。而且因为我们正在建设的可靠和高度可用的系统,这样就有一个问题:目前有多少个服务实例?
Apache ZooKeeper是 一个高度可靠的分布式服务协调者,通过ZooKeeper能够自动发现可用服务并执行一个REST 调用。
首先下载ZooKeeper,解压,拷贝目录conf/zoo_sample.cfg 到 conf/zoo.cfg,运行下面命令:
Windows: bin/zkServer.cmd Linux: bin/zkServerZookeeper现在运行在2181端口了。
下面开发一个简单的SOA服务,遵循 JAX-RS 2.0的无状态服务,查询返回人员集合。它能根据系统服务在多个服务器上运行,基础框架依赖 Apache CXF 和 Spring Framework
package com.example.rs; import java.util.Arrays; import javax.annotation.PostConstruct; import com.example.model.Person; @Path( PeopleRestService.PEOPLE_PATH ) @PostConstruct @Produces( { MediaType.APPLICATION_JSON } ) |
下面用Spring的AppConfig 是来在Jetty容器中创建JAX-RS 2.0 服务:
package com.example.config; import java.util.Arrays; import javax.ws.rs.ext.RuntimeDelegate; import org.apache.cxf.bus.spring.SpringBus; import com.example.rs.JaxRsApiApplication; @Configuration @Bean( destroyMethod = "shutdown" ) @Bean @DependsOn( "cxf" ) @Bean @Bean @Bean |
下面启动内嵌的多个Jetty服务,服务的端口是由外界输入:
package com.example; import org.apache.cxf.transport.servlet.CXFServlet; import com.example.config.AppConfig; public class ServerStarter { final int port = Integer.valueOf( args[ 0 ] ); System.setProperty( AppConfig.SERVER_PORT, Integer.toString( port ) ); // Register and map the dispatcher servlet context.setInitParameter( "contextClass", AnnotationConfigWebApplicationContext.class.getName() ); server.setHandler( context ); |
下面重点是演示 Apache ZooKeeper 如何实现服务发现定位到这个服务呢?
只要PeopleRestService 被部署时,它会将自己注册到ZooKeeper中,包括它的 URL和版本。客户端只要查询ZooKeeper,获得所有服务的列表,然后调用它们。当然,服务自己和客户端都必须知道Zookeeper在哪里运行。
这里我们是在本地机运行,这样在AppConfig 中加入localhost
private static final String ZK_HOST = "localhost";
每个服务一旦连上ZooKeeper,就保持一种持久连接,Zookeeper必须知道那些服务还活着,为了连接到ZooKeeper,我们必须创建一个CuratorFramework 实例:
@Bean( initMethod = "start", destroyMethod = "close" )
public CuratorFramework curator() {
return CuratorFrameworkFactory.newClient( ZK_HOST, new ExponentialBackoffRetry( 1000, 3 ) );
}
其中ZK_HOST是我们定义为Localhost的。
下面我们创建ServiceDiscovery 用来将服务信息提供给ZooKeeper发布。
@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();
}
在ZooKeepr内部所有数据保存在一个有层次名称空间,如同文件系统一样,服务有根路径,每个服务有自己运行的URL和端口,我们建立一个URI规范供给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 注册服务发现处,这是通过在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 );
}
