kube-proxy BoundedFrequencyRunner导致死循环分析

2023-05-16

kube-proxy使用了k8s官方工具库中的BoundedFrequencyRunner实现基于事件及时间间隔的配置同步

1 BounderFrequencyRunner构建

1.1 相关核心代码

// name: Runner名称
// func():目标方法
// minInterval:最小时间间隔(2次目标方法调用的最小间隔)
// maxInterval:最大时间间隔(2次目标方法调用的最大间隔,当没有事件时,以最大时间间隔执行)
// burstRuns:允许突发
func NewBoundedFrequencyRunner(name string, fn func(), minInterval, maxInterval time.Duration, burstRuns int) *BoundedFrequencyRunner {
	timer := &realTimer{timer: time.NewTimer(0)} // will tick immediately
	<-timer.C()                                  // consume the first tick
	return construct(name, fn, minInterval, maxInterval, burstRuns, timer)
}

//construct方法中基于最小时间间隔构建了令牌桶限速器,关键代码如下
qps := float32(time.Second) / float32(minInterval)
bfr.limiter = flowcontrol.NewTokenBucketRateLimiterWithClock(qps, burstRuns, timer)

# 其中rate.Limit(qps)将qps从float32转换为float64
func NewTokenBucketRateLimiterWithClock(qps float32, burst int, c Clock) RateLimiter {
	limiter := rate.NewLimiter(rate.Limit(qps), burst)
	return newTokenBucketRateLimiter(limiter, c, qps)
}

1.2 qps计算测试

func main() {
    duration32 :=float32(time.Second) / float32(3*time.Second)
    duration64 :=float64(time.Second) / float64(3*time.Second)
    duration32To64 := float64(duration32)
    fmt.Println(duration32)
    fmt.Println(duration64)
    fmt.Println(duration32To64)
}
# 执行结果为
0.33333334
0.3333333333333333
0.3333333432674408
结合代码及上述测试结果,可以发现,计算qps时产生了精度丢失,而在转换类型时,又产生了一次精度丢失,导致了实际使用的limit值大于期望值

注意:实际使用的limit值大于期望值

2 BounderFrequencyRunner Loop

BoundedFrequencyRunner 通过Loop方法运行

2.1 相关核心代码

func (bfr *BoundedFrequencyRunner) Loop(stop <-chan struct{}) {
	klog.V(3).Infof("%s Loop running", bfr.name)
	bfr.timer.Reset(bfr.maxInterval)
	for {
		select {
		case <-stop:
			bfr.stop()
			klog.V(3).Infof("%s Loop stopping", bfr.name)
			return
        //基于时间
		case <-bfr.timer.C():
			bfr.tryRun()
        // 基于事件
		case <-bfr.run:
			bfr.tryRun()
        // 基于事件
		case <-bfr.retry:
			bfr.doRetry()
		}
	}
}

3 tryRun

3.1 相关核心代码

func (bfr *BoundedFrequencyRunner) tryRun() {
	bfr.mu.Lock()
	defer bfr.mu.Unlock()

    // 处理逻辑1,限速令牌桶中至少有1个token
	if bfr.limiter.TryAccept() {
		// We're allowed to run the function right now.
		bfr.fn()
		bfr.lastRun = bfr.timer.Now()
		bfr.timer.Stop()
		bfr.timer.Reset(bfr.maxInterval)
		klog.V(3).Infof("%s: ran, next possible in %v, periodic in %v", bfr.name, bfr.minInterval, bfr.maxInterval)
		return
	}

    // 处理逻辑2,限速令牌桶中少于1个token
	// It can't run right now, figure out when it can run next.
	elapsed := bfr.timer.Since(bfr.lastRun)   // how long since last run
	nextPossible := bfr.minInterval - elapsed // time to next possible run
	nextScheduled := bfr.timer.Remaining()    // time to next scheduled run
	klog.V(4).Infof("%s: %v since last run, possible in %v, scheduled in %v", bfr.name, elapsed, nextPossible, nextScheduled)
	// It's hard to avoid race conditions in the unit tests unless we always reset
	// the timer here, even when it's unchanged
	if nextPossible < nextScheduled {
		nextScheduled = nextPossible
	}
	bfr.timer.Stop()
    //下一次往time管道中填充数据的时间为:当前时间+nextScheduled
    //当nextScheduled为负值时,下次调度时间永远小于当前值会引起Loop死循环
	bfr.timer.Reset(nextScheduled)
}

