SpringBoot集成Zookeeper,實現(xiàn)服務(wù)的注冊與發(fā)現(xiàn)踩坑

本文主要分為兩部分,第一部分是Zookeeper的安裝;第二部分才是SpringBoot和Zookeeper的集成。

第一部分Zookeeper的安裝;

系統(tǒng)環(huán)境win10,Zookeeper安裝版本3.4.14;

首先,下載Zookeeper的壓縮包,從https://mirrors.cnnic.cn/apache/zookeeper/zookeeper-3.4.14/下載壓縮包,這里使用3.4.14版本,下載后解壓;3.3.14版本已經(jīng)找不到了,在這里只能下載3.4.14版本的壓縮包;

第二步,創(chuàng)建數(shù)據(jù)目錄和日志目錄;來到解壓的文件夾下,建立一個data文件夾和一個log文件夾,data文件夾和log文件夾下分別建立zoo-1、zoo-2、zoo-3三個文件夾;

第三步,創(chuàng)建myid文件;在data的zoo-1、zoo-2、zoo-3文件夾下,分別創(chuàng)建一個myid文件,沒有后綴;zoo-1下的myid文件內(nèi)容為1,zoo-2下的myid文件內(nèi)容為2,zoo-3下的myid文件內(nèi)容為3;

第四步,創(chuàng)建配置文件;在conf文件夾下,將zoo_sample.cfg復(fù)制三份,分別命名為zoo-1.cfg、zoo-2.cfg、zoo-3.cfg;并為配置文件創(chuàng)建配置內(nèi)容,這里只貼出第一個配置文件的內(nèi)容,第二個和第三個與第一個不同的地方就是dataDir、dataLogDir、clientPort的不同,其余的配置都是一樣的。

tickTime=4000
initLimit=10
syncLimit=5
dataDir=C:/zookeeper-3.4.14/data/zoo-1/
dataLogDir=C:/zookeeper-3.4.14/log/zoo-1/

clientPort=2181
server.1=127.0.0.1:2888:3888
server.2=127.0.0.1:2889:3889
server.3=127.0.0.1:2890:3890

第五步,創(chuàng)建cmd快捷啟動服務(wù)的窗口;打開bin文件夾,將zkServer.cmd復(fù)制成三份,名字分別為zkServer-1.cmd、zkServer-2.cmd、zkServer-3.cmd,每個配置里分別加上對應(yīng)的配置;

set ZOOCFG=C:\zookeeper-3.4.14\conf\zoo-1.cfg

最后,啟動偽集群服務(wù); 以管理員的身份打開cmd窗口,先進(jìn)入到bin目錄下,然后再輸入命令啟動服務(wù),由于是三個服務(wù),所以需要啟動三個cmd窗口進(jìn)行啟動;

zkServer-1.cmd

當(dāng)有看到下面的消息時,代表服務(wù)已經(jīng)啟動成功了。

C:\zookeeper-3.4.14\bin>zkServer-3.cmd
C:\zookeeper-3.4.14\bin>call "C:\Program Files\Java\jdk1.8.0_181"\bin\java "-Dzookeeper.log.dir=C:\zookeeper-3.4.14\bin\.." "-Dzookeeper.root.logger=INFO,CONSOLE" -cp "C:\zookeeper-3.4.14\bin\..\build\classes;C:\zookeeper-3.4.14\bin\..\build\lib\*;C:\zookeeper-3.4.14\bin\..\*;C:\zookeeper-3.4.14\bin\..\lib\*;C:\zookeeper-3.4.14\bin\..\conf" org.apache.zookeeper.server.quorum.QuorumPeerMain "C:\zookeeper-3.4.14\conf\zoo-3.cfg"
2019-10-30 20:01:59,025 [myid:] - INFO  [main:QuorumPeerConfig@136] - Reading configuration from: C:\zookeeper-3.4.14\conf\zoo-3.cfg
2019-10-30 20:01:59,052 [myid:] - INFO  [main:QuorumPeer$QuorumServer@185] - Resolved hostname: 127.0.0.1 to address: /127.0.0.1
2019-10-30 20:01:59,054 [myid:] - INFO  [main:QuorumPeer$QuorumServer@185] - Resolved hostname: 127.0.0.1 to address: /127.0.0.1
2019-10-30 20:01:59,058 [myid:] - INFO  [main:QuorumPeer$QuorumServer@185] - Resolved hostname: 127.0.0.1 to address: /127.0.0.1
2019-10-30 20:01:59,063 [myid:] - INFO  [main:QuorumPeerConfig@398] - Defaulting to majority quorums
2019-10-30 20:01:59,070 [myid:3] - INFO  [main:DatadirCleanupManager@78] - autopurge.snapRetainCount set to 3
2019-10-30 20:01:59,071 [myid:3] - INFO  [main:DatadirCleanupManager@79] - autopurge.purgeInterval set to 0
2019-10-30 20:01:59,073 [myid:3] - INFO  [main:DatadirCleanupManager@101] - Purge task is not scheduled.
2019-10-30 20:01:59,270 [myid:3] - INFO  [main:QuorumPeerMain@130] - Starting quorum peer
2019-10-30 20:02:00,172 [myid:3] - INFO  [main:ServerCnxnFactory@117] - Using org.apache.zookeeper.server.NIOServerCnxnFactory as server connection factory
2019-10-30 20:02:00,174 [myid:3] - INFO  [main:NIOServerCnxnFactory@89] - binding to port 0.0.0.0/0.0.0.0:2183
......

