SpringBoot ,zookeeper 服务的注册与发现

本文主要讲zookeeper如何注册服务并使用这些服务。简单流程是使用springboot开发简单的restapi.并把api注册到zookeeper。最后在客户端连接到zookeeper调用api并把结果返回到客户端。

  • springboot开发restapi
  • 注册服务到zookeeper
  • 发现服务

springboot开发restapi

搭建springboot开发环境

新建一个maven工程,并引入spring boot的jar包。pom配置是:

<parent>  
      <groupId>org.springframework.bootgroupId>  
      <artifactId>spring-boot-starter-parentartifactId>  
      <version>1.3.3.RELEASEversion>  
  parent> 
<dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-webartifactId>
      dependency>

写一个测试的api

package demo.msa.sample;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
@SpringBootApplication(scanBasePackages="demo.msa")
public class SampleApplication {

    @RequestMapping(name="HelloService",method=RequestMethod.GET,path="/hello")
    public String hello(){
        return "hello";
    }

    public static void main(String[] args) {
        SpringApplication.run(SampleApplication.class, args);
    }
}

启动后访问效果如下:
SpringBoot ,zookeeper 服务的注册与发现_第1张图片
端口配置是在application.properties
SpringBoot ,zookeeper 服务的注册与发现_第2张图片

注册服务到zookeeper

zookeeper服务器搭建

zookeeper本地搭建,网上有很多教程,这里不在赘述。说一下服务注册的原理
zookeeper的结构是基于znode的树状结构。根节点是‘/’,我们可以在根节点下扩展任意节点。
SpringBoot ,zookeeper 服务的注册与发现_第3张图片
SpringBoot ,zookeeper 服务的注册与发现_第4张图片

下面开始具体的开发:
(1)新建两个项目,msa-framework和msa-sample-api。
(2)在msa-framework定义服务注册表接口

package demo.msa.framework.registry;

public interface ServiceRegistry {

    /** * 注册服务信息 * @param serviceName 服务名称 * @param serviceAddress 服务地址 */
    void register(String serviceName,String serviceAddress);
}

打包,并引入到msa-sample-api工程

<dependency>
        <groupId>demo.msagroupId>
        <artifactId>msa-frameworkartifactId>
        <version>0.0.1-SNAPSHOTversion>
      dependency>

(3)在msa-sample-api项目中创建一个ServiceRegistry的实现类。实现注册接口。

package demo.msa.frame.registry;

import java.util.concurrent.CountDownLatch;

import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.ZooKeeper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import demo.msa.framework.registry.ServiceRegistry;

@Component
public class ServiceRegistryImpl implements Watcher, ServiceRegistry {
    private static final int SESSION_TIMEOUT = 5000;
    private static final String REGISTRY_PATH = "/registry";
    private static CountDownLatch latch = new CountDownLatch(1);
    private static Logger logger = LoggerFactory
            .getLogger(ServiceRegistryImpl.class);
    private ZooKeeper zk;

    public ServiceRegistryImpl() {
        logger.debug("初始化类");
    }

    public ServiceRegistryImpl(String zkServers) {
        try {
            zk = new ZooKeeper(zkServers, SESSION_TIMEOUT, this);
            latch.await();
            logger.debug("connected to zookeeper");

        } catch (Exception e) {
            logger.error("create zookeeper client failuer", e);
        }
    }

