大家好,我是冰河~~
沒錯,這次冰河又要搞事情了,這次準備下手的是RPC框架項目。為什么要對RPC框架項目下手呢,因為在如今分布式、微服務乃至云原生不斷發(fā)展的過程中,RPC作為底層必不可少的通信組件,被廣泛應用在分布式、微服務和云原生項目中。
為啥要開發(fā)RPC框架?
事情是這樣的,在開發(fā)這個RPC框架之前,我花費了不少時間算是對Dubbo框架徹底研究透徹了。
冰河在擼透了Dubbo2.x和Dubbo3.x的源碼之后,本來想給大家寫一個Dubbo源碼解析的專欄。為此,我其實私下準備了一個多月:畫流程圖、分析源碼、寫測試Demo,自己在看Dubbo源碼時,也為Dubbo源碼添加了非常詳細的注釋。這里,就包含Dubbo2.x和Dubbo3.x的源碼。

當我就這么熬夜肝文一個多月后,突然發(fā)現(xiàn)一個問題:Dubbo經(jīng)過多年不斷的迭代開發(fā),它的源碼已經(jīng)非常多了,如果以文章的形式將Dubbo的源碼面面俱到的分析到位,那還不知道要寫到何年何月去了。當我寫文章分析Dubbo的最新版本3.x時,可能寫到專欄的中后期Dubbo已經(jīng)更新到4.x、5.x,設置有可能是6.x、7.x了。
與其這么費勁吧咧的分析源碼,還不如從零開始帶著大家一起手擼一個能夠在實際生產(chǎn)環(huán)境使用的、分布式、高性能、可擴展的RPC框架。這樣,大家也能夠直觀的感受到一個能夠在實際場景使用的RPC框架是如何一步步開發(fā)出來的。
相信大家在學完《RPC手擼專欄》后,自己再去看Dubbo源碼的話,就相對來說簡單多了。你說是不是這樣的呢?
你能學到什么??
既然是整個專欄的開篇嘛,肯定是要告訴你在這個專欄中能夠?qū)W習到哪些實用的技術的。這里,我就畫一張圖來直觀的告訴你在《RPC手擼專欄》能夠?qū)W到哪些技術吧。
《RPC手擼專欄》整體框架技術全貌如圖所示,加入星球后與冰河一起從零實現(xiàn)它,搞定它,當你緊跟冰河節(jié)奏搞定這個RPC框架后,你會發(fā)現(xiàn):什么Dubbo、什么gRPC、什么BRPC、什么Hessian、什么Tars、什么Thrift、什么motan、什么hprose等等等等,市面上主流的RPC框架,對你來說就都不叫事兒了,跟緊冰河的節(jié)奏,你可以的。

相信小伙伴們看到《RPC手擼專欄》涉及到的知識點,應該能夠了解到咱們這個從零開始的《RPC手擼專欄》還是比較硬核的吧?
另外,咱這RPC項目支持同步調(diào)用、異步調(diào)用、回調(diào)和單向調(diào)用。




對,沒錯,咱們《RPC手擼專欄》最終實現(xiàn)的RPC框架的定位就是盡量可以在實際環(huán)境使用。通過這個專欄的學習,讓大家深入了解到能夠在實際場景使用的RPC框架是如何一步步開發(fā)出來的。
代碼結構?
我將這個bhrpc項目的定位為可在實際場景使用的、分布式、高性能、可擴展的RPC框架,目前總體上已經(jīng)開發(fā)并完善的功能達到60+個子項目,大家看圖吧。

項目大量使用了對標Dubbo的自定義SPI技術實現(xiàn)高度可擴展性,各位小伙伴可以根據(jù)自己的需要,按照SPI的設計要求添加自己實現(xiàn)的自定義插件。

