All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.jingshouyan.jrpc.client.discover.ZkDiscover Maven / Gradle / Ivy

There is a newer version: 0.8.3
Show newest version
package com.github.jingshouyan.jrpc.client.discover;

import com.github.jingshouyan.jrpc.base.bean.Router;
import com.github.jingshouyan.jrpc.base.bean.ServerInfo;
import com.github.jingshouyan.jrpc.base.code.Code;
import com.github.jingshouyan.jrpc.base.exception.JrpcException;
import com.github.jingshouyan.jrpc.base.util.json.JsonUtil;
import com.github.jingshouyan.jrpc.base.util.zk.ZkUtil;
import com.github.jingshouyan.jrpc.client.config.ClientConfig;
import com.github.jingshouyan.jrpc.client.discover.selector.Selector;
import com.github.jingshouyan.jrpc.client.discover.selector.impl.SimpleSelector;
import com.github.jingshouyan.jrpc.client.node.Node;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.cache.ChildData;
import org.apache.curator.framework.recipes.cache.TreeCache;
import org.apache.curator.framework.recipes.cache.TreeCacheEvent;

import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

/**
 * @author jingshouyan
 * #date 2018/10/25 20:56
 */
@Slf4j
public class ZkDiscover {

    private static final long LATCH_TIMEOUT = 3000;

    private final Map> map = Maps.newConcurrentMap();
    private final CountDownLatch latch = new CountDownLatch(1);
    private String zkHost;
    private String zkRoot;
    GenericObjectPoolConfig poolConfig;
    private Selector selector = new SimpleSelector();

    private final List listeners = Lists.newArrayList();

    public void addListener(ServerInfoListener listener) {
        listeners.add(listener);
    }


    private CuratorFramework client;

    @SneakyThrows
    public ZkDiscover(ClientConfig config) {
        zkHost = config.getZkHost();
        zkRoot = config.getZkRoot();
        poolConfig = new GenericObjectPoolConfig();
        poolConfig.setMinIdle(config.getPoolMinIdle());
        poolConfig.setMaxIdle(config.getPoolMaxIdle());
        poolConfig.setMaxTotal(config.getPoolMaxTotal());
        poolConfig.setTestWhileIdle(true);
        poolConfig.setTimeBetweenEvictionRunsMillis(10000);

        client = ZkUtil.getClient(zkHost);
        listen();
        boolean init = latch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS);
        if (!init) {
            throw new RuntimeException("zk init timeout in " + LATCH_TIMEOUT + "ms");
        }
    }

    public Map> nodeMap() {
        return map;
    }

    public Node getNode(Router router) {
        try {
            List infos = map.get(router.getServer());
            if (infos == null || infos.isEmpty()) {
                throw new JrpcException(Code.SERVER_NOT_FOUND);
            }
            if (router.getInstance() != null) {
                return infos.stream().filter(i -> i.getServerInfo().getInstance().equals(router.getInstance()))
                        .findFirst().orElseThrow(() -> new JrpcException(Code.INSTANCE_NOT_FUND));
            }
            if (router.getVersion() != null) {
                infos = selector.versionFilter(infos, router.getVersion());
                if (infos.isEmpty()) {
                    throw new JrpcException(Code.SERVER_NOT_FOUND);
                }
            }
            return selector.pickOne(infos);
        } catch (JrpcException e) {
            throw e;
        } catch (Exception e) {
            throw new JrpcException(Code.GET_SERVER_ADDRESS_TIMEOUT, e);
        }
    }


    private void listen() {
        try {
            TreeCache cache = new TreeCache(client, zkRoot);
            cache.getListenable().addListener((cl, event) -> {
                try {
                    String type = event.getType().name();
                    String path = null;
                    String data = null;
                    ChildData childData = event.getData();
                    if (childData != null) {
                        path = childData.getPath();
                        data = byte2String(childData.getData());
                    }
                    ServerInfo info = toInfo(data);
                    log.debug("tree changed messageType:[{}] path:[{}] data:[{}]", type, path, data);
                    if (null != info || event.getType() == TreeCacheEvent.Type.INITIALIZED || event.getType() == TreeCacheEvent.Type.NODE_REMOVED) {
                        if (event.getType() == TreeCacheEvent.Type.NODE_REMOVED && null == info) {
                            info = new ServerInfo();
                            String[] strings = path.split("/");
                            String instance = strings[strings.length - 1];
                            String name = strings[strings.length - 2];
                            info.setInstance(instance);
                            info.setName(name);
                        }
                        handle(event.getType(), info);
                    }
                } catch (Exception e) {
                    log.error("zk listener error.", e);
                }
            });
            cache.start();
        } catch (Exception e) {
            log.error("zk client error.", e);
        }
    }

    private void handle(TreeCacheEvent.Type type, ServerInfo info) {
        switch (type) {
            case NODE_ADDED:
                add(info);
                triggerEvent(DiscoverEvent.ADD, info);
                break;
            case NODE_UPDATED:
                update(info);
                triggerEvent(DiscoverEvent.UPDATE, info);
                break;
            case NODE_REMOVED:
                remove(info);
                triggerEvent(DiscoverEvent.REMOVE, info);
                break;
            case INITIALIZED:
                latch.countDown();
                break;
            default:
        }
    }

    private void triggerEvent(DiscoverEvent event, ServerInfo info) {
        listeners.parallelStream().forEach(listener -> listener.handle(event, info));
    }

    private void add(ServerInfo info) {
        map.compute(info.getName(), (name, nodes) -> {
            if (nodes == null) {
                nodes = Lists.newArrayList();
            }
            Node node = new Node(info, poolConfig);
            nodes.add(node);
            return nodes;
        });
    }

    private void update(ServerInfo info) {
        List list = map.get(info.getName());
        if (null != list) {
            list.stream().filter(i -> i.getServerInfo().getInstance().equals(info.getInstance()))
                    .forEach(i -> i.getServerInfo().update(info));
        }
    }

    private void remove(ServerInfo info) {
        List list = map.get(info.getName());
        if (null != list) {
            Optional optionalNode = list.stream()
                    .filter(node -> node.getServerInfo().getInstance().equals(info.getInstance()))
                    .findFirst();
            if (optionalNode.isPresent()) {
                Node node = optionalNode.get();
                node.close();
                list.remove(node);
            }
        }
    }


    private static ServerInfo toInfo(String data) {
        ServerInfo info = null;
        if (null != data && !"".equals(data)) {
            try {
                info = JsonUtil.toBean(data, ServerInfo.class);
            } catch (Exception e) {
                log.warn("data:[{}] convert to ServiceInfo error", data, e);
            }
        }
        return info;
    }

    @SneakyThrows
    private static String byte2String(byte[] b) {
        if (b == null) {
            return null;
        }
        return new String(b, StandardCharsets.UTF_8);
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy