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ù)透傳,故障剔除等功能。
1.8.3.1SOFARPC 配置流程
首先我們在Client 端需要加入 sofaRpc 和 soul-sofa 的依賴。
<dependency>
<groupId>com.alipay.sofa</groupId>
<artifactId>rpc-sofa-boot-starter</artifactId>
<version>${rpc-sofa-boot-starter.version}</version>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>soul-spring-boot-starter-client-sofa</artifactId>
<version>${soul.version}</version>
</dependency>
我們使用 zk 作為 sofaRpc 的注冊中心,所以需要做如下配置。
com:
alipay:
sofa:
rpc:
registry-address: zookeeper://127.0.0.1:2181
bolt-port: 8888
sofaRpc 需要定義一個(gè) xml 文件類似于 dubbo.xml 配置暴露的服務(wù)。
<sofa:service ref="sofaSingleParamService" interface="org.dromara.soul.examples.sofa.api.service.SofaSingleParamService">
<sofa:binding.bolt/>
</sofa:service>
<sofa:service ref="sofaMultiParamService" interface="org.dromara.soul.examples.sofa.api.service.SofaMultiParamService">
<sofa:binding.bolt/>
</sofa:service>
最后我們在各個(gè) SofaRpc 服務(wù)中加入 @SoulSofaClient 注解即可,這里定義了服務(wù)的訪問路徑,當(dāng)我們注冊成功后就會在 zookeeper 中發(fā)現(xiàn)如下服務(wù)。并且 soul admin 也會有對應(yīng)的路徑。
同時(shí)我們需要在 soul bootstrap 引入 sofa-plugin 依賴和 zookeeper 的依賴。
1.8.3.2 sofa 插件詳解
還是按慣用流程我們先到 SofaPluginConfiguration ,它定義了 sofaRpc 在 Boostrap 中處理類。其中主要的信息有 BodyParamPlugin SofaPlugin & SofaResponsePlugin 。我們先看一下 BodyParamPlugin, BodyParamPlugin 的getOrder 方法如下,這就是類似于聲明一個(gè)前置處理器,根據(jù)我們之前的經(jīng)驗(yàn),先看 execute 方法。
public int getOrder() {
return PluginEnum.SOFA.getCode() - 1;
}
excute 主要是根據(jù)請求類型,進(jìn)行參數(shù)封裝,分為 application/json 和 x-www-form-urlencoded。
@Override
public Mono<Void> execute(final ServerWebExchange exchange, final SoulPluginChain chain) {
final ServerHttpRequest request = exchange.getRequest();
// 獲取上下文
final SoulContext soulContext = exchange.getAttribute(Constants.CONTEXT);
if (Objects.nonNull(soulContext) && RpcTypeEnum.SOFA.getName().equals(soulContext.getRpcType())) {
MediaType mediaType = request.getHeaders().getContentType();
ServerRequest serverRequest = ServerRequest.create(exchange, messageReaders);
// 判斷請求參數(shù)類型-》application/json
if (MediaType.APPLICATION_JSON.isCompatibleWith(mediaType)) {
return body(exchange, serverRequest, chain);
}
// x-www-form-urlencoded 類型
if (MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(mediaType)) {
return formData(exchange, serverRequest, chain);
}
return query(exchange, serverRequest, chain);
}
return chain.execute(exchange);
}
body 就是將 serverRequest 中的body 內(nèi)容放到交換區(qū) exchange 中,然后執(zhí)行下一個(gè) plugin 即 sofaplugin
private Mono<Void> body(final ServerWebExchange exchange, final ServerRequest serverRequest, final SoulPluginChain chain) {
return serverRequest.bodyToMono(String.class)
.switchIfEmpty(Mono.defer(() -> Mono.just(""))) // 為空則使用空字符串
.flatMap(body -> {
exchange.getAttributes().put(Constants.SOFA_PARAMS, body); // 將body 塞入 sofa_param
// 執(zhí)行以下一個(gè)插件 即 sofaplugin
return chain.execute(exchange);
});
}
sofaplugin 的 excute 上節(jié)已經(jīng)解析過,即先匹配條件是否符合 然后執(zhí)行插件的 doexecute 方法。我們看看 doExecute 方法,我們可以看到最后它調(diào)用的是 sofaProxyService 的 genericInvoker , 嗯有點(diǎn) dubbo 泛化調(diào)用的意思了。
@Override
protected Mono<Void> doExecute(final ServerWebExchange exchange, final SoulPluginChain chain, final SelectorData selector, final RuleData rule) {
// 取出參數(shù)
String body = exchange.getAttribute(Constants.SOFA_PARAMS);
// 取出上下文對象
SoulContext soulContext = exchange.getAttribute(Constants.CONTEXT);
assert soulContext != null;
MetaData metaData = exchange.getAttribute(Constants.META_DATA);
// 校驗(yàn)元數(shù)據(jù)
if (!checkMetaData(metaData)) {
assert metaData != null;
log.error(" path is :{}, meta data have error.... {}", soulContext.getPath(), metaData.toString());
exchange.getResponse().setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
Object error = SoulResultWrap.error(SoulResultEnum.META_DATA_ERROR.getCode(), SoulResultEnum.META_DATA_ERROR.getMsg(), null);
return WebFluxResultUtils.result(exchange, error);
}
// 檢測是否為空
if (StringUtils.isNoneBlank(metaData.getParameterTypes()) && StringUtils.isBlank(body)) {
exchange.getResponse().setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
Object error = SoulResultWrap.error(SoulResultEnum.SOFA_HAVE_BODY_PARAM.getCode(), SoulResultEnum.SOFA_HAVE_BODY_PARAM.getMsg(), null);
return WebFluxResultUtils.result(exchange, error);
}
// 調(diào)用 sofaProxyService 獲取返回結(jié)果
final Mono<Object> result = sofaProxyService.genericInvoker(body, metaData, exchange);
return result.then(chain.execute(exchange));
}
其主要代碼如下,sofa 的調(diào)用和 dubbo 調(diào)用類似,就是通過泛化調(diào)用的方式進(jìn)行調(diào)用,然后將返回結(jié)果放到交換區(qū)。
public Mono<Object> genericInvoker(final String body, final MetaData metaData, final ServerWebExchange exchange) throws SoulException {
// 構(gòu)造 genericService
GenericService genericService = reference.refer();
Pair<String[], Object[]> pair;
// 構(gòu)造參數(shù)
pair = sofaParamResolveService.buildParameter(body, metaData.getParameterTypes());
CompletableFuture<Object> future = new CompletableFuture<>();
RpcInvokeContext.getContext().setResponseCallback(new SofaResponseCallback<Object>() {
@Override
public void onAppResponse(final Object o, final String s, final RequestBase requestBase) {
// 通知future獲取結(jié)果
future.complete(o);
}
});
// 真正調(diào)用服務(wù)
genericService.$genericInvoke(metaData.getMethodName(), pair.getLeft(), pair.getRight());
return Mono.fromFuture(future.thenApply(ret -> {
// 獲取到真正結(jié)果
GenericObject genericObject = (GenericObject) ret;
// 將結(jié)果寫入交換區(qū)
exchange.getAttributes().put(Constants.SOFA_RPC_RESULT, genericObject.getFields());
// 設(shè)置狀態(tài)
exchange.getAttributes().put(Constants.CLIENT_RESPONSE_RESULT_TYPE, ResultEnum.SUCCESS.getName());
return ret;
})).onErrorMap(SoulException::new);
}
我們最后看一下 SofaResponsePlugin 插件,其主要是doexecute 方法
@Override
public Mono<Void> execute(final ServerWebExchange exchange, final SoulPluginChain chain) {
return chain.execute(exchange).then(Mono.defer(() -> {
final Object result = exchange.getAttribute(Constants.SOFA_RPC_RESULT); // 獲取返回結(jié)果
if (Objects.isNull(result)) { // 結(jié)果為空則改為錯(cuò)誤
Object error = SoulResultWrap.error(SoulResultEnum.SERVICE_RESULT_ERROR.getCode(), SoulResultEnum.SERVICE_RESULT_ERROR.getMsg(), null);
return WebFluxResultUtils.result(exchange, error);
}
Object success = SoulResultWrap.success(SoulResultEnum.SUCCESS.getCode(), SoulResultEnum.SUCCESS.getMsg(), JsonUtils.removeClass(result)); // 構(gòu)造成功結(jié)果
return WebFluxResultUtils.result(exchange, success);//返回結(jié)果
}));
}
至此,整個(gè)請求的流程就走完了。
1.8.3.3 總結(jié)
在這節(jié)里我們學(xué)習(xí)了 soul 集成 sofaRpc 的流程,sofaRpc 的整個(gè)流程和 Dubbo 的非常像,稍微有區(qū)別是參數(shù)的構(gòu)造這方面。