#Timer

案例分析

 

问题:如果FailureCase被频繁调用?

结论:容器CPU使用率峰值翻倍(或者更高)而且居高不下!

pprof分析

 

time包

标准常量定义

 

 

"2006-01-02 15:04:05"(format.go: nextStdChunk)

 

时间的表示

系统时钟:

CLOCK_REALTIME(wall clock):当前时间(系统展示的时间,可同步,可修改)

CLOCK_MONOTONIC(monotonic time):单调时间,系统启动后每个计时器中断+1

 

时间的计算

 

###定时器(timer)

我们经常使用的是time包暴露出来的方法,但是在time包中仅包含一些对timer操作的封装,在runtime/time.go包含绝大部分的底层实现;

time包

Timer

golang中最基础的就是Timer,在Timer的基础上封装了After,Tick,Sleep等场景;

After

在给定时间d后触发,只想f函数或者默认函数;例如超时场景

Tick

在给定时间d的间隔内,循环触发;只支持执行默认函数(写channel);比如限速场景

这里可以发现在Tick的场景中,由于period被赋值,底层timer会一直生效,所以运行一段时间之后,全局的时间堆回存在大量的timer(timer泄漏),去check和维护这个时间堆,会占用大量的cpu资源;

 

最佳实践

Sleep

具体的实现在runtime/time.go中,使用了比较hack的方式 — go:linkname, 达到跨包访问;

 

所以简单来说,Sleep做的事情是,将当前goroutine置入waiting状态,再由定时器来唤醒;

 

runtime/time.go

 

false sharing: CPU的缓存系统通常是以缓存行(cacheline,一般为64字节)来读取数据的,如果有两个进程的数据同时落到了一个cacheline, 一个进程的数据被修改了,整个cacheline需要重新加载,在高并发的场景中,这种相互之间的影响是不可忽略的;

而对于,定时器这样可能会频繁更新的数据结构,单独存在于一个或者多个cacheline是很有必要的;

 

时间堆结构(timersBucket)

全局timers的长度为64,每个timer为独立的timersBucket结构,每个timersBucket独立维护一个timer堆(以数组结构存储);

timers

在每个timersBucket中,timer满足"四叉小顶堆"数据结构,元素按广度优先的顺序存储在数组中;以及有如下特性:

调整算法(siftupTimer & siftdownTimer)

调整涉及到两种场景:新增和删除;

新增

— timerBucket分配

— siftupTimer

删除

— 删除元素

将last元素调整到已删除的index位置,调整数组的长度,以达到修改删除元素的目的;

 

— siftupTimer & siftdownTimer

为什么需要siftupTimer?

i对应的是原堆的最后一个元素,因该是属于when最大的一个层次的timers;但是存在的情况是,删除的元素和last在同一层,交换值后不满足父子关系;

siftdownTimer

 

 

时间检查(timerproc)

每一个timerBucket会有一个单独的timer goroutine来维护,所以并不是每一个timer对应一个goroutine;

waiting:无timer,timer goroutine置入waiting状态,等待重新调度;

sleeping(挂起):有timer等待触发但不是现在,timer goroutine进入短暂挂起;

noteclear/ notetsleepg / notewakeup 底层原理和gopark/goready一致,只是notetsleepg支持定时唤醒;

 

总结

  1. 所有timer由golang底层统一管理;
  2. 底层数据结构为4叉小顶堆(时间堆),利用了"最小堆"的某些特性,以牺牲精度为代价,降低了维护的时间复杂度(O(log4N);
  3. 时间堆的数量固定为64,每一个时间堆对应一个goroutine(timer goroutine);
  4. 并发性能优化:timer goroutine只在必要时执行,混存优化等;

参考:

https://github.com/cch123/golang-notes/blob/master/timer.md

 

Future:

  1. (多级)时间轮算法(linux内核,https://blog.csdn.net/zhanglh046/article/details/72833172
  2. 红黑树(nginx, https://www.cnblogs.com/doop-ymc/p/3440316.html

 

tip

 

在回到golang的atomic:

 

 

Do not communicate by sharing memory; instead, share memory by communicating.

http://blog.golang.org/share-memory-by-communicating