注意事項:

  1. 配置文件里的文件路徑一定要用/,而不要用\,結(jié)束符也要用/,否則啟動會失??;
  2. cmd文件里的ZOOCFG=,不要寫成ZOOCFG%=,否則也會啟動失?。?/li>
  3. 這三個服務(wù)是是偽集群服務(wù),當(dāng)啟動一個時,不免有報錯信息,三個都啟動就恢復(fù)正常。
  4. Zookeeper節(jié)點數(shù)必須是奇數(shù),所以這里創(chuàng)建了最少的節(jié)點數(shù),選出其中的一個為leader也就是主節(jié)點。

第二部分SpringBoot和Zookeeper的集成
首先,在pom中引入一個必須的架包;

<dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
        </dependency>
        <!-- Apache下的Zookeeper架包-->
        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <version>3.5.5</version>
        </dependency>

第二步,創(chuàng)建服務(wù)注冊的配置文件;

import java.io.IOException;
import java.util.concurrent.CountDownLatch;

import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 基于Zookeeper的服務(wù)注冊
 * @author 程就人生
 * @date 2019年10月30日
 */
public class ServiceRegistry {

    private static Logger log = LoggerFactory.getLogger(ServiceRegistry.class);
    
    private CountDownLatch latch = new CountDownLatch(1);
    //這個可以放到配置文件里,對應(yīng)Zookeeper已經(jīng)啟動的ip+port
    private String registryAddress = "127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183";
    
    private String nodePath = "/app";
    
    private String cnodePath = "/chatting";
    
    private int timeout = 3000;

    public ServiceRegistry() {
    }

    public void register(String data) {
        if (data != null) {
            ZooKeeper zk = connectServer();
            if (zk != null) {
                createNode(zk, data);
            }
        }
    }

    /**
     * 連接 zookeeper 服務(wù)器
     * @return
     */
    private ZooKeeper connectServer() {
        ZooKeeper zk = null;
        try {
            zk = new ZooKeeper(registryAddress, timeout, new Watcher() {
                @Override
                public void process(WatchedEvent event) {
                    if (event.getState() == Event.KeeperState.SyncConnected) {
                        latch.countDown();
                        log.info("Watcher.........");
                    }
                }
            });
            latch.await();
        } catch (IOException | InterruptedException e) {
            log.error("", e);
            e.printStackTrace();
        }
        return zk;
    }

