查看原文
其他

唐太宗把微服务的“心跳机制”玩到了极致!

The following article is from 悟空聊架构 Author 悟空聊架构

大家好,我是鱼皮

唐朝第二位皇帝唐太宗为了扩张领土,到处攻打周边的小国,即使有不服的小国也被唐太宗打服了。这些小国后来就都需要向唐太宗朝贡。

朝贡就是朝拜和进贡。是两国或者说是两个政府之间的一种承认对方尊卑关系的礼节性外交。

唐朝朝贡图,来源百度百科

贞观之治” 说的就是当时唐朝的鼎盛时期, 周围小国都被打趴下了,国内繁荣发展,以十分惊人的速度成为世界顶级强国。唐人街中的“唐”就是说的唐朝,足以说明唐朝对世界的影响。

小国定期向唐朝进行朝贡这不就是微服务的心跳机制吗?

他们是在告诉唐朝,我还是服你管教的。然后唐朝就会把这些小国的名字、地址、服饰外貌等特征放到一个朝贡国列表中。万一哪天这些小国不服管了,就把他们从列表中移除掉,后期可能还会攻打他们~

下面是一张多国朝贡的示例图:

朝贡示例图

在微服务领域,心跳机制出现得太频繁了,比如 Eureka、Naocs 中的客户端和服务端的服务续约、Redis 的主从复制等等,其实原理都很相似。

本篇会通过 Eureka 中的服务续约功能作为示例来剖析心跳机制

对于 Eureka,会涉及到两个端,客户端和服务端。客户端就相当于我们的订单服务、商品服务等。而 Eureka 服务端则是指 Eureka 注册中心这个服务。而保持续约就是客户端隔一段时间就向服务端发送一次心跳,告诉 Eureka 服务端自己的状态是存活的。

主要涉及以下知识点:

  • ① 谁发送的心跳请求?
  • ② 多久发送一次?
  • ③ 如何发送的?
  • ④ 如何接收心跳请求的?
  • ⑤ 接收后做了什么事情?

谁发送的心跳请求

Eureka 采用的是客户端发送心跳请求给 Eureka 服务端。如下图所示:

上图中有三个微服务:订单服务、商品服务、优惠券服务,都已经成功注册到 Eureka 服务端了(注册中心)。

然后每个微服务自己会单独发送心跳请求给注册中心。

多久发送一次

DIscoveryClient 初始化时,会调度一些定时任务。Eureka 初始化了发送心跳请求的线程池 heartbeatExecutor,用来创建发送心跳的线程 HeartbeatThread。原理如图所示:

线程池 heartbeatExecutor 源码如下所示:

线程池

线程池有核心参数:

  1. maximumPoolSize:最大线程数。线程池允许创建的最大线程数。
  2. corePoolSize:核心线程数。当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的核心线程能够执行新任务也会创建线程,等到 需要执行的任务数大于线程池基本大小时就不再创建。如果调用了线程池的 prestartAllCoreThreads() 方法,则线程池会提前创建并启动所有基本线程。
  3. keepAliveTime:线程活动保持时间 ,线程池的工作线程空闲后,保持存活的时间。
  4. runnableTaskQueue:任务队列,用于保存等待执行的任务的阻塞队列。有四种:ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue、PriorityBlockingQueue。

然后将这个线程池用来执行定时调度任务,源码如下所示,在定时任务开始后,延迟 30s 开始执行发送心跳请求,然后每隔 30秒执行一次发送心跳请求。这里可以看到 new 了一个 HeartbeatThread 线程。

定时任务

如何发送心跳请求的?

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,也就是最后更新时间

public void renew() {
    lastUpdateTimestamp = System.currentTimeMillis() + duration;
}
心跳机制

这个实例其实是从服务端注册表 registry 中拿到的,它是一个 ConcurrentHashmap,实例名当做 key,来获取 value(实例),也就是说实例信息是存在内存中的。

拿到的是一个 Lease 实例,数据结构是这样的:Lease,它有一个 volatile 修饰的字段 lastUpdateTimestamp。通过更新这个字段来记录实例信息确实存活着在,而且刚刚还跟 Eureka 通信了。

这就像古代唐朝的朝贡,唐朝周边的小国是需要定期进贡给唐朝的,目的是告诉唐朝,我现在还是依附唐朝的。

那么有了这个字段更新,Eureka Server 自身还会有个定时任务,去检查服务实例的最后更新时间,如果过期了,则认为该实例状态异常,需要进行服务下线。



以上就是本期分享了。


最后,欢迎加入  鱼皮的编程知识星球(点击了解详情),和大家一起交流学习编程,向鱼皮和大厂同学 1 对 1 提问、帮你制定学习计划不迷茫、跟着鱼皮直播做项目(往期项目可无限回看)、领取鱼皮原创编程学习/求职资料等。最近秋招开始了,星球内也会帮大家规划求职进度、完善简历和项目。



往期推荐

我做了个很帅的网站!

23 岁,我担心中年危机

该死的单元测试,写起来到底有多痛?

Vue 无虚拟DOM模式即将到来

Spring Batch 批处理框架,真心强啊!!

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存