大家好,我是冰河~~没错,冰河这次又要搞事情了,这次要开始RPC框架项目了。为什么从RPC框架项目入手,因为在分布式、微服务乃至云原生不断发展的过程中,RPC作为底层必不可少的通信组件,在分布式、微服务、云原生项目中被广泛应用。为什么要开发RPC框架?是这样的。在开发这个RPC框架之前,我花了很多时间深入研究Dubbo框架。冰河看完了Dubbo2.x和Dubbo3.x的源码后,本来想写一个Dubbo源码分析的专栏给大家。为此,我其实私底下准备了一个多月:画流程图、分析源码、写测试demo。我在看Dubbo源码的时候,也给Dubbo源码添加了非常详细的注释。这里包含了Dubbo2.x和Dubbo3.x的源码。熬夜了一个多月,突然发现一个问题:Dubbo经过多年的不断迭代开发,源码非常多。知道写什么年月。之前写文章分析Dubbo最新版本3.x的时候,可能会在专栏中后期写到Dubbo已经更新到4.x和5.x,设置可能是6.x和7.x。与其这么费劲地分析源码,还不如带领大家从零开始搭建一个分布式的、高性能的、可扩展的、可以在实际生产环境中使用的RPC框架。这样大家也能直观的感受到一个可以在实际场景中使用的RPC框架是如何一步步开发出来的。相信大家学完《RPC手撸专栏》之后,如果自己去看Dubbo源码,会比较简单。你这么认为吗?你能学到什么既然是整个专栏的开头,那肯定是要告诉你在这个专栏中你能学到哪些实用的技术。在这里,我就画一张图,直观的告诉大家在《RPC手撸专栏》可以学到哪些技巧。《RPC手撸专栏》整体框架技术图如图所示。加入星球后,我们将用冰川从头开始实施并完成它。当你跟着Glacier的节奏完成RPC框架的时候,你会发现:什么Dubbo,什么gRPC,什么BRPC,什么Hessian,什么Tars,什么Thrift,什么motan,什么hprose等等,等等,主流的RPC市场上的框架对你来说毫无意义。您可以跟随Glacier的节奏。相信大家看到《RPC手撸专栏》涉及到的知识点时,应该就能明白我们的《RPC手撸专栏》从零开始是比较硬核的吧?另外,我们的RPC项目支持同步调用、异步调用、回调和单向调用。SynchronouscallAsynchronouscallCallbackone-waycall是的,是的,我们最终实现的RPC框架的定位就是尽可能在实际环境中使用。通过本专栏的学习,让大家深入了解能够在实际场景中使用的RPC框架是如何一步步开发出来的。代码结构我把这个bhrpc项目定位为一个分布式、高性能、可扩展的、可以在实际场景中使用的RPC框架。目前总体上已开发完善的功能已达60+个子项目。让我们看一下图片。项目大量使用了对标Dubbo的自定义SPI技术,实现了高扩展性。大家可以根据自己的需要,根据SPI的设计要求添加自己定制的插件。演示效果说了这么多,我们来看看这个RPC框架的使用效果,因为我们的RPC框架支持的调用方式有:原生RPC调用、集成Spring(XML/注解)、集成SpringBoot、集成SpringCloud、IntegrateSpringCloudAlibaba,整合Docker,整合K8S的七种使用方式。在这里,我们将通过集成Spring注解来向大家演示这个RPC框架。RPC核心注解说明为了让大家更好的理解RPC框架,我先给大家展示RPC框架的两个核心注解,一个是RPC服务提供者注解@RpcService,一个是RPC服务调用者注解@RpcReference.(1)服务提供者注解@RpcService的核心源码如下。/***@authorbinghe*@version1.0.0*@descriptionbhrpc服务提供者注解*/@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Componentpublic@interfaceRpcService{/***interfaceClass*/Class>interfaceClass()defaultvoid.class;/***接口的类名*/StringinterfaceClassName()default"";/***版本号*/Stringversion()default"1.0.0";/***服务分组,默认为空*/Stringgroup()default"";/***延迟释放,保留*/intdelay()default0;/***是否导出rpc服务,reservedLeave*/booleanexport()defaulttrue;}(2)服务调用者注解@RpcReference核心源码如下。/***@authorbinghe*@version1.0.0*@descriptionbhrpc服务消费者*/@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.FIELD)@Autowiredpublic@interfaceRpcReference{/***版本号*/String版本()默认“1.0.0”;/***registry类型,当前类型包括:zookeeper、nacos、etcd、consul*/StringregistryType()default"zookeeper";/***注册地址*/StringregistryAddress()default"127.0.0.1:2181";/***负载均衡类型,默认基于ZK一致性Hash*/StringloadBalanceType()default"zkconsistenthash";/***序列化类型,当前类型Contains:protostuff,kryo,json,jdk,hessian2,fst*/StringserializationType()default"protostuff";/***超时时间,默认5s*/longtimeout()default5000;/***是否异步执行*/booleanasync()defaultfalse;/***是否单向调用*/booleanoneway()defaultfalse;/***代理的类型,jdk:jdk代理,javassist:javassist代理,cglib:cglib代理*/Stringproxy()default"jdk";/***服务组,默认为空*/Stringgroup()default"";}这里我只列出服务提供者注解@RpcService和服务调用者注解@RpcReference的部分源码。在RPC框架不断完善的过程中,大家可以慢慢的看到源码的全貌和这里各个注解实现的功能,这里就不详细介绍了。当然,在这个RPC框架实现的native调用方法中,不需要这些注解也可以实现远程调用。效果演示接口定义定义了两个接口,分别是HelloService和HelloPersonService,源码如下。HelloService接口源代码publicinterfaceHelloService{Stringhello(Stringname);Stringhello(Personperson);}HelloPersonService接口源码publicinterfaceHelloPersonService{ListgetTestPerson(Stringname,intnum);}实现服务提供者demo(1)创建HelloService接口的实现类HelloServiceImpl和HelloPersonServiceImpl以及HelloPersonService接口,如下图。HelloServiceImpl类源代码@RpcService(interfaceClass=HelloService.class,version="1.0.0")publicclassHelloServiceImplimplementsHelloService{@OverridepublicStringhello(Stringname){return"Hello!"+姓名;}@OverridepublicStringhello(Personperson){return"Hello!"+person.getFirstName()+""+person.getLastName();}}可以看到在HelloServiceImpl类中添加了RPC服务提供者注解@RpcService,表示将发布为RPC服务。HelloPersonServiceImpl类源代码@RpcService(interfaceClass=HelloPersonService.class,version="1.0.0")publicclassHelloPersonServiceImplimplementsHelloPersonService{@OverridepublicListgetTestPerson(Stringname,intnum){Listpersons=new数组列表<>(数);for(inti=0;ipersonList=helloPersonService.getTestPerson("冰河",2);personList.stream().forEach(System.out::println);}}通过TestServiceImpl类的源码可以看出,远程调用HelloService接口的方法时使用javassist动态代理,远程调用HelloPersonService接口时使用cglib动态代理。(3)创建服务调用者demo的配置类ClientConfig,如下所示。@Configuration@ComponentScan(value={"io.binghe.rpc.*"})@PropertySource(value={"classpath:rpc.properties"})publicclassClientConfig{}(4)创建服务调用者的启动类demoClientTest,如下图。publicclassClientTest{publicstaticvoidmain(String[]args){AnnotationConfigApplicationContextcontext=newAnnotationConfigApplicationContext(ClientConfig.class);TestServicetestService=context.getBean(TestService.class);测试服务.printResult();上下文.close();}}开始服务测试(1)启动Zookeeper。这里为了简单演示,直接在我的本地机器上启动单机Zookeeper就可以了。启动后的效果如下图所示。(2)启动服务提供者ServerTest类,启动后输出的日志信息如下。13:43:36,876INFOConnectionStateManager:228-状态更改:CONNECTED13:43:36,905INFORpcClient:79-使用cglib动态代理...13:43:36,942INFOCuratorFrameworkImpl:235-Starting13:43:36,943INFOZoo8正在启动客户端连接,cnotallow=127.0.0.1:2181可以看出服务提供者已经在Zookeeper中注册了发布的服务。(3)登录Zookeeper客户端,查看在Zookeeper中注册的服务,如下图。查看HelloService接口发布的服务信息[zk:localhost:2181(CONNECTED)5]get/binghe_rpc/io.binghe.rpc.test.client.HelloService#1.0.0/65eb0d7f-4bf7-4a0a-bafc-1b7e0e030353{“名称”:“io.binghe.rpc.test.client.HelloService#1.0.0”,“id”:“65eb0d7f-4bf7-4a0a-bafc-1b7e0e030353”,“地址”:“127.0.0.1”,“端口”":18866,"sslPort":null,"payload":{"@class":"io.binghe.rpc.center.meta.ServiceMeta","serviceName":"io.binghe.rpc.test.client.HelloService","serviceVersion":"1.0.0","serviceAddr":"127.0.0.1","servicePort":18866},"registrationTimeUTC":1656135817627,"serviceType":"DYNAMIC","uriSpec":null,"enabled":true}查看HelloPersonService接口发布的服务信息[zk:localhost:2181(CONNECTED)7]get/binghe_rpc/io.binghe.rpc.test.client.HelloPersonService#1.0.0/882a5cdb-f581-4a83-8d56-800a8f14e831{“名称”:“io.binghe.rpc.test.client.HelloPersonService#1.0.0”,“id”:“882a5cdb-f581-4a83-8d56-800a8f14e831”,“地址”:“127.0.0.1","端口":18866,"sslPort":空,"payload":{"@class":"io.binghe.rpc.center.meta.ServiceMeta""serviceName":"io.binghe.rpc.test.client.HelloPersonService","serviceVersion":"1.0.0","serviceAddr":"127.0.0.1","servicePort":18866},"registrationTimeUTC":1656135817274,"serviceType":"DYNAMIC","uriSpec":null,"enabled":true}从Zookeeper客户端可以看出HelloService接口和HelloPersonService接口发布的服务已经在Zookeeper中注册了(4)启动服务提供者ClientTest类实现RPC调用,输出日志信息如下。13:56:47,391INFOConnectionStateManager:228-状态变化:CONNECTED13:56:47,488INFORpcClient:76-使用javassist动态代理...13:56:47,518INFOConnectionStateManager:228-状态变化:CONNECTED13:545CliIN,RFO:79-使用cglib动态代理...13:56:48,253信息RpcConsumer:85-在端口18866上成功连接rpc服务器127.0.0.1。您好!冰河你好!binghe001binghe002===================================0binghe1binghe可以看到ClientTest类的命令行输出远程调用的结果信息。并且输出调用HelloService接口的远程方法使用了javassist动态代理。调用HelloPersonService接口的远程方法使用cglib动态代理。我们合作的RPC框架其实有很多非常强大的功能。这里就不一一演示了,后面大家一起来实现。一点建议我们的专栏属于实战型的专栏,从头开始的RPC框架会涉及到很多知识点。俗话说,纸上谈兵是肤浅的,永远不知道还得自己动手。冰河希望大家在学习本专栏的时候能够勤奋工作,把代码和专栏一起实现。期间要多动脑筋,多总结,加深对各个知识点的理解。不要志存高远,学久了却学不到东西。