Springcloud EurekaClient 底层实现(一)

最近接手一个springcloud项目,需要优雅地注销服务后再重启,这样就不会因为ribbon负载均衡到重启的服务上从而丢失请求,那么我们先来研究下怎么手动注销服务。

以下所有代码都是基于springboot2.0.9 RELEASE和springcloud Finchley.RELEASE

如何优雅的手动注销服务

1.eurekaserver:发送delete请求

比如说现在想注销下图中的PRODUCER服务
在这里插入图片描述
那么拼接url http://39.96.0.32:8000/eureka/apps/producer/DESKTOP-UJN399N:producer:8081 发送delete请求
在这里插入图片描述
发现eureka注册中心不再显示producer服务了,说明我们已成功从eureka注册表中移除了producer服务。
在这里插入图片描述
好戏来了,过一段时间后,发现producer自动回来了。
在这里插入图片描述
仔细想想也应该是这样,因为eureka-server作为注册中心,自然要不停地获取各client发送来的心跳,既然Producer程序没关,自然会给eureka-server发送心跳,这样就重新注册了。
下图是producer服务重新注册自身服务的日志。
在这里插入图片描述

显然这种方式注销服务谈不上优雅,因为很可能你刚注销服务,刚好下1s服务就发送了次心跳给server,又重新给注册上了。

2.eureka server:发送PUT请求

PUT请求 http://39.96.0.32:8000/eureka/apps/producer/DESKTOP-UJN399N:producer:8080/status?value=UP
服务上线:UP 服务下线 OUT_OF_SERVICE or DOWN
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
服务重新上线 UP
在这里插入图片描述
在这里插入图片描述

PUT 请求eureka server设置status,可以改变服务的状态(如OUT_OF_SERVICE,DOWN,UP等),这样更优雅!

3.client端:写web接口,执行shutDownComponent命令

用户调用Producer服务中自定义的 /offline 端口,即可实现从eureka-server注册表移除该服务。

/** 用户自定义 TestController中 **/
 @RequestMapping(value = "/offline", method = RequestMethod.GET)
    public void offLine(){
        DiscoveryManager.getInstance().shutdownComponent();
    }

/** com.netflix.discovery.DisconveryManager **/
    public void shutdownComponent() {
        if (discoveryClient != null) {
            try {
                discoveryClient.shutdown();
                discoveryClient = null;
            } catch (Throwable th) {
                logger.error("Error in shutting down client", th);
            }
        }
    }

shutDown()直接关闭了发送心跳的定时器,如果想重新上线,可以向 http://39.96.0.32:8001/eureka/apps/CONSUMER 发送Post请求,同时带有instanceInfo的json字符串作为BODY,但是就算是上线了,由于heartbeat的定时器一直处于关闭状态,过段时间server还是会将该服务下线。目前我还没找到重新启动定时器的方法。

4.client端:调用actuator/service-registry接口

pom中引用spring-boot-starter-actuator,在yml里放开actuator的所有端口,启动程序后会发现多了很多actuator端口。

management:
  endpoints:
    web:
      exposure:
        # 默认只放出health,info等接口,现全部开放
        include: "*"

在这里插入图片描述
通过POST请求 /actuator/service-registry 接口,可以使当前服务变为DOWN和UP状态。

  • POST status=“DOWN”
    在这里插入图片描述
  • eureka server 显示consumer服务状态为DOWN
    在这里插入图片描述
  • 使服务重新上线,post status=“UP”
    在这里插入图片描述
  • eureka server显示consumer服务处于up状态
    在这里插入图片描述

这种方式非常优雅,set status=“DOWN”等流量都没有进来后(这可能需要一段时间),可以选择重启该服务,这样不会丢失请求。也可以down掉几个其他服务,将请求负载均衡到本地服务,方便DEBUG,然后再将其他服务UP起来
后面会讲到,POST请求 /actuator/service-registry 接口实质上还是PUT调用 http://39.96.0.32:8001/eureka/apps/CONSUMER/DESKTOP-UJN399N:consumer:7777/status?value=UP

注册服务底层代码

启动consumer client服务,eureka server就会很快显示consumer服务已经上线,这是为什么呢?
向eureka server发送了DELETE consumer的请求,过段时间consumer又会重新上线,这又是为什么呢?

1.准备工作
  • pom
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.9.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.huoli</groupId>
    <artifactId>consumer</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>consumer</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Finchley.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>


    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter</artifactId>
        </dependency>


        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

  • application.yml
eureka:
  client:
    # 客户端从eureka获取applications到本地缓存的时间间隔,默认30s
    # DiscoveryClient 中启动 registry cache refresh timer 定时任务,每3s从server获取一次注册服务列表,但获取的信息并不是实时的状态
    registry-fetch-interval-seconds: 3
    service-url:
      defaultZone: http://39.96.0.32:8000/eureka/,http://39.96.0.32:8001/eureka/,http://39.96.0.32:8002/eureka/
  instance:
    # 表示eureka server至上一次收到client的心跳之后,等待下一次心跳的超时时间,在这个时间内若没收到下一次心跳,则将移除该instance,默认90s
    lease-expiration-duration-in-seconds: 90
    # 表示eureka client发送心跳给server端的频率。如果在leaseExpirationDurationInSeconds后,server端没有收到client的心跳,则将摘除该instance,默认30s
    # DiscoveryClient 中启动 Heartbeat timer 定时任务,每5s发送次请求给server,告诉自己处于上线状态
    lease-renewal-interval-in-seconds: 5
