Dubbo源码学习系列(三) 整合zookeeper注册中心并提供watcher机制

292 阅读3分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

 1. 使用zookeeper注册中心替换map

       在上一篇文章中,用到了RemoteRegistryMap替换了zookeeper实现服务的缓存,此方式的缺点是需要通过文件来共享map,如果服务提供者接口列表发生变化,那么也不能及时修改,那么我们怎么使用zookeeper注册中心实现服务注册和发现功能?

        zookeeper 本身是一个文件系统,可以在将服务的url相关信息挂载到指定节点目录下,然后通过watch机制监听服务提供者接口服务是否发生变化,如果有变化,那么watcher将会收到zookeeper的通知。

2. 整合curator

      curator是对zookeeper client的二次封装,在提供客户端实例上提供了更好的便利, 主要包含framework(核心)、client(提供客户端支持)、recipes(集群管理, leader选举、分布式锁)这三个模块。

<dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-framework</artifactId>
            <version>4.1.0</version>
        </dependency>

        <!-- curator 客户端-->
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-client</artifactId>
            <version>4.1.0</version>
        </dependency>

        <!-- leader选举、分布式锁-->
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-recipes</artifactId>
            <version>4.1.0</version>
        </dependency>

        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <version>3.4.13</version>
        </dependency>

         添加上述依赖后,我们就通过curator创建一个zookeeper client, windows环境下可以换为localhost, 在静态块中初始化连接,可以在启动时加载。

static CuratorFramework client; 
static {
        client = CuratorFrameworkFactory.newClient("127.0.0.1:2181", new RetryNTimes(3, 1000));
        client.start();
    }

       主要包含2个方法,registry()和get()方法。

       注:  在用get()方法时,我们可以利用缓存思想,通过判断接口的Class名(全限定名) 是否在map里,如果在那么从map取,如果不在那么再从zookeeper注册中心拉取,然后缓存到map里。

package com.example.dubbo.registry;

import com.alibaba.fastjson.JSONObject;
import com.example.dubbo.framework.URL;
import com.example.dubbo.provider.api.UserInterface;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.RetryNTimes;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.Watcher;

import java.util.*;
import java.util.concurrent.atomic.AtomicReference;

/**
 * @author bingbing
 * @date 2021/4/30 0030 18:39
 * 注册接口名,服务地址列表
 */
public class ZookeeperRegistry {


    // 在静态块里初始化客户端
    static CuratorFramework client;

    private static String dubboServicePath = "/dubbo/service/";

    static {
        client = CuratorFrameworkFactory.newClient("localhost:2181", new RetryNTimes(3, 1000));
        client.start();
    }

    // 用map做本地缓存
    private static Map<String, List<URL>> listMap = new HashMap<>();

    // 使用临时节点挂载URL相关信息
    public static void registry(String interfaceName, URL url) {
        // 1. 将url信息挂载到zookeeper目录下
        try {
            String result = client.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).forPath(String.format(dubboServicePath + "%s/%s", interfaceName, JSONObject.toJSONString(url)));
            System.out.println(result);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }




    public static List<URL> get(String interfaceName) {
        // 1. 如果本地map里有,那么就从map中取数据
        if (listMap.containsKey(interfaceName)) {
            // 监听机制
//            watch();
            return listMap.get(interfaceName);
        }
        List<URL> urlList = new ArrayList<>();
        // 2. 如果本地map没有,那么从zookeeper注册中心拿数据
        try {
            List<String> strs = client.getChildren().forPath(String.format(dubboServicePath + "%s", interfaceName));
            for (String s : strs) {
                // 反序列化url对象
                urlList.add(JSONObject.parseObject(s, URL.class));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        // 3. 将zookeeper里的信息缓存到本地map里
        listMap.put(interfaceName, urlList);
        return urlList;
    }

    public static void main(String[] args) {
        System.out.println(client);
        // 1. 注册
        String interfaceName = "UserService";
        List<URL> urlList = Arrays.asList(new URL("localhost", 8080), new URL("localhost", 9000));
        for (URL url : urlList) {
            ZookeeperRegistry.registry(interfaceName, url);
        }
        List<URL> urlRes = ZookeeperRegistry.get(interfaceName);
        System.out.println(urlRes);
    }

}

       每次从zookeeper获取出来的数据需要先反序列化一下,然后再返回,因为在存入zookeeper前用的是Json格式的数据

       接下来将zookeeper接入!

修改消费者

       将RemoteRegistry更换为zookeeperRegistry

package com.example.dubbo.framework;

import com.example.dubbo.protocol.http.HttpClient;
import com.example.dubbo.provider.LocalRegistry;
import com.example.dubbo.registry.RemoteRegistry;
import com.example.dubbo.registry.ZookeeperRegistry;
import org.springframework.cglib.proxy.InvocationHandler;
import org.springframework.cglib.proxy.Proxy;

import java.lang.reflect.Method;
import java.util.List;
import java.util.Random;

/**
 * @author bingbing
 * @date 2021/4/29 0029 10:46
 */
public class ProxyFactory<T> {


    public static <T> T getProxy(final Class interfaceclass) {

        return (T) Proxy.newProxyInstance(interfaceclass.getClassLoader(), new Class[]{interfaceclass}, new InvocationHandler() {
            @Override
            public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
                //  可以自己定义mock, 通过Mock实现服务降级

                // 2. 获取服务地址列表, 从zookeeper里拉取到新的服务列表
//                List<URL> lists = RemoteRegistry.get(interfaceclass.getName());
                List<URL> lists = ZookeeperRegistry.get(interfaceclass.getName());
                // 3. 负载均衡策略选择一个url进行使用。
                URL url = LoadBalance.random(lists);
                // 3. 发送http 请求
                HttpClient client = new HttpClient();
                Invocation invocation = new Invocation(interfaceclass.getName(), method.getName(), objects, method.getParameterTypes());
                Object obj = client.send(url, invocation);
                return obj;
            }
        });
    }

}

