【手写RPC框架(四)】使用Redis实现服务注册

一、Redis

1.1 Redis安装

redis

1.2 Redis可视化工具

RedisDesktopManager

1.3 Redis使用

Redis教程

二、使用Redis实现简单服务注册

在这里插入图片描述
\quad\quad 一个简单的RPC框架如上。主要流程如下,原文见手写RPC框架(四)使用Redis进行服务注册

  1. 启动服务端,服务发现,项目会扫描具有@RpcProvider注解的类,并将<服务名,服务实现类信息存储值Redis中。
  2. 启动消费端,项目通过反射获取代理类,将调用类,方法、参数等序列化,通过socket发送到服务端
  3. 服务端获取序列化对象,并将其反序列化,得到interfaceName(接口名,即服务名),参数列表,参数类型,并根据服务名获取实现类,通过反射执行调用的方法。
  4. 服务端将指定结果通过socket返回给消费端

\quad\quad 这篇文章引入Redis的原因主要是代替前一篇文章中使用list存储服务信息,利用Redis非关系型数据库特性,可以存储更多服务信息(接口名称、host、port等)。
\quad\quad 在使用Redis之前,我们发现项目代码结构设计不合理,对于客户端,服务端应该作为不同的模块,在测试和运行时可以分别运行对应的模块,在此我们将项目拆分为四个模块:

  • client: 客户端,请求服务者
  • server: 服务端,提供服务者,响应服务请求
  • service: 项目中使用的服务,服务接口及其实现类
  • utils : 项目中各个模块使用到的工具类
    项目结构参考该项目:syske-rpc-server

2.1 项目使用的服务(service)

\quad\quad RPC远程调用时服务端提供的服务均放在service模块下,和前面文章一样,此处不再赘述。

2.1.1 接口

略,见前文。

2.1.2 实现类

略,见前文。

2.2 utils工具类

\quad\quad 存放RPC的工具类,包括注解,包扫描器,序列化,Redis管理工具类,后续也可以加入zookeeper,kyro,netty,nginx等。

2.2.1 注解
  1. RpcClient:标注属性,用于客户端标注接口服务
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RpcClient {
}
  1. RpcProxy:服务消费者,客户端注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface RpcProxy {
}
  1. RpcServer:RPC服务端注解,服务提供者
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface RpcServer {
}
  1. RpcComponentScan:扫描类注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface RpcComponentScan {
    String[] value();
}
2.2.2 包扫描器
//包扫描器
public class ClassScanner {
    private static final Logger logger = LoggerFactory.getLogger(ClassScanner.class);

    private static Set<Class> classSet = Sets.newHashSet();

    private ClassScanner() {
    }

    public static Set<Class> getClassSet() {
        return classSet;
    }

    /**
     * 类加载器初始化
     *
     * @throws IOException
     * @throws ClassNotFoundException
     */
    public static void init(Class aClass) {
        try {
            // 扫描包
            componentScanInit(aClass);
        } catch (Exception e) {
            logger.error("ClassScanner init error: ", e);
        }
    }

    /**
     * 扫描指定的包路径,如果无该路径,则默认扫描服务器核心入口所在路径
     *
     * @param aClass
     * @throws IOException
     * @throws ClassNotFoundException
     */
    private static void componentScanInit(Class aClass) throws IOException, ClassNotFoundException {
        logger.info("componentScanInit start init……");
        logger.info("componentScanInit aClass: {}", aClass);
        Annotation annotation = aClass.getAnnotation(RpcComponentScan.class);
        logger.info("annotation: {}", annotation);
        if (Objects.isNull(annotation)) {
            Package aPackage = aClass.getPackage();
            scanPackage(aPackage.toString(), classSet);
        } else {
            String[] value = ((RpcComponentScan) annotation).value();
            for (String s : value) {
                scanPackage(s, classSet);
            }
        }
        logger.info("componentScanInit end, classSet = {}", classSet);
    }

