监听到服务变化
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。
https://github.com/crossoverJie/netty-action。
一个看似简单的注册、发现功能实现了,但分布式应用远远不止这些。
由于网络隔离之后带来的一系列问题还需要我们用其他方式一一完善;后续会继续更新分布式相关内容,感兴趣的朋友不妨持续关注。
你的点赞与转发是最大的支持。