从零开始写一个RPC框架

本文介绍了如何从零开始构建一个简单的RPC框架,采用的技术栈包括Netty和SpringBoot。客户端利用ClassPathBeanDefinitionScanner扫描并注入Bean,通过动态代理实现RPC调用。服务端则基于Spring的NettyServer,将带有@RpcService注解的类暴露,并注册到Zookeeper。详细代码已开源在Github,欢迎关注。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

从零开始写一个RPC框架

当前,互联网公司的应用基本都是基于微服务的,系统被拆分为多个分布式的服务,每个服务之间通过RPC远程调用来交互。常用的RPC框架为Dubbo、Spring Cloud的相关套件等。我们试着自己实现一套简易的RPC框架,加深自己的技术理解。技术选型为Netty、SpringBoot。完整代码在Github会持续更新,欢迎大家关注:https://github.com/CvShrimp/NettyRpc

客户端

客户端的实现参考了Mybatis中Mapper的实现,通过ClassPathBeanDefinitionScanner将咱们自己的Bean注入Spring容器中,通过@Autowired注解即可注入使用,实现类通过动态代理生成,下面是核心的ClassPathBeanDefinitionScanner实现类,通过注入Spring的FactoryBean实现,FactoryBean又会生成动态代理的实现类,这个实现类会作为Netty的客户端访问Netty服务端,以实现RPC调用。

package com.cvshrimp.client;

import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.core.type.filter.TypeFilter;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.Set;

/**
 * Created by CvShrimp on 2019/11/4.
 *
 * @author wkn
 */
public class ClassPathRpcScanner extends ClassPathBeanDefinitionScanner {

    private Class<? extends Annotation> annotationClass;

    public void setAnnotationClass(Class<? extends Annotation> annotationClass) {
        this.annotationClass = annotationClass;
    }

    public ClassPathRpcScanner(BeanDefinitionRegistry registry) {
        super(registry);
    }

    @Override
    public Set<BeanDefinitionHolder> doScan(String... basePackages) {
        Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);

        if (beanDefinitions.isEmpty()) {
            logger.warn("No RPC mapper was found in '"
                    + Arrays.toString(basePackages)
                    + "' package. Please check your configuration.");
        } else {
            processBeanDefinitions(beanDefinitions);
        }

        return beanDefinitions;
    }

    public void registerFilters() {
        boolean acceptAllInterfaces = true;

        if (this.annotationClass != null) {
            addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
            acceptAllInterfaces = false;
        }

        if (acceptAllInterfaces) {
            addIncludeFilter((metadataReader, metadataReaderFactory) -> true);
        }

        // exclude package-info.java
        addExcludeFilter(new TypeFilter() {
            @Override
            public boolean match(MetadataReader metadataReader,
                                 MetadataReaderFactory metadataReaderFactory)
                    throws IOException {
                String className = metadataReader.getClassMetadata()
                        .getClassName();
                return className.endsWith("package-info");
            }
        });
    }

    private void processBeanDefinitions(
            Set<BeanDefinitionHolder> beanDefinitions) {
        GenericBeanDefinition definition;

        for (BeanDefinitionHolder holder : beanDefinitions) {
            definition = (GenericBeanDefinition) holder.getBeanDefinition();
            definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName());
            definition.setBeanClass(RpcFactoryBean.class);

            definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
            System.out.println(holder);
        }
    }

    @Override
    protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
        return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
    }
}

package com.cvshrimp.client;

import lombok.extern.slf4j.Slf4j;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.annotation.Autowired;

import java.lang.reflect.Proxy;

/**
 * Created by CvShrimp on 2019/11/4.
 *
 * @author wkn
 */
@Slf4j
public class RpcFactoryBean<T> implements FactoryBean<T> {

    private Class<T> rpcInterface;

    @Autowired
    private ZkClient zkClient;

    public RpcFactoryBean() {}

    public RpcFactoryBean(Class<T> rpcInterface) {
        this.rpcInterface = rpcInterface;
    }

    @Override
    public T getObject() throws Exception {
        return getRpc();
    }

    @Override
    public Class<?> getObjectType() {
        return this.rpcInterface;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }

