分布式(一) 搞定服务注册与发现

背景

最近在作分布式相关的工做,因为人手不够只能我一我的来怼;看着这段时间的加班表想一想就是够惨的。java

不过其中也有遇到的很多有意思的事情从此再拿来分享,今天重点来讨论服务的注册与发现node

分布式带来的问题

个人业务比较简单,只是须要知道如今有哪些服务实例可供使用就能够了(并非作远程调用,只须要拿到信息便可)。git

要实现这一功能最简单的方式能够在应用中配置全部的服务节点,这样每次在使用时只须要经过某种算法从配置列表中选择一个就能够了。github

但这样会有一个很是严重的问题:算法

因为应用须要根据应用负载状况来灵活的调整服务节点的数量,这样个人配置就不能写死。apache

否则就会出现要么新增的节点没有访问或者是已经 down 掉的节点却有请求,这样确定是不行的。api

每每要解决这类分布式问题都须要一个公共的区域来保存这些信息,好比是否能够利用 Redis?缓存

每一个节点启动以后都向 Redis 注册信息,关闭时也删除数据。服务器

其实就是存放节点的 ip + port,而后在须要知道服务节点信息时候只须要去 Redis 中获取便可。网络

以下图所示:

但这样会致使每次使用时都须要频繁的去查询 Redis,为了不这个问题咱们能够在每次查询以后在本地缓存一份最新的数据。这样优先从本地获取确实能够提升效率。

但一样又会出现新的问题,若是服务提供者的节点新增或者删除消费者这边根本就不知道状况。

要解决这个问题最早想到的应该就是利用定时任务按期去更新服务列表。

以上的方案确定不完美,而且不优雅。主要有如下几点:

  • 基于定时任务会致使不少无效的更新。
  • 定时任务存在周期性,无法作到实时,这样就可能存在请求异常。
  • 若是服务被强行 kill,无法及时清除 Redis,这样这个看似可用的服务将永远不可用!

因此咱们须要一个更加靠谱的解决方案,这样的场景其实和 Dubbo 很是相似。

用过的同窗确定对这张图不陌生。

引用自 Dubbo 官网

其中有一块很是核心的内容(红框出)就是服务的注册与发现。

一般来讲消费者是须要知道服务提供者的网络地址(ip + port)才能发起远程调用,这块内容和我上面的需求其实很是相似。

而 Dubbo 则是利用 Zookeeper 来解决问题。

Zookeeper 能作什么

在具体讨论怎么实现以前先看看 Zookeeper 的几个重要特性。

Zookeeper 实现了一个相似于文件系统的树状结构:

这些节点被称为 znode(名字叫什么不重要),其中每一个节点均可以存放必定的数据。