server:
  port: 7777
spring:
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
  application:
    name: consumer
logging:
  level:
    root: info
# 一定要写这句话,否则hystrix无法检测到请求
feign:
  hystrix:
    enabled: true
  client:
    config:
      default:
        connectTimeout: 7000
        readTimeout: 7000
#hystrix 配置
hystrix:
  command:
    default:
      execution:
        timeout:
          #如果enabled设置为false,则请求超时交给ribbon控制
          enabled: true
        isolation:
          thread:
            timeoutInMilliseconds: 180000
management:
  endpoints:
    web:
      exposure:
        # 默认只放出health,info等接口,查看localhost:7777/acurator/loggers 可看到各包对应的日志等级
        # 在bootstrap.properties中配置包的日志等级,方便定位问题
        include: 'loggers'        
  • bootstrap.properties
    为了方便理解代码,在bootstrap.properties中将DiscoveryClient和loadbalancer的日志等级设为DEBUG。
# 设置指定包的日志级别
logging.level.com.netflix.discovery.DiscoveryClient=DEBUG
logging.level.com.netflix.loadbalancer=DEBUG
  • java
/**
* APPLICATION
*/
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class ConsumerApplication {

    public static void main(String[] args) {
        SpringApplication.run(ConsumerApplication.class, args);
    }
}


/**
* FeignClient
*/
@FeignClient(name = "producer")
public interface ProducerRemote {

    @RequestMapping(value = "/hello",method = RequestMethod.GET)
    public String hello(@RequestParam("name") String name);

    @RequestMapping("/test")
    public List<Object> test(@RequestBody Map<String,String> map);

}

/**
* TestController 
*/
@RestController
public class TestController {
    @Autowired
    ProducerRemote producerRemote;

    @Autowired
    private ObjectFactory<HttpMessageConverters> messageConverters;

    @RequestMapping("/hello")
    public String hello(){
        return producerRemote.hello("abc");
    }
}
2.启动服务

启动服务后,console打印如下日志,先大致浏览一遍,记住打印日志的类是DiscoveryClient
在这里插入图片描述
浏览器输入 http://localhost:7777/actuator/loggers,看见DiscoveryClient的日志级别是DEBUG,说明bootstrap.properties中的配置生效。
在这里插入图片描述

注册服务步骤
  • EurekaClientAutoConfiguration中static class RefreshableEurekaClientConfiguration,@Bean生成EurekaClient的实例
    在这里插入图片描述
  • DiscoveryClient作为EurekaClient的继承类开始实例化,调用方法initScheduledTasks(),该方法生成了2个定时器,分别是
  1. 间隔registryFetchIntervalSeconds时间执行**CacheRefreshThread()**方法,从eureka server获取注册信息,存储在本地缓存
  2. 间隔renewalIntervalInSecs时间执行**HeartbeatThread()**方法,向eureka server发送heart beat,保持自身服务在线状态
    在这里插入图片描述

至此,注册服务流程讲解完毕,该服务会间隔renewalIntervalInSecs时间,反复执行renew()的代码,第一次先register注册服务,再然后不停地跟sendHeartBeat说自己处于UP状态。
其中renewalIntervalInSecs可在yml中配置,下图中配置的是5s,当然这个数值大小还是根据实际情况去设置。
在这里插入图片描述

DiscoveryClient.shutDown()


unregister()最后调用的核心代码是 EurekaHttpClient.cancle(),即向eureka server发送DELETE请求 。
在这里插入图片描述

总结

至此介绍完了如何优雅的关闭和启动eureka client服务,以及底层的代码实现。
需要记住的名词有

  • DiscoveryClient (父类为EurekaClient) 操作和eureka server连接的工具类
  • initScheduledTasks 启动2个定时器,一个发送heartbeat,一个从server拉取注册信息(fetch registry)
  • renewalIntervalInSecs 发送heartbeat的时间间隔

eureka server的rest接口可参阅 https://github.com/Netflix/eureka/wiki/Eureka-REST-operations
这里我们自己总结下规律,注意sendHeartBeat和statusUpdate之间的不同

EurekaHttpClient中的方法请求方式请求地址其他
registerposthttp://39.96.0.32:8001/eureka/apps/CONSUMERInstanceInfo的json对象
canceldeletehttp://39.96.0.32:8001/eureka/apps/CONSUMER/DESKTOP-UJN399N:consumer:7777
sendHeartBeatputhttp://39.96.0.32:8001/eureka/apps/CONSUMER/DESKTOP-UJN399N:consumer:7777?status=UP&lastDirtyTimestamp=1562759993141
statusUpdateputhttp://39.96.0.32:8001/eureka/apps/CONSUMER/DESKTOP-UJN399N:consumer:7777/status?value=UP&lastDirtyTimestamp=1562759993141status=UP,OUT_OF_SERVICE,DOWN等等

下一章将介绍EurekaClient是如何获取服务列表,然后负载均衡去调用其他服务的。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值