手写RPC框架<三>引入注册中心接入Nacos

351 阅读4分钟

一、服务注册设计

image.png

image.png

二、服务注册与引用

public interface Registry {

    /**
     * 注册服务
     *
     * @param url
     */
    void register(URL url);

    /**
     * 订阅服务
     *
     * @param url
     */
    void subscribe(URL url,NotifyListener listener);

}

2.1 构建服务变更监听器和注册目录

/**
 * 服务变更监听器
 * @author jinzhou
 * @data 2023/4/11 22:03
 */
public interface NotifyListener {

    void notify(List<URL> urls);
}

服务注册表

/**
 * 注册表
 *
 * 存储所有可用的调用接口
 * @author jinzhou
 */
public class RegistryDirectory <T> implements NotifyListener {

    /**
     * 注册中心提供者集合
     */
    protected volatile Map<URL, Invoker<T>> urlInvokerMap = new ConcurrentHashMap<>();

    /**
     * 类型
     */
    private final Class<T> serviceType;

    /**
     * 资源
     */
    private final URL url;

    /**
     * 注册中心
     */
    private Registry registry;

    /**
     * 注册协议
     */
    protected RpcProtocol protocol;

    public RegistryDirectory(Registry registry,RpcProtocol protocol,Class<T> serviceType, URL url) {
        this.registry = registry;
        this.serviceType = serviceType;
        this.url = url;
        this.protocol = protocol;
    }

    /**
     * 订阅注册中心
     * @param url
     */
    public void subscribe(URL url) {
        //订阅服务
        registry.subscribe(url,this);
    }

    /**
     * 当收到服务更改通知时触发
     * @param urls
     */
    @Override
    public synchronized void notify(List<URL> urls) {
        toInvokers(urls);
    }

    /**
     * 将注册中的URL转化为调用器
     * @param urls
     * @return
     */
    private Map<URL, Invoker<T>> toInvokers(List<URL> urls) {
        //说明没有可用服务
        if (CollectionUtil.isEmpty(urls)){
            urlInvokerMap.clear();
            return urlInvokerMap;
        }
        //旧集合
        List<URL> oldInvokerList = new ArrayList<>(urlInvokerMap.keySet());

        //待移除集合
        List<URL> removeUrlList = new ArrayList<>();
        //添加集合
        List<URL> newUrlList = new ArrayList<>();
        //找出旧集合中多余的
        for (URL oldUrl : oldInvokerList) {
            if (!urls.contains(oldUrl)) {
                removeUrlList.add(oldUrl);
            }
        }
        for (URL newUrls : urls) {
            if (!oldInvokerList.contains(newUrls)) {
                newUrlList.add(newUrls);
            }
        }
        for (URL removeUrl : removeUrlList) {
            urlInvokerMap.remove(removeUrl);
        }
        //新增注册中心提供者
        for (URL newUrl : newUrlList) {
            urlInvokerMap.put(newUrl,protocol.refer(serviceType,newUrl));
        }
        return urlInvokerMap;
    }

    /**
     * 获取注册中心调用者集合
     * @param invocation
     * @return
     */
    public List<Invoker<T>> doList(Invocation invocation){
        return new ArrayList<>(urlInvokerMap.values());
    }

    public URL getUrl() {
        return url;
    }
}

2.2 构建注册协议:RegistryProtocol

负责将本地服务注册到远程的注册中心,以及获取注册中心服务同时还能够监听注册中心的变化,为服务的动态调用提供支持

/**
 * 用来定义和暴露远程服务的协议的,为上层提供了统一的调用方式。具体来说,
 * Protocol类负责将服务暴露到网络上并监听客户端请求,并将请求转发给对应的服务实现进行处理,
 * 最后将处理结果返回给客户端。
 *
 * @author jinzhou
 */
public interface Protocol {

    /**
     * 暴露调用服务
     * @param invoker
     * @param <T>
     * @return
     * @throws RpcException
     */
    <T>void export(Invoker<T> invoker) throws RpcException;

    /**
     * 获取调用对象
     * @param type
     * @param url
     * @param <T>
     * @return
     * @throws RpcException
     */
    <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;
}

注册中心协议

/**
 * 注册中心协议
 *
 * @author jinzhou
 */
public class RegistryProtocol implements Protocol {

    private RpcProtocol rpcProtocol;

    private NacosRegistryFactory registryFactory = new NacosRegistryFactory();

    public RegistryProtocol(RpcProtocol rpcProtocol) {
        this.rpcProtocol = rpcProtocol;
    }

