ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Git与SSH实战:大文件传输优化与Git LFS配置指南

Git与SSH实战:大文件传输优化与Git LFS配置指南 在日常开发和服务器运维中Git 和 SSH 几乎是绕不开的两个基础工具。Git 负责把代码和历史记录管得明明白白SSH 则负责从本地连到远程服务器、拉代码、执行命令、传文件。我日常最常做的事就是本地改代码、用 Git 提交、通过 SSH 登录服务器部署偶尔还要把几十 GB 的数据集或日志快速传到服务器上。这篇文章把我这些年积累下来的 Git 常用命令、SSH 密钥配置以及大文件传输时常用的优化方案和 Git LFS 用法一并整理出来给有同样需求的朋友做个参考。先说一个核心观点工具本身不难难的是把工作流理顺。代码用 Git 管远程访问靠 SSH大文件传输用 rsync over SSH仓库内的大文件交给 Git LFS——这套组合我用了很多年踩过的坑也都消化成了自己的操作习惯。下面逐个展开。1. 日常开发里绕不开的Git高频命令先讲 Git。不管你是前端、后端还是搞算法的Git 的日常使用其实就集中在那么几个操作上。我不打算把官方文档搬过来直接按场景拆解。1.1 从克隆仓库到第一次提交先装好 Git。Linux 上 Debian/Ubuntu 执行apt install gitCentOS 执行yum install gitmacOS 可以用brew install gitWindows 直接下载 Git for Windows 安装包一路下一步即可。装完用git --version验证。拿到一个项目最常用的肯定是克隆git clone url # 指定目录 git clone url my-project如果项目比较深、历史很久一次性拉全量历史会慢可以用浅克隆只取最近一次提交git clone --depth 1 url新项目的话先git init初始化然后写代码。写完之后的提交链路一般是git add . # 把变更加入暂存区 git commit -m 提交说明 # 把暂存区内容提交到本地仓库 git push origin branch # 推到远程分支很多人刚开始会把add和commit搞混简单理解add是挑货把想提交的文件先放到购物车commit是结算给这次变更打一个快照并写上说明。每次提交都应该是一个逻辑完整的变更而不是改了一半的中间态。提交信息也有讲究。别写update或者修改尽量说清楚做了什么。比如fix: 修复用户登录接口空指针异常这样无论是自己回看历史还是同事 review都省力很多。1.2 分支管理合并与会话中的冲突解决分支是 Git 比较核心的设计。常用操作git branch # 查看本地分支 git branch -r # 查看远程分支 git checkout -b feature/xxx # 创建并切换分支 git checkout main / git switch main # 切换已有分支 git branch -d feature/xxx # 删除本地分支 git push origin --delete feature/xxx # 删除远程分支合并分支最常用的是git merge。举个例子在 main 分支上执行git merge feature/login就是把 login 分支的提交合进来。合并完如果有冲突Git 会在冲突文件里用、、标出双方改动手工解决后执行git add再git commit即可。这里提醒一句多人协作的项目合并之前先把 main 更新到最新git pull origin main或者先git fetch再merge这样能少很多无谓的冲突。一个大特性合入 main 之前最好在分支上先git merge main把主干的更新吸收过来本地测试通过再提 Merge Request。这个习惯帮我少处理了很多冲突现场的烂摊子。至于rebase它是把当前分支的提交垫到目标分支的最新提交之后历史更线性。但它会改写提交记录刚上手的新手先别在公共分支上使用rebase否则很容易把队友的提交搞乱。1.3 撤销操作那些需要后悔药的时刻Git 的权限很大所以后悔药分好几档还没add想撤销工作区的修改git checkout -- file这个会把文件恢复到上一次提交或暂存区里的状态。已经add了想退出暂存区git restore --staged file。刚提交完想撤回这次提交但保留文件改动git reset --soft HEAD~1。提交完的文件改动也想全部还原git reset --hard HEAD~1。小心这个命令会丢弃工作区里所有对应的变更如果没有备份改动的代码就找不回来了。已经push到远程分支想取消优先用git revert commit。它会在历史里加一条反向提交而不是删掉原来的提交这样不会破坏被别人拉走的历史。我见过很多人在reset --hard上吃过亏所以个人建议在提交之后、推送之前想撤销用reset没问题只要提交已经推送出去了就用revert不要去改写远程历史除非团队就你一个人。另外还有一个特别好用的临时保存命令git stash。在代码改了一半、需要临时切到别的分支修 bug 时git stash把当前改动存起来切分支干完活再git stash pop恢复回来。带说明的话用git stash save 登录模块实现中查看列表用git stash list。1.4 查看状态、日志与差异这一块很多人容易忽略但我觉得它是用好 Git的分水岭。光会用add、commit、push不叫会 Git能随时读懂仓库状态才算。git status看当前状态哪个文件改了、哪个文件没跟踪一目了然。git log --oneline --graph --decorate看提交历史加上--graph能显示分支拓扑配合-10只看最近 10 条。git diff看工作区里还没暂存的具体改动内容。git diff --cached看已经暂存但还没提交的改动。排查问题的时候我会用git log -p file看某个文件的完整变更历史用git blame file看每一行是谁、在哪个提交里改的。定位线上 bug 的来源这两个命令能提供的线索价值非常高。顺带提一个几乎人人在用的命令git pull。它其实是git fetch加git merge的合体。如果你的本地分支和远程分支上游关系没配对git pull会给你一堆提示这时可以显式执行git pull origin main或者设置上游分支git branch --set-upstream-toorigin/main main一套 Git 日常命令就可以支撑大部分开发场景了。下面把 Git 和 SSH 串起来解决最常见的认证和连接问题。2. Git与SSH密钥配置、认证失败与网络排查Git 要连远程仓库最常用的认证方式有两种HTTPS 口令认证和 SSH 密钥认证。HTTPS 需要每次输账号密码或配置凭据管理器SSH 则是一次配置、长期免密。我推荐大家用 SSH尤其是频繁推拉代码的场景体验差距非常大。2.1 SSH密钥的生成与配置流程第一步在本地生成密钥对。大部分 Linux/macOS 都自带 OpenSSH 客户端Windows 10 以后也默认装了 OpenSSH。生成命令ssh-keygen -t rsa -b 4096 -C 你的邮箱或备注命令执行后会让你选择保存位置和 passphrase私钥口令。保存位置用默认的~/.ssh/id_rsa即可passphrase 可选如果设置了口令每次使用私钥都要输入除非用ssh-agent缓存。日常自己电脑上我一般留空公司电脑建议设置口令再加 agent多一层保险。生成的公钥在~/.ssh/id_rsa.pub查看cat ~/.ssh/id_rsa.pub把公钥内容加到 Git 平台的 SSH keys 设置里。以 GitHub 为例路径是 Settings - SSH and GPG keys - New SSH key。其他平台大同小异都是把ssh-rsa AAAA...那一整行粘进去。接下来让 Git 知道你用什么身份提交git config --global user.name 你的名字 git config --global user.email 你的邮箱然后测试连接ssh -T gitgithub.com如果你用的是 Gitee 或者 GitLab把域名换掉即可。首次连接时会出现 host key 询问输入yes回车之后会把指纹记录到~/.ssh/known_hosts。到这里SSH 密钥认证就算配好了。之后git clone gitgithub.com:user/repo.git就不会再问你密码。有人嫌每次敲ssh -T gitgithub.com试连接麻烦可以在~/.ssh/config里写一个别名Host github HostName github.com User git IdentityFile ~/.ssh/id_rsa ServerAliveInterval 60这样可以直接用ssh github测试git clone github:user/repo.git也能识别。ServerAliveInterval是保活参数后面传大文件时还会提到。2.2 Permission denied 的排查路径SSH 认证失败是最常见的问题之一一般报错是Permission denied (publickey)或者gitgithub.com: Permission denied (publickey).。我排查的思路都是按顺序走确认公钥是否真的加到了平台。核对一下公钥内容要用cat显示的内容不要手动截取很容易漏中间部分。确认本地用的是哪个私钥。如果~/.ssh/下有多个密钥文件Git 默认可能选错。用ssh -vT gitgithub.com查看输出里面会显示尝试了哪个 IdentityFile。必要时用-i ~/.ssh/yourkey指定。确认仓库地址是不是 SSH 形式。用git remote -v看 remote 地址如果显示的是https://那就算 SSH 配好了走的仍然是 HTTPS 认证。检查权限。服务端用户的.ssh目录权限、authorized_keys文件权限不对也会拒绝。本地私钥权限如果太开放比如 644SSH 会拒绝使用一般要求 600。如果是公司自建的 Git 服务防火墙或代理拦截也可能导致无法连接这就不只是密钥问题了。这里补充一个服务器端的权限要求登录用户的.ssh目录要执行chmod 700 ~/.sshauthorized_keys文件要chmod 600 ~/.ssh/authorized_keys。很多人配了公钥但忘了权限老是遇到 Permission denied就是卡在这一步。2.3 连接超时、断开后的服务保活还有一类问题是不报认证错而是直接卡住或超时。可能是网络原因、对方 SSH 服务端口不通或者是服务器的 sshd 服务没起来。以自建 Linux 服务器为例用 SSH 登录之后很多人会遇到这样一个场景在远程终端里启动了一个 node 服务关了 SSH 窗口服务也停了。这其实是因为进程是终端会话的子进程终端断开后收到 SIGHUP 信号就退出了。解决方式有几种用nohupnohup node app.js app.log 21 让进程忽略挂断信号。用tmux或screen先tmux new -s app在里面启动服务之后按Ctrlb再按d脱离会话服务继续跑。下次tmux attach -t app还能回去看日志。最规范的是用systemd写一个 service 单元让服务由系统托管开机自启、异常重启都省心。另外通过 SSH 传大文件传着传着突然断开那种挫败感我太懂了。下面重点讲rsync这个真正能救命的方案。3. SSH传输大文件三大痛点与两套优化方案先讲清楚这里说的通过 SSH 传输大文件场景是你已经能用 SSH 登录远程服务器希望把本地的一个大文件比如几个 GB 的数据集、日志包、虚拟机镜像传到服务器上或者从服务器下载下来。常见的工具是scp和sftp但大家都知道一旦文件上了 GB速度慢、断线重传这些痛点就全部暴露了。3.1 为什么传大文件会慢慢在哪先说scp。它基于 SSH 协议本质上是在加密隧道里复制文件。影响速度的因素主要有三个加密开销。SSH 默认使用的加密算法、MAC 算法有计算开销在 CPU 不太行的机器上会明显影响吞吐。单连接限制。scp 是单条 TCP 连接无法利用多线程并行传输而且 TCP 的窗口和拥塞控制会限制在长肥网络的吞吐表现。无断点续传。传一半断了scp只能从头再来。这是最让人崩溃的一点。所以我的第一建议是传输大文件别用 scp改用rsync over SSH。它在底层仍然走 SSH 认证和加密但多了增量同步、断点续传、多文件保留权限等能力可以说为传输场景而生。3.2 SSH配置参数调优在讲 rsync 之前先看看 SSH 这一层能优化的参数。这些配置写在客户端的~/.ssh/config里或者直接用命令行参数传。推荐的一组Host myserver HostName 192.168.1.100 User root Port 22 Compression yes Ciphers aes128-gcmopenssh.com ServerAliveInterval 30 ServerAliveCountMax 3 ControlMaster auto ControlPath ~/.ssh/controlmux/%r%h:%p ControlPersist 10m逐条解释Compression yes开启压缩。对文本类文件、代码、json 这类高冗余数据效果非常明显对已经压缩过的图片、视频、压缩包压缩反而浪费 CPU这时可以把这里关掉。Ciphers aes128-gcmopenssh.com把加密算法切到 AES-GCM。新版本 OpenSSH 默认已经比较高效但如果你想榨干性能可以显式指定。注意服务器端要支持该算法老版本系统不一定支持。ServerAliveInterval 30每 30 秒发一个心跳包防止长时间没有数据传输连接被路由器或防火墙掐断。ControlMaster和ControlPersist连接复用。第一次连接后后续同目标的 SSH 会话直接复用已有连接省去握手和密钥交换的开销。传大量小文件时尤其有用。举一个实际例子我在局域网里传一个 4GB 的文本类日志文件用 scp 默认参数大概要两三分钟加上Compression yes和 AES-GCM 之后能缩短到一分钟出头。如果是跨公网传输TCP 单连接本身的带宽瓶颈才是主要矛盾这部分优化只能减少 CPU 因素不能让物理带宽变大这个要有预期。还有一个小细节如果服务器把 SSH 端口改成了非 22 端口记得在配置里写Port否则连接永远超时或者报连接被拒。排查连接问题第一步就是确认端口通不通可以用telnet 主机IP 端口或者nc -vz 主机IP 端口快速看。3.3 rsync 断点续传大文件传输的正确姿势rsync 是我传大文件的首选。它和 scp 最大的区别是它会对比本地和远端文件只传输有差异的部分并且支持中断后继续。传 20GB 的文件断了网络重新执行同样的命令它会从断点继续而不是重头再来。最常用的命令rsync -avz --progress --partial /本地路径/文件 userserver:/远程路径/参数含义-a归档模式保留权限、时间戳、软链接等元数据。-v显示过程。-z传输时压缩对文本类文件有明显收益。--progress显示进度和速率。--partial关键参数。传输中断时保留已接收的部分文件不加这个中断后临时文件会被删除续传就无从谈起。这里给一个 scp 和 rsync 的直观对比对比项scprsync over SSH断点续传不支持支持增量同步不支持支持保留权限/时间戳部分保留完整可配置传输前压缩不支持支持-z限速无原生功能支持--bwlimit大量小文件性能一般更好如果传的是整个目录注意路径末尾的斜杠。rsync -avz /src/ userserver:/data/表示把 src 目录里面的内容同步到 data 目录rsync -avz /src userserver:/data/则是把 src 目录本身放到 data 目录下。斜杠差一个目录层级完全不同。还有一些参数按需添加--bwlimit5000限制带宽为 5000 KB/s避免传输占满带宽影响同网络里的其他服务。--delete让远端目录和本地完全一致删除本地已不存在但在远端存在的文件。这个参数很危险一般建议先用--dry-run预览。-n或--dry-run演练模式只显示将要传输的文件列表不实际传输。--exclude *.tmp排除特定文件。下载方向的 rsync 也很简单把源和目标反过来即可rsync -avz --progress userserver:/远程路径/文件 /本地路径/实际使用 rsync 有一个需要注意的点如果两端文件差异很小比如只改了一个字节rsync 依然需要扫描整个文件块来计算差异大文件首次扫描会有些耗时但相比传输整个文件已经省很多了。rsync 的算法会按固定块大小对文件做分块校验两端都要算一遍CPU 也有一定开销。不过对绝大多数运维场景收益远大于开销。几个真实案例备份数据库导出文件每天生成的 dump 文件大约 8GB。直接 scp 下载要 20 多分钟断了就得重来。改成 rsync 增量同步每天只传变更部分几分钟搞定断线了重跑一次能继续。多台服务器同步静态资源一台机器拉取构建产物再 rsync 到内网多台机器。配合ControlMaster连接复用整体时间从半小时降到几分钟。rsync 没装的话Debian/Ubuntu 用apt install rsyncCentOS 用yum install rsync。服务器端只需要 openssh-server 正常工作不需要额外跑 rsync daemon就能通过 SSH 隧道传输。4. Git LFS让大文件不拖垮仓库的专用工具如果说 rsync 解决的是文件传到服务器的问题那么 Git LFSLarge File Storage解决的就是大文件放进 Git 仓库的问题。这两者经常被放在一起讨论因为很多人上传几 GB 的模型文件、设计稿到 Git 仓库结果仓库体积膨胀clone 和 pull 慢到爆炸。Git LFS 就是干这个的。4.1 Git LFS 的工作原理普通 Git 仓库里每次提交会在.git对象库里存一份文件快照。一个大文件哪怕只改了一点点提交后也会在对象库里多一整个文件副本。历史一多仓库就成了胖子每次 clone 都要把这个胖子完整拉下来。Git LFS 的思路是把大文件本体从 Git 仓库里挪走放到专门的文件存储服务上Git 仓库里只保存一个几十到几百字节的指针文件。指针内容大概长这样version https://git-lfs.github.com/spec/v1 oid sha256:4d7a214b8b6ecf... size 123456789clone 或者 checkout 的时候Git LFS 根据指针文件里的 oid从 LFS 存储服务器把真实文件取回本地。普通文件由 Git 管大文件由 LFS 管互不干扰。这意味着远程仓库的 Git 数据量只和大文件的数量有关和大文件的实际大小无关。别人 clone 仓库时默认可以只拉指针不拉大文件需要时再触发拉取体验会好很多。4.2 安装配置与基础命令安装 Git LFS# Debian/Ubuntu apt install git-lfs # macOS brew install git-lfs装完之后在仓库里执行一次初始化git lfs install这一步会把 Git LFS 的 filter 写进全局配置~/.gitconfig让 Git 在 add/checkout 时自动调用 LFS 处理。然后指定哪些文件类型归 LFS 管git lfs track *.psd git lfs track *.zip git lfs track models/*.bin它会修改仓库里的.gitattributes文件把规则固化下来这个文件本身要提交到仓库。之后就是正常的 Git 流程git add . git commit -m add large files git push origin mainpush 时 LFS 文件会自动传到 LFS 存储。查看仓库里已经有哪些文件被 LFS 管理git lfs ls-files顺带提醒一个坑GitHub、Gitee 这类平台对 LFS 都有容量限制免费额度超了会直接拒绝上传。如果 push 报了一个batch response: Repository or file not found或者 403 错误多半是额度或权限问题。要么升级套餐要么换自建的 LFS 服务器Gitea 和 GitLab 都支持内置 LFS 存储。4.3 clone 卡住的排查与应对很多人反馈git lfs clone 卡住这个我很熟。Git LFS 拉取大文件时默认会并发下载如果文件特别大、网络不稳卡住几乎是必然的。Git LFS 2.x 之后官方建议直接用普通git clone不必再刻意用git lfs clone。但 clone 时的 LFS 拉取行为默认会把当前分支指向的所有 LFS 文件全部下载下来如果仓库里 LFS 文件总大小有几十 GB卡住就容易发生。排查思路先看是不是网络问题。git lfs env可以看当前 LFS 配置git lfs logs last看最近的传输日志有没有大量超时重连。控制并发数。命令行设置环境变量GIT_LFS_CONCURRENT_TRANSFERS1降低并发减少断连概率。开启断点续传。Git LFS 对单个文件的上传下载本身支持断点续传但如果进程被你 CtrlC 掉了下次会从头开始。尽量让它自然重试。clone 的时候先不拉 LFS。Git 有一个环境变量GIT_LFS_SKIP_SMUDGE1设置后 clone 只拉指针文件不拉真实大文件。需要时再单独执行git lfs pull这样至少仓库数据能立刻到位。组合操作大概是GIT_LFS_SKIP_SMUDGE1 git clone gitgithub.com:user/repo.git cd repo git lfs pullgit lfs pull也支持只拉指定子目录或指定文件git lfs pull -I models/*.bin。如果模型文件特别多、只想取其中一个模型这个参数很实用。还有个相关问题如果你的仓库里已经不小心提交了大文件想彻底清理历史常规做法是用git filter-repo或 BFG 重写历史然后把远程仓库强制推送。这会改历史只适合在团队能接受的情况下操作而且搞完之后所有人要重新 clone。对于新文件从一开始就用 LFS 跟踪才是省心的方案。5. 把 Git、SSH 和高效传文件串起来的日常流程工具单独讲完了最后串一下我日常工作里真实在用的流程组合。这些细节是官方文档里不会写的但实际用起来非常关键。5.1 一个典型的部署/同步流程假设本地项目里既有普通代码又有几个 GB 的模型文件。我的一般流程代码部分走 Git 常规流程git add、git commit、git push。模型/大文件如果项目里必需就用 Git LFS 跟踪提交推到支持 LFS 的远端仓库。部署到服务器时不一定每次从 Git 拉。如果是内网服务器直接用 rsync 把构建产物推过去rsync -avz --progress --partial ./dist userserver:/var/www/project/如果服务器需要通过 Git 更新代码我会在服务器上git pull大文件用git lfs pull按需拉取避免把所有模型都甩到服务器上。这里有一个经验不要把服务器当作 Git 仓库的消费者来 Pull 大文件。很多部署场景里服务器只需要最新一版的产物不需要完整历史。更合理的做法是在本地或 CI 机器上构建好再用 rsync 把产物同步过去。rsync 天然实现只传变化的部分比在服务器上git pull重拉一遍所有变更要高效得多。5.2 运维视角下的细节与教训最后说几个我踩过的坑希望能帮你少走弯路。权限问题永远是第一位的。rsync 走 SSH 时如果服务器端用户的~/.ssh/authorized_keys权限不对你会一直看到Permission denied。服务器端检查一下chmod 700 ~/.ssh、chmod 600 ~/.ssh/authorized_keys。本地私钥同理chmod 600 ~/.ssh/id_rsa。传输中断别急着重新 scp。先看看 rsync 有没有装能不能直接续传。很多人在断线后下意识重跑 scp白白浪费大量时间。用 rsync 只要记得加--partial重跑一次即可。大文件传完别忘了校验。文件几 GB 的传输过程中万一有静默损坏解压或读取时才暴露问题成本更高。传输完跑一下md5sum或sha256sum对比两端校验值。rsync 本身基于校验和能保证一致性但某些非文件复制场景比如在目标上再处理过的文件还是需要手动校验。关于 SSH 连接断开导致服务停掉的问题建议优先用 systemd 管理常驻服务。tmux适合调试场景nohup适合临时任务但 systemd 才能保证重启后自动拉起、崩溃后自动恢复。我在生产环境上基本不用前两种。如果你经常要在多台机器之间传文件记得在~/.ssh/config里把常用主机配置好别名配合 ControlMaster 复用连接整体体验会有一个质的提升。尤其是大量小文件的 rsync连接复用能把耗时砍掉一半以上。工具都是很成熟的东西关键是把自己的工作流理清楚代码用 Git 管小文件随便 scp大文件走 rsync仓库里的大文件交给 Git LFS常驻服务交给 systemd。这套组合我用了几年省下的时间不是一点半点。最后再分享一个我自己的小习惯每次传超大文件之前我都会先看一眼目标磁盘空间还剩多少别传着传着把磁盘写满了才发现那比传输中断还要难处理。希望这篇整理对你有用。
返回列表