[{"content":" 你好，我是蒋大培 👋 我是一名热衷于全栈开发的软件工程师。\n我喜欢探索技术细节，也关注宏观的系统架构。在这里，我记录我在软件开发路上的工程实践、技术复盘以及对新技术的尝试与思考。\n我不只是写代码，更致力于构建可靠、高效且优雅的软件解决方案。\n💻 代码人生 · 🏗️ 系统架构 · 📝 技术随笔 ","date":null,"permalink":"https://ygwa.github.io/","section":"蒋大培","summary":"\u003cdiv class=\"lead !mb-9 text-xl\"\u003e\n  你好，我是蒋大培 👋\n\u003c/div\u003e\n\n\u003cp\u003e我是一名热衷于\u003cstrong\u003e全栈开发\u003c/strong\u003e的软件工程师。\u003c/p\u003e\n\u003cp\u003e我喜欢探索技术细节，也关注宏观的系统架构。在这里，我记录我在软件开发路上的\u003cstrong\u003e工程实践\u003c/strong\u003e、\u003cstrong\u003e技术复盘\u003c/strong\u003e以及对\u003cstrong\u003e新技术\u003c/strong\u003e的尝试与思考。\u003c/p\u003e\n\u003cp\u003e我不只是写代码，更致力于构建\u003cstrong\u003e可靠\u003c/strong\u003e、\u003cstrong\u003e高效\u003c/strong\u003e且\u003cstrong\u003e优雅\u003c/strong\u003e的软件解决方案。\u003c/p\u003e\n\u003chr\u003e\n\u003cdiv style=\"margin-top: 2rem; opacity: 0.8;\"\u003e\n\u003cp style=\"font-size: 0.9rem; color: var(--color-contrast-medium);\"\u003e\n💻 代码人生 · 🏗️ 系统架构 · 📝 技术随笔\n\u003c/p\u003e\n\u003c/div\u003e","title":"蒋大培"},{"content":"","date":null,"permalink":"https://ygwa.github.io/tags/git/","section":"标签","summary":"","title":"Git"},{"content":"","date":null,"permalink":"https://ygwa.github.io/tags/rust/","section":"标签","summary":"","title":"Rust"},{"content":"在日常开发中，我经常需要在工作项目和个人项目之间切换。每次切换项目时，都需要手动修改 Git 的用户名和邮箱配置，不仅繁琐，还容易出错。更糟糕的是，一旦忘记切换，错误的身份信息就会被永久记录在 Git 历史中，后续清理起来也很麻烦。\n为了解决这个问题，我开发了 gid (Git Identity Manager)，一个用 Rust 编写的命令行工具，用于管理多个 Git 身份。本文将介绍这个工具的使用方法，以及在实际使用中遇到的问题和思考。\n背景 #在开发过程中，我们通常需要维护多个 Git 身份：\n工作身份：使用公司邮箱和对应的 SSH 密钥 个人身份：使用个人邮箱和不同的 SSH 密钥 开源项目：可能需要特定的 GPG 签名密钥 传统的管理方式要么是手动切换配置，要么是写一些脚本来自动化这个过程。但手动切换容易出错，而脚本的方式又不够灵活，特别是在需要根据项目路径或远程仓库 URL 自动匹配身份的场景下。\n解决方案 #gid 通过规则引擎和 Git Hooks 机制，实现了 Git 身份的自动化管理。它支持以下核心功能：\n身份管理 #首先需要添加不同的身份配置：\n# 添加工作身份 gid add --id work \\ --name \u0026#34;Your Name\u0026#34; \\ --email \u0026#34;you@company.com\u0026#34; \\ --ssh-key ~/.ssh/id_rsa_work # 添加个人身份 gid add --id personal \\ --name \u0026#34;Your Name\u0026#34; \\ --email \u0026#34;you@gmail.com\u0026#34; \\ --ssh-key ~/.ssh/id_rsa_personal 添加身份后，可以通过 gid switch 命令快速切换：\n# 切换到工作身份 gid switch work # 切换到个人身份 gid switch personal # 全局切换 gid switch -g work 规则引擎 #gid 支持基于路径和远程 URL 的规则匹配，可以根据项目位置自动切换身份：\n# 添加路径规则：所有 ~/work/ 下的项目自动使用工作身份 gid rule add -t path -p \u0026#34;~/work/**\u0026#34; -i work # 添加远程 URL 规则：特定组织的仓库使用工作身份 gid rule add -t remote -p \u0026#34;github.com/my-company/*\u0026#34; -i work # 自动应用规则 gid auto 规则引擎支持优先级设置，当多个规则匹配时，会选择优先级最高的规则。\nSSH 和 GPG 集成 #gid 不仅管理 Git 身份，还支持自动配置对应的 SSH 密钥和 GPG 签名：\ngid add --id work \\ --name \u0026#34;John Doe\u0026#34; \\ --email \u0026#34;john@company.com\u0026#34; \\ --ssh-key ~/.ssh/id_work \\ --gpg-key ABCD1234 \\ --gpg-sign true 这样在切换身份时，相关的 SSH 密钥和 GPG 配置也会自动更新。\nGit Hooks 保护 #为了避免提交时使用错误的身份，gid 提供了 Git Hooks 集成：\n# 为当前仓库安装钩子 gid hook install # 全局安装 gid hook install -g 安装后，在每次提交前会自动检查身份配置，如果发现配置不匹配，会提示用户。\n历史审计 #gid audit 可以扫描 Git 历史，找出身份配置不一致的提交：\n# 审计当前仓库 gid audit # 审计指定目录下的所有仓库 gid audit --path ~/projects 这个功能对于清理历史记录中的身份问题很有帮助。\n实际使用 #配置示例 #下面是一个典型的使用场景，将工作项目和个人项目分离：\n# 配置工作身份 gid add --id work \\ --name \u0026#34;Your Name\u0026#34; \\ --email \u0026#34;you@company.com\u0026#34; \\ --ssh-key ~/.ssh/id_rsa_work # 配置个人身份 gid add --id personal \\ --name \u0026#34;Your Name\u0026#34; \\ --email \u0026#34;you@gmail.com\u0026#34; \\ --ssh-key ~/.ssh/id_rsa_personal # 设置规则 gid rule add -t path -p \u0026#34;~/work/**\u0026#34; -i work gid rule add -t path -p \u0026#34;~/personal/**\u0026#34; -i personal # 安装全局钩子 gid hook install -g 配置完成后，当你进入 ~/work/project 目录时，运行 gid auto 就会自动切换到工作身份。\n配置管理 #所有配置存储在 TOML 格式的配置文件中，默认位置：\nLinux/macOS: ~/.config/gid/config.toml Windows: %APPDATA%\\gid\\config\\config.toml 配置文件格式如下：\n[[identities]] id = \u0026#34;work\u0026#34; name = \u0026#34;John Doe\u0026#34; email = \u0026#34;john@company.com\u0026#34; ssh_key = \u0026#34;~/.ssh/id_work\u0026#34; gpg_key = \u0026#34;ABCD1234\u0026#34; gpg_sign = true [[rules]] type = \u0026#34;path\u0026#34; pattern = \u0026#34;~/work/**\u0026#34; identity = \u0026#34;work\u0026#34; priority = 100 配置文件采用 TOML 格式，易于阅读和编辑，也方便备份和迁移。\n遇到的问题 #在实际使用过程中，我遇到了一些需要注意的地方：\n规则匹配的优先级 #当多个规则同时匹配时，gid 会根据优先级选择身份。但在某些场景下，规则的优先级设置可能不够直观。例如，如果同时设置了路径规则和远程 URL 规则，需要仔细考虑优先级设置，避免出现意外的身份切换。\nGit Hooks 的兼容性 #gid hook install 会在 Git 仓库中安装 pre-commit 钩子。如果项目中已经存在其他钩子脚本，可能会产生冲突。建议在使用前先检查现有的钩子配置。\n历史记录的清理 #虽然 gid audit 可以找出历史记录中的身份问题，但清理这些记录需要使用 git filter-repo 等工具重写历史。这个过程比较复杂，需要谨慎操作，特别是在多人协作的项目中。\n跨平台兼容性 #gid 使用 Rust 编写，理论上支持跨平台。但在 Windows 系统上，路径规则的处理可能需要特别注意，因为 Windows 的路径格式与 Unix 系统不同。\n我的思考 #通过开发和使用 gid，我对 Git 身份管理有了更深入的理解。这个工具确实解决了多身份切换的问题，但在实际使用中也有一些需要注意的地方：\n适用场景 #gid 特别适合以下场景：\n工作与个人项目分离：通过路径规则自动切换身份，避免手动操作 开源项目贡献：为不同的开源项目配置特定的身份和 GPG 签名 团队协作规范：统一团队成员的 Git 身份管理方式 局限性 #但 gid 也有一些局限性：\n学习成本：需要理解规则引擎的工作原理，对于简单的使用场景可能显得过于复杂 配置维护：随着项目增多，规则配置可能会变得复杂，需要定期维护 历史清理：虽然提供了审计功能，但清理历史记录仍然需要额外的工具和操作 改进方向 #基于实际使用经验，我认为 gid 可以在以下方面进行改进：\n规则可视化：提供更直观的规则配置界面，帮助用户理解规则匹配逻辑 历史清理集成：将历史清理功能集成到工具中，简化操作流程 配置验证：提供配置验证功能，在配置错误时给出更明确的提示 技术选型 #选择 Rust 作为开发语言，主要考虑了以下因素：\n性能：Rust 的零成本抽象和编译优化，让工具启动速度很快 内存安全：无需担心内存泄漏和段错误，提高了工具的稳定性 跨平台：原生支持 Linux、macOS 和 Windows，无需额外的运行时环境 单文件部署：编译后的二进制文件无需依赖，可以直接运行 不过，Rust 的学习曲线相对陡峭，对于不熟悉 Rust 的开发者来说，贡献代码的门槛可能会比较高。\n总结 #gid 通过规则引擎和 Git Hooks 机制，实现了 Git 身份的自动化管理。在实际使用中，它确实解决了多身份切换的问题，提高了开发效率。但工具本身也有一些局限性，需要根据实际场景选择合适的配置方式。\n如果你也经常在多个 Git 身份之间切换，或者需要为不同的项目配置不同的身份，可以尝试使用 gid。它可能会成为你开发工具箱中一个有用的工具。\n项目地址：https://github.com/ygwa/gid\n参考资料 # gid GitHub 仓库 Git 配置文档 Rust 官方文档 ","date":"2025-12-01","permalink":"https://ygwa.github.io/writings/gid-git-identity-manager/","section":"技术文章","summary":"\u003cp\u003e在日常开发中，我经常需要在工作项目和个人项目之间切换。每次切换项目时，都需要手动修改 Git 的用户名和邮箱配置，不仅繁琐，还容易出错。更糟糕的是，一旦忘记切换，错误的身份信息就会被永久记录在 Git 历史中，后续清理起来也很麻烦。\u003c/p\u003e\n\u003cp\u003e为了解决这个问题，我开发了 \u003ccode\u003egid\u003c/code\u003e (Git Identity Manager)，一个用 Rust 编写的命令行工具，用于管理多个 Git 身份。本文将介绍这个工具的使用方法，以及在实际使用中遇到的问题和思考。\u003c/p\u003e","title":"告别 Git 身份混乱，一个命令搞定多身份管理"},{"content":"","date":null,"permalink":"https://ygwa.github.io/tags/%E5%B7%A5%E5%85%B7%E4%BD%BF%E7%94%A8/","section":"标签","summary":"","title":"工具使用"},{"content":" 这里是我记录技术思考的地方。内容涵盖后端架构、前端开发、DevOps 实践以及我在日常编码中遇到的有趣挑战。与其说是教程，不如说是我的工程日志，希望能给你带来一些启发。 ","date":null,"permalink":"https://ygwa.github.io/writings/","section":"技术文章","summary":"\u003cdiv class=\"lead !mb-9 text-xl\"\u003e\n  这里是我记录技术思考的地方。内容涵盖后端架构、前端开发、DevOps 实践以及我在日常编码中遇到的有趣挑战。与其说是教程，不如说是我的工程日志，希望能给你带来一些启发。\n\u003c/div\u003e","title":"技术文章"},{"content":" 通过标签可以快速找到相关主题的文章。点击标签查看该标签下的所有文章。 ","date":null,"permalink":"https://ygwa.github.io/tags/","section":"标签","summary":"\u003cdiv class=\"lead !mb-9 text-xl\"\u003e\n  通过标签可以快速找到相关主题的文章。点击标签查看该标签下的所有文章。\n\u003c/div\u003e","title":"标签"},{"content":"","date":null,"permalink":"https://ygwa.github.io/tags/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E8%B7%B5/","section":"标签","summary":"","title":"项目实践"},{"content":"","date":null,"permalink":"https://ygwa.github.io/tags/mysql/","section":"标签","summary":"","title":"Mysql"},{"content":"","date":null,"permalink":"https://ygwa.github.io/tags/sql/","section":"标签","summary":"","title":"Sql"},{"content":"众所周知，预编译技术对于 SQL 查询具有显著的性能提升作用。具体而言，在执行 SQL 操作时，利用预编译技术可以省略编译步骤，仅需传递特定参数即可借助已准备好的执行计划快速完成查询，从而极大地提升了后端系统的查询效率。\n然而，预编译技术并非总是适用且无懈可击的。在实际应用中，可能会遇到一些问题和挑战。例如，在某个场景下，当系统需要处理超过1亿条记录的数据量时，即使采用了预编译技术，查询速度仍然会变得缓慢甚至导致超时现象的发生。尽管从接口层面进行了充分的优化，并建立了相应的索引以加速查询过程，但在实际运行环境中，由于数据规模的巨大差异，这些措施可能无法完全解决问题。\n因此，在面对大规模数据集时，我们需要更深入地分析和理解预编译技术的工作原理及其局限性，并结合具体的业务需求和技术特点来制定合适的解决方案。同时，我们也应该积极探索新的技术和方法，不断提升系统的整体性能和稳定性。\n在最近的项目中，我们在使用预编译的 SQL 进行数据查询时，经常会遇到稳定性超时的问题。虽然在客户端执行时并未出现超时情况，但这种现象与常规认知存在较大出入。因为我们通常认为预编译的 SQL 应比非预编译的 SQL 拥有更好的性能表现。此外，当我们尝试通过给定特定参数的方式在客户端进行查询时，基本能在 500 毫秒内获得响应结果。因此，我开始思考：为什么客户端和程序中的查询行为会出现如此大的差异？\n首先，我考虑是否是因为程序与 SQL 查询之间存在某些差异，如参数配置等。在过去的经验中，我们知道当查询数据命中所在页并发生插入操作时，可能会产生锁，进而导致查询性能变慢。然而，在本案例中，我们查询的是单条数据（通过 ID），并且已经对比过性能差异，并模拟验证了事务及隔离级别等因素的影响，均未取得明显成效。若我们将数据量减小、关联表数量减少，则能够成功查询到所需数据。因此，我们可以初步排除因页频繁写入而导致的性能问题。\n在网上寻找解决方案的过程中，我发现了一些有价值的建议。其中一条是关于 Size 配置的调整，另一条则是建议将井号参数（#）改为美元符号参数（$）。\n参数类型的区别 #在这里，我想详细解释一下这两种参数类型的区别：\n井号参数（#）：在 MySQL 中生成的 SQL 是一种预编译的 SQL，即 SQL 语句会被编译并缓存起来，每次执行时只需传入参数即可。这种方式可以有效缩短每次编译 SQL 的时间，从而提高性能。这是大多数数据库普遍采用的做法。\n美元符号参数（$）：更像是以 SQL 文本模式提交的 SQL 请求，每次提交都需要重新编译。虽然这种方法看似不符合常规认知，但在某些场景下可能更合适。\n解决方案 #我决定尝试将井号参数改为美元符号参数，并进行了修改和验证。令人惊喜的是，性能得到了显著改善，查询时间也稳定在 500 毫秒左右。此时，我才意识到这才是与我们在客户端执行 SQL 查询相一致的行为方式，并且返回的结果也非常相似。\n问题分析 #至此，我对问题的定位逐渐清晰——预编译的 SQL 为何反而比普通 SQL 查询慢？这个问题引起了我的深思。\n可能的原因包括：\n预编译 SQL 的缓存机制在某些场景下可能失效 执行计划的缓存可能不适合当前的查询模式 参数绑定的开销在某些情况下可能超过编译的开销 总结 #通过这次问题排查，我认识到：\n预编译技术并非在所有场景下都是最优解 需要根据实际场景选择合适的参数类型 性能优化需要结合实际测试数据，不能仅凭理论判断 ","date":"2024-12-13","permalink":"https://ygwa.github.io/writings/performance-in-prepared-sql/","section":"技术文章","summary":"\u003cp\u003e众所周知，预编译技术对于 SQL 查询具有显著的性能提升作用。具体而言，在执行 SQL 操作时，利用预编译技术可以省略编译步骤，仅需传递特定参数即可借助已准备好的执行计划快速完成查询，从而极大地提升了后端系统的查询效率。\u003c/p\u003e\n\u003cp\u003e然而，预编译技术并非总是适用且无懈可击的。在实际应用中，可能会遇到一些问题和挑战。例如，在某个场景下，当系统需要处理超过1亿条记录的数据量时，即使采用了预编译技术，查询速度仍然会变得缓慢甚至导致超时现象的发生。尽管从接口层面进行了充分的优化，并建立了相应的索引以加速查询过程，但在实际运行环境中，由于数据规模的巨大差异，这些措施可能无法完全解决问题。\u003c/p\u003e","title":"SQL预编译技术与性能优化"},{"content":"","date":null,"permalink":"https://ygwa.github.io/tags/%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96/","section":"标签","summary":"","title":"性能优化"},{"content":"","date":null,"permalink":"https://ygwa.github.io/tags/%E9%97%AE%E9%A2%98%E6%8E%92%E6%9F%A5/","section":"标签","summary":"","title":"问题排查"},{"content":"在日常的项目开发过程中，多人协作时难免会有开发同学因误操作，将敏感信息（如密钥、数据库密码等）提交到 Git 仓库中。如果还未推送到远端，通常可以使用 git reset --soft 重新提交。但一旦推送到远端仓库，仅删除或修改文件已无法彻底擦除这些敏感信息。因为Git作为版本控制工具，会在历史纪录中保留所有的内容，不仅影响项目的安全性，还有可能会对客户的产品和终端用户产生影响。\n本文将从一个实际案例出发，介绍如何使用Git工具git filter-repo来清理Git仓库中的敏感信息，并探讨其中的原理及其他适用场景。\n背景 #最近在项目中，就遇到了开发同学误将数据库的密码提交到了Git远程仓库中，且发现的时候在那个提交之后有了更多的提交纪录。导致敏感信息被纪录到历史的情况。由于代码库在组织内是公开的，这就导致项目的敏感信息会被暴露给了更多的人，从而可能产生一些安全问题。\n为了确保敏感数据不被更多的人获取到，我们需要有效的从Git的历史纪录中清除这些信息，而不是简单的删除掉文件，再重新提交。\n那么如何能在不丢失历史纪录的前提下，从Git仓库删除掉这些敏感信息呢？🙈\n解决方案 #经过调研，我们发现有两款工具可以用来解决我们上述说到的问题。git filter-branch和git filter-repo，Git官方的建议是filter-branch存在较多的安全缺陷1，且无法向后兼容的进行修复，因此已经不建议使用了。推荐使用git filter-repo，经过调研后发现其更加高效且功能强大，是清理敏感信息、移除文件、重构 Git 历史记录的首选工具。\n现场还原 #为了更好的说明这个问题，我们下面会初始化一个git仓库，并且还原提交了敏感信息到仓库的状态\n初始化项目目录\nmkdir git_repo_test; cd git_repo_test; git init .; echo \u0026#34;hello git filter-repo\u0026#34; \u0026gt;\u0026gt; hello.txt git add .; git commit -m\u0026#34;init repo\u0026#34;; 模拟提交敏感信息\necho \u0026#34;password=UEJn1k1@3k\u0026#34; \u0026gt;\u0026gt; .env; echo \u0026#34;update some business\u0026#34; \u0026gt;\u0026gt; hello.txt; git add .; git commit -m\u0026#34;add security message\u0026#34;; echo \u0026#34;repo has security issues\u0026#34; \u0026gt;\u0026gt; hello.txt; git add .; git commit -m\u0026#34;update something else\u0026#34;; 通过Git log查看历史纪录\ngit log --graph --pretty=format:\u0026#39;%C(cyan)%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(magenta)\u0026lt;%an\u0026gt;%Creset\u0026#39; --abbrev-commit --date=relative 进行修复 #Git filter-repo是采用Python开发的一个工具，因此在安装前需要确认下本机是否有Python3的环境。具体的安装方法可以参考Github的介绍。\n工具安装 ## 包管理软件安装 apt install git-filter-repo # for linux brew install git-filter-repo # for macos # 也可以使用python的pip工具进行安装 pip install git-filter-repo 移除敏感文件 #在上述模拟的历史仓库中，第二条提交中误将.env文件提交到了历史中，并且还包含了针对其他文件的修改，我们下面借助git filter-repo来进行清除所有包含.env这个文件的提交\ngit filter-repo --path .env --invert-paths --force 执行结果\nParsed 3 commits New history written in 0.35 seconds; now repacking/cleaning... Repacking your repo and cleaning out old unneeded objects HEAD is now at e8aeffb update something else Enumerating objects: 9, done. Counting objects: 100% (9/9), done. Delta compression using up to 12 threads Compressing objects: 100% (4/4), done. Writing objects: 100% (9/9), done. Total 9 (delta 0), reused 0 (delta 0), pack-reused 0 Completely finished after 0.40 seconds. 历史记录\n在清理后，我们再次查看Git历史纪录，可以发现符合我们的预期。已经将.env文件移除了，同时由于提交的内容发生变化了，第二个提交的commit ID也发生了变化，第三个提交的 commit ID 也发生了变化。这是因为Git的commit ID在计算的时候会参考提交内容、父提交的hash值，提交元信息等因素。git filter-repo工具在检查第二个提交的时候，发现存在敏感信息，因此重新修改了第二个提交的内容并重新生产了新的提交，因此导致后续所有的提交的commit ID都发生了变化，这是Git的原理所决定的。\n确认清理效果\n推送远端仓库\n清理后因为仓库的变化和远端已经匹配不上了，因此需要使用force来强制推送\ngit push origin master --force 原理探究 #其实在上面的操作中，我们基本上已经能够推测出git filter-repo的工作原理，具体来说有这么几个步骤\n使用过滤器找到目标记录: git filter-repo 内置了多种过滤器，可以方便使用者按照特定的规则来查找需要修改的 git 提交。支持使用 Python 代码来定制规则。 处理过滤后的提交记录: 对于需要处理的提交，会按照给定的需求来进行修改对应对象的内容，如作者信息，文件树信息、文件内容以及提交信息等，并基于修改的提交内容构建新的提交对象。 重写 Git 的提交历史记录: 由于 Git 的提交记录会包含父节点的信息，因此在修改了过滤出来的提交记录后，会将受影响的提交记录都进行改写，产生新的 commit ID。这也说明了为什么上面的例子中，第三个提交的 commit ID 也会发生变化。 适用场景 #删除历史中的特定文件或目录\ngit filter-repo --path node_modules --invert-paths 替换历史中的敏感信息\ngit filter-repo --replace-text \u0026lt;replacement-file\u0026gt; 修改提交者信息邮箱\ngit-filter-repo --email-callback \u0026#39;return email.replace(b\u0026#34;old-email.com\u0026#34;, b\u0026#34;new-email.com\u0026#34;)\u0026#39; 修改提交消息\ngit-filter-repo --message-callback \u0026#39;return message.replace(b\u0026#34;old-message\u0026#34;, b\u0026#34;new-message\u0026#34;)\u0026#39; 自定义规则\n上述的 email-callback 和 message-callback 中使用的 return 语句其实是 python 的代码，因此如果上述规则不匹配，我们还可以编写 python 代码来进行替换规则的编写。\n修改某个提交者的个人信息，下面这段自定义代码会将 git 历史记录中ygwang的邮箱地址后缀替换成 tuta.io。\ngit filter-repo --email-callback \u0026#39; import re new_domain = b\u0026#34;tuta.io\u0026#34; if email.startswith(b\u0026#34;ygwang\u0026#34;): email = re.sub(b\u0026#34;@.*$\u0026#34;, b\u0026#34;@\u0026#34; + new_domain, email) return email \u0026#39; 支持的 callback 都有\n--filename-callback \u0026lt;function_body\u0026gt; --message-callback \u0026lt;function_body\u0026gt; --name-callback \u0026lt;function_body\u0026gt; --email-callback \u0026lt;function_body\u0026gt; --refname-callback \u0026lt;function_body\u0026gt; --file-info-callback \u0026lt;function_body\u0026gt; --blob-callback \u0026lt;function_body\u0026gt; --commit-callback \u0026lt;function_body\u0026gt; --tag-callback \u0026lt;function_body\u0026gt; --reset-callback \u0026lt;function_body\u0026gt; 当然除了上述给定的常见场景外，git filter-repo还提供了非常强大的能力，可以参考官方的参考文档。\n我的思考 #通过 git filter-repo 我们可以非常灵活的对于 Git 的历史记录进行修改，其提供强大的功能给了我们很多操作的空间。当然这也不是没有代价，在对于其原理的分析中我们可以看到，修改历史提交对于仓库本身的影响还是比较大的，且会关联影响到很多的历史记录，在多人协作或者采用monorepo的时候，这种操作往往会产生交大的影响。因此对于历史的修改，不应该是一种常态，迫不得已时，我们更多时候需要尊重历史。如果我们希望避免修改的发生，可以从以下几个方面进行优化：\n明确项目中密钥管理的流程，如定期更换密钥信息等 做好 onboarding 培训，如何 setup 工程，分支规范的明确，提交信息的规范化 规范本地门禁，定义 pre-commit 检查，使用 gitleaks 这类工具进行敏感信息扫描 定义 CI 门禁，在 CI 中使用工具和步骤做敏感信息的检查扫描 使用环境变量或秘密管理工具来管理敏感信息 https://git-scm.com/docs/git-filter-branch#_warning\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"2024-12-10","permalink":"https://ygwa.github.io/writings/git-history-update-by-filter-repo/","section":"技术文章","summary":"\u003cp\u003e在日常的项目开发过程中，多人协作时难免会有开发同学因误操作，将敏感信息（如密钥、数据库密码等）提交到 Git 仓库中。如果还未推送到远端，通常可以使用 \u003ccode\u003egit reset --soft\u003c/code\u003e 重新提交。但一旦推送到远端仓库，仅删除或修改文件已无法彻底擦除这些敏感信息。因为Git作为版本控制工具，会在历史纪录中保留所有的内容，不仅影响项目的安全性，还有可能会对客户的产品和终端用户产生影响。\u003c/p\u003e\n\u003cp\u003e本文将从一个实际案例出发，介绍如何使用Git工具\u003ccode\u003egit filter-repo\u003c/code\u003e来清理Git仓库中的敏感信息，并探讨其中的原理及其他适用场景。\u003c/p\u003e","title":"Git历史信息修正"},{"content":"","date":null,"permalink":"https://ygwa.github.io/tags/docker/","section":"标签","summary":"","title":"Docker"},{"content":"","date":null,"permalink":"https://ygwa.github.io/tags/redis/","section":"标签","summary":"","title":"Redis"},{"content":"docker run -it --name=linx_1 ubuntu sleep infinity create-a-redis-cluster ","date":"2024-12-09","permalink":"https://ygwa.github.io/writings/setup-a-redis-cluster-use-docker/","section":"技术文章","summary":"\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-sh\" data-lang=\"sh\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker run -it --name\u003cspan class=\"o\"\u003e=\u003c/span\u003elinx_1 ubuntu sleep infinity\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003col\u003e\n\u003cli\u003e\u003ca href=\"https://redis.io/docs/latest/operate/oss_and_stack/management/scaling/#create-a-redis-cluster\" target=\"_blank\" rel=\"noreferrer\"\u003ecreate-a-redis-cluster\u003c/a\u003e\u003c/li\u003e\n\u003c/ol\u003e","title":"使用 Docker 来配置 Redis 集群"},{"content":"","date":null,"permalink":"https://ygwa.github.io/tags/%E8%BF%90%E7%BB%B4%E9%83%A8%E7%BD%B2/","section":"标签","summary":"","title":"运维部署"},{"content":"","date":null,"permalink":"https://ygwa.github.io/tags/elasticsearch/","section":"标签","summary":"","title":"Elasticsearch"},{"content":"ElasticSearch是一个高可用的搜索引擎，最近在项目中由于客户基础设施的成熟度不够，需要Ops来进行ElasticSearch的高可用部署，了解了其底层集群相关的知识，本文将对其进行总结，介绍ElasticSearch中集群的架构及数据的存储。\n测试环境的搭建 #为什么是三个? #数据的分片治理 #分片的健康检查 #Elasticsearch 提供了完善的健康检查机制，通过 _cluster/health API 可以监控集群和分片的状态。\n总结 #本文介绍了 Elasticsearch 集群架构的核心概念，包括节点角色、分片机制和健康检查。理解这些概念对于构建高可用的搜索服务至关重要。\n参考资料 # Elasticsearch Architecture Best Practices System Design Series: Elasticsearch - Architecting for Search ","date":"2024-12-03","permalink":"https://ygwa.github.io/writings/elasticsearch/","section":"技术文章","summary":"\u003cp\u003eElasticSearch是一个高可用的搜索引擎，最近在项目中由于客户基础设施的成熟度不够，需要Ops来进行ElasticSearch的高可用部署，了解了其底层集群相关的知识，本文将对其进行总结，介绍ElasticSearch中集群的架构及数据的存储。\u003c/p\u003e","title":"Elasticsearch 架构探索"},{"content":"","date":null,"permalink":"https://ygwa.github.io/tags/spring-boot/","section":"标签","summary":"","title":"Spring-Boot"},{"content":"在实际项目中，我们偶尔会遇到由于客户部署环境的网络限制问题，导致 Docker 镜像的推送与拉取速度很慢，影响部署的效率。 最近接手的一个项目中，我就遇到了类似的情况，我们在客户的内网部署了 Docker 的私有仓库，Jenkins 打包完后需要推送到 Docker 仓库中。 由于客户基于安全要求对于内部网络之间的传输做了网速的限制，通过传统的 Spring Boot 的 FatJar 生成的 Docker 镜像过大，在每次推送镜像和拉取镜像都需要花不少时间。 我们在私有仓库部署的服务较多，对私有仓库的存储容量要求较高。\n为了解决这个问题，我通过优化 Docker Layers 的方式大幅减少了镜像的大小变化，提高了部署效率。下面是完整的实战过程，希望对你有所帮助。\nFatJar 介绍 #默认情况下，Spring Boot 的应用会采用 FatJar 的模式来将我们的 Spring Boot 应用打包成一个可执行的文件。通过如下的 Dockerfile 可以构建出该服务的 Docker 镜像用来启动服务\nFROM openjdk:8-jdk-alpine ARG JAR_FILE=target/*.jar COPY ${JAR_FILE} app.jar ENTRYPOINT [\u0026#34;java\u0026#34;,\u0026#34;-jar\u0026#34;,\u0026#34;/app.jar\u0026#34;] java -jar app.jar -Dspring.profile.active=dev FatJar 是一种将所有依赖打包到单个可执行 JAR 文件中的格式，采用这种设计方便了开发和运行，但也带来了一个明显的缺点：\nJAR 文件过大：常规的 Spring Boot 应用可能生成 100MB 到 200MB 的 JAR 文件，甚至更大。 依赖关系难以拆分：每次应用代码修改后，整个 JAR 文件都需要重新打包并更新。 我们可以使用 unzip -v 来确认这一点\n在 Docker 镜像中，FatJar 通常被直接复制到镜像中，导致镜像每次更新时即使只有很小的代码变动，也需要重新推送整个镜像。这对于网络受限的环境来说，是不可接受的。\n\u0026lt;plugin\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-maven-plugin\u0026lt;/artifactId\u0026gt; \u0026lt;configuration\u0026gt; \u0026lt;executable\u0026gt;true\u0026lt;/executable\u0026gt; \u0026lt;/configuration\u0026gt; \u0026lt;/plugin\u0026gt; Docker 镜像 #Docker 构建镜像时，每一层都会生成一个可缓存的 Layer。如果某一层没有变化，Docker 就会复用缓存，而无需重新上传或下载。基于这一点，我们可以将 Spring Boot 应用分层打包，将不常变动的依赖和经常变动的代码分开，优化镜像的传输效率。\n解决方案 #基于 Docker Layers 优化镜像构建\n\u0026lt;project\u0026gt; \u0026lt;build\u0026gt; \u0026lt;plugins\u0026gt; \u0026lt;plugin\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-maven-plugin\u0026lt;/artifactId\u0026gt; \u0026lt;configuration\u0026gt; \u0026lt;layers\u0026gt; \u0026lt;enabled\u0026gt;true\u0026lt;/enabled\u0026gt; \u0026lt;/layers\u0026gt; \u0026lt;/configuration\u0026gt; \u0026lt;/plugin\u0026gt; \u0026lt;/plugins\u0026gt; \u0026lt;/build\u0026gt; \u0026lt;/project\u0026gt; 优化的效果 ## Perform the extraction in a separate builder container FROM bellsoft/liberica-openjre-debian:17-cds AS builder WORKDIR /builder # This points to the built jar file in the target folder # Adjust this to \u0026#39;build/libs/*.jar\u0026#39; if you\u0026#39;re using Gradle ARG JAR_FILE=target/*.jar # Copy the jar file to the working directory and rename it to application.jar COPY ${JAR_FILE} application.jar # Extract the jar file using an efficient layout RUN java -Djarmode=tools -jar application.jar extract --layers --destination extracted # This is the runtime container FROM bellsoft/liberica-openjre-debian:17-cds WORKDIR /application # Copy the extracted jar contents from the builder container into the working directory in the runtime container # Every copy step creates a new docker layer # This allows docker to only pull the changes it really needs COPY --from=builder /builder/extracted/dependencies/ ./ COPY --from=builder /builder/extracted/spring-boot-loader/ ./ COPY --from=builder /builder/extracted/snapshot-dependencies/ ./ COPY --from=builder /builder/extracted/application/ ./ # Start the application jar - this is not the uber jar used by the builder # This jar only contains application code and references to the extracted jar files # This layout is efficient to start up and CDS friendly ENTRYPOINT [\u0026#34;java\u0026#34;, \u0026#34;-jar\u0026#34;, \u0026#34;application.jar\u0026#34;] 总结 #通过优化 Docker 镜像分层，我们成功解决了网络受限环境下的部署效率问题：\n减少镜像传输量：只有应用代码层需要更新，依赖层可以复用缓存 提升部署速度：镜像推送和拉取时间大幅缩短 降低存储压力：减少了私有仓库的存储占用 这种优化方式特别适合：\n网络带宽受限的环境 需要频繁部署更新的项目 对部署速度有较高要求的场景 希望这个实战经验对你有帮助！如果你也遇到过类似的部署问题，欢迎在评论区分享你的解决方案。\n","date":"2024-12-03","permalink":"https://ygwa.github.io/writings/spring-boot-docker-layer/","section":"技术文章","summary":"\u003cp\u003e在实际项目中，我们偶尔会遇到由于客户部署环境的网络限制问题，导致 Docker 镜像的推送与拉取速度很慢，影响部署的效率。\n最近接手的一个项目中，我就遇到了类似的情况，我们在客户的内网部署了 Docker 的私有仓库，Jenkins 打包完后需要推送到 Docker 仓库中。\n由于客户基于安全要求对于内部网络之间的传输做了网速的限制，通过传统的 Spring Boot 的 FatJar 生成的 Docker 镜像过大，在每次推送镜像和拉取镜像都需要花不少时间。\n我们在私有仓库部署的服务较多，对私有仓库的存储容量要求较高。\u003c/p\u003e","title":"利用 Docker 分层，构建高效的 Spring Boot 镜像"},{"content":"","date":null,"permalink":"https://ygwa.github.io/tags/%E6%9E%B6%E6%9E%84%E8%AE%BE%E8%AE%A1/","section":"标签","summary":"","title":"架构设计"},{"content":" 这里是我自己做的一些产品和工具。有些是解决实际问题的工具，有些是个人兴趣项目，记录一下开发过程和想法。 ","date":null,"permalink":"https://ygwa.github.io/projects/","section":"精选项目","summary":"\u003cdiv class=\"lead !mb-9 text-xl\"\u003e\n  这里是我自己做的一些产品和工具。有些是解决实际问题的工具，有些是个人兴趣项目，记录一下开发过程和想法。\n\u003c/div\u003e","title":"精选项目"},{"content":"👨‍💻 关于我 #你好，我是蒋大培，一名全栈开发工程师。\n我对代码有着纯粹的热爱，享受从零开始构建产品的过程。从底层的数据库设计到前端的交互体验，我致力于打通技术栈的每一个环节，交付高质量的软件产品。\n我相信技术服务于产品，代码体现着思考。\n🛠️ 技术栈与技能 #在多年的开发生涯中，我积累了跨领域的开发经验：\n后端开发: 深入理解微服务架构，擅长高并发、高可用系统的设计与实现。 前端开发: 关注用户体验，熟练运用现代前端框架构建响应式应用。 基础设施: 熟悉 Docker、K8s 等容器化技术，推崇 DevOps 文化和自动化运维。 数据库: 精通关系型数据库优化，熟悉 Redis、Elasticsearch 等 NoSQL 解决方案。 💡 工程理念 # Keep It Simple: 追求简洁、清晰的代码结构，反对过度设计。 Automate Everything: 只要是重复的工作，就应该被自动化。 Continuous Learning: 技术日新月异，保持好奇心和学习能力是工程师的核心竞争力。 📫 联系我 #如果你对我的项目感兴趣，或者想交流技术话题，欢迎通过以下方式找到我：\nGitHub: @ygwa Email: (在这里填写你的邮箱) 这里的每一行代码和每一个字符，都代表着我对技术的热爱。\n","date":"2024-08-21","permalink":"https://ygwa.github.io/about/","section":"蒋大培","summary":"\u003ch2 id=\"-关于我\" class=\"relative group\"\u003e👨‍💻 关于我 \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#-%e5%85%b3%e4%ba%8e%e6%88%91\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h2\u003e\u003cp\u003e你好，我是蒋大培，一名\u003cstrong\u003e全栈开发工程师\u003c/strong\u003e。\u003c/p\u003e\n\u003cp\u003e我对代码有着纯粹的热爱，享受从零开始构建产品的过程。从底层的数据库设计到前端的交互体验，我致力于打通技术栈的每一个环节，交付高质量的软件产品。\u003c/p\u003e\n\u003cp\u003e我相信\u003cstrong\u003e技术服务于产品，代码体现着思考\u003c/strong\u003e。\u003c/p\u003e\n\u003ch2 id=\"-技术栈与技能\" class=\"relative group\"\u003e🛠️ 技术栈与技能 \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#-%e6%8a%80%e6%9c%af%e6%a0%88%e4%b8%8e%e6%8a%80%e8%83%bd\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h2\u003e\u003cp\u003e在多年的开发生涯中，我积累了跨领域的开发经验：\u003c/p\u003e","title":"关于我"},{"content":"","date":null,"permalink":"https://ygwa.github.io/categories/","section":"Categories","summary":"","title":"Categories"},{"content":" 这里按时间顺序整理了所有文章，方便你按时间线浏览内容。 ","date":null,"permalink":"https://ygwa.github.io/archives/","section":"归档","summary":"\u003cdiv class=\"lead !mb-9 text-xl\"\u003e\n  这里按时间顺序整理了所有文章，方便你按时间线浏览内容。\n\u003c/div\u003e","title":"归档"}]