简历上写了,面试官问了,我懵了,不会了~

time to live,为key设置失效时间。如果有大量的key,存在着大量的调度器,每一个key使用自己的调度器管理自己的任务时间周期,会很浪费资源,因此不得不提到 时间轮算法
核心是存储key->expiretime的映射map,expireTime到达时,自动删除过期的key。

时间轮算法

不止是redis,其他使用定时特点的都使用时间轮算法。它是一个高效的利用线程资源进行批量化调度的调度模型,大量的调度任务使用同一个调度器,高效的管理各种周期性任务。

type TimeWheel struct {
   interval time.Duration
   ticker   *time.Ticker
   slots    []*list.List
   timer             map[string]*location
   currentPos        int
   slotNum           int
   addTaskChannel    chan task
   removeTaskChannel chan string
   stopChannel       chan bool

使用一个数组表示整个时间轮,数组的每个元素是一个双向循环链表,存储着当前时间间隔中的任务。每个时间轮的时间间隔为 interval,时间轮的个数为slowNum,currentPos指向当前正在执行的时间轮。

  • 开启一个以interval为时间间隔的时间轮
  • **tw**.ticker = ***time***.NewTicker(**tw**.interval)

    时间轮进行select监听

  • 当时间轮开始运行,定时跳动一格 ,更改当前的调度位置currentpos = (currentPos+1)%slotNum,同时在列表中从头节点开始遍历,对于已经过期的节点,会执行回调函数,从数据库中删除。对于下次get key时,会根据ttlMap获取过期时间,与当前时间相比看是否过期,如果过期直接从内存中删除。
  • timewheel.At(expireTime, taskKey, func() {
       keys := []string{key}
    db.RWLocks(keys, nil)
       deferdb.RWUnLocks(keys, nil)
       // check-lock-check, ttl may be updated during waiting lock
    logger.Info("expire " + key)
       // 获取过期时间
       rawExpireTime, ok :=db.ttlMap.Get(key)
       if !ok {
          return
    expireTime, _ := rawExpireTime.(time.Time)
       expired :=time.Now().After(expireTime)
       if expired {
    db.Remove(key)
    

    判断是否要新增任务case task := <-tw.addTaskChannel:

  • 根据expireTime/interval/slotNum判断当前任务所处的圈数,根据(currentPos+delay/interval)%slotNum判断当前所处第几个间隔中
  • 将任务放入该位置的间隔对应的列表中
  • 判断removeTaskChannel 是否有任务

  • 从该时间轴对应的列表中移除该任务
  • 判断stopChannel 是否开启,即是否停止时间轮算法

  • 停止定时器的计时
  • expire key second: key在seconds秒后过期

    向addTaskChannel中放入任务,key是“expire:”+key,time是此次key所对应的过期时间。

    TTL key: 查询键的剩余过期时间

    // 获取key当前的过期时间 raw, exists := db.ttlMap.Get(key) expireTime, _ := raw.(time.Time) ttl := expireTime.Sub(time.Now())

    persist key : 清除key的过期时间

  • 从ttlmap中移除key和expireTime的映射**db**.ttlMap.Remove(key)
  • 将key放入removeTaskChannel,select中监听到channel的io,会移除时间轮中对应位置的任务**tw**.removeTaskChannel <- key
  • 对于过期时间功能的实现,主要使用时间轮算法,通过一个循环数组实现,由定时任务(默认1s)控制着数组元素的更改,数组的每一个元素指向一个双向链表,存储着对应时间段的任务。当使用expire命令行增加key的过期时间时,根据过期时间计算出该任务所处时间轮的圈数和位置。

    使用三个channel,addTaskChannel对应着增加过期时间的任务,包含了所在时间轮的位置和key;stopChannel表示是否要停用该时间轮算法;removeTaskChannel 是为了实现persist指令,清除ttl,将时间轮中对应位置的任务移除。同时会使用map记录key到expireTime的映射,便于ttl命令查询过期时间。使用select监听三个channel,并进行相应的事件。

    定时任务运行,定时更改当前要运行的任务列表,同时执行回调删除过期的key。

    www.cnblogs.com/luozhiyun/p… github.com/Zerlina-ysl…