手把手教你撸一个RPC框架

什么是RPC调用

在动手之前,首先要明确rpc调用的概念,RPC(Remote Procedure Call)远程过程调用,简单的理解是一个节点请求另一个节点提供的服务,简单来说就是无感知的调用远程服务的方法

举个例子,如果在A服务中实现了接口addCount(),进行计数+1,而B服务想调用A服务的这个接口,本地只需要通过函数指针来调用即可,而在两个服务中的调用就可以成为rpc调用

实现RPC调用的流程

生产者

  1. 扫描所有的提供服务的Service并对应的注册到注册中心中,暴露服务出去,并且在本地根据服务名称缓存服务执行对象。
  2. 开启本地服务接受消费者请求。
  3. 在接收到请求后根据服务名称寻找对应的执行对象,并通过反射的方式调用对应的方法。
  4. 将执行结果返回给消费者

消费者

  1. 对含有相应注解的字段注入代理对象。实现类似@Autowired的功能
  2. 将调用的类名,方法,参数,封装成元数据,并从注册中心获取对应执行地址
  3. 向执行地址发送相应数据,并接受返回结果

总体来说一个完整的RPC调用流程基本上如上所述,接下来我们就来实现一个简易的RPC调用框架,当然是基于java的,在理论上rpc调用是不限语言的,比如gRPC或者Thrift都是可以跨语言执行的,而当当网二次开发的Dubbox也是可以跨语言的。

工具选型

通过上述流程可以得知,在这个rpc框架中我们需要选择一个注册中心以及网络通讯框架,并且要选择通讯协议,在这个框架中我选择zookeeper做为注册中心,netty为网络通讯框架,通讯协议选择Http(别问,问就是好写)序列化协议选择JSON(别问,好写)。

项目创建

手把手教你撸一个RPC框架_第1张图片

整项目结构如上图,common项目是通用的内容也就是框架的主题内容,consumer是消费者项目,producer是生产者项目,这两个项目都是springboot项目并且依赖common项目。

通用部分

在common项目pom.xml中加入以下依赖

 <dependency>
            <groupId>com.alibabagroupId>
            <artifactId>fastjsonartifactId>
            <version>1.2.47version>
        dependency>
        <dependency>
            <groupId>org.apache.zookeepergroupId>
            <artifactId>zookeeperartifactId>
            <version>3.4.14version>
            <exclusions>
                <exclusion>
                    <groupId>org.slf4jgroupId>
                    <artifactId>slf4j-log4j12artifactId>
                exclusion>
            exclusions>

        dependency>
        
        <dependency>
            <groupId>io.nettygroupId>
            <artifactId>netty-allartifactId>
            <version>4.1.42.Finalversion>
        dependency>
        
        <dependency>
            <groupId>org.apache.curatorgroupId>
            <artifactId>curator-recipesartifactId>
            <version>4.2.0version>
        dependency>
  <dependency>
            <groupId>cn.hutoolgroupId>
            <artifactId>hutool-allartifactId>
            <version>5.0.5version>
        dependency>

在common下创建config包,并创建RpcConfig类

@Component
@ConfigurationProperties(prefix = "myrpc")
public class RpcConfig {
     

    private String localAddress;

    private Integer  nettyPort;
  
    private String  basePackage;

    public String getBasePackage() {
     
        return basePackage;
    }

    public void setBasePackage(String basePackage) {
     
        this.basePackage = basePackage;
    }

    public Integer getNettyPort() {
     
        return nettyPort;
    }

    public void setNettyPort(Integer nettyPort) {
     
        this.nettyPort = nettyPort;
    }

    public String getLocalAddress() {
     
        return localAddress;
    }

    public void setLocalAddress(String localAddress) {
     
        this.localAddress = localAddress;
    }
}

该配置类配置本地请求地址以及netty启动对应端口

然后创建ZookeeperConfig类。

@Component
@ConfigurationProperties(prefix = "myrpc.zookeeper")
public class ZookeeperConfig {
     

    private String url;

    private Integer sessionTimeOut;