    /**
     * 扫描指定包名下所有类,并生成classSet
     *
     * @param packageName
     * @param classSet
     * @throws IOException
     * @throws ClassNotFoundException
     */
    private static void scanPackage(String packageName, Set<Class> classSet)
            throws IOException, ClassNotFoundException {
        logger.info("start to scanPackage, packageName = {}", packageName);
        //ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        Enumeration<URL> classes = ClassLoader.getSystemResources(packageName.replace('.', '/'));
        logger.info("classes: {}", classes.hasMoreElements());
        while (classes.hasMoreElements()) {
            URL url = classes.nextElement();
            File packagePath = new File(url.getPath());
            if (packagePath.isDirectory()) {
                File[] files = packagePath.listFiles();
                for (File file : files) {
                    String fileName = file.getName();
                    if (file.isDirectory()) {
                        String newPackageName = String.format("%s.%s", packageName, fileName);
                        scanPackage(newPackageName, classSet);
                    } else {
                        String className = fileName.substring(0, fileName.lastIndexOf('.'));
                        String fullClassName = String.format("%s.%s", packageName, className);
                        classSet.add(Class.forName(fullClassName));
                    }
                }
            } else {
                String className = url.getPath().substring(0, url.getPath().lastIndexOf('.'));
                String fullClassName = String.format("%s.%s", packageName, className);
                classSet.add(Class.forName(fullClassName));
            }
        }
    }
}

在启动项目时通过以下语句,可使用包扫描器获取对应类集合:

ClassScanner.init(Server.class);
Set<Class> classSet = ClassScanner.getClassSet();
2.2.3 Redis管理工具类

\quad\quad Redis用作服务注册,key值存储服务名,value存储序列化信息(实现类信息)(序列化信息使用RpcRegisterEntity实现)

public class RedisUtil {
    private static Jedis jedis = new Jedis("127.0.0.1", 6379);
	//服务注册
    public static void record2Cache(String key, String value) {
        jedis.set(key, value);
    }
	//服务获取
    public static String getObject(String key) {
        return jedis.get(key);
    }
}
2.2.4 序列化
  1. RpcRegisterEntity类
    序列化服务信息,即:
  • interfaceClassFullName:服务接口类全称
  • serviceImplClassFullName:服务实现类全称
  • host:接口ip
  • port:接口端口号
public class RpcRegisterEntity implements Serializable {
    /**
     * 对服务消费者,该值表示调用方全类名;对服务提供者而言,该值表示接口实现全类名
     */
    private String interfaceClassFullName;

    /**
     * 接口Ip
     */
    private String host;

    /**
     * 接口端口号
     */
    private int port;
    /**
     * 服务提供者实现类名
     */
    private String serviceImplClassFullName;

    private String methodName;

    public String getMethodName() {
        return methodName;
    }

    public void setMethodName(String methodName) {
        this.methodName = methodName;
    }

    private Class<?>[] parameterTypes;

    private Object[] parameters;

    public Class<?>[] getParameterTypes() {
        return parameterTypes;
    }

    public void setParameterTypes(Class<?>[] parameterTypes) {
        this.parameterTypes = parameterTypes;
    }

    public Object[] getParameters() {
        return parameters;
    }

    public void setParameters(Object[] parameters) {
        this.parameters = parameters;
    }

    public RpcRegisterEntity() {
    }

    public RpcRegisterEntity(String interfaceFullName, String host, int port) {
        this.interfaceClassFullName = interfaceFullName;
        this.host = host;
        this.port = port;
    }

    public String getInterfaceClassFullName() {
        return interfaceClassFullName;
    }

    public void setInterfaceClassFullName(String interfaceClassFullName) {
        this.interfaceClassFullName = interfaceClassFullName;
    }

    public String getHost() {
        return host;
    }

    public RpcRegisterEntity setHost(String host) {
        this.host = host;
        return this;
    }

    public int getPort() {
        return port;
    }

    public RpcRegisterEntity setPort(int port) {
        this.port = port;
        return this;
    }

    public String getServiceImplClassFullName() {
        return serviceImplClassFullName;
    }

    public RpcRegisterEntity setServiceImplClassFullName(String serviceImplClassFullName) {
        this.serviceImplClassFullName = serviceImplClassFullName;
        return this;
    }

    @Override
    public String toString() {
        return "RpcRegisterEntity{" +
                "interfaceFullName='" + interfaceClassFullName + '\'' +
                "serviceImplClassFullName" + serviceImplClassFullName + '\'' +
                ", host='" + host + '\'' +
                ", port=" + port +
                '}';
    }
}

  1. RpcSerializable类
    这里用于客户端和服务端建立socket连接后,用于传输请求信息。和前文实现方法一致,不再赘述。

