使用GitHub导入器

在GitLab 10.2中引入了一个新版本的GitHub导入器。这个新的导入器使用Sidekiq并行执行它的工作,大大减少了将GitHub项目导入到GitLab实例所需的时间。

GitHub导入器提供了两种不同类型的导入器:顺序导入器和并行导入器。Rake任务进口:github使用顺序导入器,而其他所有内容使用并行导入器。这两个导入器之间的区别非常简单:顺序导入器在单个线程中完成所有工作,这使得它对调试目的或Rake任务更有用。另一方面,平行进口商使用Sidekiq。

需求

  • GitLab CE 10.2.0或更新版本。
  • Sidekiq工人处理github_importergithub_importer_advance_stage队列(默认情况下启用)。
  • Octokit(用于与GitHub API交互)

代码结构

导入器的代码库分为以下目录:

  • lib / gitlab / github_import:此目录包含大部分代码,例如用于导入资源的类。
  • app /工人/ gitlab / github_import:该目录包含Sidekiq工作程序。
  • 应用程序/工人/ / gitlab / github_import担忧:这个目录包含了一些被各种Sidekiq工作器重用的模块。

体系结构概述

当导入GitHub项目时,我们为项目安排并执行一个作业RepositoryImportworker工人和所有其他进口商一样。然而,与其他进口商不同,我们不会立即执行必要的工作。相反,工作被划分为单独的阶段,每个阶段由一组执行的Sidekiq作业组成。在每个阶段之间安排一个作业,该作业定期检查当前阶段的所有工作是否已完成,如果是这种情况,则将导入过程推进到下一阶段。处理此操作的工作线程被调用Gitlab: GithubImport:: AdvanceStageWorker

阶段

1.RepositoryImportWorker

这个worker将通过为下一个worker安排一个作业来启动导入过程。

2.阶段:ImportRepositoryWorker

这个worker将导入存储库和wiki,并在完成后调度下一阶段。

3.阶段:ImportBaseDataWorker

该工作者将导入基本数据,如标签、里程碑和版本。这项工作是在一个线程中完成的,因为它可以执行得足够快,我们不需要并行地执行这项工作。

4.阶段:ImportPullRequestsWorker

这个worker将导入所有的pull请求。对于每个pull请求,都有一个jobGitlab: GithubImport:: ImportPullRequestWorker工人被调度。

5.阶段:ImportIssuesAndDiffNotesWorker

这个worker将导入所有的issue并提取请求注释。对于每个问题,我们安排了一个作业Gitlab: GithubImport:: ImportIssueWorker工人。对于拉取请求注释,我们改为调度作业Gitlab: GithubImport:: DiffNoteImporter工人。

这个worker并行处理issue和diff notes,所以我们不需要安排一个单独的阶段并等待前一个阶段完成。

问题从拉取请求中单独导入,因为只有“问题”API包含问题和拉取请求的标签。在同一个worker中导入问题和设置标签链接,无需在API数据中执行单独的抓取,从而减少了导入项目所需的API调用数量。

6.阶段:ImportNotesWorker

这个worker为issue和pull请求导入常规注释。对于每个评论,我们为Gitlab: GithubImport:: ImportNoteWorker工人。

常规的评论必须在最后导入,因为使用的GitHub API会为问题和拉取请求返回评论。这意味着我们必须等待所有问题和pull请求被导入,然后才能导入常规注释。

7.阶段:FinishImportWorker

该worker将通过执行一些内务处理(例如刷新任何缓存)并将导入标记为已完成来结束导入过程。

推进阶段

推进阶段有两种方式:

  1. 直接为下一阶段调度工人。
  2. 调度作业Gitlab: GithubImport:: AdvanceStageWorker这将在当前阶段的所有工作完成后推进阶段。

第一种方法只适用于在单个线程中执行所有工作的工人,而AdvanceStageWorker应该用在其他地方。