演示效果?
說了那么多,咱們一起來看看這個RPC框架的使用效果吧,因為咱們這個RPC框架支持的調(diào)用方式有:原生RPC調(diào)用、整合Spring(XML/注解)、整合SpringBoot、整合SpringCloud、整合SpringCloud Alibaba,整合Docker和整合K8S七種使用方式。
這里,咱們就以 整合Spring注解的方式 來給大家演示下這個RPC框架。
RPC核心注解說明
為了讓大家更好的了解這個RPC框架,我先給大家看下RPC框架的兩個核心注解,一個是RPC的服務提供者注解@RpcService?,一個是RPC的服務調(diào)用者注解@RpcReference。
(1)服務提供者注解@RpcService的核心源碼如下所示。
/**
* @author binghe
* @version 1.0.0
* @description bhrpc服務提供者注解
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface RpcService {
/**
* 接口的Class
*/
Class<?> interfaceClass() default void.class;
/**
* 接口的ClassName
*/
String interfaceClassName() default "";
/**
* 版本號
*/
String version() default "1.0.0";
/**
* 服務分組,默認為空
*/
String group() default "";
/**
* 延遲發(fā)布,預留
*/
int delay() default 0;
/**
* 是否導出rpc服務,預留
*/
boolean export() default true;
}
(2)服務調(diào)用者注解@RpcReference的核心源碼如下所示。
/**
* @author binghe
* @version 1.0.0
* @description bhrpc服務消費者
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Autowired
public @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;
/**
* 是否異步執(zhí)行
*/
boolean async() default false;
/**
* 是否單向調(diào)用
*/
boolean oneway() default false;
/**
* 代理的類型,jdk:jdk代理, javassist: javassist代理, cglib: cglib代理
*/
String proxy() default "jdk";
/**
* 服務分組,默認為空
*/
String group() default "";
}
這里,我只列出了服務提供者注解@RpcService和服務調(diào)用者注解@RpcReference的部分源碼,后續(xù)在RPC框架不斷完善的過程中,大家就可以慢慢看到源碼的全貌和其每個注解實現(xiàn)的功能。這里,我就不詳細介紹了。
當然啦,在這個RPC框架實現(xiàn)的原生調(diào)用方式中,可以不用這些注解就能夠?qū)崿F(xiàn)遠程調(diào)用。
效果演示
接口定義
定義兩個接口,分別為HelloService和HelloPersonService,源碼如下所示。
public interface HelloService {
String hello(String name);
String hello(Person person);
}
public interface HelloPersonService {
List<Person> getTestPerson(String name,int num);
}
實現(xiàn)服務提供者demo
(1)創(chuàng)建HelloService接口和HelloPersonService接口的實現(xiàn)類HelloServiceImpl和HelloPersonServiceImpl,如下所示。
@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?
?,表示將其發(fā)布為一個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,表示將其發(fā)布為一個RPC服務。
(2)創(chuàng)建服務提供者demo的配置類ServerConfig,在ServerConfig類中注入RegistryService注冊中心接口的實現(xiàn)類,以及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)創(chuàng)建服務提供者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);
}
}
實現(xiàn)服務調(diào)用者demo
(1)創(chuàng)建測試服務調(diào)用者的TestService接口,如下所示。
public interface TestService {
void printResult();
}
(2)創(chuàng)建TestService接口的實現(xiàn)類TestServiceImpl,在TestServiceImpl類上標注Spring的??@Service?
?注解,并在TestServiceImpl類中通過??@RpcReference?
?注解注入HelloService接口的實現(xiàn)類和HelloPersonService接口的實現(xiàn)類,并實現(xiàn)TestService接口的printResult()方法,源碼如下所示。
/**
* @author binghe
* @version 1.0.0
* @description 測試RPC服務調(diào)用者
*/
@Service
public 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類的源碼我們可以看到,遠程調(diào)用HelloService接口的方法時使用的是javassist動態(tài)代理,遠程調(diào)用HelloPersonService接口時,使用的是cglib動態(tài)代理。
(3)創(chuàng)建服務調(diào)用者demo的配置類ClientConfig,如下所示。
@Configuration
@ComponentScan(value = {"io.binghe.rpc.*"})
@PropertySource(value = {"classpath:rpc.properties"})
public class ClientConfig {
}
(4)創(chuàng)建服務調(diào)用者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: CONNECTED
13:43:36,905 INFO RpcClient:79 - use cglib dynamic proxy...
13:43:36,942 INFO CuratorFrameworkImpl:235 - Starting
13:43:36,943 INFO ZooKeeper:868 - Initiating client connection, cnotallow=127.0.0.1:2181
可以看到,服務提供者已經(jīng)將發(fā)布的服務注冊到了Zookeeper中。
(3)登錄Zookeeper客戶端查看Zookeeper中注冊的服務,如下所示。
- 查看HelloService接口發(fā)布的服務信息
[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接口發(fā)布的服務信息
[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接口發(fā)布的服務都已經(jīng)被注冊到Zookeeper了。
(4)啟動服務提供者ClientTest類,實現(xiàn)RPC調(diào)用,輸出的日志信息如下所示。
13:56:47,391 INFO ConnectionStateManager:228 - State change: CONNECTED
13:56:47,488 INFO RpcClient:76 - use javassist dynamic proxy...
13:56:47,518 INFO ConnectionStateManager:228 - State change: CONNECTED
13: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! binghe
Hello! binghe001 binghe002
=================================
0 binghe
1 binghe
可以看到,在ClientTest類的命令行輸出了遠程調(diào)用的結果信息。并輸出了調(diào)用HelloService接口的遠程方法使用的是javassist動態(tài)代理。調(diào)用HelloPersonService接口的遠程方法使用的是cglib動態(tài)代理。
咱們一起手擼的RPC框架其實還有很多非常強大的功能,這里,就不一一演示了,后面咱們都會一起手擼來實現(xiàn)它。
一點點建議?
咱們這個專欄屬于實戰(zhàn)類型比較強的專欄,加上咱們一起從零開始手擼的RPC框架會涉及眾多的知識點。正所謂紙上得來終覺淺,絕知此事要躬行。冰河希望大家在學習這個專欄的時候勤動手,跟著專欄一起實現(xiàn)代碼。期間要多動腦,多總結,這樣才能夠加深對各項知識點的理解。切忌眼高手低,學了半天卻最終啥也沒學會。