前言
从以前的单体架构到现在的微服务分布式架构, 随着架构的演变, 所需要的技术越来越多, 要求的也越来越多了, 今天来谈一下微服务领域中的心跳机制
在微服务领域,心跳机制很常见了, 比如Eureka. Nacos中的客户端和服务端的服务续约, Redis的主从复制等
本文主要来谈一下Eureka中的服务续约机制来展示心跳机制实现
对于Eureka, 会涉及到两个端, 一个客户端, 一个服务端. 客户端就相当于我们的微服务: 订单服务, 商品服务等. 而服务端就是指Eureka注册中心的这个服务, 而保持续约就是客户端在相隔一段时间内向服务端发送一次心跳, 告诉Eureka一个自己的状态是存活的
主要知识点
- 谁发送的心跳请求?
- 多久发送一次?
- 如何发送?
- 如何接收心跳请求?
- 接收了之后做了什么?
谁发送的心跳请求
Eureka采用的是客户端向服务端发送心跳请求, 如下图:
上图中左边三个客户端都已经注册到了Eureka服务端上, 之后每个微服务都会自己单独发送心跳请求到注册中心
多久发送一次
客户端进行初始化时, 会调度一些定时任务, Eureka初始化了发送了心跳请求的线程池heartbeatExecutor, 用来创建发送心跳的线程HeartbeatThread, 如下图:
线程池有核心参数 :
- maximumPoolSize:最大线程数, 线程池允许创建的最大线程数
- corePoolSize:核心线程数。当提交一个任务到线程池时,线程池会创建一个线程来执行任务, 即使其他空闲的核心线程能够执行新任务也会创建线程, 等到需要执行的任务数大于线程池基本大小时就不再创建。如果调用了线程池的 prestartAllCoreThreads() 方法, 则线程池会提前创建并启动所有基本线程
- keepAliveTime:线程活动保持时间, 线程池的工作线程空闲后, 保持存活的时间
- runnableTaskQueue:任务队列, 用于保存等待执行的任务的阻塞队列。有四种:ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue、PriorityBlockingQueue 利用此线程池来执行定时调度任务, 在定时任务开始后, 延迟30s开始执行发送心跳请求, 然后每隔30s发送一次心跳请求
如何发送心跳请求
HeartbeatThread类实现了Runnable类的run方法, 这个里面有执行发送心跳的具体逻辑
进入 renew 方法中 ,核心逻辑就这一行
eurekaTransport.registrationClient.sendHeartBeat(
instanceInfo.getAppName(),
instanceInfo.getId(),
instanceInfo,null);
调用 EurekaHttpClient 的 sentHeartBeat 方法, 将实例信息发送给注册中心。
拼接的请求URL示例如下:
http://localhost:8080/v2/apps/order/i-000000-1
而且这个请求是个PUT请求。
如何接收心跳请求的
从客户端发起心跳请求之后, 服务端就要接收这个请求了
负责接受请求的类为 ApplicationsResource,它相当于 MVC 中的Controller。
根据请求的 URL 格式和请求方式(PUT),我们可以找到服务端的方法为InstanceResource.renewLease()。
ApplicationsResource->ApplicationResource->InstanceResource
接收后做了什么
里面的核心代码就是 renew 方法, 将实例的一个字段给更新了, 这个字段叫做 ‘lastupdateTimestamp’, 也就是最后更新时间。
publicvoidrenew(){
lastUpdateTimestamp =System.currentTimeMillis()+ duration;}
这个实例其实是从服务端注册表 registry 中拿到的, 它是一个ConcurrentHashMap, 实例名当做 key, 来获取value(实例), 也就是说实例信息是存在内存中的
拿到的是一个 Lease 实例, 数据结构是这样的:Lease, 它有一个 volatile 修饰的字段 lastUpdateTimestamp。通过更新这个字段来记录实例信息确实存活着在, 而且刚刚还跟 Eureka 通信了。
那么有了这个字段更新, Eureka Server 自身还会有个定时任务, 去检查服务实例的最后更新时间, 如果过期了, 则认为该实例状态异常, 需要进行服务下线。
本文到此结束
版权归原作者 lypde 所有, 如有侵权,请联系我们删除。