继第一次搭建springcloud环境踩坑之后,时隔三个月,第二次踩坑记录也跟着上线了,SpringCloudConfig坑系列。第一次踩坑让我理解了 用户线程和守护线程 这一块的知识盲点,这次踩的坑就是基本就是 配置 上的坑。但是多踩踩坑会让我们更容易理解具体配置起到了什么样的作用。
出现此错误可以检查一下以下几点:
spring.cloud.config.discovery.service-id
配置是否和服务名能对应上。 eureka.client.fetch-registry
是否为true(其实默认值就是true,防止手贱误操作)。 回顾下错误发生点:
public List<ServiceInstance> getConfigServerInstances(String serviceId) { logger.debug("Locating configserver (" + serviceId + ") via discovery"); List<ServiceInstance> instances = this.client.getInstances(serviceId); if (instances.isEmpty()) { throw new IllegalStateException( "No instances found of configserver (" + serviceId + ")"); } logger.debug("Located configserver (" + serviceId + ") via discovery. No of instances found: " + instances.size()); return instances; } 复制代码
从上方代码可以看出,在 this.client.getInstances(serviceId)
获取到实例为空的时候会抛出此异常,一步步追踪一下,发现最终会调用到 DiscoveryClient.getInstancesByVipAddress()
方法。
public List<InstanceInfo> getInstancesByVipAddress(String vipAddress, boolean secure, @Nullable String region) { if (vipAddress == null) { throw new IllegalArgumentException( "Supplied VIP Address cannot be null"); } Applications applications; if (instanceRegionChecker.isLocalRegion(region)) { applications = this.localRegionApps.get(); } else { applications = remoteRegionVsApps.get(region); if (null == applications) { logger.debug("No applications are defined for region {}, so returning an empty instance list for vip " + "address {}.", region, vipAddress); return Collections.emptyList(); } } if (!secure) { return applications.getInstancesByVirtualHostName(vipAddress); } else { return applications.getInstancesBySecureVirtualHostName(vipAddress); } } 复制代码
从这里可以明显看出,要么 applications
为空,即注册中心没有可用服务或者 eureka.client.fetch-registry
配置成了false;要么通过 vipAddress
在 applications
查询不出实例结果,即给定的 service-id
在注册中心中不存在。
①注册中心没有可用服务,获取不到服务列表很容易理解。
② service-id
对应不上,也很容易理解。就比如拿一个不存在的 key
去一个 collection
中获取 value
,肯定是获取不到服务的。
③ eureka.client.fetch-registry
配置成了false,这一点需要解释一下:
要知道咱们内存中存储的 applications
列表并不是每次请求都会进行刷新,而是维护了一个 CacheRefreshThread
去定时轮询获取注册中心中的服务,然后塞到 localRegionApps
中,然而,这个线程开启需要一个条件, clientConfig.shouldFetchRegistry()==true
,看方法名就知道需要 eureka.client.fetch-registry=true
任务才会开启。但是默认这个值就是true,当时不晓得是不是脑子抽风了配置成了false,然后找这个bug迷糊了好一会儿。具体开启任务线程的代码如下所示:
private void initScheduledTasks() { if (clientConfig.shouldFetchRegistry()) { // registry cache refresh timer int registryFetchIntervalSeconds = clientConfig.getRegistryFetchIntervalSeconds(); int expBackOffBound = clientConfig.getCacheRefreshExecutorExponentialBackOffBound(); scheduler.schedule( new TimedSupervisorTask( "cacheRefresh", scheduler, cacheRefreshExecutor, registryFetchIntervalSeconds, TimeUnit.SECONDS, expBackOffBound, new CacheRefreshThread() ), registryFetchIntervalSeconds, TimeUnit.SECONDS); } ... } 复制代码
访问ip:port/actuator/refresh返回404。在搭建的过程中,很多老版本的教程都只是说引入下方依赖即可。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> 复制代码
但在springboot 2.x以上的版本,默认只对 health
和 info
这两个端点进行暴露出来,如下图所示。
而对 refresh
端点并未暴露出来,这里就需要咱们自己去手动配置暴露,感兴趣的朋友可以去Endpoints看一下具体有哪些可以暴露的端点,咱们也可以使用 management.endpoints.web.exposure.include=*
将所有端点全部暴露出来,当然,实际生产环境中也不建议如此。目前我测试配置 management.endpoints.web.exposure.include=refresh,info,health
暴露了 refresh,info,health 三个端点。
使用refresh端点时,它只会针对有@RefreshScope注解的类和方法进行刷新。
访问这些端点时都需要加上actuator这个basePath。
最后附上config-server端和config-client端的bootstrap.yml配置。
server端:
spring: cloud: config: server: git: uri: https://github.com/crazyStrongboy/config/ searchPaths: foo application: name: myserver server: port: 8003 eureka: instance: hostname: TTT-HJ instance-id: ${spring.application.name}:${server.port} client: fetch-registry: false service-url: defaultZone: http://${eureka.instance.hostname}:8000/eureka/ 复制代码
client端:
spring: application: name: application cloud: config: discovery: service-id: myserver enabled: true profile: dev server: port: 8004 eureka: instance: hostname: TTT-HJ instance-id: ${spring.application.name}:${server.port} client: service-url: defaultZone: http://${eureka.instance.hostname}:8000/eureka/ fetch-registry: true management: endpoints: web: exposure: include: refresh,info,health 复制代码
目前仅仅只是简单的测试一下springcloud config注册中心,后续会加上springcloud bus消息总线安排一下,看看还有木有坑点继续分享~~,具体案例见 github 。