大家好,我是小楼。作为基础组件开发人员,服务好每一位业务开发同学是我们的义务(KPI)。在客服群里,经常有业务开发的同学丢一段代码,报错,我们当然要微笑服务,耐心解答。有些问题根据多年踩坑的经验,一眼就能看出来。有的问题看代码一眼就能看出来,有的问题光看是看不出来的。这个时候只能下载代码在本地运行。熟悉我的朋友都知道,我从事dubbo相关的开发(客户)交付(服务)工作多年,所以说下dubbo排错过程中的一件有趣的事情。通常遇到解决不了的问题,先git拉代码,然后导入IDEA,找到main方法点击开始,运行一小会儿,肯定会出现一个小错误,比如这个:Socketerroroccurred:localhost/127.0.0.1:2181:Connectionrefused看到2181端口就知道本地没有安装zookeeper(以下简称zk)。问题不大。Docker直接拉取一个zk镜像,启动一个容器。有了这样的习惯积累,etcd、redis、mysql等容器已经在低端Mac上跑起来了,重要的是开了N个IDEA窗口。每当启动新项目时,风扇呼呼作响,直接卡死IDEA。这时候我陷入了思考,能不能少跑点程序呢?etcd、redis、mysql暂时不确定,不过dubbo的注册中心我比较熟悉!当然,柿子一定要软。在开始梳理需求之前,得先梳理一下需求,于是脑海中闪过无数瞬间在本地测试时遇到的dubbo注册中心相关的问题,但仔细一看,不外乎两个类型:作为提供者:最重要的是不要阻止应用程序启动。作为消费者:不要阻止应用程序启动。可以发现并呼叫本地提供商。可以调用远程提供程序。可以手动指定和调用任何提供程序。除了这两个功能需求,我们还要解决我们原来的问题:“不要依赖第三方服务”(比如zk)。从一开始研究的时候就想到了使用dubboregistryextension来实现这个功能。为了不重蹈覆辙,查看了dubbo源码,看看有没有对应的实现:发现除了dubbo-registry-multicast,都是依赖第三方提供的服务,那么这个multicast是什么?dubbo官方文档说的很清楚:乍一看满足了我们的需求,但是细细想来还是有几点不尽如人意:远程提供者可能找不到,如果Everyone的代码使用zk,如果下拉编码,把注册中心改成组播,就找不到远程服务了;您不能手动指定和调用任何提供商。产品设计服务的发现必须要有一个载体,要么通过第三方组件,要么通过互联网。但是我们忽略了,在本地,磁盘也可以作为载体。提供者注册写入磁盘文件,消费者订阅读取磁盘文件。当磁盘文件更改时,会通知消费者。看起来像这样:这种设计有什么好处?它不依赖于其他服务,只是读写文件。它将阻止应用程序启动。当消费者和提供者都在本地时,它们可以像其他注册中心(如zk、nacos等)一样工作,对开发者完全透明。您可以手动修改并指定调用任何提供商。唯一的缺点就是找不到remoteprovider,不过我们可以手动指定,没问题。我们的产品设计基于dubbo2.7.x版本的接口级服务发现,因为这个版本用的最多。首先要考虑的是如何组织服务发现文件。既然是接口级的服务发现,我们就以服务名作为文件名,每个服务都有一个文件:其次,每个文件的内容如何组织?最简单的方法就是使用dubbo注册的url直接写到文件里,一行一个url,像这样:但是你可能发现了一个问题,dubbo的url有点长~做起来会不会很困难如果我手动指定它呢?这个问题很容易解决,我们实现了一个简写版本的URL,比如如果有这样的简写行,就会恢复成可用的URL。127.0.0.1:20880代码实现在实现之前,首先要了解dubbo注册中心的扩展是怎么写的。这块,直接参考官方文档:https://dubbo.apache.org/zh/docs/v2.7/dev/impls/registry/虽然我觉得你以后可能无法实现dubboregistryextension阅读文档,但不要惊慌,先往下看,也许看完这篇文章你可以自己写一篇。先看代码结构:项目名称为:dubbo-registry-mock,与dubbo源码中的命名风格一致。MockRegistry是注册表的核心实现。MockRegistryFactory是mockregistry的工厂,dubbo会通过这个类创建MockRegistry。org.apache.dubbo.registry.RegistryFactory文件指定了如何加载MockRegistryFactory,即dubbo的SPI发现文件。dubbo的registry配置只需要改成:dubbo.registry.address=mock://127.0.0.1:2181这里只有mock起作用,ip和port不重要,只是占一个地方。dubbo应用启动时,读取配置的mock后,会在resources/META-INF.dubbo下寻找org.apache.dubbo.registry.RegistryFactory文件,其内容为:mock=org.newboo.MockRegistryFactory,所以去新建一个MockRegistryFactory。注:newboo.org是我曾经注册过用来托管博客的域名,后来没有续费,现在这个包名经常出现在我的测试代码中。MockRegistryFactory也很简单,直接新建一个MockRegistry:}}最后看核心实现MockRegistry类:publicMockRegistry(URLurl){super(url);StringbasePath=DISCOVERY_DEFAULT_DIR;如果(StringUtils.isNotEmpty(url.getParameter(DISCOVERY_FILE_DIR_KEY))){basePath=url.getParameter(DISCOVERY_FILE_DIR_KEY);}mockService=newMockService(basePath);ScheduledExecutorServicescheduledExecutorService=Executors.newScheduledThreadPool(1,newNamedThreadFactory("file_scan",true));scheduledExecutorService.scheduleWithFixedDelay(newSubscribeScan(),1000L,5000,TimeUnit.MILLISECONDS);}这个构造方法做了3件事:获取basePath,也就是服务发现文件夹的base路径,有默认值,也可以根据url的参数进行调整,比如:dubbo.registry.address=mock://127.0.0.1:2181?discovery_file=/tmp/mock-registry2new一个MockService,承载了核心的服务发现逻辑,后面会讲到。启动定时任务,每5秒扫描一次文件,查看文件是否有变化,如果有变化通知消费者,后面会详细介绍。MockRegistry继承自FailbackRegistry,只需要实现其doRegister、doUnregister、doSubscribe、doUnsubscribe、isAvailable方法即可。其中isAvailable是判断注册中心是否可用,我们直接返回true即可。doUnsubscribe就是退订,这里什么都不用做。我们将其余3个方法的逻辑封装在MockService中:@OverridepublicvoiddoRegister(URLurl){try{mockService.writeUrl(url);}catch(Throwablee){thrownewRpcException("注册失败"+url,e);}}@OverridepublicvoiddoUnregister(URLurl){try{mockService.removeUrl(url);}catch(Throwablee){thrownewRpcException("注销失败"+url,e);}}@OverridepublicvoiddoSubscribe(URLurl,NotifyListenerlistener){try{List