相信大家碰到源码时经常无从下手🙃,不知道从哪开始阅读,面对大量代码晕头转向,索性就读不下去了,又浪费了一次提升自己的机会😭。
我认为有一种方法,可以解决大家的困扰!那就是通过阅读某一次开源的【PR】、【ISSUE】,从这个入口出发去阅读源码!!
至此,我们发现自己开始从大量堆砌的源码中脱离开来😀。豁然开朗,柳暗花明又一村。
ShenYu是一个异步的,高性能的,跨语言的,响应式的 API 网关。有关ShenYu的介绍可以戳这。
一、前瞻
好了,今天我们来看看本次的PR提交
我们看下PR的标题和Concersation的头一句,大概意思就是重构注册中心数据同步到ShenYu网关的方式。
阅读源码前,我们先思考我们本次要阅读的线索:
- 贡献者是怎么重构的
- 重构了相对于原先旧方式有什么好处
二、探索
本次PR代码量还是很巨大了,从PR提交可以看到一共有82次提交!!!不要怕,我们先整体浏览下整个提交,在高的维度去看贡献者具体做了什么动作。
总的来说可以分为两部分,重构提供同步数据的客户端、重构使用同步数据的Admin调用端(准确来说是监听端,下文都书写为调用端更容易理解)。
我们先看看Nacos的重构步骤,其他注册中心重构逻辑大致也是一致的。
public class NacosSyncDataService extends AbstractNodeDataSyncService implements SyncDataService {
}
Nacos同步数据的NacosSyncDataService
继承了AbstractNodeDataSyncService
,我们应该重点阅读父类约定的规则和提供的实现。
上图可以看到有两个抽象类AbstractNodeDataSyncService、AbstractPathDataSyncService,NodeData、PataData顾名思义前者是处理Nacos这类节点类型的注册中心,后者就是处理ZooKeeper这类的路径节点注册中心。
大家思考下,本次PR提交一直提到同步数据,那究竟靠什么来同步数据?其实Nacos有配置中心的功能,同步数据也就是同步Nacos配置中心的数据,方便ShenYu修改数据就立刻生效而不用重启。
可以看到AbstractNodeDataSyncService
的startWatch方法,各个watch方法来监听配置中心的数据。
protected void startWatch() {
try {
final List<String> pluginNames = getConfigListOnWatch(changeData.getPluginDataId() + DefaultNodeConstants.POINT_LIST, updateData -> {
List<String> changedPluginNames = GsonUtils.getInstance().fromList(updateData, String.class);
watcherPlugin(changedPluginNames);
});
watcherPlugin(pluginNames);
watchCommonList(changeData.getAuthDataId() + DefaultNodeConstants.JOIN_POINT, this::cacheAuthData, this::unCacheAuthData);
watchCommonList(changeData.getMetaDataId() + DefaultNodeConstants.JOIN_POINT, this::cacheMetaData, this::unCacheMetaData);
} catch (Exception e) {
throw new ShenyuException(e);
}
}
其中getServiceConfig
就是留给各个子类来实现的功能,不同的配置中心,不同获取配置的方法。
protected abstract String getServiceConfig(String key, Consumer<String> updateHandler, Consumer<String> deleteHandler);
我们来看看获取Nacos配置的子类NacosSyncDataService是怎么获取的。
public class NacosSyncDataService extends AbstractNodeDataSyncService implements SyncDataService {
private final ConfigService configService;
@Override
protected String getServiceConfig(final String key, final Consumer<String> updateHandler, final Consumer<String> deleteHandler) {
try {
if (watchCache.containsKey(key)) {
return null;
}
final Listener listener = new Listener() {
@Override
public Executor getExecutor() {
return null;
}
@Override
public void receiveConfigInfo(final String configInfo) {
try {
if (StringUtils.isBlank(configInfo) && deleteHandler != null) {
deleteHandler.accept(key);
} else {
updateHandler.accept(configInfo);
}
} catch (Exception e) {
LOG.error("nacos sync listener receiveConfigInfo error", e);
}
}
};
final String serviceConfig = configService.getConfigAndSignListener(key, NacosPathConstants.GROUP, 3000, listener);
watchCache.put(key, listener);
return serviceConfig;
} catch (Exception e) {
throw new ShenyuException(e);
}
}
}
可以看到该类通过调用Nacos提供的ConfigService服务来进行获取配置,同时Listener就是监听器的作用。
大致看完了客户端的重构,我们来看看ShenYu Admin调用端的重构。既然客户端是同步配置中心的数据,那调用端也就是起到创建、更新、删除配置的作用。
回顾下上文提到的AbstractNodeDataSyncService和AbstractPathDataSyncService,上图的AbstractNodeDataChangeListener和AbstractPathDataChangeListener同样是实现某些具体的功能,需要子类实现的延迟到子类实现。
我们看下子类NacosDataChangedListener实现的功能。
public class NacosDataChangedListener extends AbstractNodeDataChangedListener {
private static final Logger LOG = LoggerFactory.getLogger(NacosDataChangedListener.class);
private final ConfigService configService;
public NacosDataChangedListener(final ConfigService configService) {
super(new ChangeData(NacosPathConstants.PLUGIN_DATA_ID, NacosPathConstants.SELECTOR_DATA_ID,
NacosPathConstants.RULE_DATA_ID, NacosPathConstants.AUTH_DATA_ID, NacosPathConstants.META_DATA_ID,
NacosPathConstants.PROXY_SELECTOR_DATA_ID, NacosPathConstants.DISCOVERY_DATA_ID));
this.configService = configService;
}
@Override
public void doPublishConfig(final String dataId, final Object data) {
try {
configService.publishConfig(
dataId,
NacosPathConstants.GROUP,
GsonUtils.getInstance().toJson(data),
ConfigType.JSON.getType());
} catch (NacosException e) {
LOG.error("Publish data to nacos error.", e);
throw new ShenyuException(e.getMessage());
}
}
@Override
public void doDelConfig(final String dataId) {
try {
configService.removeConfig(
dataId,
NacosPathConstants.GROUP);
} catch (NacosException e) {
LOG.error("Publish data to nacos error.", e);
throw new ShenyuException(e.getMessage());
}
}
@Override
public String getConfig(final String dataId) {
try {
return configService.getConfig(dataId, NacosPathConstants.GROUP, NacosPathConstants.DEFAULT_TIME_OUT);
} catch (NacosException e) {
LOG.error("Get data from nacos error.", e);
throw new ShenyuException(e.getMessage());
}
}
}
正如上文我们所说的:
既然客户端是同步配置中心的数据,那调用端也就是起到创建、更新、删除配置的作用。
三、拓展
不会吧,大家还记得我们的阅读线索2吗,重构了相对于原先旧方式有什么好处?
我们仔细看下上文提到的AbstractNodeDataSyncService和AbstractPathDataSyncService等这几个抽象类就知道了,抽象父类把重复的功能实现,而获取配置这种各个配置中心有不同的获取方式,就留给了各个配置中心的子类实现。
而原先的旧代码,每个不同的配置中心都要实现一套重复的方法代码,代码耦合且不易扩展。大家看看重构了有没好处呢。
咦?仔细看了下,贡献者的类注释写错了啊。
开心又获得了一次开源贡献!想看下开源PR戳这
好了,今天的分享就到这了。大家能否感受到通过PR这种方式来阅读源码的乐趣呢!不仅获得了知识,还获得了一次开源贡献,何乐而不为呢
创作不易,不妨点赞、收藏、关注支持一下,各位的支持就是我创作的最大动力❤️