    /**
     * 向注册中心发送请求,将自己的地址、服务名称和版本等信息注册到注册中心。
     *
     * @param originInvoker
     * @param <T>
     * @return
     * @throws RpcException
     */
    @Override
    public <T> void export(Invoker<T> originInvoker) throws RpcException {
        //暴露服务到本地
        URL registryUrl = originInvoker.getUrl();
        String interfaceName= registryUrl.getInterfaceName();

        //创建本地服务url
        URL providerUrl = new URL();
        ProtocolConfig protocolConfig = RpcManager.RPC_MANAGER.getProtocolConfig();
        providerUrl.setHost(protocolConfig.getIp());
        providerUrl.setPort(protocolConfig.getPort());
        providerUrl.setInterfaceName(interfaceName);
        providerUrl.setProtocol(ProtocolEnum.RPC.getProtocolName());
        providerUrl.setParameters(registryUrl.getParameters());
        doLocalExport(originInvoker,providerUrl);

        //获取注册表
        Registry registry = registryFactory.getRegistry(registryUrl);
        //注册服务到注册中心
        registry.register(providerUrl);
    }

    /**
     * 向注册中心发送请求,获取可调用的服务信息
     *
     * @param type
     * @param url
     * @param <T>
     * @return
     */
    @Override
    public <T> Invoker<T> refer(Class<T> type, URL url) {
        Registry registry = registryFactory.getRegistry(url);
        return getInvoker(registry, type, url);
    }


    /**
     * 获取执行器
     * @param registry 注册中心
     * @param type 请求类型
     * @param url 请求资源
     * @param <T>
     * @return
     */
    private <T> Invoker<T> getInvoker(Registry registry, Class<T> type, URL url) {
        //创建注册表(存储从注册中心获取的服务)
        RegistryDirectory<T> directory = createDirectory(registry, type, url);
        //从注册中心获取服务
        directory.subscribe(url);
        return new FailoverClusterInvoker<T>(directory);
    }


    /**
     * 创建注册表
     *
     * @param type 类型
     * @param url  url
     * @param <T>
     * @return
     */
    private <T> RegistryDirectory<T> createDirectory(Registry registry, Class<T> type, URL url) {
        return new RegistryDirectory<>(registry, this.rpcProtocol, type, url);
    }


    /**
     * 本地暴露
     *
     * @param originInvoker
     */
    private <T> void doLocalExport(Invoker<T> originInvoker,URL providerUrl) {
        InvokerWrapper<T> invokerWrapper = new InvokerWrapper(originInvoker,providerUrl);
        rpcProtocol.export(invokerWrapper);
    }

}

2.3构建Nacos服务管理类:NacosNamingService

NacosNamingService 是Nacos服务的核心管理类,提供了服务管理的相关功能,例如:

  1. 服务注册:当一个服务启动时,它可以向NacosNamingService注册自己的实例信息(IP地址、端口号、健康状态等)。
  2. 服务发现:当一个服务需要调用另一个服务时,它可以通过NacosNamingService查询对应服务的实例信息,以便进行调用。
  3. 服务订阅:当一个服务需要获取某个服务的最新实例信息时,它可以向NacosNamingService订阅该服务,以便在有新实例注册或注销时及时获得通知。

我们如果想要完成在Nacos上的对服务的管理就需要先构建出NacosNamingService

/**
 * @author jinzhou
 */
public class NacosNamingServiceUtils {

    private static final Logger logger = LoggerFactory.getLogger(NacosNamingServiceUtils.class);

    /**
     * 创建Nacos服务管理器
     * @param serverAddr 注册中心地址
     * @param username 注册中心用户名
     * @param password 注册中心密码
     * @return
     */
    public static NamingService createNamingService(String serverAddr,String username,String password) {
        Properties nacosProperties = buildNacosProperties(serverAddr,username,password);
        NamingService namingService;
        try {
            namingService = NacosFactory.createNamingService(nacosProperties);
        } catch (NacosException e) {
            if (logger.isErrorEnabled()) {
                logger.error(e.getErrMsg(), e);
            }
            throw new IllegalStateException(e);
        }
        return namingService;
    }

    /**
     * 构建Nacos参数集合
     * @param serverAddr 注册中心地址
     * @param username 注册中心用户名
     * @param password 注册中心密码
     * @return
     */
    private static Properties buildNacosProperties(String serverAddr,String username,String password) {
        Properties properties = new Properties();
        properties.put(SERVER_ADDR, serverAddr);
        properties.put(USERNAME,username);
        properties.put(PASSWORD,password);
        return properties;
    }
}

2.4 Nacos服务注册器

public class NacosRegistry implements Registry {

    private final Logger logger = LogProvider.getLogger(getClass());

    private final NamingService namingService;

    public NacosRegistry(NamingService namingService) {
        this.namingService = namingService;
    }

    /**
     * 注册服务
     *
     * @param url
     */
    @Override
    public void register(URL url) {
        final String serviceName = getServiceName(url);
        final Instance instance = createInstance(url);
        execute(namingService -> namingService.registerInstance(serviceName,
                Constants.DEFAULT_GROUP, instance));
    }