    public Integer getSessionTimeOut() {
     
        return sessionTimeOut;
    }

    public void setSessionTimeOut(Integer sessionTimeOut) {
     
        this.sessionTimeOut = sessionTimeOut;
    }

    public String getUrl() {
     
        return url;
    }

    public void setUrl(String url) {
     
        this.url = url;
    }


}

该类用于获取zookeeper相关的配置,然后在创建zookeeperRegister类,该类为zookeeper操作相关工具类

@Component
public class ZookeeperRegister {
     
		//根节点
    public static final String BASE_NODE = "/myrpc/";
    //缓存生产者数据
    public static ConcurrentHashMap<String, List<String>> map = new ConcurrentHashMap<>();

    @Autowired
    private ZookeeperConfig zookeeperConfig;

    private ZooKeeper zooKeeper = null;
  
		//获取生产者信息
    public List<String> getProducer(Class<?> injectedType) throws Exception {
     
        List<String> producerList = map.get(injectedType.getTypeName());
        if (CollectionUtil.isEmpty(producerList)) {
     
            producerList = this.getChildNode(injectedType.getTypeName());
            if (CollectionUtil.isEmpty(producerList)) {
     
                throw new RuntimeException("没有对应生产者,执行失败");
            }
        }
        return producerList;
    }


    @PostConstruct
    public void init() throws Exception {
     
        zooKeeper = new ZooKeeper(zookeeperConfig.getUrl(), zookeeperConfig.getSessionTimeOut(), watchedEvent -> {
     
            if(watchedEvent.getState().equals(Watcher.Event.KeeperState.SyncConnected)){
     
                try {
     
                  //链接完成后如果没有根节点则创建根节点
  if(zooKeeper.exists(BASE_NODE.substring(0,BASE_NODE.lastIndexOf("/")),false)==null){
     
                        zooKeeper.create(BASE_NODE.substring(0,BASE_NODE.lastIndexOf("/")),null,ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT);
                    }
                } catch (KeeperException e) {
     
                    e.printStackTrace();
                } catch (InterruptedException e) {
     
                    e.printStackTrace();
                }
            }
            System.out.println(watchedEvent);});
    }


    public void createNode(String node,CreateMode createMode) throws Exception {
     
        if(!exists(node)){
     
            zooKeeper.create(BASE_NODE + node, null, ZooDefs.Ids.OPEN_ACL_UNSAFE, createMode);
        }
    }

    public boolean exists(String node) throws KeeperException, InterruptedException {
     
        return zooKeeper.exists(BASE_NODE + node,false)!=null;
    }


    private void resetProducer(String path, List<String> value) {
     
        String className = path.replaceFirst(BASE_NODE, "");
        map.put(className, value);
    }


    public List<String> getChildNode(String path) throws KeeperException, InterruptedException {
     
        return zooKeeper.getChildren(BASE_NODE + path, watchedEvent -> {
     
            System.out.println(JSON.toJSON(watchedEvent) + "nodewatchEvent");
            try {
     
              	//监听根节点并刷新本地缓存
                resetProducer(path, getChildNode(path));
            } catch (Exception e) {
     
                e.printStackTrace();
            }
        });
    }



在该类中维护了一个Map,该map缓存了对应的生产者的数据,通过getProducer方法获取对应生产者的数据,而缓存的map的key为接口的全名。

zookeeper相关的类就创建好了,接下来创建netty相关的,创建netty包并创建NettyServer类

@Component
public class NettyServer {
     

    @Resource
    private RpcConfig rpcConfig;

    @PostConstruct
    public void start() throws InterruptedException {
     

        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workGroup = new NioEventLoopGroup();

        ServerBootstrap serverBootstrap = new ServerBootstrap();

        serverBootstrap.group(bossGroup, workGroup)
                .channel(NioServerSocketChannel.class)
                .option(ChannelOption.SO_BACKLOG, 128)
                .childOption(ChannelOption.SO_KEEPALIVE, true)
                .childHandler(new ChannelInitializer<SocketChannel>() {
     
                    @Override
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
     
                                .addLast(new HttpServerCodec())
                                .addLast(new HttpObjectAggregator(5 * 1024 * 1024))
                                .addLast(new NettyServerHandler());
                    }
                });
        ChannelFuture cf = serverBootstrap.bind(rpcConfig.getNettyPort());
        cf.channel().closeFuture();
        System.out.println("服务启动完毕");

    }
}