    @Override
    public void register(String serviceName, String serviceAddress) {
        String registryPath = REGISTRY_PATH;
        try {
            logger.debug("-zk---------"+zk);
            //创建根节点:持久节点
            if (zk.exists(registryPath, false) == null) {
                zk.create(registryPath, null, ZooDefs.Ids.OPEN_ACL_UNSAFE,
                        CreateMode.PERSISTENT);
                logger.debug("create registry node:{}",registryPath);
            }
            //创建服务节点:持久节点
            String servicePath=registryPath+"/"+serviceName;
            if(zk.exists(servicePath, false)==null){
                zk.create(servicePath, null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
                logger.debug("create service node :{}"+servicePath);
            }
            //创建地址节点:临时顺序节点
            String addressPath=servicePath+"/address-";
            String addressNode=zk.create(addressPath, serviceAddress.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
            logger.debug("create node address:{}=>{}"+addressNode);
        } catch (Exception e) {
            logger.error("create node failure",e);
        }

    }

    @Override
    public void process(WatchedEvent event) {
        if (event.getState() == Event.KeeperState.SyncConnected) {
            latch.countDown();
        }

    }

}

(4)创建一个配置类来读取application.properties的配置

package demo.msa.configure;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import demo.msa.frame.registry.ServiceRegistryImpl;
import demo.msa.framework.registry.ServiceRegistry;

@Configuration
@ConfigurationProperties(prefix="registry")
public class RegistryConfig {

    private String servers;

    @Bean
    public ServiceRegistry serviceRegistry(){
        return new ServiceRegistryImpl(servers);
    }

    public String getServers() {
        return servers;
    }

    public void setServers(String servers) {
        this.servers = servers;
    }
}

(4)由于我们的服务是以javaweb的形式发布的。所以在每个应用初始化时,去完成接口的注册是一个好时机。因此我们新加一个类去实现ServletContextListener,并把这个类交给spring来管理。

package demo.msa.listener;

import java.util.Map;

import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import demo.msa.framework.registry.ServiceRegistry;

@Component
public class WebListener implements ServletContextListener{
    private static Logger logger = LoggerFactory
            .getLogger(WebListener.class);
    @Value("${server.address}")
    private String serverAddress;
    @Value("${server.port}")
    private int serverPort;
    @Value("${server.name}")
    private String serverName;

    @Autowired
    private ServiceRegistry serviceRegistry;
    @Override
    public void contextDestroyed(ServletContextEvent event) {
        // TODO Auto-generated method stub

    }

    @Override
    public void contextInitialized(ServletContextEvent event) {
        //获取请求映射
        ServletContext servletContext=event.getServletContext();
        ApplicationContext applicationContext=WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext);
        RequestMappingHandlerMapping mapping=applicationContext.getBean(RequestMappingHandlerMapping.class);
        Map infoMap=mapping.getHandlerMethods();
        for(RequestMappingInfo info:infoMap.keySet()){
            String serviceName=info.getName();
            logger.debug("-----------"+serviceName);
            if(null!=serviceName){
                serviceRegistry.register(serviceName,String.format("%s:%d/%s", serverAddress,serverPort,serverName) );
            }
        }
    }

}

启动msa-sample-api应用程序,我们可以在控制台看到以下信息,说明服务已经注册成功了。
SpringBoot ,zookeeper 服务的注册与发现_第5张图片

服务发现

服务发现策略

一个服务注册到zookeeper可以有多个地址节点,当有多个地址节点的时候,可以通过轮询或者hash的方式来获取地址节点

服务发现

新建一个工程msa-sample-web,这个工程相当于这个架构中的前端部分。因此我们需要一个html页面来发送一个ajax请求。
具体实现如下:
引入相关jar:

    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-webartifactId>
      dependency>
    <dependency>
    <dependency>
        <groupId>org.apache.zookeepergroupId>
        <artifactId>zookeeperartifactId>
        <version>3.4.6version>
    dependency>

页面实现:


<html>
  <head>
    <title>TestZk.htmltitle>

    <meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
    <meta http-equiv="description" content="this is my page">
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">

    

  head>

  <body>
    This is my HTML page. <br>
    <div >
    <textarea id="console" value='1234' >
    textarea>div>
    <script type="text/javascript" src="js/jquery-1.6.2.min.js">script>
    <script type="text/javascript"> $(function(){ $.ajax({ method:'GET', dataType: 'JSONP', url:'hello', dataType:'text', headers:{ 'Service-Name':'HelloService'}, success: function (data,textStatus){ $('#console').text(data); window.location.href = 'http://'+data; }, error:function (XMLHttpRequest, textStatus, thrownError) { alert("error"); var result = XMLHttpRequest.responseText; alert(result); } }); }); script>
  body>
html>

web.xml配置

