外贸营销型网站制作公司,建立个人网页需要多少钱,网站案例鉴赏,开发一个软件的流程是什么最近开发了一个部署相关的工具#xff0c;使用 Jenkins 来构建应用。Jenkins 的任务从模板中创建而来。每次部署时#xff0c;通过 Jenkins API 来触发构建任务。在线上运行时发现#xff0c;通过 API 触发的 Jenkins 任务总是会时不时在队列中等待较长的时间。某些情况下的… 最近开发了一个部署相关的工具使用 Jenkins 来构建应用。Jenkins 的任务从模板中创建而来。每次部署时通过 Jenkins API 来触发构建任务。在线上运行时发现通过 API 触发的 Jenkins 任务总是会时不时在队列中等待较长的时间。某些情况下的等待时间甚至长达几分钟。直接在 Jenkins 界面上触发的任务却几乎不需要排队直接马上就可以执行。过长的等待时间影响了构建的效率这是一个急需解决的问题。这个问题奇怪的地方在于手动从界面上触发的任务几乎不需要排队而 API 触发的任务的排队时间则完全随机毫无规律可言。 当任务在队列中时Jenkins 会在界面上显示该任务在队列中等待的原因。对于 API 创建的任务它们的等待原因是“Finished waiting”。从这个原因的字面含义确实看不出来什么。当出现这样的问题时最直接的办法是从源代码中寻找答案。 从 GitHub 上把 Jenkins 的源代码下载到本地。寻找问题的起点是搜索字符串“Finished waiting”。这个字符串定义在资源包resource bundle中对应的键是 Queue.FinishedWaiting。再搜索使用了这个键的代码定位到了类 hudson.model.Queue。从名字可以看出来这个类表示的是 Jenkins 内部的工作队列。在这个类中定义了可能出现在队列中的不同类型的条目。类 WaitingItem 表示的是处于等待状态的条目。这个类的 getCauseOfBlockage()方法刚好用到了Queue.FinishedWaiting 这个键表示当前条目被阻塞的原因。这个 WaitingItem 类是主要的调查目标。 WaitingItem 类中有 enter() 和 leave() 两个方法分别表示该条目进入队列和离开队列。看起来那些等待时间过长的任务在进入了队列之后经过很长一段时间它们的 leave() 方法才被调用。 继续追踪这两个方法的调用会发现 enter() 方法在 scheduleInternal() 中被调用用来调度新的任务。而 leave() 方法则在 maintain() 中被调用用来在合适的时机从队列中移除任务并执行。所以看起来问题是出在 maintain() 方法中。 maintain() 方法负责维护队列并把任务在不同的状态中移动。当有可能影响到任务调度的情况发生时Jenkins 会在内部调用这个方法。在 scheduleInternal() 方法中把新的任务添加到队列之后它会调用 scheduleMaintenance() 方法提交一个 Runnable 任务来调用 maintain() 方法。 经过上面的分析任务等待时间过长的原因可能是maintain() 方法被调用时并没有发现处于等待的任务。所以这个任务需要等到下一次 maintain() 方法调用时才会被执行。这中间可能有与时序相关的问题。 问题找到了之后下一步考虑的是怎么解决问题。如果要从根本上解决问题应该从 Jenkins 入手尝试在 Jenkins 中找到问题发生的根源并进行修复。这种方案要求对 Jenkins 的代码库有足够程度的了解在本地构建开发环境并尝试稳定地复现问题。找到问题并解决之后还需要添加 Pull Request 到 Jenkins 的代码库并等待新版本的发布。整个过程耗时漫长。作为 Jenkins 的使用者我自己并没有太大的意愿去花费精力在 Jenkins 自身的问题上。这种与时序有关的问题很难复现和调试。我需要的是一个能够有效解决问题的 workaround。 前面提到了产生问题的原因是 maintain() 方法在执行时可能没有发现等待中的任务。那么解决的办法可以很直接那就是每次提交任务之后等待几秒钟再调用一次 maintain() 方法。这样就可以确保 maintain() 方法能够发现等待中的任务。maintain() 方法是由 scheduleMaintenance() 方法调用的而 scheduleMaintenance() 是 Queue 类的一个公开方法。我只需要能够调用这个 scheduleMaintenance() 方法就可以了。 Jenkins 有一个叫做脚本控制台的功能可以在运行的 Jenkins 实例上执行 Groovy 脚本。Groovy 脚本可以直接对运行的 Jenkins 实例进行修改。 每个 Jenkins 运行的实例中Queue 对象只有一个。只需要找到这个 Queue 对象并调用其中的 scheduleMaintenance() 方法问题就解决了。脚本控制台已经内置提供了很多 Jenkins 的对象。实际上需要执行的代码很简单。 Jenkins.instance.queue.scheduleMaintenance() 通过在脚本控制台进行测试发现只要执行了上述的脚本原本在队列中的任务马上就可以被调度执行。这也证明了问题确实解决了。 最后一个问题是如何把对 scheduleMaintenance() 的调用自动化也就是在每次通过 API 触发了任务等待几秒钟之后自动调用 scheduleMaintenance() 方法。Jenkins 的脚本控制台并没有提供公开的 API。我采取的做法是用 HTTP 客户端模拟脚本控制台界面上的操作。脚本控制台的界面在运行脚本时实际上执行了一个表单提交动作。这个表单被提交到了网址 jenkins_url/computer/(built-in)/script内容类型是 application/x-www-form-urlencoded。请求中只需要包含一个参数 script表示需要执行的脚本内容。Jenkins 使用的是 BASIC 认证需要把访问的用户名和密码包含在请求中。使用 HTTP 客户端模拟上述请求并不难。 下面给出了使用 Spring RestTemplate 执行 Groovy 脚本的代码示例。 var headers new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
var authentication Basic Base64.getEncoder().encodeToString(String.format(%s:%s, username, password).getBytes(StandardCharsets.UTF_8));
headers.add(Authorization, authentication);
MultiValueMapString, String map new LinkedMultiValueMap();
map.add(script, Jenkins.instance.queue.scheduleMaintenance());
var entity new HttpEntity(map, headers);
try {restTemplate.exchange(String.format(%s/computer/(built-in)/script, jenkinsUrl),HttpMethod.POST, entity,String.class);
} catch (Exception e) {// 处理异常
} 至此Jenkins 任务排队时间过长的问题得到了解决。虽然并没有从根本上解决问题但已经是一个在有限的时间内可以完成的不错的解法。