该类为netty启动类,随服务启动而启动,并绑定配置中的端口。并且创建NettyServerHandler类,该类用于处理所有到该服务中的请求

public class NettyServerHandler extends SimpleChannelInboundHandler<FullHttpMessage> {
     


    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, FullHttpMessage msg) throws Exception {
     

    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
     
        ctx.flush();
    }

    private void writeResponse(ChannelHandlerContext ctx, boolean keepAlive, String responseJson) {
     
        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, Unpooled.copiedBuffer(responseJson, CharsetUtil.UTF_8));  
        response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html;charset=UTF-8");      
        response.headers().set(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes());
        if (keepAlive) {
     
            response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
        }
        ctx.writeAndFlush(response);
    }
}

该类中的channelRead0方法是处理请求的方法,该方法我们后续填充。

然后创建元数据MetaDataObject类,该类为生产者与消费者的通讯类,该类包含需要请求的类名,方法名以及参数列表

public class MetaDataObject implements Serializable {
     

    private Class<?> clazz;

    private String  methodName;

    private Object[] parameters;

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

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

    public Class<?> getClazz() {
     
        return clazz;
    }

    public void setClazz(Class<?> clazz) {
     
        this.clazz = clazz;
    }

    public String getMethodName() {
     
        return methodName;
    }

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

接下来创建RpcResponse类,该类为生产者返回调用结果的类

public class RpcResponse {
     

    public static final String SUCCESS="200";

    private String code;

    private Object content;

    public String getCode() {
     
        return code;
    }

    public void setCode(String code) {
     
        this.code = code;
    }

    public Object getContent() {
     
        return content;
    }

    public void setContent(Object content) {
     
        this.content = content;
    }
}

生产者部分

基础部分创建完毕后,开始创建生产者部分,在改部分中我们需要实现,在服务启动后,扫描所有需要暴露的服务类,并且将服务注册到注册中心中然后缓存到本地。并且要处理消费者发送的请求找到对应的服务进行反射调用

首先需要创建标记服务的注解@RpcService,该注解标记的类视为暴露出的服务

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({
     ElementType.TYPE})
public @interface RpcService {
     
}

我们可以利用Spring的ApplicationListener,在容器初始化完成后,扫描所有对应的包,并且找到所有被@RpcService标记的类,并且将该类缓存并且暴露到注册中心中

@Component
public class MyApplicationListener implements ApplicationListener {
     
  
    public static final Map producerHolder = new ConcurrentHashMap();

    @Autowired
    private RpcConfig rpcConfig;

    @Autowired
    private ZookeeperRegister zookeeperRegister;

    @Override
    public void onApplicationEvent(ApplicationEvent applicationEvent) {
     
        ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
        final String RESOURCE_PATTERN = "/**/*.class";
        try {
     
            String pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + ClassUtils.convertClassNameToResourcePath(rpcConfig.getBasePackage())
                    + RESOURCE_PATTERN;
            Resource[] resources = resourcePatternResolver.getResources(pattern);
            MetadataReaderFactory readerFactory = new CachingMetadataReaderFactory(resourcePatternResolver);
            for (Resource resource : resources) {
     
                if (resource.isReadable()) {
     
                    MetadataReader reader = readerFactory.getMetadataReader(resource);
                    //扫描到的class
                    String className = reader.getClassMetadata().getClassName();
                    Class<?> clazz = Class.forName(className);
                    //判断是否有指定注解
                    RpcService annotation = clazz.getAnnotation(RpcService.class);
                    if (annotation != null && producerHolder.get(className) == null) {
     
                        //这个类使用了自定义注解
                        Class<?>[] face = clazz.getInterfaces();
                        for (Class<?> aClass : face) {
     
                            producerHolder.put(aClass.getTypeName(), clazz.newInstance());
                            registerProducer(aClass);
                        }
                    }
                }
            }
        } catch (Exception e) {
     
            e.printStackTrace();
        }
    }