2.3 服务端

服务端基本逻辑:

  • 扫描提供服务的包路径,注册服务
  • 打开socket,等待监听
  • 建立连接后,响应请求,返回服务计算结果
@RpcServer
@RpcComponentScan("cn.dhu.service")
public class Server {
    static final String PROVIDER_KEY = "%s:server";
    static final int port=8886;
    static final String host = "127.0.0.1";
    public static void main(String[] args) {
        try {
            ClassScanner.init(Server.class);
            initServiceProvider();
            //启动nettyServer
            //NettyServer nettyServer=new NettyServer("192.168.31.214",port);
            //nettyServer.startNettyServer();

            ServerSocket serverSocket = new ServerSocket(port);


            while (true) {
                Socket accept = serverSocket.accept();
                ObjectInputStream objectInputStream = new ObjectInputStream(accept.getInputStream());
                ObjectOutputStream objectOutputStream = new ObjectOutputStream(accept.getOutputStream());
                RpcSerializable rpcSerializable=(RpcSerializable)objectInputStream.readObject();
                // 读取类名
                Class serviceClass= rpcSerializable.getClassName();
                // 读取方法名
                String methodName=rpcSerializable.getMethodName();
                // 读取方法入参类型
                Class<?>[] parameterTypes=rpcSerializable.getParameterTypes();
                // 读取方法调用入参
                Object[] parameters=rpcSerializable.getArguments();
                System.out.println(String.format("收到消费者远程调用请求:类名 = {%s},方法名 = {%s},调用入参 = %s,方法入参列表 = %s",
                        serviceClass, methodName, Arrays.toString(parameters), Arrays.toString(parameterTypes)));

                String serviceObject = RedisUtil.getObject(String.format(PROVIDER_KEY, serviceClass.getName()));
                System.out.println("PROVIDER_KEY:"+PROVIDER_KEY);
                RpcRegisterEntity rpcRegisterEntity = JSON.parseObject(serviceObject, RpcRegisterEntity.class);
                String serviceImplClassFullName = rpcRegisterEntity.getServiceImplClassFullName();

                Class<?> aClass = Class.forName(serviceImplClassFullName);
                System.out.println("服务实现类:"+aClass);
                Method method = aClass.getMethod(methodName,parameterTypes);
                Object invoke = method.invoke(aClass.newInstance(), parameters);
                System.out.println("方法调用结果:" + invoke);
                objectOutputStream.writeObject(invoke);
            }
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static void initServiceProvider() {
        Set<Class> classSet = ClassScanner.getClassSet();
        classSet.forEach(c -> {
            Annotation annotation = c.getAnnotation(RpcServer.class);
            if (Objects.nonNull(annotation)) {
                Method[] methods = c.getDeclaredMethods();
                for (Method method : methods) {
                    Class<?>[] parameterTypes = method.getParameterTypes();
                    String methodName = method.getName();
                    Class[] interfaces = c.getInterfaces();
                    String interfaceName = interfaces[0].getName();
                    RpcRegisterEntity rpcRegisterEntity = new RpcRegisterEntity(interfaceName,host,port);
                    rpcRegisterEntity.setServiceImplClassFullName(c.getName());
                    RedisUtil.record2Cache(String.format(PROVIDER_KEY, interfaceName), JSON.toJSONString(rpcRegisterEntity));
                    rpcRegisterEntity.setMethodName(methodName);
                    rpcRegisterEntity.setParameterTypes(parameterTypes);
                    System.out.println(JSON.toJSONString(rpcRegisterEntity));
                }
            }
        });
    }
}

2.4 客户端

客户端基本逻辑:

  • 扫描需要请求的服务全类名,并从注册中心(Redis)中获取服务
  • 获取服务信息后生成代理对象(生成代理对象的方法可参照前文)
  • 在代理对象的invoke方法中添加执行操作
  • 在invoke中向服务端发送请求,并接收返回信息
@RpcProxy
@RpcComponentScan("cn.dhu.client")
public class Client {
    private static final Logger logger = (Logger) LoggerFactory.getLogger(Client.class);
    @RpcClient
    private Calculator calculator;

    public static void main(String[] args) {
        initServiceClient();
        Map<Class, Object> rpcClientContentMap = RpcClientContentHandler.getRpcClientContentMap();
        logger.info("Client:{}",Client.class);
        Client client = (Client) rpcClientContentMap.get(Client.class);
        logger.info("Client:{}",client);

        int result = client.calculator.add(1,2);
        logger.info("消费者远程调用返回结果:{}", result);
        int result1 = client.calculator.sub(1,2);
        logger.info("消费者远程调用返回结果:{}", result1);
        int result2 = client.calculator.mul(3,2);
        logger.info("消费者远程调用返回结果:{}", result2);
        int result3 = client.calculator.div(1,2);
        logger.info("消费者远程调用返回结果:{}", result3);
    }

    private static void initServiceClient() {
        final String PROVIDER_KEY = "%s:server";
        ClassScanner.init(Client.class);
        Set<Class> classSet = ClassScanner.getClassSet();
        logger.info("classSet:{}",classSet);
        classSet.forEach(c -> {
            Field[] declaredFields = c.getDeclaredFields();
            for (Field field : declaredFields) {
                try {
                    RpcClient annotation = field.getAnnotation(RpcClient.class);
                    if (Objects.nonNull(annotation)) {
                        Class<?> fieldType = field.getType();
                        String name = fieldType.getName();
                        //RedisUtil.getObject(String.format(PROVIDER_KEY, name));
                        String serviceObject = RedisUtil.getObject(String.format(PROVIDER_KEY, name));
                        RpcRegisterEntity rpcRegisterEntity = JSON.parseObject(serviceObject, RpcRegisterEntity.class);
                        String host = rpcRegisterEntity.getHost();
                        int port = rpcRegisterEntity.getPort();
                        logger.info("fieldType:{}",fieldType);
                        Object proxyInstance = getProxyInstance1(fieldType,host,port);
                        //logger.info("proxyInstance:{}",proxyInstance);
                        Object consumer = c.newInstance();
                        field.set(consumer, proxyInstance);
                        RpcClientContentHandler.initRpcClientContent(c, consumer);

                    }
                } catch (InstantiationException | IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        });
    }

    /**
     * 获取动态代理实例
     *
     *
     *
     */
    private static Object getProxyInstance1(Class tClass,String host, int port) {
        return  Proxy.newProxyInstance(Client.class.getClassLoader(),
                new Class[]{tClass}, new ClientProxyInvocationHandler1(tClass,host,port));
    }
}

代理类:

public class ClientProxyInvocationHandler1 implements public class ClientProxyInvocationHandler1 implements InvocationHandler {
    private final Logger logger = LoggerFactory.getLogger(ClientProxyInvocationHandler.class);
    /**
     * 代理类的class
     */
    private Class<?> serviceClass;
    String host;
    int port;

    public ClientProxyInvocationHandler1(Class<?> serviceClass,String host, int port) {
        this.serviceClass = serviceClass;
        this.host = host;
        this.port = port;

    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws IOException, ClassNotFoundException {

        //logger.info("建立socket连接: {}:{}", host, port);
        Socket socket = new Socket(host, port);
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
        ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream());
        RpcSerializable rpcSerializable = new RpcSerializable(proxy.getClass().getInterfaces()[0], method.getName(), method.getParameterTypes(), args);
        objectOutputStream.writeObject(rpcSerializable);

        return inputStream.readObject();
    }
}
 {
    private final Logger logger = LoggerFactory.getLogger(ClientProxyInvocationHandler.class);
    /**
     * 代理类的class
     */
    private Class<?> serviceClass;
    String host;
    int port;

    public ClientProxyInvocationHandler1(Class<?> serviceClass,String host, int port) {
        this.serviceClass = serviceClass;
        this.host = host;
        this.port = port;

    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws IOException, ClassNotFoundException {

        //logger.info("建立socket连接: {}:{}", host, port);
        Socket socket = new Socket(host, port);
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
        ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream());
        RpcSerializable rpcSerializable = new RpcSerializable(proxy.getClass().getInterfaces()[0], method.getName(), method.getParameterTypes(), args);
        objectOutputStream.writeObject(rpcSerializable);

        return inputStream.readObject();
    }
}

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值