 <servlet>
        <servlet-name>helloservlet-name>
        <servlet-class>demo.msa.servlet.MsaDemoServletservlet-class>
        <load-on-startup>0load-on-startup>
    servlet>
    <servlet-mapping>
        <servlet-name>helloservlet-name>
        <url-pattern>/hellourl-pattern>
    servlet-mapping>

servlet实现

package demo.msa.servlet;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import demo.msa.help.ServiceHelp;
import demo.msa.help.ServiceHelpImpl;

public class MsaDemoServlet extends HttpServlet{

    /** * */
    private static final long serialVersionUID = 1L;

    private String addressData="";
    private static Logger logger = LoggerFactory
            .getLogger(MsaDemoServlet.class);
    protected void doGet(HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException {
        try {
        String serviceName=request.getHeader("Service-Name");
        ServiceHelp help=new ServiceHelpImpl("127.0.0.1:2181");
        addressData=help.getData(serviceName);
        } catch (Exception e) {
            logger.error("get address node data error",e);
        }

        response.setContentType("application/json; charset=UTF-8");
        response.setCharacterEncoding("UTF-8");
        response.getWriter().print(addressData);
    }

    protected void doPost(HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException {
        doGet(request,response);
    }

}

定义一个接口获取地址节点的数据

package demo.msa.help;

public interface ServiceHelp {
    public String getData(String serviceName);

}

实现该接口的类:

package demo.msa.help;

import java.util.List;
import java.util.concurrent.CountDownLatch;

import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

@Component
public class ServiceHelpImpl implements Watcher,ServiceHelp{
    private static final int SESSION_TIMEOUT = 5000;
    private static final String REGISTRY_PATH = "/registry";
    private static CountDownLatch latch = new CountDownLatch(1);
    private static Logger logger = LoggerFactory
            .getLogger(ServiceHelpImpl.class);
    private ZooKeeper zk;
    public ServiceHelpImpl() {
        logger.debug("初始化类");
    }

    public ServiceHelpImpl(String zkServers) {
        try {
            zk = new ZooKeeper(zkServers, SESSION_TIMEOUT, this);
            latch.await();
            logger.debug("connected to zookeeper");

        } catch (Exception e) {
            logger.error("create zookeeper client failuer", e);
        }
    }
    @Override
    public String getData(String serviceName) {
        try {
        String servicePath=REGISTRY_PATH+"/"+serviceName;
        logger.debug("service_path is :"+servicePath);
        List childPath;

            childPath = zk.getChildren(servicePath, false);

        if(childPath.size()==0){
            logger.error("%s address node is not exsited",serviceName);
            throw(new Exception("地址未找到"));
        }
        String addressPath=servicePath+"/";
        if(childPath.size()==1){
            addressPath+=childPath.get(0);
        }
        if(childPath.size()>1){
            addressPath+=childPath.get((int)(Math.random()*childPath.size()));
        }
        logger.debug("address node is "+addressPath);
        byte[] data=zk.getData(addressPath, null,null);     
        return new String(data);
        } catch (Exception e) {
            logger.error("get data failure",e);
        }
        return "";
    }

    @Override
    public void process(WatchedEvent event) {
        if (event.getState() == Event.KeeperState.SyncConnected) {
            latch.countDown();
        }

    }

}

部署到tomcat,访问该页面,最后展示效果如下:
SpringBoot ,zookeeper 服务的注册与发现_第6张图片

有些不明白的地方,可以下载我的源码来参考。由于是初学,有些没有考虑到的地方,欢迎大家给我提意见。

你可能感兴趣的:(编程)