目標(biāo)
-
基于Sofa-Rpc協(xié)議的Demo演示
- 引入網(wǎng)關(guān)(bootstarp)對(duì)sofa支持的插件
- 使用XML方式引入實(shí)際業(yè)務(wù)接口
- Publish service on server
- 被代理業(yè)務(wù)增加yml配置
- 配置接口注冊(cè)admin地址信息
- 啟動(dòng)真實(shí)業(yè)務(wù)服務(wù)
-
soul-spring-boot-starter-client-sofa注入解析
- sofaConfig讀取soul.sofa.xx配置信息
- SofaServiceBeanPostProcessor類注入Bean
- BeanPostProcessor執(zhí)行
- admin頁面展示已經(jīng)注冊(cè)的接口
演示和總結(jié)
Sofa-Rpc介紹
基于Sofa-Rpc協(xié)議的Demo演示
引入網(wǎng)關(guān)(bootstarp)對(duì)sofa支持的插件
<dependency>
<groupId>com.alipay.sofa</groupId>
<artifactId>sofa-rpc-all</artifactId>
<version>5.7.6</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-client</artifactId>
<version>4.0.1</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>4.0.1</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>4.0.1</version>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>soul-spring-boot-starter-plugin-sofa</artifactId>
<version>2.2.1</version>
</dependency>
其中主要的依賴有sofa-rpc-all和封裝的soul-spring-boot-starter-plugin-sofa
使用xml方式引入業(yè)務(wù)接口
<sofa:service ref="sofaTestService" interface="org.dromara.soul.examples.dubbo.api.service.DubboTestService"> <sofa:binding.bolt/> </sofa:service> @ImportResource({ "classpath*:invoke-server-example.xml"})
使用注解@ImportResource() 自動(dòng)注入
Publish service on server
Configure the followings in the xml file. When the Spring context is refreshed, SOFABoot registers the service implementation on the server, communicates with the client by bolt protocol, and publishes metadata such as address to registry center (local file is used as registry center by default).
上文從Sofa-rpc 官網(wǎng)Getting started 摘錄,當(dāng)被代理服務(wù)Bean注入完Spring Context刷新,SOFABoot 就開始將需要被代理的接口注入到Sofa server
被代理業(yè)務(wù)增加yml配置
- 本實(shí)例sofa采用zookeeper作為注冊(cè)中心,所以本地要啟動(dòng)一個(gè)zk,這里使用了之前dubbo實(shí)例中的zk,同時(shí)配置sofa配置如下:
com:
alipay:
sofa:
rpc: registry-address: zookeeper://127.0.0.1:2181
bolt-port: 8888
Bolt protocol port 是8888
配置接口注冊(cè)admin地址信息
soul:
sofa:
adminUrl: http://localhost:9095
contextPath: /sofa
appName: sofa
啟動(dòng)真實(shí)業(yè)務(wù)服務(wù)
說明Sofa客戶端啟動(dòng)成功
從服務(wù)啟動(dòng)日志中可以看出 Sofa 與 soul-sofa-client 各個(gè)服務(wù)的配置先后順序
Soul-Spring-boot-starter-client-sofa 注入解析
sofaConfig 讀取soul.sofa.xx 配置信息
@Bean
@ConfigurationProperties(prefix = "soul.sofa")
public SofaConfig sofaConfig() {
return new SofaConfig();
}
SofaServiceBeanPostProcessor 類注入Bean
public SofaServiceBeanPostProcessor(final SofaConfig sofaConfig) {
String contextPath = sofaConfig.getContextPath();
String adminUrl = sofaConfig.getAdminUrl();
if (contextPath == null || "".equals(contextPath)
|| adminUrl == null || "".equals(adminUrl)) {
throw new RuntimeException("sofa client must config the contextPath, adminUrl");
}
this.sofaConfig = sofaConfig;
url = sofaConfig.getAdminUrl() + "/soul-client/sofa-register";
executorService = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>());
}
獲取注冊(cè)admin的sofa相關(guān)參數(shù)和創(chuàng)建執(zhí)行注冊(cè)的線程池
BeanPostProcessor執(zhí)行
@Override
public Object postProcessAfterInitialization(final Object bean, final String beanName) throws BeansException {
if (bean instanceof ServiceFactoryBean) {
executorService.execute(() -> handler((ServiceFactoryBean) bean));
}
return bean;
}
private void handler(final ServiceFactoryBean serviceBean) {
Class<?> clazz;
try {
clazz = ((Service) Objects.requireNonNull(serviceBean.getObject())).getTarget().getClass();
} catch (Exception e) {
log.error("failed to get sofa target class");
return;
}
if (ClassUtils.isCglibProxyClass(clazz)) {
String superClassName = clazz.getGenericSuperclass().getTypeName();
try {
clazz = Class.forName(superClassName);
} catch (ClassNotFoundException e) {
log.error(String.format("class not found: %s", superClassName));
return;
}
}
final Method[] methods = ReflectionUtils.getUniqueDeclaredMethods(clazz);
for (Method method : methods) {
SoulSofaClient soulSofaClient = method.getAnnotation(SoulSofaClient.class);
if (Objects.nonNull(soulSofaClient)) {
RegisterUtils.doRegister(buildJsonParams(serviceBean, soulSofaClient, method), url, RpcTypeEnum.SOFA);
}
}
}
主要是使用@SoulSofaClient注解配合反射拿到需要代理的接口
admin 頁面展示已經(jīng)注冊(cè)的接口
到這里前期初始化工作完成,下面看下調(diào)用演示
演示與總結(jié)
前面幾篇最后都沒有總結(jié),今天到此我們已經(jīng)演示了SpringMvc/SpringCloud,Dubbo,Sofa不同協(xié)議的代理方式,總結(jié)一下,soul的設(shè)計(jì)思路:
- 不同協(xié)議封裝成獨(dú)立的客戶端包,至于使用的協(xié)議規(guī)范那一套全部在客戶端中封裝了,使用者只需要根據(jù)對(duì)應(yīng)的附加配置信息即可。
- 為每個(gè)客戶端創(chuàng)建一個(gè)spring boot starter,使用了SpringBoot 自動(dòng)化配置的原理,讀取 META-INF/spring.factories 中的自動(dòng)化配置類進(jìn)行向admin注冊(cè)
- 每個(gè)協(xié)議的客戶端基本實(shí)現(xiàn)一樣,通過BeanPostProcessor和ApplicationListener<ContextRefreshedEvent>加上自定義注解從而達(dá)到接口的注冊(cè)
Sofa-Rpc介紹
SOFARPC 是螞蟻金服開源的一款基于 Java 實(shí)現(xiàn)的 RPC 服務(wù)框架,為應(yīng)用之間提供遠(yuǎn)程服務(wù)調(diào)用能力,具有高可伸縮性,高容錯(cuò)性,目前螞蟻金服所有的業(yè)務(wù)的相互間的 RPC 調(diào)用都是采用 SOFARPC。SOFARPC 為用戶提供了負(fù)載均衡,流量轉(zhuǎn)發(fā),鏈路追蹤,鏈路數(shù)據(jù)透?jìng)鳎收咸蕹裙δ堋?/p>
SOFARPC 還支持不同的協(xié)議,目前包括 bolt,RESTful,dubbo,H2C 協(xié)議進(jìn)行通信。其中 bolt 是螞蟻金融服務(wù)集團(tuán)開放的基于 Netty 開發(fā)的網(wǎng)絡(luò)通信框架。
基本原理
當(dāng)一個(gè) SOFARPC 的應(yīng)用啟動(dòng)的時(shí)候,如果發(fā)現(xiàn)當(dāng)前應(yīng)用需要發(fā)布 RPC 服務(wù)的話,那么 SOFARPC 會(huì)將這些服務(wù)注冊(cè)到服務(wù)注冊(cè)中心上。如圖中 Service 指向 Registry。
- 當(dāng)引用這個(gè)服務(wù)的 SOFARPC 應(yīng)用啟動(dòng)時(shí),會(huì)從服務(wù)注冊(cè)中心訂閱到相應(yīng)服務(wù)的元數(shù)據(jù)信息。服務(wù)注冊(cè)中心收到訂閱請(qǐng)求后,會(huì)將發(fā)布方的元數(shù)據(jù)列表實(shí)時(shí)推送給服務(wù)引用方。如圖中 Registry 指向 Reference。
- 當(dāng)服務(wù)引用方拿到地址以后,就可以從中選取地址發(fā)起調(diào)用了。如圖中 Reference 指向 Service。
后面研究單獨(dú)插件時(shí)候好好研究下他的源碼