开个脑洞,带你写一个自己的极狐GitLab CI Runner

news2024/11/16 1:19:11

极狐GitLab Runner 是极狐GitLab CI/CD 执行的利器,能够帮助完成 CI/CD Pipeline Job 的执行。

目前极狐GitLab Runner 是一个开源项目,以 Golang 编写。

极狐Gitlab 有个不错的特性,就是你可以使用自己的极狐Gitlab CI Runner。可是,如果你没有自己的CI Runner该怎么办呢?别担心,我们可以自己写一个。`[]~( ̄▽ ̄)~*`

在这篇文章里,我们会:

  • 阐述极狐GitLab Runner 的核心任务;

  • 分析 Runner 工作时和极狐GitLab 的交互内容;

  • 设计和实施一个我们自己的 Runner;

  • 让我们的 Runner 运行自己的 CI 工作;

  • 埋一个彩蛋!

当然,如果你习惯直接看代码,欢迎访问极狐GitLab仓库。如果喜欢,欢迎留个star。

Here we go!

明确核心任务


打蛇打七寸,极狐GitLab Runner最核心的任务是这些:

  1. 从极狐GitLab拉取工作;

  2. 获取工作后,准备一个独立隔离可重复的环境;

  3. 在环境中运行工作,上传运行日志;

  4. 在工作完成/异常退出后上报执行结果(成功/失败)。我们DIY的Runner同样要完成这些任务。

接下来我们按顺序捋一捋各个核心任务,同时观察Runner是怎么和极狐GitLab交互的。为了行文简明,下文的API请求和返回的内容有所精简。

注册

如果你用过自托管的极狐GitLab Runner,你应该熟悉这个页面:

用户在这个页面获取注册token,然后通过gitlab-runner register命令把Runner实例注册到极狐GitLab。这个注册过程本质上是在调用接口POST /api/v4/runners,其body形如:

{
   "description": "一段用户自己提供的描述",
   "info": {
       "architecture": "amd64", # runner的架构
       "features": { # runner具备的特性,极狐GitLab可能会拒绝不具备某些特性的runner注册
         "trace_checksum": true, # 是否支持计算上传日志的checksum
         "trace_reset": true,
         "trace_size": true
       },
       "name": "gitlab-runner",
       "platform": "linux",
       "revision": "f98d0f26",
       "version": "15.2.0~beta.60.gf98d0f26"
   },
   "locked": true,
   "maintenance_note": "用户提供的维护备注",
   "paused": false,
   "run_untagged": true,
   "token": "my-registration-token" #极狐GitLab提供的注册token
}

如果注册token无效,极狐GitLab会返回403 Forbidden。在成功注册时会返回:

{
   "id": 2, # Runner在极狐GitLab这边的全局编号
   "token": "bKzi84WitiHSN4N4TYU6", # runner的鉴权token
   "token_expires_at": null # 据我观察,这个字段对应的功能没有做
}

Runner只关心其中的token,它代表了runner的身份,同时作为共享密钥参与后面的API调用的鉴权。这个token会连同其他设置被保存到文件~/.gitlab-runner/config.toml中。

拉取工作

Runner在设定中有个最大并行工作数,在目前执行的工作数目小于设定值时,它会轮询POST /api/v4/jobs/request以获取工作,传入的body很像注册时的body,形如:

{
   "info": {
       "architecture": "amd64", # runner的架构
       "executor": "docker", # runner使用的执行器
       "features": { # runner具备的特性,例如,如果一个runner不支持上传产物,那么需要上传产物的工作就不会调度到它身上。
           "artifacts": true,
           "artifacts_exclude": true,
           "cache": true,
           "cancelable": true,
           "image": true
       },
       "name": "gitlab-runner",
       "platform": "linux",
       "revision": "f98d0f26",
       "shell": "bash",
       "version": "15.2.0~beta.60.gf98d0f26"
   },
   "last_update": "d8a43f53bb125ec6599d778b9969a601", # 游标
   "token": "bKzi84WitiHSN4N4TYU6" # 前面注册时拿到的token
}

如果没有要执行的工作,极狐GitLab会返回状态码204 No Content,Header中会有游标,形如X-Gitlab-Last-Update: 2794e577289a38db0df0e93e3215f597,供下次请求传入。

