我是靠谱客的博主 聪明花生,最近开发中收集的这篇文章主要介绍手写实现RPC框架(带注册中心),觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

基于上一篇博客:https://blog.csdn.net/Dongguabai/article/details/83624822完成的RPC框架进行改造,增加基于Curator实现的ZK注册中心。

 

可能这个图不太准确,但是大体意思就是服务端在注册中心中注册服务,客户端在注册中心获取服务地址进行调用,中间可能还会有一些LB等:

定义一个注册服务的顶层接口IRegistryCenter:

package dgb.nospring.myrpc.registry;

/**
 * 注册中心顶层接口
 * @author Dongguabai
 * @date 2018/11/1 19:05
 */
public interface IRegistryCenter {

    /**
     * 注册服务
     * @param serviceName 服务名称
     * @param serviceAddress 服务地址
     */
    void register(String serviceName,String serviceAddress);
}

实现类RegistryCenterImpl:

package dgb.nospring.myrpc.registry;

import lombok.extern.slf4j.Slf4j;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.CreateMode;

/**
 * 注册中心实现
 *
 * @author Dongguabai
 * @date 2018/11/1 19:10
 */
@Slf4j
public class RegistryCenterImpl implements IRegistryCenter {

    private CuratorFramework curatorFramework;

    {
        curatorFramework = CuratorFrameworkFactory.builder()
                .connectString(RegistryCenterConfig.CONNECTING_STR)
                .sessionTimeoutMs(RegistryCenterConfig.SESSION_TIMEOUT)
                .retryPolicy(new ExponentialBackoffRetry(1000, 10)).build();
        curatorFramework.start();
    }

    //注册相应服务
    @Override
    public void register(String serviceName, String serviceAddress) {
        String serviceNodePath = RegistryCenterConfig.NAMESPACE + "/" + serviceName;
        try {
            //如果serviceNodePath(/rpcNode/userService)不存在就创建
            if (curatorFramework.checkExists().forPath(serviceNodePath)==null){
                //持久化节点
                curatorFramework.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).forPath(serviceNodePath,RegistryCenterConfig.DEFAULT_VALUE);
            }
            //注册的服务的节点路径
            String addressPath = serviceNodePath+"/"+serviceAddress;
            //临时节点
            String rsNode = curatorFramework.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).forPath(addressPath, RegistryCenterConfig.DEFAULT_VALUE);
            log.info("服务注册成功:{}",rsNode);

        } catch (Exception e) {
            throw new RuntimeException("注册服务出现异常!",e);
        }
    }

}

注册中心的一些配置参数RegistryCenterConfig:

package dgb.nospring.myrpc.registry;

/**
 * @author Dongguabai
 * @date 2018/11/1 19:13
 */
public interface RegistryCenterConfig {

    /**
     * ZK地址int
     */
    String CONNECTING_STR = "192.168.220.136,192.168.220.137";

    int SESSION_TIMEOUT = 4000;

    /**
     * 注册中心namespace
     */
    String NAMESPACE = "/rpcNode";

    /**
     * value一般来说作用不大;一般主要是利用节点特性搞点事情
     */
    byte[] DEFAULT_VALUE = "0".getBytes();
}

为了方便,增加了一个服务发布的注解RpcAnnotation,在接口的实现类上标明这个注解表示对外发布这个接口:

package dgb.nospring.myrpc.registry;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author Dongguabai
 * @date 2018/11/2 8:54
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface RpcAnnotation {

    /**
     * 对外发布服务的接口
     *
     * @return
     */
    Class<?> value();

    /**
     * 版本,用来区分不同版本
     * @return
     */
    String version() default "";
}

这个版本号的作用在本次Demo中没有体现出来,不过其实使用也是很简单的,可以将版本号与ZK node地址中的serviceName拼接或者绑定起来,然后根据版本号+serviceName获取相应的服务调用地址。那么客户端在发现服务的时候也要传入相应的版本进去。

首先改造服务端,服务端要将服务发布到注册中心,RpcServer:

package dgb.nospring.myrpc;

import dgb.nospring.myrpc.registry.IRegistryCenter;
import dgb.nospring.myrpc.registry.RpcAnnotation;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @author Dongguabai
 * @date 2018/11/1 15:53
 */
@Slf4j
public class RpcServer {

    /**
     * 注册中心
     */
    private IRegistryCenter registryCenter;

    /**
     * 服务的发布地址
     */
    private String addressService;

    /**
     * 服务名称和服务对象之间的关系
     */
    private static final Map<String,Object> HANDLER_MAPPING = new HashMap<>();

    //不建议通过Executors创建线程池,这里为了方便
    private static final ExecutorService executor = Executors.newCachedThreadPool();