资源事件或周期性定时器,都会触发tryRun方法,在该方法中,处理分为2种情况:
(1)限速令牌桶中至少有1个token,进入if中处理逻辑,此时同步方法被调用,进行配置同步处理下发
(2)限速令牌桶中少于1个token。在按最大时间间隔调用tryRun情况,可能会出现本来应该进行处理逻辑1处理,因为limit的精度误差导致了token偏差,而进行了处理逻辑2处理的情况。此时,elapsed值为最大时间间隔,那么nextPossible值必然小于0。这个时候会导致nextScheduled值为负数,进而导致Loop死循环,cpu占用100%。

4 advance

调用逻辑:TryAccept->AllowN->reserveN→advance

advance方法基于时间计算限速结果

4.1 相关核心代码

func (lim *Limiter) advance(now time.Time) (newNow time.Time, newLast time.Time, newTokens float64) {
	last := lim.last
	if now.Before(last) {
		last = now
	}
    //计算还需要多少时间,把令牌桶填满
	// Avoid making delta overflow below when last is very old.
	// brust令牌桶的大小,maxElapsed=把桶填满的时间(生成剩余容量token需要多久)
	maxElapsed := lim.limit.durationFromTokens(float64(lim.burst) - lim.tokens)
	elapsed := now.Sub(last)
   // 如果TryAccept执行时间间隔大于把桶填满的时间,则时间间隔应该为maxElapsed,即不能桶溢出(如果只是按间隔运行,maxElapsed为1/qps)
	if elapsed > maxElapsed {
		elapsed = maxElapsed
	}
	// Calculate the new number of tokens, due to time that passed.
	// 根据时间间隔,计算现在桶里面应该有多少令牌
	delta := lim.limit.tokensFromDuration(elapsed)
	tokens := lim.tokens + delta
	//如果当前tokens值大于了桶容量,则当前桶中token数量为burst,即不能桶溢出
	if burst := float64(lim.burst); tokens > burst {
		tokens = burst
	}

	return now, last, tokens
}

//用来计算生成指定数量token需要多少时间
func (limit Limit) durationFromTokens(tokens float64) time.Duration {
	seconds := tokens / float64(limit)
	return time.Nanosecond * time.Duration(1e9*seconds)
}

// 用来计算指定时间间隔可以生成多少token
func (limit Limit) tokensFromDuration(d time.Duration) float64 {
	// Split the integer and fractional parts ourself to minimize rounding errors.
	// See golang.org/issues/34861.
	sec := float64(d/time.Second) * float64(limit)
	nsec := float64(d%time.Second) * float64(limit)
	return sec + nsec/1e9
}