      修改服务提供者

       同样在启动时,用ZookeeperRegistry 替换RemoteRegistry

package com.example.dubbo.provider;

import com.example.dubbo.framework.URL;
import com.example.dubbo.protocol.http.HttpServer;
import com.example.dubbo.provider.api.UserInterface;
import com.example.dubbo.provider.impl.UserInterfaceImpl;
import com.example.dubbo.registry.RemoteRegistry;
import com.example.dubbo.registry.ZookeeperRegistry;


/**
 * @author bingbing
 * @date 2021/4/29 0029 10:18
 */
public class ProviderApplication {

    public static void main(String[] args) {
        // 1. 指定容器启动地址和端口
        URL url = new URL("127.0.0.1", 9090);
        // 2. 本地注册
        LocalRegistry.regist(UserInterface.class.getName(), UserInterfaceImpl.class);
        // 3. 远程注册
//        RemoteRegistry.registry(UserInterface.class.getName(), url);
        ZookeeperRegistry.registry(UserInterface.class.getName(), url);
        // 4. 启动容器,处理请求
        HttpServer server = new HttpServer();
        server.start(url);
    }
}

 重写启动服务后,观察调用结果:

  

       至此,zookeeper整合成功!

3. 提供watcher机制

      在每次从本地map中获取数据时,先从看一下Zookeeper有没有给我消费者这边发消息,如果有的话,那么我就先更新Map,然后再从本地map中返回。

package com.example.dubbo.registry;

import com.alibaba.fastjson.JSONObject;
import com.example.dubbo.framework.URL;
import com.example.dubbo.provider.api.UserInterface;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.RetryNTimes;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.Watcher;

import java.util.*;
import java.util.concurrent.atomic.AtomicReference;

/**
 * @author bingbing
 * @date 2021/4/30 0030 18:39
 * 注册接口名,服务地址列表
 */
public class ZookeeperRegistry {


    // 在静态块里初始化客户端
    static CuratorFramework client;

    private static String dubboServicePath = "/dubbo/service/";

    static {
        client = CuratorFrameworkFactory.newClient("localhost:2181", new RetryNTimes(3, 1000));
        client.start();
    }

    // 用map做本地缓存
    private static Map<String, List<URL>> listMap = new HashMap<>();

    // 使用临时节点挂载URL相关信息
    public static void registry(String interfaceName, URL url) {
        // 1. 将url信息挂载到zookeeper目录下
        try {
            String result = client.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).forPath(String.format(dubboServicePath + "%s/%s", interfaceName, JSONObject.toJSONString(url)));
            System.out.println(result);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    /**
     * 通过watch机制监听zookeeper
     *
     * @return
     */
    public static void watch() {
        //将interfaceName的服务地址列表更新zookeeper
        AtomicReference<Boolean> watchFlag= new AtomicReference<>(false);
        Watcher watcher = (WatchedEvent -> {
            watchFlag.set(true);
            System.out.println("监听到事件!" + WatchedEvent);
        });
        if (watchFlag.get()==false) {
            return;
        }
        // 1.获取到所有的接口节点,刷新本地map

        try {
            List<String> strs = client.getChildren().forPath(dubboServicePath);
            for (String s : strs) {
                // 先获取到类Class
                String userInterfaceName = JSONObject.parseObject(s, UserInterface.class).getClass().getName();
                List<URL> urlList = new ArrayList<>();
                if (listMap.containsKey(userInterfaceName)) {
                    List<String> urlStrs = client.getChildren().forPath(dubboServicePath + userInterfaceName);
                    for (String urlStr : urlStrs) {
                        urlList.add(JSONObject.parseObject(urlStr, URL.class));
                    }
                    //更新map
                    listMap.put(userInterfaceName, urlList);
                }
            }
        } catch (Exception e) {

        }
    }


    public static List<URL> get(String interfaceName) {
        // 1. 如果本地map里有,那么就从map中取数据
        if (listMap.containsKey(interfaceName)) {
            // 监听机制
            watch();
            return listMap.get(interfaceName);
        }
        List<URL> urlList = new ArrayList<>();
        // 2. 如果本地map没有,那么从zookeeper注册中心拿数据
        try {
            List<String> strs = client.getChildren().forPath(String.format(dubboServicePath + "%s", interfaceName));
            for (String s : strs) {
                // 反序列化url对象
                urlList.add(JSONObject.parseObject(s, URL.class));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        // 3. 将zookeeper里的信息缓存到本地map里
        listMap.put(interfaceName, urlList);
        return urlList;
    }

    public static void main(String[] args) {
        System.out.println(client);
        // 1. 注册
        String interfaceName = "UserService";
        List<URL> urlList = Arrays.asList(new URL("localhost", 8080), new URL("localhost", 9000));
        for (URL url : urlList) {
            ZookeeperRegistry.registry(interfaceName, url);
        }
        List<URL> urlRes = ZookeeperRegistry.get(interfaceName);
        System.out.println(urlRes);
    }

}

\