    /*public void publisher(final Object service, int port) {
        //启动一个服务监听
        try (ServerSocket serverSocket = new ServerSocket(port)) {
            while (true){
                //通过ServerSocket获取请求
                Socket socket = serverSocket.accept();
                executor.execute(new ProcessorHandler(socket,service));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }*/

    /**
     * 改造后的发布服务的方法
     */
    public void publisher() {
        //启动一个服务监听
        //获取端口
        int port = Integer.parseInt(addressService.split(":")[1]);
        try (ServerSocket serverSocket = new ServerSocket(port)) {
            //循环获取所有的接口Name
            HANDLER_MAPPING.keySet().forEach(interfaceName->{
                registryCenter.register(interfaceName,addressService);
                log.info("注册服务成功:【serviceName:{},address:{}】",interfaceName,addressService);
            });
            while (true){
                //通过ServerSocket获取请求
                Socket socket = serverSocket.accept();
                executor.execute(new ProcessorHandler(socket,HANDLER_MAPPING));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 绑定服务名称和服务对象
     * @param services
     */
    public void bind(Object... services){
        for (Object service : services) {
            //获取发布的服务接口
            RpcAnnotation rpcAnnotation = service.getClass().getAnnotation(RpcAnnotation.class);
            if (rpcAnnotation==null){
                continue;
            }
            //发布接口的class
            String serviceName = rpcAnnotation.value().getName();
            //将serviceName和service进行绑定
            HANDLER_MAPPING.put(serviceName,service);
        }
    }

    public RpcServer(IRegistryCenter registryCenter, String addressService) {
        this.registryCenter = registryCenter;
        this.addressService = addressService;
    }
}

改造任务处理类ProcessorHandler:

package dgb.nospring.myrpc;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.Socket;
import java.util.Map;

/**
 * 任务处理类
 *
 * @author Dongguabai
 * @date 2018/11/1 16:10
 */
public class ProcessorHandler implements Runnable {

    private Socket socket;
    /**
     * 服务端发布的服务
     */
    private Map<String,Object> handlerMap;

    /**
     * 通过构造传入Map
     * @param socket
     * @param handlerMap
     */
    public ProcessorHandler(Socket socket, Map<String, Object> handlerMap) {
        this.socket = socket;
        this.handlerMap = handlerMap;
    }

    //处理请求
    @Override
    public void run() {
        ObjectInputStream objectInputStream = null;
        try {
            objectInputStream = new ObjectInputStream(socket.getInputStream());
            //反序列化
            RpcRequest rpcRequest = (RpcRequest) objectInputStream.readObject();
            Object result = invoke(rpcRequest);
            //将结果返回给客户端
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
            objectOutputStream.writeObject(result);
            objectOutputStream.flush();
            objectInputStream.close();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (objectInputStream != null) {
                try {
                    objectInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 反射调用
     *
     * @param rpcRequest
     */
    /*private Object invoke(RpcRequest rpcRequest) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        System.out.println("服务端开始调用------");
        Object[] parameters = rpcRequest.getParameters();
        Class[] parameterTypes = new Class[parameters.length];
        for (int i = 0, length = parameters.length; i < length; i++) {
            parameterTypes[i] = parameters[i].getClass();
        }
        Method method = service.getClass().getMethod(rpcRequest.getMethodName(), parameterTypes);
        return method.invoke(service, parameters);
    }*/

    /**
     * 反射调用(之前通过Service进行反射调用,现在通过Map获取service)
     *
     * @param rpcRequest
     */
    private Object invoke(RpcRequest rpcRequest) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        System.out.println("服务端开始调用------");
        Object[] parameters = rpcRequest.getParameters();
        Class[] parameterTypes = new Class[parameters.length];
        for (int i = 0, length = parameters.length; i < length; i++) {
            parameterTypes[i] = parameters[i].getClass();
        }
        //从Map中获得Service(根据客户端请求的ServiceName,获得相应的服务),依旧是通过反射发起调用
        Object service = handlerMap.get(rpcRequest.getClassName());
        Method method = service.getClass().getMethod(rpcRequest.getMethodName(), parameterTypes);
        return method.invoke(service, parameters);
    }



}

测试服务发布Demo:

package dgb.nospring.myrpc.demo;

import dgb.nospring.myrpc.RpcServer;
import dgb.nospring.myrpc.registry.IRegistryCenter;
import dgb.nospring.myrpc.registry.RegistryCenterImpl;

/**
 * @author Dongguabai
 * @date 2018/11/1 18:07
 */
public class ServerDemo {

    public static void main(String[] args) {
        //之前发布服务
/*
        RpcServer rpcServer = new RpcServer();
        rpcServer.publisher(new HelloServiceImpl(),12345);
*/
        //改造后
        IRegistryCenter registryCenter = new RegistryCenterImpl();
        //这里为了方便,获取ip地址就直接写了
        RpcServer rpcServer = new RpcServer(registryCenter,"127.0.0.1:12345");
        //绑定服务
        rpcServer.bind(new HelloServiceImpl());
        rpcServer.publisher();
    }
}

运行结果:

在ZK客户端:

服务客户发布后,现在要解决的就是服务发现的问题。

定义一个顶层服务发现接口IServiceDiscovery:

package dgb.nospring.myrpc.registry;

/**
 * @author Dongguabai
 * @date 2018/11/2 9:55
 */
public interface IServiceDiscovery {

    /**
     * 根据接口名称发现服务调用地址
     * @param serviceName
     * @return
     */
    String discover(String serviceName);
}

实现类:

package dgb.nospring.myrpc.registry;

import dgb.nospring.myrpc.registry.loadbalance.LoadBalance;
import dgb.nospring.myrpc.registry.loadbalance.RandomLoadBanalce;
import lombok.extern.slf4j.Slf4j;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.cache.PathChildrenCache;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheListener;
import org.apache.curator.retry.ExponentialBackoffRetry;

import java.util.List;

/**
 * 服务发现实现类
 * @author Dongguabai
 * @date 2018/11/2 9:56
 */
@Slf4j
public class ServiceDiscoveryImpl implements IServiceDiscovery {

    /**
     * /rpcNode/dgb.nospring.myrpc.demo.IHelloService
     * 当前服务下所有的协议地址
     */
    private  List<String> repos;

    /**
     * ZK地址
     */
    private String zkAddress;

    private CuratorFramework curatorFramework;

    @Override
    public String discover(String serviceName) {
        //获取/rpcNode/dgb.nospring.myrpc.demo.IHelloService下所有协议地址
        String nodePath = RegistryCenterConfig.NAMESPACE+"/"+serviceName;
        try {
            repos = curatorFramework.getChildren().forPath(nodePath);
        } catch (Exception e) {
            throw new RuntimeException("服务发现获取子节点异常!",e);
        }
        //动态发现服务节点变化,需要注册监听
        registerWatcher(nodePath);

        //这里为了方便,直接使用随机负载
        LoadBalance loadBalance  = new RandomLoadBanalce();
        return loadBalance.selectHost(repos);
    }

    /**
     * 监听节点变化,给repos重新赋值
     * @param path
     */
    private void registerWatcher(String path){
        PathChildrenCache pathChildrenCache = new PathChildrenCache(curatorFramework,path,true);
        PathChildrenCacheListener pathChildrenCacheListener = new PathChildrenCacheListener() {
            @Override
            public void childEvent(CuratorFramework client, PathChildrenCacheEvent event) throws Exception {
                repos = curatorFramework.getChildren().forPath(path);
            }
        };
        pathChildrenCache.getListenable().addListener(pathChildrenCacheListener);
        try {
            pathChildrenCache.start();
        } catch (Exception e) {
            throw new RuntimeException("监听节点变化异常!",e);
        }
    }

    public ServiceDiscoveryImpl(String zkAddress) {
        this.zkAddress = zkAddress;
        curatorFramework = CuratorFrameworkFactory.builder()
                .connectString(RegistryCenterConfig.CONNECTING_STR)
                .sessionTimeoutMs(RegistryCenterConfig.SESSION_TIMEOUT)
                .retryPolicy(new ExponentialBackoffRetry(1000, 10)).build();
        curatorFramework.start();
    }
}

还有一套负载算法(这里简单实现了一个随机负载):

package dgb.nospring.myrpc.registry.loadbalance;

import java.util.List;

/**
 * 负载顶层接口
 * @author Dongguabai
 * @date 2018/11/2 10:11
 */
public interface LoadBalance {

    String selectHost(List<String> repos);
}
package dgb.nospring.myrpc.registry.loadbalance;

import org.apache.commons.collections.CollectionUtils;

import java.util.List;

/**
 * @author Dongguabai
 * @date 2018/11/2 10:15
 */
public abstract class AbstractLoadBanance implements LoadBalance{

    /**
     * 通过模板方法,做一些牵制操作
     * @param repos
     * @return
     */
    @Override
    public String selectHost(List<String> repos) {
        if(CollectionUtils.isEmpty(repos)){
            return null;
        }
        if(repos.size()==1){
            return repos.get(0);
        }
        return doSelect(repos);
    }

    /**
     * 实现具体的实现负载算法
     * @param repos
     * @return
     */
    protected  abstract String doSelect(List<String> repos);

}
package dgb.nospring.myrpc.registry.loadbalance;

import java.util.List;
import java.util.Random;

/**
 * 随机负载算法
 * @author Dongguabai
 * @date 2018/11/2 10:17
 */
public class RandomLoadBanalce extends AbstractLoadBanance{

    @Override
    protected String doSelect(List<String> repos) {
        return repos.get(new Random().nextInt(repos.size()));
    }
}

还有获取服务的RpcClientProxy需要进行改造,其实就是改了一个参数传递而已:

package dgb.nospring.myrpc;

import dgb.nospring.myrpc.registry.IServiceDiscovery;

import java.lang.reflect.Proxy;

/**
 * 客户端代理
 * @author Dongguabai
 * @date 2018/11/1 16:18
 */
public class RpcClientProxy {

    private IServiceDiscovery serviceDiscovery;

   /* public <T> T clientProxy(final Class<T> interfaceClass,final String host,final int port){
        return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(),new Class[]{interfaceClass},new RemoteInvocationHandler(host, port));
    }*/
    public <T> T clientProxy(final Class<T> interfaceClass){
        return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(),new Class[]{interfaceClass},new RemoteInvocationHandler(serviceDiscovery));
    }

    public RpcClientProxy(IServiceDiscovery serviceDiscovery) {
        this.serviceDiscovery = serviceDiscovery;
    }
}

同样的,动态代理的InvocationHandler也要修改:

package dgb.nospring.myrpc;

import dgb.nospring.myrpc.registry.IServiceDiscovery;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * @author Dongguabai
 * @date 2018/11/1 16:20
 */
public class RemoteInvocationHandler implements InvocationHandler{

    private IServiceDiscovery serviceDiscovery;

    /**
     *发起客户端和服务端的远程调用。调用客户端的信息进行传输
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        RpcRequest rpcRequest = new RpcRequest();
        rpcRequest.setClassName(method.getDeclaringClass().getName());
        rpcRequest.setMethodName(method.getName());
        rpcRequest.setParameters(args);
        //从ZK中获取地址 127.0.0.1:12345
        String discover = serviceDiscovery.discover(rpcRequest.getClassName());
        TcpTransport tcpTransport = new TcpTransport(discover);
        return tcpTransport.send(rpcRequest);
    }

    public RemoteInvocationHandler(IServiceDiscovery serviceDiscovery) {
        this.serviceDiscovery = serviceDiscovery;
    }
}

同样的。TCPTransport也要进行改造:

package dgb.nospring.myrpc;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;

/**
 * socket传输
 *
 * @author Dongguabai
 * @date 2018/11/1 16:25
 */
public class TcpTransport {

    private String serviceAddress;

    private Socket newSocket() {
        System.out.println("准备创建Socket连接,"+serviceAddress);
        String[] split = serviceAddress.split(":");
        try {
            Socket socket = new Socket(split[0], Integer.parseInt(split[1]));
            return socket;
        } catch (IOException e) {
            throw new RuntimeException("Socket连接创建失败!" + serviceAddress);
        }
    }

    public TcpTransport(String serviceAddress) {
        this.serviceAddress = serviceAddress;
    }

    public Object send(RpcRequest rpcRequest) {
        Socket socket = null;
        try {
            socket = newSocket();
            try {
                ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream());
                outputStream.writeObject(rpcRequest);
                outputStream.flush();
                ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream());
                Object result = inputStream.readObject();
                inputStream.close();
                outputStream.close();
                return result;
            } catch (IOException | ClassNotFoundException e) {
                throw new RuntimeException("发起远程调用异常!",e);
            }
        } finally {
            if (socket != null) {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

客户端Demo:

package dgb.nospring.myrpc.demo;

import dgb.nospring.myrpc.RpcClientProxy;
import dgb.nospring.myrpc.registry.IServiceDiscovery;
import dgb.nospring.myrpc.registry.RegistryCenterConfig;
import dgb.nospring.myrpc.registry.ServiceDiscoveryImpl;

/**
 * @author Dongguabai
 * @date 2018/11/1 18:10
 */
public class ClientDemo {

    public static void main(String[] args) {
        /*RpcClientProxy proxy = new RpcClientProxy();
        IHelloService helloService = proxy.clientProxy(IHelloService.class, "127.0.0.1", 12345);
        String name = helloService.sayHello("张三");
        System.out.println(name);*/

        IServiceDiscovery serviceDiscovery = new ServiceDiscoveryImpl(RegistryCenterConfig.CONNECTING_STR);
        RpcClientProxy proxy = new RpcClientProxy(serviceDiscovery);
        IHelloService service = proxy.clientProxy(IHelloService.class);
        System.out.println(service.sayHello("张三"));

    }
}

控制台输出:

如果需要验证集群环境下,我们可以创建两个ServerDemo:

两个服务均注册到注册中心:

客户端调用还是不变:

连续调用两次客户端:

 

最后

以上就是聪明花生为你收集整理的手写实现RPC框架(带注册中心)的全部内容,希望文章能够帮你解决手写实现RPC框架(带注册中心)所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(54)

评论列表共有 0 条评论

立即
投稿
返回
顶部