    /**
     * 获取服务
     *
     * @param url
     * @param listener
     */
    @Override
    public void subscribe(URL url, NotifyListener listener) {
        final String serviceName = getServiceName(url);
        execute(namingService -> {
            List<Instance> instances = namingService.getAllInstances(serviceName,
                    Constants.DEFAULT_GROUP);

            List<URL> urls = buildURLs(instances);
            //通知订阅者
            listener.notify(urls);
            //监听实例
            subscribeEventListener(serviceName, url, listener);
        });

    }

    /**
     * 监听服务变更
     * @param serviceName
     * @param url
     * @param listener
     * @throws NacosException
     */
    private void subscribeEventListener(String serviceName, final URL url, final NotifyListener listener)
            throws NacosException {
        EventListener eventListener = event -> {
            if (event instanceof NamingEvent) {
                NamingEvent e = (NamingEvent) event;
                List<Instance> instances = e.getInstances();
                notifySubscriber(url, listener, instances);
            }
        };
        namingService.subscribe(serviceName,
                Constants.DEFAULT_GROUP,
                eventListener);
    }

    private void notifySubscriber(URL url, NotifyListener listener, Collection<Instance> instances) {
        List<Instance> enabledInstances = new LinkedList<>(instances);
        if (enabledInstances.size() > 0) {
            //  过滤出可用实例
            enabledInstances = enabledInstances.stream().filter(Instance::isEnabled).collect(Collectors.toList());
        }
        List<URL> urls = buildURLs(enabledInstances);
        listener.notify(urls);

    }

    /**
     * 获取服务名称
     *
     * @param url
     * @return
     */
    private String getServiceName(URL url) {
        return url.getServiceKey();
    }

    /**
     * 创建注册实例
     *
     * @param url
     * @return
     */
    private Instance createInstance(URL url) {
        HashMap<String, String> newParamsMap = MapUtil.copyHashMap(url.getParameters());
        newParamsMap.put(INTERFACE_KEY, url.getInterfaceName());

        String ip = url.getHost();
        int port = url.getPort();
        Instance instance = new Instance();
        instance.setIp(ip);
        instance.setPort(port);
        instance.setMetadata(newParamsMap);
        return instance;
    }

    /**
     * 将实例装换为URL
     *
     * @param instances
     * @return
     */
    private List<URL> buildURLs(Collection<Instance> instances) {
        List<URL> urls = new LinkedList<>();
        if (instances != null && !instances.isEmpty()) {
            for (Instance instance : instances) {
                URL url = buildURL(instance);
                urls.add(url);
            }
        }
        return urls;
    }

    private URL buildURL(Instance instance) {
        Map<String, String> metadata = instance.getMetadata();
        URL url = new URL();
        url.setHost(instance.getIp());
        url.setPort(instance.getPort());
        url.setInterfaceName(metadata.get(INTERFACE_KEY));
        url.setParameters(metadata);
        return url;
    }

    /**
     * 执行NamingService任务异常统一处理
     *
     * @param callback
     */
    private void execute(TaskCallback<NamingService> callback) {
        try {
            callback.callback(namingService);
        } catch (NacosException e) {
            if (logger.isErrorEnabled()) {
                logger.error(e.getErrMsg(), e);
            }
        } catch (Throwable throwable) {
            logger.error(throwable.getMessage(), throwable);
        }
    }

}

Nacos服务注册构建工厂

public class NacosRegistryFactory {

    private final Map<String, Registry> REGISTRIES = new HashMap<>();

    private final ReentrantLock LOCK = new ReentrantLock();

    /**
     * 获取注册中心
     * @param registryUrl
     * @return
     */
    public Registry getRegistry(URL registryUrl) {
        String serverAddr = registryUrl.getParameter(URLParameterConstants.ADDRESS);
        String username = registryUrl.getParameter(URLParameterConstants.USERNAME);
        String password = registryUrl.getParameter(URLParameterConstants.PASSWORD);
        Registry registry = REGISTRIES.get(serverAddr);
        if (registry == null){
            LOCK.lock();
            try {
                registry = REGISTRIES.get(serverAddr);
                if (registry == null){
                    registry = createRegistry(serverAddr,username,password);
                    if (registry == null) {
                        throw new IllegalStateException("Can not create registry :" + serverAddr);
                    }
                }
            }finally {
                LOCK.unlock();
            }
        }
        return registry;
    }

    /**
     * 创建注册中心
     * @param serverAddr 注册中心地址
     * @param username  用户名
     * @param password  密码
     * @return
     */
    public Registry createRegistry(String serverAddr, String username, String password) {
        NamingService namingService = NacosNamingServiceUtils.createNamingService(serverAddr,username,password);
        return new NacosRegistry(namingService);
    }
}