首页 > 其他分享 >对tidb-lightning导入机制的一点点研究

对tidb-lightning导入机制的一点点研究

时间:2023-04-09 10:31:44浏览次数:41  
标签:scheduler max region lightning 导入 pd tidb ctl config

作者: buddyyuan



前言

最近生产上出现了一个问题,就是一堆 empty region 不进行合并。通过分析发现是和lightning失败有关的,于是把这个问题研究了一下,以下是关于这个问题的一点点原理。



Lightning 究竟停止了什么

首先我们先阅读一下官方文档。

在导入数据之前,tidb-lightning 会自动将 TiKV 节点切换为“导入模式” (import mode),优化写入效率并停止自动压缩。tidb-lightning 会根据 TiDB 集群的版本决定是否停止全局调度。

  1. 当 TiDB 集群版本 >= v6.1.0 且 TiDB Lightning 版本 >= v6.2.0 时,tidb-lightning 在向 TiKV 导入数据时,只会暂停目标表数据范围所在 region 的调度,并在目标表导入完成后恢复调度。
  2. 当 TiDB 集群版本 < v6.1.0 或 TiDB Lightning 版本 < v6.2.0 时,tidb-lightning 会暂停全局调度。

通过官方文档的导读,我们可以清楚的知道,当TiDB版本<6.1版本的时候 或者是 TiDB Lightning<6.2.0的时候,它会暂停全局调度。这也是我本次遇到的问题,它究竟停了哪些调度?



做个实验验证一下

要简单验证这个问题,就是找个 TiDB Lightning 版本 <6.2 的验证一下,我这里是 6.1 的版本,我做了一个简单的 lightning 的导入。在日志中我们发现了以下日志。

[2023/03/11 14:16:55.211 +08:00] [INFO] [pd.go:416] ["pause scheduler successful at beginning"] [name="[balance-hot-region-scheduler,balance-leader-scheduler,balance-region-scheduler]"]
[2023/03/11 14:16:55.225 +08:00] [INFO] [pd.go:424] ["pause configs successful at beginning"] [cfg="{\\\\"enable-location-replacement\\\\":\\\\"false\\\\",\\\\"leader-schedule-limit\\\\":12,\\\\"max-merge-region-keys\\\\":0,\\\\"max-merge-region-size\\\\":0,\\\\"max-pending-peer-count\\\\":2147483647,\\\\"max-snapshot-count\\\\":40,\\\\"region-schedule-limit\\\\":40}"]

打开6.1的源码,搜索一下pause scheduler successful at beginning关键字,发现是在 br -> pkg -> pdutl - > pd.go 代码中,和我们日志打印的信息可以辉映上。

func (p *PdController) pauseSchedulersAndConfigWith(ctx context.Context, schedulers []string, schedulerCfg map[string]interface{}, post pdHTTPRequest, ) ([]string, error) {
   // first pause this scheduler, if the first time failed. we should return the error
   // so put first time out of for loop. and in for loop we could ignore other failed pause.
   removedSchedulers, err := p.doPauseSchedulers(ctx, schedulers, post)
   if err != nil {
      log.Error("failed to pause scheduler at beginning",
         zap.Strings("name", schedulers), zap.Error(err))
      return nil, errors.Trace(err)
   }
   log.Info("pause scheduler successful at beginning", zap.Strings("name", schedulers))
   if schedulerCfg != nil {
      err = p.doPauseConfigs(ctx, schedulerCfg, post)
      if err != nil {
         log.Error("failed to pause config at beginning",
            zap.Any("cfg", schedulerCfg), zap.Error(err))
         return nil, errors.Trace(err)
      }
      log.Info("pause configs successful at beginning", zap.Any("cfg", schedulerCfg))
   }

   go func() {
      tick := time.NewTicker(pauseTimeout / 3)
      defer tick.Stop()

      for {
         select {
         case <-ctx.Done():
            return
         case <-tick.C:
            _, err := p.doPauseSchedulers(ctx, schedulers, post)
            if err != nil {
               log.Warn("pause scheduler failed, ignore it and wait next time pause", zap.Error(err))
            }
            if schedulerCfg != nil {
               err = p.doPauseConfigs(ctx, schedulerCfg, post)
               if err != nil {
                  log.Warn("pause configs failed, ignore it and wait next time pause", zap.Error(err))
               }
            }
            log.Info("pause scheduler(configs)", zap.Strings("name", removedSchedulers),
               zap.Any("cfg", schedulerCfg))
         case <-p.schedulerPauseCh:
            log.Info("exit pause scheduler and configs successful")
            return
         }
      }
   }()
   return removedSchedulers, nil
}

