当前位置: 首页 > 科技观察

微服务的服务太简单了,Nacos还没反应过来,怎么办?

时间:2023-03-18 01:40:40 科技观察

本文转载自微信公众号《程序新视野》,作者为二哥。转载本文请联系程序新视界公众号。前言我们知道微服务的治理可以通过Nacos等注册中心来实现。但是引入Nacos之后,是不是真的像理想中的那样,所有的服务都被Nacos完美管理了呢?太年轻,太单纯!今天的文章就来和你聊聊。当服务异常宕机时,Nacos还没有做出反应时,可能出现的情况和已有的解决方案。Nacos健康检查的故事,也是从Nacos对服务实例的健康检查开始的。Nacos目前支持临时实例使用心跳上报来维持活动。Nacos客户端会维护一个定时任务,每5秒发送一次心跳请求,以确保其处于活动状态。如果Nacos服务器在15秒内没有收到客户端的心跳请求,就会将该实例设置为不健康,如果在30秒内没有收到心跳,就会移除该临时实例。正常业务场景下如果服务突然挂掉,如果某个服务实例关闭,默认会在关闭前主动调用注销接口,清除Nacos服务器注册的实例。如果服务实例还没来得及注销就已经被kill掉了,比如你正常kill一个应用,这个应用会处理完手头的事情然后关闭。但是,如果使用kill-9强行杀掉,则无法登出。对于这种突发情况,无法正确调用服务注销接口。这时需要进行健康检查,确保实例被删除。通过上面分析的Nacos健康检查机制,我们会发现服务突然挂掉后会有15秒的间隔。在此期间,Nacos服务器没有感知到服务宕机,仍然为客户端提供服务。这时,必须将一部分请求分配给异常实例。针对这种情况,该如何处理呢?如何保证服务不影响正常业务?自定义心跳周期针对以上问题,我们能想到的最简单的解决办法就是缩短默认的健康检查时间。本来发现服务异常并标记为不健康需要15秒,那么可以缩短吗?这样,错误影响的范围就可以缩小,变得可控。为此,Nacos1.1.0之后,提供了自定义心跳周期的配置。如果基于客户端操作,创建实例时,可以在实例的元数据数据中配置心跳周期、健康检查过期时间、实例删除时间。相关例子如下:StringserviceName=randomDomainName();Instanceinstance=newInstance();instance.setIp("1.1.1.1");instance.setPort(9999);Mapmetadata=newHashMap();//设置心跳周期,单位毫秒metadata.put(PreservedMetadataKeys.HEART_BEAT_INTERVAL,"3000");//设置心跳超时时间,单位毫秒;如果服务端在6秒内没有收到客户端的心跳,则发送给客户端将在客户端注册的实例设置为不健康:metadata.put(PreservedMetadataKeys.HEART_BEAT_TIMEOUT,"6000");//设置实例删除的超时时间,以毫秒为单位;即如果服务端在9秒内没有收到客户端的心跳,就会向客户端发送删除注册在该端的实例:metadata.put(PreservedMetadataKeys.IP_DELETE_TIMEOUT,"9000");instance.setMetadata(metadata);命名.registerInstance(服务名称,实例);如果是基于SpringCloudAlibaba的项目,可以这样配置:spring:application:name:user-service-providercloud:nacos:discovery:server-addr:127.0.0.1:8848heart-beat-interval:1000#心跳间隔。单位是毫秒。heart-beat-timeout:3000#心跳超时。单位是毫秒。ip-delete-timeout:6000#ip删除超时。单位是毫秒。在某些SpringCloud版本中,上述配置可能不会生效。您也可以直接配置元数据数据。配置方法如下:spring:application:name:user-service-providercloud:nacos:discovery:server-addr:127.0.0.1:8848metadata:preserved.heart.beat.interval:1000#heartbeatinterval:1000#heartbeatinterval。时间单位:毫秒。preserved.heart.beat.timeout:3000#心跳暂停。时间单位:毫秒。即如果服务器在6秒内没有收到客户端的心跳,则客户端注册的实例将被设置为不健康;preserved.ip.delete.timeout:6000#ip删除超时。时间单位:秒。即如果服务器在9秒内没有收到客户端的心跳,就会删除客户端注册的实例;第一次配置,有兴趣的朋友可以看看NacosServiceRegistryAutoConfiguration中相关组件的实例化。在某些版本中,由于NacosRegistration和NacosDiscoveryProperties实例化的先后顺序,配置不生效。这时候可以考虑第二种配置形式。以上配置项在NacosServiceRegistry进行实例注册时最终会被getNacosInstanceFromRegistration方法封装:;instance.setWeight(nacosDiscoveryProperties.getWeight());instance.setClusterName(nacosDiscoveryProperties.getClusterName());instance.setEnabled(nacosDiscoveryProperties.isInstanceEnabled());//设置元数据tainstance.setMetadata(registration.getMetadata());instance.setEphemeral(nacosDiscoveryProperties.isEphemeral());returninstance;}setMetadata方法所在的位置。通过Nacos提供的心跳周期配置,结合自身业务场景,选择最合适的心跳检测机制,尽可能避免对业务造成影响。这种方案看似心跳周期较短,但是会对Nacos服务器造成一定的压力。如果服务器允许,可以尽量缩短。Nacos的保护阈值在上面的配置中,我们还需要结合自己的项目情况来考虑Nacos的保护阈值的配置。在Nacos中,注册的服务实例有一个保护阈值配置项。该配置项的值是一个0到1之间的浮点数。本质上,保护阈值是一个比例值(当前服务的健康实例数/当前服务的实例总数)。一般流程下,服务消费者需要从Nacos获取健康/不健康状态的可用实例。Nacos返回实例时,只会返回健康的实例。但是在高并发、大流量的场景下会存在一定的问题。比如服务A有100个实例,有98个实例处于不健康状态,如果Nacos只返回这两个健康实例。流量洪流的到来可能会直接破坏这两项服务,进一步产生雪崩效应。保护阈值的意义在于,当服务A的健康实例数/总实例数<保护阈值时,说明健康实例不多,会触发保护阈值(状态为真)。Nacos会将服务的所有实例信息(健康+不健康)提供给消费者。消费者可能会访问不健康的实例,导致请求失败,但这总比造成雪崩好。牺牲一些请求来保证整个系统的可用性。在上面的解决方案中,我们提到了可以自定义心跳周期,从健康、不健康和移除中可以看到实例的状态。这些参数的定义还应考虑保护阈值的触发,以避免雪崩效应的发生。SpringCloud的请求重试即使我们在上面调整了心跳周期,当一个实例失败时,Nacos服务仍然会有一小段时间来不及移除异常实例。此时,如果消费者请求该实例,请求仍然会失败。为了构建一个更健壮的应用系统,我们希望在请求失败时有一定策略的重试机制,而不是直接返回失败。这时候需要开发者实现重试机制。在微服务架构中,我们通常基于Ribbon或者SpringCloudLoadBalancer进行负载均衡。除了Ribbon和Feign框架本身已经支持的请求重试和请求传递功能。SpringCloud也提供了标准的loadbalancer相关配置。这里不多说Ribbon框架的使用,重点介绍SpringCloud是如何帮助我们实现的。异常模拟我们先模拟一下异常情况,先把上面的心跳周期调大,方便测试。然后启动两个provider和一个consumer服务,负载均衡是基于SpringCloudLoadBalancer来处理的。这时候通过consumer发出请求,你会发现LoadBalancer通过round-robin训练(打印日志)将请求平均分配给两个provider。此时,通过kill-9命令关闭其中一个提供程序。这时候你再通过consumer来请求,你会发现一次成功,一次失败,如此交替。解决方案我们通过SpringCloud提供的LoadBalancerProperties配置类中定义的配置项来配置重试机制。详细的配置项可以对照这个类的属性。在consumer的application配置中添加retry相关的配置:spring:application:name:user-service-consumercloud:nacos:discovery:server-addr:127.0.0.1:8848loadbalancer:retry:#开启retryenabled:true#同一个实例是thelargestAttemptsmax-retries-on-same-service-instance:1#其他实例的最大尝试次数max-retries-on-next-service-instance:2#对所有操作启用重试(谨慎使用,尤其是对于POST提交,power等价保证)retry-on-all-operations:true在上面的配置中,retry是默认开启的。max-retries-on-same-service-instance是指当前实例的尝试次数,包括第一次请求,这里配置为1,即第一次请求失败转其他实例。当然你也可以配置一个大于1的值,这样你会在当前实例中重试。max-retries-on-next-service-instance配置将请求传输到其他实例的最大尝试次数。retry-on-all-operations默认为false,即只支持重试Get请求。此处设置为true以启用所有重试。由于涉及重试,需要保证业务的幂等性。以上配置完成后,再次演示异常模拟,你会发现即使服务挂了,Nacos中依然存在,业务处理依然可以正常进行。Ribbon或者其他类似的组件也有类似的解决方案,大家可以自行研究。解决方案的坑在使用SpringCloudLoadBalancer的时候其实有一个坑,可能会遇到上面的配置没有生效的情况。为什么是这样?其实是因为依赖引入的问题。SpringCloudLoadBalancer的重试机制是基于spring-retry的。如果没有引入相应的依赖,配置不会生效。公文业务并未给出解释。org.springframework.retryspring-retry另外上面的例子是基于SpringCloud2020.0.0版本的,其他版本可能有不同的配置。小结在使用微服务时,仅仅集成SpringCloud组件是不够的。在这篇文章中,我们可以看到,即使集成了Nacos,也会因为心跳机制的原因,做出一些妥协,比如调整心跳频率。同时,即使调整了心跳参数,也需要通过其他组件来兼顾请求异常时的重试,防止系统雪崩的发生。关注并持续更新微服务系列实战内容。