    public void registerProducer(Class<?> clazz) throws Exception {
     
        zookeeperRegister.createNode(clazz.getTypeName(), CreateMode.PERSISTENT);
        zookeeperRegister.createNode(clazz.getTypeName()+"/"+rpcConfig.getLocalAddress(), CreateMode.EPHEMERAL);
    }
}

这样我们就将所有的服务缓存在producerHolder这个Map中,并且注册到zookeeper中,注册列表为 根目录->类名->生产者地址接下来我们创建处理消费者请求的部分,在刚刚创建的NettyServerHandler中的channelRead0方法中加入以下代码

  @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, FullHttpMessage msg) throws Exception {
     
        MetaDataObject metaData = JSON.parseObject(msg.content().toString(CharsetUtil.UTF_8),MetaDataObject.class);
        Object handler = MyApplicationListener.producerHolder.get(metaData.getClazz().getTypeName());
        Object result = ReflectUtil.invoke(handler, metaData.getMethodName(), metaData.getParameters());
        RpcResponse response= new RpcResponse();
        response.setCode(RpcResponse.SUCCESS);
        response.setContent(result);
        writeResponse(channelHandlerContext,true,JSON.toJSONString(response));
    }

在改方法中我们首先获取请求传入过来的数据,并且转换成对应的元数据对象,然后在producer中获取对应处理的handler并且通过反射的方式调用对应的方法并将返回值通过通道返回给消费者。

至此 生产者的部分就创建完毕了。

消费者部分

在消费者部分,我们需要实现将代理自动注入到对应的service中并且在代理中通过网络请求的方式去消费者请求数据。

首先需要创建自动注入的标记注解@RpcAutowired

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface RpcAutowired {
}

然后创建RpcAutowiredAnnotationBeanPostProcesser用来进行自动注入的功能

public class RpcAutowiredAnnotationBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter {
     


    @Override
    public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) throws BeansException {
     

        try {
     
            InjectionMetadata metadata = new InjectionMetadata(bean.getClass(), getAnnotationFieldElement(bean.getClass(), beanName));
            metadata.inject(bean, beanName, pvs);
        } catch (Throwable throwable) {
     
            throwable.printStackTrace();
        }


        return pvs;
    }

    private List<InjectionMetadata.InjectedElement> getAnnotationFieldElement(Class<?> aClass, String beanName) {
     
        List<InjectionMetadata.InjectedElement> list = new ArrayList<>();
        for (Field field : ReflectUtil.getFields(aClass)) {
     
            RpcAutowired auto = field.getAnnotation(RpcAutowired.class);
            if (auto != null) {
     
                list.add(new AnnotatedFieldElement(field));
            }
        }
        return list;
    }

    public class AnnotatedFieldElement extends InjectionMetadata.InjectedElement {
     

        private final Field field;


        private volatile Object bean;

        protected AnnotatedFieldElement(Field field) {
     
            super(field, null);
            this.field = field;
        }

        @Override
        protected void inject(Object bean, String beanName, PropertyValues pvs) throws Throwable {
     

            Class<?> injectedType = field.getType();

            Object injectedObject = ProxyFactory.create(injectedType);

            ReflectionUtils.makeAccessible(field);

            field.set(bean,injectedObject);

        }

    }

}

该类继承了InstantiationAwareBeanPostProcessorAdapter,是Spring提供的拓展接口,在该接口实现了postProcessProperties,会在Spring初始化bean的时候调用。通过反射获取改类的所有Field如果该Field被@RpcAutowired注解,则加入AnnotatedFieldElement类,该类继承了InjectedElement类,spring在注入的时候会调用该类的inject方法对该类进行注入,我们在该类中利用反射注入我们创建的代理对象,实现自动注入,顺带一提,Spring的自动注入也是利用类似的手段完成的