这段代码调用 p.doPauseSchedulersp.doPauseConfigs 两个函数,分别去暂停 SchedulerConfig。然后这个方法里面还有一个 go func() {…}(),它会创建和启动一个协程,这个协程会循环根据上下文信息,来判断是否退出循环和结束协程。这个协程里面执行的操作也是暂停 SchedulerConfig。这样做的目的是保证 lightning 在执行的过程中,SchedulerConfig 必须处于暂停或者关闭的状态,如果被谁中途打开了,就继续再关闭它。

需要注意的一点是这个函数的两个入参,分别是 schedulersschedulerCfg,这两个参数分别被传入给了 doPauseSchedulersdoPauseConfigs 函数,也就是暂停 SchedulerConfig 的函数,因此,我们分析停止了什么,就要分析函数的入参( schedulersschedulerCfg

而这两个入参,稍微翻一下代码就可以找到。schedulers 上面有定义。

Schedulers = map[string]struct{}{
		"balance-leader-scheduler":     {},
		"balance-hot-region-scheduler": {},
		"balance-region-scheduler":     {},

		"shuffle-leader-scheduler":     {},
		"shuffle-region-scheduler":     {},
		"shuffle-hot-region-scheduler": {},
	}

这里就是要停止的调度。这里列了6种,而其实对我们来说它只会停止前面三种。因为当前数据库默认装好是以下4种调度。

[root@test ~]# tiup ctl:v6.1.1 pd -u 127.0.0.1:2379 scheduler show
Starting component `ctl`: /root/.tiup/components/ctl/v6.1.1/ctl pd -u 127.0.0.1:2379 scheduler show
[
  "balance-hot-region-scheduler",
  "balance-leader-scheduler",
  "balance-region-scheduler",
  "split-bucket-scheduler"
]

schedulerCfg 这个入参稍微复杂一些,我们需要找到函数上一层的调用。

func (p *PdController) doRemoveSchedulersWith(
	ctx context.Context,
	needRemoveSchedulers []string,
	disablePDCfg map[string]interface{},
) ([]string, error) {
	var removedSchedulers []string
	var err error
	if p.isPauseConfigEnabled() {
		// after 4.0.8 we can set these config with TTL
		removedSchedulers, err = p.pauseSchedulersAndConfigWith(ctx, needRemoveSchedulers, disablePDCfg, pdRequest)
	} else {
		// adapt to earlier version (before 4.0.8) of pd cluster
		// which doesn't have temporary config setting.
		err = p.doUpdatePDScheduleConfig(ctx, disablePDCfg, pdRequest)
		if err != nil {
			return nil, err
		}
		removedSchedulers, err = p.pauseSchedulersAndConfigWith(ctx, needRemoveSchedulers, nil, pdRequest)
	}
	return removedSchedulers, err
}

上层函数调用的时候,看注释其实分两种情况,一种是4.0.8之后,注意看这里调用 pauseSchedulersAndConfigWith 的时候传入的参数是 disablePDCfg。所以只要弄清楚 disablePDCfg 是什么就清楚了它要停的 config 配置。而 disablePDCfg 信息继续搜索得知,它的取值是循环自 expectPDCfg 的。

disablePDCfg := make(map[string]interface{}, len(expectPDCfg))
	originPDCfg := make(map[string]interface{}, len(expectPDCfg))
	for cfgKey, cfgValFunc := range expectPDCfg {
		value, ok := scheduleCfg[cfgKey]
		if !ok {
			// Ignore non-exist config.
			continue
		}
		disablePDCfg[cfgKey] = cfgValFunc(len(stores), value)
		originPDCfg[cfgKey] = value
	}

那么 expectPDCfg 又是什么呢 ?其实在最上面也定义了。

expectPDCfg = map[string]pauseConfigGenerator{
		"max-merge-region-keys": zeroPauseConfig,
		"max-merge-region-size": zeroPauseConfig,
		// TODO "leader-schedule-limit" and "region-schedule-limit" don't support ttl for now,
		// but we still need set these config for compatible with old version.
		// we need wait for <https://github.com/tikv/pd/pull/3131> merged.
		// see details <https://github.com/pingcap/br/pull/592#discussion_r522684325>
		"leader-schedule-limit":       pauseConfigMulStores,
		"region-schedule-limit":       pauseConfigMulStores,
		"max-snapshot-count":          pauseConfigMulStores,
		"enable-location-replacement": pauseConfigFalse,
		"max-pending-peer-count":      constConfigGeneratorBuilder(maxPendingPeerUnlimited),
	}

到这里,我们基本清楚了它要停止的7个config配置信息。但是它要改成多少呢 ?从上面的代码可以得知为下面的值。

"max-merge-region-keys" -> 0
"max-merge-region-size" -> 0
"leader-schedule-limit" -> (函数返回值是return math.Min(40, rawCfg*float64(stores)),返回值是将当前值 * store数量,然后和40比较那个值小选哪个)
"region-schedule-limit" -> (函数返回值是return math.Min(40, rawCfg*float64(stores)),返回值是将当前值 * store数量,然后和40比较那个值小选哪个)
"max-snapshot-count"    -> (函数返回值是return math.Min(40, rawCfg*float64(stores)),返回值是将当前值 * store数量,然后和40比较那个值小选哪个)
"enable-location-replacement" -> 修改成pauseConfigFalse ,函数返回值是false
"max-pending-peer-count" -> 修改成 constConfigGeneratorBuilder(maxPendingPeerUnlimited),maxPendingPeerUnlimited uint64 = math.MaxInt32,这个值是2的31次方=2147483648

到此为止我们就彻底从源码上弄清楚了,在< 6.2 版本的 lightning 中,究竟会停止哪些schedule 和 config 。



总结一下如何恢复

最后总结一下如何恢复,当 lightning 出现异常后,根据我前面查看的配置反向操作就行了。

1.首先需要检查 schedulers 的状态,如果发现 schedulers 处于暂停状态,需要按照下一步来恢复。

//查看当前暂停的调度
tiup ctl:v6.1.1 pd -u 127.0.0.1:2379 scheduler show --status paused

//将以下停止的调度 resume
tiup ctl:v6.1.1 pd -u 127.0.0.1:2379 scheduler resume "balance-hot-region-scheduler"
tiup ctl:v6.1.1 pd -u 127.0.0.1:2379 scheduler resume "balance-region-scheduler"
tiup ctl:v6.1.1 pd -u 127.0.0.1:2379 scheduler resume "split-bucket-scheduler"

2.恢复config 的值,这里恢复到值需要根据上面的源码的公式进行反向推演。

tiup ctl:v6.1.1 pd -u 127.0.0.1:2379 config set max-merge-region-keys 200000
tiup ctl:v6.1.1 pd -u 127.0.0.1:2379 config set max-merge-region-size 20
tiup ctl:v6.1.1 pd -u 127.0.0.1:2379 config set leader-schedule-limit 4
tiup ctl:v6.1.1 pd -u 127.0.0.1:2379 config set region-schedule-limit 2048
tiup ctl:v6.1.1 pd -u 127.0.0.1:2379 config set enable-location-replacement true
tiup ctl:v6.1.1 pd -u 127.0.0.1:2379 config set max-pending-peer-count 64
tiup ctl:v6.1.1 pd -u 127.0.0.1:2379 config set max-snapshot-count 64

3.恢复完成后,可以到 grafana 相关面板里面去检查一下调度是否在正常运行。

需要注意的是,以上步骤仅适用于 tidb-lightning 版本 < 6.2 的情况。

标签:scheduler,max,region,lightning,导入,pd,tidb,ctl,config
From: https://blog.51cto.com/u_15550868/6178532

相关文章

  • SimpleAdmin手摸手教学之:导入导出
    一、说明导入导出是系统中经常需要用到的功能,大部分系统的导入功能都是上传一个excel文件,然后导入成功就提示导入成功,失败就提示导入失败,顶多返回一个导入结果的excel,非常的不直观。如何设计一个优雅的导入让用户能非常直观的在数据还没导入进系统的时候看到有哪些数据是可以导入......
  • 导入 three.js 库
    发现导入three.js文件的时候,官方文档的写法是:import*asTHREEfrom'three';我并不清除three指的是文件夹还是js文件,如果是后者,应当加上.js后缀由于我并没有使用任何框架,我发现只有导入Three.js文件才能运行:import*asTHREEfrom'../node_modules/three/src......
  • TS 导入导出那些事
    前言最近用TypeScript写npm包,各种模块、命名空间、全局定义等等扰得我睡不着觉。我便苦心研究,总结了几个比较冷门的,国内貌似基本上找不到资料的导入导出用法,顺便在其中又插入一些不那么冷门的用法,于是本篇文章来了。因为一开始也没想做成大全,可能之后还会继续更新吧。目录......
  • vue导入处理Excel表格详解
    https://blog.csdn.net/m0_46309087/article/details/125022676 目录1.前言2.vue导入Excel表格2.1使用ElementUI中的upload组件2.2使用input文件上传3.总体代码与效果4.总结1.前言  最近遇到前端导入并处理excel表格的情况,趁此机会刚好研究一下vue导入并处理excel数据;......
  • IDEA导入普通web项目
    公司项目是普通web项目,导入使用IDEA启动需要一定的步骤,在此做下记录在IDEA中依次点击File->New->ProjectfromExistingSources选中项目目录之后直接下一步下一步下一步。然后点击File->ProjectStructure在Project面板修改SDK版本在Modules面板中的Sources面板把res......
  • spring导入第三方资源对应的配置类
      importcom.alibaba.druid.pool.DruidDataSource;importorg.springframework.beans.factory.annotation.Value;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.PropertySource;@PropertySource("classpa......
  • 调用kibana API操作,导入导出仪表板和索引
    导出导出ChatGPT:Java1importjava.io.*;2importjava.net.*;3importorg.apache.commons.io.IOUtils;4importorg.json.JSONObject;56publicclassExportAndImportKibanaDashboardAndIndex{7publicstaticvoidmain(String[]args)throwsExcepti......
  • python中动态导入文件的方法
    1.简介在实际项目中,我们可能需要在执行代码的过程中动态导入包并执行包中的相应内容,通常情况下,我们可能会将所需导入的包及对象以字符串的形式传入,例如test.test.run,下面将介绍如何动态导入。假设存在如下包:其中test.py的内容如下:count=1defrun():print("run")......
  • 导入jar包到本地的maven仓库
    当我们需要用maven来管理依赖但是又没有在线的仓库可用时,可以直接导入到本地仓库来管理依赖。在cmd中执行一下命令:mvninstall:install-file"-Dfile=testjar1-1.2-SNAPSHOT.jar""-DgroupId=com.test.test""-DartifactId=testjar1""-Dversion=1.2-SNAPSHOT""-Dpackaging=......
  • vue excel导入,导出
    @GetMapping("/exportExample")@Inner(false)//publicRexportExample(Integercs,Stringcs2){publicvoidexportExample(MeterWatermeterWater,HttpServletResponseresponse)throwsIOException{//查询所有用户Map<String,......