    /**
     * 創(chuàng)建節(jié)點
     * @param zk
     * @param data
     */
    private void createNode(ZooKeeper zk, String data) {
        try {
            //父節(jié)點不存在時進(jìn)行創(chuàng)建
            Stat stat = zk.exists(nodePath, true);
            if(stat == null){
                zk.create(nodePath, null,  ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
            }
            //這里的第一個參數(shù)和3.4.13版本的zookeeper不一樣,如果不加父目錄,直接就是使用/app/會報錯,所以智能加父目錄
            //CreateMode.EPHEMERAL_SEQUENTIAL,創(chuàng)建臨時順序節(jié)點,客戶端會話結(jié)束后,節(jié)點將會被刪除
            String createPath = zk.create(nodePath+cnodePath, data.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
            log.info("create zookeeper node ({} =&gt; {} =&gt; {})", data, createPath);
        } catch (KeeperException | InterruptedException e) {
            log.info("", e);
            e.printStackTrace();
        }
    }
}

第三步,創(chuàng)建服務(wù)發(fā)現(xiàn)的配置文件;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ThreadLocalRandom;

import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 基于Zookeeper的服務(wù)發(fā)現(xiàn)
 * @author 程就人生
 * @date 2019年10月30日
 */
public class ServiceDiscovery {
    
    private static Logger log = LoggerFactory.getLogger(ServiceDiscovery.class);
    
    private CountDownLatch latch = new CountDownLatch(1);
    
    private volatile List<String> serviceAddressList = new ArrayList<>();
    //這個可以放到配置文件里,對應(yīng)Zookeeper已經(jīng)啟動的ip+port
    private String registryAddress = "127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183";
    
    private String nodePath = "/app";
    
    private int timeout = 3000;
    //注冊中心的地址
    public ServiceDiscovery() { 
        ZooKeeper zk = connectServer(); 
        if (zk != null) {
            watchNode(zk); 
        }
    }
    
    /**
     * 通過服務(wù)發(fā)現(xiàn),獲取服務(wù)提供方的地址
     * @return
     */
    public String discover() { 
        String data = null;
        int size = serviceAddressList.size(); 
        if (size > 0) { 
            if (size == 1) {
                //只有一個服務(wù)提供方
                data = serviceAddressList.get(0);
                log.info("unique service address :{}", data); 
            } else { 
                //使用隨機分配法,簡單的負(fù)載均衡法
                data = serviceAddressList.get(ThreadLocalRandom.current().nextInt(size));
                log.info("choose an address : {}",data); 
            } 
        } 
        return data; 
    }

    /**
     * 連接 zookeeper
     * @return
     */
    private ZooKeeper connectServer() {
        ZooKeeper zk = null;
        try {
            zk = new ZooKeeper(registryAddress, timeout, new Watcher() {
                @Override
                public void process(WatchedEvent event) {
                    if (event.getState() == Watcher.Event.KeeperState.SyncConnected) {
                        latch.countDown();
                    }
                }
            });
            latch.await();
        } catch (IOException | InterruptedException e) {
            log.error("", e);
            e.printStackTrace();
        }
        return zk;
    }

    /**
     * 獲取服務(wù)地址列表
     * @param zk
     */
    private void watchNode(final ZooKeeper zk) { 
        try {
            //獲取子節(jié)點列表 
            List<String> nodeList = zk.getChildren(nodePath,new Watcher() { 
                @Override 
                public void process(WatchedEvent event) {
                    if (event.getType() == Event.EventType.NodeChildrenChanged) {
                        // 發(fā)生子節(jié)點變化時再次調(diào)用此方法更新服務(wù)地址 
                        watchNode(zk); 
                    }
                } 
            }); 
            List<String> dataList = new ArrayList<>(); 
            for (String node :nodeList) { 
                byte[] bytes = zk.getData(nodePath + "/" + node, false, null);
                dataList.add(new String(bytes)); 
            }
            log.info("node data: {}", dataList);
            this.serviceAddressList = dataList;
        }catch(KeeperException|InterruptedException e){
            log.error("", e);
            e.printStackTrace();
        }
    }
    
    public static void main(String[] agro){     
        //服務(wù)發(fā)現(xiàn)
        ServiceDiscovery serviceDiscovery = new ServiceDiscovery();
        serviceDiscovery.discover();
    }
}

第三步,修改啟動類,在啟動類啟動時進(jìn)行注冊;

import java.net.InetAddress;
import java.net.UnknownHostException;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;

import com.example.demo.netty.NettyServer;
import com.example.demo.zookeeper.ServiceRegistry;
/**
 * 實現(xiàn)CommandLineRunner接口,把要執(zhí)行的代碼放入到run里,即可在啟動后執(zhí)行
 * @author 程就人生
 * @date 2019年10月30日
 */
@SpringBootApplication
public class SpringbootZookeeperApplication  implements CommandLineRunner{
    
    @Value("${im.server.port}")
    private int port;

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

    @Override
    public void run(String... args) throws Exception {
        try {
            NettyServer server = new NettyServer();
            server.start(port);
            //服務(wù)注冊
            ServiceRegistry serviceRegistry = new ServiceRegistry();
            String ip = InetAddress.getLocalHost().getHostAddress();
            serviceRegistry.register(ip+":"+port);
                        
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }
    }
}

最后,測試;啟動服務(wù)啟動類,注冊已經(jīng)啟動的服務(wù);最后在服務(wù)發(fā)現(xiàn)類中運行main,就可以看到返回的ip及端口號地址;

啟動服務(wù),注冊服務(wù)

發(fā)現(xiàn)并返回服務(wù)

在這里多啟動了幾個端口,從服務(wù)發(fā)現(xiàn)里可以看到,啟動的端口都被注冊到了zookeeper服務(wù)器上,這樣就可以根據(jù)返回的ip+port進(jìn)行調(diào)用具體的服務(wù)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容