接下来创建代理工厂累ProxyFactory

@Component
public class ProxyFactory  {
     


    @Autowired
    public ProxyFactory(ZookeeperRegister zookeeperRegister) {
     
        ProxyFactory.zookeeperRegister = zookeeperRegister;
    }

    private static ZookeeperRegister zookeeperRegister;

    public static Object create(Class<?> injectedType) {
     
        return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{
     injectedType}, (proxy, method, args) -> {
     
            MetaDataObject metaDataObject = new MetaDataObject();
            List<String> producerList = zookeeperRegister.getProducer(injectedType);
            metaDataObject.setClazz(injectedType);
            metaDataObject.setMethodName(method.getName());
            metaDataObject.setParameters(args);
            String result = HttpUtil.post(producerList.get(0), JSON.toJSONString(metaDataObject));
            RpcResponse response = JSON.parseObject(result, RpcResponse.class);
            System.out.println(JSON.toJSON(response));
            if (!response.getCode().equals(RpcResponse.SUCCESS)) {
     
                throw new RuntimeException("远程调用失败");
            }else{
     
                return response.getContent();
            }
        });

    }
}

在该类中,利用jdk的动态代理创建一个实例,在该代理中,利用Http请求对生产者服务进行请求。

然后创建CommonRegister类,将我们自己创建的RpcAutowiredAnnotationBeanPostProcessor注册到spring中使其生效

public class CommonRegister implements ImportBeanDefinitionRegistrar {
     


    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
     
        registerInfrastructureBean(beanDefinitionRegistry,"RpcAutowiredAnnotationBeanPostProcessor", RpcAutowiredAnnotationBeanPostProcessor.class);

    }


    public static boolean registerInfrastructureBean(BeanDefinitionRegistry beanDefinitionRegistry,
                                                     String beanName,
                                                     Class<?> beanType) {
     
        boolean registered = false;
        if (!beanDefinitionRegistry.containsBeanDefinition(beanName)) {
     
            RootBeanDefinition beanDefinition = new RootBeanDefinition(beanType);
            beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
            beanDefinitionRegistry.registerBeanDefinition(beanName, beanDefinition);
            registered = true;
        }

        return registered;
    }

}

该类继承了ImportBeanDefinitionRegistrar,该类只能通过@Import方式进行注入,会在spring启动时调用方法动态的注册bean,所以要创建一个BaseConfig类将该类注册到Spring中

@Configuration
@Import(CommonRegister.class)
public class BaseConfig {
     
}

至此消费者的部分也已经实现完毕了。该项目的部分实现,参考了dubbo的部分代码。

测试

首先需要在common中创建一个BaseService并且提供一个test接口

public interface BaseService {
     
    String test(String msg);
}

然后在生产者项目中创建一个实现类并且用RpcService注解

@RpcService
public class BaseServiceImpl implements BaseService {
     
    @Override
    public String test(String msg) {
     
        System.out.println("我接受到了远程调用的信息===================>"+msg);
        return "返回给消费者结果";
    }
}

然后在消费者项目中创建一个Controller

@RestController
public class ConsumerController {
     

    @RpcAutowired
    private BaseService baseService;

    @RequestMapping("test")
    public void test(){
     
        baseService.test("我是消费者");
    }

}

生产者项目的配置文件

myrpc:
  zookeeper:
    url: localhost:2181
    sessionTimeOut: 10000
  netty-port: 10000
  local-address: localhost:10000
  base-package: com.myrpc
server:
  port: 8980

消费者配置文件

myrpc:
  zookeeper:
    url: localhost:2181
    sessionTimeOut: 10000
  local-adderss: localhost:9999
  netty-port: 9999
  base-package: com.myrpc
server:
  port: 8979

将两个项目同时启动并且请求消费者的接口会得到如下结果

手把手教你撸一个RPC框架_第2张图片

至此整个简易RPC框架就已经完成了相信对RPC调用的流程也有了一定的了解。

手把手教你撸一个RPC框架_第3张图片

你可能感兴趣的:(java,java)