游标其实是个随机字符串,请求进入极狐GitLab的前置代理(名为Workhorse)时,代理会检查Runner提交的游标是否和Redis中的游标一致,如果一致就让Runner等着(long poll),不一致就把请求原样代理到极狐GitLab后端。Redis中的游标的更新由后端维护,在变更时会通过Redis Pub/Sub通知到Workhorse. 工作的选取在后端实现为一个复杂的SQL查询。

在有新工作需要执行时,极狐GitLab会返回201 Created,其body形如:

{
   "allow_git_fetch": true,
   "artifacts": null, # 要上传的产物
   "cache": [], # 要使用的缓存
   "credentials": [
       {
           "password": "jTruJD4xwEtAZo1hwtAp", # 用来拉取代码、上传日志、上报执行结果的通用密钥
           "type": "registry",
           "url": "gitlab.example.com",
           "username": "gitlab-ci-token" # 用户名是固定的
       }
   ],
   "dependencies": [],
   "features": {
       "failure_reasons": [ # 服务端可接受的工作错误原因
           "unknown_failure",
           "script_failure"
       ]
   },
   "git_info": {
       "before_sha": "6b55b6ffd17b57a2ec0cf8e7d7c66ff709343528",
       "depth": 20, # 克隆深度
       "ref": "master", # 目标分支/tag
       "ref_type": "branch",
       "refspecs": [
           "+refs/pipelines/52:refs/pipelines/52",
           "+refs/heads/master:refs/remotes/origin/master"
       ],
       "repo_url": "http://gitlab-ci-token:jTruJD4xwEtAZo1hwtAp@gitlab.example.com/flightjs/Flight.git",
       "sha": "cb4717728e8f885558a4e0bb28c58288b8bf4746" # commit hash
   },
   "id": 823, # 工作id,是后面很多API调用的重要参数
   "image": null,
   "job_info": {
       "id": 823,
       "name": "build-job",
       "project_id": 6,
       "project_name": "Flight",
       "stage": "build"
   },
   "services": [],
   "steps": [ # 要执行的脚本
       {
           "allow_failure": false,
           "name": "script",
           "script": [ # 脚本内容,每项对应 .gitlab-ci.yml 中的一个数组元素
               "echo \"sleeping 1\"",
               "sleep 5",
               "echo \"sleeping 2\"",
               "sleep 5"
           ],
           "timeout": 3600, # 脚本最大执行超时
           "when": "on_success"
       }
   ],
   "token": "jTruJD4xwEtAZo1hwtAp", # job凭据,用来鉴权后面的API调用
   "variables": [ # job执行时的环境变量,用户自己定义的环境变量也会放在这里
       {
           "key": "CI_JOB_ID",
           "masked": false,
           "public": true,
           "value": "823"
       },
       {
           "key": "CI_JOB_URL",
           "masked": false,
           "public": true,
           "value": "http://gitlab.example.com/flightjs/Flight/-/jobs/823"
       },
       {
           "key": "CI_JOB_TOKEN",
           "masked": true,
           "public": false,
           "value": "jTruJD4xwEtAZo1hwtAp"
       }
   ]
}

准备环境和克隆仓库

为了让CI的执行稳定、可重复,Runner执行的环境需要一定程度的隔离,执行环境的准备、脚本的执行由Executor负责,聊几个常见的:

Shell

  • 好处:调试容易,易于理解。

  • 坏处:隔离级别很低,只提供基于文件目录的隔离,项目依赖、可用端口会在CI job之间相互影响。

Docker或k8s

  • 好处:除了操作系统内核,其他资源都隔离了;镜像生态丰富,CI job可重复性高。

  • 坏处:不适用于不可信的工作负载。

VirtualBox或Docker Machine

  • 好处:操作系统级别的隔离,安全性高。

  • 坏处:挺重的,拖累CI执行效率。

所有的Executor都提供必须的API供极狐GitLab Runner调用:

  • 准备环境;

  • 执行Runner提供的脚本,获取执行时的输出,返回执行结果(这个API会被调用多次);

  • 清理环境。

克隆仓库其实就是在环境中执行一个git clone,所需参数在上一步“拉取工作”中获得:

git clone -b [分支/tag名] --single-branch --depth [克隆深度] https://gitlab-ci-token:job-token@gitlab.example.com/user/repo.git [克隆目的地文件夹名称]

执行工作和上传日志

所有要执行的工作都会被Runner编排成几个脚本文本,发给Executor执行,编排时会考虑Executor里的脚本执行环境是哪一个(bash/Powershell)。环境变量会放在编排的脚本最前面,例如对于bash环境,环境变量在脚本中使用export声明。

