用zookeeper实现分布式框架的服务注册与发现功能

本篇文章主要讲述怎么通过zookeeper作为注册中心实现分布式系统中服务注册与发现的具体实现(具体的细节可能因不同的框架而不同,但原理其实都是一样的)

本文章设计的主要思路:

利用zookeeper临时顺序节点的性质,为每个应用服务在zookeeper上创建临时顺序的节点就(这个节点成为服务节点),而实现注册功能;服务消费者去相应服务节点下取出服务节点的信息,从而实现服务发现功能。具体实现如下:

zookeeper的环境搭建在这就不累述了大家可以在网上轻松的找到相关的文档

本实例采用zookeeper-java客户端原生api设计(大家额可以使用封装过的开源框架zkClient,apache 的Curator框架)

1 搭建项目(采用的开发工具是intellij IDEA)-- 用什么开发工具都无所谓啦

①我们采用spring boot项目快速搭建项目

②建立spring boot项目后添加zookeeper的maven依赖,创建zookeeper--java客户端

这里我们使用的3.4.8版本的,同学也可以随意但建议使用3.3以上的版本,因为3.3.6以后版本变更较大。

<!-- https://mvnrepository.com/artifact/org.apache.zookeeper/zookeeper -->
<dependency>
   <groupId>org.apache.zookeeper</groupId>
   <artifactId>zookeeper</artifactId>
   <version>3.4.8</version>
</dependency>
 
 
@Service
public class Zook {

    public static ZooKeeper zooKeeper ;
    //获得配置资源
    @Autowired
    Environment env;

    private static CountDownLatch countDownLatch = new CountDownLatch(1);

    public ZooKeeper create() {
        String connectString = env.getProperty("zookeeper.address");
        String outtime = env.getProperty("zookeeper.sessionouttime");
        try {
            Watcher watcher = new Watcher() {
                @Override
                public void process(WatchedEvent watchedEvent) {
                    System.out.println("创建zk会话成功");
                    countDownLatch.countDown();
                }
            };
            //默认6s
            if (outtime.isEmpty()) {
                outtime = "6000";
            }
            ZooKeeper zooKeeper = new ZooKeeper(connectString, Integer.parseInt(outtime), watcher);
            //zookeeper会话建立的特点是:触发一次defaultWatcher(就是注册时传入的watcher)才能真正的被建立
            countDownLatch.await();
            return zooKeeper;
        } catch (Exception e) {
            return null;
        }
    }
}

③在zookeeper上创建一个所有服务节点的根节点(例如:公司英文简写)这样方便统一路径,当然这个节点一定是持久节点;

④创建服务节点(这里是持久节点),而实际保存服务信息的节点是服务节点下子节点,子节点是临时顺序的节点(这一点与阿里的dubbo不同,如果同学门感兴趣可以关注我的博客,后期我会编写dubbo与Eureka对比的相关文章,里面将会详细介绍dubbo知识,着急了解的这方面知识的同学可以给我留言)

@Service
public class ZookRegister {
    //获得配置资源
    @Autowired
    Environment env;

    //固定的根目录比如公司名
    final String fixedpath = "/xxs";

    @Value("spring.application.name")
    String servername;
    @Autowired
    Zook zook;

    //spring容器初始化ZookRegister的实例时执行
    @PostConstruct
    public void register() throws Exception {
        String servername = env.getProperty("spring.application.name");
        String port = env.getProperty("server.port");
        String ip = env.getProperty("server.address");
        Zook.zooKeeper = zook.create();
        ZooKeeper zooKeeper = Zook.zooKeeper;
        Stat existsFixedpath = zooKeeper.exists(fixedpath, false);
        if (existsFixedpath == null) {
            //参数分别是创建的节点路径、节点的数据、权限(此处对所有用户开放)、节点的类型(此处是持久节点)
            zooKeeper.create(fixedpath, "".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
        }
        String svnode = fixedpath + "/" + servername;
        Stat existsSvnode = zooKeeper.exists(svnode, false);
        //create(String path, byte[] data, List<ACL> acl, CreateMode createMode)
        if (existsSvnode == null) {
            zooKeeper.create(svnode, "".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
        }
        if (ip == null || "".equals(null)) {
            //如果配置文件中没有指定服务ip获取本机ip
            ip = InetAddress.getLocalHost().getHostAddress();
        }
        if (port == null || "".equals(null)) {
            port = "8080";
        }
        String address = ip + ":" + port;
        //临时节点的前缀是服务名称,节点数据是服务address
        String svipmlNode = fixedpath + "/" + servername + "/" + servername;
        zooKeeper.create(svipmlNode, address.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
    }

}

 
 

配置文件:

server.port=8081
zookeeper.address=172.0.0.1
zookeeper.sessionouttime=4000
spring.application.name=test


服务注册得工作至此完成

服务发现工作:

@Service
public class GetServer {
    //本地缓存服务列表
    private static Map<String, List<String>> servermap;

    public static Watcher myWatcher = new Watcher() {
        @Override
        public void process(WatchedEvent watchedEvent) {
            //如果服务节点数据发生变化则清空本地缓存(这种做法有点欠佳)
            if (watchedEvent.getType().equals(Event.EventType.NodeChildrenChanged)) {
                servermap = null;
            }
        }
    };

    private static List<String> getNodeList(String serverName) throws KeeperException, InterruptedException {
        if (servermap == null) {
            servermap = new HashMap<>();
        }
        ZooKeeper zooKeeper = Zook.zooKeeper;
        Stat exists = zooKeeper.exists("/xxs/" + serverName, false);
        //判断是否存在该服务
        if (exists == null) return null;
        List<String> serverList = servermap.get("serverName");
        if (serverList != null && serverList.size() > 0) {
            return serverList;
        }
        List<String> children = zooKeeper.getChildren("/xxs/" + serverName, myWatcher);
        List<String> list = new ArrayList<>();
        for (String s : children) {
            byte[] data = zooKeeper.getData("/xxs/" + serverName + "/" + s, myWatcher, null);
            list.add(new String(data));
        }
        servermap.put(serverName, list);
        return list;
    }

    public static String getServerinfo(String serverName)  {
        try {
            List<String> nodeList = getNodeList(serverName);
            if (nodeList == null){
                return null;
            }
            //这里使用得随机负载策略,如需需要自己可以实现其他得负载策略
            String s = nodeList.get((int) (Math.random() * nodeList.size()));
            return s;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}

服务发现调用getServerinfo()方法传入servername即可获得服务提供者得ip+port,然后去条用服务即可;

至此简单得服务注册与发现工作已完成;而一个框架得功能远不止这一点功能,比如还需要容错机制,负载的策略等功能;这些功能需要自己去完善;(有时间的话我会更新容错机制的实现)




猜你喜欢

转载自blog.csdn.net/qq_40197576/article/details/80256441