分析请求时 RPCType 如何产生
调用网关转发服务的过程中, 具体是走入 Dubbo 转发, Http 转发, 亦或者 SpringCloud 转发, 都是由上下文中请求对象的 RpcType 属性来确定的, 这次分析下它的由来.
直接找到被 GlobalPlugin (插件链的第一个插件) 调用的 DefaultSoulContextBuilder 类:
public class DefaultSoulContextBuilder implements SoulContextBuilder {
@Override
public SoulContext build(final ServerWebExchange exchange) {
final ServerHttpRequest request = exchange.getRequest();
String path = request.getURI().getPath();
// 从缓存中获得各个路径的元数据信息
MetaData metaData = MetaDataCache.getInstance().obtain(path);
if (Objects.nonNull(metaData) && metaData.getEnabled()) {
exchange.getAttributes().put(Constants.META_DATA, metaData);
}
return transform(request, metaData);
}
private SoulContext transform(final ServerHttpRequest request, final MetaData metaData) {
// ...
if (Objects.nonNull(metaData) && metaData.getEnabled()) {
// 根据 metaData 的 rpcType, 判断走 springcloud or dubbo
if (RpcTypeEnum.SPRING_CLOUD.getName().equals(metaData.getRpcType())) {
setSoulContextByHttp(soulContext, path);
soulContext.setRpcType(metaData.getRpcType());
} else {
setSoulContextByDubbo(soulContext, metaData);
}
} else {
// metaData 为空或者 enable=false 会直接写为 HTTP 请求
setSoulContextByHttp(soulContext, path);
soulContext.setRpcType(RpcTypeEnum.HTTP.getName());
}
// ...
return soulContext;
}
}
重点看这句 MetaDataCache.getInstance().obtain(path)
:
public final class MetaDataCache {
private static final ConcurrentMap<String, MetaData> META_DATA_MAP = Maps.newConcurrentMap();
public MetaData obtain(final String path) {
// 从缓存中取出对应资源的元数据信息
MetaData metaData = META_DATA_MAP.get(path);
if (Objects.isNull(metaData)) {
// 没有则循环再次匹配, PathMatchUtils.match 不细分析
String key = META_DATA_MAP.keySet().stream().filter(k -> PathMatchUtils.match(k, path)).findFirst().orElse("");
return META_DATA_MAP.get(key);
}
return metaData;
}
}
通过这里可以知道, 请求进入通过元数据缓存匹配拿到 RpcType
类型, 那么 META_DATA_MAP
缓存的数据从哪里来呢? 最终可以追踪到网关与管理后台的同步配置这, 网关这边的监听类 SoulWebsocketClient:
public final class SoulWebsocketClient extends WebSocketClient {
@Override
public void onMessage(final String result) {
handleResult(result);
}
private void handleResult(final String result) {
WebsocketData websocketData = GsonUtils.getInstance().fromJson(result, WebsocketData.class);
// 得到更新数据的类型, 比如 metadata(元数据) 、plugins(插件)、ruleData(规则)、selector(选择器)等
ConfigGroupEnum groupEnum = ConfigGroupEnum.acquireByName(websocketData.getGroupType());
// 得到更新事件, 比如 refresh、update、myself 等
String eventType = websocketData.getEventType();
// 具体要更新的配置
String json = GsonUtils.getInstance().toJson(websocketData.getData());
// 通过 groupEnum 更新类型, 获得数据处理子类 DataHandler
websocketDataHandler.executor(groupEnum, json, eventType);
}
}
SoulWebsocketClient 类借助 WebSocket 客户端的实现, 与管理后台建立 WebSocket 连接并同步配置, 在收到信息之后, 判断是什么数据类型, 并调用具体的 DataHandler 数据处理子类.
它们都有共同的抽象父类 AbstractDataHandler, 作为更新事件分发器的 handler()
方法:
public abstract class AbstractDataHandler<T> implements DataHandler {
@Override
public void handle(final String json, final String eventType) {
List<T> dataList = convert(json);
if (CollectionUtils.isNotEmpty(dataList)) {
DataEventTypeEnum eventTypeEnum = DataEventTypeEnum.acquireByName(eventType);
switch (eventTypeEnum) {
case REFRESH:
case MYSELF:
doRefresh(dataList);
break;
case UPDATE:
case CREATE:
doUpdate(dataList);
break;
case DELETE:
doDelete(dataList);
break;
default:
break;
}
}
}
}
这次分析的是元数据, 就继续进入 MetaDataHandler 类查看代码, 一路追踪可以在 MetaDataAllSubscriber 类中找到元数据缓存的更新:
public class MetaDataAllSubscriber implements MetaDataSubscriber {
@Override
public void onSubscribe(final MetaData metaData) {
MetaDataCache.getInstance().cache(metaData);
}
}
TIPS1 如何主动触发元数据的更新?
在管理后台的元数据页, 点击"同步配置"即可
TIPS2 如果访问请求时出现404等情况如何排查?
在 MetaDataCache 的 obtain()
方法上断点:
public MetaData obtain(final String path) {
// 检查下 META_DATA_MAP 里都有什么数据
MetaData metaData = META_DATA_MAP.get(path);
if (Objects.isNull(metaData)) {
String key = META_DATA_MAP.keySet().stream().filter(k -> PathMatchUtils.match(k, path)).findFirst().orElse("");
return META_DATA_MAP.get(key);
}
return metaData;
}
- 检查
META_DATA_MAP
元数据缓存中的路径, 是否能与你访问的path
相匹配 - 查看管理后台的"元数据管理"页面, 是否存在相应元数据, 且状态是"开启"
- 检查服务是否启动, 如果不启动并注册, 后台管理项目不会发送相应元数据, 即使管理页面存在且开启. (启动服务项目后若元数据缓存中未及时触发更新, 可以手动点下同步数据)
- 一个小坑: 如果你要请求 dubbo 服务但输错了路径, 肯定会走入到Http匹配那块去, 因为如果元数据缓存匹配到的路径为 null, 默认 RpcType 为 HTTP.