zookeeper 结合 springboot实现服务注册与发现,负载均衡。

       zookeeper简介:ZooKeeper致力于提供一个高性能、高可用,且具备严格的顺序访问控制能力的分布式协调服务,是雅虎公司创建,是GoogleChubby一个开源的实现,也是HadoopHbase的重要组件。

      zookeeper作为款高性能,高可用的工具,它可以实现多种多样的功能,如负载均衡,master选举,集群管理,配置管理,分布式锁等等。

      今天我们就来针对zookeeper工具结合着springboot来实现服务的注册与发现的案例,类似于eureka。dubbo中的服务注册就是用zookeeper实现的。

      要实现服务的注册和发现,我们必须要构建多个服务,这里就订单和产品为例。创建两个服务。

      1、打开idea创建两个maven module  ,order  和 product

       

     2、在父maven的pom文件中引入 springboot  和 zookeeper的依赖,如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.nginx.demo</groupId>
    <artifactId>nginx_instance</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>order</module>
        <module>product</module>
    </modules>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.2.RELEASE</version>
        <relativePath/>
    </parent>


    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <version>3.4.12</version>
        </dependency>
        <dependency>
            <groupId>com.101tec</groupId>
            <artifactId>zkclient</artifactId>
            <version>0.10</version>
        </dependency>

        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-framework</artifactId>
            <version>4.0.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-recipes</artifactId>
            <version>4.0.0</version>
        </dependency>

    </dependencies>


    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
          
        </plugins>
    </build>
</project>

     3、产品模块,添加application.properties文件

   

   

     4、在产品模块中新建Register

public class Register {

    private static final String SERVER_PATH = "/product";

    private static final String ZK_ADDRESS = "192.168.0.101:2181";

    private static final int ZK_TIMEOUT = 20000;

    /**
     * 注册
     * @param address  地址
     */
    public void register(String address){
        try {
            ZooKeeper zooKeeper = new ZooKeeper(ZK_ADDRESS, ZK_TIMEOUT, WatchEvent -> {
            });
            Stat stat = zooKeeper.exists(SERVER_PATH, false);
            if (stat == null) {
                zooKeeper.create(SERVER_PATH, "".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
            }
            String path = address;
            //创建短暂的可排序的子节点
            zooKeeper.create(SERVER_PATH+"/instance", path.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
        }catch (Exception e){
            e.printStackTrace();
        }
    }



}

    这个类,主要是将当前产品服务的域名端口保存到zookeeper节点中。如果zookeeper没有安装的话这里补充一下:

   下载路径: https://zookeeper.apache.org/releases.html
        解压后,打开conf文件夹,将zoo_sample.cfg文件复制一份重命名为zoo.cfg
        然后点击bin目录下的zkServer.cmd 运行即可。zookeeper默认连接端口2181

    5、产品模块,添加Application.class文件,springboot启动类

@SpringBootApplication
@RestController
public class Application {


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

    @RequestMapping(value = "/product/id",method = RequestMethod.GET)
    public String  get(HttpServletRequest request){
        return "访问 product  服务端口:"+ request.getLocalPort();
    }

    @Bean
    public Register register(){
        Register register = new Register();
        register.register(getAddressAndPort());
        return register;
    }


    private String getAddressAndPort(){
        try {
            Properties properties = new Properties();
            InputStream stream = Application.class.getClassLoader().getResourceAsStream("application.properties");
            properties.load(stream);

            Object port = properties.get("server.port");
            return "127.0.0.1:" + port;
        }catch (Exception e){
            throw new RuntimeException();
        }
    }
}

    6、在订单模块order,添加ZookListener.class

public class ZookListener {
    private static final String SERVER_PATH = "/product";

    private static final String ZK_ADDRESS = "192.168.0.101:2181";

    private static final int ZK_TIMEOUT = 20000;

    private ZooKeeper zooKeeper;

    private List<String> paths = new LinkedList<>();


    public void init(){
        try {
            zooKeeper = new ZooKeeper(ZK_ADDRESS, ZK_TIMEOUT, (WatchEvent) -> {
                //监听该节点的变化,如果节点出现变化,则重新获取节点下的ip和端口
                if(WatchEvent.getType() == Watcher.Event.EventType.NodeChildrenChanged &&
                        WatchEvent.getPath().equals(SERVER_PATH)){
                    getChilds();
                }

            });
            getChilds();
        }catch (Exception e){
            e.printStackTrace();
        }
    }


    private  void  getChilds(){
        List<String> ips =new LinkedList<>();
        try {
            //添加监听
            List<String> childs = this.zooKeeper.getChildren(SERVER_PATH, true);
            for(String child : childs){
                byte[] obj = zooKeeper.getData(SERVER_PATH+"/"+child,false,null);
                String path = new String(obj,"utf-8");
                ips.add(path);
            }
            this.paths = ips;
        }catch (Exception e){
            e.printStackTrace();
        }

    }


    public String  getPath(){
        if(paths.isEmpty()){
            return null;
        }
        //这里我们随机获取一个ip端口使用
        int index =  new Random().nextInt(paths.size());
        return paths.get(index);
    }
    
}

       该类的主要功能:获取产品服务保存在zookeeper中的多个服务ip和端口,并且监听zookeeper节点的变化,获取后并以各种算法获取其中一个ip端口使用,这里我们使用随机获取其中一个。

    7、 订单服务添加application.properties ,这里端口我们直接用8080

   

    8、订单服务添加Application.class文件,springboot启动类

@SpringBootApplication
@RestController
public class Application {

    @Resource
    private RestTemplate restTemplate;

    @Resource
    private ZookListener listener;

    @Bean
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }

    @Bean
    public ZookListener zookListener(){
        ZookListener listener = new ZookListener();
        listener.init();
        return listener;
    }

    @RequestMapping(value = "/order/id",method = RequestMethod.GET)
    public String get(){
        //从zookeeper中获取调用的ip
        String path = listener.getPath();
        if(path == null){
            return "对不起,产品暂时停止服务!";
        }
        return restTemplate.getForObject("http://"+listener.getPath()+"/product/id",String.class);
    }


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

   到此为止,所有的代码编写完毕,下面我们来进行测试。

   首先是服务的注册:这里是产品服务的注册,我们需要将产品服务中的application.properties中的port修改成几个不同的端口。

   然后每个端口都启动起来。

    启动之后我们会发现zookeeper中已经将我们设置的各个端口都保存起来了。

    这是zookeeper提供的一个java客户端连接工具,这里可以看到我们针对产品服务,启动了4个不同端口的集群服务。

 现在我们打开浏览器,在地址栏输入订单order  服务的controller测试接口

 

   刷新一次:

    

 

  如果我们将idea中4个产品服务关掉一个后,会发现,zookeeper中针对于这个产品服务的节点数据也会被自动删除掉。如下

 

  查看zookeeper节点数据:

  

  本身是4个服务,现在关闭了一个,zookeeper保存的暂时节点数据也会自动删除,当然这个删除并不会马上触发,大概在10秒钟左右。

   以上。

猜你喜欢

转载自blog.csdn.net/zhanglei082319/article/details/88930872