时钟轮在 RPC 中的应用

今天这篇文章介绍一下RPC中如何使用时钟轮实现定时任务,比如调用端的超时处理、定时心跳....

定时任务带来了什么问题?

在讲解时钟轮之前,我们先来聊聊定时任务。相信你在开发的过程中,很多场景都会使用到定时任务,在 RPC 框架中也有很多地方会使用到它。就以调用端请求超时的处理逻辑为例,下面我们看一下 RPC 框架是如果处理超时请求的。

在讲解 Future 的时候说过:无论是同步调用还是异步调用,调用端内部实行的都是异步,而调用端在向服务端发送消息之前会创建一个 Future,并存储这个消息标识与这个 Future 的映射,当服务端收到消息并且处理完毕后向调用端发送响应消息,调用端在接收到消息后会根据消息的唯一标识找到这个 Future,并将结果注入给这个 Future。

那在这个过程中,如果服务端没有及时响应消息给调用端呢?调用端该如何处理超时的请求?

没错,就是可以利用定时任务。每次创建一个 Future,我们都记录这个 Future 的创建时间与这个 Future 的超时时间,并且有一个定时任务进行检测,当这个 Future 到达超时时间并且没有被处理时,我们就对这个 Future 执行超时逻辑。

那定时任务该如何实现呢?

有种实现方式是这样的,也是最简单的一种。每创建一个 Future 我们都启动一个线程,之后 sleep,到达超时时间就触发请求超时的处理逻辑。

这种方式吧,确实简单,在某些场景下也是可以使用的,但弊端也是显而易见的。就像刚才我讲的那个 Future 超时处理的例子,如果我们面临的是高并发的请求,单机每秒发送数万次请求,请求超时时间设置的是 5 秒,那我们要创建多少个线程用来执行超时任务呢?超过 10 万个线程,这个数字真的够吓人了。

别急,我们还有另一种实现方式。我们可以用一个线程来处理所有的定时任务,还以刚才那个 Future 超时处理的例子为例。假设我们要启动一个线程,这个线程每隔 100 毫秒会扫描一遍所有的处理 Future 超时的任务,当发现一个 Future 超时了,我们就执行这个任务,对这个 Future 执行超时逻辑。

这种方式我们用得最多,它也解决了第一种方式线程过多的问题,但其实它也有明显的弊端。

同样是高并发的请求,那么扫描任务的线程每隔 100 毫秒要扫描多少个定时任务呢?如果调用端刚好在 1 秒内发送了 1 万次请求,这 1 万次请求要在 5 秒后才会超时,那么那个扫描的线程在这个 5 秒内就会不停地对这 1 万个任务进行扫描遍历,要额外扫描 40 多次(每 100 毫秒扫描一次,5 秒内要扫描近 50 次),很浪费 CPU。

在我们使用定时任务时,它所带来的问题,就是让 CPU 做了很多额外的轮询遍历操作,浪费了 CPU,这种现象在定时任务非常多的情况下,尤其明显。

什么是时钟轮?

这个问题也不难解决,我们只要找到一种方式,减少额外的扫描操作就行了。比如我的一批定时任务是 5 秒之后执行,我在 4.9 秒之后才开始扫描这批定时任务,这样就大大地节省了 CPU。这时我们就可以利用时钟轮的机制了。

我们先来看下我们生活中用到的时钟。

很熟悉了吧,时钟有时针、分针和秒针,秒针跳动一周之后,也就是跳动 60 个刻度之后,分针跳动 1 次,分针跳动 60 个刻度,时针走动一步。

而时钟轮的实现原理就是参考了生活中的时钟跳动的原理。

在时钟轮机制中,有时间槽和时钟轮的概念,时间槽就相当于时钟的刻度,而时钟轮就相当于秒针与分针等跳动的一个周期,我们会将每个任务放到对应的时间槽位上。

时钟轮的运行机制和生活中的时钟也是一样的,每隔固定的单位时间,就会从一个时间槽位跳到下一个时间槽位,这就相当于我们的秒针跳动了一次;

时钟轮可以分为多层,下一层时钟轮中每个槽位的单位时间是当前时间轮整个周期的时间,这就相当于 1 分钟等于 60 秒钟;当时钟轮将一个周期的所有槽位都跳动完之后,就会从下一层时钟轮中取出一个槽位的任务,重新分布到当前的时钟轮中,当前时钟轮则从第 0 槽位从新开始跳动,这就相当于下一分钟的第 1 秒。

为了方便你了解时钟轮的运行机制,我们用一个场景例子来模拟下,一起看下这个场景。

假设我们的时钟轮有 10 个槽位,而时钟轮一轮的周期是 1 秒,那么我们每个槽位的单位时间就是 100 毫秒,而下一层时间轮的周期就是 10 秒,每个槽位的单位时间也就是 1 秒,并且当前的时钟轮刚初始化完成,也就是第 0 跳,当前在第 0 个槽位。