最主要的是 znode 有四种类型:

  • 永久节点(除非手动删除,节点永远存在)
  • 永久有序节点(按照建立顺序会为每一个节点末尾带上一个序号如:root-1
  • 瞬时节点(建立客户端与 Zookeeper 保持链接时节点存在,断开时则删除并会有相应的通知)
  • 瞬时有序节点(在瞬时节点的基础上加上了顺序)

考虑下上文使用 Redis 最大的一个问题是什么?

其实就是不能实时的更新服务提供者的信息。

那利用 Zookeeper 是怎么实现的?

主要看第三个特性:瞬时节点

Zookeeper 是一个典型的观察者模式。

  • 因为瞬时节点的特色,咱们的消费者能够订阅瞬时节点的父节点。
  • 当新增、删除节点时全部的瞬时节点也会自动更新。
  • 更新时会给订阅者发起通知告诉最新的节点信息。

这样咱们就能够实时获取服务节点的信息,同时也只须要在第一次获取列表时缓存到本地;也不须要频繁和 Zookeeper 产生交互,只用等待通知更新便可。

而且无论应用什么缘由节点 down 掉后也会在 Zookeeper 中删除该信息。

效果演示

这样实现方式就变为这样。

为此我新建了一个应用来进行演示:

github.com/crossoverJi…

就是一个简单的 SpringBoot 应用,只是作了几件事情。

  • 应用启动时新开一个线程用于向 Zookeeper 注册服务。
  • 同时监听一个节点用于更新本地服务列表。
  • 提供一个接口用于返回一个可有的服务节点。

我在本地启动了两个应用分别是:127.0.0.1:8083,127.0.0.1:8084。来看看效果图。

两个应用启动完成:


当前 Zookeeper 的可视化树状结构:


当想知道全部的服务节点信息时:


想要获取一个可用的服务节点时:

这里只是采起了简单的轮询。


当 down 掉一个节点时:应用会收到通知更新本地缓存。同时 Zookeeper 中的节点会自动删除。


再次获取最新节点时:


当节点恢复时天然也能获取到最新信息。本地缓存也会及时更新。

编码实现

实现起来倒也比较简单,主要就是 ZKClient 的 api 使用。

贴几段比较核心的吧。

注册

启动注册 Zookeeper。

主要逻辑都在这个线程中。

  • 首先建立父节点。如上图的 Zookeeper 节点所示;须要先建立 /route 根节点,建立的时候会判断是否已经存在。
  • 接着须要判断是否须要将本身注册到 Zookeeper 中,由于有些节点只是用于服务发现,他自身是不须要承担业务功能(是我本身项目的需求)。
  • 将当前应用的所在 ip 以及端口注册上去,同时须要监听根节点 /route ,这样才能在其余服务上下线时候得到通知。

根据本地缓存

监听到服务变化

public void subscribeEvent(String path) {
        zkClient.subscribeChildChanges(path, new IZkChildListener() {
            @Override
            public void handleChildChange(String parentPath, List<String> currentChilds) throws Exception {
                logger.info("清除/更新本地缓存 parentPath=【{}】,currentChilds=【{}】", parentPath,currentChilds.toString());

                //更新全部缓存/先删除 再新增
                serverCache.updateCache(currentChilds) ;
            }
        });


    }
复制代码

能够看到这里是更新了本地缓存,该缓存采用了 Guava 提供的 Cache,感兴趣的能够查看以前的源码分析

/** * 更新全部缓存/先删除 再新增 * * @param currentChilds */
    public void updateCache(List<String> currentChilds) {
        cache.invalidateAll();
        for (String currentChild : currentChilds) {
            String key = currentChild.split("-")[1];
            addCache(key);
        }
    }
复制代码

客户端负载

同时在客户端提供了一个负载算法。

其实就是一个轮询的实现:

/** * 选取服务器 * * @return */
    public String selectServer() {
        List<String> all = getAll();
        if (all.size() == 0) {
            throw new RuntimeException("路由列表为空");
        }
        Long position = index.incrementAndGet() % all.size();
        if (position < 0) {
            position = 0L;
        }

        return all.get(position.intValue());
    }
复制代码

固然这里能够扩展出更多的如权重、随机、LRU 等算法。

Zookeeper 其余优点及问题

Zookeeper 天然是一个很棒的分布式协调工具,利用它的特性还能够有其余做用。

  • 数据变动发送通知这一特性能够实现统一配置中心,不再须要在每一个服务中单独维护配置。
  • 利用瞬时有序节点还能够实现分布式锁。

在实现注册、发现这一需求时,Zookeeper 其实并非最优选。

因为 Zookeeper 在 CAP 理论中选择了 CP(一致性、分区容错性),当 Zookeeper 集群有半数节点不可用时是不能获取到任何数据的。

对于一致性来讲天然没啥问题,但在注册、发现的场景下更加推荐 Eureka,已经在 SpringCloud 中获得验证。具体就不在本文讨论了。

但鉴于个人使用场景来讲 Zookeeper 已经可以胜任。

总结

本文全部完整代码都托管在 GitHub。

github.com/crossoverJi…

一个看似简单的注册、发现功能实现了,但分布式应用远远不止这些。

因为网络隔离以后带来的一系列问题还须要咱们用其余方式一一完善;后续会继续更新分布式相关内容,感兴趣的朋友不妨持续关注。

你的点赞与转发是最大的支持。

相关文章
相关标签/搜索