    @SuppressWarnings(value = "unchecked")
    private <T> T getRpc() {
        ZooKeeper zooKeeper = zkClient.getInstance();
        StringBuilder pathBuilder = new StringBuilder("/rpc/");
        pathBuilder.append(rpcInterface.getName());
        String providerAddress;
        try {
            providerAddress = new String(zooKeeper.getData(pathBuilder.toString(), true, new Stat()));
            String[] infoArray = providerAddress.split(":");
            RpcClient rpcClient = new RpcClient(infoArray[0], Integer.valueOf(infoArray[1]));
            return (T) Proxy.newProxyInstance(rpcInterface.getClassLoader(), new Class[] { rpcInterface },
                    new RpcFactory<>(rpcInterface, rpcClient));
        } catch (Exception e) {
            log.error("No provider, interface={}", rpcInterface.getName());
        }
        return null;
    }
}

服务端

服务端基于一个Spring的NettyServer的Bean来实现,该Bean扩展了ApplicationContextAware和InitializingBean。将容器中带有@RpcService自定义注解的类暴露出去,并将服务的调用地址注册到Zookeeper中,这里参考了Dubbo的实现。

package com.cvshrimp.server;

import com.cvshrimp.tool.ThreadPoolManagerUtil;
import com.google.common.collect.Maps;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.serialization.ClassResolvers;
import io.netty.handler.codec.serialization.ObjectDecoder;
import io.netty.handler.codec.serialization.ObjectEncoder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

import java.net.InetSocketAddress;
import java.util.Map;

/**
 * Created by CvShrimp on 2019/11/4.
 *
 * @author wkn
 */
@Slf4j
@Component
public class NettyServer implements ApplicationContextAware, InitializingBean {

    @Value("${export.port}")
    private int port;

    private Map<String, Object> serviceMap = Maps.newHashMap();

    @Autowired
    private ServiceRegistry serviceRegistry;

    /**
     * 设置ApplicationContext,先于InitializingBean的afterPropertiesSet被调用
     * @param applicationContext
     * @throws BeansException
     */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        Map<String, Object> serviceBeanMap = applicationContext.getBeansWithAnnotation(RpcService.class);
        serviceRegistry.registerRoot();
        for(Object serviceBean : serviceBeanMap.values()) {
            Class<?> clazz = serviceBean.getClass();
            Class<?>[] interfaces = clazz.getInterfaces();
            for(Class<?> inter : interfaces) {
                String interfaceName = inter.getName();
                serviceMap.put(interfaceName, serviceBean);
                serviceRegistry.registerService(inter, "127.0.0.1:" + port);
                // 注册服务成功
                log.info("Register service successful: {}", interfaceName);
            }
        }
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        ThreadPoolManagerUtil.getInstance().addTask(() -> {
            EventLoopGroup bossGroup = new NioEventLoopGroup();
            EventLoopGroup workGroup = new NioEventLoopGroup();
            // 将注册的服务给NettyServerHandler
            NettyServerHandler nettyServerHandler = new NettyServerHandler(serviceMap);
            try {
                ServerBootstrap bootstrap = new ServerBootstrap().group(bossGroup, workGroup)
                        .channel(NioServerSocketChannel.class).localAddress(new InetSocketAddress(port))
                        .childHandler(new ChannelInitializer<SocketChannel>() {
                            @Override
                            protected void initChannel(SocketChannel ch) throws Exception {
                                ch.pipeline().addLast(new ObjectDecoder(1024 * 1024,
                                        ClassResolvers.weakCachingConcurrentResolver(this.getClass().getClassLoader())));
                                ch.pipeline().addLast(new ObjectEncoder());
                                ch.pipeline().addLast(nettyServerHandler);
                            }
                        }).option(ChannelOption.SO_BACKLOG, 1024)
                        .childOption(ChannelOption.TCP_NODELAY, true).childOption(ChannelOption.SO_KEEPALIVE, true);

                ChannelFuture future = bootstrap.bind(port).sync();
                future.channel().closeFuture().sync();
            } catch (InterruptedException e) {
                bossGroup.shutdownGracefully();
                workGroup.shutdownGracefully();
            }
        });
    }


}

完整的实现已经在Github,会持续更新,欢迎大家关注:https://github.com/CvShrimp/NettyRpc

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值