好,现在我们有 3 个任务,分别是任务 A(90 毫秒之后执行)、任务 B(610 毫秒之后执行)与任务 C(1 秒 610 毫秒之后执行),我们将这 3 个任务添加到时钟轮中,任务 A 被放到第 0 槽位,任务 B 被放到第 6 槽位,任务 C 被放到下一层时间轮的第 1 槽位,如下面这张图所示。

当任务 A 刚被放到时钟轮,就被即刻执行了,因为它被放到了第 0 槽位,而当前时间轮正好跳到第 0 槽位(实际上还没开始跳动,状态为第 0 跳);600 毫秒之后,时间轮已经进行了 6 跳,当前槽位是第 6 槽位,第 6 槽位所有的任务都被取出执行;1 秒钟之后,当前时钟轮的第 9 跳已经跳完,从新开始了第 0 跳,这时下一层时钟轮从第 0 跳跳到了第 1 跳,将第 1 槽位的任务取出,分布到当前的时钟轮中,这时任务 C 从下一层时钟轮中取出并放到当前时钟轮的第 6 槽位;1 秒 600 毫秒之后,任务 C 被执行。

看完了这个场景,相信你对时钟轮的机制已经有所了解了。在这个例子中,时钟轮的扫描周期仍是 100 毫秒,但是其中的任务并没有被过多的重复扫描,它完美地解决了 CPU 浪费的问题。

这个机制其实不难理解,但实现起来还是很有难度的,其中要注意的问题也很多.

时钟轮在 RPC 中的应用

通过刚才对时钟轮的讲解,相信你可以看出,它就是用来执行定时任务的,可以说在 RPC 框架中只要涉及到定时相关的操作,我们就可以使用时钟轮。

那么 RPC 框架在哪些功能实现中会用到它呢?

刚才我举例讲到的调用端请求超时处理,这里我们就可以应用到时钟轮,我们每发一次请求,都创建一个处理请求超时的定时任务放到时钟轮里,在高并发、高访问量的情况下,时钟轮每次只轮询一个时间槽位中的任务,这样会节省大量的 CPU。

调用端与服务端启动超时也可以应用到时钟轮,以调用端为例,假设我们想要让应用可以快速地部署,例如 1 分钟内启动,如果超过 1 分钟则启动失败。我们可以在调用端启动时创建一个处理启动超时的定时任务,放到时钟轮里。

除此之外,你还能想到 RPC 框架在哪些地方可以应用到时钟轮吗?还有定时心跳。RPC 框架调用端定时向服务端发送心跳,来维护连接状态,我们可以将心跳的逻辑封装为一个心跳任务,放到时钟轮里。

这时你可能会有一个疑问,心跳是要定时重复执行的,而时钟轮中的任务执行一遍就被移除了,对于这种需要重复执行的定时任务我们该如何处理呢?在定时任务的执行逻辑的最后,我们可以重设这个任务的执行时间,把它重新丢回到时钟轮里。

总结

今天我们主要讲解了时钟轮的机制,以及时钟轮在 RPC 框架中的应用

这个机制很好地解决了定时任务中,因每个任务都创建一个线程,导致的创建过多线程的问题,以及一个线程扫描所有的定时任务,让 CPU 做了很多额外的轮询遍历操作而浪费 CPU 的问题。

时钟轮的实现机制就是模拟现实生活中的时钟,将每个定时任务放到对应的时间槽位上,这样可以减少扫描任务时对其它时间槽位定时任务的额外遍历操作。

在时间轮的使用中,有些问题需要你额外注意:

  • 时间槽位的单位时间越短,时间轮触发任务的时间就越精确。例如时间槽位的单位时间是 10 毫秒,那么执行定时任务的时间误差就在 10 毫秒内,如果是 100 毫秒,那么误差就在 100 毫秒内
  • 时间轮的槽位越多,那么一个任务被重复扫描的概率就越小,因为只有在多层时钟轮中的任务才会被重复扫描。比如一个时间轮的槽位有 1000 个,一个槽位的单位时间是 10 毫秒,那么下一层时间轮的一个槽位的单位时间就是 10 秒,超过 10 秒的定时任务会被放到下一层时间轮中,也就是只有超过 10 秒的定时任务会被扫描遍历两次,但如果槽位是 10 个,那么超过 100 毫秒的任务,就会被扫描遍历两次。

结合这些特点,我们就可以视具体的业务场景而定,对时钟轮的周期和时间槽数进行设置。

在 RPC 框架中,只要涉及到定时任务,我们都可以应用时钟轮,比较典型的就是调用端的超时处理、调用端与服务端的启动超时以及定时心跳等等。

本文由mdnice多平台发布

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,347评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,435评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,509评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,611评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,837评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,987评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,730评论 0 267
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,194评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,525评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,664评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,334评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,944评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,764评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,997评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,389评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,554评论 2 349

推荐阅读更多精彩内容