场景
尝试本地IDEA启动dubbo-admin,连接到测试环境的zookeeper,查看各微服务情况。
等了几分钟发现仍未启动成功,日志里不断打印[DUBBO] Subscribe...
信息。
而成功启动应打印
Tomcat started on port(s): 7001 (http) with context path ''
Started DubboAdminApplication in xxx seconds...
分析
日志里一直在打印订阅(subscribe)相关信息,而测试环境dubbo-admin的启动却很快,不到1分钟启动完成。
dubbo-admin里有个RegistryServerSync
类,实现了InitializingBean
,DisposableBean
,NotifyListener
3个接口,
其中在InitializingBean
的afterPropertiesSet
方法:
public void afterPropertiesSet() throws Exception {
logger.info("Init Dubbo Admin Sync Cache...");
registryService.subscribe(SUBSCRIBE, this);
}
其中registryService
是注入到Spring容器的:
@Autowired
private RegistryService registryService;
在dubbo-admin应用的application.properties文件里配置了注册中心类型和地址:
dubbo.registry.address=zookeeper://192.168.20.4:2181
通过Dubbo的SPI,接口RegistryService
的实现类是ZookeeperRegistry
,注意到它实现其父抽象类FailbackRegistry
里
的doSubscribe
方法中,是通过for循环遍历服务逐个处理的,测试环境的zookeeper在阿里云内网服务器上,本地公司内网与阿里云内网
有大概20-30ms左右的延迟(通过ping命令和咨询负责网络运维的同事得知),而测试环境注册的服务有几百个,这是启动慢的原因所在。
解决
思路: 自定义一个类跟ZookeeperRegistry
类似,单线程遍历处理改为多线程并发处理。
步骤:
- 新建
MyZookeeperRegistry
类继承FailbackRegistry
类,将原ZookeeperRegistry
类的代码拷过来进行修改
关键代码如下:
private ExecutorService es = Executors.newFixedThreadPool(50);
...
List<String> services = zkClient.addChildListener(root, zkListener);
if (services != null && !services.isEmpty()) {
long totalStart = System.currentTimeMillis();
for (String service : services) {
service = URL.decode(service);
anyServices.add(service);
String finalService = service;
es.submit(new Runnable() {
@Override
public void run() {
long start = System.currentTimeMillis();
subscribe(url.setPath(finalService).addParameters(Constants.INTERFACE_KEY, finalService,
Constants.CHECK_KEY, String.valueOf(false)), listener);
logger.info("subscribe cost=" + (System.currentTimeMillis() - start) + "ms" + ",url=" + url);
}
});
}
es.shutdown();
if (!es.awaitTermination(1, TimeUnit.MINUTES)) {
es.shutdownNow();
}
logger.info("subscribe all done,cost=" + (System.currentTimeMillis() - totalStart) + "ms");
}
注:
- 这里使用固定线程池用50个线程来并发处理订阅
- 通过线程池提供的
shutdown
,awaitTermination
,shutdownNow
3个方法来实现主线程等待子线程执行,设置等待超时时间,并且打印总耗时
-
修改
application.properties
配置文件,使用自定义的MyZookeeperRegistry
类
dubbo.registry.address=zookeeper://xxx:2181
修改为:
dubbo.registry.address=myzookeeper://xxx:2181
-
重启应用大概20多秒启动成功
问题解决:)