vppinfra --- Timer Wheels(时间轮)

2023-03-07 17:01:36 浏览数 (3)

Vppinfra支持可配置计时器的时间轮。可以在…/src/vppinfra/tw_timer_template.[ch]中查看源代码。以及…/src/vppinfra/tw_timer_xx.[ch]中定义的相当数量的模板实例。

关于时间轮的实现原理详细介绍可以参考文章:

https://blog.csdn.net/u013256816/article/details/80697456。是介绍kafka时间轮的实现。vpp 中实现细节时间轮也是一样的。这里不再讲解了。

tw_timer_template.h的实例化生成命名结构来实现特定的计时器时间轮。选择包括:计时器轮的数量(当前最多支持3个),每个时间轮的环槽的数量(必须是2的幂),以及每个“对象句柄”的计时器数量。

以tw_timer_2t_1w_2048sl.h模版为例子,时间轮的数量是1,时间轮的ring槽数量是2048,每个对象的句柄的占用的bit数量是2.通过make_internal_timer_handle函数可以了解到用户时间轮句柄user_handle是由2部分组成的。pool_index及timer_id。使用者需要注意。

代码语言:javascript复制
#define TW_TIMER_WHEELS 1
    #define TW_SLOTS_PER_RING 2048
    #define TW_RING_SHIFT 11
    #define TW_RING_MASK (TW_SLOTS_PER_RING -1)
    #define TW_TIMERS_PER_OBJECT 2
    #define LOG2_TW_TIMERS_PER_OBJECT 1
    #define TW_SUFFIX _2t_1w_2048sl
    #define TW_FAST_WHEEL_BITMAP 0
    #define TW_TIMER_ALLOW_DUPLICATE_STOP 0

static inline u32
TW (make_internal_timer_handle) (u32 pool_index, u32 timer_id)
{
  u32 handle;
  ASSERT (timer_id < TW_TIMERS_PER_OBJECT);
#if LOG2_TW_TIMERS_PER_OBJECT > 0
  ASSERT (pool_index < (1 << (32 - LOG2_TW_TIMERS_PER_OBJECT)));
  handle = (timer_id << (32 - LOG2_TW_TIMERS_PER_OBJECT)) | (pool_index);
#else
  handle = pool_index;
#endif
  return handle;
}

时间轮使用实例:

我们可以查看vpp测试例子src/vppinfra/test_tw_timer.c来了解使用。

1、时间轮初始化

初始化一个间隔1s的的时间轮。每秒时间轮转一个槽位,最多支持2048s。timer interval也可以设置成0.1s,那么每秒ticks就是10。最多支持204.8s。

代码语言:javascript复制
tw_timer_wheel_init_2t_1w_2048sl (&tm->single_wheel,
              expired_timer_single_callback,
              1.0 / * timer interval * / );

2、添加计时任务

当前设置的最大支持expiration_time_in_u32_ticks 是2048s,超过就达不到准确计时器的作用,也可能存在异常。

代码语言:javascript复制
handle = tw_timer_start_2t_1w_2048sl (&tm->single_wheel, elt_index,
                                   [0 | 1] / * timer id * / ,
                               expiration_time_in_u32_ticks);

3、删除计时任务

代码语言:javascript复制
tw_timer_stop_2t_1w_2048sl (&tm->single_wheel, handle);

4、周期性调用查看是否过期事件。

代码语言:javascript复制
 tw_timer_expire_timers_2t_1w_2048sl (tw, now)

5、计时时间到时,会调用回调处理函数

代码语言:javascript复制
    static void
    expired_timer_single_callback (u32 * expired_timers)
    {
      int i;
        u32 pool_index, timer_id;
        tw_timer_test_elt_t *e;
        tw_timer_test_main_t *tm = &tw_timer_test_main;

        for (i = 0; i < vec_len (expired_timers);
            {
            pool_index = expired_timers[i] & 0x7FFFFFFF;
            timer_id = expired_timers[i] >> 31;

            ASSERT (timer_id == 1);

            e = pool_elt_at_index (tm->test_elts, pool_index);

            if (e->expected_to_expire != tm->single_wheel.current_tick)
              {
                fformat (stdout, "[%d] expired at %d not %dn",
                         e - tm->test_elts, tm->single_wheel.current_tick,
                         e->expected_to_expire);
              }
         pool_put (tm->test_elts, e);
         }
     }

总结

简单介绍了时间轮的api,具体实现细节需要自己去阅读代码,实现逻辑并不复杂。时间轮的添加、删除、更新操作都是0(1)复杂度。在vpp主机协议栈及flowprope-plugins都有使用。

1 人点赞