前面几篇我们陆陆续续介绍了Eureka服务注册中心、Ribbon客户端负载均衡以及Feign声明式REST服务调用组件,那么本篇我们来聊一下有关于这几个组件的常见问题以及解决方案。
一、Eureka常见问题
1.System Status信息修改
我们一般在启动了Eureka Server的时候,在Eureka监控面板的首页会看系统状态信息:
可以看到目前环境为“test”测试,数据中心为“default”,当前这一块信息是可以修改的。
在Spring Cloud的文档里没有提到这个,但是在Eureka的文档中有提到(https://github.com/Netflix/eureka/wiki/Configuring-Eureka):
上面的意思就是Eureka Client和Eureka Server可以运行在云上和非云上,如果我们运行在云上(如AWS云),需要给它打一个上云的标签,我们可以设定eureka.datacenter=cloud参数,此时Eureka Client和Eureka Server会专门为AWS cloud初始化一些AWS需要的信息(这里其实就是datacenter,即数据中心)。
同理,上面的环境标签,同样可以使用eureka.environment来制定目前服务环境的信息。
我们在之前的microserver-discovery-eureka工程中的application.yml配置文件中添加以下信息:
重启服务,打开Eureka监控面板,就可以看到我们配置的状态信息了:
2、Eureka开启自我保护和解决不踢出问题
我们有时候在一些复杂情况下启动Eureka Server的时候,会在Eureka的控制面板上看到类似的提示:
出现这个提示,就意味着Eureka开启了自我保护机制。有关自我保护机制,我们在官方文档上可以看到以下描述(https://github.com/Netflix/eureka/wiki/Understanding-Eureka-Peer-to-Peer-Communication):
翻译一下就是:
当Eureka服务器启动时,它试图从相邻节点获取所有实例注册表信息。如果从节点获取信息时出现问题,服务器将在放弃之前,去尝试所有对等节点。如果服务器能够成功地获取所有实例,它将根据这些信息设置应该接收的续订阈值。如果任何时候,续约都低于为该值配置的百分比(15分钟内低于85%),服务器将停止过期实例以保护当前实例注册表信息。
在Netflix中,上述保护称为自我保护模式,主要用于在一组客户机和Eureka服务器之间存在网络分区的情况下作为保护。在这些场景中,服务器试图保护它已经拥有的信息。在大规模停机的情况下,可能会出现一些场景,这可能会导致客户机获取不再存在的实例。客户机必须确保它们对返回不存在或没有响应的实例的Eureka服务器具有弹性。在这些场景中,最好的保护是快速超时并尝试其他服务器。
总结起来其实就是之前我们在《6.将微服务注册到Eureka Server上》篇中提到的:
默认情况下,如果EurekaServer在一定时间内没有接收到某个微服务实例的心跳时,EurekaServer将会注销该实例(默认90s)。但是当网络发生故障时,微服务与EurekaServer之间无法通信,这样就会很危险了,因为微服务本身是很健康的,此时就不应该注销这个微服务(即不把这个服务踢出ServerList),而Eureka通过自我保护机制来预防这种情况,当网络健康后,该EurekaServer节点就会自动退出自我保护模式。
我们来模拟一下保护模式的触发,首先我们启动user服务,然后启动eureka服务,此时服务正常:
然后我们强行关闭user,回到Eureka面板观察(刷新一会),发现Eureka进入了保护模式:
user服务的状态也变为了DOWN。
我们在开发过程中,有时候会需要反复启停一些服务,这个时候我们是希望Eureka Server直接踢出已关停的节点的,可以进行以下配置来解决Eureka Server不踢出已关停节点的问题:
server端:
client端:
示例:
服务器端配置:
客户端配置:
这里我们设置服务端关闭自我保护,然后清理服务列表的时间间隔为4秒;服务端租期更新时间间改为10秒,租期到期时间改为30秒,这样Eureka会更快感知到其租期到期,并直接关闭该服务。
我们重启两个服务,一开始我们会在Eureka的监控面板上看到自我保护机制被关闭的警告:
因为我们就是故意关闭它的,为了方便开发,所以这里该警告不去理会,但是生产上是一定需要关注的。
然后关闭user服务, 刷新Eureka控制面板,发在在没有出现自我保护机制的警告后,服务列表中的user服务已经被剔除(大约30+4=34秒左右):
注意:生产环境中一定一定一定不要把自我保护模式关闭!!!!!!!!而且,我们擅自修改eureka的自动续约时间,也会打破Eureka的自我保护机制。
3、Eureka配置instanceId显示IP
我们之前在Eureka控制面板看到的服务都是服务实例名称+端口:
实际上它是按照我们在注册的客户端服务的application.yml中配置的instanceId规则显示的:
在没有配置的情况下,instanceId的配置默认为:
${spring.cloud.client.hostname}:${spring.application.name}:${spring.application.instance_id:${server.port}}
也就是:主机名:应用名:应用端口。
一般来说很多时候我们做运维工作的时候需要知道机器的ip地址,方便追踪问题,此时我们可以添加以下参数:
注意:spring could 2.0版本 需要改成${spring.cloud.client.ip-address}
该参数就可以展示客户端服务所在的ip地址了。
我们在user服务上修改instanceId的配置:
然后重启user服务,在eureka上观察一下该服务的Status,可以看到IP显示出来了:
二、Ribbon常见问题
1、自定义配置时,@Configuration和@ComponentScan包不应重叠
这个问题我们在讲Ribbon以及Feign的时候都提到过,自定义的RibbonConfiguration类必须用@Configuration注解标注,但是它不应该在主Application Context的组件扫描之中,否则它将被所有的Ribbon客户端共享。如果你用@ComponentScan(或者@SpringBootApplication),那么你应该采取措施来避免它被包含到扫描的范围中。
2、使用RestTemplate时,想要获得一个List时,应该用数组,而不应该直接用List
准确的来说,这个问题不是Ribbon的坑,而是RestTemplate的坑。
我们在user中编写代码来反映这个问题。我们在user的Controller类中添加这样一个服务:
启动user,直接访问“list-all”得到的结果如下:
然后我们需要在movie的服务中通过ribbon去调用这个服务,所以在movie工程的Controller方法中添加这样一个服务:
注意,这里重点就是遍历list,如果不遍历,这个list还是可以打印到页面上的(转换json的过程不会出错)。
启动movie服务,访问一下它的“list-all”服务:
可以看到报错了,报错原因我们查看控制台信息:
上面的意思是LinkedHashMap不能转换为User类,由此我们可以得知,通过restRemplate拿过来的List中的对象,不是我们期望的User类,而是LinkedHashMap。这是因为当我们调用postForObject方法时,restRemplate因为无法知道具体的实例化类型,所以将List中的实体解析为了LinkedHashMap,
解决办法,将接收的List类型数据,使用目标实体类的数组来接收:
重启项目,重新访问“list-all”服务,发现不再报错:
三、Feign常见问题
1、自定义配置时,@Configuration和@ComponentScan包不应重叠
这一块和Ribbon一样,这里不再赘述。
2、@FeignClient所在的接口中,不支持@GetMapping等组合注解
通常我们在FeignClient中使用的接口修饰都是@RequestMapping或者@RequestLine(主要看Contract 契约的设定,默认是SpringMVC的@RequestMapping,配置成Default就是@RequestLine),例如之前编写的UserFeignClient:
3、使用@PathVariable时,需要指定其value
我们在FeignClient中使用@PathVariable时,即使参数不使用别名,也需要为@PathVariable指定value。
4、Feign暂不支持复杂对象作为一个参数
例如之前我们在浏览器上调用testPullUser方法。url路径是这么拼写的:
http://localhost:7901/testPullUser?id=888&username=杰克&name=jack&age=25&balance=2500
在后台服务是这么写的:
userFeignClient的postUser方法如下:
我们在url后面拼接的参数,会被Spring MVC自动转换为User这个复杂对象,然后使用Feign接口进行数据的传输,此时是成功的。
那么当我们将“method=RequestMethod.POST”改为“method=RequestMethod.GET”的时候,即使服务提供端也改为GetMapping,此时请求也不会成功,因为只要参数是复杂对象,即使指定了GET方法,Feign依然会以POST方法进行请求的发送。
参考:《51CTO学院Spring Cloud高级视频》