作者:冰河
博客地址:https://binghe001.github.io
大家好,我是冰河~~
没错,这次冰河又要搞事件了,这次筹备下手的是RPC框架我的项目
。为什么要对RPC框架我的项目
下手呢,因为在现在分布式、微服务乃至云原生一直倒退的过程中,RPC作为底层必不可少的通信组件,被广泛应用在分布式、微服务和云原生我的项目中。
为啥要开发RPC框架
事件是这样的,在开发这个RPC框架之前,我破费了不少工夫算是对Dubbo框架彻底钻研透彻了。
冰河在撸透了Dubbo2.x和Dubbo3.x的源码之后,原本想给大家写一个Dubbo源码解析的专栏。为此,我其实私下筹备了一个多月:画流程图、剖析源码、写测试Demo,本人在看Dubbo源码时,也为Dubbo源码增加了十分具体的正文。这里,就蕴含Dubbo2.x和Dubbo3.x的源码。
当我就这么熬夜肝文一个多月后,忽然发现一个问题:Dubbo通过多年一直的迭代开发,它的源码曾经十分多了,如果以文章的模式将Dubbo的源码八面玲珑的剖析到位,那还不晓得要写到何年何月去了。当我写文章剖析Dubbo的最新版本3.x时,可能写到专栏的中后期Dubbo曾经更新到4.x、5.x,设置有可能是6.x、7.x了。
与其这么吃力吧咧的剖析源码,还不如从零开始带着大家一起手撸一个可能在理论生产环境应用的、分布式、高性能、可扩大的RPC框架。这样,大家也可能直观的感触到一个可能在理论场景应用的RPC框架是如何一步步开发进去的。
置信大家在学完《RPC手撸专栏》后,本人再去看Dubbo源码的话,就相对来说简略多了。你说是不是这样的呢?
你能学到什么?
既然是整个专栏的开篇嘛,必定是要通知你在这个专栏中可能学习到哪些实用的技术的。这里,我就画一张图来直观的通知你在《RPC手撸专栏》可能学到哪些技术吧。
《RPC手撸专栏》整体框架技术全貌如图所示,退出星球后与冰河一起从零实现它,搞定它,当你紧跟冰河节奏搞定这个RPC框架后,你会发现:什么Dubbo、什么gRPC、什么BRPC、什么Hessian、什么Tars、什么Thrift、什么motan、什么hprose等等等等,市面上支流的RPC框架,对你来说就都不叫事儿了,跟紧冰河的节奏,你能够的。
置信小伙伴们看到《RPC手撸专栏》波及到的知识点,应该可能理解到咱们这个从零开始的《RPC手撸专栏》还是比拟硬核的吧?
另外,咱这RPC我的项目反对同步调用、异步调用、回调和单向调用。
- 同步调用
- 异步调用
- 回调
- 单向调用
对,没错,咱们《RPC手撸专栏》最终实现的RPC框架的定位就是尽量能够在理论环境应用。通过这个专栏的学习,让大家深刻理解到可能在理论场景应用的RPC框架是如何一步步开发进去的。
代码构造
我将这个bhrpc我的项目
的定位为可在理论场景应用的、分布式、高性能、可扩大的RPC框架,目前总体上曾经开发并欠缺的性能达到60+个子我的项目,大家看图吧。
我的项目大量应用了对标Dubbo的自定义SPI技术实现高度可扩展性,各位小伙伴能够依据本人的须要,依照SPI的设计要求增加本人实现的自定义插件。
演示成果
说了那么多,咱们一起来看看这个RPC框架的应用成果吧,因为咱们这个RPC框架反对的调用形式有:原生RPC调用、整合Spring(XML/注解)、整合SpringBoot、整合SpringCloud、整合SpringCloud Alibaba,整合Docker和整合K8S七种应用形式。
这里,咱们就以 整合Spring注解的形式 来给大家演示下这个RPC框架。
RPC外围注解阐明
为了让大家更好的理解这个RPC框架,我先给大家看下RPC框架的两个外围注解,一个是RPC的服务提供者注解@RpcService
,一个是RPC的服务调用者注解@RpcReference
。
(1)服务提供者注解@RpcService
的外围源码如下所示。
/** * @author binghe * @version 1.0.0 * @description bhrpc服务提供者注解 */@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Componentpublic @interface RpcService { /** * 接口的Class */ Class<?> interfaceClass() default void.class; /** * 接口的ClassName */ String interfaceClassName() default ""; /** * 版本号 */ String version() default "1.0.0"; /** * 服务分组,默认为空 */ String group() default ""; /** * 提早公布,预留 */ int delay() default 0; /** * 是否导出rpc服务,预留 */ boolean export() default true;}
(2)服务调用者注解@RpcReference
的外围源码如下所示。
/** * @author binghe * @version 1.0.0 * @description bhrpc服务消费者 */@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.FIELD)@Autowiredpublic @interface RpcReference { /** * 版本号 */ String version() default "1.0.0"; /** * 注册核心类型, 目前的类型蕴含:zookeeper、nacos、etcd、consul */ String registryType() default "zookeeper"; /** * 注册地址 */ String registryAddress() default "127.0.0.1:2181"; /** * 负载平衡类型,默认基于ZK的一致性Hash */ String loadBalanceType() default "zkconsistenthash"; /** * 序列化类型,目前的类型蕴含:protostuff、kryo、json、jdk、hessian2、fst */ String serializationType() default "protostuff"; /** * 超时工夫,默认5s */ long timeout() default 5000; /** * 是否异步执行 */ boolean async() default false; /** * 是否单向调用 */ boolean oneway() default false; /** * 代理的类型,jdk:jdk代理, javassist: javassist代理, cglib: cglib代理 */ String proxy() default "jdk"; /** * 服务分组,默认为空 */ String group() default "";}
这里,我只列出了服务提供者注解@RpcService
和服务调用者注解@RpcReference
的局部源码,后续在RPC框架不断完善的过程中,大家就能够缓缓看到源码的全貌和其每个注解实现的性能。这里,我就不具体介绍了。
当然啦,在这个RPC框架实现的原生调用形式中,能够不必这些注解就可能实现近程调用。
成果演示
接口定义
定义两个接口,别离为HelloService和HelloPersonService,源码如下所示。
- HelloService接口源码
public interface HelloService { String hello(String name); String hello(Person person);}
- HelloPersonService接口源码
public interface HelloPersonService { List<Person> getTestPerson(String name,int num);}
实现服务提供者demo
(1)创立HelloService接口和HelloPersonService接口的实现类HelloServiceImpl和HelloPersonServiceImpl,如下所示。
- HelloServiceImpl类源码
@RpcService(interfaceClass = HelloService.class, version = "1.0.0")public class HelloServiceImpl implements HelloService { @Override public String hello(String name) { return "Hello! " + name; } @Override public String hello(Person person) { return "Hello! " + person.getFirstName() + " " + person.getLastName(); }}
能够看到,在HelloServiceImpl类上增加了RPC服务提供者注解@RpcService
,示意将其公布为一个RPC服务。
- HelloPersonServiceImpl类源码
@RpcService(interfaceClass = HelloPersonService.class, version = "1.0.0")public class HelloPersonServiceImpl implements HelloPersonService { @Override public List<Person> getTestPerson(String name, int num) { List<Person> persons = new ArrayList<>(num); for (int i = 0; i < num; ++i) { persons.add(new Person(Integer.toString(i), name)); } return persons; }}
能够看到,在HelloPersonServiceImpl类上增加了RPC服务提供者注解@RpcService
,示意将其公布为一个RPC服务。
(2)创立服务提供者demo的配置类ServerConfig,在ServerConfig类中注入RegistryService注册核心接口的实现类,以及RPC服务提供者的外围类RpcServer,如下所示。
/** * @author binghe * @version 1.0.0 * @description 基于注解的配置类 */@Configuration@ComponentScan(value = {"io.binghe.rpc.demo"})@PropertySource(value = {"classpath:rpc.properties"})public class SpringAnnotationProviderConfig { @Value("${registry.address}") private String registryAddress; @Value("${registry.type}") private String registryType; @Value("${registry.loadbalance.type}") private String registryLoadbalanceType; @Value("${server.address}") private String serverAddress; @Value("${reflect.type}") private String reflectType; @Bean public RpcSpringServer rpcSpringServer(){ return new RpcSpringServer(serverAddress, registryAddress, registryType, registryLoadbalanceType, reflectType); }}
(3)创立服务提供者demo的启动类ServerTest,如下所示。
/** * @author binghe * @version 1.0.0 * @description RPC整合Spring注解,服务提供者demo启动类 */public class ServerTest { public static void main(String[] args){ new AnnotationConfigApplicationContext(ServerConfig.class); }}
实现服务调用者demo
(1)创立测试服务调用者的TestService接口,如下所示。
public interface TestService { void printResult();}
(2)创立TestService接口的实现类TestServiceImpl,在TestServiceImpl类上标注Spring的@Service
注解,并在TestServiceImpl类中通过@RpcReference
注解注入HelloService接口的实现类和HelloPersonService接口的实现类,并实现TestService接口的printResult()办法,源码如下所示。
/** * @author binghe * @version 1.0.0 * @description 测试RPC服务调用者 */@Servicepublic class TestServiceImpl implements TestService { @RpcReference(version = "1.0.0", timeout = 3000, proxy = "javassist", isAsync = true) private HelloService helloService; @RpcReference(proxy = "cglib") private HelloPersonService helloPersonService; @Override public void printResult() { String result = helloService.hello("binghe"); System.out.println(result); result = helloService.hello(new Person("binghe001", "binghe002")); System.out.println(result); System.out.println("================================="); List<Person> personList = helloPersonService.getTestPerson("binghe", 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"})public class ClientConfig {}
(4)创立服务调用者demo的启动类ClientTest,如下所示。
public class ClientTest { public static void main(String[] args){ AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ClientConfig.class); TestService testService = context.getBean(TestService.class); testService.printResult(); context.close(); }}
启动服务测试
(1)启动Zookeeper,这里,为了演示简略,就间接在我本机启动单机Zookeeper好了,启动后的成果如下图所示。
(2)启动服务提供者ServerTest类,启动后输入的日志信息如下所示。
13:43:36,876 INFO ConnectionStateManager:228 - State change: CONNECTED13:43:36,905 INFO RpcClient:79 - use cglib dynamic proxy...13:43:36,942 INFO CuratorFrameworkImpl:235 - Starting13:43:36,943 INFO ZooKeeper:868 - Initiating client connection, connectString=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{"name":"io.binghe.rpc.test.client.HelloService#1.0.0","id":"65eb0d7f-4bf7-4a0a-bafc-1b7e0e030353","address":"127.0.0.1","port":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{"name":"io.binghe.rpc.test.client.HelloPersonService#1.0.0","id":"882a5cdb-f581-4a83-8d56-800a8f14e831","address":"127.0.0.1","port":18866,"sslPort":null,"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,391 INFO ConnectionStateManager:228 - State change: CONNECTED13:56:47,488 INFO RpcClient:76 - use javassist dynamic proxy...13:56:47,518 INFO ConnectionStateManager:228 - State change: CONNECTED13:56:47,545 INFO RpcClient:79 - use cglib dynamic proxy...13:56:48,253 INFO RpcConsumer:85 - connect rpc server 127.0.0.1 on port 18866 success.Hello! bingheHello! binghe001 binghe002=================================0 binghe1 binghe
能够看到,在ClientTest类的命令行输入了近程调用的后果信息。并输入了调用HelloService接口的近程办法应用的是javassist动静代理。调用HelloPersonService接口的近程办法应用的是cglib动静代理。
咱们一起手撸的RPC框架其实还有很多十分弱小的性能,这里,就不一一演示了,前面咱们都会一起手撸来实现它。
一点点倡议
咱们这个专栏属于实战类型比拟强的专栏,加上咱们一起从零开始手撸的RPC框架会波及泛滥的知识点。正所谓纸上得来终觉浅,绝知此事要躬行。冰河心愿大家在学习这个专栏的时候勤入手,跟着专栏一起实现代码。期间要多动脑,多总结,这样才可能加深对各项知识点的了解。切忌眼高手低,学了半天却最终啥也没学会。
好了,明天的开篇文章就到这儿吧,如果文章对你有点帮忙,记得给冰河一键三连哦,欢送将文章转发给更多的小伙伴,冰河将不胜感激~~
一起登程
我会将《RPC手撸专栏》的源码获取形式放到常识星球中,同时在微信上会创立专门的常识星球群,冰河会在常识星球上和星球群里解答球友的发问,关注 冰河技术公号 回复 星球 即可获取优惠券。
欢送大家将文章或者星球转发到群里或者朋友圈,这些内容冰河将用上班、周末、假期的工夫不断完善。通过视频+文章+常识小册+直播+作业的模式与你一起学习、晋升和提高,最终的目标就是晋升你的技术实力,让你退职场走的更远,顺便多赚些钱。
好了,明天就到这儿吧,我是冰河,咱们下期见~~