在没有事件,只按最大时间间隔执行时,elapsed值等于lim.limit.durationFromTokens(float64(lim.burst) - lim.tokens)(没事件时,maxElapsed=把桶填满的时间,elapsed= maxInterVal,这里把桶填满的最大时间为minInterval值,因此maxElapsed总是小于elapsed, elapsed=1/qps),且当最小时间间隔为2的指数时,maxElapsed=1/qps=最小时间间隔,delta为1。而当最小时间间隔不是2的指数时,maxElapsed=1/qps<最小时间间隔, delta值不为1。这个时候token会产生偏差值即delta-1.(这是由float32和float64的存储格式决定的。qps=1/minInterval会比实际值偏大,elapsed会比实际值偏小,delta值会比实际值偏小

5 reserveN

该方法计算当前tokens值,并返回是否允许Accept结果

5.1 相关核心代码

func (lim *Limiter) reserveN(now time.Time, n int, maxFutureReserve time.Duration) Reservation {
	lim.mu.Lock()

	if lim.limit == Inf {
		lim.mu.Unlock()
		return Reservation{
			ok:        true,
			lim:       lim,
			tokens:    n,
			timeToAct: now,
		}
	}

	now, last, tokens := lim.advance(now)

	// Calculate the remaining number of tokens resulting from the request.
	// 对于TryAccept()调用,这里传入的n是1,这里是减去1个token
	tokens -= float64(n)

	// Calculate the wait duration
	var waitDuration time.Duration
	// 如果tokens值大于0,则表明令牌桶中有1个令牌,TryAccept()这个时候应该为true
	// 如果tokens值小于0,则计算产生差值token需要的时间间隔,如果时间间隔小于1纳秒,则,TryAccept()为Ture,否则为false
	if tokens < 0 {
	    //计算waitDuration时候会用到,这个值可能会比实际值偏小,偏小不会影响ACCEPT
		waitDuration = lim.limit.durationFromTokens(-tokens)
	}

	// Decide result
	//lim.burst值由创建Runner时传入的值决定,这里是1
	//maxFutureReserve 值为0
	ok := n <= lim.burst && waitDuration <= maxFutureReserve

	// Prepare reservation
	r := Reservation{
		ok:    ok,
		lim:   lim,
		limit: lim.limit,
	}
	if ok {
		r.tokens = n
		r.timeToAct = now.Add(waitDuration)
	}

	// Update state
	if ok {
		lim.last = now
		// 这里token值实际上会不断累加每次的delta-1偏差值,直到这个偏差大于1纳秒,会引起本该Accept的没有Accept
		lim.tokens = tokens
		lim.lastEvent = r.timeToAct
	} else {
		lim.last = last
	}

	lim.mu.Unlock()
	return r
}

reserveN在调用lim.advance(now),获取到tokens后会进行判断,判断当前桶中是否可以取出一个令牌。其做法是,用token值减去1,如果token减去1的值大于0,则表明当前桶中有令牌。如果token减去1的值小于0时,则计算产生这个token差的时间,如果这个时间小于1纳秒,则还是认为这次可以accept。在精度丢失情况下,delta值不是1,导致了每次tokens值都出现了delta-1的偏差,这个偏差累加到1纳秒时,会导致本该Accept的情况,没有Accept。导致了nextScheduled为负值

6 问题分析

由于浮点数精度问题,导致实际作用的limit值大于期望值,导致基于limit得到的elapsed偏小,导致基于elapsed得到的token偏小,进而使得某次不该限制的调用,被拒绝,导致了下次调度时间小于当前时间值,导致了死循环,导致了cpu 100%

7 问题复现

在最小时间间隔为3秒,最大时间间隔为10秒时,在运行11天14小时后,cpu 100%。

11天14小时 = 1000800秒 =1000800秒/10秒/周期= 100080周期

7.1 相关代码修改

1 tryRun

func (bfr *BoundedFrequencyRunner) tryRun() {
	bfr.mu.Lock()
	defer bfr.mu.Unlock()

	//if bfr.limiter.TryAccept() {
	//	// We're allowed to run the function right now.
	//	bfr.fn()
	//	bfr.lastRun = bfr.timer.Now()
	//	bfr.timer.Stop()
	//	bfr.timer.Reset(bfr.maxInterval)
	//	klog.V(3).Infof("%s: ran, next possible in %v, periodic in %v", bfr.name, bfr.minInterval, bfr.maxInterval)
	//	return
	//}

    //打印出第1个TryAccept为false时的周期值
	for i := 1; i < 100000000; i++ {
		if !bfr.limiter.TryAccept() {
			klog.Infof("----un accept %v", i)
			break
		}
	}

	// It can't run right now, figure out when it can run next.
	elapsed := bfr.timer.Since(bfr.lastRun)   // how long since last run
	nextPossible := bfr.minInterval - elapsed // time to next possible run
	nextScheduled := bfr.timer.Remaining()    // time to next scheduled run
	klog.V(4).Infof("%s: %v since last run, possible in %v, scheduled in %v", bfr.name, elapsed, nextPossible, nextScheduled)
	if nextPossible < 0 {
		klog.Infof("----nextScheduled %v < 0", nextScheduled)
		return
	}
	// It's hard to avoid race conditions in the unit tests unless we always reset
	// the timer here, even when it's unchanged
	if nextPossible < nextScheduled {
		nextScheduled = nextPossible
	}
	bfr.timer.Stop()
	bfr.timer.Reset(nextScheduled)
}

# 2 advance
func (lim *Limiter) advance(now time.Time) (newNow time.Time, newLast time.Time, newTokens float64) {
	last := lim.last
	if now.Before(last) {
		last = now
	}

	// Avoid making delta overflow below when last is very old.
	maxElapsed := lim.limit.durationFromTokens(float64(lim.burst) - lim.tokens)
	//elapsed := now.Sub(last)
	//if elapsed > maxElapsed {
	//	elapsed = maxElapsed
	//}
    // 因为elapsed 为最大时间间隔,必然大于maxElapsed值,这里直接赋值为maxElapsed
	elapsed := maxElapsed
	// Calculate the new number of tokens, due to time that passed.
	delta := lim.limit.tokensFromDuration(elapsed)
	fmt.Printf("delta %v \n", delta)
	tokens := lim.tokens + delta
	if burst := float64(lim.burst); tokens > burst {
		tokens = burst
	}

	return now, last, tokens
}

# 3 调用
func main() {
	runner := NewBoundedFrequencyRunner("vmELBSyncs", syncVMELBs, 3*time.Second, 10*time.Second, 1)
	go runner.Loop(wait.NeverStop)
	select {
	
	}
}

8 解决方案

由于不能直接修改官方库,且修改后难以验证,这里直接选择使用修改参数的方式解决,即minInterval值为2的指数。

9 ISSUES

https://github.com/kubernetes/kubernetes/issues/103286
https://go-review.googlesource.com/c/time/+/336469/1/rate/rate.go#387

// advance calculates and returns an updated state for lim resulting from the passage of time.
// lim is not changed.
// advance requires that lim.mu is held.
func (lim *Limiter) advance(now time.Time) (newNow time.Time, newLast time.Time, newTokens float64) {
	last := lim.last
	if now.Before(last) {
		last = now
	}

	// Calculate the new number of tokens, due to time that passed.
	// 这里不再计算maxElapsed,因为burst判断,可以保证tokens不会溢出
	// 计算maxElapsed,会因为limit精度问题,导致maxElapsed偏小,进而导致delta偏小,进而导致tokens偏小
	elapsed := now.Sub(last)
	delta := lim.limit.tokensFromDuration(elapsed)
	tokens := lim.tokens + delta
	if burst := float64(lim.burst); tokens > burst {
		tokens = burst
	}
	return now, last, tokens
}

// durationFromTokens is a unit conversion function from the number of tokens to the duration
// of time it takes to accumulate them at a rate of limit tokens per second.
func (limit Limit) durationFromTokens(tokens float64) time.Duration {
	seconds := tokens / float64(limit)
	return time.Duration(float64(time.Second) * seconds)
}

// tokensFromDuration is a unit conversion function from a time duration to the number of tokens
// which could be accumulated during that duration at a rate of limit tokens per second.
func (limit Limit) tokensFromDuration(d time.Duration) float64 { 
    //这里相较于原先其实没有变化,只是进行了方法的封装
    
	return d.Seconds() * float64(limit)
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

kube-proxy BoundedFrequencyRunner导致死循环分析 的相关文章

  • STM32——ARM与STM32之间的联系

    ARM与STM32之间的联系 stm32是基于ARM内核的一种控制器 xff0c 是包含与被包含的关系 ARM xff08 STM32 xff09
  • VTK坐标转换

    VTK坐标转换
  • 软件工程师校招面试救急包

    LeetCode牛人总结 xff08 手撕代码前看看 xff0c 抱佛脚 xff09 https github com labuladong fucking algorithm blob master README md 剑指offer x
  • unix环境高级编程——UNIX体系架构

    本期主题 xff1a unix环境高级编程 UNIX体系架构 文件IO 0 初始UNIX1 系统调用2 库函数2 1 C语言的运行库 3 shell 0 初始UNIX 这里略过unix的历史不讲 xff0c 网上有比较详细的资料 我们可以将
  • CVPR2019:无监督深度追踪

    本文提出了一种无监督的视觉跟踪方法 与使用大量带注释数据进行监督学习的现有方法不同 xff0c 本文的CNN模型是在无监督的大规模无标签视频上进行训练的 动机是 xff0c 强大的跟踪器在向前和向后预测中均应有效 xff08 即 xff0c
  • 各种智能优化算法比较与实现(matlab版)

    各种智能优化算法比较与实现 xff08 matlab版 xff09 一 方法介绍 1免疫算法 xff08 Immune Algorithm xff0c IA xff09 1 1算法基本思想 免疫算法是受生物免疫系统的启发而推出的一种新型的智
  • 我与人工智能的故事

    本文作者 xff1a 诸葛越 前 言 人工智能的三次浪潮 2018年年初 xff0c 招聘季正如火如荼地进行 xff0c 而 数据科学家 和 算法工程师 绝对算得上热门职业 人工智能 机器学习 深度学习 建模 卷积神经网络 等关键词 xff
  • ovn-architecture

    参考 文章目录 1 Name2 Description2 1 Information Flow in OVN OVN中的信息流向 2 2 Chassis Setup2 3 Logical Networks2 4 Life Cycle of
  • IDEA创建maven项目添加jar包依赖出错

    Problem1 xff1a 由于网络原因无法下载jar包 解决方法 xff1a 在maven的settings xml文件的 lt mirrors gt 标签中配置阿里镜像 lt mirror gt lt id gt nexus aliy
  • 判断一个字符串是否为回文字符串

    题目 输入一个字符串 xff0c 判断该字符串中除去空格之后的字符串是否为回文字符串 要求 xff1a 不可使用 Java 已实现的方法替换空格 xff0c 不可消耗额外的空间 代码实现 此处为判断方法的实现 public static b
  • Spring中的 JdbcTemplate和声明式事务控制

    Spring中的 JdbcTemplate和声明式事务控制 JdbcTemplate概述 JdbcTemplate的作用 xff1a 他就是用于和数据库交互的 xff0c 实现CRUD操作 如何创建该对象 在dao的实现类中定义并用set方
  • SpringMVC(一)

    SpringMVC xff08 一 xff09 SpringMVC的基本概念 三层架构 表现层业务层持久层 MVC模型 Model xff08 模型 xff09 xff1a 通常就是指我们的数据模型 xff0c 一般情况下用于封装数据 Vi
  • SpringMVC(二)

    SpringMVC xff08 二 xff09 响应数据和结果视图 返回值分类 xff1a 字符串voidModelAndView 对象 xff1a 是 spring 提供的一个对象 xff0c 可以用来调整具体的 JSP 视图 span
  • SpringMvc(三)

    SpringMvc xff08 三 xff09 SSM 整合可以使用多种方式 xff0c 一般会选择 XML 43 注解 的方式 整合的思路 xff1a 搭建整合环境先把spring 的配置搭建完成再使用 spring 整合SpringMV
  • 工厂方法模式(Factory Method)--多态工厂的实现

    工厂方法模式 xff08 Factory Method xff09 多态工厂的实现 定义 xff1a 定义一个用于创建对象的接口 xff0c 让子类决定实例化哪一个类 xff0c 工厂方法使一个类的实例化延迟到其子类 类图 xff1a 外链
  • 无人机自主定位导航避障VINS+fast_planner实测~

    厦大研一研究的一个项目 xff0c 将项目开发用到的技术和难点在这记录一下 常更新 xff0c 先把框架写好 xff0c 有空的时候就过来更新 xff0c 要是有漏的或者有错误的地方 xff0c 请大佬指点 因为采用的是TX2 xff0c
  • rs_D455相机内外参标定+imu联合标定

    IMU标定 lt launch gt lt node pkg 61 34 imu utils 34 type 61 34 imu an 34 name 61 34 imu an 34 output 61 34 screen 34 gt lt
  • GVINS论文简明解读

    VIN与GNSS融合的必要性 VIN系统只工作在局部坐标系下 x y z yaw不可观 里程计存在不可避免的漂移 而GNSS系统可提供无漂移的全局定位 VIN与GNSS融合的难点 不同于cmaera与imu此类的外参标定 GNSS坐标与VI
  • onos2.5.2编译安装

    onos编译安装 Ubuntu18 04 1 前置下载安装 1 1 前置包安装 参考docker file sudo apt get install y ca certificates zip python python3 git bzip
  • C++和C的区别(汇总)

    1 C是面向过程的语言 xff0c 而C 43 43 是面向对象的语言 2 C和C 43 43 动态管理内存的方法不一样 xff0c C是使用malloc free函数 xff0c 而C 43 43 除此之外还有new delete关键字

随机推荐

  • PX4学习笔记—通过串口发送自定义数据

    最近因为项目需要实现一个通过pixhawk串口收发自定义数据的功能 搜索发现 xff0c 博客上大神们FantasyJXF FreeApe的博文已经详细介绍了通过pixhawk串口读取自定义数据 xff0c 这部分功能实现后就可以将自己开发
  • 一步步入门搭建SpringSecurity OAuth2(密码模式)

    什么是OAuth2 xff1f 是开放授权的一个标准 xff0c 旨在让用户允许第三方应用去访问改用户在某服务器中的特定私有资源 xff0c 而可以不提供其在某服务器的账号密码给到第三方应用 大概意思就是比如如果我们的系统的资源是受保护的
  • STM32驱动SG90舵机与HC_SR04超声波模块

    前言 一 项目涉及的内容 项目简介 二 模块实操 1 SysTick系统定时器模块 2 SG90 舵机模块 3 HC SR04 超声波模块 4 main函数 总结 前言 这篇文章的内容主要对一个超声波 43 舵机小项目进行总结 xff0c
  • STM32基于IIC协议的OLED模块的使用

    前言 一 项目涉及的内容 项目简介 二 模块实操 1 IIC模块 1 1 IIC协议格式 1 2 开始信号与停止信号 1 3 写数据 1 3 1 硬件IIC代码编写 1 3 2 软件模拟IIC代码编写 2 OLED板块 前言 本篇文章对使用
  • STM32DMA功能详解

    目录 一 DMA的基本介绍 1 什么是DMA xff08 DMA的基本定义 xff09 2 DMA传输参数 3 DMA的主要特征 二 DMA功能框图 1 DMA请求 2 通道 3 仲裁器 三 DMA 数据配置 1 从哪里来到哪里去 外设到存
  • STM32对FreeRTOS单片机实时操作系统的移植

    前言 一 FreeRTOS是什么 二 FreeRTOS的移植 1 资料下载 2 开始移植 2 1 移植Source源码文件 2 2 添加 FreeRTOSConfig h 2 3 添加SYSTEM文件夹 2 4 复制 main c 文件进行
  • C语言 指针中的常见名称与用法

    目录 前言 一 指针是什么 二 指针与数组 数组指针 指针数组 三 指针与常量 指针常量 常量指针 四 指针与函数 指针函数 函数指针 前言 指针是C语言中大家接触的比较早但是也是内容比较多和实用的一块知识点 xff0c 之前虽然也大概知道
  • 过拟合及常见处理办法整理

    过拟合及常见处理办法整理 jingbo18的博客 CSDN博客 模型过拟合如何解决 判断方法 过拟合 xff08 over fitting xff09 xff0c 机器学习模型或者是深度学习模型在训练样本中表现得过于优越 xff0c 导致在
  • OBS 安装与考试参数设置及屏幕无法完全捕获、录屏不完整的解决方法

    目录 一 OBS 的下载与安装 二 OBS 考试参数设置 三 问题解决 xff08 1 xff09 屏幕无法完全捕获 xff08 2 xff09 录屏不完整 一 OBS 的下载与安装 官网 xff08 Open Broadcaster So
  • kube-proxy源码阅读(iptables实现)

    Reference 文章目录 1 入口2 ProxyServer创建及调用3 ProxyServer 核心调用流程3 1 func o Options Run err3 2 func o Options runLoop error3 3 f
  • TypeError: Expected Ptr<cv::UMat> for argument ‘image‘

    python3 43 opencv TypeError Expected Ptr xff1c cv UMat xff1e for argument image 输入的不是UMat格式 xff0c 使用cv2 UMat img get 转化一
  • SLAM中的因子图

    看论文 xff0c 发现很多SLAM方面的文章都涉及图优化 xff0c 其中更包含有因子图 正好前段时间看了PRML xff0c 将其进行整理 xff08 诶 xff0c 果然理论的内容就是得及时用起来 xff0c 现在又记不太清楚了 xf
  • TFmini Plus IIC在开源飞控 pixhawk上的应用

    TFmini Plus 可以在 Pixhawk 中使用 xff0c 以避开障碍物 本文结构 xff1a 1 TFmini Plus IIC在开源飞控 pixhawk上的应用 2 TFmini Plus参数设置 3 配置说明 1 TFmini
  • TFmini Plus 在开源飞控 pixhawk 上的应用

    TFmini Plus 在开源飞控 pixhawk 上的应用 TFmini Plus 可以直接连接 Pixhawk 的串口使用 飞行器可以使用 TFmini Plus 来实现定高或 者避障功能 本文档适用于 pixhawk ArduCopt
  • TFmini在开源飞控PX4上的应用

    TFmini在开源飞控PX4上的应用 TFmini 是一款小型激光雷达模组 主要实现实时 无接触式的距离测量功能 xff0c 具有测量准确 稳定 高速的特点 产品型号 xff1a TFmini 产品名称 xff1a 小型激光雷达模组 制造商
  • TFmini Plus在开源飞控PX4上的应用

    TFmini Plus在开源飞控PX4上的应用 PX4有着自己独特的优势 xff0c 受到广大爱好者的喜爱 TFmini Plus是北醒公司推出的性价比极高的激光雷达 xff0c 受到广大爱好者的追捧 本文介绍TFmini Plus和PX4
  • TFmini在开源飞控pixhawk上的应用

    TFmini在开源飞控pixhawk上的应用 TFmini可以直接连接Pixhawk的串口使用 飞行器可以使用TFmini来实现定高或者避障功能 本文档适用于pixhawk ArduCopter V3 6 2或更高版固件 xff08 注 x
  • 北邮oj-旋转图像

    include lt bits stdc 43 43 h gt using namespace std define maxn 105 int buf maxn maxn int ans maxn maxn int main int T N
  • 获取激光雷达数据

    从激光雷达获得距离 搭建turtlebot仿真环境 下载 sudo apt get install ros kinetic turtlebot 配置环境 sudo apt get install ros kinetic joy 将turtl
  • kube-proxy BoundedFrequencyRunner导致死循环分析

    kube proxy使用了k8s官方工具库中的BoundedFrequencyRunner实现基于事件及时间间隔的配置同步 1 BounderFrequencyRunner构建 1 1 相关核心代码 name Runner名称 func s