用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以后版本变更较大。

org.apache.zookeeper zookeeper 3.4.8 @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=localhost:2181
zookeeper.sessionouttime=4000
spring.application.name=test

服务注册得工作至此完成

服务发现工作:

@Service
public class GetServer {

//本地缓存服务列表
private static Map<String, List<String>> servermap;

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

private List<String> getNodeList(String serverName) throws KeeperException, InterruptedException {
    if (servermap == null) {
        servermap = new HashMap<>();
    }
    ZooKeeper zooKeeper = Zook.zooKeeper;
    Stat exists = null;
    Watcher existsWatcher = new Watcher() {
        @Override
        public void process(WatchedEvent watchedEvent) {
            System.out.println("sssss");
        }
    };
    try {
        String s = "/xxs/" + serverName;
        exists = zooKeeper.exists(s, existsWatcher);
    } catch (Exception e) {
    }

    //判断是否存在该服务
    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 String getServerinfo(String serverName) {
    try {
        List<String> nodeList = getNodeList(serverName);
        if (nodeList == null|| nodeList.size()<1) {
            return null;
        }
        //这里使用得随机负载策略,如需需要自己可以实现其他得负载策略
        String s = nodeList.get((int) (Math.random() * nodeList.size()));
        return s;
    } catch (Exception e) {
        e.printStackTrace();
        return null;
    }
}

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

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

————————————————
版权声明:本文为CSDN博主「邢动动」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_40197576/article/details/80256441

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值