说个有趣的,你在CI log里看到的,标识为绿色的接下来要执行的语句是Runner在编排脚本时用echo命令+终端颜色控制符输出的,类似这样:

echo -e $'\x1b[32;1m$ date\x1b[0;m' # 打印出绿色的 $ date
date # 真正执行 date 命令

执行器的标准输出和标准错误会被Runner捕获,存放在/tmp临时文件中。job执行结束前,Runner会周期性地调用接口PATCH /api/v4/jobs/{job_id}/trace增量上传日志,请求的header形如:

Host: gitlab.example.com
User-Agent: gitlab-runner 15.2.0~beta.60.gf98d0f26 (main; go1.18.3; linux/amd64)
Content-Length: 314 # 这个增量的长度
Content-Range: 0-313 # 这个增量在全部日志中的位置
Content-Type: text/plain
Job-Token: jTruJD4xwEtAZo1hwtAp
Accept-Encoding: gzip

body里就是这批增量上传的日志,本例形如:

\x1b[0KRunning with gitlab-runner 15.2.0~beta.60.gf98d0f26 (f98d0f26)\x1b[0;m
\x1b[0K  on rockgiant-1 bKzi84Wi\x1b[0;m
section_start:1663398416:prepare_executor
\x1b[0K\x1b[0K\x1b[36;1mPreparing the "docker" executor\x1b[0;m\x1b[0;m
\x1b[0KUsing Docker executor with image ubuntu:bionic ...\x1b[0;m
\x1b[0KPulling docker image ubuntu:bionic ...\x1b[0;m

下一次上传日志时,新请求的Content-RangeContent-Length的内容同样会对应请求body的信息。

极狐GitLab在成功接受请求后会返回202 Accepted,返回的header中有一些有意思的值:

Job-Status: running # job运行状态
Range: 0-1899 # 当前收到的字节范围,每次都是0-n这个形式
X-Gitlab-Trace-Update-Interval: 60 # runner最低上报间隔,单位秒

这里有一个有意思的优化,当CI log页面有用户正在观看时,X-Gitlab-Trace-Update-Interval的值会是3,即Runner应该3秒就增量上报一次日志,这样用户才能更实时地看到最新进展。

上报执行结果

在用户定义的脚本执行成功或失败后,Runner会做两件事:

  • 如果还有没上传的日志,按前述方法将剩余日志全部上传;

  • 调用PUT /api/v4/jobs/{job_id}更新job的状态。

一个成功的job对应的HTTP body形如:

{
   "checksum": "crc32:4a182676", # 所有日志的CRC32校验,用来让服务端确定所有日志都已经成功上传
   "info": { ... }, # 这个字段已经在前面见过很多次了,内容从略
   "output": {
       "bytesize": 1899, # 日志的总字节数
       "checksum": "crc32:4a182676" # 同checksum
   },
   "state": "success", # job执行结果
   "token": "jTruJD4xwEtAZo1hwtAp" # job凭据
}

一个失败的job对应的HTTP body形如:

{
   "checksum": "crc32:f67200bc",
   "exit_code": 42, # 用户脚本的退出码
   "failure_reason": "script_failure", # 错误原因,从一个与服务端约定的列表里选
   "info": { ... }, # 这个字段已经在前面见过很多次了,内容从略
   "output": {
       "bytesize": 1723,
       "checksum": "crc32:f67200bc"
   },
   "state": "failed", # job执行结果
   "token": "Lx1oBNfw2e9xhZvNKsdX"
}

极狐GitLab后端在成功接受状态更新请求后会返回200 OK,Runner的工作就结束了。

有时,服务端没准备好接受状态更新(日志的处理是异步的,还没落盘),此时会返回202 Accepted,header里的X-GitLab-Trace-Update-Interval会告知Runner在下次尝试之前的等待时间(类似指数退避),Runner会一直重发请求,直到服务端返回200 OK或者超过最大重试次数。

整体来看,上述流程是这样子的:

构建专属Runner


OK,我们已经把极狐GitLab Runner的核心任务捋了一遍了,现在该打开IDE,写我们自己的Runner啦!

取个名字

我喜欢吃蛋挞,我们就叫我们的DIY Runner “蛋挞” 吧,英文名Tart.

画个Logo,这样看上去比较像一个正经项目:

再打开编程祖师娘Ada Lovelace的画像拜一拜接受祝福,万事俱备,开工大吉!

规划功能

和极狐GitLab Runner一样,蛋挞也是个命令行程序,主要功能有:

  • 注册(register):注册极狐GitLab runner token,输出配置文件到标准输出,这样我们可以再把它重定向到文件里,还能避(丢)免(给)处(用)理(户)“什么时候该覆盖配置文件”这样的玄学问题。

  • 尝试获取和运行一个工作(single):监听工作,运行工作,提交结果,退出。这个命令主要是为了调试方便。

  • 运行多个工作(run):监听工作,运行工作,提交结果,重复。

用上spf13/cobra,我们可以很快把命令行本体捏出来:

$ tart
An educational purpose, unofficial Gitlab Runner.
 
Usage:
 tart [command]
 
Available Commands:
 completion  Generate the autocompletion script for the specified shell
 help        Help about any command
 register    Register self to Gitlab and print TOML config into stdout
 run         Listen and run CI jobs
 single      Listen, wait and run a single CI job, then exit
 version     Print version and exit
 
Flags:
     --config string   Path to the config file (default "tart.toml")
 -h, --help            help for tart
 
Use "tart [command] --help" for more information about a command.

构建隔离的执行环境

构建隔离执行环境可能是Runner的一个最重要的任务了,理想的执行环境应该有这些特征:

  • 资源隔离,文件系统、端口、进程空间独享,包括:

    • 不被上一个job影响;

    • 不被同时运行的其他job影响;

    • 不被宿主机的其他进程影响。

  • 可重复性:同一个commit对应的job每次执行结果应该是一致的。

  • 宿主安全:job的执行不会影响到宿主机或其他job。

  • 缓存友好:用空间换时间。

分析现有的极狐GitLab Runner的Executor各自满足了上述哪些特征就作为留给读者的练习了。

既然蛋挞是我们自己的Runner,我们有充分的自由,让我们选择Firecracker来构建执行环境吧。

Firecracker是亚马逊云服务(AWS)开发和开源的虚拟机管理器,特点是轻量,它依靠KVM实现,通过模拟尽可能少的硬件以及跳过BIOS启动,可以在不到一秒内启动一台具有终端输入输出的虚拟机,并且每台虚拟机的额外内存开销不大于5MB,AWS使用Firecracker来构建自己的函数计算服务Lambda和无服务器托管服务Fargate。

启动一台能供CI使用的MicroVM(Firecracker对虚拟机的称呼)需要三个依赖:

  • Linux内核镜像;

  • 联通外部网络的TAP设备(一个虚拟的layer-2网络设备);

  • 根文件系统(rootFS,以文件的形式存在,可以类比docker image来理解,里面有操作系统的根/及其下属内容)。

你可以查看蛋挞对它们的具体实现,其中,根文件系统值得说道一下。

还记得我们梳理的极狐GitLab Runner Executor的必备API吗?虽然蛋挞并不直接仿写极狐GitLab Runner的Executor,但是这三个操作仍然是必要的:

  • 准备环境:按样本复制一份根文件系统交给Firecracker,启动虚拟机。

  • 执行Runner提供的脚本,获取执行时的输出,返回执行结果(这个API会被调用多次):我们稍后讨论。

  • 清理环境:关闭虚拟机,删掉根文件系统。

让每个虚拟机都在根文件系统的副本上操作可以提供资源隔离和可重复性。

Firecracker提供的终端只有一个输入和输出,操作自由度不够,这意味着我们在虚拟机里需要一个agent,脚本交给它去执行,输出和退出码由它转交给蛋挞。思来想去,我们最常用的agent恐怕是ssh了:

  • 在根文件系统里安装好sshd和登录公钥;

  • 每次虚拟机启动后,蛋挞使用ssh去连接虚拟机;

  • 蛋挞经过ssh执行命令,获取执行时的输出和执行结果。

sshd会调用虚拟机本地的bash运行蛋挞提供的脚本,这正是我们想要的。

脚本的生成和执行

这步不难,极狐GitLab提供的用户脚本是一个字符串数组,环境变量是一个对象数组:

  • 脚本开头写一个set -euo pipefail,这样执行会在遇到错误的时候停下来;

  • git clonecd到仓库目录;

  • export环境变量,每个一行,其中环境变量的值需要escape;

  • 写一个set +x,这样bash就会把接下来要执行的每个命令写到标准输出了;

  • 写入用户脚本,每个一行;

  • 每行末尾记得写断行符\n.

脚本交给sshd后就可以执行了,标准输出和标准错误会被蛋挞实时收集写到本地临时文件中,另有一个进程会把它周期性地增量上传到极狐GitLab。

脚本执行结束后,sshd会返回退出码,蛋挞会视情况上报job成功或失败。

运行自己的CI

既然蛋挞是用来运行CI任务的,我们就找点任务来让它运行,比如……它自己的CI?

让我们为蛋挞写一个.gitlab-ci.yml:

variables:
 # speed up go dependency downloading
 GOPROXY: "https://goproxy.cn,direct"
 
# we have go and build-essential pre-installed
our-exceiting-job:
 script:
   - echo "run test"
   - go test ./...
   - echo "build tart"
   - make
   - echo "run tart"
   - cd bin
   - ./tart
   - ./tart version

把蛋挞注册为仓库的CI Runner后,禁用shared runner(确保任务调度到蛋挞上),触发一次CI执行,看上去效果还不错!

埋一个彩蛋

对了,我还埋了一个小彩蛋与大家分享,如果你在星期四使用蛋挞运行 CI job,将会有一个神秘惊喜!点击👉即可访问蛋挞代码仓库

一点历史


2014年~2015年,GitLab Runner有很多活跃的第三方实现,其中Kamil Trzciński基于Go的GitLab CI Multi-purpose Runner实现被GitLab相中,替代了GitLab自己基于Ruby的实现,成为了我们今天看到的极狐GitLab Runner. 那时Kamil Trzciński还在Polidea工作,因此极狐GitLab CI Multi-purpose Runner是一个社区贡献。开源真是奇妙。

参考资料




  • Gitlab Runner

  • Gitlab Rails

  • mitmproxy

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/126187.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Oracle 单实例如何开机自启动

作者 | JiekeXu来源 |公众号 JiekeXu DBA之路(ID: JiekeXu_IT)如需转载请联系授权 | (个人微信 ID:JiekeXu_DBA)大家好,我是 JiekeXu,很高兴又和大家见面了,今天和大家一起来看看 Oracle 单实例如何开机自启动,欢迎点击…

盘点JAVA程序猿必备的webserver

作为java工程师,除了必备的java编程能力,我们还需要些什么呢? 一般而言,要从工程师进化为构架师,一个合格的java工作者需要掌握一些关于构架的知识, 比如互联网的结构,服务器的建设&#xff0c…

PhotoShop入门

PhotoShop入门 零、文章目录 文章地址 个人博客-CSDN地址:https://blog.csdn.net/liyou123456789个人博客-GiteePages:https://bluecusliyou.gitee.io/techlearn 代码仓库地址 Gitee:https://gitee.com/bluecusliyou/TechLearnGithub&am…

JAVA结构、循环语句

一、 if选择结构 代码示例: int num 1;if (num 1) {System.out.println("壹");} else if (num 2) {System.out.println("贰");} else if (num 3) {System.out.println("参");}输出: 壹 二、switch结构 1.switch 会根…

HNU编译原理实验四cminus_compiler-2022-fall

前言:原本想认认真真把这个实验给完成的,但是当时时间太赶了,一周要做三个实验,所以这次实验基本都是抄的了,有些地方也抄的不明不白,不过懂不懂这个对课程学习的帮助并不是很大,毕竟这个实验的…

vue 弹窗 惯性滚动 加速滚动

惯性滚动组件 新建文件 components/scroll-viwe <template><div v-if"visiable"><div class"mapbox-result-scroll-hidden"><div class"mapbox-result-wrap" ref"resultWrap"><div class"mapbox-resu…

服务了可口可乐、海底捞、某头部商业银行,我有这些体会

我非常喜欢巴西队的内马尔&#xff0c;他曾说&#xff1a;“你可能会看到我一秒钟、一分钟、一天不开心&#xff0c;但第二天你会看到我的笑脸。” 在 Authing 工作两年多了&#xff0c;在这期间&#xff0c;我为可口可乐、海底捞、某头部商业银行等客户做了交付&#xff0c;在…

jq实现倒计时功能

效果如下&#xff1a; 代码如下&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>jq实现…

10 IO实例

IO 1 流 流可以认为是一条通道&#xff0c;它可以将数据从源端传送到目的地。 例如将程序中的某些数据写入文件&#xff0c;或将文件中的某些数据读入程序。 Java中数据的操作是以“流”的方式进行。 Java中的“流”是一个具体的Java对象&#xff0c;该对象提供一些方法进行…

组件的概念

文章目录组件&#xff1f;从UI层面看组件化组件&#xff1f; 等下&#xff0c;你有没有留意到我说了一个很关键的词&#xff0c;叫组件。组件&#xff1f;直观的理解组件是一个什么东西&#xff1f;可拼接&#xff0c;可组合&#xff0c;搭积木&#xff0c;乐高积木? 对&…

Springboot定时任务调度的实现原理

前言 源码的世界是一片汪洋大海&#xff0c;springboot的源码更是如此&#xff0c;虽然用的时候似乎很简单&#xff0c;然而正是因为其内部的设计巧妙、复杂&#xff0c;才造就了其使用上的简单易上手。罗马不是一天建起来的&#xff0c;要完全理解它也并非一时的事&#xff0c…

webdriver的尝试:一 【webdriver自动打开浏览器与页面】

文章目录Webdriver尝试使用步骤1&#xff1a;安装类库2&#xff1a;安装驱动3&#xff1a;配置环境3&#xff1a;编写脚本4&#xff1a;执行脚本Webdriver 网站地址 Selenium webdriver 简单介绍&#xff1a;webdriver是一个api和协议。支持多种语言。主要功能&#xff0c;通…

大米新闻微信小程序和Springboot新闻管理系统项目源码

介绍 本项目分为大米news小程序端和springboot新闻管理系统后台项目。小程序主要用来新闻展示&#xff0c;后台管理系统用于提供相关新闻API。 项目源码 参考&#xff1a;https://www.bilibili.com/video/BV1TD4y1j7g3/?spm_id_from333.337.search-card.all.click&vd_s…

day08 常用API

1.API 1.1 API概述-帮助文档的使用 什么是API ​ API (Application Programming Interface) &#xff1a;应用程序编程接口 java中的API ​ 指的就是 JDK 中提供的各种功能的 Java类&#xff0c;这些类将底层的实现封装了起来&#xff0c;我们不需要关心这些类是如何实现的&a…

两个链表的第一个公共结点

今天为大家带来一道题目&#xff1a; 这个题目先来看看我自己写的错误版本 public class Solution {public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {ListNode tmp1pHead1;ListNode tmp2pHead2;while(pHead1!null&&pHead2!null){ListNode cur…

Axure8.0动态面板使用

Axure动态面板是最常使用的&#xff0c;今天我们就来详细介绍一下。 动态面板是Axure中一个非常强大的高级元件&#xff0c;用于实现多个状态的切换展示&#xff0c;可以将其看成一个容器&#xff0c;可以容纳多种不同状态&#xff0c;通过各种交互触发其状态发生变化。 通过以…

年终盘点丨2022边缘计算大事记

2022年进入尾声了&#xff0c;每年到了年底&#xff0c;边缘计算社区都会盘点过去一年边缘计算领域发生的值得您关注的事情。今年的边缘计算领域发生很多不一样的精彩&#xff1a;加强面向特定场景的边缘计算能力刷屏一整年&#xff0c;安波福43亿美元收购风河&#xff0c;全球…

C++图论 最短路问题总结

目录 最短路问题 图的存储 一、单源最短路 ① 朴素Dijkstra O(n^2) 练习题 代码 ② 堆优化Dijkstra O(mlogn) 练习题 代码 ③ Bellman_ford O(nm) 练习题 代码 ④ Spfa O(n) - O(nm) 练习题 ​代码 二、多源最短路 Floyd O(n^3) 练习题 代码 最短路问题 图…

C# 数据库访问方法

一 访问数据的两种基本方式 1 方式1&#xff1a;DataAdapter及DataSet ① 适合于“离线”处理&#xff1b; ② 自动建立Command对象&#xff1b; 方式2&#xff1a;DataReader ① 适合于只读数据&#xff0c;效率较高 它们都要使用Connection及Command 二 Connection对象…

Android解析服务器响应数据

文章目录Android解析服务器响应数据解析XML格式数据Pull解析方式SAX解析方式解析JSON数据使用JSONObject使用GSON的方式来解析JSON数据Android解析服务器响应数据 解析XML格式数据 通常情况下,每一个需要访问网络的应用程序都会有一个自己的服务器,我们可以向服务器提交自己的…