的方式AdvanceStageWorkerWorks相当简单。当调度作业时,它将获得一个项目ID,一个Redis键列表和下一阶段的名称。Redis密钥(由Gitlab: JobWaiter)用于检查当前运行阶段是否已完成。如果这个阶段还没有完成AdvanceStageWorker会重新安排的。一旦一个阶段结束AdvanceStageworker将刷新导入JID(下面将详细介绍)并安排下一阶段的工作者。

减少…的数量AdvanceStageWorker调度此worker的作业将在决定下一步操作之前短暂地等待作业完成。对于小型项目,这可能会减慢导入过程,但它也会减少整个系统的压力。

刷新导入jid

GitLab包含一个名为StuckImportJobsWorker如果项目导入运行超过15小时,它将定期运行并将其标记为失败。对于GitHub项目,这带来了一点问题:导入大型项目可能需要几个小时,这取决于我们达到GitHub速率限制的频率(详见下面),但我们不希望StuckImportJobsWorker将我们的进口标记为失败,因为这个。

为了防止这种情况发生,我们定期刷新导入流程的过期时间。其工作原理是将导入作业的JID存储在数据库中,然后在整个导入过程的各个阶段刷新该JID的TTL。这是通过调用来完成的项目# refresh_import_jid_expiration。通过刷新这个TTL,我们可以确保只要我们仍在执行工作,我们的导入就不会被标记为失败。

GitHub速率限制

GitHub每小时有5000个API调用的速率限制。导入项目所需的请求数量在很大程度上取决于项目中涉及的唯一用户的数量(例如问题作者)。其他数据,如问题页面和评论,通常只需要几十个请求就可以导入。这是因为我们需要用户的Email地址,以便将它们映射到GitLab用户。

我们通过以下操作来处理这个问题:

  1. 一旦达到速率限制,所有作业将自动重新调度,直到速率限制被重置,它们才会执行。
  2. 我们在Redis中缓存GitHub用户到GitLab用户的映射。

关于用户缓存的更多信息可以在下面找到。

缓存用户查找

当将GitHub用户映射到GitLab用户时,我们需要(在最坏的情况下)执行:

  1. 一个API调用来获取用户的Email地址。
  2. 两个数据库查询查看是否存在相应的GitLab用户。一个查询将尝试根据GitHub用户ID查找用户,而第二个查询用于使用用户的GitHub电子邮件地址查找用户。

因为这个过程非常昂贵,我们将这些查找的结果缓存在Redis中。对于每个查找到的用户,我们存储三个键:

  1. 一个Redis键映射GitHub用户名到他们的电子邮件地址。
  2. 一个映射GitHub电子邮件地址到GitLab用户ID的Redis键。
  3. 一个映射GitHub用户ID到GitLab用户ID的Redis键。

我们缓存了两种类型的查找:

  1. 一个正查找,意味着我们找到了一个GitLab用户ID。
  2. 负查找,意味着我们没有找到GitLab用户ID。缓存这可以防止我们为我们知道在我们的GitLab数据库中不存在的用户执行相同的工作。

这些密钥的过期时间为24小时。当检索一个正查找的缓存时,我们自动刷新TTL。错误查找的TTL永远不会刷新。

由于这个缓存层,新注册的GitHub帐户可能不会链接到相应的GitHub帐户。然而,一旦缓存的键过期,这将自行排序。

用户缓存查找是跨项目共享的。这意味着导入的项目越多,所需的GitHub API调用就越少。

此操作的代码位于:

  • lib / gitlab / github_import / user_finder.rb
  • lib / gitlab / github_import / caching.rb

映射标签和里程碑

为了减少对数据库的压力,我们在为问题和合并请求设置标签和里程碑时不查询数据库。相反,我们在导入标签和里程碑时缓存这些数据,然后在将它们分配给问题/合并请求时重用这些缓存。与用户查找类似,这些缓存键在未使用24小时后自动过期。

与用户查找缓存不同,这些标签和里程碑缓存的作用域限定在正在导入的项目中。

此操作的代码位于:

  • lib / gitlab / github_import / label_finder.rb
  • lib / gitlab / github_import / milestone_finder.rb
  • lib / gitlab / github_import / caching.rb
Baidu
map