[{"content":"在之前的文章中，我介绍过一种比较直接 的链式 VPN 方案：所有设备先连接到自己搭建的WireGuard 服务器，再由服务器把流量转 发到 Gluetun，最终通过上游 VPN 服务商的出口访问互联网。\n这个方案的核心价值很明确：\n所有设备只需要配置一个 WireGuard； 上游 VPN 只需要在服务器端维护； 可以把手机、电脑、平板、家用设备统一接入同一个虚拟局域网； 出口 IP 由商业 VPN、公司 VPN 或其他上游 VPN 提供，而不是直接暴露 VPS IP； 整套系统可以通过 Docker 部署，尽量不污染宿主机环境。 相比于直连VPN，该方案下VPN服务商能看到的只是你的VPN网关出口（你的VPS服务 器），而不是每个设备的真实 IP。 另外，本地的ISP等也看不到你是否是在使用主流VPN，因为他们只能知道你在连接到一个 VPN网关（你的VPS服务器），但流量最终去了哪里是未知的。 不过，随着使用场景变多，原来的结构也逐渐暴露出一些局限。最典型的问题是，对流量的 控制不够细粒度。默认情况下所有流量都必须进入上游 VPN 隧道，这对于某些服务可能会 触发风控，或者导致访问问题。而且比较难方便地进行流量工程，对不同服务采用不同的出 口策略。原先通过 iptables 和路由表的方式虽然能实现分流，但规则一多就难维护。\n因此，我把原来的方案重新整理成了一个更完整的个人 VPN 网关架构。新版方案不再只是 简单的 WG-Easy + Gluetun，而是变成了：\nWG-Easy + sing-box + Gluetun + AdGuard Home + config.yaml 统一配置生成 对我来说，这套方案已经不只是“VPN 套 VPN”，而是一个可以长期维护、可扩展、可观察的 个人网络出口。它既可以服务日常上网，也可以服务远程办公、家庭内网访问、开发测试和 多设备统一管理。如果你只需要最简单的全局 VPN 转发，旧版思路仍然够用。但如果你希 望在隐私、可控性、分流规则和 DNS 管理之间取得更好的平衡，新版架构会更合适。\n这篇文章会介绍新版架构的设计思路、流量路径、适合的使用场景，以及相比旧方案的主要 改进。如果你对新版方案感兴趣，或者想直接部署使用，可以直接查看我在 GitHub 上的 项目仓库。\n为什么需要新版架构 最早的链式 VPN 方案其实已经能解决很多问题。比如我在家里、办公室、外出网络中都可 以使用同一个 WireGuard 配置接入 VPS，然后通过 VPS 上的 Gluetun 进入上游 VPN。这 样所有设备都共享同一个上游 VPN 出口，也能避免在每台设备上反复安装、登录、配置不 同 VPN 客户端。\n这个方案实际我平时使用下来也很稳定，性能也能够满足我的需求，基本上没有什么大问 题。但长时间使用下来，我还是遇到了几大痛点。\n不是所有流量都适合走上游 VPN 我在实际的使用中发现，某些服务可能会因为 VPN 出口触发风控，例如账号登录、支 付、AI 服务等。比如一些我常用的流媒体平台，对于VPN有着严格的限制，有时这些流量更 适合直连。还有就是，我有时候需要网文在校园网内部的课程服务器，有时候会涉及远程桌 面，这需要比较低的延迟。这种情况下，走VPN的话即使是本国出口体验也非常不佳。\n因此，我希望能有更细粒度的分流能力。比如通过规则集把我信任的一些服务的流量绕过上 游 VPN，直接从服务器侧访问互联网。这样既能保持上游 VPN 的隐私保护，又能避免一些 不必要的访问问题。以前的做法更多依赖 iptables、路由表和容器网络。能工作，但规则 一多就难维护。\n在通过调研和测试后，我发现 sing-box 的 TUN 模式非常适合做这种基于规则的分流。它 支持丰富的规则类型和灵活的路由策略，可以根据域名、IP等多维度来决定流量的下一跳。 通过把 sing-box 放在 WG-Easy 和 Gluetun 之间，我就能实现更智能的流量分流，而不是 简单的全局 VPN。然后singbox能够引用远程的规则集，例如 geosite-openai、geosite-paypal 等，这样我就不需要自己维护这些规则了。还有就是， 相比于部署一个openwrt虚拟机，singbox的部署和维护都更轻量级，对docker更友好。\n如此一来，我就能够更灵活地进行流量工程了。比如：默认流量，为了隐私保护需要它们走 VPN。而有些在特定区域的服务，我希望从另外的VPN 出口访问；有些服务我希望直接访 问；有些服务我希望通过特定的 VPN 出口访问。通过sing-box 的规则分流，我可以更方便 地实现这些策略。\n配置需要集中管理 原先仓库中配置都是分散在conf目录下，修改网段的等配置本来就不太方便。在加入 sing-box后，Docker Compose、wg-easy hook、sing-box JSON、AdGuardHome 配 置、iptables 规则一起维护，就比较痛苦了。所以，新版方案我用config.yaml 作为单 一配置入口，再通过模板生成 runtime 文件。\n每次修改配置后，直接运行生成up.sh脚本就能把所有相关配置文件更新到位，避免了“改 了这个忘了改那个”的问题。\n更完善的网络监控 原来的方案主要依赖日志来观察网络状态。我有时候像看一下当前有哪些设备现在连接了 WireGuard，这些设备都会访问什么服务，有没有异常流量等。在原先的方案里，这些信息 都需要自己在容器里查看日志，或者额外安装一些监控工具。新版方案通过singbox提供的 API接口，可以配合MetaCubeXD等工具来实时查看流量分布、规则命中、上游访问等信息， 提升了可观测性。\n因此，新版架构的目标不是单纯“再套一层 VPN”，而是构建一个更像家庭或个人使用的可控 网络出口。\n新版方案的组件 新版方案主要由四个核心容器组成。\nWG-Easy：WireGuard 接入层 WG-Easy 仍然负责最基础也最重要的部分：提供 WireGuard 服务端和 Web 管理界面。所有 手机、电脑、平板、服务器都作为 WireGuard 客户端连接到 WG-Easy。用户可以通过 WG-Easy 的 Web UI 创建 peer、下载配置文件或扫码导入手机端。\n在新版架构中，WG-Easy 不再只是把流量直接交给 Gluetun，而是通过 hook 和 policy routing 把客户端流量导入更复杂的网关路径。\nAdGuard Home：统一 DNS 与过滤层 AdGuard Home 负责 DNS 解析、广告过滤、跟踪域名拦截和 DNS 查询日志。\n新版架构中，来自 WireGuard 客户端的普通 DNS 请求会被限制到 AdGuard Home。也就是 说，客户端不能随便把 DNS 发到 8.8.8.8、1.1.1.1 或其他外部 DNS 服务器。\n这样做的好处是：\n可以集中设置上游 DNS； 可以统一管理过滤列表； 可以查看客户端 DNS 查询； 可以降低 DNS 泄露风险； 可以避免每台设备单独配置过滤软件。 当然，这并不等于可以完全阻止所有形式的 DNS 绕过。比如 DoH、DoT 或某些应用内置解 析仍然需要额外策略处理。但对于普通 UDP/TCP 53 DNS 请求，网关可以做到统一收口。\nsing-box：规则分流层 sing-box 是新版架构中最关键的变化。\n它通过 TUN inbound 接收 WG-Easy 转发过来的流量，然后根据规则决定下一跳：\n命中 bypass_rule_sets 的流量直接出站； 私有 IP 流量直接出站，避免误送进上游 VPN； 其他默认流量打上 routing mark，交给 Linux policy routing，再进入 Gluetun； 规则集可以使用远程 .srs 文件，例如 geosite-openai、geosite-paypal 等。 如果你看对应的singbox的配置文件，会发现一个比较奇怪的点：sing-box 里的 gluetun outbound 在配置上也是 direct类型，但它带有 routing_mark。实际上，这个 mark 会被Linux policy routing 捕获，把流量导向 Gluetun。因此它并不是普通意义上的直 连，而是“通过系统路由送到 Gluetun”。\n换句话说，sing-box 在这里更像一个智能分流器，而 Gluetun 是最终的上游 VPN 出口。 这里除了通过gluetun，singbox自己也可以通过类似的方式配置其他类型的 outbound，例 如 WireGuard、VLESS、Trojan 等，来适配更多样化的上游服务。如果你不需要gluetun提 供的各种便利性服务，甚至可以在这里就把它换成其他 VPN 客户端或直接的互联网出口。\n需要注意的是，singbox在这里是拿不到域名信息，所以我们在这里需要开启sniff 功能来识别域名。这样才能基于域名的规则集来做分流。对于一些不支持sniff的协议或加 密流量，可能就只能基于IP规则来做分流了。\nGluetun：上游 VPN 出口层 Gluetun 负责连接商业 VPN、公司 VPN 或其他支持的上游 VPN 服务。\n它的优势是支持大量 VPN 服务商，并且把 OpenVPN / WireGuard 客户端、认证参数、防火 墙规则、健康检查等逻辑都封装在容器里。\n在新版架构中，绝大多数默认流量最终都会进入 Gluetun，再由 Gluetun 通过上游 VPN 隧 道访问互联网。这样客户端看到的是自己的 WireGuard 网络，而互联网看到的是上游 VPN 的出口 IP。\n整体网络拓扑 新版架构可以理解为下面这个路径：\nflowchart TD subgraph devices[\u0026#34;Your Devices\u0026#34;] phone[\u0026#34;Phone\u0026#34;] pc[\u0026#34;PC\u0026#34;] laptop[\u0026#34;Laptop\u0026#34;] end subgraph server[\u0026#34;Your Server\u0026#34;] wgeasy[\u0026#34;WG-Easy (WireGuard Server)\u0026#34;] adguard[\u0026#34;AdGuard Home (DNS Filtering)\u0026#34;] singbox[\u0026#34;sing-box (TUN Router / Rule Engine)\u0026#34;] gluetun[\u0026#34;Gluetun (VPN Provider Client)\u0026#34;] end phone -- WireGuard --\u0026gt; wgeasy pc -- WireGuard --\u0026gt; wgeasy laptop -- WireGuard --\u0026gt; wgeasy wgeasy -- DNS only --\u0026gt; adguard wgeasy -- regular traffic --\u0026gt; singbox singbox -- bypass_rule_sets --\u0026gt; direct[\u0026#34;Direct Internet\u0026#34;] singbox -- default route --\u0026gt; gluetun gluetun -- VPN tunnel --\u0026gt; provider[\u0026#34;VPN Provider\u0026#34;] provider --\u0026gt; internet[\u0026#34;Internet\u0026#34;] direct --\u0026gt; internet 从流量角度看，可以分成三类：\nDNS 流量：WireGuard 客户端的 DNS 请求被固定转发到 AdGuard Home。 命中绕过规则的流量：例如 OpenAI、PayPal 或其他你放进 bypass_rule_sets 的规则集，可以走 direct。 默认流量：没有命中绕过规则的普通互联网流量会进入 Gluetun，再通过上游 VPN 出口访问互联网。 此外，还可以配置 direct_subnets，让特定网段绕过 sing-box。这适合一些明确的内网 访问场景，例如访问家庭局域网、特定 Docker 网络或某些专用子网。\n配置管理：从手写配置到生成式配置 新版方案的另一个重要变化是配置管理方式。\n以前你可能需要同时修改：\ndocker-compose.yml WireGuard hook iptables 规则 Gluetun 环境变量 DNS 配置 sing-box JSON AdGuard Home 配置 这样虽然灵活，但维护成本很高。尤其是网络结构稍微变化后，很容易出现“一个地方改 了，另一个地方忘了改”的问题。新版方案把主要配置集中到 config.yaml， 然后通过生成脚本把它渲染成 runtime/ 下的各种实际的配置文件。\n因此，普通用户只需要改 config.yaml。而高级用户如果要调整更多的配置，则可以修改 templates/ 下的 Jinja2 模板。这样既保证了配置的集中管理，也保留了足够的灵活性。\n需要注意的是，我建议不要直接修改 runtime/ 目录下的文件。它们是生成物，每次重新运行都会被覆 盖。\nsing-box 分流规则如何工作 新版方案中，sing-box 的规则大致可以理解为：\n{ \u0026#34;route\u0026#34;: { \u0026#34;rules\u0026#34;: [ { \u0026#34;inbound\u0026#34;: \u0026#34;tun-in\u0026#34;, \u0026#34;action\u0026#34;: \u0026#34;sniff\u0026#34; }, { \u0026#34;ip_is_private\u0026#34;: true, \u0026#34;outbound\u0026#34;: \u0026#34;direct\u0026#34; }, { \u0026#34;rule_set\u0026#34;: [\u0026#34;geosite-openai\u0026#34;, \u0026#34;geosite-paypal\u0026#34;], \u0026#34;outbound\u0026#34;: \u0026#34;direct\u0026#34; }, { \u0026#34;outbound\u0026#34;: \u0026#34;gluetun\u0026#34; } ] } } 这表示：\n先对流量做 sniff，识别域名等信息； 私有 IP 地址直接走 direct； 命中规则集的流量走 direct； 其他所有流量走 gluetun outbound。 bypass_rule_sets 的含义是“绕过上游 VPN”，不是“绕过你的服务器”。\n例如你配置了：\nsingbox: bypass_rule_sets: - tag: \u0026#34;geosite-openai\u0026#34; url: \u0026#34;https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-openai.srs\u0026#34; - tag: \u0026#34;geosite-paypal\u0026#34; url: \u0026#34;https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-paypal.srs\u0026#34; 那么这些服务的流量仍然会经过你的 WireGuard 服务器和 sing-box，只是不再进入 Gluetun 的上游 VPN 隧道，而是由服务器侧直接访问互联网。\n这对一些对 VPN 出口敏感的服务很有帮助。比如有些服务看到商业 VPN IP 会要求额外验 证，甚至直接拒绝访问。通过规则绕过上游 VPN 后，可以减少这类问题。\nextra_rule_sets 是什么 新版配置里还有一个 extra_rule_sets：\nsingbox: extra_rule_sets: - tag: \u0026#34;geosite-google\u0026#34; url: \u0026#34;https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-google.srs\u0026#34; - tag: \u0026#34;geosite-apple\u0026#34; url: \u0026#34;https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-apple.srs\u0026#34; 它的作用是：下载并缓存这些规则集，但不参与当前路由匹配。也就是 说，extra_rule_sets 不会改变流量路径。只有放进 bypass_rule_sets，或者在 sing-box 模板里显式引用，规则才会真正生效。\n我保留这个配置项，是为了方便未来扩展。例如我可能想先把 Google、Apple、Amazon 等 规则集准备好，但不马上启用。等到需要时，只需要把对应规则移到 bypass_rule_sets，或者添加新的 route rule。\n如果你追求最简配置，也可以把 extra_rule_sets 设置成空数组：\nsingbox: extra_rule_sets: [] 注意，YAML 中最好写成显式空数组，而不是只写一个空 key：\n# 推荐 extra_rule_sets: [] # 不推荐，容易被解析成 null extra_rule_sets: DNS：为什么要默认引入 AdGuard Home 在 VPN 网关中，DNS 是一个很容易被忽略的问题。如果只管普通流量，不管 DNS，就可能 出现这种情况：\n浏览器流量走了你的 VPN 网关； DNS 查询却发给了本地网络、运营商 DNS 或某个公共 DNS； 最终造成 DNS 泄露，或者规则分流判断不准确。 新版架构里，WireGuard 客户端的普通 DNS 请求会被限制到 AdGuard Home。这样你可以在 网关侧统一管理 DNS。\nAdGuard Home 可以带来几个好处：\n广告和跟踪域名过滤； 自定义上游 DNS； 查询日志； 本地域名重写； 对不同客户端做差异化策略。 不过需要注意，如果你想完全禁止所有 DNS 绕过，还需要考虑 DoH/DoT/QUIC等情况。本文 中的 DNS 限制主要针对传统 UDP/TCP 53 查询。\n适合的使用场景 新版架构可以覆盖的场景比旧版更多，综合考虑下，主要适用于以下几种情况：\n1. 多设备共享一个上游 VPN 这是最基础的使用方式。\n你只需要在服务器上配置一次上游 VPN，所有设备通过 WireGuard 接入。手机、电脑、平 板、备用机都不需要分别安装上游 VPN 客户端。\n这对于设备数量较多、平台较杂，或者上游 VPN 客户端体验不佳的情况尤其有用。\n2. 远程访问家庭或办公网络 所有设备接入同一个 WireGuard 网络后，可以形成一个统一的虚拟局域网。\n你可以用它访问：\n家里的 NAS； 办公室内网服务； 私有 Git 服务； 家庭媒体服务器； SSH 管理入口。 如果配合 direct_subnets，还可以让部分内网访问不经过复杂的 sing-box 分流逻辑。\n3. 对不同服务采用不同出口策略 有些服务适合走商业 VPN，有些服务则不适合。\n例如：\n默认流量走 Gluetun 上游 VPN； OpenAI、PayPal 等敏感服务走 direct； Torrent、P2P 流量走Gluetun或者其他专门的 VPN 出口； 私有 IP 和内网地址不进入上游 VPN； 特定规则集预先下载，但暂不启用。 这种结构比旧版本的“全局代理”更灵活，也比纯手写 iptables 规则更容易维护。\n5. 低侵入式部署 整套方案基于 Docker Compose。宿主机只需要安装 Docker 和 Docker Compose，主要网络 逻辑都在容器和生成配置中完成。这对 VPS、家用服务器、软路由、树莓派都比较友好。\n部署流程概览 实际部署时，流程大致是：\ngit clone https://github.com/saturneric/wg-easy-gluetun.git cd wg-easy-gluetun 然后编辑：\nvim config.yaml 至少需要配置：\n上游 VPN 的 WireGuard / OpenVPN 参数； WG-Easy 管理密码； 服务器公网地址或域名； AdGuard Home 上游 DNS； sing-box 绕过规则； 必要的服务端口和固定容器 IP。 启动时使用：\n./up.sh -d 不要直接运行普通的 docker compose up，因为 runtime compose 文件和相关配置需要 先由生成器创建。up.sh 会先运行配置生成逻辑，再启动完整 stack。\n安全注意事项 这类网关方案虽然方便，但也需要注意安全边界。\n1. 不要使用默认密码 config.yaml 里的 WG-Easy 密码、Clash API secret、AdGuard Home 凭据都应该修改。 尤其是管理界面如果暴露在公网，默认密码会非常危险。\n2. 谨慎暴露管理端口 通常只应该公开 WireGuard UDP 端口。WG-Easy、MetaCubeXD、AdGuard Home 管理界面最 好不要直接暴露在公网。\n3. 确认上游 VPN 服务条款 多设备共享、网关转发、链式 VPN 是否被允许，取决于你的上游 VPN 服务条款。部署前应 该自己确认。\n4. 注意 DNS 绕过 本方案会限制普通 DNS 查询，但不能自动阻止所有应用层 DoH / DoT 行为。如果你需要更 严格的 DNS 策略，需要额外做域名、IP、SNI 或应用层策略控制。\n总结：相比旧版方案的主要变化 和旧版 WG-Easy + Gluetun 相比，新版架构主要有这些变化：\n方面 旧版 新版 接入层 WG-Easy WG-Easy 上游 VPN Gluetun Gluetun 分流能力 主要依赖路由和 iptables sing-box 规则集分流 DNS 需要额外配置 默认集成 AdGuard Home 配置方式 多文件手动维护 config.yaml + 模板生成 规则管理 粗粒度 geosite / rule-set 级别 可观测性 主要看日志 可配合 MetaCubeXD 查看 sing-box 扩展能力 相对有限 更适合未来加入更多规则和服务 总而言之，旧版更简单，适合只想把所有流量送进上游 VPN 的用户。新版则更适合长期使用，尤其适 合需要分流、DNS 过滤、集中管理和多场景适配的用户。\n","permalink":"https://blog.bktus.com/archives/vr4bm4/","summary":"\u003cp\u003e在\u003ca href=\"https://blog.bktus.com/archives/ozzkto/\"\u003e之前的文章\u003c/a\u003e中，我介绍过一种比较直接\n的链式 VPN 方案：所有设备先连接到自己搭建的WireGuard 服务器，再由服务器把流量转\n发到 Gluetun，最终通过上游 VPN 服务商的出口访问互联网。\u003c/p\u003e\n\u003cp\u003e这个方案的核心价值很明确：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e所有设备只需要配置一个 WireGuard；\u003c/li\u003e\n\u003cli\u003e上游 VPN 只需要在服务器端维护；\u003c/li\u003e\n\u003cli\u003e可以把手机、电脑、平板、家用设备统一接入同一个虚拟局域网；\u003c/li\u003e\n\u003cli\u003e出口 IP 由商业 VPN、公司 VPN 或其他上游 VPN 提供，而不是直接暴露 VPS IP；\u003c/li\u003e\n\u003cli\u003e整套系统可以通过 Docker 部署，尽量不污染宿主机环境。\u003c/li\u003e\n\u003cli\u003e相比于直连VPN，该方案下VPN服务商能看到的只是你的VPN网关出口（你的VPS服务\n器），而不是每个设备的真实 IP。\u003c/li\u003e\n\u003cli\u003e另外，本地的ISP等也看不到你是否是在使用主流VPN，因为他们只能知道你在连接到一个\nVPN网关（你的VPS服务器），但流量最终去了哪里是未知的。\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e不过，随着使用场景变多，原来的结构也逐渐暴露出一些局限。最典型的问题是，对流量的\n控制不够细粒度。默认情况下所有流量都必须进入上游 VPN 隧道，这对于某些服务可能会\n触发风控，或者导致访问问题。而且比较难方便地进行流量工程，对不同服务采用不同的出\n口策略。原先通过 iptables 和路由表的方式虽然能实现分流，但规则一多就难维护。\u003c/p\u003e\n\u003cp\u003e因此，我把原来的方案重新整理成了一个更完整的个人 VPN 网关架构。新版方案不再只是\n简单的 \u003ccode\u003eWG-Easy + Gluetun\u003c/code\u003e，而是变成了：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-text\" data-lang=\"text\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eWG-Easy + sing-box + Gluetun + AdGuard Home + config.yaml 统一配置生成\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e对我来说，这套方案已经不只是“VPN 套 VPN”，而是一个可以长期维护、可扩展、可观察的\n个人网络出口。它既可以服务日常上网，也可以服务远程办公、家庭内网访问、开发测试和\n多设备统一管理。如果你只需要最简单的全局 VPN 转发，旧版思路仍然够用。但如果你希\n望在隐私、可控性、分流规则和 DNS 管理之间取得更好的平衡，新版架构会更合适。\u003c/p\u003e","title":"搭建一个可分流的个人VPN网关：WireGuard、sing-box与Gluetun的组合实践"},{"content":"我开始认真折腾个人 self-host，大概已经有五六年了。严格意义上开始管理我自己的 VPS 的时间点，应该还可以往前追溯。最初的动机其实很简单：有些服务我每天都在用，比如密 码管理器、音乐播放、笔记同步、个人网站、代码仓库等等。既然如此，为什么不干脆自己 部署？这样一来数据在自己手里，也不用被各种平台订阅和涨价牵着走。\n这几年一路走下来，说实话，self-host 并不省心。我要维护系统、备份数据、关心安全、 偶尔还要救火。但它给我的回报也很明确：\n数据完全掌控在自己手里 不再被订阅价格牵着走 对系统、网络和安全的理解明显更深 现在我一台服务器跑着十几个服务，成本只是一台服务器的钱。在这两年取消了不少第三方 平台的订阅后，算下来，整体成本可能反而更低。对我来说，这种“复杂但可控”的状态，反 而比依赖一堆外部服务更安心。当然不这适合所有人，我感觉有：有一定技术背景的人、喜 欢折腾的人以及愿意为稳定与掌控付出时间的人，这三者合一就适合这样。\n到现在，我并不觉得自己成了什么“专家”，但有到现在两件事的感受越来越强烈：安全性 以及服务器环境的整洁程度，这也是这篇文章的两个主线。这两个问题，几乎决定了 你这台服务器上的各类服务能不能长期稳定地用下去。然后就是需要迁移的时候，比如换了 一个更便宜或者更好的 VPS 供应商，这个时候的工作量（我自己就换了好几次，知道真的 换不动了才稳定下来）。\n草莽阶段 最开始的时候，我的做法非常朴素。我会直接在宿主机上安装服务，然后安装 Nginx 或者 Apache2（httpd）。比如用 Nginx 做反向代理，Nginx 这类 Web 服务器 的好处是配置文 件可以拆开，每个站点一份，用域名来区分各个服务而不是端口号，看起来井井有条。那时 候我觉得这套方案挺优雅的。\n但随着服务慢慢变多，问题开始一点点浮现。有的服务需要特定版本的依赖，我很难在同一 个环境安装不同版本的各种依赖库或者数据库。而有的会在系统里留下各种临时文件；配置 文件散落在 /etc、数据放在 /srv，日志又在 /var。一开始你还能记得“这是哪个服 务的”，时间一长就完全混在一起了。\n这期间，当我想删除一个不用的服务时，我已经不敢确定自己删得干不干净。特别是有的服 务提供“一键安装脚本”，安装的时候一时畅快，等到了你想摆脱它的时候，你会发现当初的 脚本不知道做了什么改动，如何复原都毫无头绪。你很难确认还有没有残留的配置、后台进 程，甚至不知道删错东西会不会影响别的服务。这种不确定感，本身就是一个风险，对于强 迫症来说，每每想起简直是如鲠在喉。\n容器化阶段 后来我把几乎所有服务都迁移到了 Docker 里。而且，到现在越来越倾向于将所有能用的到 的服务或者工具软件容器化，再去部署。有些不提供标准的容器化部署方案的，但又很想用 的服务，我会直接自己进行容器化包装，形成自己的容器化的解决方案。\n真正改变体验的不是 Docker 本身，而是 Docker Compose。容器这东西，本质上就不 应该被当成“长期维护对象”，它随时都可以被删掉、重建，你需要意识到这个。早期的时 候，我觉得 Docker Compose 配置文件麻烦，为什么不用命令直接创建一个容器，然后让这 个容器长期运行。后来等我要更新，或者这个服务依赖多个底层的其他容器的时候，问题就 逐渐浮现出来。如果当初将对服务的部署写下来，写到一个配置文件中，然后每次就执行这 个配置文件就能完美复现原先的部署，不需要自己额外配置什么 Bridge 网络，或者什么 hostname，亦或者是重启策略。这个节省了大量的时间和精力，并且配置的时候出错的可能 性，也大大降低了。\n到现在，我能够总结出：真正需要被长期维护的，只有三样东西：\n服务配置（包括版本，网络，存储等） 数据（主要是 Volumes 和其他映射的目录） 部署配置（Docker Compose 的各类 Yml 配置文件） 不管是重装系统，还是换一台机器，只要这三者还在，这些服务就能在这个新的地方完美地 克隆。而且，宿主机本身终于变得“干净”了。各个目录下不再到处是你几年之前装过、现在 已经忘记用途的东西。所以，在这个阶段，我单独创建了一个 git 仓库来收集和管理所有 我用到的服务的对应的 Docker Compose 配置文件，一年下来，我感觉这样是经得起各类场 景的考验的。\n一个服务一个数据库 这个是一个值得一提的点。很长一段时间里，我的所有服务共用一个数据库实例。这在逻辑 上看起来非常合理：资源省、结构简单、管理集中。但真正长期维护下来，你会发现这其实 是一个非常不友好的设计。\n每多一个服务，就多一套用户、权限、密码；想迁移一个服务，就得小心翼翼地把数据库剥 离出来；数据库版本一旦升级，所有服务都要跟着承担风险。对于不熟悉某个数据库的人来 说，管理权限和用户是一件头疼的东西，你需要通过命令行或者什么数据库管理软件，仔细 地区分和妥善保管各个服务的用户以及他们的密码。\n后来我干脆反过来做了一件事：**每个服务，单独一个数据库实例。**也就是，每个 Docker Compose 中独立地会有一个数据库的 service。长期观察下来，对个人 self-host 来说，这并没有想象中那么“浪费”。一个数据库容器通常只占一两百兆内存，在一台 4c8g 的 VPS，甚至 2c4g， 或家用服务器上，是可以容忍的。\n但换来的好处是非常实在的：服务之间彻底隔离，大大提高了安全性，然后后续也不用再为 权限和兼容性焦头烂额。直接一件将所有数据和配置打包带走即可，每个数据库就只有这个 对应服务的数据。\n最小暴露面原则 早期我几乎把所有服务都直接暴露在公网，用反向代理加登录验证来保护：\n一个 Nginx Proxy Manager 所有服务都有公网域名 靠应用层用户名/密码防护 当时我也觉得：“所有服务都设置有用户名和密码，应该没问题吧？”，后来我慢慢才意识 到，这就是自己骗自己。只要其中一个服务出现：暴力破解漏洞、认证绕过或者 RCE（远程代码执行），攻击者就可能拿到容器权限甚至宿主机权限。最初的是我遇到的情 况是，我非容器部署的 gitlab 服务，在 gitlab 有次出现严重的远程执行漏洞后，我没有 及时知道这件事。然后某一天，我发现服务器就被注入了木马，这还是 Cloud Provider 提 醒我我才知道的。如果那个时候我的服务器上存着照片、视频、备份数据，后果会是灾难性 的。\n另外不同服务的安全成熟度差异巨大，技术栈五花八门；有些项目还很年轻，对安全的考虑 远不成熟；我也不可能每天盯着各类安全公告。一旦其中某一个服务出现漏洞，或者这些服 务用得框架或者依赖，比如最近的 React 技术栈相关的安全漏洞，那这个或者这些服务就 可能在短时间内爆雷。如果这个时候容器化以及隔离做的不好，攻击者拿到的不一定只是那 个服务，而很可能是整台机器。然后就是隐藏的木马脚本，或者挖矿脚本。\n这也是我后来形成的一个核心原则：能不对公网开放的服务，就不要对公网开放。博客、静 态页面这种，被攻击了损失也有限；但笔记、密码管理器、管理后台这种东西，一旦出事就 是灾难。所以，需要好好评估自己现在对外暴露的服务。以我个人的经验来看，最能够让人 放心的就是各类静态网站，或者只是通过参数生成页面没有数据库的。但是，很多服务我都 想用，如何做到有些暴露有些不暴露？因此，我现在的实践是只对公网暴露真正需要的服 务，比如静态博客、极少数公共服务。Git 仓库尽量不对外或只暴露只读静态视图（通过 gitweb 或者 cgit 暴露），而动态服务、管理后台、笔记、密码管理器等一律不直接暴露 在公网。\n部署私有 VPN 在后来，我发现解决这个问题的关键，是 VPN。我在服务器上部署了一个私有的 VPN（比如 WireGuard），把所有私有服务全部放到只在内网可访问的网络里。只有当我连接 VPN，拿到一个虚拟内网 IP 后，然后以这个内网 IP 为起点，才能访问这些服务。整体思 路是：\nVPN 提供一个虚拟私有网段（如 10.8.x.x） Docker 内部服务使用另一个私有网段（如 172.x.x.x） 两个网段通过路由互通 公网无法直接访问这些服务 这一步的意义在于：我不再需要为每个服务单独设计“防御方案”，因为它们根本就不在攻击 面上。再配合一个只在内网工作的反向代理和 DNS，使用体验反而比公网还好：域名清晰、 服务稳定，而且非常安心。\n内部反代与域名解析 进一步优化体验，我在内网部署一个 内部 Nginx 容器（在 172.24.0.0/16 下），和对外 提供服务的 Nginx 实例（比如 172.20.0.0/16）完全隔离开来。这个内部 Nginx 实例固定 绑定到私有 IP（比如 172.24.0.2），只服务通过 VPN 网络连接进来的流量。\nflowchart LR %% Clients PUB[\u0026#34;Public Internet User\u0026#34;] --\u0026gt;|\u0026#34;HTTPS\u0026#34;| PNGX[\u0026#34;Public Nginx (*.example.com)\u0026#34;] PNGX --\u0026gt; PBLOG[\u0026#34;Public Service (e.g., blog/static)\u0026#34;] %% VPN access DEV[\u0026#34;Your Device\\n(WireGuard Client)\u0026#34;] --\u0026gt;|\u0026#34;VPN Tunnel\u0026#34;| WG[\u0026#34;WireGuard Server (10.8.0.0/24)\u0026#34;] %% Internal name + reverse proxy (VPN-only) WG --\u0026gt;|\u0026#34;DNS\u0026#34;| DNS[\u0026#34;Internal DNS (172.24.0.4)\u0026#34;] WG --\u0026gt;|\u0026#34;HTTPS\u0026#34;| INGX[\u0026#34;Internal Nginx (172.24.0.2)\u0026#34;] %% Private services subgraph DOCKER[\u0026#34;Docker Private Networks\u0026#34;] INGX --\u0026gt; VAULT[\u0026#34;Vaultwarden\u0026#34;] INGX --\u0026gt; NOTES[\u0026#34;Notes / Joplin\u0026#34;] INGX --\u0026gt; GIT[\u0026#34;Git Service\u0026#34;] end 然后，再加一个强制的内部 DNS 服务，所有通过 VPN 进来的流量都只能够使用这个 DNS 提供的域名解析服务。实际的成熟的部署方案有很多，比如 AdguardHome 等，这还能够同 时解决广告拦截、域名劫持问题。这个 DNS 服务除了解析通过的普通的网络流量外，还专 门代理*.vpn.example.com 下的所有 DNS 解析，比如：\ngit.vpn.example.com notes.vpn.example.com vault.vpn.example.com 这些 DNS 都统一解析到内部的 Nginx 的监听的 IP，172.24.0.2。这样在 VPN 已连接的情 况下，后续通过浏览器直接输入对应的域名，就能够直接访问对应的内部服务。所有流量都 在内网完成。这个域名，最好是要在自己名下，然后在外部 DNS 管理 中，*.vpn.example.com 设置为 NXDOMAIN 或者 VPS 的 IP 地址，来确保如果不小心没有 通过 VPN 来访问内部服务，流量不会被意外转发到其他地方去。\n隔离数据库容器 数据库不应该和其他无关服务在同一个 Docker 网络里。为此我又做了一件事：让每个服 务、数据库单独一个 isolated bridge，在这个 bridge 下只有这两个容器能互相通信且该 bridge 不连接外部网络。也就是说，一个服务即便被攻破，它能接触到的，也只有自己的 数据库，而不是整台服务器上所有的数据库。比如这样：\nservices: db: image: postgres:16-alpine ... networks: - iso restart: unless-stopped joplin: image: git.stdv.de/saturneric/joplin:latest ... depends_on: - db networks: - iso - vpn restart: unless-stopped networks: iso: internal: true vpn: external: true 可以看到，db 只在 iso 网络中和 joplin 一对一通信，这个网络阻止该在网络内直接访问 外部网络（internal: true）。这样其他服务的被入侵后，一般无法通过网络的方式来访问 到我的 joplin 的数据库。但这并不是“绝对安全”，前提始终是攻击者没有拿到宿主机权 限。但在现实世界里，减少横向移动的可能性，本身就是非常重要的一层防护。\n这样做其实也可以防止 hostname / database 名冲突，就是很多在同一个网络下的 database 公用同一个 hostname，那么很可能会导致有的时候某些服务访问错了数据库。\n防火墙策略 如果你使用的是 VPS，我的建议是：优先使用 Cloud Provider 提供的防火墙，而尽量减少 宿主机防火墙规则。然后对于外部防火墙，尽可能地严格限制入方向的放行的端口，服务都 通过域名和反代来访问。原因包括：双重防火墙极易混乱，你很容易忘记配置了哪一个，哪 一个没有配置；然后，我也发现 Docker 端口映射有时会绕过本地防火墙，这个可能引起意 外的内部服务端口暴露，很多时候自己并没有发现。然后，云防火墙能提前挡掉大规模扫描 流量，这个也是将这个计算消耗转移到 Cloud Provider 身上。\n一般来说云防火墙入方向只放行下面的这些端口：\n80 / 443 VPN 端口 SSH 其他你认为真的有必要开的端口 其他端口即便在宿主机监听，只要云防火墙不放行，对外就是不可见的。你只能够通过 VPN 来访问到这些端口，这个就省心多了。\nSSH 安全 这个领域有很多其他的优秀的文章，我就一些很基础但非常重要的点，这些也是我现在一直 在做的：\n禁止 root 用户 SSH 登录。如果需要使用到 root 用户，则使用普通用户 + sudo 普通用户的 SSH 用户名不要太容易猜测，比如 admin 什么的。 普通用户设置长随机口令（建议密码管理器生成），而平时默认用密钥登录。 可选：只允许普通用户使用密钥登录，禁用密码。（要小心密钥没有备份然后设备丢失的 情况） 很多人会建议更换 SSH 的端口号，有关于个人环境下是否更换 SSH 端口，下面是个人的观 点：\n个人环境下安全收益有限 后续某些配置复杂度会上升 最终还是视个人习惯决定 后记 我现在其实上了 Dedicated Server，而不再是 VPS，也就是类似在机房租了一台看得见的 物理服务器。因为我现在其实需要托管几十个容器（捋了一遍都是我要的）。这可能也是这 个 Self-Host 确实已经成为了自己每天离不开的技术基础设施的一种体现，也是说明，我 认为我每个月的投入更多的资金确实有其价值。\n然后，需要指出的是，在某些地方，文章中提到的，通过私有 VPN 来一体化支持自己的内 部服务流量和普通上网的流量，很难做到。比如在这些地方，服务器的上行带宽非常贵或者 用的是流量计费。这个时候，我的建议是在自己家里的软路由中，提前进行分流，将对于 Self-Host 的流量先分离出来，然后正常的上网流量就走家庭带宽出去。\n还有就是，如果你能做到有足够的服务器的上行带宽支撑私有 VPN，很多时候你可能并不想 自己的 VPS 的 IP 来浏览网页，这无疑直接暴露了你的身份。更好的情况是，你将流量引 入另一个 VPN Provider 的 VPN 服务器，然后再通过它提供的出口 IP 出来。对于这个问 题，我有一个长期自用的成熟的解决方案，你可以参考我的我博客文章：搭建个人链式 VPN 网络：高效隐私保护、设备无限制管理与远程访 问。\n","permalink":"https://blog.bktus.com/archives/6upskf/","summary":"\u003cp\u003e我开始认真折腾个人 self-host，大概已经有五六年了。严格意义上开始管理我自己的 VPS\n的时间点，应该还可以往前追溯。最初的动机其实很简单：有些服务我每天都在用，比如密\n码管理器、音乐播放、笔记同步、个人网站、代码仓库等等。既然如此，为什么不干脆自己\n部署？这样一来数据在自己手里，也不用被各种平台订阅和涨价牵着走。\u003c/p\u003e\n\u003cp\u003e这几年一路走下来，说实话，self-host 并不省心。我要维护系统、备份数据、关心安全、\n偶尔还要救火。但它给我的回报也很明确：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e数据完全掌控在自己手里\u003c/li\u003e\n\u003cli\u003e不再被订阅价格牵着走\u003c/li\u003e\n\u003cli\u003e对系统、网络和安全的理解明显更深\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e现在我一台服务器跑着十几个服务，成本只是一台服务器的钱。在这两年取消了不少第三方\n平台的订阅后，算下来，整体成本可能反而更低。对我来说，这种“复杂但可控”的状态，反\n而比依赖一堆外部服务更安心。当然不这适合所有人，我感觉有：有一定技术背景的人、喜\n欢折腾的人以及愿意为稳定与掌控付出时间的人，这三者合一就适合这样。\u003c/p\u003e\n\u003cp\u003e到现在，我并不觉得自己成了什么“专家”，但有到现在两件事的感受越来越强烈：\u003cstrong\u003e安全性\n\u003cstrong\u003e以及\u003c/strong\u003e服务器环境的整洁程度\u003c/strong\u003e，这也是这篇文章的两个主线。这两个问题，几乎决定了\n你这台服务器上的各类服务能不能长期稳定地用下去。然后就是需要迁移的时候，比如换了\n一个更便宜或者更好的 VPS 供应商，这个时候的工作量（我自己就换了好几次，知道真的\n换不动了才稳定下来）。\u003c/p\u003e\n\u003ch2 id=\"草莽阶段\"\u003e草莽阶段\u003c/h2\u003e\n\u003cp\u003e最开始的时候，我的做法非常朴素。我会直接在宿主机上安装服务，然后安装 Nginx 或者\nApache2（httpd）。比如用 Nginx 做反向代理，Nginx 这类 Web 服务器 的好处是配置文\n件可以拆开，每个站点一份，用域名来区分各个服务而不是端口号，看起来井井有条。那时\n候我觉得这套方案挺优雅的。\u003c/p\u003e\n\u003cp\u003e但随着服务慢慢变多，问题开始一点点浮现。有的服务需要特定版本的依赖，我很难在同一\n个环境安装不同版本的各种依赖库或者数据库。而有的会在系统里留下各种临时文件；配置\n文件散落在 \u003ccode\u003e/etc\u003c/code\u003e、数据放在 \u003ccode\u003e/srv\u003c/code\u003e，日志又在 \u003ccode\u003e/var\u003c/code\u003e。一开始你还能记得“这是哪个服\n务的”，时间一长就完全混在一起了。\u003c/p\u003e\n\u003cp\u003e这期间，当我想删除一个不用的服务时，我已经不敢确定自己删得干不干净。特别是有的服\n务提供“一键安装脚本”，安装的时候一时畅快，等到了你想摆脱它的时候，你会发现当初的\n脚本不知道做了什么改动，如何复原都毫无头绪。你很难确认还有没有残留的配置、后台进\n程，甚至不知道删错东西会不会影响别的服务。这种不确定感，本身就是一个风险，对于强\n迫症来说，每每想起简直是如鲠在喉。\u003c/p\u003e\n\u003ch2 id=\"容器化阶段\"\u003e容器化阶段\u003c/h2\u003e\n\u003cp\u003e后来我把几乎所有服务都迁移到了 Docker 里。而且，到现在越来越倾向于将所有能用的到\n的服务或者工具软件容器化，再去部署。有些不提供标准的容器化部署方案的，但又很想用\n的服务，我会直接自己进行容器化包装，形成自己的容器化的解决方案。\u003c/p\u003e\n\u003cp\u003e真正改变体验的不是 Docker 本身，而是 \u003cstrong\u003eDocker Compose\u003c/strong\u003e。容器这东西，本质上就不\n应该被当成“长期维护对象”，它随时都可以被删掉、重建，你需要意识到这个。早期的时\n候，我觉得 Docker Compose 配置文件麻烦，为什么不用命令直接创建一个容器，然后让这\n个容器长期运行。后来等我要更新，或者这个服务依赖多个底层的其他容器的时候，问题就\n逐渐浮现出来。如果当初将对服务的部署写下来，写到一个配置文件中，然后每次就执行这\n个配置文件就能完美复现原先的部署，不需要自己额外配置什么 Bridge 网络，或者什么\nhostname，亦或者是重启策略。这个节省了大量的时间和精力，并且配置的时候出错的可能\n性，也大大降低了。\u003c/p\u003e","title":"个人 VPS 自部署的长期主义：干净与安全"},{"content":"缘起：偶遇 GnuPG（GPG） 我在 2021 年 4 月左右第一次接触 GPG。那时我还只是个对计算机世界充满好奇的学生， 对密码学的理解也仅停留在“密码就是加密字符串”这样的表层概念。但当我第一次读到非对 称加密的原理——一个公钥可以让所有人加密信息，而只有私钥拥有者才能解密——我被深 深震撼了。这种“数学意义上的信任”让我着迷。它优雅、严谨、不可逆。那一刻，我意识 到，这种技术的美感并不亚于任何艺术创作。于是我开始学习 GPG 命令行的各种操作，从 密钥生成到签名验证，反复实验，琢磨其中的逻辑。\n但与此同时，我也发现了问题：**命令行的门槛实在太高。**特别是有的时候，想快点做一 些简单操作却要输入一串命令，让我感觉“不爽”。对非技术用户来说，GPG 的命令行是一片 晦涩的荒原，几乎挡住了所有想尝试的人。我开始想，如果能为 GPG 做一个简单易用的图 形界面，让它像一台看得见摸得着的“密码机机”那样可视化与便捷；如果每个人都可以简单 地拥有者与一台耐用且小巧的“密码机”，也许会有更多人愿意尝试。这个想法当时看起来微 不足道，但正是它，开启了我后来整整数年的技术旅程。\n“密码机”让人立刻联想到某种坚固、可靠的小设备，就像早期的机械密码机（如 Enigma）那样充满仪式感。它承载的不仅是技术，更是一种对安全的信念。这种机器功能 不求繁多，却必须精准可靠。用户按下一个按钮，期望它执行的动作必须**可预期且安全 **。这种对“可控性”的追求，就是“密码机”的灵魂。\n我开始寻找现成的解决方案。那个时候已经有一些 GUI 工具，比如 Kleopatra。但它们的 很多都界面复杂、功能太多，甚至还引入了 X.509 证书体系，这在当时一下子劝退了我。 我希望的是一个更轻、更纯粹的东西。后来我发现了一个叫 gpg4usb 的项目，它非常接近我理 想中的形态——轻量、可携带、跨平台。然而遗憾的是，它早已停止更新，只支持 GPG 1.4 版本。那意味着它与现代系统的兼容性问题几乎无法回避，也无法支持子密钥机制。\n我开始琢磨：**能不能在它的基础上重写一部分，让它重新焕发生命？**在 5 月份左右， 我花了大约半个月的时间，在宿舍里读它的源码，摸索 GPG 的接口调用方式，研究 Qt 框 架与跨平台兼容。那是一个孤独却极其充实的过程。每当我让一个新功能成功运行时，心里 的成就感都难以言喻。当我终于让它在 GPG2 环境下运行起来，并修复了不少原有的 Bug 后，我决定给这个焕然一新的项目一个新名 字：GpgFrontend——GPG 的图形前端，也是我人 生中的第一个完整开源项目。\n亮相：从宿舍代码到 GitHub 上的光点 我将第一个版本上传到 GitHub 后，并没有期待太多关注。毕竟这是个极小众的领域，GPG 本身就不是大众常用的东西。但几天后，我收到了第一个 issue。那是一位用户，提到程序 在某个操作系统上无法运行。他不仅提供了日志，还提出了修改建议。那一刻，我第一次感 受到某种力量，愿意继续为这一个项目投入时间。\n随后，越来越多的人开始使用 GpgFrontend。他们提出问题、建议、甚至翻译。每一个反馈 都让我意识到，这个项目不再只是我个人的“技术玩具”，而是真正有用的工具。从那以后， 我对“开源”这个概念的理解有了根本改变：它不只是代码共享，更是一个不断反馈、持续演 化的生态。\n在后续的 2-3 年，我逐渐明白，GpgFrontend 不可能也不需要成为“大而全”的工具。它的 使命很清晰：帮助用户更方便地执行加密、解密、签名和验证操作。它的价值，不在于 功能数量，而在于易用性和可靠性。最终到现在，我清晰地把它定义成一个“轻量级、安全 的密码机”，就像我当初刚刚接触 GPG 的时候渴望得的一样。\n对熟悉命令行的老用户，它是一个节省时间的辅助工具；对初学者，它是一座通向密码世界 的桥梁。所以在 UI 设计上，我尽量保持简洁，让所有核心操作一目了然；在底层实现上， 我让每个动作都直接对应 GPG 原生命令，确保稳定与兼容。后来我意识到，这种“**小而专 **”的定位，反而成了 GpgFrontend 最独特的竞争力。当别的项目追求“更多功能”时，我选 择让它保持“更纯粹的专注”。\n演进：把项目当成技术的试验田 随着版本的推进，GpgFrontend 成了我学习新技术的练兵场。我不断尝试把在工作和学习中 掌握的新概念融入项目中。我研究 Chromium 的多线程架构，把线程调度与任务隔离机制引 入 GpgFrontend，使其在执行加密时界面不再卡顿，而且尽可能减少竞态和一些多线程经常 遇到的 BUG；我为它设计了插件系统和 SDK 接口，让第三方能在运行时动态扩展功能；我 实现了优化了构建脚本，引入了 CI/CD，让不同平台的打包流程更自动化、更可靠。另外， 我也通过这个项目的跨平台的支持，学习了 Windows、macOS、Linux 下的各种实现差异， 了解了在不同的操作系统和发布平台下的打包流程。\n这些尝试和实践不仅提升了工具本身的性能，也让我学到了真正的工程经验——如何设计可维 护的架构，如何兼顾稳定性与创新，如何在不破坏旧功能的前提下进行重构。GpgFrontend 从一个简单的实验项目，逐渐成长为一个结构合理、稳定可靠的开源工具。\n在我学习网络安全课程（Netzwerksicherheit）的那段时间，教授讲到数据安全与密钥管理 的重要性。我突然意识到，自己在项目早期的一些实现方式存在隐患——比如密钥派生算法过 于简单、存储缺乏轮转机制、部分内存未加保护。于是我开始进行一次全面的安全重构。\n我在2.1.9版本中 加入了安全等级、启动自检、AES-GCM 加密模式、密钥轮转机制与安全内存。 同时，我重新设计了程序的数据结构，使得任何临时的机密信息在内存中都能在使用后尽可 能安全地擦除。这些看似“幕后”的改动，实际上显著提升了 GpgFrontend 的安全性与可靠 性。所以，正如我一直坚信的，**安全不是一个功能，而是一种态度。**只有把安全内化到 设计阶段，工具才能经得起时间与信任的考验。\n在维护项目的这些年里，我逐渐感受到一种超越技术的满足感。虽然 GpgFrontend 不是一 个“热门项目”，但它确实帮助到了一些真实的用户。有人用它验证软件包签名，有人用它在 工作中快速处理安全通信，还有人告诉我，它是他学习 GPG 的第一扇门。我不记录用户， 也专门汇总各个平台的下载量。说真的，我不知道到底有多少人在使用它。但我喜欢这种 “未知”——它让我觉得 GpgFrontend 是一份纯粹的贡献。具象化一点，它就是一个“小密码 机”，唾手可得，可以在任何地方伴随人，默默服务着那些需要的场景。\n展望：稳定、专注与长远主义 未来几年，我不会让 GpgFrontend 发生剧烈的变化。更新会持续进行，但主要集中在使用 体验优化、稳定性提升、bug 修复和轻微优化上。我更倾向于保持它的轻盈与可控。在软件 行业充满“快节奏迭代”的今天，我反而希望它能成为一个例外——一个稳重、可靠、值得信 赖的工具。或许它永远不会流行，也不会登上技术新闻的头条；但只要它能持续帮助那些 真正需要它的人，我就觉得这一切都值得。\n近年来，Rust 语言在安全领域的崛起让我十分关注。我研究了几个基于 Rust 实现的 GPG 替代库，如 RPGP、Sequoia 等。它们拥有更好的内存安全性与并发管理机制，并且支持最 新的 OpenPGP 的一些特性，这无疑是未来的方向。但与此同时，我也必须面对现实：Rust 生态尚不完全成熟，与现有 GPG 生态的兼容性仍待验证。\n因此，我计划在未来建立一个独立分支，以实验性方式接入 Rust 版本的加密库，并通 过新的密钥数据库层进行封装。这种“渐进式”迁移策略能让我在不破坏主线稳定性的情况 下，探索新技术的可行性。我始终认为，稳健的更新比盲目的创新更重要。毕竟，一个安 全工具的价值，不在于新潮，而在于信任。而这份信任的积累可能需要以 10 年为单位。\n","permalink":"https://blog.bktus.com/archives/5l9c2j/","summary":"\u003ch2 id=\"缘起偶遇-gnupggpg\"\u003e缘起：偶遇 GnuPG（GPG）\u003c/h2\u003e\n\u003cp\u003e我在 2021 年 4 月左右第一次接触 GPG。那时我还只是个对计算机世界充满好奇的学生，\n对密码学的理解也仅停留在“密码就是加密字符串”这样的表层概念。但当我第一次读到非对\n称加密的原理——\u003cstrong\u003e一个公钥可以让所有人加密信息，而只有私钥拥有者才能解密\u003c/strong\u003e——我被深\n深震撼了。这种“数学意义上的信任”让我着迷。它优雅、严谨、不可逆。那一刻，我意识\n到，这种技术的美感并不亚于任何艺术创作。于是我开始学习 GPG 命令行的各种操作，从\n密钥生成到签名验证，反复实验，琢磨其中的逻辑。\u003c/p\u003e\n\u003cp\u003e但与此同时，我也发现了问题：**命令行的门槛实在太高。**特别是有的时候，想快点做一\n些简单操作却要输入一串命令，让我感觉“不爽”。对非技术用户来说，GPG 的命令行是一片\n晦涩的荒原，几乎挡住了所有想尝试的人。我开始想，如果能为 GPG 做一个简单易用的图\n形界面，让它像一台看得见摸得着的“密码机机”那样可视化与便捷；如果每个人都可以简单\n地拥有者与一台耐用且小巧的“密码机”，也许会有更多人愿意尝试。这个想法当时看起来微\n不足道，但正是它，开启了我后来整整数年的技术旅程。\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e“密码机”让人立刻联想到某种坚固、可靠的小设备，就像早期的机械密码机（如\nEnigma）那样充满仪式感。它承载的不仅是技术，更是一种对安全的信念。这种机器功能\n不求繁多，却必须精准可靠。用户按下一个按钮，期望它执行的动作必须**可预期且安全\n**。这种对“可控性”的追求，就是“密码机”的灵魂。\u003c/p\u003e\u003c/blockquote\u003e\n\u003cp\u003e我开始寻找现成的解决方案。那个时候已经有一些 GUI 工具，比如 Kleopatra。但它们的\n很多都界面复杂、功能太多，甚至还引入了 X.509 证书体系，这在当时一下子劝退了我。\n我希望的是一个更轻、更纯粹的东西。后来我发现了一个叫\n\u003ca href=\"https://git.bktus.com/gpgfrontend/gpg4usb/\"\u003e\u003cstrong\u003egpg4usb\u003c/strong\u003e\u003c/a\u003e 的项目，它非常接近我理\n想中的形态——轻量、可携带、跨平台。然而遗憾的是，它早已停止更新，只支持 GPG 1.4\n版本。那意味着它与现代系统的兼容性问题几乎无法回避，也无法支持子密钥机制。\u003c/p\u003e\n\u003cp\u003e我开始琢磨：**能不能在它的基础上重写一部分，让它重新焕发生命？**在 5 月份左右，\n我花了大约半个月的时间，在宿舍里读它的源码，摸索 GPG 的接口调用方式，研究 Qt 框\n架与跨平台兼容。那是一个孤独却极其充实的过程。每当我让一个新功能成功运行时，心里\n的成就感都难以言喻。当我终于让它在 GPG2 环境下运行起来，并修复了不少原有的 Bug\n后，我决定给这个焕然一新的项目一个新名\n字：\u003ca href=\"https://gpgfrontend.bktus.com/\"\u003e\u003cstrong\u003eGpgFrontend\u003c/strong\u003e\u003c/a\u003e——GPG 的图形前端，也是我人\n生中的第一个完整开源项目。\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"GpgFrontend\" loading=\"lazy\" src=\"https://image.cdn.bktus.com/i/2025/11/13/6c111b21bb6d134d72fbf356f5f0d8c520115f13-1.webp\"\u003e\u003c/p\u003e\n\u003ch2 id=\"亮相从宿舍代码到-github-上的光点\"\u003e亮相：从宿舍代码到 GitHub 上的光点\u003c/h2\u003e\n\u003cp\u003e我将第一个版本上传到 GitHub 后，并没有期待太多关注。毕竟这是个极小众的领域，GPG\n本身就不是大众常用的东西。但几天后，我收到了第一个 issue。那是一位用户，提到程序\n在某个操作系统上无法运行。他不仅提供了日志，还提出了修改建议。那一刻，我第一次感\n受到某种力量，愿意继续为这一个项目投入时间。\u003c/p\u003e","title":"GpgFrontend的前世今生：我与一个开源加密工具的成长之路"},{"content":"原文经过 AI 润色，非本人文风。\n在全国范围（如广州、深圳、上海、北京）部署大量客户端的配置下发服务中，面对节假 日、活动上线等高并发场景，传统的单地数据中心架构难以支撑。本文分享我当属在“配置 下发服务”层面进行的多地部署、数据层优化及缓存机制升级，阐述其技术背景、方案设 计、落地实践与收获。\n背景 我服务的客户端分布在广州、深圳、上海、北京等全国多个城市，通常需要根据运营策略获 取各种动态或静态配置，比如节日活动、功能开关、故障临时屏蔽等。这些配置由服务端统 一下发，客户端根据自身属性（地域、版本等）拉取对应内容。\n随着客户端规模扩大，特别是在活动上线、客户端批量启动的场景中，配置拉取请求会在短 时间内剧增，导致服务端QPS暴涨，严重时甚至会拖垮主服务节点。我虽在容器层实现了全 国多地的 Kubernetes Workload 部署，但数据层（包括数据库和 Redis）仍集中在广州。 所有请求即便从北京或成都的容器发出，最终还是要跨地域访问广州的数据中心，延迟高、 压力集中、体验差。\n此外，虽然服务中使用了容器内缓存机制，但考虑到容器本身应保持无状态，其运行内存不 适合长时间、海量地持有配置缓存。一旦配置数据规模扩大至几百兆甚至上 GB 级别，运维 和资源成本都急剧上升。\n为了解决这些问题，我决定从数据层和缓存层入手，进行更彻底的多地部署改造。\n设计思路 基于上述挑战，我设计的方案主要包括：\n主数据库实例（写 + 读）部署在广州，运营人员在这个实例上进行配置管理、写入 操作。 各地部署 只读副本（Read‑Only）数据库实例，例如上海、北京、成都等地。各地 容器连接对应地区的只读实例以拉取配置。 Redis 亦做多地部署：每个地域有自己的 Redis 实例，容器拉取缓存优先从本地 Redis；若缓存未命中，再访问本地只读数据库或主数据库。 在配置下发平台中，对不同 Kubernetes Workload 打标签（如地域、版本、标签等）， 客户端根据标签拉取对应配置。运营人员仍在统一平台管理配置。配置平台写入主数据 库，然后同步至只读副本，各地容器读取对应地域的副本。 架构优势 读请求分散到各地只读副本，减轻主数据库压力。 客户端拉配置网络路径变短、延迟下降、体验提升。 配置写入集中在主实例，确保配置管理统一、操作简便。 架构符合“写少、读多”的典型场景：操作多为配置读取，写入（新增活动、屏蔽功能等） 相对少。 实施 根据上面的设计，我继续保留广州作为主写中心，同时在全国多个区域（如北京、上海、成 都）部署只读副本，配合就近的 Redis 实例，实现配置服务的分布式读请求调度。主数据 库仍由运营人员维护和写入更新，通过异步同步机制，将数据分发到各地只读副本。各地的 容器服务则连接本地副本，从而避免跨地域拉取配置数据。\nRedis 也采用相似策略，在每个部署地域都设立实例，服务端优先从 Redis 缓存中获取所 需配置，未命中时才回源数据库。这样既减轻数据库压力，也提升了配置拉取效率。为了让 客户端能快速命中就近服务节点，我基于 DNS 做了地域级别的接入调度，同时结合 Kubernetes Workload 的标签机制，自动下发对应配置。所有运营配置仍在部署在广州的 “统一平台”进行集中管理，通过主数据库推送同步，容器按标签识别自身身份，拉取所需数 据。\n落地效果 经过部署实践，这套方案在实际运行中带来了显著改善。各地客户端的配置拉取延迟明显降 低，主数据库的读请求压力几乎被完全转移到了只读副本上。整个系统在活动流量高峰时表 现稳定，再无集中节点被打爆的风险。\n几点经验 虽然配置更新不频繁，但读请求量大；因此采用“主写/多地只读”模型非常匹配。 多地部署数据库及缓存时，必须评估网络延迟、数据同步延迟与一致性需求：对配置下发 场景，最终一致性即可，无需强同步。 缓存层（Redis）地域就近部署效果更好，尽可能避免网络访问延迟拖累。 K8s Workload 与标签管理是核心：标签化管理便于灵活控制不同客户端区域／版本对应 的配置。 地域入口（DNS 或其他流量导向机制）必须支持解析到就近入口，以保证客户端走最近节 点。 容器无状态设计仍然要坚持：缓存尽量外部化，避免容器失效导致缓存丢失或资源浪费。 写 + 同步机制需要监控：虽然配置更新少，但副本同步延迟还是要监控，避免某地配置 未及时生效。 安全权限划分要明确：主实例写权限、只读副本只读账户、运营平台权限划分。 ","permalink":"https://blog.bktus.com/archives/0ee6o6/","summary":"\u003cp\u003e\u003cstrong\u003e原文经过 AI 润色，非本人文风。\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e在全国范围（如广州、深圳、上海、北京）部署大量客户端的配置下发服务中，面对节假\n日、活动上线等高并发场景，传统的单地数据中心架构难以支撑。本文分享我当属在“配置\n下发服务”层面进行的多地部署、数据层优化及缓存机制升级，阐述其技术背景、方案设\n计、落地实践与收获。\u003c/p\u003e\n\u003ch2 id=\"背景\"\u003e背景\u003c/h2\u003e\n\u003cp\u003e我服务的客户端分布在广州、深圳、上海、北京等全国多个城市，通常需要根据运营策略获\n取各种动态或静态配置，比如节日活动、功能开关、故障临时屏蔽等。这些配置由服务端统\n一下发，客户端根据自身属性（地域、版本等）拉取对应内容。\u003c/p\u003e\n\u003cp\u003e随着客户端规模扩大，特别是在活动上线、客户端批量启动的场景中，配置拉取请求会在短\n时间内剧增，导致服务端QPS暴涨，严重时甚至会拖垮主服务节点。我虽在容器层实现了全\n国多地的 Kubernetes Workload 部署，但数据层（包括数据库和 Redis）仍集中在广州。\n所有请求即便从北京或成都的容器发出，最终还是要跨地域访问广州的数据中心，延迟高、\n压力集中、体验差。\u003c/p\u003e\n\u003cp\u003e此外，虽然服务中使用了容器内缓存机制，但考虑到容器本身应保持无状态，其运行内存不\n适合长时间、海量地持有配置缓存。一旦配置数据规模扩大至几百兆甚至上 GB 级别，运维\n和资源成本都急剧上升。\u003c/p\u003e\n\u003cp\u003e为了解决这些问题，我决定从数据层和缓存层入手，进行更彻底的多地部署改造。\u003c/p\u003e\n\u003ch2 id=\"设计思路\"\u003e设计思路\u003c/h2\u003e\n\u003cp\u003e基于上述挑战，我设计的方案主要包括：\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\u003cstrong\u003e主数据库实例（写 + 读）部署在广州\u003c/strong\u003e，运营人员在这个实例上进行配置管理、写入\n操作。\u003c/li\u003e\n\u003cli\u003e各地部署 \u003cstrong\u003e只读副本（Read‑Only）数据库实例\u003c/strong\u003e，例如上海、北京、成都等地。各地\n容器连接对应地区的只读实例以拉取配置。\u003c/li\u003e\n\u003cli\u003eRedis 亦做多地部署：每个地域有自己的 Redis 实例，容器拉取缓存优先从本地\nRedis；若缓存未命中，再访问本地只读数据库或主数据库。\u003c/li\u003e\n\u003cli\u003e在配置下发平台中，对不同 Kubernetes Workload 打标签（如地域、版本、标签等），\n客户端根据标签拉取对应配置。运营人员仍在统一平台管理配置。配置平台写入主数据\n库，然后同步至只读副本，各地容器读取对应地域的副本。\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch3 id=\"架构优势\"\u003e架构优势\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e读请求分散到各地只读副本，减轻主数据库压力。\u003c/li\u003e\n\u003cli\u003e客户端拉配置网络路径变短、延迟下降、体验提升。\u003c/li\u003e\n\u003cli\u003e配置写入集中在主实例，确保配置管理统一、操作简便。\u003c/li\u003e\n\u003cli\u003e架构符合“写少、读多”的典型场景：操作多为配置读取，写入（新增活动、屏蔽功能等）\n相对少。\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"实施\"\u003e实施\u003c/h3\u003e\n\u003cp\u003e根据上面的设计，我继续保留广州作为主写中心，同时在全国多个区域（如北京、上海、成\n都）部署只读副本，配合就近的 Redis 实例，实现配置服务的分布式读请求调度。主数据\n库仍由运营人员维护和写入更新，通过异步同步机制，将数据分发到各地只读副本。各地的\n容器服务则连接本地副本，从而避免跨地域拉取配置数据。\u003c/p\u003e\n\u003cp\u003eRedis 也采用相似策略，在每个部署地域都设立实例，服务端优先从 Redis 缓存中获取所\n需配置，未命中时才回源数据库。这样既减轻数据库压力，也提升了配置拉取效率。为了让\n客户端能快速命中就近服务节点，我基于 DNS 做了地域级别的接入调度，同时结合\nKubernetes Workload 的标签机制，自动下发对应配置。所有运营配置仍在部署在广州的\n“统一平台”进行集中管理，通过主数据库推送同步，容器按标签识别自身身份，拉取所需数\n据。\u003c/p\u003e","title":"二零二三年四月第二周技术周报"},{"content":"很多时候，我在做事前都会去考虑很久利弊得失。就比如：我去做了这件事会遇到哪些情 况？这些情况下我该怎么办？那些情况下会不如人意？我用的这些个方法真的好吗？等等。 不得不说，这伴随了我的很多的决策，特别是在一些比较大的决策下，这些事先的思考真的 有用处。所以，我在做很多事情之前，都会去想一下这些问题。如果我的结论是：这件事情 目前条件不成熟，或者这件事情很麻烦，亦或者这件事情目前前置条件没达到。那么我就不 会去做这件事情，以免投入了很多，却收效甚微。但是，最近我就发现，我这样将这个方法 扩大化并不好。因为很多事情，对我来说是新的，在我现有的经历和经验下，对这些事情的 认知还处于比较幼稚的阶段。这种情况下，我无法正确地去推理上面说的这些问题，从而去 得出正确的结论。\n具体来说，利用常识是真的能枚举一些可以预见的情况的，理论上，这些情况会发生。但 是，实际上，一件事情，或者一个项目，这些理论上会发生的事情都有它们会发生的概率。 这些概率又由许许多多的前置变量决定，如果对这件事情的认知不足，很难说对这些变量抓 大放小，找出主要。这样就容易放大一些变量的影响，最终导致预测的事情其实并不会发 生。另外，在很多情况下，我也会对一件事物的性质和反应对出某种推断，特别是对一类人 的推断，比如说我觉得某些人不友善，某些人友善这样。但实际上，在你真正地接触这些人 后，你才能知道他们是否真如你想得那样友善。或者说，他们对于你和他们接触的真正反 应，只有你去接触后才知道。这些事情是无法进行预先推断的，除非建立在你经常和别人打 交道的基础上。所以，在很多时候，我对于某些事物，某些人群的判断，是先入为主的，是 从别人那里听来的，模糊的，加入了自己想象的一种判断。大多数时候，这些判断并不准 确。如果我想让这些判断准确，那么我就要先行动起来，去做这些事情。\n先行动，也就是说在对事情有个轮廓，模糊的前提下，优先行动起来，放低姿态去尝试，然 后再根据反馈的结果看看是否可行。如果反馈良好，完全可以继续行动。如果反馈不好，也 不必立即收手，可以分析一下原因，或者拿着这些具体的案例问问其他过来人。不论哪一种 情况，都比先不动，然后想一大堆，这样如何那样如何，来得具体、实在与真切。很多我们 认为实践中会遇到的比较大的问题，往往会在真正的实践中找到思路。反之，在实践中真正 遇到的大的问题，在想法中往往预料不到。所以，先激活自己，去迈出第一步，在对事情认 知比较模糊的情况下，比什么都重要。\n先推理，我认为是建立在对事情已经有一个比较成熟的把握的情况下，特别是在这个事情所 述的领域，我并不是一个纯粹的新手。而推理的准确度，建立在我在这个领域的学习与实践 的成果的叠加。也就是说，推理需要真真切切的样本，这些样本需要自己得到，而不是纯粹 从别人那里“听来”，更重要地是自己通过实践“寻来”。这种情况下，有了这些样本作为基 础，对事情的推理预测的把握才能更大，对于事物的认知才能更加准确。这样，后续就能不 断靠推理来规避一些在这该领域下的陷阱，从而让自己能够跑得更快更稳。\n如果综合上面的论述，我可以发现，总是应该先去行动，因为推理没有行动的结果作为支 撑，是摇摇欲坠的。先行动而非先推理，确实是可以作为我在对于一些自己并不熟悉的领域 的一种行事的习惯，从而避免先入为主，然后想太多，最后什么都没做，白白错过了可能的 机遇。\n","permalink":"https://blog.bktus.com/archives/0jriuh/","summary":"\u003cp\u003e很多时候，我在做事前都会去考虑很久利弊得失。就比如：我去做了这件事会遇到哪些情\n况？这些情况下我该怎么办？那些情况下会不如人意？我用的这些个方法真的好吗？等等。\n不得不说，这伴随了我的很多的决策，特别是在一些比较大的决策下，这些事先的思考真的\n有用处。所以，我在做很多事情之前，都会去想一下这些问题。如果我的结论是：这件事情\n目前条件不成熟，或者这件事情很麻烦，亦或者这件事情目前前置条件没达到。那么我就不\n会去做这件事情，以免投入了很多，却收效甚微。但是，最近我就发现，我这样将这个方法\n扩大化并不好。因为很多事情，对我来说是新的，在我现有的经历和经验下，对这些事情的\n认知还处于比较幼稚的阶段。这种情况下，我无法正确地去推理上面说的这些问题，从而去\n得出正确的结论。\u003c/p\u003e\n\u003cp\u003e具体来说，利用常识是真的能枚举一些可以预见的情况的，理论上，这些情况会发生。但\n是，实际上，一件事情，或者一个项目，这些理论上会发生的事情都有它们会发生的概率。\n这些概率又由许许多多的前置变量决定，如果对这件事情的认知不足，很难说对这些变量抓\n大放小，找出主要。这样就容易放大一些变量的影响，最终导致预测的事情其实并不会发\n生。另外，在很多情况下，我也会对一件事物的性质和反应对出某种推断，特别是对一类人\n的推断，比如说我觉得某些人不友善，某些人友善这样。但实际上，在你真正地接触这些人\n后，你才能知道他们是否真如你想得那样友善。或者说，他们对于你和他们接触的真正反\n应，只有你去接触后才知道。这些事情是无法进行预先推断的，除非建立在你经常和别人打\n交道的基础上。所以，在很多时候，我对于某些事物，某些人群的判断，是先入为主的，是\n从别人那里听来的，模糊的，加入了自己想象的一种判断。大多数时候，这些判断并不准\n确。如果我想让这些判断准确，那么我就要先行动起来，去做这些事情。\u003c/p\u003e\n\u003cp\u003e先行动，也就是说在对事情有个轮廓，模糊的前提下，优先行动起来，放低姿态去尝试，然\n后再根据反馈的结果看看是否可行。如果反馈良好，完全可以继续行动。如果反馈不好，也\n不必立即收手，可以分析一下原因，或者拿着这些具体的案例问问其他过来人。不论哪一种\n情况，都比先不动，然后想一大堆，这样如何那样如何，来得具体、实在与真切。很多我们\n认为实践中会遇到的比较大的问题，往往会在真正的实践中找到思路。反之，在实践中真正\n遇到的大的问题，在想法中往往预料不到。所以，先激活自己，去迈出第一步，在对事情认\n知比较模糊的情况下，比什么都重要。\u003c/p\u003e\n\u003cp\u003e先推理，我认为是建立在对事情已经有一个比较成熟的把握的情况下，特别是在这个事情所\n述的领域，我并不是一个纯粹的新手。而推理的准确度，建立在我在这个领域的学习与实践\n的成果的叠加。也就是说，推理需要真真切切的样本，这些样本需要自己得到，而不是纯粹\n从别人那里“听来”，更重要地是自己通过实践“寻来”。这种情况下，有了这些样本作为基\n础，对事情的推理预测的把握才能更大，对于事物的认知才能更加准确。这样，后续就能不\n断靠推理来规避一些在这该领域下的陷阱，从而让自己能够跑得更快更稳。\u003c/p\u003e\n\u003cp\u003e如果综合上面的论述，我可以发现，总是应该先去行动，因为推理没有行动的结果作为支\n撑，是摇摇欲坠的。先行动而非先推理，确实是可以作为我在对于一些自己并不熟悉的领域\n的一种行事的习惯，从而避免先入为主，然后想太多，最后什么都没做，白白错过了可能的\n机遇。\u003c/p\u003e","title":"先行动而非先推理"},{"content":"摘要 本文系统记录了我如何将树莓派 5 打造成主线 Linux 内核（Vanilla Kernel）的极简开发 与测试平台——包括仓库极简裁剪思路、定制化内核配置、交叉编译与自动部署脚 本、overlay 移除技巧，以及长期与主线社区同步维护的实战心得。适合关注 Linux 主 线、ARM64 适配、内核开发、Upstream 贡献或喜欢折腾内核的极客们参考和实践。\n背景与动机 最近一段时间，我在深入研究 Linux Kernel 的理论和架构，准备正式切入操作系统内核领 域。因此，特意购买了树莓派 5 作为开发板，用于学习和实践内核开发。然而刚上手就发 现，目前树莓派 5 并不能直接跑主线 Linux 内核，只能依赖树莓派团队维护的内核仓 库。当时最新的分支是 rpi-6.14.y（现在是 rpi-6.16.y），仓库的维护方式是“动态拉取上游主线代码+rebase+大量自定义补丁”，涉及 上万行的修改和历史反复变动。\n这些定制补丁对于官方内核在树莓派上稳定运行、兼容所有功能确实非常重要，但对我这种 纯粹想研究主线内核特性、关注内核本身的开发者来说，这些补丁反而变成了负担：\n主线同步困难，merge 经常出冲突， 版本历史频繁重写，难以回溯和比较， 验证主线新特性或做反馈变得不直观。 我对比了 x86 开发板，发现它们大多能直接运行主线内核，但价格昂贵，性价比远不如树 莓派 5。因此，我开始思考，如何让树莓派 5 也能顺畅运行主线内核，真正实现随时体验 和测试内核最新特性，并为 ARM64 平台反馈问题、贡献补丁提供便利。我的目标很明确： 用最少的补丁、最贴近 upstream 的方式，让树莓派 5 成为个人主线内核实验和学习的理 想 ARM64 平台。\n仓库结构与修改范围 我的做法十分简单却又直奔核心——只保留最必要的驱动与补丁，其余全部紧跟主线。仅对 drivers/ 目录做补丁，聚焦核心硬件支持，主要包括：\nSD 卡驱动：保证树莓派能正常引导，内核能加载根文件系统。虽然启动固件自带部 分初始化功能，但主线内核仍需驱动挂载根文件系统。\n有线网卡（Ethernet）：方便通过 SSH 实现远程登录、下载新内核镜像、远程开发 和调试，是内核开发过程中最重要的 IO 通道之一。\nUART 串口：作为内核调试和 early print 的输出窗口，可以直接观察内核日志，判 断启动和运行状态。\nUSB 控制器：用于外部设备挂载或偶尔传输离线数据，兼容更多实验场景。\n基本 GPIO、电源管理等与 RPi5 硬件紧密相关的部分。\n剔除官方内核中与 Wi-Fi、蓝牙、GPU（VideoCore）、HDMI、音视频加速等非内核开 发必需的子系统支持，这些对内核本身的研究和测试意义不大。\n维护 arch/arm64/boot/dts/ 下的树莓派 5 DTS（Device Tree）配置\n重点保证启动时能正确识别 RPi5 的各项硬件资源、CPU 配置和外设映射，确保主线内核 能顺利运行。 其余全部与 Linus 主线保持 1:1 同步\n除上述极少量补丁外，仓库其它部分完全保持与 官方主线仓 库 一致。 定期（如每月或每两周）从主线拉取最新代码，只需应用极少量补丁并解决小范围冲突。 通过以上做法，仓库极简、易维护，也为后续的配置精简与快速主线跟进打下了基础。\n定制化内核配置示例 在实际部署过程中，我为树莓派 5 主线内核准备了一套自定义 .config 配置文件，目标是 在最大限度裁剪无关功能、最小化编译体积和时间的基础上，满足我个人的开发、测试 与服务需求。这个过程也伴随着不断试错和优化。\n第一阶段：极致裁剪，只留核心 最初版本的 .config 文件，几乎关掉了所有我认为不需要的驱动和功能模块，包括：\n所有非板载网卡驱动（只保留树莓派 5 板载 Cadence 网卡驱动） 虚拟化支持（KVM 等） 图形子系统、声卡、HDMI、多媒体加速等 所有网络文件系统 除了 ext4 和 SD 卡（mmcblk）以外的文件系统 与上述模块相关的部分加密算法和内核机制 甚至关闭了 cgroup 经过几轮调试和实测，虽然最终配置未必是理论上“最精简”，但已经让内核编译时间大幅下 降，同时可以稳定启动和完成我需要的基础功能。\n第二阶段：实用扩展，支持 Docker 负载与日常服务 随着内核实验的深入，我意识到：\n需要借助 Docker 跑一些实际负载以验证 kernel 的稳定性和兼容性； 需要支持局域网文件服务器（NAS）等日常场景； 还想用另一个树莓派长期运行自编译内核，部署不那么关键的服务。 因此，新的配置文件又逐步扩展了部分功能，如：\ncgroup、iptables、fuse、overlayfs、wireguard、macvlan、NFS、Samba 等常见内 核模块（用于支持 Docker 及文件服务） 大多数新增特性编译为模块（module），按需加载，进一步优化启动和运行效率 这一步虽然让编译时间略有增加，但相比主线默认配置仍非常精简，也更贴合我的实际使用 和验证需求。\n配置文件仓库托管在： https://git.bktus.com/Linux/kernel-configs/\n部分关键配置文件及时间线：\neric-rpi-v1_defconfig eric-vanilla-c-6.13_defconfig eric-vanilla-c-6.14_defconfig eric-vanilla-c-6.15_defconfig eric-vanilla-v1_defconfig eric-vanilla-v2_defconfig eric-vanilla-v3_defconfig 可以通过这些配置文件，看到我从极致裁剪到逐步扩展的调试和优化历程。\n优势与应用场景 贴近主线，快速体验和验证新特性 保持极简补丁的最大好处在于：你可以几乎“零成本”地将 linux-next 分支的最新补丁，乃 至 Linus Merge Window 期间的新功能直接拉到 RPi5 上跑起来。无论是新调度器、文件系 统新特性、虚拟化和安全子系统增强（如 KVM、eBPF、LSM），还是 Zstd 压缩、新的内存 管理机制，只要主线有变化，你都能第一时间在真机上测试、验证其兼容性和性能表现， 并及时为上游 ARM64 社区反馈问题和优化建议。\n更重要的是，通过运行“香草”内核（Vanilla Kernel），你能最大程度还原和理解主线内 核本身的实现逻辑，避免树莓派官方补丁引入的“副作用”，也不用担心误把 vendor patch 的问题当成主线 bug。\n采用 merge 而非 rebase 的方式维护补丁，仓库历史更加清晰，主线与本地差异一目了 然，每次合并主线几乎“秒通过”，让你能始终跟进内核最新开发进度——比如随时 pull linux-next 测试，或者 benchmark 内核新功能，体验感和参与感都极强。\n这种方式对内核开发者、upstream 爱好者甚至“硬核”hacker 都非常有价值——你遇到的 bug、提交的 patch，就是主线环境下的真实问题，可以直接和内核社区无缝对接。\n维护与开发更高效 补丁极少、冲突极少：只聚焦 RPi5 必需硬件支持，每次上游合并只需关注 drivers/net/arm/…、drivers/usb/…、drivers/bus/… 和 arch/arm64/boot/dts/broadcom/… 这几处，冲突极有限，维护量低。 调试与回滚极其方便：如果主线引入了回归或异常，只需排查极少补丁即可回退或调 整，无需在成千上万行 vendor patch 里大海捞针。 便于贡献上游：你做的驱动补丁和 bug 修复非常“干净”，可直接整理成 patch，推 送到 kernel mailing list，方便社区 review，不会因为 vendor patch 干扰而被卡 住。 理想的开发、测试与学习平台 新特性探索与验证：RPi5 作为一台低成本、主流 ARM64 开发板，可以轻松测试 linux-next 或任意主线分支，为 upstream 社区贡献测试与反馈。 性能调优与驱动测试：可以做性能 benchmark、驱动优化、功耗实验等，评估主线新 功能在 ARM64 上的真实表现。 教学与自学环境：对于学习 Linux 驱动、设备树和 HAL 的同学来说，极简环境最大 限度减少“环境噪声”，专注理解主线驱动框架和硬件抽象。 官方仓库在驱动齐全性、厂商适配性方面占据绝对优势，适合需要“开箱即用、多媒体完整 体验”的用户；而我的主线仓库则专注于“紧贴上游、简化维护、便于开发”的需求。如果你 只是想用树莓派 5 看视频、玩游戏、运行 GUI 桌面，官方仓库或许更方便；但对于内核工 程师、开发者或热衷实验的同学，“轻量化主线仓库”则能让你更专注于内核本身。\n如何使用与贡献 理论上，树莓派 5 相比与树莓派 4 性能有了可观的提升，它自己来编译自己的内核是完全 可以的，体验上也不错（内存大于 4GB 的情况下）。但是如果你需要进行内核或者内核模 块开发，然后开启代码索引等能力的话，它的性能就不足了。\n我还尝试过给它加上官方的 SSD Kit（512GB），虽然解决了 I/O 方面的吞吐问题，但最终 还是遇到了 CPU 瓶颈，因为它只有 4 个 core！所以，如果你纯粹是需要一个可以跑的最 新内核，并不经常编译，你可以在树莓派上编译安装。但是需要进行更进一步的开发调试的 话，我还是强烈建议使用交叉编译，也就是在 PC 或者工作站（一般是 x86 平台）上编译 内核和内核模块等，然后再通过网络拷贝到树莓派上（也通过 SD 卡实现）。比如，我的工 作站有 12 个 core，跑编译时间缩短了好几倍（相对于 RPi5 的 4 个 core 来说）。所 以，下面我就拿我用的交叉编译环境来举例子。\n在此之前，你需要先安装 ARM64 的交叉编译环境。我建议安装 ccache 来进一步缩短重复 编译的时间，如果你不想用 ccache 的话，那请去掉后续章节给出的所有命令中关于 ccache 的部分。\nsudo apt install bc bison flex libssl-dev make libc6-dev libncurses5-dev sudo apt install crossbuild-essential-arm64 sudo apt install ccache 克隆仓库并检出最新分支 我仓库是在我自己私有 Git 上的，但你可以使用我在 GitHub 的镜像。我会定期追踪 linus 的主线仓库并更新 main 分支。\ngit clone https://git.bktus.com/Linux/kernel-configs.git git clone https://github.com/saturneric/linux (recommand) or https://git.bktus.com/linux/kernel.git cd linux git checkout main 注意：第一条命令是克隆我前面给出的内核配置文件仓 库，所以注意后续配置不要再用树莓 派团队提供的配置文件（启用了大量特性），这大概率会导致后续编译好的内核无法启 动。\n其中，https://github.com/saturneric/linux 这个地址是属于GitHub的，理论上速度最 快，所以没有什么特别的要求的话尽量从这里克 隆。https://git.bktus.com/linux/kernel.git 是我自己服务器托管的仓库，而上面提到 的GitHub的仓库是这个的镜像。\n进行简单配置 你需要在 kernel-configs 仓库选取一个适合当前版本的配置文件，重命名为.config， 然后拷贝到内核仓库的根目录下。\ncp ../kernel-configs/eric-vanilla-c-6.16_defconfig .config 你可以在我提供的配置基础上进一步裁减内核，或者增加功能。\nmake ARCH=arm64 CROSS_COMPILE=\u0026#34;ccache aarch64-linux-gnu-\u0026#34; menuconfig 编译 在准备好交叉编译环境直接编译。根据你的具体配置和机器性能，实际编译时间长短不一。 我自己的编译时间大概是 10 分钟左右。\nmake ARCH=arm64 CROSS_COMPILE=\u0026#34;ccache aarch64-linux-gnu-\u0026#34; -j$(nproc) 拷贝内核和内核模块到树莓派 你可以使用我提供的脚本，方便快速地把刚刚编译好的内核镜像与内核模块传输到树莓派的 默认用户的家目录下。强烈建议内核镜像与设备树不要和树莓派 5 的默认内核同名，使用 类似 kernel-vanilla.c.img和bcm2712d0-rpi-5-b.vanilla.c.dtb这样的名字，这样即 使出了什么问题，我们也可以通过配置文件快速切换回原来的可用内核或者设备树。另外， 脚本用到的 rsync 需确保免密 SSH，建议提前配置好公钥。\n#!/bin/bash # ====== 可配置参数（请根据实际情况修改） ====== # 本地存放内核编译产物的目录 LOCAL_KERNEL_ARTIFACTS=\u0026#34;./kernel-artifacts\u0026#34; # 目标树莓派的 SSH 连接信息（如 pi@192.168.1.100） REMOTE_HOST=\u0026#34;pi@192.168.1.100\u0026#34; # 树莓派上的目标存放路径（建议使用绝对路径，如 /home/pi/kernel） REMOTE_KERNEL_ARTIFACTS=\u0026#34;/home/pi/kernel\u0026#34; # 目标内核文件名（可自定义，默认 kernel_2712.img） KERNEL_IMG=\u0026#34;kernel-vanilla.c.img\u0026#34; # 设备树文件名（可自定义） DTB_FILE=\u0026#34;bcm2712d0-rpi-5-b.vanilla.c.dtb\u0026#34; DTB_SOURCE=\u0026#34;arch/arm64/boot/dts/broadcom/bcm2712d0-rpi-5-b.dtb\u0026#34; # ============================================== set -e echo \u0026#34;\u0026gt;\u0026gt;\u0026gt; 清理并准备本地目录 \u0026lt;\u0026lt;\u0026lt;\u0026#34; rm -rf \u0026#34;$LOCAL_KERNEL_ARTIFACTS\u0026#34; mkdir -p \u0026#34;$LOCAL_KERNEL_ARTIFACTS\u0026#34; echo \u0026#34;\u0026gt;\u0026gt;\u0026gt; 安装内核模块到本地输出目录 \u0026lt;\u0026lt;\u0026lt;\u0026#34; make ARCH=arm64 CROSS_COMPILE=\u0026#34;aarch64-linux-gnu-\u0026#34; INSTALL_MOD_PATH=\u0026#34;$LOCAL_KERNEL_ARTIFACTS\u0026#34; modules_install echo \u0026#34;\u0026gt;\u0026gt;\u0026gt; 拷贝内核镜像和设备树 \u0026lt;\u0026lt;\u0026lt;\u0026#34; cp -v -f arch/arm64/boot/Image.gz \u0026#34;$LOCAL_KERNEL_ARTIFACTS/$KERNEL_IMG\u0026#34; cp -v -f \u0026#34;$DTB_SOURCE\u0026#34; \u0026#34;$LOCAL_KERNEL_ARTIFACTS/$DTB_FILE\u0026#34; echo \u0026#34;\u0026gt;\u0026gt;\u0026gt; 上传内核产物至树莓派 \u0026lt;\u0026lt;\u0026lt;\u0026#34; ssh \u0026#34;$REMOTE_HOST\u0026#34; \u0026#34;sudo mkdir -p $REMOTE_KERNEL_ARTIFACTS\u0026#34; rsync -avz --progress --delete \u0026#34;$LOCAL_KERNEL_ARTIFACTS/\u0026#34; \u0026#34;$REMOTE_HOST:$REMOTE_KERNEL_ARTIFACTS\u0026#34; echo \u0026#34;\u0026gt;\u0026gt;\u0026gt; 上传完成！你可以登录树莓派并配置/切换启动内核 \u0026lt;\u0026lt;\u0026lt;\u0026#34; 这样，在执行完脚本后，待会你要安装的所有文件都先拷贝到树莓派上了，但注意只是拷贝 上面去而已，还没有真正应用这个新内核。\n安装树莓派内核与内核模块 使用下面的脚本，你可以方便地将刚刚拷贝到树莓派上的文件（设备树、内核与内核模块） 安装到预想的位置上。注意：该脚本涉及系统多个关键目录，请务必确认参数避免误删或 者覆盖重要文件。\n#!/bin/bash # ============================================== # RPi5 主线内核本地部署/刷机脚本 # 用于将内核镜像、设备树、内核模块，统一安装到 /boot 和 /lib/modules # 请确保在树莓派上、且拥有 sudo 权限运行 # ============================================== set -e # ====== 可配置参数（请根据实际情况修改） ====== # 上传产物存放目录（刚才 rsync 下来的） REMOTE_KERNEL_ARTIFACTS=\u0026#34;/home/pi/kernel\u0026#34; # 内核镜像名（和上一个上传脚本保持一致） KERNEL_IMG=\u0026#34;kernel-vanilla.c.img\u0026#34; # 设备树文件名 DTB_FILE=\u0026#34;bcm2712d0-rpi-5-b.vanilla.c.dtb\u0026#34; # 内核镜像与设备树所在位置 BOOT_FIRMWARE_PATH=\u0026#34;/boot/firmware\u0026#34; # 内核模块实际安装目录（目标） LIB_MODULES_PATH=\u0026#34;/lib/modules\u0026#34; # ============================================== echo \u0026#34;\u0026gt;\u0026gt;\u0026gt; 1. 安装内核镜像和设备树 \u0026lt;\u0026lt;\u0026lt;\u0026#34; sudo cp -v \u0026#34;$REMOTE_KERNEL_ARTIFACTS/$KERNEL_IMG\u0026#34; \u0026#34;$BOOT_FIRMWARE_PATH/$KERNEL_IMG\u0026#34; sudo cp -v \u0026#34;$REMOTE_KERNEL_ARTIFACTS/$DTB_FILE\u0026#34; \u0026#34;$BOOT_FIRMWARE_PATH/$DTB_FILE\u0026#34; echo \u0026#34;\u0026gt;\u0026gt;\u0026gt; 2. 设置镜像和 DTB 权限 \u0026lt;\u0026lt;\u0026lt;\u0026#34; sudo chown root:root \u0026#34;$BOOT_FIRMWARE_PATH/$KERNEL_IMG\u0026#34; \u0026#34;$BOOT_FIRMWARE_PATH/$DTB_FILE\u0026#34; sudo chmod 644 \u0026#34;$BOOT_FIRMWARE_PATH/$KERNEL_IMG\u0026#34; \u0026#34;$BOOT_FIRMWARE_PATH/$DTB_FILE\u0026#34; echo \u0026#34;\u0026gt;\u0026gt;\u0026gt; 3. 安装内核模块 \u0026lt;\u0026lt;\u0026lt;\u0026#34; # 会自动覆盖同版本号模块 sudo rsync -av --progress \u0026#34;$REMOTE_KERNEL_ARTIFACTS/lib/modules/\u0026#34; \u0026#34;$LIB_MODULES_PATH/\u0026#34; echo \u0026#34;\u0026gt;\u0026gt;\u0026gt; 4. 设置内核模块权限 \u0026lt;\u0026lt;\u0026lt;\u0026#34; sudo chown -R root:root \u0026#34;$LIB_MODULES_PATH/\u0026#34; find \u0026#34;$LIB_MODULES_PATH/\u0026#34; -type d -exec sudo chmod 755 {} \\; find \u0026#34;$LIB_MODULES_PATH/\u0026#34; -type f -exec sudo chmod 644 {} \\; echo \u0026#34;\u0026gt;\u0026gt;\u0026gt; 内核与模块部署完成！如有需要请更新/boot/config.txt 指定新内核 \u0026lt;\u0026lt;\u0026lt;\u0026#34; 配置树莓派固件引导新内核 你需要编辑/boot/firmware/config.txt来确保树莓派固件引导新的内核并传递相应的设 备树，然后使用内核树，最主要是修改或添加下面的两行。\n# DTB device_tree=bcm2712d0-rpi-5-b.vanilla.c.dtb # Kernel kernel=kernel-vanilla.c.img 移除树莓派的 DTS Overlay 特性（并避免常见启动问题） 树莓派团队为其平台引入了 DTS Overlay 机制，这一设计允许在加载主设备树（Device Tree）时，根据固件或用户需求动态叠加额外的 overlay 片段，从而灵活开启/调整外设、 参数或特性。比如，你为树莓派增加了某个新模块或功能，仅需配置一个 overlay 即可激 活，非常方便。\n然而，这套机制目前并不被主线 Linux 内核支持！\n如果在 /boot/firmware/config.txt 开启了 overlay 相关配置，而 overlay 文件与主 线内核设备树（DTB）版本不兼容，极易出现启动异常、设备识别失败等问题。这对于追求 极简、纯主线内核体验的内核开发者来说，往往带来更多困扰而非便利。\n处理思路 对我个人而言，DTS Overlay 的灵活性虽强，但主线内核并未原生支持，且对内核调试和纯 粹环境带来很多干扰。因此我选择彻底移除 overlay 功能，只保留主设备树文件。这 么做直接的副作用是：某些硬件参数（如 CPU 启动参数）原本依赖 overlay 机制注入， 直接去掉 overlay 会导致树莓派无法正常启动。\n解决办法：我通过直接将所需的 CPU 参数写入主设备树文件来绕过了这个问题。实际 上，bcm2712d0-rpi-5-b.dtb 这份官方设备树文件就是为无 overlay 场景准备的，只要使 用它作为主设备树，系统即可正常引导，无需任何 overlay。\n如何移除 overlay 配置（操作指南） 编辑 /boot/firmware/config.txt，确保只设置 device_tree，并移除或注释所有 overlay 配置（如 dtoverlay= 相关行）。示例如下：\n# 指定主设备树文件（建议直接使用 bcm2712d0-rpi-5-b.vanilla.c.dtb） device_tree=bcm2712d0-rpi-5-b.vanilla.c.dtb # 不加载任何 overlay #dtoverlay=xxx # 请全部注释或删除 # 其它 overlay 自动检测功能建议关闭 camera_auto_detect=0 display_auto_detect=0 # 启动内核镜像 kernel=kernel-vanilla.c.img 检查其它与 overlay 相关的配置 推荐关闭 camera_auto_detect 和 display_auto_detect。 确认没有任何 dtoverlay= 配置项被启用。 重启树莓派 确保上述修改生效后，重启设备，系统将直接加载主设备树文件，不会再应用任何 overlay，从而最大程度保持主线内核的纯净性和可控性。如果后续没有任何问题的话， 可以移除/boot/firmware/overlays这个目录来节省空间，这也能确保后续启动如论如何 都不会加载这些 overlay 导致环境污染。\n推荐 config.txt 最小配置模板 # 仅指定主设备树和主线内核镜像 device_tree=bcm2712d0-rpi-5-b.vanilla.c.dtb kernel=kernel-vanilla.c.img # 关闭 overlay 自动检测 camera_auto_detect=0 display_auto_detect=0 # 其它通用参数可保留 disable_fw_kms_setup=1 arm_64bit=1 disable_overscan=1 arm_boost=1 提交补丁或 Issue 如果你在参考本文章操作时遇到任何问题，或者发现某个环节导致流程无法跑通，欢迎随 时反馈。 如果你修复了某个兼容性问题，或在使用过程中发现了 bug，也欢迎直接提交 Issue，与 我和其他使用者共同交流、完善项目。 如果你在本仓库基础上新增了硬件支持、做了性能优化，或移植了主线内核的新补丁，建 议按照 scripts/format-patch.sh 生成标准化补丁，然后直接发往上游 Linux 内核 mailing list，推动改进被社区采纳。 GitHub 仓库地址: https://github.com/saturneric/linux\n长期维护说明 本仓库和这篇文章都不是一次性的“快闪项目”。由于我将长期研究 Linux 内核和操作系统 相关理论，会持续关注并跟进主线内核的每一次重大版本发布（包括 rc1 … rcX 等阶段性 release），并将关键改动和适配及时合入 main 分支。每次主线同步前，我都会先在我的 工作站（Debian Testing）上编译然后在树莓派 5 上实机验证，确保仓库始终可用、流程 通畅。\n如果你也在用树莓派做主线内核开发，或遇到相关兼容性、适配问题，欢迎评论交流！也期 待你加入主线内核社区，推动 ARM64 平台持续进步。\n","permalink":"https://blog.bktus.com/archives/mv9qo5/","summary":"\u003ch2 id=\"摘要\"\u003e摘要\u003c/h2\u003e\n\u003cp\u003e本文系统记录了我如何将树莓派 5 打造成主线 Linux 内核（Vanilla Kernel）的极简开发\n与测试平台——包括仓库极简裁剪思路、定制化内核配置、交叉编译与自动部署脚\n本、overlay 移除技巧，以及长期与主线社区同步维护的实战心得。适合关注 Linux 主\n线、ARM64 适配、内核开发、Upstream 贡献或喜欢折腾内核的极客们参考和实践。\u003c/p\u003e\n\u003ch2 id=\"背景与动机\"\u003e背景与动机\u003c/h2\u003e\n\u003cp\u003e最近一段时间，我在深入研究 Linux Kernel 的理论和架构，准备正式切入操作系统内核领\n域。因此，特意购买了树莓派 5 作为开发板，用于学习和实践内核开发。然而刚上手就发\n现，目前树莓派 5 并不能直接跑主线 Linux 内核，只能依赖\u003ca href=\"https://github.com/raspberrypi/linux\"\u003e树莓派团队维护的内核仓\n库\u003c/a\u003e。当时最新的分支是 rpi-6.14.y（现在是\nrpi-6.16.y），仓库的维护方式是“动态拉取上游主线代码+rebase+大量自定义补丁”，涉及\n上万行的修改和历史反复变动。\u003c/p\u003e\n\u003cp\u003e这些定制补丁对于官方内核在树莓派上稳定运行、兼容所有功能确实非常重要，但对我这种\n\u003cstrong\u003e纯粹想研究主线内核特性\u003c/strong\u003e、关注内核本身的开发者来说，这些补丁反而变成了负担：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e主线同步困难，merge 经常出冲突，\u003c/li\u003e\n\u003cli\u003e版本历史频繁重写，难以回溯和比较，\u003c/li\u003e\n\u003cli\u003e验证主线新特性或做反馈变得不直观。\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e我对比了 x86 开发板，发现它们大多能直接运行主线内核，但价格昂贵，性价比远不如树\n莓派 5。因此，我开始思考，如何让树莓派 5 也能顺畅运行主线内核，真正实现随时体验\n和测试内核最新特性，并为 ARM64 平台反馈问题、贡献补丁提供便利。我的目标很明确：\n用最少的补丁、最贴近 upstream 的方式，让树莓派 5 成为个人主线内核实验和学习的理\n想 ARM64 平台。\u003c/p\u003e\n\u003ch2 id=\"仓库结构与修改范围\"\u003e仓库结构与修改范围\u003c/h2\u003e\n\u003cp\u003e我的做法十分简单却又直奔核心——只保留最必要的驱动与补丁，其余全部紧跟主线。仅对\ndrivers/ 目录做补丁，聚焦核心硬件支持，主要包括：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e\u003cstrong\u003eSD 卡驱动\u003c/strong\u003e：保证树莓派能正常引导，内核能加载根文件系统。虽然启动固件自带部\n分初始化功能，但主线内核仍需驱动挂载根文件系统。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e\u003cstrong\u003e有线网卡（Ethernet）\u003c/strong\u003e：方便通过 SSH 实现远程登录、下载新内核镜像、远程开发\n和调试，是内核开发过程中最重要的 IO 通道之一。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e\u003cstrong\u003eUART 串口\u003c/strong\u003e：作为内核调试和 early print 的输出窗口，可以直接观察内核日志，判\n断启动和运行状态。\u003c/p\u003e","title":"让树莓派 5 跑上“香草”主线内核：精简裁剪、交叉编译与部署全流程"},{"content":"这里汇集了我在网络世界中欣赏和推荐的优秀博客、网站与朋友们的作品。无论是技术干 货、生活随笔，还是创意分享，都在此与大家相互链接、相互支持。\n敖武的博客 ","permalink":"https://blog.bktus.com/friends/","summary":"\u003cp\u003e这里汇集了我在网络世界中欣赏和推荐的优秀博客、网站与朋友们的作品。无论是技术干\n货、生活随笔，还是创意分享，都在此与大家相互链接、相互支持。\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://z.wiki/\"\u003e敖武的博客\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e","title":"友情链接"},{"content":"在某些场景中，我们可能需要搭建一个链式 VPN 结构，即先让内网设备连接到自建的 WireGuard 服务器（如个人 VPS），再通过该服务器将所有流量转发到另一个 VPN（公司、 学校或商业 VPN 服务）后才进入互联网。搭建这种「VPN 套 VPN」的网络架构，有以下明 显优势：\n强大的隐私保护：内网设备的数据流量首先通过自己搭建的 WireGuard 加密隧道传输至 个人 VPS，再由 VPS 二次通过商业或公司 VPN 进行转发，最终从该 VPN 的 IP 出口访 问互联网。这种方式彻底避免了使用 VPS 自身 IP 地址直接访问目标网络，大大降低了 个人 IP 被跟踪或泄露的风险。\n集中化便捷管理：所有客户端设备统一连接到单个 WireGuard 服务，免去逐一配置每台 设备的麻烦。更重要的是，它还能巧妙绕过一些商业 VPN 服务商设定的账号设备数量限 制，一次付费，所有设备共享。\n灵活便捷，轻松切换：由于商业或公司 VPN 配置仅在自建 WireGuard 服务器端修改一次 即可生效，下游客户端（手机、电脑等）无需单独调整。这使得更换或重新配置上游 VPN 时更加迅速和便捷，大大提高日常维护的效率。\n打造专属虚拟局域网，实现跨设备互通：一旦所有设备连接到自建的 WireGuard VPN 服 务器后，将自动获得统一的 VPN 虚拟局域网 IP 地址（例如：10.8.0.0/16）。处于该网 络中的设备能够直接互相通信，无需额外设置。\n这种便捷性意味着无论你身处何处，都能轻松实现：\n远程 SSH 登录：随时访问家中或办公室的服务器。 私有 NAS 媒体访问：在外轻松访问家中 NAS 存储的媒体文件。 远程办公：随时随地安全访问公司内部网络资源。 方案 经过本人的探索和实践，我逐步摸索出了自己的一套链式 VPN 方案。总体来看，我的方案 完全基于 Docker 部署，所用到的核心组件有：\ngluetun：一个 Docker 镜像，内置 OpenVPN/ WireGuard 客户端，可连接到各大 VPN 服务提供商。本例中我们通过配置在 Docker 网络中提供 VPN 网关功能。 wg-easy：一个简化的 WireGuard Docker 镜像，内置 Web 界面，便于创建/管理 WireGuard 配置文件。下游设备通过 WireGuard 客户端连接到该服务，所有流量将被转 发到 gluetun 容器，由 gluetun 再发给上游 VPN。 该方案的优势在于完全容器化部署，避免污染服务器/VPS 的环境。然后就是比较稳定，个 人日常使用未发现明显延迟、卡顿、断流问题，而且性能良好（在个人家用带宽下测试）。 另外，该方案现在能够支持 IPv6，即使设备身处 IPv4 的网络环境，也能通过链路实现对 IPv6 资源的代理访问。\n在网络拓扑上，整个系统仅需要一个 Docker 网桥（bridge）：\nvpn：用于 gluetun 与 wg-easy 之间的“VPN 网络”， gluetun 在其中作为网 关，wg-easy 发出的流量都将进入该网络并通过 gluetun 转发到上游 VPN。 flowchart TD subgraph s1[\u0026#34;Your Devices\u0026#34;] n1[\u0026#34;Phone\u0026#34;] E[\u0026#34;PC\u0026#34;] D[\u0026#34;Laptop\u0026#34;] end subgraph s2[\u0026#34;Your Server (VPS)\u0026#34;] n2[\u0026#34;WG-Easy (WireGuard Server)\u0026#34;] F[\u0026#34;Gluetun (VPN Client)\u0026#34;] end n1 -- WireGuard Tunnel --\u0026gt; n2 n2 --\u0026gt; F E -- \u0026#34;WireGuard Tunnel\u0026#34; --\u0026gt; n2 D -- \u0026#34;WireGuard Tunnel\u0026#34; --\u0026gt; n2 F -- VPN Tunnel --\u0026gt; B[\u0026#34;VPN Provider\u0026#34;] B --\u0026gt; n3[\u0026#34;Internet\u0026#34;] n1@{ shape: rect} B@{ shape: rect} n3@{ shape: rect} 其中，wg-easy 监听 UDP 51820，客户端（自己的手机电脑）连接后，wg-easy （在容器 中）会将流量通过 Docker 网络 vpn 发往 gluetun 容器，gluetun 再使用 WireGuard/OpenVPN 连接上游 VPN。所有下游流量最终从 gluetun 的 tun0 接口出去，达 到链式 VPN 的效果。\n对于想跳过下面的详细设置步骤、直接使用完整集成方案的用户，可以参考我在 GitHub 上 的仓库：\n👉 https://github.com/saturneric/wg-easy-gluetun\n环境与前置条件 一台支持 Docker 与 Docker Compose 的 Linux 服务器/VPS（内核支持 WireGuard 和 iptables/NFT）。建议内核版本 ≥ 5.10。 已安装 Docker 和 Docker Compose v2+。 拥有一个支持 OpenVPN/WireGuard 的商业 VPN 服务商账号。 确保宿主机允许 CAP_NET_ADMIN 与 SYS_MODULE 权限，能加载 WireGuard 内核模块（可 通过 lsmod | grep wireguard 验证）。 创建自定义网络 在正式部署前，先在宿主机上创建网桥\ndocker network create \\ --driver bridge \\ --subnet 172.31.0.0/16 \\ --subnet fd01:beee:beee::/48 \\ vpn 说明：如果已有网络与子网冲突，请根据实际情况调整子网段。本示例假设 Docker 网桥支 持 IPv6（用于示例 IPv6 转发）。若不使用 IPv6，可省略相关配置或将 disable_ipv6=1。\nDocker Compose 配置详解 version: \u0026#34;3\u0026#34; services: gluetun: image: qmcgaw/gluetun cap_add: - NET_ADMIN - SYS_MODULE devices: - /dev/net/tun:/dev/net/tun environment: - VPN_SERVICE_PROVIDER=${VPN_SERVICE_PROVIDER} - VPN_TYPE=${VPN_TYPE} - WIREGUARD_PRIVATE_KEY=${WIREGUARD_PRIVATE_KEY} - WIREGUARD_PRESHARED_KEY=${WIREGUARD_PRESHARED_KEY} - WIREGUARD_ADDRESSES=${WIREGUARD_ADDRESSES} - SERVER_REGIONS=${SERVER_REGIONS} configs: - source: post-rules.txt target: /iptables/post-rules.txt volumes: - /lib/modules:/lib/modules:ro - /data/gluetun-ws:/gluetun sysctls: - net.ipv4.ip_forward=1 - net.ipv4.conf.all.src_valid_mark=1 - net.ipv6.conf.all.disable_ipv6=0 - net.ipv6.conf.all.forwarding=1 - net.ipv6.conf.default.forwarding=1 networks: vpn: ipv4_address: 172.31.0.4 ipv6_address: \u0026#34;fd01:beee:beee::4\u0026#34; restart: unless-stopped wg-easy: image: ghcr.io/wg-easy/wg-easy:15 ports: - \u0026#34;51820:51820/udp\u0026#34; networks: vpn: ipv4_address: 172.31.0.8 ipv6_address: \u0026#34;fd01:beee:beee::8\u0026#34; cap_add: - NET_ADMIN - SYS_MODULE sysctls: - net.ipv4.ip_forward=1 - net.ipv4.conf.all.src_valid_mark=1 - net.ipv6.conf.all.disable_ipv6=0 - net.ipv6.conf.all.forwarding=1 - net.ipv6.conf.default.forwarding=1 volumes: - /lib/modules:/lib/modules:ro - /data/wg-easy:/etc/wireguard restart: unless-stopped networks: vpn: external: true configs: post-rules.txt: content: | iptables -A FORWARD -i eth0 -o tun0 -j ACCEPT iptables -A FORWARD -i tun0 -o eth0 -m state --state RELATED,ESTABLISHED -j ACCEPT iptables -t nat -A POSTROUTING -o tun0 -j MASQUERADE ip6tables -I INPUT -p icmpv6 -j ACCEPT ip6tables -I OUTPUT -p icmpv6 -j ACCEPT ip6tables -A FORWARD -i eth0 -o tun0 -j ACCEPT ip6tables -A FORWARD -i tun0 -o eth0 -m state --state RELATED,ESTABLISHED -j ACCEPT ip6tables -t nat -A POSTROUTING -o tun0 -j MASQUERADE 接下来，逐项拆解说明：\nimage: qmcgaw/gluetun：Lightweight swiss-army-knife-like VPN client to multiple VPN service providers，能够使用在容器环境提供 VPN 服务，给其他有需要 的容器提供 VPN 连接。官方维护的 gluetun 镜像，支持多种 VPN 协议与服务商。 cap_add: [NET_ADMIN, SYS_MODULE]：需要 NET_ADMIN、SYS_MODULE 权限，以及开启 IP 转发等设置（wg-easy 与 gluetun 一致），赋予容器网络管理与加载内核模块的权 限，以便创建 TUN 设备及配置路由，且保证 WireGuard 隧道与路由功能正常。 devices: [\u0026quot;/dev/net/tun:/dev/net/tun\u0026quot;]：挂载宿主机的 TUN 设备到容器，使 gluetun 能创建虚拟网卡。 image: ghcr.io/wg-easy/wg-easy:15：wg-easy 的官方镜像（版本号可根据需要调 整）。 \u0026quot;51820:51820/udp\u0026quot;：将宿主机 UDP 51820 端口映射到容器，以便下游设备通过宿主机 或直接通过 Docker 网络来连接 WireGuard。如果你需要对外暴露 WG-Easy 的 Web 管理 界面，那你需要额外加入\u0026quot;51821:51821/tcp\u0026quot;。 环境变量中的具体参数可参考 gluetun 官方文 档。\n需要注意的是上面提到的这两个配置，用于固定容器在 Docker 网桥中的 IP 地址，后续配 置 WG-Easy 的路由表和防火墙规则的时候会频繁引用。如果需要替换，那么需要注意修改 后续配置中的相应值。\nnetworks: vpn: ipv4_address: 172.31.0.4 ipv6_address: \u0026#34;fd01:beee:beee::4\u0026#34; networks: vpn: ipv4_address: 172.31.0.8 ipv6_address: \u0026#34;fd01:beee:beee::8\u0026#34; gluetun 额外启动脚本post-rules.txt加入的防火墙规则的解释如下：\n# 1. IPv4 转发规则 （允许 eth0 \u0026lt;-\u0026gt; tun0 双向转发） iptables -A FORWARD -i eth0 -o tun0 -j ACCEPT iptables -A FORWARD -i tun0 -o eth0 -m state --state RELATED,ESTABLISHED -j ACCEPT # 2. IPv4 NAT 伪装（所有走 tun0 的包都做 SNAT） iptables -t nat -A POSTROUTING -o tun0 -j MASQUERADE # 3. IPv6 ICMP 放行（保证 Neighbor Discovery Protocol 等协议正常） ip6tables -I INPUT -p icmpv6 -j ACCEPT ip6tables -I OUTPUT -p icmpv6 -j ACCEPT # 4. IPv6 转发规则 （允许 eth0 \u0026lt;-\u0026gt; tun0 双向转发） ip6tables -A FORWARD -i eth0 -o tun0 -j ACCEPT ip6tables -A FORWARD -i tun0 -o eth0 -m state --state RELATED,ESTABLISHED -j ACCEPT # 5. IPv6 NAT 伪装（所有走 tun0 的 IPv6 包都做 SNAT） ip6tables -t nat -A POSTROUTING -o tun0 -j MASQUERADE 通过以上规则，下游（来自 wg-easy 的流量）在 gluetun 容器内部可以顺利地被转发到 tun0 接口，进入上游 VPN 通道；上游返回的数据包同样能回到 Docker 网络，并最终到达 wg-easy 再返回客户端。这就实现了一个 VPN 网关的功能。\nWG-Easy \u0026ldquo;Post Up\u0026rdquo; 脚本说明 除了 gluetun 的网络规则，还需在 wg-easy 里设置“Post Up”和“Post Down”脚本，用于在 WireGuard 隧道建立后动态添加路由与 ip rule，让下游客户端的流量都走 gluetun。本例 针对 wg-easy v15 版本，因为在 v15 版本后 wg-easy 才支持 IPv6。如果想知道 v14 版 本的配置，可以看我在GitHub 发布的帖 子。\n在目前 v15.0.0 版本的实际的操作中，需要把我给出的示例转换为单行（用分号分隔每 一条命令）填写到截图所示的输入框中。或者，你可以把脚本写成文件，添加到 wg-easy 中的目录中，然后引用。然后点击Save，耐心等待 wg-easy 弹出配置成功的提示框。必 要时，可以在配置后重启 wg-easy 容器。\n配置示例 以下为完整示例以及解释（注意需在 wg-easy 的 WEB 管理页面配置）:\n# WG-Easy Post Up 脚本（示例） VPN=$(ifconfig | grep -B1 172.31.0.8 | grep -o \u0026#34;^\\w*\u0026#34;) # 先默认拒绝所有转发流量 iptables -P FORWARD DROP ip6tables -P FORWARD DROP # 开放 WireGuard UDP 端口（{{port}} 会被替换为 WireGuard 端口号，一般为 51820） iptables -A INPUT -p udp -m udp --dport {{port}} -j ACCEPT ip6tables -A INPUT -p udp -m udp --dport {{port}} -j ACCEPT # 允许本地与 WireGuard 隧道之间的转发 iptables -A FORWARD -i wg0 -j ACCEPT iptables -A FORWARD -o wg0 -m state --state RELATED,ESTABLISHED -j ACCEPT iptables -A FORWARD -s {{ipv4Cidr}} -d {{ipv4Cidr}} -i wg0 -o wg0 -j ACCEPT ip6tables -A FORWARD -i wg0 -j ACCEPT ip6tables -A FORWARD -o wg0 -m state --state RELATED,ESTABLISHED -j ACCEPT ip6tables -A FORWARD -s {{ipv6Cidr}} -d {{ipv6Cidr}} -i wg0 -o wg0 -j ACCEPT # 配置 IPv4 路由表（表 200 用作下游流量专用表） iptables -t nat -A POSTROUTING -s {{ipv4Cidr}} -o $VPN -j MASQUERADE # 添加 ip rule，让源自 {{ipv4Cidr}} 的流量走表 200 ip rule add from {{ipv4Cidr}} table 200 ip route add default via 172.31.0.4 dev $VPN table 200 ip route add 172.31.0.0/16 via 172.31.0.1 dev $VPN table 200 ip route add {{ipv4Cidr}} dev wg0 table 200 # 配置 IPv6 路由表（同理，使用表 200） ip -6 rule add from {{ipv6Cidr}} table 200 ip -6 route add default via fd01:beee:beee::4 dev $VPN table 200 ip -6 route add fd01:beee:beee::/48 via fd01:beee:beee::1 dev $VPN table 200 ip -6 route add {{ipv6Cidr}} dev wg0 table 200 下面三个变量是 wg-easy 的模板变量，会由 wg-easy 自动填充，但用户要确保参数有效：\n{{port}}：WireGuard 监听的端口，一般与 Docker 映射的端口（51820）一致。 {{ipv4Cidr}}：下游客户端网段，比如 10.8.0.0/24。由 wg-easy 分配给 Peer。 {{ipv6Cidr}}：下游客户端的 IPv6 段，比如 fdcc:ad94:bacf:61a4::cafe::/64。 这里提供用于 PostDown 的脚本（简单将-A替换为-D，add变成del）：\nVPN=$(ifconfig | grep -B1 172.31.0.8 | grep -o \u0026#34;^\\w*\u0026#34;) iptables -P FORWARD DROP ip6tables -P FORWARD DROP iptables -D INPUT -p udp -m udp --dport {{port}} -j ACCEPT ip6tables -D INPUT -p udp -m udp --dport {{port}} -j ACCEPT iptables -D FORWARD -i wg0 -j ACCEPT iptables -D FORWARD -o wg0 -m state --state RELATED,ESTABLISHED -j ACCEPT iptables -D FORWARD -s {{ipv4Cidr}} -d {{ipv4Cidr}} -i wg0 -o wg0 -j ACCEPT ip6tables -D FORWARD -i wg0 -j ACCEPT ip6tables -D FORWARD -o wg0 -m state --state RELATED,ESTABLISHED -j ACCEPT ip6tables -D FORWARD -s {{ipv6Cidr}} -d {{ipv6Cidr}} -i wg0 -o wg0 -j ACCEPT iptables -t nat -D POSTROUTING -s {{ipv4Cidr}} -o $VPN -j MASQUERADE ip rule del from {{ipv4Cidr}} table 200 ip route del default via 172.31.0.4 dev $VPN table 200 ip route del 172.31.0.0/16 via 172.31.0.1 dev $VPN table 200 ip route del {{ipv4Cidr}} dev wg0 table 200 ip -6 rule del from {{ipv6Cidr}} table 200 ip -6 route del default via fd01:beee:beee::4 dev $VPN table 200 ip -6 route del fd01:beee:beee::/48 via fd01:beee:beee::1 dev $VPN table 200 ip -6 route del {{ipv6Cidr}} dev wg0 table 200 使用多张 Docker 网桥 在我的环境中，wg-easy 会连接到多张 Docker 网桥，也就是除了 vpn 之外，还有其他服 务专用的网桥。如果你需要为 wg-easy 再接入新的桥接网络，建议保留上面配置中 $VPN 变量而不是直接 wg-easy 提供的 {{device}}模板变量。因为通过 $VPN 变量可以自动绑定 对应网段对应的网卡，确保路由和防火墙规则只作用在指定的接口上，而不会误伤其他网络 服务。对于第二个 Docker 网桥，你可以在 PostUp 中添加如下命令：\nVPN_IF=$(ifconfig | grep -B1 172.31.0.8 | grep -o \u0026#34;^\\w*\u0026#34;) 需要注意将172.31.0.8替换为 对应 Docker 网桥上的 wg-easy 容器的 IP，这个需要你 固定 wg-easy 在该 Docker 网桥中的 IP。或者，你可以调整一下这个正则匹配，让 grep 筛选网段而非 IP 地址。这样，$VPN_IF 将解析为对应的网段正在使用的网络接口。然 后，请同时更新路由表和防火墙规则，保证 各个服务网络之间的流量隔离与正常转发：\niptables -t nat -A POSTROUTING -s \u0026lt;NETWORK_CIDR\u0026gt; -o \u0026lt;NETWORK_INTERFACE\u0026gt; -j MASQUERADE ip6tables -t nat -A POSTROUTING -s \u0026lt;NETWORK_CIDR\u0026gt; -o \u0026lt;NETWORK_INTERFACE\u0026gt; -j MASQUERADE # 如果此桥有 IPv6 ip route add \u0026lt;NETWORK_CIDR\u0026gt; via \u0026lt;NETWORK_GATEWAY\u0026gt; dev \u0026lt;NETWORK_INTERFACE\u0026gt; table 200 ip -6 route add \u0026lt;NETWORK_CIDR\u0026gt; via \u0026lt;NETWORK_GATEWAY\u0026gt; dev \u0026lt;NETWORK_INTERFACE\u0026gt; table 200 # 如果此桥有 IPv6 对于需要给多个容器提供 VPN 服务的但又不希望直接给 wg-easy 添加多个 Docker 网桥的 读者。这里提供一种更精细可控的做法：将那些需要走 VPN 的其他服务分别绑定到独立的 WireGuard 客户端容器（如 linuxserver/wireguard 或类似 Gluetun 的镜像）中，然后通 过 wg-easy（或类似管理界面）为每个服务创建单独的 WireGuard 账户。最后将对应的配 置导入到 服务对应的 WireGuard 客户端容器中。这样做的好处在于：\n每个服务拥有独立的 WireGuard 配置文件，流量隔离更彻底； 可以在 wg-easy 中更直观地监控、限制单个服务的网络访问； 不再需要编写诸如 iptables -A FORWARD -i eth+ -o tun0 这类对某个网络接口一概 放行的规则，而是仅对特定容器进行信任授权。 这种方式能够为每个容器提供更细粒度的权限控制，例如实时流量监控、随时撤销某个服务 的 VPN 权限等等。不过，如果运行多个 WireGuard 客户端容器会带来额外的 CPU 和内存 消耗。\n部署 创建并加载环境变量 在与 docker-compose.yml 同目录下，创建一个 .env 文件，写入（参考前面给出的 gluetun 文档）：\nVPN_SERVICE_PROVIDER=XXX VPN_TYPE=XXXX WIREGUARD_PRIVATE_KEY=YOUR_PRIVATE_KEY WIREGUARD_PRESHARED_KEY=YOUR_PSK WIREGUARD_ADDRESSES=XXX SERVER_REGIONS=XXX 确认 Docker 网络存在 如前文所述，确保 名为 vpn 的自定义网络已创建。\n启动容器 执行下面的命令，观察 wg-easy 与 gluetun-ws 容器启动日志，确认 WireGuard 客户端与 服务端都正常建立。\n$ docker-compose up -d 添加 WireGuard 客户端 通过浏览器访问 http://\u0026lt;WG-Easy容器IP\u0026gt;:51821（取决于 wg-easy 配置），进入 wg-easy 的 Web 界面。或者你可以在 Docker Compose 中加入对于 51821 端口的 TCP 映射，然后用 http://\u0026lt;本机IP\u0026gt;:51821 访问 Web 管理界面。或者用 Nginx 进行反向 代理也行。 在 Admin Panel 的 Hooks 栏，填写上面给出的的 Post Up 和 Post Down 配置，并保 存。这一步特别关键。 创建一个新的 Peer（填写 Peer 名称），wg-easy 会自动生成对应的配置文件，带有 AllowedIPs = 0.0.0.0/0, ::/0，并下载 .conf 文件。 在本地设备（Windows/Mac/Linux/手机）上导入该配置，启动 WireGuard，进行测试。 验证 VPN2VPN 通道 在客户端连上 WireGuard 后，打开 https://ipleak.net 或 https://ifconfig.co，查 看出口 IP。 若显示的是上游商业 VPN 的 IP，则说明链式 VPN 通道正常。 可在 gluetun 容器中执行 curl ifconfig.co，应该能看到商业 VPN 给的公网 IP；在 wg-easy 容器中执行同样操作，也能看到相同的出口 IP（走上游 VPN）。 检查流量路由 在 wg-easy 容器中，可执行 ip rule show / ip route show table 200，确认路由表 200 已生效，默认路由指向 172.31.0.4（gluetun）。 在 gluetun 容器中，执行 iptables -t nat -L -nv，可看到对应的 MASQUERADE 规则。 如需排查，可在两容器内分别使用 tcpdump -i 对应接口进行抓包，确认流量状态。 注意事项 DNS 泄露 本例如果不做额外的 DNS 安全措施，就存在 DNS 泄露的风险：当容器或客户端直接使用宿 主机或公共 DNS 时，DNS 查询可能绕过 VPN 通道而直连上游，暴露真实 IP 和访问的网站 记录；其实 gluetun 在自己内部会创建一个 DNS 服务器，然后强制所有的 DNS 流量走这 个服务，来避免使用本地 DNS 造成 DNS 泄露。但 gluetun 默认只在容器内部监听 DNS 访 问，外部容器或主机无法访问。这也让 wg-easy 无法直接使用 gluetun 内部的 DNS 服 务，所以我们不能简单把 wg-easy 中的 DNS 流量直接转发到 gluetun。\n为了避免这些问题，通常需要部署一个独立的 DNS 服务（如 AdGuard Home 或 Pi-hole），并将所有 DNS 查询都通过该服务处理，通过 VPN 出口进行上游解析，这样既 能保证 DNS 查询也走 VPN，又能实现广告拦截和恶意域名过滤。\n部署 AdGuard Home 或类似的 DNS 服务 我们可以在同一个 Docker 网络（例如 vpn 网络）中运行一个独立的 AdGuard Home 容 器。AdGuard Home 会同时监听 UDP/TCP 53 端口，并且具有更强的拦截广告、恶意域名过 滤等功能。举例来说，你可以在 docker-compose.yml 中加入类似以下服务（仅示范）：\nadguard: image: adguard/adguardhome restart: unless-stopped networks: vpn: ipv4_address: 172.31.0.16 ipv6_address: \u0026#34;fd01:beee:beee::16\u0026#34; ports: - \u0026#34;53:53/udp\u0026#34; - \u0026#34;53:53/tcp\u0026#34; - \u0026#34;3000:3000/tcp\u0026#34; # Web 界面端口 volumes: - /data/adguard/workdir:/opt/adguardhome/work - /data/adguard/conf:/opt/adguardhome/conf 这样，AdGuard Home 会暴露在宿主机的 53 端口，同时在 Docker vpn 网络内分配一个 IP（比如 172.31.0.5）。后续我们就能够从 wg-easy、客户机甚至其他容器直接访问这个 AdGuard 容器。你也可以选择不暴露 53 端口，仅仅通过 vpn 这个网桥提供 DNS 服务。\n访问 AdGuard 的 Web 界面（通常在 http://\u0026lt;Host-IP\u0026gt;:3000），确认上游 DNS 设置正 确，广告拦截等策略正常。然后你需要进行测试，最好在 WG-Easy 容器中执行nslookup hostname 172.31.0.16，测试 wg-easy 到 AdGuard Home 的 DNS 链路是否是通的。\n修改 DNS 配置 在 Adgaurd 部署、配置并测试成功后，我们需要调整在 wg-easy 生成的客户端 DNS 配 置，因为 wg-easy 默认会把 DNS 服务器设置为1.1.1.1与2606:4700:4700::111。这一 步需要在 wg-easy 的 Admin Panel 把 DNS 改为 AdGuard 容器的 IP：172.31.0.16与 fd01:beee:beee::16。\n需要注意：已经添加的设备的 DNS 配置并不会跟随 Admin Panel 中的 DNS 配置自动进行 修改，需要你手动更新设备中 WireGuard 里的 DNS 配置，或者在 WG-Easy 中对这个设备 单独进行调整然后在重新下载并应用配置文件到设备。\n最后，为了严格限制 WG-Easy 对外的 DNS 访问，杜绝 DNS 泄露。我们可以在 PostUp Hook 脚本中补充如下指令：\n# 允许向 adgaurd 的 DNS 端口发包 iptables -A OUTPUT -o $VPN -d 172.31.0.16 -p udp --dport 53 -j ACCEPT iptables -A OUTPUT -o $VPN -d 172.31.0.16 -p tcp --dport 53 -j ACCEPT ip6tables -A OUTPUT -o $VPN -d fd01:beee:beee::16 -p udp --dport 53 -j ACCEPT ip6tables -A OUTPUT -o $VPN -d fd01:beee:beee::16 -p tcp --dport 53 -j ACCEPT # 拒绝其它网络栈的 DNS 查询，避免漏出到宿主机 iptables -A OUTPUT -p tcp --dport 53 -j REJECT iptables -A OUTPUT -p udp --dport 53 -j REJECT ip6tables -A OUTPUT -p tcp --dport 53 -j REJECT ip6tables -A OUTPUT -p udp --dport 53 -j REJECT 其对应的 PostDown 脚本如下：\niptables -D OUTPUT -o $VPN -d 172.31.0.16 -p udp --dport 53 -j ACCEPT iptables -D OUTPUT -o $VPN -d 172.31.0.16 -p tcp --dport 53 -j ACCEPT ip6tables -D OUTPUT -o $VPN -d fd01:beee:beee::16 -p udp --dport 53 -j ACCEPT ip6tables -D OUTPUT -o $VPN -d fd01:beee:beee::16 -p tcp --dport 53 -j ACCEPT iptables -D OUTPUT -p tcp --dport 53 -j REJECT iptables -D OUTPUT -p udp --dport 53 -j REJECT ip6tables -D OUTPUT -p tcp --dport 53 -j REJECT ip6tables -D OUTPUT -p udp --dport 53 -j REJECT IPv6 支持 如不使用 IPv6，可在 docker-compose.yml 中将 ipv6_address、ip6tables 规则等相关 配置移除，并在创建网络时不启用 IPv6。 如果使用 IPv6，需要确保宿主机与 VPN 服务商支持 IPv6 隧道。 性能与资源 WireGuard 本身对 CPU 要求较高，特别在满速传输时，需要较好的硬件支持。\nMulti-Hop（多跳） 本架构即为双跳（下游 → wg-easy → gluetun → 上游 VPN → 互联网）。如果想再进一步配 置多跳，可在上游 VPN 选择不同区域，或再叠加一个 VPN 容器。但需注意性能下降与维护 复杂度上升。\n防火墙与安全 本例中，wg-easy 容器默认仅开放 UDP 51820 端口，如需在公网访问，请在宿主机防火墙 （iptables/nftables）开放对应端口，同时只允许可信 IP 访问，以减少暴露面。\nPostDown 脚本 为避免卸载后遗留规则，务必为 wg-easy 的 PostDown 添加相应的 iptables -D、ip rule del、ip route del 等操作。否则重启时可能产生重复路由或冲突。\n网络排查示例 当发现客户端无法访问 LAN 或 Internet 时，我在这里提供一些常用步骤快速定位问题的 方案。这个方案也是我平时常用的。我相信通过这种由粗到细的思路，就能有效找出大多数 网络连接故障的根源。\n下面给出测试方案的具体的步骤，但这里仅仅展示在 IPv4 下的排查指令，对于 IPv6，读 者可以很容易通过对指令进行微调来排查。\n分阶段 Ping 测试（本地） ping 10.8.0.1：确认 VPN 隧道是否建立。 ping 172.31.0.8：检查客户端是否能到达 wg-easy 容器（Docker 桥接网络）。 ping 172.31.0.4：验证桥接网络到 gluetun 容器的连通性。 ping 8.8.8.8：测试 gluetun 是否能正常访问外网。 如果某步失败，就有针对性地检查对应的路由或防火墙规则。\nDNS 解析验证（本地） 通过上面的测试后，说明 IP 层面链路没有问题，接下来我们就继续排查 DNS 的问题。\nnslookup example.com ：通过 默认 DNS 服务器 。 nslookup example.com 172.31.0.16 ：通过 adgaurd 。 nslookup example.com 8.8.8.8 ：测试公共 DNS 是否可用。 若无法得到正确响应，说明 DNS 请求没有到达预期的服务器。\n进入容器内部排查 如果 DNS 也没有问题，那可能是一些更深层次的路由表或者防火墙规则的冲突问题。我们 可以分别进入 wg-easy 或者 gluetun 容器进行调试：\ndocker exec -it \u0026lt;container-name/id-of-wg-easy\u0026gt; /bin/bash docker exec -it \u0026lt;container-name/id-of-gluetun\u0026gt; /bin/sh 在对应容器内执行：\nip rule show # 查看路由规则是否正确 ip route show table 200 # 验证路由表 200 ping -c 2 8.8.8.8 # 测试容器出网 ping -c 2 example.com # 测试容器内 DNS iptables-saves # 检查防火墙规则 ip route # 验证默认路由表 更高阶方法：tcpdump、路由表与防火墙检查 如果上述简单手段仍无法定位，那就说明存在一些更深的路由表或者防火墙配置冲突问题， 这个时候可使用 tcpdump -i \u0026lt;接口\u0026gt; 捕获数据包，观察 DNS（UDP/TCP 53）或其他流量 是否到达容器或被丢弃。然后，请仔细检查宿主机和容器内的路由表（ip rule show、ip route show）是否存在重叠或冲突项；确认 ip rule 的优先级和表表项是否生效。再次审 视 iptables/ip6tables 规则，尤其是 FORWARD、OUTPUT、nat 表，确认是否有误拒绝或重 复的规则导致流量被拦截。\n更新造成的问题 需要注意，你遇到的问题也很有可能是某一个容器的镜像更新后造成的问题，这个时候需要 检查是否最近有自动更新，然后尝试通过版本降级该解决。此时如果遇到的问题消失，那么 接下来，在问题最终解决之前，可以先固定旧的镜像版本确保服务可用。\n","permalink":"https://blog.bktus.com/archives/ozzkto/","summary":"\u003cp\u003e在某些场景中，我们可能需要搭建一个链式 VPN 结构，即先让内网设备连接到自建的\nWireGuard 服务器（如个人 VPS），再通过该服务器将所有流量转发到另一个 VPN（公司、\n学校或商业 VPN 服务）后才进入互联网。搭建这种「VPN 套 VPN」的网络架构，有以下明\n显优势：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e强大的隐私保护：内网设备的数据流量首先通过自己搭建的 WireGuard 加密隧道传输至\n个人 VPS，再由 VPS 二次通过商业或公司 VPN 进行转发，最终从该 VPN 的 IP 出口访\n问互联网。这种方式彻底避免了使用 VPS 自身 IP 地址直接访问目标网络，大大降低了\n个人 IP 被跟踪或泄露的风险。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e集中化便捷管理：所有客户端设备统一连接到单个 WireGuard 服务，免去逐一配置每台\n设备的麻烦。更重要的是，它还能巧妙绕过一些商业 VPN 服务商设定的账号设备数量限\n制，一次付费，所有设备共享。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e灵活便捷，轻松切换：由于商业或公司 VPN 配置仅在自建 WireGuard 服务器端修改一次\n即可生效，下游客户端（手机、电脑等）无需单独调整。这使得更换或重新配置上游 VPN\n时更加迅速和便捷，大大提高日常维护的效率。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e打造专属虚拟局域网，实现跨设备互通：一旦所有设备连接到自建的 WireGuard VPN 服\n务器后，将自动获得统一的 VPN 虚拟局域网 IP 地址（例如：10.8.0.0/16）。处于该网\n络中的设备能够直接互相通信，无需额外设置。\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e这种便捷性意味着无论你身处何处，都能轻松实现：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e远程 SSH 登录：随时访问家中或办公室的服务器。\u003c/li\u003e\n\u003cli\u003e私有 NAS 媒体访问：在外轻松访问家中 NAS 存储的媒体文件。\u003c/li\u003e\n\u003cli\u003e远程办公：随时随地安全访问公司内部网络资源。\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"方案\"\u003e方案\u003c/h2\u003e\n\u003cp\u003e经过本人的探索和实践，我逐步摸索出了自己的一套链式 VPN 方案。总体来看，我的方案\n完全基于 Docker 部署，所用到的核心组件有：\u003c/p\u003e","title":"搭建个人链式VPN网络：高效隐私保护、设备无限制管理与远程访问"},{"content":"在现在，生活中充斥着各种信息。我能够从各种消息来源获取各类信息，比如新闻、社交平 台、长短视频等等。在这些信息中，包含着对周遭世界的各种变化的概要情况，或者是细节 分析。其中不妨也有一些基于现在的情况，对一件事情进行定论和判断。有的分析往左边 说，有的分析往右边说，有的分析自称“中立”。总之，众说纷纭，莫衷一是。我也是很好 奇，我该如何判断这件事情的性质？哪一些是“对”的，哪一些是“错”的？\n很多时候，我发现人们对一件事情的判断，其实是处处受到其自身所处的状态的影响。自身 所处的状态又受到其社会地位、人际关系、经济实力、自身经历等影响。而且，对于一件事 情，若非当事人，旁观者从各种消息中得到的总是这个事件的某个描述，描述者也将会有意 无意在描述中渗入一些倾向性，这是不可避免的。比如说，虽然这个描述者说得都是事实， 但是对于事实的描述也可以有侧重。有些他觉得重要的或者有意义的细节，他会多说。而另 一些他认为不重要的，当然就会少说，甚至直接忽略。一件事情，除了其发生的时候的过程 以外，还涉及到各种背景，包括但不限于参与者各自的背景，还有事情发生之前的各种关联 性事件，甚至当事人在某一时刻闪过的想法。如果要在描述中，把这些事情都交代详尽，那 是不可能的，更不用说有些细节根本难以探知。\n很多时候我注意到，对于一件事情、一个事物或者一种现象，从两个完全不同的立场出发， 完全可以各自发展出一套非常有道理且充满正义的说法。然而从这两个不同的立场看对方， 往往会轻易给出非常尖锐的批评，双方都可以援引各种实际的例子。有趣的是，这些批评可 以互相套用，用在各自身上，从切换一下立场，然后按照这个立场的模式进行一套有逻辑的 推理，即可很好对号入座。所以，我尝试对我身边发生的事情、我感兴趣的大事、我忧虑的 事情和我日常的小事等等进行这样的正反分析，几乎都能得出两个相反的判断和结论。在这 段时间内，我陷入了一种分裂。我经常苦苦思索，到底哪一种说法是对的，按照事情的发 展，哪一种立场可以笑到最后。我也渴望，一种中立、理性、客观的立场。然而，每当我认 为某个人阐述的这种立场非常客观，非常中立，一段时间后就会有针对持有这种立场的人的 批评之声。要么就是，客观到味同嚼蜡，我都不想继续继续听下去。我为什么会这样，我常 常反思。\n在长达几年的断断续续地思考后，我最终得出我自己的结论，寻找一种”中立“且”客观“的立 场观点，或者是对一件事物去苦苦追寻”真相“都毫无意义。这样反而会让自己活得非常累。 根本的原因在于，我自己是有自己的立场的，不需要再去强迫自己接受其他人的立场。我的 立场来源于个人喜好、社会地位、经济实力、过往经历、经验见识等等，这些也是我上面所 提到的影响个人对于事物判断的几个影响点。所以，我会在某些时候，对于一些声音感觉 很”客观“，对于另一些声音觉得很反感。那是因为，其实对于这些事情早已经在自己的内心 产生的自己的判断。那为什么我不能去乐呵呵地接受所有的声音？我承认，我可以去听取所 有的声音，并思考其他的声音中包含的那些道理，然后尝试去加深自己对于一件事情的认 识，但其实很难做到去开心地接受。然后，让我去站在我自己的对立面去思考，完全可以， 但是对于我自己并无意义，因为我毕竟无法成为对立面的那一些人，很难按照他们的立场来 做或者享受这样的立场带来的利益。\n我更加倾向于认为，每个人都有自己的基本法，基本法是按照自己的个人情况量身定制，是 自己对于周遭世界该怎么样才会“完美”的朴素认识。我自己是立法者，为了对于一件事情进 行认识和判断，我可以通过基本法衍生出一些实际的法律。我自己也是法官，根据基本法衍 生出的现行法律，对一件事情进行定性和判断。我是否要去参考其他人的定性和判断，可 以，但是不是必须，应该更多地秉持着一种认识世界上的思想多样性的态度。别人如何判 断，那是基于其他人的基本法，他们的基本法可能与我有相似之处，也可能与我大相径庭， 完全相同的可能性非常小。那既然不是同一套基本法，那么各自也会衍生出自己的一套实际 的法律。既然他们与我所处的体系都不同，我为什么要完全接受一种立场？\n对待日常生活中，那些自称的”中立者“的态度，基于我自己的这套观点，也有了一些清晰的 方法应对。这些中立者，其中一类，无非是宣称自己所述的是事情的真相，但是前面已经说 过，一件事情的真相是很难原汁原味的还原的，各类描述都带有自己的侧重，而且总有不为 人知的细节未曾曝光。既然这样，那空谈真相还有何意义，即使事情有一个绝对的争相，那 理论上也只有神知道了。所以我觉得，我大可放下“追求真相”的精神洁癖，基于我自己所知 道的，去给出我自己的看法。还有一类，说自己的观点很“中立”和“客观”，这是可笑的，因 为他也是一个人，有自己的基本法，如何能抛却七情六欲，成为一个神仙，从而来达到“中 立”和“客观”。如果有，那就有一种相对的中立和客观，那就是穷尽世界上的所有的立场与 其对应的思想观点，以及他们对于这件事情的看法，然后全部精确记录并阐述。有些“中立 者”正在向着这方面努力，这类人，我觉得确实是往正确的方向在走。但是，我作为一个 人，听取这么多的立场有什么意义，难道我能随心所遇地切换自己的人生，来去实地体会和 践行这些思想观点和判断？类似地，就比如一个问题有即使多种解决的方法，但是最终我还 是会用一种方法去解决它，选择哪一种方法完全是个人的事情。所以在这里，我认为某个事 情该怎么判断，听从自己基于自己立场的判断即可，在自己的这个参考系下，这就是中立和 客观的了。其他的参考系下的看法，又与自己何干？当然，我们也可以多看看其他的参考 系，但这绝不能作为一个强制的要求。如果自己想真正独立，那么维护自己的参考系的独立 性，捍卫自己的是立场、观点和偏好，是非常重要的。\n我也注意到，对于基本法某些部分或者条文有比较多相似性的一些人，是完全可能聚合成一 个团体的。一个人独自战斗地久了，找到和自己有相似观点的人，还是一件很欣喜的事情 的。对于一些事情，一个人完全可以去发现和寻找自己的团体，不一定要加入和做些什么实 际的事情，也可以是一种内心的靠拢。很多时候，人们会为了一件事情的看法，相互攻讦， 给不同立场的人扣上一顶帽子。如果这个时候，一个人能够站在一个自己舒服的团体下，那 可以减轻很多压力并有可能产生一种安定感。很多情况下，我觉得“站队”是对个人有利的。 更多的人如果能通过“站队”，共同努力去维护和传播一种相似的立场，才能让一种“共同立 场”持续存在并产生力量去真正推动现实中的一些事情。这样才有可能，在大家去解决某个 社会性问题时去考虑这种立场，然后去采取这种立场提供的方法或者部分思路。如果真能这 样，个人才能够在这种情况下获得更多的利益，让自己觉得自己的观点偏好与自己所喜爱的 有了更多的意义。\n很多人说，自己“看开了”，没有太多的想法了或者是总想看看中立的或者什么都不看，我觉 得不如说自己“绝望了”或者“放弃了”。既然上面说，出于自己的特性和基本法，个人总有自 己的立场，这种情况下，如果“看开了”，想让自己陷入一种“中立”，那其实是抛弃了自己的 基本法，把原来属于自己的权利——对一件事情保留自己的观点和看法，让渡给了他人，然后 自己甘愿随波逐流。甚至有“看开”的人说，那些没有看开的人，“幼稚”且“不成熟”，他们所 汲汲或戚戚于的事情，很多是陷入世俗的不成熟的表现。然而，在所谓“幼稚”的人看来，这 些“看开”者无非也就是一个自我精神阉割的可悲个体，由于世俗的压力丧失了属于自己的基 本权利，甘愿做一个随波逐流的空壳。看似“廓然无累”，其实任由他人摆布，这种摆布不限 于观点立场的通过潜移默化的强制接受，而且也会通过其他观点掌握绝对话语权后，通过提 出属于他们解决社会问题的方案来产生个人利益的受损。所以，我们也无须去贩卖“成熟”， 或者是感到沾沾自喜。基于世界上思想的多样性，成熟也是一个相对的概念了，社会主流立 场所看来的“成熟”，在另一些立场看来，反而是幼稚且不成熟的。关键就在于，那些立场掌 握了这个社会的话语权。\n想了这么久，终于能够把自己一部分对于这个主题的思考比较具体地阐述出来，这也是一件 好事，能够认识到自己的某一条法律条文究竟该怎么写。当然，基于我自己所想的，我自己 所写下的也是一家之言，也收到自己所叙述的东西约束。就是说上面这些，完全可以归为一 种属于我相对的观点，从读者的立场看，这些可以没有任何作用，完全可以不以为然，甚至 可以完全荒谬或者全盘错误。但这种观点或者理论，对我有效，让我对周遭世界有一个我自 己认为自洽的阐释，那就够了。而我写出来，对于他人的意义在哪里？用我自己的话来说， 就是能够让别人在愿意的时候，去体会一下这个世界上的思想多样性。\n","permalink":"https://blog.bktus.com/archives/di3xgp/","summary":"\u003cp\u003e在现在，生活中充斥着各种信息。我能够从各种消息来源获取各类信息，比如新闻、社交平\n台、长短视频等等。在这些信息中，包含着对周遭世界的各种变化的概要情况，或者是细节\n分析。其中不妨也有一些基于现在的情况，对一件事情进行定论和判断。有的分析往左边\n说，有的分析往右边说，有的分析自称“中立”。总之，众说纷纭，莫衷一是。我也是很好\n奇，我该如何判断这件事情的性质？哪一些是“对”的，哪一些是“错”的？\u003c/p\u003e\n\u003cp\u003e很多时候，我发现人们对一件事情的判断，其实是处处受到其自身所处的状态的影响。自身\n所处的状态又受到其社会地位、人际关系、经济实力、自身经历等影响。而且，对于一件事\n情，若非当事人，旁观者从各种消息中得到的总是这个事件的某个描述，描述者也将会有意\n无意在描述中渗入一些倾向性，这是不可避免的。比如说，虽然这个描述者说得都是事实，\n但是对于事实的描述也可以有侧重。有些他觉得重要的或者有意义的细节，他会多说。而另\n一些他认为不重要的，当然就会少说，甚至直接忽略。一件事情，除了其发生的时候的过程\n以外，还涉及到各种背景，包括但不限于参与者各自的背景，还有事情发生之前的各种关联\n性事件，甚至当事人在某一时刻闪过的想法。如果要在描述中，把这些事情都交代详尽，那\n是不可能的，更不用说有些细节根本难以探知。\u003c/p\u003e\n\u003cp\u003e很多时候我注意到，对于一件事情、一个事物或者一种现象，从两个完全不同的立场出发，\n完全可以各自发展出一套非常有道理且充满正义的说法。然而从这两个不同的立场看对方，\n往往会轻易给出非常尖锐的批评，双方都可以援引各种实际的例子。有趣的是，这些批评可\n以互相套用，用在各自身上，从切换一下立场，然后按照这个立场的模式进行一套有逻辑的\n推理，即可很好对号入座。所以，我尝试对我身边发生的事情、我感兴趣的大事、我忧虑的\n事情和我日常的小事等等进行这样的正反分析，几乎都能得出两个相反的判断和结论。在这\n段时间内，我陷入了一种分裂。我经常苦苦思索，到底哪一种说法是对的，按照事情的发\n展，哪一种立场可以笑到最后。我也渴望，一种中立、理性、客观的立场。然而，每当我认\n为某个人阐述的这种立场非常客观，非常中立，一段时间后就会有针对持有这种立场的人的\n批评之声。要么就是，客观到味同嚼蜡，我都不想继续继续听下去。我为什么会这样，我常\n常反思。\u003c/p\u003e\n\u003cp\u003e在长达几年的断断续续地思考后，我最终得出我自己的结论，寻找一种”中立“且”客观“的立\n场观点，或者是对一件事物去苦苦追寻”真相“都毫无意义。这样反而会让自己活得非常累。\n根本的原因在于，我自己是有自己的立场的，不需要再去强迫自己接受其他人的立场。我的\n立场来源于个人喜好、社会地位、经济实力、过往经历、经验见识等等，这些也是我上面所\n提到的影响个人对于事物判断的几个影响点。所以，我会在某些时候，对于一些声音感觉\n很”客观“，对于另一些声音觉得很反感。那是因为，其实对于这些事情早已经在自己的内心\n产生的自己的判断。那为什么我不能去乐呵呵地接受所有的声音？我承认，我可以去听取所\n有的声音，并思考其他的声音中包含的那些道理，然后尝试去加深自己对于一件事情的认\n识，但其实很难做到去开心地接受。然后，让我去站在我自己的对立面去思考，完全可以，\n但是对于我自己并无意义，因为我毕竟无法成为对立面的那一些人，很难按照他们的立场来\n做或者享受这样的立场带来的利益。\u003c/p\u003e\n\u003cp\u003e我更加倾向于认为，每个人都有自己的基本法，基本法是按照自己的个人情况量身定制，是\n自己对于周遭世界该怎么样才会“完美”的朴素认识。我自己是立法者，为了对于一件事情进\n行认识和判断，我可以通过基本法衍生出一些实际的法律。我自己也是法官，根据基本法衍\n生出的现行法律，对一件事情进行定性和判断。我是否要去参考其他人的定性和判断，可\n以，但是不是必须，应该更多地秉持着一种认识世界上的思想多样性的态度。别人如何判\n断，那是基于其他人的基本法，他们的基本法可能与我有相似之处，也可能与我大相径庭，\n完全相同的可能性非常小。那既然不是同一套基本法，那么各自也会衍生出自己的一套实际\n的法律。既然他们与我所处的体系都不同，我为什么要完全接受一种立场？\u003c/p\u003e\n\u003cp\u003e对待日常生活中，那些自称的”中立者“的态度，基于我自己的这套观点，也有了一些清晰的\n方法应对。这些中立者，其中一类，无非是宣称自己所述的是事情的真相，但是前面已经说\n过，一件事情的真相是很难原汁原味的还原的，各类描述都带有自己的侧重，而且总有不为\n人知的细节未曾曝光。既然这样，那空谈真相还有何意义，即使事情有一个绝对的争相，那\n理论上也只有神知道了。所以我觉得，我大可放下“追求真相”的精神洁癖，基于我自己所知\n道的，去给出我自己的看法。还有一类，说自己的观点很“中立”和“客观”，这是可笑的，因\n为他也是一个人，有自己的基本法，如何能抛却七情六欲，成为一个神仙，从而来达到“中\n立”和“客观”。如果有，那就有一种相对的中立和客观，那就是穷尽世界上的所有的立场与\n其对应的思想观点，以及他们对于这件事情的看法，然后全部精确记录并阐述。有些“中立\n者”正在向着这方面努力，这类人，我觉得确实是往正确的方向在走。但是，我作为一个\n人，听取这么多的立场有什么意义，难道我能随心所遇地切换自己的人生，来去实地体会和\n践行这些思想观点和判断？类似地，就比如一个问题有即使多种解决的方法，但是最终我还\n是会用一种方法去解决它，选择哪一种方法完全是个人的事情。所以在这里，我认为某个事\n情该怎么判断，听从自己基于自己立场的判断即可，在自己的这个参考系下，这就是中立和\n客观的了。其他的参考系下的看法，又与自己何干？当然，我们也可以多看看其他的参考\n系，但这绝不能作为一个强制的要求。如果自己想真正独立，那么维护自己的参考系的独立\n性，捍卫自己的是立场、观点和偏好，是非常重要的。\u003c/p\u003e\n\u003cp\u003e我也注意到，对于基本法某些部分或者条文有比较多相似性的一些人，是完全可能聚合成一\n个团体的。一个人独自战斗地久了，找到和自己有相似观点的人，还是一件很欣喜的事情\n的。对于一些事情，一个人完全可以去发现和寻找自己的团体，不一定要加入和做些什么实\n际的事情，也可以是一种内心的靠拢。很多时候，人们会为了一件事情的看法，相互攻讦，\n给不同立场的人扣上一顶帽子。如果这个时候，一个人能够站在一个自己舒服的团体下，那\n可以减轻很多压力并有可能产生一种安定感。很多情况下，我觉得“站队”是对个人有利的。\n更多的人如果能通过“站队”，共同努力去维护和传播一种相似的立场，才能让一种“共同立\n场”持续存在并产生力量去真正推动现实中的一些事情。这样才有可能，在大家去解决某个\n社会性问题时去考虑这种立场，然后去采取这种立场提供的方法或者部分思路。如果真能这\n样，个人才能够在这种情况下获得更多的利益，让自己觉得自己的观点偏好与自己所喜爱的\n有了更多的意义。\u003c/p\u003e","title":"关于对事物定性判断的一些体会"},{"content":"我叫 Saturneric ，任何时候都可以直接称呼我 Eric。目前坐标德国。\n概况 身份职业： 硕士生（KaIT, 信息学, 2025-） 后台工程师（腾讯，2022-2024） 开源软件贡献者（GpgFrontend及其他项目，2021-） 技术领域：C/C++，GnuPG，Linux Kernel，Qt，Docker。 操作系统：Debian，macOS，Windows，Android，iOS。 常用软件：Firefox，Thunderbird，Matrix/Element，Joplin。 兴趣爱好：天文（观测），历史（中国古代史）。 掌握语言：汉语，英语，德语。 常用链接 邮箱：eric@bktus.com（优先使用） Matrix：@eric:bktus.com Git：https://git.bktus.com Github：https://github.com/saturneric 联系我 如果你想与我建立联系，请参考联系与验证中的说明。\n站点声明 关于本站的相关法律和隐私申明,请参考站点申明中的内容。\n","permalink":"https://blog.bktus.com/about-me/","summary":"\u003cp\u003e我叫 Saturneric ，任何时候都可以直接称呼我 Eric。目前坐标德国。\u003c/p\u003e\n\u003ch2 id=\"概况\"\u003e概况\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e身份职业：\n\u003cul\u003e\n\u003cli\u003e硕士生（KaIT, 信息学, 2025-）\u003c/li\u003e\n\u003cli\u003e后台工程师（腾讯，2022-2024）\u003c/li\u003e\n\u003cli\u003e开源软件贡献者（GpgFrontend及其他项目，2021-）\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e技术领域：C/C++，GnuPG，Linux Kernel，Qt，Docker。\u003c/li\u003e\n\u003cli\u003e操作系统：Debian，macOS，Windows，Android，iOS。\u003c/li\u003e\n\u003cli\u003e常用软件：Firefox，Thunderbird，Matrix/Element，Joplin。\u003c/li\u003e\n\u003cli\u003e兴趣爱好：天文（观测），历史（中国古代史）。\u003c/li\u003e\n\u003cli\u003e掌握语言：汉语，英语，德语。\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"常用链接\"\u003e常用链接\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e邮箱：\u003ca href=\"mailto:eric@bktus.com\"\u003eeric@bktus.com\u003c/a\u003e（优先使用）\u003c/li\u003e\n\u003cli\u003eMatrix：\u003ca href=\"https://matrix.to/#/@eric:bktus.com\"\u003e@eric:bktus.com\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003eGit：\u003ca href=\"https://git.bktus.com\"\u003ehttps://git.bktus.com\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003eGithub：\u003ca href=\"https://github.com/saturneric\"\u003ehttps://github.com/saturneric\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"联系我\"\u003e联系我\u003c/h2\u003e\n\u003cp\u003e如果你想与我建立联系，请参考\u003ca href=\"/contact-and-verification/\"\u003e联系与验证\u003c/a\u003e中的说明。\u003c/p\u003e\n\u003ch2 id=\"站点声明\"\u003e站点声明\u003c/h2\u003e\n\u003cp\u003e关于本站的相关法律和隐私申明,请参考\u003ca href=\"/site-declaration/\"\u003e站点申明\u003c/a\u003e中的内容。\u003c/p\u003e","title":"关于我"},{"content":"这里记录着长文之外的灵感碎片、日常废话与即时状态。不定期更新，随看随走。\n","permalink":"https://blog.bktus.com/memos/","summary":"\u003cp\u003e这里记录着长文之外的灵感碎片、日常废话与即时状态。不定期更新，随看随走。\u003c/p\u003e","title":"随笔"},{"content":"看了一下当时的周报，值得说的事情也就是关于日志整理的事情，然后就是关于一个配置下 发服务的多地部署。关于多地部署的事情，下一篇技术周报再详细说明。\n首先来说说，日志方面的事情。现在的话，线上有很多服务。然后目前 DEBUG 的日志，是 用了腾讯云的 CLS。而目前存在一些问题，这些服务的日志有些分散，不太好找。如果一个 请求跨越了多个服务，有可能需要研发去跨越几个日志集然后来定位问题。然后就是，这些 服务技术栈并不统一，并没有一个统一的日志监控框架来监控这些服务的状态。目前追踪跨 服务的请求使用 TraceID，TraceID 是在请求进入网关服务或者入口服务的时候自动生成 的。TraceID 有可能是纯数字，也有可能是数字带字母。后续的服务之间的请求基本上会带 有 TraceID 这个字段，用于追踪请求。然而，也还有很多请求内容里不带 TraceID，导致 服务没办法从请求本身来拿到 TraceID，只能索性重新生成一个，所以这些请求在各个服务 之间的 TraceID 都会不同。总而言之，理论上，正常情况下，一个 TraceID 将伴随一个请 求，从进入追踪范围到出追踪范围为止。\n另外还有个问题，就是这些个日志集中，日志内带有的字段也不太统一，有写日志集的请求 内容的字段叫 X，有些又叫 Y。有些字段日志的结构上有，实际输出的日志中却没有填写。 如此这些，都增加了新的研发上手定位 BUG 原因的学习成本。上述这些问题，都很让人头 疼。\n还有一个就是降本增效的问题，在目前的已有日志的基础上需要看看还有哪些日志可以精简 优化的。整个部门，每个月的单单存储和整理日志成本都十多万。而我负责的这一块的业务 量又占大头，日志量也是非常大的，每天大概有 540 亿条日志。所以，我需要在这一块多 下功夫。\n对于当前的的 TraceID 问题，我采取先把同样技术栈服务的 TraceID 的生成方式和传递方 式都改为统一的。对于 Java 技术栈的服务，用的无非都是 Spring Boot 框架。所以我对 每个服务都增加了一个统一的拦截器，来拦截请求和响应。对于请求，这些 Java 服务处理 的都是文本类型的请求，基本上都是 JSON 格式编码的。所以，无外乎就是检查 JSON 字段 中是否有相应的 traceId 字段，如果有的话，那就拿来用。具体就是把 TraceID 放置到线 程私有化存储中（MDC），伴随着这个线程处理。这个方法也有局限性，有些请求会在某些 步骤上进行异步处理，转移到其他线程池的线程上处理了。这样，TraceID 的追踪就丢失 了。我的办法是写一个包装类，接管所有异步处理的需求，参数与调用方式和原来一模一 样，然后在其中检查上一个线程中是否含有 TraceID 字段，然后把 TraceID 字段先放到包 装类产生的对象中。切换到另一个线程后，这个线程先看看包装类对象中是否有上一个线程 存下的 TraceID，如果有就提取这个 TraceID 然后记录到自己的私有化存储中。这样 TraceID 的追踪就不会中断了。\n请求拦截器的 TraceID 生成过程，也可以提一下。由于很多以前的老服务使用了二进制的 请求格式，技术原因导致了这些请求无法灵活变更。一旦更改了请求，那么上下游都需要重 新编译生成编解码器，然后重新部署。这对于日志改造来说，工作量不可接受。然后有很多 请求中，TraceID 是 long 类型的，而非字符串。所以我就需要照顾这些老服务，大家都统 一使用 long 类型的 TraceID。然后，我还希望在 TraceID 中带有一些额外的信息，比如 说这个请求是从哪个地方被赋予 TraceID 的。如果这个信息能直接从 TraceID 中看出来， 那就再好不过了。所以我不是简单地随机生成一串数字当作 TraceID，而是保留前面的 4 位数字。前两位用 99 作为开头，表示这个是使用了统一后的 TraceID 方案，和以前的纯 数字的 TraceID 区分开来。然后，后两位数字按序号表示各个服务。01，表示网关服务 A；02，表示入口服务 B 等等，这个我来手动分配，最后会把分配方案写到文档中。后面的 数字，都是随机生成的。\n注意，这里采用随机生成的方式，在每天这么大量的日志的情况下，可能产生重复。但我评 估了一下，这种重复对我们定位问题这个需求来说，无关痛痒。所以索性重复就重复，再重 复 15 分钟之内重复的概率也非常小，可以不考虑。最后，生成的 TraceID 都是固定位 数，以 99 开头的数字。\n在业务处理请求的过程中，会不断有日志输出，采用异步化的方式输出到日志盘中的目标文 件中。这个日志输出的格式和分类需要强制统一，包含下面的内容：时间、日志等 级、TraceID、对应线程 ID 与日志内容。这些都用“|”隔开来。然后，需要把不同等级的日 志区分开来存储。后续腾讯云 CLS 会通过规则来监控这些日志文件的修改，然后采集这些 输出的日志，通过上面的统一格式和区分等级，能更简单地统一各个服务的采集配置，并避 免把一些低等级的日志采集上去。低等级的日志可以留在日志盘中，如果研发人员需要看， 就可以登录到容器中来看。毕竟，相比于被 CLS 采集后，附带产生的流量、计算和存储的 成本，日志盘的存储成本还是能接受。这样也算是通过差异化地方式提供服务来降低成本。 后续，对于不是那么高频用到日志，只需要通过降低其日志等级来避免被采集，从而降低成 本。值得一提的是，被采集上去的高等级日志，只会按照 TraceID 索引，而不再索引内 容，这样可以非常有效地降低成本。我称这些日志为业务日志，这些日志在 CLS 上只能通 过 TraceID 来找，而不必通过内容来查找。\n在响应拦截器中，我们需要一次性向日志输出这个请求的简要信息，也就是 Access 日志， 包括请求的字段，响应的字段，还有处理时间，容器的 IP 地址，TraceID，服务名称等等 内容。Access 日志将单独输出的 access.log 中，然后这个文件也将被 CLS 采集。CLS 会 按照其中最关键的字段（请求字段，响应字段，TraceID，服务名称）索引这些日志，后续 我们通过模糊搜索请求字段或者响应字段就能定位到具体的一次业务请求了，我们可以对这 个请求有直观地了解，然后也能拿到 TraceID。通过 TraceID，我们可以用 CLS 一次性找 出与这次请求相关的所有业务日志，摸清楚这次业务请求的来龙去脉。\n通过上面对于业务日志和 Access 日志的方案的设置，我能够避免让 CLS 建立全文索引 （索引日志的所有内容），从而大幅度降低成本。虽然没有了全文索引，但是研发人员通过 BUG 单中的用户给出的信息，搜索请求或者响应中的对应字段，来查出 TraceID，后续也能 快速且准确找出所有有关的日志来。这实现了成本和易用性的一种平衡。还有一个没有提到 的，就是被 CLS 采集上去的日志，就没必要在日志盘中长期存在了，可以设置滚动规 则，1-2 小时后就删除掉。\n对于 Node.js 技术栈的服务，我也基本上采取相同的思路，通过框架提供的 context 能 力，来处理 TraceID。日志的输出和采集方案，也严格遵守 Java 服务的规范。然后还有 C++技术栈的服务，那就麻烦很多，这些服务非常老了，但依然处于关键位置上。但我没时 间处理这些了，这些服务出问题的概率非常小，先放着。\n通过这样的处理，CLS 上对每个服务配置日志文件的采集规则都是统一的了，大大简化了配 置的繁琐程度。对于每一个服务，我们只需要复制粘贴现有的采集和索引规则即可，因为这 些服务的日志格式和输出都是呈现形式都是相同的了。这样处理下来，有一个月的时间。完 成后，日志这块确实都规范起来了，查问题也变得方便了。我也能对现有日志进行归类，看 看哪些业务日志对查问题真正有效，哪些并没有太大的帮助。后续我再带着另一个同事，削 减了一大批以前没发现的无效或者重复的日志，又节省了一大笔费用。具体地，我们两个经 过评估分析发现每天可减少 121 亿条无效与重复的日志，削减现有成本的 38%。\n","permalink":"https://blog.bktus.com/archives/5mqhyo/","summary":"\u003cp\u003e看了一下当时的周报，值得说的事情也就是关于日志整理的事情，然后就是关于一个配置下\n发服务的多地部署。关于多地部署的事情，下一篇技术周报再详细说明。\u003c/p\u003e\n\u003cp\u003e首先来说说，日志方面的事情。现在的话，线上有很多服务。然后目前 DEBUG 的日志，是\n用了腾讯云的 CLS。而目前存在一些问题，这些服务的日志有些分散，不太好找。如果一个\n请求跨越了多个服务，有可能需要研发去跨越几个日志集然后来定位问题。然后就是，这些\n服务技术栈并不统一，并没有一个统一的日志监控框架来监控这些服务的状态。目前追踪跨\n服务的请求使用 TraceID，TraceID 是在请求进入网关服务或者入口服务的时候自动生成\n的。TraceID 有可能是纯数字，也有可能是数字带字母。后续的服务之间的请求基本上会带\n有 TraceID 这个字段，用于追踪请求。然而，也还有很多请求内容里不带 TraceID，导致\n服务没办法从请求本身来拿到 TraceID，只能索性重新生成一个，所以这些请求在各个服务\n之间的 TraceID 都会不同。总而言之，理论上，正常情况下，一个 TraceID 将伴随一个请\n求，从进入追踪范围到出追踪范围为止。\u003c/p\u003e\n\u003cp\u003e另外还有个问题，就是这些个日志集中，日志内带有的字段也不太统一，有写日志集的请求\n内容的字段叫 X，有些又叫 Y。有些字段日志的结构上有，实际输出的日志中却没有填写。\n如此这些，都增加了新的研发上手定位 BUG 原因的学习成本。上述这些问题，都很让人头\n疼。\u003c/p\u003e\n\u003cp\u003e还有一个就是降本增效的问题，在目前的已有日志的基础上需要看看还有哪些日志可以精简\n优化的。整个部门，每个月的单单存储和整理日志成本都十多万。而我负责的这一块的业务\n量又占大头，日志量也是非常大的，每天大概有 540 亿条日志。所以，我需要在这一块多\n下功夫。\u003c/p\u003e\n\u003cp\u003e对于当前的的 TraceID 问题，我采取先把同样技术栈服务的 TraceID 的生成方式和传递方\n式都改为统一的。对于 Java 技术栈的服务，用的无非都是 Spring Boot 框架。所以我对\n每个服务都增加了一个统一的拦截器，来拦截请求和响应。对于请求，这些 Java 服务处理\n的都是文本类型的请求，基本上都是 JSON 格式编码的。所以，无外乎就是检查 JSON 字段\n中是否有相应的 traceId 字段，如果有的话，那就拿来用。具体就是把 TraceID 放置到线\n程私有化存储中（MDC），伴随着这个线程处理。这个方法也有局限性，有些请求会在某些\n步骤上进行异步处理，转移到其他线程池的线程上处理了。这样，TraceID 的追踪就丢失\n了。我的办法是写一个包装类，接管所有异步处理的需求，参数与调用方式和原来一模一\n样，然后在其中检查上一个线程中是否含有 TraceID 字段，然后把 TraceID 字段先放到包\n装类产生的对象中。切换到另一个线程后，这个线程先看看包装类对象中是否有上一个线程\n存下的 TraceID，如果有就提取这个 TraceID 然后记录到自己的私有化存储中。这样\nTraceID 的追踪就不会中断了。\u003c/p\u003e","title":"二零二三年四月第一周技术周报"},{"content":"本周有个插入的事项，就是说我们目前在使用某云服务商的 API，对于其中的 API 鉴权， 那边有更安全的方案。原先，我们应用调取云产品的功能时，是通过一个 AccessKey 和 AccessSecret 来的。在调用的时候，需要在 SDK 中提供这两个值，然后就可以在应用中使 用 SDK 提供的云产品 API 了。原先的安全要求是不能在代码中存储这两个值，公司会有某 种自动扫描机制，通过这个机制可以检测代码仓库中的敏感信息，这两个鉴权用的字段也当 然囊括在内。所以，我们一般都会把这两个值写在配置文件中，安全性会更好一些，也方便 更改。\n而现在他们说这两个值是静态的，无法在泄露的情况下快速进行更换。毕竟我们服务一般都 有几十上百个容器，而目前来说，启动配置虽然已经集中于各种配置中心里了，但是在技术 上是无法实现秒级别的动态更改的，必须在修改配置后重新启动容器。而容器的启动是无法 通过一次性重启所有容器来完成，必须进行分批重启以保证服务的平稳，没有十几分钟甚至 几十分钟是搞不定的。\n所以云提供商的技术团队提出了一个新的方案，通过向我们提供一些必要的而且泄露后不容 易造成直接影响的凭据（好像是一个密钥文件），然后通过在线认证的机制下发动态的验证 信息。这个过程比我上文提到的验证机制复杂许多，特别是我们需要在容器中纳入这样一个 配置文件，或者直接在 JAR 包中纳入。然后我们需要引入一个 SDK，来专门做这个动态验 证信息的获取，然后将这个信息通过某种方式传递给云产品的 SDK，最后才能实现云产品 API 的调用。\n这个具体的过程并不是我来跟进，我把这些问题交给其他同事了，由他们来具体执行。我只 是作为云账号权限的控制者，为他们提供密钥文件并给他们提供正确的方向即可。\n","permalink":"https://blog.bktus.com/archives/0b0nkf/","summary":"\u003cp\u003e本周有个插入的事项，就是说我们目前在使用某云服务商的 API，对于其中的 API 鉴权，\n那边有更安全的方案。原先，我们应用调取云产品的功能时，是通过一个 AccessKey 和\nAccessSecret 来的。在调用的时候，需要在 SDK 中提供这两个值，然后就可以在应用中使\n用 SDK 提供的云产品 API 了。原先的安全要求是不能在代码中存储这两个值，公司会有某\n种自动扫描机制，通过这个机制可以检测代码仓库中的敏感信息，这两个鉴权用的字段也当\n然囊括在内。所以，我们一般都会把这两个值写在配置文件中，安全性会更好一些，也方便\n更改。\u003c/p\u003e\n\u003cp\u003e而现在他们说这两个值是静态的，无法在泄露的情况下快速进行更换。毕竟我们服务一般都\n有几十上百个容器，而目前来说，启动配置虽然已经集中于各种配置中心里了，但是在技术\n上是无法实现秒级别的动态更改的，必须在修改配置后重新启动容器。而容器的启动是无法\n通过一次性重启所有容器来完成，必须进行分批重启以保证服务的平稳，没有十几分钟甚至\n几十分钟是搞不定的。\u003c/p\u003e\n\u003cp\u003e所以云提供商的技术团队提出了一个新的方案，通过向我们提供一些必要的而且泄露后不容\n易造成直接影响的凭据（好像是一个密钥文件），然后通过在线认证的机制下发动态的验证\n信息。这个过程比我上文提到的验证机制复杂许多，特别是我们需要在容器中纳入这样一个\n配置文件，或者直接在 JAR 包中纳入。然后我们需要引入一个 SDK，来专门做这个动态验\n证信息的获取，然后将这个信息通过某种方式传递给云产品的 SDK，最后才能实现云产品\nAPI 的调用。\u003c/p\u003e\n\u003cp\u003e这个具体的过程并不是我来跟进，我把这些问题交给其他同事了，由他们来具体执行。我只\n是作为云账号权限的控制者，为他们提供密钥文件并给他们提供正确的方向即可。\u003c/p\u003e","title":"二零二三年三月第四周技术周报"},{"content":"本周值得注意的事项就是，测试人员那边反馈在测试环境下，某些账号突然无法正常使用的 问题。目前整个账号的体系还是在沿用老的系统。而在当前老系统中，账号信息使用某种特 殊的数据存储分成多个模块存储，每个模块相当于一张表。每张表都各有侧重，有些侧重于 与其他账号之间的关联关系，有些侧重于查询等等。这些账号无法使用的问题在于，其中最 重要的一张表也就是主表，这个用户的记录消失了。在其他表中该用户的相关记录还是存在 的。\n这就引发了我的各种猜想，是否是测试人员测试的过程中的环境弄混了？或者是某个服务的 配置有问题导致了部分应该写在测试环境的数据写到了生产环境去了？说代码写得有问题导 致了某些极端情况下，用户的主表信息写入失败？通过调查发现，最终要的查询表中，数据 结构还是完整的，而且能够反推出用户注册时候的微信账号。查询表只有在账号创建的时候 才会去写，后续基本不会再修改了。而主表中的数据应该是和查询表在用户注册的时候一块 写入。\n对于这个问题，后续推断应该是数据模块发生了某种问题导致数据丢失。而在我咨询数据模 块相关维护人员的时候，我还没说清楚问题，他们就说是测试环境并不保证服务的可用性， 可能发生任何问题。那么这个问题可能就无解了，因为如果是数据模块这层问题，我作为使 用方基本没有任何能力来自己解决。除非我能替换掉这个数据模块，使用更加稳定可靠的解 决方案。那对于这个问题，我只能够然同事手动清理查询表中的索引关系，然后在让用户重 新进入应用。当用户重新进入应用的时候，由于没有索引就相当于没有这个用户，此时就会 重新创建用户。后续几个月遇到了十几例相同的问题，没有办法，让同事添加了一段自动处 理这个问题的业务代码，以免去每次手动处理的麻烦。\n这个问题第一次出现将近一年后，由于某个重要客户反馈了这个问题。而又正好这个重要客 户当时半个月之内反馈的我们的各种问题有点多，领导对此十分困扰，所以对于我这个问题 他特别重视。领导后续找到我让我推动解决这件事，我同意执行，因为我也想知道问题具体 是什么。现在能够确定的是，问题出现在数据存储层，而这一层又不是我们直接控制，我们 只是使用这样一个产品。这个产品已经很老了，维护人员很少了，而且还有其他事情。他们 不愿意来主动寻找问题，也是情有可原的。所以想要推动他们的维护人员来解决，就只能抓 住具体且直接的证据来证明他们的问题在哪里。\n我以当时的了解，知道数据层分为缓存和落盘数据库，而且经过一年在各种问题的解决过程 中学习，我也能很熟练调取分析落盘数据库里的数据了。我调取了落盘数据库中的用户 ID 的倒序情况，发现从某个用户 ID 开始，后面就没有了。最近一年，用户报问题的用户 ID 以及新注册的用户 ID 是远远大于这个 ID 的。我又去日志中获取了一个最新注册的用户 ID，查询数据层，发现是有的。说明缓存中是有正常数据的，但是由于某种原因数据并没有 落到数据库中，而是在缓存满后直接消失了。\n这已经能够说明问题了。于是我将这些调查记录截图发给数据层产品的维护人员，并提了一 个工单，推动他们来解决这个问题。该问题的脉络我已经提前分析地很清楚明了了，而且又 有直接的证据，他们也开始认真对待这个问题。后面确实，他们重启某个模块后，这个问题 就好了。而且最终他们承认问题与缓存层落库的机制有关系。\n","permalink":"https://blog.bktus.com/archives/ge5n6u/","summary":"\u003cp\u003e本周值得注意的事项就是，测试人员那边反馈在测试环境下，某些账号突然无法正常使用的\n问题。目前整个账号的体系还是在沿用老的系统。而在当前老系统中，账号信息使用某种特\n殊的数据存储分成多个模块存储，每个模块相当于一张表。每张表都各有侧重，有些侧重于\n与其他账号之间的关联关系，有些侧重于查询等等。这些账号无法使用的问题在于，其中最\n重要的一张表也就是主表，这个用户的记录消失了。在其他表中该用户的相关记录还是存在\n的。\u003c/p\u003e\n\u003cp\u003e这就引发了我的各种猜想，是否是测试人员测试的过程中的环境弄混了？或者是某个服务的\n配置有问题导致了部分应该写在测试环境的数据写到了生产环境去了？说代码写得有问题导\n致了某些极端情况下，用户的主表信息写入失败？通过调查发现，最终要的查询表中，数据\n结构还是完整的，而且能够反推出用户注册时候的微信账号。查询表只有在账号创建的时候\n才会去写，后续基本不会再修改了。而主表中的数据应该是和查询表在用户注册的时候一块\n写入。\u003c/p\u003e\n\u003cp\u003e对于这个问题，后续推断应该是数据模块发生了某种问题导致数据丢失。而在我咨询数据模\n块相关维护人员的时候，我还没说清楚问题，他们就说是测试环境并不保证服务的可用性，\n可能发生任何问题。那么这个问题可能就无解了，因为如果是数据模块这层问题，我作为使\n用方基本没有任何能力来自己解决。除非我能替换掉这个数据模块，使用更加稳定可靠的解\n决方案。那对于这个问题，我只能够然同事手动清理查询表中的索引关系，然后在让用户重\n新进入应用。当用户重新进入应用的时候，由于没有索引就相当于没有这个用户，此时就会\n重新创建用户。后续几个月遇到了十几例相同的问题，没有办法，让同事添加了一段自动处\n理这个问题的业务代码，以免去每次手动处理的麻烦。\u003c/p\u003e\n\u003cp\u003e这个问题第一次出现将近一年后，由于某个重要客户反馈了这个问题。而又正好这个重要客\n户当时半个月之内反馈的我们的各种问题有点多，领导对此十分困扰，所以对于我这个问题\n他特别重视。领导后续找到我让我推动解决这件事，我同意执行，因为我也想知道问题具体\n是什么。现在能够确定的是，问题出现在数据存储层，而这一层又不是我们直接控制，我们\n只是使用这样一个产品。这个产品已经很老了，维护人员很少了，而且还有其他事情。他们\n不愿意来主动寻找问题，也是情有可原的。所以想要推动他们的维护人员来解决，就只能抓\n住具体且直接的证据来证明他们的问题在哪里。\u003c/p\u003e\n\u003cp\u003e我以当时的了解，知道数据层分为缓存和落盘数据库，而且经过一年在各种问题的解决过程\n中学习，我也能很熟练调取分析落盘数据库里的数据了。我调取了落盘数据库中的用户 ID\n的倒序情况，发现从某个用户 ID 开始，后面就没有了。最近一年，用户报问题的用户 ID\n以及新注册的用户 ID 是远远大于这个 ID 的。我又去日志中获取了一个最新注册的用户\nID，查询数据层，发现是有的。说明缓存中是有正常数据的，但是由于某种原因数据并没有\n落到数据库中，而是在缓存满后直接消失了。\u003c/p\u003e\n\u003cp\u003e这已经能够说明问题了。于是我将这些调查记录截图发给数据层产品的维护人员，并提了一\n个工单，推动他们来解决这个问题。该问题的脉络我已经提前分析地很清楚明了了，而且又\n有直接的证据，他们也开始认真对待这个问题。后面确实，他们重启某个模块后，这个问题\n就好了。而且最终他们承认问题与缓存层落库的机制有关系。\u003c/p\u003e","title":"二零二三年三月第三周技术周报"},{"content":"本周相关的一些事项，我认为最值得讲得就是某个数据库接入层服务的给每一条数据加上键 过期时间。这个数据库接入层是一个 Java 服务，本质上做的工作就是将 Redis 与数据库 结合起来。对上的接口遵循某一个公司内部的特定协议，而对下则使用开源且很多人使用的 Java 包来访问 Redis 或者数据库。访问 Redis 使用的是 lettuce，而访问数据库用的就 是 mybatis。原来访问 Redis 其实用的是 Jedis，后面之所以改用 lettuce，是因为它在 高并发下的性能/资源表现更好一些。我认为具体地主要体现在多路复用连接，使得不需要 在连接池中维护很多连接，总之就是在连接上的开销不会很大。\n然后对于数据库来说，并非直接写入，而是在消息队列中写入一条插入指令并附上一些上下 文。这样消息中包含了写入数据库需要的所有信息了。然后这条消息队列也是自己在消费， 消费的时候将数据写入数据库，如果成功就 ACK 消息，失败了就让这条消息 5 分钟后再回 来。\n这个服务总的读写策略就是，如果一条数据查询来了，Redis 中没有数据时就将数据库中的 数据拉到 Redis。然后返回 Redis 中的数据。Redis 中有数据则直接返回。对于写入数据 来说，这条数据会双写 Redis 和数据库。修改数据的时候，操作复杂得多。因为这个 Redis 中数据是使用 HSET 的方式进行存储的，然后一条修改指令可能只是修改 HSET 中的 一部分字段。这个就需要对一些具体的情况进行讨论了，数据库指令也是根据具体情况来构 造。\n值得一提的事写数据库的时候，目前看前人升级了策略，不直接拿消息中的数据来写，而是 消费到消息时，直接从 Redis 中读数据（这个时候 Redis 是早就被双写了的），然后直接 将 Redis 中读区到的数据写入数据库。我认为这样做的好处是，让数据库中的信息尽快更 新。因为从消息投递到消息队列再到消费的过程中，这一条数据可能已经修改了好几次了。\n在这几周我发现这个服务在 Redis 中的缓存并未设置过期时间，而是依赖 Redis 实例中设 置的统一过期策略来进行数据淘汰。这个策略是在 Redis 数据存满的时候才会执行的，这 就会引入一个风险。根据我们云上分布式 Redis 的设计，如果 Redis 存储一直都是满的然 后这个时候有大量的数据写入操作（某种特殊的业务高峰期），这样云上分布式 Redis 为 了一次性腾出足够的空间会集中进行淘汰操作。这个时候无法处理任何请求，这种情况将持 续几分钟。这对于业务来说，存在较大影响，因为这个时候上游业务服务将无法读或者写入 数据了。而现实情况就是，这个服务的 Redis 一直处于满的状态。\n为了降低风险，最好就是为每一条数据设置过期时间。使得 Redis 中存储的始终是较热的 数据。而设置过期时间，采取的策略是取一个固定的时间 Y，然后再生成一个均匀分布的浮 点随机数因子 a，从 0-1 变化。然后我们对于某一条数据的过期时间就是 Y+aY。这样设置 能尽可能避免某些情况下大量数据集中过期造成的某段时间内数据库压力过大问题。\n当时 Redis 中有 64GB 的数据，且整个 Redis 实例是满的。我评估了一下，如果只是增量 进行数据更新，确保新的数据是设置了过期时间的，这样应该能让存储空间缓慢降下来。最 终应该是会剩下少量的数据没有更新，占比应该不大，因为我们的业务不是存量业务，大部 分用户应该会继续使用。\n2024 年 4 月 将近一年多时间观察下来，Redis 中的数据只剩下了 10GB 左右。而 Redis 实例的规格我 也在半年前降下来了，成本有所削减。\n","permalink":"https://blog.bktus.com/archives/88phw6/","summary":"\u003cp\u003e本周相关的一些事项，我认为最值得讲得就是某个数据库接入层服务的给每一条数据加上键\n过期时间。这个数据库接入层是一个 Java 服务，本质上做的工作就是将 Redis 与数据库\n结合起来。对上的接口遵循某一个公司内部的特定协议，而对下则使用开源且很多人使用的\nJava 包来访问 Redis 或者数据库。访问 Redis 使用的是 lettuce，而访问数据库用的就\n是 mybatis。原来访问 Redis 其实用的是 Jedis，后面之所以改用 lettuce，是因为它在\n高并发下的性能/资源表现更好一些。我认为具体地主要体现在多路复用连接，使得不需要\n在连接池中维护很多连接，总之就是在连接上的开销不会很大。\u003c/p\u003e\n\u003cp\u003e然后对于数据库来说，并非直接写入，而是在消息队列中写入一条插入指令并附上一些上下\n文。这样消息中包含了写入数据库需要的所有信息了。然后这条消息队列也是自己在消费，\n消费的时候将数据写入数据库，如果成功就 ACK 消息，失败了就让这条消息 5 分钟后再回\n来。\u003c/p\u003e\n\u003cp\u003e这个服务总的读写策略就是，如果一条数据查询来了，Redis 中没有数据时就将数据库中的\n数据拉到 Redis。然后返回 Redis 中的数据。Redis 中有数据则直接返回。对于写入数据\n来说，这条数据会双写 Redis 和数据库。修改数据的时候，操作复杂得多。因为这个\nRedis 中数据是使用 HSET 的方式进行存储的，然后一条修改指令可能只是修改 HSET 中的\n一部分字段。这个就需要对一些具体的情况进行讨论了，数据库指令也是根据具体情况来构\n造。\u003c/p\u003e\n\u003cp\u003e值得一提的事写数据库的时候，目前看前人升级了策略，不直接拿消息中的数据来写，而是\n消费到消息时，直接从 Redis 中读数据（这个时候 Redis 是早就被双写了的），然后直接\n将 Redis 中读区到的数据写入数据库。我认为这样做的好处是，让数据库中的信息尽快更\n新。因为从消息投递到消息队列再到消费的过程中，这一条数据可能已经修改了好几次了。\u003c/p\u003e\n\u003cp\u003e在这几周我发现这个服务在 Redis 中的缓存并未设置过期时间，而是依赖 Redis 实例中设\n置的统一过期策略来进行数据淘汰。这个策略是在 Redis 数据存满的时候才会执行的，这\n就会引入一个风险。根据我们云上分布式 Redis 的设计，如果 Redis 存储一直都是满的然\n后这个时候有大量的数据写入操作（某种特殊的业务高峰期），这样云上分布式 Redis 为\n了一次性腾出足够的空间会集中进行淘汰操作。这个时候无法处理任何请求，这种情况将持\n续几分钟。这对于业务来说，存在较大影响，因为这个时候上游业务服务将无法读或者写入\n数据了。而现实情况就是，这个服务的 Redis 一直处于满的状态。\u003c/p\u003e","title":"二零二三年三月第二周技术周报"},{"content":"原文经过 AI 润色，非本人文风。\n从 2022 年 11 月开始，我开始使用 AWS 服务，已经接近 1 年的时间了，而且不是免费套 餐。从一开始对它的使用感到新奇和陌生，到现在对它的了解更加深入，期间经历了很多有 趣的事情。那么，为什么我选择使用 AWS 呢？因为我个人有一些需要在 AWS 上运行的服 务，比如博客网站（Wordpress）、Git 仓库、RSS 订阅等等。当时，我觉得 AWS 在云服务 市场占有最大的份额，所以它的解决方案应该已经非常成熟了。对于 AWS，我还是有一定的 信任，愿意花钱购买适合的服务，以确保我的个人托管业务能够长期稳定运行。\n在开始使用 AWS 之前，我也尝试过免费套餐，但是一直没有找到合适的需求场景，所以用 了一段时间后就不再使用了。这样，我的免费套餐周期就白白浪费了。\n当我第一次接触 AWS 的时候，我首先尝试了 EC2 产品，并试用了 1 核心 1G 内存的 T2.micro 实例，但后来发现内存不够用，导致机器卡死。因此，我决定换成 T2.small 型 机器，它拥有 1 核心 2G 内存，并且我还增加了 SWAP 分区，每个月大概需要花费 17 美 元。虽然这个价格包括了带宽费用，但我仍然觉得有些贵。然后，我开始考虑是否可以使用 AWS 的保留实例，听说可以减少 60%-70%的费用，看起来非常划算。于是，我下定决心购买 了三年的保留实例，先支付了 170 美元，然后每个月承诺支付 4.75 美元。这样一来，每 个月的费用大约是 8.84 美元，这个价格看起来相对合理。\n然后遇到了一个坑，当时我配置了 30G 的系统盘和 10G 的数据盘，以为 30G 的系统盘是 免费的。然而，我又考虑到数据的安全性，因此还增加了定期快照备份，以为快照的费用不 会很多。最后，月度账单一出来，竟然要 20 美元！我很惊讶钱都花在哪里了？\n我在控制台上查找，又在 Cost Explorer 上进行了进一步的查询，才发现系统盘和数据盘 的费用都按照我申请的规格计算了！没想到，AWS 竟然是按照服务器和云硬盘分别计算价格 的。而且，我还不知道如何配置第二代 SSD，虽然性能很好，但我用不到这么高的性能，而 且价格比第三代 SSD 贵 20%！还有，几个快照居然每个月费用超过了 7 美元。而且，费用 查找界面非常复杂，一个选项下可能有很多项目，需要一个一个取消勾选，慢慢查找到底是 哪一项花费了多少钱。\n因此，从 2022 年 12 月开始，我开始不断削减成本。我逐渐取消了快照备份，然后取消了 数据盘，最后还设法减小了系统盘的容量。系统盘容量扩容很方便，可以一键操作，但缩减 容量非常麻烦，需要手动操作。在网上找了一圈，发现 AWS 有个名为 Lightsail 的个人用 户友好产品，我感到后悔了。\n我还发现 DNS 托管每个域名每个月要 0.5 美元！而且，根据 DNS 访问次数还会额外计 费。特别是域名内有多个 CNAME 跳转的 DNS 配置风格，一次访问会被计算多次解析次数。 如果不想被计费，就需要设置 Alias。虽然 DNS 功能很强大，但我这种个人用户用不上， 而且好像 Cloudflare 的 DNS 更快而且免费。当时，我还把两个域名迁移到了 AWS，并且 都托管了。因此，这块 DNS 每个月花费大约 1.5 美元左右。最后，我想了想，还是把 DNS 解析迁移到了 Cloudflare。值得一提的是，AWS 的域名续费很贵，每个月 com 域名要花费 12+美元，而 Cloudflare 大约是 7 美元左右。这也是一笔支出，几乎相当于一个月的 EC2 费用。对于个人用户来说，实在不划算。\n总结一下，AWS 提供了很多功能和选项，但对于个人用户来说，有些功能可能用不上，而且 费用也会让人感到意外。因此，在使用之前，一定要仔细了解每个选项的费用计算方式，以 免产生不必要的额外费用。\n从 2024 年 2 月开始，AWS 开始对公网 IPv4 地址收费，无论是弹性 IP 还是绑定到 EC2 上。根据计算，每月大约需要花费约 3 美元。考虑到这并不划算，我决定移除公网 IPv4 地址，将服务器改为纯 IPv6。这样一来，我的网站基本上无法直接访问了，因为在很多情 况下，对 IPv6 的支持并不完善。我尝试了 AWS CloudFront 的解决方案，但发现 CloudFront 的源站访问不支持 IPv6，也不支持内网的 IPv4 地址。我思前想后，好像没有 解决方案了？（实际上，可以将域名托管到 Cloudflare，然后开启代理）\n综上所述，作为个人用户，我不推荐使用 EC2 等产品。如果必须使用，建议选择类似 Lightsail 这样的产品，计费较为简单，而且不会在不知不觉中开启\u0026quot;增值服务\u0026quot;，账单无法 控制。而且最好使用用 AWS 托管比较轻量的静态网站，用 Lambda、Amplify、S3 这类产 品，按量付费才是划算的。对于域名和 DNS 方面，个人用户推荐选择像 Cloudflare 这样 的服务提供商，因为它们免费且性能很好，而且域名续费也不贵。我认为 AWS 的产品更适 合企业等大型用户，这类用户可以协商价格，相比我们按照官网给出的标价会更划算。最后 需要提醒的是，AWS 的技术服务需要额外付费！\n当然，AWS 也有它的优势。我的 1 核心（平均性能为 20%）的机器能够运行很多服务，并 且响应速度也很快。一年下来也非常稳定，几乎没有出现过什么问题。另外，像 CloudFront 这样的加速服务，每个月的免费额度居然有 1TB，而且还是永久的。因此，我 将我一些速度敏感的站点都配置在 AWS 的 CloudFront 上，一年来发现速度稳定且响应快 速。只是配置有些复杂，需要一些基础的 WEB 技术知识。\n现状 目前，我将我的博客网站和 RSS 订阅等动态网站迁移到了专业的网站托管服务商。我选择 了一个可靠的服务商，他们提供了稳定的运行环境和安全性保障。不仅如此，我还趁着最近 的黑色星期五促销，以非常优惠的价格购买了几年的服务，这样我可以放心地运行我的网 站。这家网站托管服务商还提供了许多附加服务，比如邮件服务，让我可以方便地与读者进 行沟通。他们还提供了缓存加速功能，可以提升网站的加载速度，让访问者有更好的体验。 另外，他们还提供了域名注册服务，让我可以方便地管理我的域名。管理界面也非常简单易 用，让我可以轻松地进行网站的管理和维护。对于个人用户来说，这样的服务就足够了。\n除了博客网站，我还有一些个人开源项目使用的静态网站需要托管。为了简化操作，我选择 了 Netifly 作为静态网站托管平台。Netifly 提供了简单易用的界面，让我可以快速部署 和管理我的静态网站。他们的服务非常可靠，我可以放心地将我的项目托管在他们的平台 上。\n另外，为了进行后端开发和测试，我计划使用 AWS 的 EC2 来部署一些个人开发的后端程 序。EC2 是一个强大的云计算服务，可以提供稳定的运行环境和高性能的计算资源。我还在 EC2 上配置了公网 IPv6 地址，为将来使用 Cloudflare 代理做准备。使用 Cloudflare 代 理可以避开 IPV4 公网收费的问题。\n总的来说，我对我目前的网站的迁移和托管选择感到非常满意。我相信在接下来的几年里， 我的网站将能够稳定运行，并为读者提供更好的体验。\n总结 在使用 AWS 服务一年后，我有以下几点个人感受和总结：\n我选择使用 AWS 是因为我个人有一些需要在 AWS 上运行的服务，如博客网站、Git 仓库 等。 我尝试了 EC2 产品，并先试用了 T2.micro 实例，但发现内存不够用导致机器卡死，后 来换成了 T2.small 型机器，并购买了三年的保留实例以降低费用。 我发现系统盘和数据盘的费用都按照我申请的规格计算，费用查找界面也很复杂，需要一 个一个取消勾选来查找具体费用。 为了削减成本，我取消了快照备份、数据盘，并减小了系统盘的容量。 我发现 DNS 托管每个域名每个月要 0.5 美元，并且根据 DNS 访问次数还会额外计费。 我最终将 DNS 解析迁移到了 Cloudflare。 从 2024 年开始，我移除了公网 IPv4 地址，将服务器改为纯 IPv6。然而，由于 IPv6 支持不完善，导致我的网站无法直接访问，尝试了 AWS CloudFront 的解决方案但遇到了 限制。 作为个人用户，我不推荐使用 EC2 等专业的产品，而是建议选择类似 Lightsail 这样的 产品，并使用像 Cloudflare 这样的服务提供商来处理域名和 DNS 需求。 总的来说，我认为 AWS 对于个人用户来说费用较高且某些功能可能用不上。在使用之前， 一定要仔细了解每个选项的费用计算方式，以免产生不必要的额外费用。\n","permalink":"https://blog.bktus.com/archives/fms9jf/","summary":"\u003cp\u003e\u003cstrong\u003e原文经过 AI 润色，非本人文风。\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e从 2022 年 11 月开始，我开始使用 AWS 服务，已经接近 1 年的时间了，而且不是免费套\n餐。从一开始对它的使用感到新奇和陌生，到现在对它的了解更加深入，期间经历了很多有\n趣的事情。那么，为什么我选择使用 AWS 呢？因为我个人有一些需要在 AWS 上运行的服\n务，比如博客网站（Wordpress）、Git 仓库、RSS 订阅等等。当时，我觉得 AWS 在云服务\n市场占有最大的份额，所以它的解决方案应该已经非常成熟了。对于 AWS，我还是有一定的\n信任，愿意花钱购买适合的服务，以确保我的个人托管业务能够长期稳定运行。\u003c/p\u003e\n\u003cp\u003e在开始使用 AWS 之前，我也尝试过免费套餐，但是一直没有找到合适的需求场景，所以用\n了一段时间后就不再使用了。这样，我的免费套餐周期就白白浪费了。\u003c/p\u003e\n\u003cp\u003e当我第一次接触 AWS 的时候，我首先尝试了 EC2 产品，并试用了 1 核心 1G 内存的\nT2.micro 实例，但后来发现内存不够用，导致机器卡死。因此，我决定换成 T2.small 型\n机器，它拥有 1 核心 2G 内存，并且我还增加了 SWAP 分区，每个月大概需要花费 17 美\n元。虽然这个价格包括了带宽费用，但我仍然觉得有些贵。然后，我开始考虑是否可以使用\nAWS 的保留实例，听说可以减少 60%-70%的费用，看起来非常划算。于是，我下定决心购买\n了三年的保留实例，先支付了 170 美元，然后每个月承诺支付 4.75 美元。这样一来，每\n个月的费用大约是 8.84 美元，这个价格看起来相对合理。\u003c/p\u003e\n\u003cp\u003e然后遇到了一个坑，当时我配置了 30G 的系统盘和 10G 的数据盘，以为 30G 的系统盘是\n免费的。然而，我又考虑到数据的安全性，因此还增加了定期快照备份，以为快照的费用不\n会很多。最后，月度账单一出来，竟然要 20 美元！我很惊讶钱都花在哪里了？\u003c/p\u003e","title":"关于个人对AWS服务的使用感受"},{"content":"一般而言，我们总是倾向于捡着他人走过的路来走。一旦偏离这个所谓的“大众路线”，我们 很可能就会觉得自己处于悬空状态，从而陷入一种循环地自我怀疑中，总认为自己处于一种 脚不着地的危险的状态。我感觉这是我们倾向于认为，大众的某种“主流”的观念是一种无可 辩驳地正确，从而不会去思考其中正确性来源的几方观念的出发点，以及这些观念背后的逻 辑。\n具体来说在我们的生活中，常常会有一种压力，让我们按照主流的方式去行动。这种压力直 接来自社会与文化的期望、他人的看法以及我们自身对所谓“成功”的刻板印象。我们害怕偏 离主流，也害怕在便利主流后失败，从而被周围的人认为是典型异类或失败者的活生生的例 子。因此，我们倾向于随着周围的人选择安全、确定的道路，即那些大家已经验证过的方 法。顺从主流方式可以当然可以带来一些实实在在的成果，但这种方式并不一定最适合作为 单独个体的某个人，你可能并未从自身的具体情况的细节出发来选择，而可能带有一定的盲 目性。我们不断仰视其他人的成功，给自己制造一个个“神像”，时时刻刻去回忆去参拜，在 谈论中夹杂这感叹、羡慕或者是丝丝的嫉妒。但我们强调要像这些“神像”看齐，要成为他 们，但却始终忽视了自己的特点。我能不能成为另一种定义下的“神像”？这个是很多人都很 少去思考的问题，很多时候大多是人可能认为这种思考无意义。\n这个时候想象一下，如果别人向你寻求有关他们当下困难的决定或者说是某段时期的人生道 路，你可能会毫无思考地说出这些“主流”方式，但当别人进一步问你为什么这些方式是正确 的？是否可以列出实际的步骤并从自身出发去论证这些步骤的合理性？你可能第一时间就不 知道如何回答了。\n虽然说，当你跳出固有的“大众”思维去思考的时候，想着我可否在这个主干道的这个岔路口 上走拐进另一条路。这可能会产生很多困惑，因为这对于你思维的惯性来说，何尝不是一次 急刹车？也许你第一次考虑一种未曾设想的可能性。并且有可能突然发现原来这件事的逻辑 是非常复杂的并陷入一种思维错乱中。在你尝试你认为是正确的新方法时，也有可能发现你 其实是走了弯路。最后你可能会得出结论，主流方法也是有一定道理的，是基本上正确的。 然而，只有意识到这些，有意识地去了解这些，在拥有可行性的基础上去试着尝试这些，你 才能在不断尝试和试错中更清晰地把握事情的本质，最终正确认识“主流”方式的合理性以及 它为什么会存在。这个时候你的脚或许真正开始走在实地上，这大概会逐渐在内心深处建立 处一种内在的自信，不断引导你尝试其他的可能性，从而有可能找到最适合你个人特质和当 下实际情况的道路。\n所以，我个人认为不要因为害怕事情的复杂性而放弃寻找其他解决方法的可能性。这个时候 要相信自己的现有的能力和基于大量有效思考而对事物判断产生的直觉，不要过分依赖他人 的意见和观点，从而自己的一套思想理论和解决问题的宏观的体系。最重要的是，要始终坚 持自己的价值观和目标，顺着这个内心的最终指引找到真正适合道路，才能有效避免在大量 的思考和尝试汇总迷失方向。\n","permalink":"https://blog.bktus.com/archives/pzovx4/","summary":"\u003cp\u003e一般而言，我们总是倾向于捡着他人走过的路来走。一旦偏离这个所谓的“大众路线”，我们\n很可能就会觉得自己处于悬空状态，从而陷入一种循环地自我怀疑中，总认为自己处于一种\n脚不着地的危险的状态。我感觉这是我们倾向于认为，大众的某种“主流”的观念是一种无可\n辩驳地正确，从而不会去思考其中正确性来源的几方观念的出发点，以及这些观念背后的逻\n辑。\u003c/p\u003e\n\u003cp\u003e具体来说在我们的生活中，常常会有一种压力，让我们按照主流的方式去行动。这种压力直\n接来自社会与文化的期望、他人的看法以及我们自身对所谓“成功”的刻板印象。我们害怕偏\n离主流，也害怕在便利主流后失败，从而被周围的人认为是典型异类或失败者的活生生的例\n子。因此，我们倾向于随着周围的人选择安全、确定的道路，即那些大家已经验证过的方\n法。顺从主流方式可以当然可以带来一些实实在在的成果，但这种方式并不一定最适合作为\n单独个体的某个人，你可能并未从自身的具体情况的细节出发来选择，而可能带有一定的盲\n目性。我们不断仰视其他人的成功，给自己制造一个个“神像”，时时刻刻去回忆去参拜，在\n谈论中夹杂这感叹、羡慕或者是丝丝的嫉妒。但我们强调要像这些“神像”看齐，要成为他\n们，但却始终忽视了自己的特点。我能不能成为另一种定义下的“神像”？这个是很多人都很\n少去思考的问题，很多时候大多是人可能认为这种思考无意义。\u003c/p\u003e\n\u003cp\u003e这个时候想象一下，如果别人向你寻求有关他们当下困难的决定或者说是某段时期的人生道\n路，你可能会毫无思考地说出这些“主流”方式，但当别人进一步问你为什么这些方式是正确\n的？是否可以列出实际的步骤并从自身出发去论证这些步骤的合理性？你可能第一时间就不\n知道如何回答了。\u003c/p\u003e\n\u003cp\u003e虽然说，当你跳出固有的“大众”思维去思考的时候，想着我可否在这个主干道的这个岔路口\n上走拐进另一条路。这可能会产生很多困惑，因为这对于你思维的惯性来说，何尝不是一次\n急刹车？也许你第一次考虑一种未曾设想的可能性。并且有可能突然发现原来这件事的逻辑\n是非常复杂的并陷入一种思维错乱中。在你尝试你认为是正确的新方法时，也有可能发现你\n其实是走了弯路。最后你可能会得出结论，主流方法也是有一定道理的，是基本上正确的。\n然而，只有意识到这些，有意识地去了解这些，在拥有可行性的基础上去试着尝试这些，你\n才能在不断尝试和试错中更清晰地把握事情的本质，最终正确认识“主流”方式的合理性以及\n它为什么会存在。这个时候你的脚或许真正开始走在实地上，这大概会逐渐在内心深处建立\n处一种内在的自信，不断引导你尝试其他的可能性，从而有可能找到最适合你个人特质和当\n下实际情况的道路。\u003c/p\u003e\n\u003cp\u003e所以，我个人认为不要因为害怕事情的复杂性而放弃寻找其他解决方法的可能性。这个时候\n要相信自己的现有的能力和基于大量有效思考而对事物判断产生的直觉，不要过分依赖他人\n的意见和观点，从而自己的一套思想理论和解决问题的宏观的体系。最重要的是，要始终坚\n持自己的价值观和目标，顺着这个内心的最终指引找到真正适合道路，才能有效避免在大量\n的思考和尝试汇总迷失方向。\u003c/p\u003e","title":"关于主流道路的一些思考"},{"content":"个人有一些图片托管的需求，在很多场景下比如说在博客文章中插入图片，在社交网站上分 享图片，抑或是个人开源项目中的图片等等，都需要在文档（特别是 MarkDown）中嵌入一 个 URL 直链来指向图片。对于一些免费的图床服务商，个人感觉并不是很信任。数据安全 是一方面，另一方面如果服务商跑路了，那么很多原先我创建的链接都会失效。据我所知， 这样的例子比比皆是。\n网上还有一些教程将 GitHub 当成免费图床来用。这样做弊端也很明显，GitHub 在国内的 访问速度非常慢，图片加载很慢。另外，缺少很多图床该有的功能，比如说自动压缩，自动 转换成 Webp，自动重命名等等。这样做也是不太可行的。下面就是一个我将 GitHub 用作 图床的例子。每次我还需要将图片移动到代码仓库里，然后 commit，push，我个人感觉很 麻烦。\n还有一段时间我试过把 WordPress 的 Midea 库当作图床来使用，但是我发现图片链接中带 有 wp-content/upload 这样的路径。然后链接中还会出现图片的名称。我感觉并不是很雅 观，而且在隐私的保护中有一种说不好的感觉。就像是别人一看这个 URL 链接，就能获知 一些图片以外的信息。下面就是用 WordPress 的 Media 库当图床例子。可以看到，产生的 直链不是很美观，而且还能暴露一些信息。\n所以，我这边产生了自建图床服务的想法。这种图床最好能自动转换 jpg、png 格式的图片 为 webp。这样图片就能比较高质量、快速地在用户浏览器中加载出来。有关 WebP 的简要 介绍如下：\nWebP is a raster graphics file format developed by Google intended as a replacement for JPEG, PNG, and GIF file formats. It supports both lossy and lossless compression,[8] as well as animation and alpha transparency.\nGoogle announced the WebP format in September 2010, and released the first stable version of its supporting library in April 2018.\n然后图床还要能够自动压缩图片，然后产生带有 MD5 或者 SHA256 的 URL 链接，能够不暴 露我的图片原始信息。然后还能够配置 CDN 加速，毕竟对于图床来说 CDN 非常重要，载入 速度很大程度上影响用户的终端体验。还有最好能够用 PHP 写成，因为这样我能够上传到 我的网站托管服务商进行托管，不需要另外去单独为这个买 VPS 来部署。\n搭建 EasyImages2.0 自建图床 经过查找，我发现一款叫做EasyImages2.0 的图床项目比较符合我的上述需要。这款图床用 PHP 写成，理论上能够在我的服务商托管 的规则容许下正常运行。而且不需要数据库，免去了额外的数据库的配置，节约时间。\n话不多说，先进入项目仓库下载最新发布版本。\n先解压检查目录，不出意外的话应该是这样。项目根路径下有 index.php，这种结构就可以 直接 FTP 上传到托管服务商 FTP 上。不需要额外的配置，应该就可以使用了。然后，我就 将压缩包上传到托管服务商的 FTP 上，解压到网站根目录 public_html 下（有些托管服务 商是根目录 www 目录）\n上述步骤完成后，FTP 目录应该是下面这样。这个时候我们记得提前去 app 目录下，将 upload.php 权限改为 0755。\n修改权限的操作详情如下。可以按照我这样来。\n还需要注意，根据该项目的兼容性描述，PHP 环境需要满足一定的要求。\n最低PHP 5.6,推荐PHP≥7.0及以上版本，需要 PHP 支持 fileinfo,iconv,zip,mbstring,openssl扩展,如果缺失会导致无法上传/删除图片 文件 上传视图提供文件列表管理和文件批量上传功能，允许拖拽（需要HTML5支持）来添加 上传文件，支持上传大图片，优先使用HTML5旧得浏览器自动使用Flash和 Silverlight的方式兼容\n请确保fileinfo,iconv,zip,mbstring,openssl这几个扩展已经安装妥当。找到下面这种 界面，然后用浏览器搜索检查是否都安装了。\n然后，配置好网站的域名 DNS 解析后。我们应该就能看到站点了。根据软件的提示检查环 境，设置用户名和密码后，就可以使用了。非常简单。我们接下来要进行一些设置，确保自 建图床站点的安全，然后我们需要开启一些功能。\n进入设置界面后，网站域名和图片域名可以配置相同的。如果你需要开启 CDN 加速，就配 置不同的域名。图片域名配置为你预想的 CDN 域名。其他配置项如网站标题、网站关键字 等待随意。\n对外展示设置最好配置成这样，安全起见，最好不开启任何未登录用户的广场查看功能。其 他默认即可。\n然后上传设置中，可以配置 URL 的样式（已上传文件的命名样式）。我这边为了安全起见 配置成 UUID 的样式，最大可能避免碰撞，然后用小写来保证 URL 的兼容性。我这边也配 置了将图片统一转换成 WEBP 的格式，格式上规范一些，并且能够在 WEB 上最优化传输。 其他配置可以不改。\n在 API 设置中自己建立一个新的 TOKEN 用，然后删掉初始的两个。如果有 Bot 访问需 求，用这个 API 很方便。\n图床安全设置方面记得关掉开放数据并开启登录上传和验证码，最大程度保证自建图床的安 全和隐私。\n其他设置可以根据自己的需要来。然后哪里不懂的，可以查阅项目文 档 来指导自己的设置。另外还有一些关于 HTTP 服务器的安全配 置， 需要特别注意。\n搭建 AWS CloudFront CDN 全球加速（永久免费） 自建图床准备妥当后，就可以开始着手搭建 CDN 加速了。目前 AWS Cloudfront 属于永久 免费套餐（免费套餐一年过期后也可以继续用），然后每个月有 1TB 的免费上载额度还有 数量 HTTP/HTTPS 访问访问额度。我这一年使用下来发现国内外访问都很快，没有什么问 题。另外，AWS Cloudfront 配置很灵活，还能提供免费的 SSL 证书。这些对于个人用途来 说，绰绰有余。所以我选择了 AWS 的服务，没有再去考虑 Cloudflare。不得不说一 下，AWS 的付费服务真的是很贵！！！\n然而首先你需要一个 AWS 的账号，并且能够享受免费套餐。这些教程网上一大把，可以去 搜搜。我这里就不讲了。准备好 AWS 账号后，进入 AWS 控制台。点击创建分配（什么叫做 分配，难道是 Distribution 的机翻）。\n然后源这个配置下，源域填写你的自建图床所在的域名，比如 image.xxxx.com。其他都是 可选的，特别注意不要开源护盾，这个要额外收费的。\n默认缓存行为这样配置，我们不缓存除了图片以外的其他资源。\n然后选择不要开启安全防护。因为开启了下个月又是一笔账单费用。后面就是按照我截图上 的来设置。备用域名可以选择你刚刚填写的在图床那边填写的预定的 CDN 域名，如果你希 望用 AWS 提供的域名来作为你图床的 CDN 直链域名，省得麻烦，那么可以不写。如果你填 写了备用域名，那么在下面的自定义 SSL 证书那一栏先点击请求证书，为域名生成一个 SSL 证书。生成步骤就按照界面提示来，这里就不多说了。SSL 证书发下来后，点击刷新输 入框旁边的按钮来加载刚刚生成的证书，选择即可。\n最后的一些设置，IPv6 可以打开，毕竟现在很多网络环境都支持 IPv6 了。其他随意。在 一切都妥当后，就可以点击创建分配了。\n创建好后，你就可以看到这样的界面。你可以看到“分配域名“，你可异质结把这个当成你的 图片直链 URL，也可以在你的 DNS 解析中加入一条 CNAME 记录指向这个域名（前提是刚才 设置了备用域名）。\n在行为栏目，我们选择创建行为。\n然后路径模式填写/i/*，这样就可以 CDN 代理访问所有的图片。自动压缩选 No，因为图 床已经做过一层压缩了。而且图片也不确定是不是可压缩对象，这边策略还是保证 CDN 的 行为可控，免去一些麻烦。其他的就按照我截图来配置即可。\n然后，一切设置妥当后，就可以点击页面底部的”创建行为“按钮。最后，为了更好的体验， 你可以创建另外两条路径模式。如下图所示，除了路径模式不一样以外，其他配置和我们刚 刚建立的行为的配置都一样。\n图床中配置 CDN 直链 这里再提示一下，图床”图片域名“配置中检查 CDN 直链是否正确配置。示意如下。\n最后 上面的流程都走完，都检查正确后你就可以随意上传图片，并获取直链了。非常方便。然后 如果还嫌麻烦，还可以建立一个 Bot，调用 API 来完整这个过程。这里就不详细展开了。\n获取直链后，可以直接粘贴到 MarkDown 文档中使用。\n","permalink":"https://blog.bktus.com/archives/57cscu/","summary":"\u003cp\u003e个人有一些图片托管的需求，在很多场景下比如说在博客文章中插入图片，在社交网站上分\n享图片，抑或是个人开源项目中的图片等等，都需要在文档（特别是 MarkDown）中嵌入一\n个 URL 直链来指向图片。对于一些免费的图床服务商，个人感觉并不是很信任。数据安全\n是一方面，另一方面如果服务商跑路了，那么很多原先我创建的链接都会失效。据我所知，\n这样的例子比比皆是。\u003c/p\u003e\n\u003cp\u003e网上还有一些教程将 GitHub 当成免费图床来用。这样做弊端也很明显，GitHub 在国内的\n访问速度非常慢，图片加载很慢。另外，缺少很多图床该有的功能，比如说自动压缩，自动\n转换成 Webp，自动重命名等等。这样做也是不太可行的。下面就是一个我将 GitHub 用作\n图床的例子。每次我还需要将图片移动到代码仓库里，然后 commit，push，我个人感觉很\n麻烦。\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"Image\" loading=\"lazy\" src=\"https://image.cdn.bktus.com/i/2/2025/04/28/1afe06e2-82d2-fa2c-d2e8-17ac420b42c2-2.webp\"\u003e\u003c/p\u003e\n\u003cp\u003e还有一段时间我试过把 WordPress 的 Midea 库当作图床来使用，但是我发现图片链接中带\n有 wp-content/upload 这样的路径。然后链接中还会出现图片的名称。我感觉并不是很雅\n观，而且在隐私的保护中有一种说不好的感觉。就像是别人一看这个 URL 链接，就能获知\n一些图片以外的信息。下面就是用 WordPress 的 Media 库当图床例子。可以看到，产生的\n直链不是很美观，而且还能暴露一些信息。\u003c/p\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"https://image.cdn.bktus.com/i/2/2025/04/28/4048195f-be6c-2764-6418-bef3891ca4cb-2.webp\"\u003e\u003c/p\u003e\n\u003cp\u003e所以，我这边产生了自建图床服务的想法。这种图床最好能自动转换 jpg、png 格式的图片\n为 webp。这样图片就能比较高质量、快速地在用户浏览器中加载出来。有关 WebP 的简要\n介绍如下：\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003eWebP\u003c/strong\u003e is a \u003ca href=\"https://en.wikipedia.org/wiki/Raster_graphics\"\u003eraster\ngraphics\u003c/a\u003e \u003ca href=\"https://en.wikipedia.org/wiki/Image_file_format\"\u003efile\nformat\u003c/a\u003e developed\nby \u003ca href=\"https://en.wikipedia.org/wiki/Google\"\u003eGoogle\u003c/a\u003e intended as a replacement\nfor \u003ca href=\"https://en.wikipedia.org/wiki/JPEG\"\u003eJPEG\u003c/a\u003e, \u003ca href=\"https://en.wikipedia.org/wiki/Portable_Network_Graphics\"\u003ePNG\u003c/a\u003e,\nand \u003ca href=\"https://en.wikipedia.org/wiki/GIF\"\u003eGIF\u003c/a\u003e file formats. It supports\nboth \u003ca href=\"https://en.wikipedia.org/wiki/Lossy_compression\"\u003elossy\u003c/a\u003e and \u003ca href=\"https://en.wikipedia.org/wiki/Lossless_compression\"\u003elossless\u003c/a\u003e compression,\u003ca href=\"https://en.wikipedia.org/wiki/WebP#cite_note-8\"\u003e[8]\u003c/a\u003e as\nwell\nas \u003ca href=\"https://en.wikipedia.org/wiki/Computer_animation\"\u003eanimation\u003c/a\u003e and \u003ca href=\"https://en.wikipedia.org/wiki/Alpha_compositing\"\u003ealpha\ntransparency\u003c/a\u003e.\u003c/p\u003e","title":"自建图床服务并配置免费CDN全球加速 过程记录"},{"content":"最近，合规的要求逐渐渗透到技术层面。最近的一段时间，总有产品来找，说要实现这样那 样的合规需求。或者是，做一些什么合规的调查问卷。我感觉，合规也主要就是说对于用户 信息的存储、访问需要规范化，然后用户能够逐渐开始掌控自己的数据。然后还有一些人事 上、组织架构上的变动对于技术层面造成的冲击，比如说一个业务被调度到其他部门去了， 然后我们还在一直和这个业务共享数据库等资源。这个时候费用问题就突出了，就是说钱该 算在那边。虽然是一个公司、一个事业群，但感觉可能是由于公司内部的核算机制问题，对 于这些成本问题还是比较较真的，总会掰扯什么：他们用了我们的数据库，钱还在我们这边 什么的。有时候感觉，这些都是属于内部消耗，并不是出于发展的眼光看待问题。另外，别 的业务可能由于什么需求调整，需要修改一些公用的数据库结构或者接口，就比较麻烦了。 因为这个时候，我们为他们花费工时来做这些事情，并不能得到任何肯定。但是，又会被那 边的人催着，天天轰炸。自己夹在中间，感觉就比较难受。\n然后，我上个周报提到的 PHP 网关迁移的问 题，其实前一两个月就开始在做了。其实也早就写好了，但是在后续切换流量到新服务的时 候，总是会遇到这样那样的问题。具体表现就是，测试环境流量切换很久了没有人来找，一 切正式环境这样那样的人就来找了。所这个接口突然用不了，那个接口突然报错什么的。这 种情况下，由于问题发生在线上，只能先把流量切回去再分析问题。一来二回，一个星期就 过去了。有时候，切换了 4、5 天才会有人来找。这样一个流程下来久更久了。\nleader 一直说这件事情做了很久如何，但实际的情况就是这样。虽然这个服务的后续具体 的代码编写和维护工作并非我来做了，但是我也知道一个新的服务要能够完全替代就有的服 务必须有一个这样的流程。因为在缺乏文档和测试用例的情况下，你不知道一个接口有什么 暗藏的机制，也无法完全清楚我写的代码是否和原来的等效。所以执行层面，很多事情没有 明确汇报，但是我感觉管理上也要能体会到。虽然过程比较反复、艰难，但最终这个服务的 重构工作在本周还是完成了，所有的流量都迁移到新的服务上了。也就是代表，这个老 PHP 网关的生命周期终结了。 我接手主要的后台业务也快半年了，现在发现刚接手的时候由于 缺乏经验，总感觉别人的技术高深莫测。但是，自己接触久了才知道里面有这样那样不完善 的地方。还存在一些设计缺陷，非常严重的缺陷都有。比如说把 Redis 当作数据库使用， 说白了就是没有设置缓存过期时间，然后 Redis 里面的数据又很重要不能丢失。这对服务 里面还存在着一些缺乏文档、维护人员早已离职的老旧服务，这些老旧服务基本上平时无法 来仔细研究。但这些服务中总有那么一两个依然在被访问，或者早就不能访问了，客户这几 天突然发现然后急着催我们解决。很多安全问题也需要整改，某几个 API 账号有一些高位 的权限需要收敛，但是我并不知道这个几个账号具体是做什么的。这些问题，就是日常频繁 遇到的，很难避免。\n","permalink":"https://blog.bktus.com/archives/onjhri/","summary":"\u003cp\u003e最近，合规的要求逐渐渗透到技术层面。最近的一段时间，总有产品来找，说要实现这样那\n样的合规需求。或者是，做一些什么合规的调查问卷。我感觉，合规也主要就是说对于用户\n信息的存储、访问需要规范化，然后用户能够逐渐开始掌控自己的数据。然后还有一些人事\n上、组织架构上的变动对于技术层面造成的冲击，比如说一个业务被调度到其他部门去了，\n然后我们还在一直和这个业务共享数据库等资源。这个时候费用问题就突出了，就是说钱该\n算在那边。虽然是一个公司、一个事业群，但感觉可能是由于公司内部的核算机制问题，对\n于这些成本问题还是比较较真的，总会掰扯什么：他们用了我们的数据库，钱还在我们这边\n什么的。有时候感觉，这些都是属于内部消耗，并不是出于发展的眼光看待问题。另外，别\n的业务可能由于什么需求调整，需要修改一些公用的数据库结构或者接口，就比较麻烦了。\n因为这个时候，我们为他们花费工时来做这些事情，并不能得到任何肯定。但是，又会被那\n边的人催着，天天轰炸。自己夹在中间，感觉就比较难受。\u003c/p\u003e\n\u003cp\u003e然后，我\u003ca href=\"https://blog.bktus.com/archives/3680\"\u003e上个周报\u003c/a\u003e提到的 PHP 网关迁移的问\n题，其实前一两个月就开始在做了。其实也早就写好了，但是在后续切换流量到新服务的时\n候，总是会遇到这样那样的问题。具体表现就是，测试环境流量切换很久了没有人来找，一\n切正式环境这样那样的人就来找了。所这个接口突然用不了，那个接口突然报错什么的。这\n种情况下，由于问题发生在线上，只能先把流量切回去再分析问题。一来二回，一个星期就\n过去了。有时候，切换了 4、5 天才会有人来找。这样一个流程下来久更久了。\u003c/p\u003e\n\u003cp\u003eleader 一直说这件事情做了很久如何，但实际的情况就是这样。虽然这个服务的后续具体\n的代码编写和维护工作并非我来做了，但是我也知道一个新的服务要能够完全替代就有的服\n务必须有一个这样的流程。因为在缺乏文档和测试用例的情况下，你不知道一个接口有什么\n暗藏的机制，也无法完全清楚我写的代码是否和原来的等效。所以执行层面，很多事情没有\n明确汇报，但是我感觉管理上也要能体会到。虽然过程比较反复、艰难，但最终这个服务的\n重构工作在本周还是完成了，所有的流量都迁移到新的服务上了。也就是代表，这个老 PHP\n网关的生命周期终结了。 我接手主要的后台业务也快半年了，现在发现刚接手的时候由于\n缺乏经验，总感觉别人的技术高深莫测。但是，自己接触久了才知道里面有这样那样不完善\n的地方。还存在一些设计缺陷，非常严重的缺陷都有。比如说把 Redis 当作数据库使用，\n说白了就是没有设置缓存过期时间，然后 Redis 里面的数据又很重要不能丢失。这对服务\n里面还存在着一些缺乏文档、维护人员早已离职的老旧服务，这些老旧服务基本上平时无法\n来仔细研究。但这些服务中总有那么一两个依然在被访问，或者早就不能访问了，客户这几\n天突然发现然后急着催我们解决。很多安全问题也需要整改，某几个 API 账号有一些高位\n的权限需要收敛，但是我并不知道这个几个账号具体是做什么的。这些问题，就是日常频繁\n遇到的，很难避免。\u003c/p\u003e","title":"二零二三年三月第一周周报"},{"content":"本周我发现有些服务框架写得并不是很好，特别是某些 Java 框架。当 CPU 占用达到 40% 左右的时候，就会出现大量的超时现象。这些服务，CPU 核心和内存容量不是不多，工作线 程数目也不是不够。但是，就是跑不满 CPU。用 Java 性能工具分析发现，其实发现大部分 工作线程处于 Idel 或者 Waiting 状态。目前综合所有情况分析，还是百思不得其解。NIO 也用上了，还用的是 Netty 框架，但是吞吐量就是上不去。通过对于线程的分析，发现并 没有特别繁忙的业务线程。推断应该是 IO 或者某种等待机制导致了这种低处理效率。 这 次我是准备通过削减节点数量来降本，在执行前并没有考虑特别多的因素。所以我在削减节 点容量的时候，只是通过查看 CPU 的负载来判断节点是否能够承受。当我将北京地域的 workload 平均 CPU 占用率提升到 35%-40%的时候，整个服务出现了大量的超时现象。当时 把我吓了一跳。后面从监控上分析，几乎是整个背景地域的节点都超时了，也就是处于一 种”休克“状态。\n本周也继续重构某一个老的 PHP 网关服务，新的网关用 Java 写成。但是我其实不是很赞 成网关用 Java，毕竟 Java 这种语言的执行特性很大程度上决定了它不适合特别高的并 发。并且，我们目前所用的都是 JDK 8，并没有引入轻量级的线程，每个 4c8g 容器内线程 最多也就 800 个，多了线程切换开销就会特别大。所以单个容器的吞吐量有限，承载同样 的流量需要更多的容器。刚开始我是用 Go 重写的，这个框架很不错而且也有专门的团队维 护，但是 leader 还是让我用部门自研的那个 Java 框架。可能人事上的考虑居多吧，无 奈，我还是先写吧。\n","permalink":"https://blog.bktus.com/archives/rr0u6h/","summary":"\u003cp\u003e本周我发现有些服务框架写得并不是很好，特别是某些 Java 框架。当 CPU 占用达到 40%\n左右的时候，就会出现大量的超时现象。这些服务，CPU 核心和内存容量不是不多，工作线\n程数目也不是不够。但是，就是跑不满 CPU。用 Java 性能工具分析发现，其实发现大部分\n工作线程处于 Idel 或者 Waiting 状态。目前综合所有情况分析，还是百思不得其解。NIO\n也用上了，还用的是 Netty 框架，但是吞吐量就是上不去。通过对于线程的分析，发现并\n没有特别繁忙的业务线程。推断应该是 IO 或者某种等待机制导致了这种低处理效率。 这\n次我是准备通过削减节点数量来降本，在执行前并没有考虑特别多的因素。所以我在削减节\n点容量的时候，只是通过查看 CPU 的负载来判断节点是否能够承受。当我将北京地域的\nworkload 平均 CPU 占用率提升到 35%-40%的时候，整个服务出现了大量的超时现象。当时\n把我吓了一跳。后面从监控上分析，几乎是整个背景地域的节点都超时了，也就是处于一\n种”休克“状态。\u003c/p\u003e\n\u003cp\u003e本周也继续重构某一个老的 PHP 网关服务，新的网关用 Java 写成。但是我其实不是很赞\n成网关用 Java，毕竟 Java 这种语言的执行特性很大程度上决定了它不适合特别高的并\n发。并且，我们目前所用的都是 JDK 8，并没有引入轻量级的线程，每个 4c8g 容器内线程\n最多也就 800 个，多了线程切换开销就会特别大。所以单个容器的吞吐量有限，承载同样\n的流量需要更多的容器。刚开始我是用 Go 重写的，这个框架很不错而且也有专门的团队维\n护，但是 leader 还是让我用部门自研的那个 Java 框架。可能人事上的考虑居多吧，无\n奈，我还是先写吧。\u003c/p\u003e","title":"二零二三年二月第四周技术周报"},{"content":"本周主要处理一个节前发现的风险项。某个服务，在使用 Redis 的时候并没有为键设置 TTL，而是寄希望于 redis 的淘汰策略。我看这个服务使用的 Redis 设置了 LRU 淘汰策 略。这种策略看似比较完美，但是当某一较短时间段内，写入流量很大的时候，存在隐患。 这个时候 Redis 会触发淘汰过程，并集中尽力于此，以求能腾出足够的空间。这意味 着，Redis 不能很好地执行查询等正常操作了。这会造成业务层对 Redis 的读写延时均出 现剧烈波动。到现在，我已经遇到过 2 次这样的问题了。而且不设置 TTL，Redis 一直处 于 100%的使用率状态，我们从容量上并不能获知按照现在的业务量配置多少购买容量足 够。业务低峰时期是否能够通过缩容降低成本也是不能确定的。所以，为 Redis 留出一个 足够的可用空间是十分重要的。\n所以，我需要改造这个服务，使之对每个写入的新键设置 TTL。按照 Redis 的逻辑，如果 键在写入的时候已经存在，则也会被设置一个 TTL。为了业务稳定，我不求所有的键都设置 TTL。所以舍弃了在遍历 Redis 寻找未设置 TTL 的键的方案。其实我调研过这个方案，在 遍历的时候，使用需要游标来做。设置的 TTL 长短也有讲究，为了防止同一时间大量键过 期造成延时的大波动，设置 TTL 的时候在基础时长后加入了随机时长。假设基础时长为 T，那么加入随机时长后，TTL 的长度会处于 T ～ 2T 之间。这意味着，要求存留时间越长 的键，过期的分布也越广。这能够确保业务延时更加平稳，防止数据库压力过大。\n","permalink":"https://blog.bktus.com/archives/o6wu6l/","summary":"\u003cp\u003e本周主要处理一个节前发现的风险项。某个服务，在使用 Redis 的时候并没有为键设置\nTTL，而是寄希望于 redis 的淘汰策略。我看这个服务使用的 Redis 设置了 LRU 淘汰策\n略。这种策略看似比较完美，但是当某一较短时间段内，写入流量很大的时候，存在隐患。\n这个时候 Redis 会触发淘汰过程，并集中尽力于此，以求能腾出足够的空间。这意味\n着，Redis 不能很好地执行查询等正常操作了。这会造成业务层对 Redis 的读写延时均出\n现剧烈波动。到现在，我已经遇到过 2 次这样的问题了。而且不设置 TTL，Redis 一直处\n于 100%的使用率状态，我们从容量上并不能获知按照现在的业务量配置多少购买容量足\n够。业务低峰时期是否能够通过缩容降低成本也是不能确定的。所以，为 Redis 留出一个\n足够的可用空间是十分重要的。\u003c/p\u003e\n\u003cp\u003e所以，我需要改造这个服务，使之对每个写入的新键设置 TTL。按照 Redis 的逻辑，如果\n键在写入的时候已经存在，则也会被设置一个 TTL。为了业务稳定，我不求所有的键都设置\nTTL。所以舍弃了在遍历 Redis 寻找未设置 TTL 的键的方案。其实我调研过这个方案，在\n遍历的时候，使用需要游标来做。设置的 TTL 长短也有讲究，为了防止同一时间大量键过\n期造成延时的大波动，设置 TTL 的时候在基础时长后加入了随机时长。假设基础时长为\nT，那么加入随机时长后，TTL 的长度会处于 T ～ 2T 之间。这意味着，要求存留时间越长\n的键，过期的分布也越广。这能够确保业务延时更加平稳，防止数据库压力过大。\u003c/p\u003e","title":"二零二三年二月第三周技术周报"},{"content":"从一月底到二月初，都属于春节的范畴。期间，负责保障春节阶段的运行，需要随时待命处 理线上问题。我一直处于一种担忧的状态，好在线上问题并没有主动找上门。整个春节期间 维持整体不动是最好的。 这周我在评估一个重要的需求，所带来的影响。我认为，对于一 个新的业务需求，特别是应用于一个复杂的业务系统，需要考虑多方面的影响。如果这个时 候，对这个系统并不是特别熟悉，经验不多的话，最好选择做最小改动。这不是保守，而是 将影响控制在你可以想象地到的地方。因为你不会知道在什么地方，某个违背直觉的机制在 运行重要的业务逻辑。得出这样的结论，并不是出于我的想象。\n这篇文章写于半年后，到我写这篇文章的时候，我已经遇到至少两次这样的事情了。当时我 对某个服务做出了大刀阔斧的调整，在调整完的时候，一切正常。发布后，也看似正常。直 到若干星期之后，我偶然发现了某个串联上下游的机制，它差点受到我的影响。在承接一个 业务系统的时候，很大概率它是转接和很多手的，藏有很多你不知道的历史，所以框架、核 心逻辑能不动就不动。 然后，本周彻底解决了针对一个安全加密服务加密接口不兼容中文 的问题。主要的问题是，它将加密的原文直接作为 Redis 的 Key。当原文中存在中文，会 发现虽然 Key 存储了，但是找不到。我的方案是在存储的时候，Key 的内容不要直接包含 任何业务原文，先取哈希。这样既能够避免一些编码、兼容的问题，也可以大大提升安全 性。\n","permalink":"https://blog.bktus.com/archives/q1pqco/","summary":"\u003cp\u003e从一月底到二月初，都属于春节的范畴。期间，负责保障春节阶段的运行，需要随时待命处\n理线上问题。我一直处于一种担忧的状态，好在线上问题并没有主动找上门。整个春节期间\n维持整体不动是最好的。 这周我在评估一个重要的需求，所带来的影响。我认为，对于一\n个新的业务需求，特别是应用于一个复杂的业务系统，需要考虑多方面的影响。如果这个时\n候，对这个系统并不是特别熟悉，经验不多的话，最好选择做最小改动。这不是保守，而是\n将影响控制在你可以想象地到的地方。因为你不会知道在什么地方，某个违背直觉的机制在\n运行重要的业务逻辑。得出这样的结论，并不是出于我的想象。\u003c/p\u003e\n\u003cp\u003e这篇文章写于半年后，到我写这篇文章的时候，我已经遇到至少两次这样的事情了。当时我\n对某个服务做出了大刀阔斧的调整，在调整完的时候，一切正常。发布后，也看似正常。直\n到若干星期之后，我偶然发现了某个串联上下游的机制，它差点受到我的影响。在承接一个\n业务系统的时候，很大概率它是转接和很多手的，藏有很多你不知道的历史，所以框架、核\n心逻辑能不动就不动。 然后，本周彻底解决了针对一个安全加密服务加密接口不兼容中文\n的问题。主要的问题是，它将加密的原文直接作为 Redis 的 Key。当原文中存在中文，会\n发现虽然 Key 存储了，但是找不到。我的方案是在存储的时候，Key 的内容不要直接包含\n任何业务原文，先取哈希。这样既能够避免一些编码、兼容的问题，也可以大大提升安全\n性。\u003c/p\u003e","title":"二零二三年二月第二周技术周报"},{"content":"Kubernetes（k8s）是一种流行的容器编排平台，它允许开发人员在云环境中部署、管理和 自动化容器化应用程序。在 Kubernetes 中，CPU 资源的分配是一个关键的问题，它直接影 响着应用程序的性能和可靠性。本文将介绍 Kubernetes 中的 CPU 资源分配机制，包括 CPU 请求和限制、CPU Share 机制以及 CPU 调度器等相关概念，以帮助开发人员更好地控 制容器的 CPU 分配，从而提高应用程序的性能和可靠性。\nCPU 分配单位 在 Kubernetes 中，CPU 的分配单位是 millicpu（毫核），一个 CPU 资源等于 1000 毫 核。例如，一个 Pod 请求 0.5 个 CPU 资源，可以表示为 500 毫核。它是基于 Linux 内 核的 CPU 分配机制实现的。在 Linux 内核中，CPU 时间是以时间片的形式分配的，每个时 间片通常为几毫秒。Kubernetes 利用这个特性，将 CPU 时间片分配单位转换为毫核。\nCPU 资源分配机制 在 Kubernetes 中，CPU 资源的分配是通过两种方式来实现的：CPU 请求和限制。CPU 请求 用于告诉 Kubernetes 调度器，该 Pod 至少需要多少 CPU 资源才能正常运行；CPU 限制用 于告诉 Kubernetes，该 Pod 最多可以使用多少 CPU 资源。如果节点上可用的 CPU 资源不 足以满足 Pod 的 CPU 请求，该 Pod 就无法调度到该节点上运行。如果节点上可用的 CPU 资源不足以满足 Pod 的 CPU 限制，该 Pod 仍然可以运行，但可能会受到 CPU 资源的限 制，导致运行缓慢或者出现其他问题。 需要注意的是，CPU 请求和限制是 Kubernetes 中 的两个独立的概念。通常情况下，应该根据应用程序的实际需求来设置 CPU 请求和限制。 如果设置的 CPU 请求过低，可能会导致 Pod 无法正常运行或者运行缓慢；如果设置的 CPU 限制过高，可能会导致节点上的其他 Pod 的 CPU 资源不足，影响整个集群的性能和可用 性。\nCPU Share 机制 另外，在 Kubernetes 中，CPU Share（CPU 份额）是一种用于控制容器 CPU 资源分配的机 制。它通过为每个容器分配一个相对权重值来控制容器之间的 CPU 分配比例。CPU Share 机制是 Linux 内核的一个特性，它被用于控制进程之间的 CPU 时间分配，Kubernetes 利 用这个特性为容器分配 CPU 资源。 CPU Share 是一个整数值，表示相对于其他容器的 CPU 使用量。例如，如果一个容器的 CPU Share 值是 1024，另一个容器的 CPU Share 值是 512，那么前者将获得更多的 CPU 时间，约为后者的两倍。如果两个容器的 CPU Share 值 相等，它们将按照时间片轮转的方式共享 CPU 资源。 在 Kubernetes 中，可以通过设置容 器的 CPU 请求和限制来控制容器的 CPU Share 值。容器的 CPU 请求用于告诉 Kubernetes 调度器，该容器需要的最小 CPU 资源量；容器的 CPU 限制用于告诉 Kubernetes，该容器 最多可以使用多少 CPU 资源。Kubernetes 调度器根据容器的 CPU 请求和限制以及节点上 的 CPU 资源量来计算容器的 CPU Share 值。如果多个容器的 CPU 请求和限制相等，则它 们将获得相等的 CPU Share 值，即按照时间片轮转的方式共享 CPU 资源。\nCPU 调度器 在 Kubernetes 中，CPU 调度器是负责将 Pod 调度到节点上的组件之一。它会根据节点上 的可用 CPU 资源和 Pod 的 CPU 请求来做出调度决策。CPU 调度器还会考虑节点上的其他 因素，例如内存、磁盘、网络等资源的使用情况。 当一个 Pod 被调度到节点上 时，Kubernetes 会为该 Pod 分配一个 CPU Cgroup。Cgroup 是 Linux 内核的一个特性， 它可以将进程（或者容器）分组，并限制它们的资源使用量。在 Kubernetes 中，每个 Pod 都有自己的 CPU Cgroup，用于限制该 Pod 使用的 CPU 资源。 总之，CPU 资源的分配是 Kubernetes 中的一个关键问题。通过设置 CPU 请求和限制、使用 CPU Share 机制以及 CPU 调度器等功能，可以更好地控制容器的 CPU 分配，提高应用程序的性能和可靠性。\n","permalink":"https://blog.bktus.com/archives/fhs17u/","summary":"\u003cp\u003eKubernetes（k8s）是一种流行的容器编排平台，它允许开发人员在云环境中部署、管理和\n自动化容器化应用程序。在 Kubernetes 中，CPU 资源的分配是一个关键的问题，它直接影\n响着应用程序的性能和可靠性。本文将介绍 Kubernetes 中的 CPU 资源分配机制，包括\nCPU 请求和限制、CPU Share 机制以及 CPU 调度器等相关概念，以帮助开发人员更好地控\n制容器的 CPU 分配，从而提高应用程序的性能和可靠性。\u003c/p\u003e\n\u003ch2 id=\"cpu-分配单位\"\u003eCPU 分配单位\u003c/h2\u003e\n\u003cp\u003e在 Kubernetes 中，CPU 的分配单位是 millicpu（毫核），一个 CPU 资源等于 1000 毫\n核。例如，一个 Pod 请求 0.5 个 CPU 资源，可以表示为 500 毫核。它是基于 Linux 内\n核的 CPU 分配机制实现的。在 Linux 内核中，CPU 时间是以时间片的形式分配的，每个时\n间片通常为几毫秒。Kubernetes 利用这个特性，将 CPU 时间片分配单位转换为毫核。\u003c/p\u003e\n\u003ch2 id=\"cpu-资源分配机制\"\u003eCPU 资源分配机制\u003c/h2\u003e\n\u003cp\u003e在 Kubernetes 中，CPU 资源的分配是通过两种方式来实现的：CPU 请求和限制。CPU 请求\n用于告诉 Kubernetes 调度器，该 Pod 至少需要多少 CPU 资源才能正常运行；CPU 限制用\n于告诉 Kubernetes，该 Pod 最多可以使用多少 CPU 资源。如果节点上可用的 CPU 资源不\n足以满足 Pod 的 CPU 请求，该 Pod 就无法调度到该节点上运行。如果节点上可用的 CPU\n资源不足以满足 Pod 的 CPU 限制，该 Pod 仍然可以运行，但可能会受到 CPU 资源的限\n制，导致运行缓慢或者出现其他问题。 需要注意的是，CPU 请求和限制是 Kubernetes 中\n的两个独立的概念。通常情况下，应该根据应用程序的实际需求来设置 CPU 请求和限制。\n如果设置的 CPU 请求过低，可能会导致 Pod 无法正常运行或者运行缓慢；如果设置的 CPU\n限制过高，可能会导致节点上的其他 Pod 的 CPU 资源不足，影响整个集群的性能和可用\n性。\u003c/p\u003e","title":"理解 Kubernetes 中的 CPU 资源分配机制"},{"content":"这一周主要是确保在春节前各类服务的稳定。近期发现某个服务经常在流量高峰时段报超 时，我提醒转交服务负责人处理。但是几天过去，服务负责人依然无法说明原因。只好亲自 处理这个问题，因为报警已经十分严重，部分节点超时率能够达到 20%。\n在这段时间中，应该是由于临近假期，流量大幅度上涨，12 月底相比已经上涨了 100%。所 以首先是怀疑服务的承载力不足，所以先进行了一次扩容。但是，扩容后并未解决此问题， 告警频率和超时率基本未变化。这种情况下，对服务的源代码进行分析后发现，该服务的接 口会首先调用一个下游服务，然后再异步地向数据库中插入数据。因为异步操作并不阻塞工 作线程，所以首先应该怀疑是这个下游调用的问题（实际上，我在数据库那边纠结了很 久）。 从终端登录进一个节点检查日志，发现所有下游调用走的居然是同一个 IP 和端 口，并且走的是稻草人节点。所以我先将该服务做上云操作，排除稻草人转发损失造成的影 响。上云完成后，刚切流量的时候发现某个地域（不是原先的地域）下有大量超时产生，做 了扩容后也无解，十分疑惑，所以暂时切回来了。我非常奇怪，为什么上云之后，超时率却 变得更高了。\n从云上监控分析问题，发现在切流量后，下游服务的某个云上节点 CPU 占用非常高，而其 他的节点却很低。开始怀疑是负载均衡的问题，所以又回去检查服务的源代码。果然，该服 务从注册中心拿到下游服务可用节点列表后，只会调用列表中的第一项。所以某个地域的几 乎所有流量都集中在下有服务的某一个节点上了。而原先通过稻草人转发的时候，稻草人调 用云上节点的时候会做一次负载均衡。所以，最初的问题应该是流量大量上涨，该服务却没 有负载均衡，所以导致某个稻草人过载，进而导致超时。稻草人是代理节点本身没有逻辑， 所以能够处理的并发数更大，超时问题不是很明显。而一旦上云，流量会直接集中在某个业 务节点上，大量流量一齐涌入，会瞬间导致该业务节点大批量超时。 有了上述的论断后， 回去找稻草人的监控，发现某个稻草人确实已经过载了，CPU 占用居然达到了 95 以上。知 道原因后，接下来继续上云，先最大限度消除超时告警，然后再改代码添加随机负载均衡算 法。这样做是因为节前修改代码，流程上会比较麻烦。\n所以首先，我加倍了下游服务的单个节点的核数，这大大增加处理能力。然后，做切流量的 操作，一个下游节点的 CPU 占用数突然升高，最终一直保持在比其他节点高很多的比例 上，这是意料之中的事情。当整个服务平稳下来后，上云就成功了，这个时候告警已经消 除，超时率归零。接着，就是修改改服务的代码，添加负载均衡的机制，在各个流程审批通 过后为该服务发布新的代码版本。 在我发布完新版本后，下游服务的各个节点的 CPU 占用 就接近了，最终问题解决。\n","permalink":"https://blog.bktus.com/archives/14abwa/","summary":"\u003cp\u003e这一周主要是确保在春节前各类服务的稳定。近期发现某个服务经常在流量高峰时段报超\n时，我提醒转交服务负责人处理。但是几天过去，服务负责人依然无法说明原因。只好亲自\n处理这个问题，因为报警已经十分严重，部分节点超时率能够达到 20%。\u003c/p\u003e\n\u003cp\u003e在这段时间中，应该是由于临近假期，流量大幅度上涨，12 月底相比已经上涨了 100%。所\n以首先是怀疑服务的承载力不足，所以先进行了一次扩容。但是，扩容后并未解决此问题，\n告警频率和超时率基本未变化。这种情况下，对服务的源代码进行分析后发现，该服务的接\n口会首先调用一个下游服务，然后再异步地向数据库中插入数据。因为异步操作并不阻塞工\n作线程，所以首先应该怀疑是这个下游调用的问题（实际上，我在数据库那边纠结了很\n久）。 从终端登录进一个节点检查日志，发现所有下游调用走的居然是同一个 IP 和端\n口，并且走的是稻草人节点。所以我先将该服务做上云操作，排除稻草人转发损失造成的影\n响。上云完成后，刚切流量的时候发现某个地域（不是原先的地域）下有大量超时产生，做\n了扩容后也无解，十分疑惑，所以暂时切回来了。我非常奇怪，为什么上云之后，超时率却\n变得更高了。\u003c/p\u003e\n\u003cp\u003e从云上监控分析问题，发现在切流量后，下游服务的某个云上节点 CPU 占用非常高，而其\n他的节点却很低。开始怀疑是负载均衡的问题，所以又回去检查服务的源代码。果然，该服\n务从注册中心拿到下游服务可用节点列表后，只会调用列表中的第一项。所以某个地域的几\n乎所有流量都集中在下有服务的某一个节点上了。而原先通过稻草人转发的时候，稻草人调\n用云上节点的时候会做一次负载均衡。所以，最初的问题应该是流量大量上涨，该服务却没\n有负载均衡，所以导致某个稻草人过载，进而导致超时。稻草人是代理节点本身没有逻辑，\n所以能够处理的并发数更大，超时问题不是很明显。而一旦上云，流量会直接集中在某个业\n务节点上，大量流量一齐涌入，会瞬间导致该业务节点大批量超时。 有了上述的论断后，\n回去找稻草人的监控，发现某个稻草人确实已经过载了，CPU 占用居然达到了 95 以上。知\n道原因后，接下来继续上云，先最大限度消除超时告警，然后再改代码添加随机负载均衡算\n法。这样做是因为节前修改代码，流程上会比较麻烦。\u003c/p\u003e\n\u003cp\u003e所以首先，我加倍了下游服务的单个节点的核数，这大大增加处理能力。然后，做切流量的\n操作，一个下游节点的 CPU 占用数突然升高，最终一直保持在比其他节点高很多的比例\n上，这是意料之中的事情。当整个服务平稳下来后，上云就成功了，这个时候告警已经消\n除，超时率归零。接着，就是修改改服务的代码，添加负载均衡的机制，在各个流程审批通\n过后为该服务发布新的代码版本。 在我发布完新版本后，下游服务的各个节点的 CPU 占用\n就接近了，最终问题解决。\u003c/p\u003e","title":"二零二三年一月第二周技术周报"},{"content":"时间进入二零二三年，今年是将一个辛苦的一年。今年将面临几方面的挑战，一个是将原先 部署在物理服务器上的数据都迁移上云。然后就是，加快培养几个团队新人，让他们尽快承 接目前的主要业务所涉及的服务，并且要求能够独立解决用户问题并对服务做出优化。这样 我就能将一些工作转交到他们名下，专注今年预估耗时较长的重要目标。还有就是，个人的 在技术和其他方面学习也到了一个攻坚阶段，这些方面决定了我未来 7~8 年的人生方向。\n这一周，我的主要的精力在几个服务的日志框架与日志追踪的设计与规范上。首先是解决日 志追踪的问题，为了能够跨服务地对调用过程中产生的日志进行统一的追踪，需要现将 TraceId 统一。但是目前这几个服务 TraceId 的类型层次不齐，有使用 Long 类型的也有 用字符串的。并且，这些服务所使用的语言和技术栈也不一致。直接使用一些标准的分布式 追踪框架中的 TraceId 应该无法支持所有服务目前的情况，只能用在一些技术栈比较新的 服务上。 所以，从兼容性与改造的简洁性考虑，准备采用自定义生成的 Long 类型的数值 作为 TraceId 并且限定位数为 16 位。前四位以 99 开头，作为统一 TraceId 的特征，然 后剩余两位标识服务。接下来 4 位为当前的微秒数，最后的 8 位为两组四位随机数拼接。\n虽然说这种 TraceId 并不能保证唯一性，但是在当前这种情况下是足够用的。如果服务为 Java 技术栈，生成 TraceId 需要充分考虑到线程竞争的情况，最好为每个线程分配一个随 机数发生器。或者，直接使用 TreadLocal。 Java 服务在处理请求时，会提取请求中的 TraceId。如果 TraceId 是 99 开头的，就不生成新的 TraceId 了。如果不是的话，按照 上述方式产生 TracId 并存储于 MDC 中。当遇到异步执行的时候，需要注意拷贝 MDC 中的 内容到旁线程，不然会丢失追踪信息。当需要调用下游服务时，需要将存储的 TaceId 传递 给下游服务。最后，当请求处理完毕时，需要清空 MDC，防止污染下一个请求的追踪信息。\n","permalink":"https://blog.bktus.com/archives/5pv1l2/","summary":"\u003cp\u003e时间进入二零二三年，今年是将一个辛苦的一年。今年将面临几方面的挑战，一个是将原先\n部署在物理服务器上的数据都迁移上云。然后就是，加快培养几个团队新人，让他们尽快承\n接目前的主要业务所涉及的服务，并且要求能够独立解决用户问题并对服务做出优化。这样\n我就能将一些工作转交到他们名下，专注今年预估耗时较长的重要目标。还有就是，个人的\n在技术和其他方面学习也到了一个攻坚阶段，这些方面决定了我未来 7~8 年的人生方向。\u003c/p\u003e\n\u003cp\u003e这一周，我的主要的精力在几个服务的日志框架与日志追踪的设计与规范上。首先是解决日\n志追踪的问题，为了能够跨服务地对调用过程中产生的日志进行统一的追踪，需要现将\nTraceId 统一。但是目前这几个服务 TraceId 的类型层次不齐，有使用 Long 类型的也有\n用字符串的。并且，这些服务所使用的语言和技术栈也不一致。直接使用一些标准的分布式\n追踪框架中的 TraceId 应该无法支持所有服务目前的情况，只能用在一些技术栈比较新的\n服务上。 所以，从兼容性与改造的简洁性考虑，准备采用自定义生成的 Long 类型的数值\n作为 TraceId 并且限定位数为 16 位。前四位以 99 开头，作为统一 TraceId 的特征，然\n后剩余两位标识服务。接下来 4 位为当前的微秒数，最后的 8 位为两组四位随机数拼接。\u003c/p\u003e\n\u003cp\u003e虽然说这种 TraceId 并不能保证唯一性，但是在当前这种情况下是足够用的。如果服务为\nJava 技术栈，生成 TraceId 需要充分考虑到线程竞争的情况，最好为每个线程分配一个随\n机数发生器。或者，直接使用 TreadLocal。 Java 服务在处理请求时，会提取请求中的\nTraceId。如果 TraceId 是 99 开头的，就不生成新的 TraceId 了。如果不是的话，按照\n上述方式产生 TracId 并存储于 MDC 中。当遇到异步执行的时候，需要注意拷贝 MDC 中的\n内容到旁线程，不然会丢失追踪信息。当需要调用下游服务时，需要将存储的 TaceId 传递\n给下游服务。最后，当请求处理完毕时，需要清空 MDC，防止污染下一个请求的追踪信息。\u003c/p\u003e","title":"二零二三年一月第一周技术周报"},{"content":"本周我感染了新冠病毒，一共在家待了 9 天。在此期间，工作上的最主要的事情是评估一 款小程序的推广上线，对我所负责的基础服务体系的影响。这款小程序切中了当时国人的需 求，预计有大量流量涌入，可能对基础服务体系的核心服务造成冲击。原先，他们有也一个 功能将要上线，流量也很大，我已经评估过并且扩容了。但是，这次还是在他们推送了上亿 的量级的通知后，发生了大批量的超时现象。 早上八点钟，躺在床上修养的我就被叫起 来，说是登录接口出现大量超时现象。\n我马上拿出笔记本，连上内网一看，这个接口流量直接翻了 20 多倍。我都捏了一把汗，首 先是怀疑业务容器数量不足，无法承接这么大的流量。然后通过筛选日志，发现并不是这个 问题。问题分为两个部分，其一是内部某个接口有限流，当前流量已经超出限流；另一个是 缓存数据库挂接的数据库打满。 内部接口限流比较好解决，通过紧急呼叫找到了对应的同 事调高了限流的阈值。另一个缓存数据库，由于大量老用户涌入导致不断要从数据库中读取 老数据到缓存中，而且频率随着流量的增长不断提升，最终打满了缓存后面挂接的数据库的 I/O。后续针对几个核心的缓存数据库，再次做了一批扩容，提升了缓存容量和节点数量以 应对后续更大的流量和并发。\n在日常维护上面，主要是将某个网关服务 Pod 的 k8s 调度策略做了调整，将其调度到由 CVM 虚拟化直接支持（一层调度）的计算节点上。原先，这个服务调度的到的计算节点是我 们公司采购的物理服务器上虚拟出来的 CVM，用作计算节点。调度到这上面以后，再于这个 CVM 上虚拟出一个执行空间（参考 Docker 技术），然后将 Pod 调度到这个节点开始运行 （二层调度）。一个计算节点上，可能存在几个甚至十几个 Pod。这些后台程序都运行于同 一台操作系统中，并且只是执行空间不同罢了。这就导致了 CPU、内存、网卡等资源的隔离 性不强，常常会互相干扰。并且当这台 CVM 计算节点需要重启或者升级的时候，其上的所 有 Pod 都会被驱逐。\n计算节点操作系统中的风吹草动，会影响到其上所有 Pod。这对于这种网关这种可靠性和延 迟要求苛刻的服务，来说并不合适。 而一层调度计算节点，其实是由云来分配计算资源并 虚拟出 CVM 来单独执行这个程序，CVM 资源是从整个云资源池中取得。并且这种方式支持 k8s，因为这种新的调度方式会虚拟成 k8s 上的一种“超级节点”。k8s 可以直接将 Pod 调 度到超级节点上，Pod 可以直接基于一层调度运行，也就是直接运行在一个单独 CVM 上。 这种方式隔离性强，互相之间不会受到影响。由于每个 CVM 只运行一个节点，所以当单独 一个 Pod 出现问题时，可以针对这个 Pod 的问题进行修复，而不会影响到其他 Pod。 通 过修改调度策略，将服务的所有 Pod 调度到超级节点后，整个服务运行更加稳定了。在高 峰期超时率和 CPU 占用率双高的情况也很少再出现。\n","permalink":"https://blog.bktus.com/archives/4sm6qv/","summary":"\u003cp\u003e本周我感染了新冠病毒，一共在家待了 9 天。在此期间，工作上的最主要的事情是评估一\n款小程序的推广上线，对我所负责的基础服务体系的影响。这款小程序切中了当时国人的需\n求，预计有大量流量涌入，可能对基础服务体系的核心服务造成冲击。原先，他们有也一个\n功能将要上线，流量也很大，我已经评估过并且扩容了。但是，这次还是在他们推送了上亿\n的量级的通知后，发生了大批量的超时现象。 早上八点钟，躺在床上修养的我就被叫起\n来，说是登录接口出现大量超时现象。\u003c/p\u003e\n\u003cp\u003e我马上拿出笔记本，连上内网一看，这个接口流量直接翻了 20 多倍。我都捏了一把汗，首\n先是怀疑业务容器数量不足，无法承接这么大的流量。然后通过筛选日志，发现并不是这个\n问题。问题分为两个部分，其一是内部某个接口有限流，当前流量已经超出限流；另一个是\n缓存数据库挂接的数据库打满。 内部接口限流比较好解决，通过紧急呼叫找到了对应的同\n事调高了限流的阈值。另一个缓存数据库，由于大量老用户涌入导致不断要从数据库中读取\n老数据到缓存中，而且频率随着流量的增长不断提升，最终打满了缓存后面挂接的数据库的\nI/O。后续针对几个核心的缓存数据库，再次做了一批扩容，提升了缓存容量和节点数量以\n应对后续更大的流量和并发。\u003c/p\u003e\n\u003cp\u003e在日常维护上面，主要是将某个网关服务 Pod 的 k8s 调度策略做了调整，将其调度到由\nCVM 虚拟化直接支持（一层调度）的计算节点上。原先，这个服务调度的到的计算节点是我\n们公司采购的物理服务器上虚拟出来的 CVM，用作计算节点。调度到这上面以后，再于这个\nCVM 上虚拟出一个执行空间（参考 Docker 技术），然后将 Pod 调度到这个节点开始运行\n（二层调度）。一个计算节点上，可能存在几个甚至十几个 Pod。这些后台程序都运行于同\n一台操作系统中，并且只是执行空间不同罢了。这就导致了 CPU、内存、网卡等资源的隔离\n性不强，常常会互相干扰。并且当这台 CVM 计算节点需要重启或者升级的时候，其上的所\n有 Pod 都会被驱逐。\u003c/p\u003e\n\u003cp\u003e计算节点操作系统中的风吹草动，会影响到其上所有 Pod。这对于这种网关这种可靠性和延\n迟要求苛刻的服务，来说并不合适。 而一层调度计算节点，其实是由云来分配计算资源并\n虚拟出 CVM 来单独执行这个程序，CVM 资源是从整个云资源池中取得。并且这种方式支持\nk8s，因为这种新的调度方式会虚拟成 k8s 上的一种“超级节点”。k8s 可以直接将 Pod 调\n度到超级节点上，Pod 可以直接基于一层调度运行，也就是直接运行在一个单独 CVM 上。\n这种方式隔离性强，互相之间不会受到影响。由于每个 CVM 只运行一个节点，所以当单独\n一个 Pod 出现问题时，可以针对这个 Pod 的问题进行修复，而不会影响到其他 Pod。 通\n过修改调度策略，将服务的所有 Pod 调度到超级节点后，整个服务运行更加稳定了。在高\n峰期超时率和 CPU 占用率双高的情况也很少再出现。\u003c/p\u003e","title":"二零二二年十二月第四周技术回顾"},{"content":"这一周，主要的工作是针对某个 Java 服务进行优化。该服务一直存在 CPU 占用率无法提 升上来的问题。首先考虑的问题是，该服务是否存在工作线程不足的问题。后面发现，并非 CPU 占用无法提升，而是 CPU 占用提升后会导致较多的超时问题。该服务很久以前就有反 馈性能不足，不建议继续使用。\n所以我感觉，这个问题来自于框架，而非业务代码。 经过对框架代码进行阅读和梳理，该 框架使用 netty 作为 NIO 服务器框架，并且会在执行业务逻辑的时候分发业务处理任务到 工作线程。然后工作线程来处理业务逻辑。之前框架有个问题就是，工作线程数太少，难以 应对高 IO 的情况。而现在的情况下，推断并非上次一样的问题，从日志来看工作线程数是 足够的。会不会是客户端的问题。在整个微服务架构下，该服务会作为客户端去调用其他服 务的接口。 这可能是一个切入点。通过阅读代码，发现该服务框架通过实现 Java 的 InvocationHandler 接口来生成代理类 ObjectProxy，该 ObjectProxy 接管对其他服务的 RPC 调用。业务代码中，通过 RPC 的方式发起对其他服务接口的调用时，ObjectProxy 会 通过它关联的 ProtocolInvoker 来获取目标服务对应有效节点列表（这个有效节点列表每 隔 30s 刷新一次）。\n而后，通过将列表传入负载均衡器 LoadBalancer 获得本次调用的目标节点。然后，就通过 具体协议对应的 Invoker 类向目标节点发起调用。Invoker 负责管理与目标服务之间的长 连接，在调用的时候会选取一个连接来发送请求并接收响应。具体的请求方式与此次调用类 型是异步还是同步有关。 客户端需要调用的每个目标服务由多个节点组成。对于每个节 点，该框架都会默认创建两个 I/O 线程负责网络 I/O 传输（NIO 模式）。另外，该框架会 为每个节点分别默认创建和处理器个数相当的 TCP 连接。每个 I/O 线程包含有一个 Selector 对连接的相关的事件进行轮询监听。而在每个 TCP 连接上会建立一个 TCPSession，每当需要发送一次请求时，会建立一个 Ticket 来跟踪这次请求及其关联响 应。对于同步请求来说，发送请求后会被阻塞，直到响应到来为止。\n对异步请求来说，会在 Ticket 中填入 callback 函数，当响应到来时，会通过 TicketNumber（Ticket 的唯一索引）找到对应的 Ticket 并调用其中事先填入的 callback 函数，进行后续的处理。 该框架对 NIO 的操作，在底层用到了 Java 提供的 NIO 库的能 力。上面所提到的 TCP 链接，其实就是 NIO 库中的 SocketChannel。那么该框架如何分割 各个数据包呢，该框架通过 Buffer 来存储目前已经读到的数据，而我们常用的 RPC 协 议，会将该包的字节数存在包的首部，只需要比较 Buffer 与字节数的大小即可知道包是否 读全了。如果包没有读全，就继续等待后续的数据到来。如果包读全了，则按照字节数规定 的大小从 Buffer 中分割出相应的数据来处理即可。在该框架中，会额外从一个线程池中取 出工作线程来进行有关于 Ticket 的后续处理。目前从现有的代码来看，这个工作线程池只 用来做这件事情，它的默认工作线程个数与核心数相同，最大线程数是核心数的两倍。 根 据 Java NIO 库的定义，Channel 中规定了几个 IO 操作，Selector 会轮询检查这些操作 是否就绪，如果就绪就会返回 SelectionKey。SelectionKey 中包含了从就绪通道中进行正 确操作的必要参数。\n目前经过阅读代码，从客户端侧并未发现有什么明显的问题。所使用的方案基本上是成熟且 稳定的，那有可能问题出现在服务端，这还有待后续的熟梳理。\n","permalink":"https://blog.bktus.com/archives/kwvjou/","summary":"\u003cp\u003e这一周，主要的工作是针对某个 Java 服务进行优化。该服务一直存在 CPU 占用率无法提\n升上来的问题。首先考虑的问题是，该服务是否存在工作线程不足的问题。后面发现，并非\nCPU 占用无法提升，而是 CPU 占用提升后会导致较多的超时问题。该服务很久以前就有反\n馈性能不足，不建议继续使用。\u003c/p\u003e\n\u003cp\u003e所以我感觉，这个问题来自于框架，而非业务代码。 经过对框架代码进行阅读和梳理，该\n框架使用 netty 作为 NIO 服务器框架，并且会在执行业务逻辑的时候分发业务处理任务到\n工作线程。然后工作线程来处理业务逻辑。之前框架有个问题就是，工作线程数太少，难以\n应对高 IO 的情况。而现在的情况下，推断并非上次一样的问题，从日志来看工作线程数是\n足够的。会不会是客户端的问题。在整个微服务架构下，该服务会作为客户端去调用其他服\n务的接口。 这可能是一个切入点。通过阅读代码，发现该服务框架通过实现 Java 的\nInvocationHandler 接口来生成代理类 ObjectProxy，该 ObjectProxy 接管对其他服务的\nRPC 调用。业务代码中，通过 RPC 的方式发起对其他服务接口的调用时，ObjectProxy 会\n通过它关联的 ProtocolInvoker 来获取目标服务对应有效节点列表（这个有效节点列表每\n隔 30s 刷新一次）。\u003c/p\u003e\n\u003cp\u003e而后，通过将列表传入负载均衡器 LoadBalancer 获得本次调用的目标节点。然后，就通过\n具体协议对应的 Invoker 类向目标节点发起调用。Invoker 负责管理与目标服务之间的长\n连接，在调用的时候会选取一个连接来发送请求并接收响应。具体的请求方式与此次调用类\n型是异步还是同步有关。 客户端需要调用的每个目标服务由多个节点组成。对于每个节\n点，该框架都会默认创建两个 I/O 线程负责网络 I/O 传输（NIO 模式）。另外，该框架会\n为每个节点分别默认创建和处理器个数相当的 TCP 连接。每个 I/O 线程包含有一个\nSelector 对连接的相关的事件进行轮询监听。而在每个 TCP 连接上会建立一个\nTCPSession，每当需要发送一次请求时，会建立一个 Ticket 来跟踪这次请求及其关联响\n应。对于同步请求来说，发送请求后会被阻塞，直到响应到来为止。\u003c/p\u003e","title":"二零二二年十二月第三周技术回顾"},{"content":"这一周的工作主要是对我负责的这一方面的工作的一个梳理，目前发现了许多的问题。这些 问题主要集中在数据上云方面，目前的问题主要是如何安全上云、怎么改造目前的单地域部 署方案、如何修复云下数据和云上数据之间的不一致。另外，发现还是有一些服务在使用云 下的数据库，这些云下数据库按道理是要废弃的。但是，这些服务都是一些老服务，代码改 动会带来一些风险，这就需要在行动前调查清楚。 调查的方面包括，现有的数据上云辅助 服务的基本原理和相关的代码逻辑细节，最好能够尽早发现其中存在的问题并及时修复。\n另外一方面，是数据上云过程需要实时监控，尽量全面地对服务接口调用质量、超时率、写 失败率、不一致率等等有个清晰的把握。这一方面最好能从上报监控和日志监控两方面来 做。而多地部署方案，目前打算采用一主多从，主从之间单方面复制，只写主库，从库只读 这几条原则入手。采用多地部署方案主要是提升服务稳定性，降低大多数请求的延迟，提升 服务质量，消除跨地域的链路不稳定带来的影响。多地部署所带来的数据同步延迟不容忽 视，必须有一个可以接受的延迟，这一块要从理论和监控两个方面来把握。 再就是，掌握 一门脚本语言很重要。特别是当有大量重复的事情需要处理，或者需要分析一些数据来获得 一个结论的时候。能够较为熟练地掌握类似 Python 这样的脚本语言，有很大的优势。但 是，如果说拿 Python 来写一个大型程序的话，我觉得是不明智的。每种编程语言就像不同 的刀，都可以拿来切菜，但是有些刀更适合用于切肉或切骨头。\n","permalink":"https://blog.bktus.com/archives/mauhuw/","summary":"\u003cp\u003e这一周的工作主要是对我负责的这一方面的工作的一个梳理，目前发现了许多的问题。这些\n问题主要集中在数据上云方面，目前的问题主要是如何安全上云、怎么改造目前的单地域部\n署方案、如何修复云下数据和云上数据之间的不一致。另外，发现还是有一些服务在使用云\n下的数据库，这些云下数据库按道理是要废弃的。但是，这些服务都是一些老服务，代码改\n动会带来一些风险，这就需要在行动前调查清楚。 调查的方面包括，现有的数据上云辅助\n服务的基本原理和相关的代码逻辑细节，最好能够尽早发现其中存在的问题并及时修复。\u003c/p\u003e\n\u003cp\u003e另外一方面，是数据上云过程需要实时监控，尽量全面地对服务接口调用质量、超时率、写\n失败率、不一致率等等有个清晰的把握。这一方面最好能从上报监控和日志监控两方面来\n做。而多地部署方案，目前打算采用一主多从，主从之间单方面复制，只写主库，从库只读\n这几条原则入手。采用多地部署方案主要是提升服务稳定性，降低大多数请求的延迟，提升\n服务质量，消除跨地域的链路不稳定带来的影响。多地部署所带来的数据同步延迟不容忽\n视，必须有一个可以接受的延迟，这一块要从理论和监控两个方面来把握。 再就是，掌握\n一门脚本语言很重要。特别是当有大量重复的事情需要处理，或者需要分析一些数据来获得\n一个结论的时候。能够较为熟练地掌握类似 Python 这样的脚本语言，有很大的优势。但\n是，如果说拿 Python 来写一个大型程序的话，我觉得是不明智的。每种编程语言就像不同\n的刀，都可以拿来切菜，但是有些刀更适合用于切肉或切骨头。\u003c/p\u003e","title":"二零二二年十二月第二周技术周报"},{"content":"这一周的工作，总结说来，主要就是将一个核心服务上云，然后不断将云下的节点切换成流 量转发节点。上云的第一步就是，在云上环境部署该服务的节点：迁移配置文件、环境，然 后根据稳定版本的代码编译适用于云上环境的镜像，然后让服务在云上环境跑起来。服务跑 起来后并且测试完成后，云上节点目前是没有任何流量的，这个时候需要将云下的部分流量 转发到云上，首先是要将云下的部分节点替换成转发节点，转发节点的作用是将主调打过来 的流量转发到云上的节点。后续可以利用这部分流量来观察云上节点的工作状态，检查异 常，可以称之为“流量灰度”。流量灰度一般控制在整体流量的 1%-5%作用，要看服务的关键 性来调整比例，测试环境下可以适当多一些，25%-50%都可以。流量灰度是发生问题时，或 者接到问题单时，只需要关闭云下的流量转发节点即可。当然，上面的一切必须先要在测试 环境操作，测试妥当了再按照同样的方案谨慎操作生产环境。 有一些后台体系错综复杂， 一次调用涉及多个服务，业务逻辑扑朔迷离，这时候尽量控制变量一段时间内仅仅做出一次 改动，等观察一段时间后，情况稳定下来了再做下一步操作。或者可以把大的步骤拆分成几 个小的步骤，一段时间内只做一小步，观察一段时间后再继续推进下一小步。这样不容易出 错。 接下来，如果灰度验证通过了（一般持续一个星期）。\n接下来就是切换路由了。切换路由就是将云上的服务节点的路由直接切换到云上节点，在这 以后其他云上的主调服务将直接访问该服务的云上节点，而不会再去访问云下节点。在切换 路由的时候必须特别小心，因为大量的线上流量会直接打在云上节点上。在操作之前首先， 需要根据往常数据计算云上各个地域下需要的节点数量，一般是按照高峰期流量水平来计 算，如果不够要扩容，以免由于容量不够造成线上大量超时。这个时候，云上节点数宁愿多 一些，因为多了后续还能够慢慢缩回来，成本不高。但是如果少了造成大量超时的话，节点 扩容是需要时间的（主要是资源调度与服务启动耗时），这个时候容易造成用户投诉。如果 由于超时引发客户端大量重试，就有可能带垮整个服务，引发线上事故。当时，我是挑了一 个流量少的时候操作，这样能够减少由于切换抖动造成的影响。 切换了路由后，云下节点 就可以逐渐全部换成转发节点，将云下的流量都转发到云上来处理。然后通知主调服务的负 责人，尽量把服务上云。因为，转发节点转发到云上是有开销的，会增加延迟。\n","permalink":"https://blog.bktus.com/archives/qqqw9n/","summary":"\u003cp\u003e这一周的工作，总结说来，主要就是将一个核心服务上云，然后不断将云下的节点切换成流\n量转发节点。上云的第一步就是，在云上环境部署该服务的节点：迁移配置文件、环境，然\n后根据稳定版本的代码编译适用于云上环境的镜像，然后让服务在云上环境跑起来。服务跑\n起来后并且测试完成后，云上节点目前是没有任何流量的，这个时候需要将云下的部分流量\n转发到云上，首先是要将云下的部分节点替换成转发节点，转发节点的作用是将主调打过来\n的流量转发到云上的节点。后续可以利用这部分流量来观察云上节点的工作状态，检查异\n常，可以称之为“流量灰度”。流量灰度一般控制在整体流量的 1%-5%作用，要看服务的关键\n性来调整比例，测试环境下可以适当多一些，25%-50%都可以。流量灰度是发生问题时，或\n者接到问题单时，只需要关闭云下的流量转发节点即可。当然，上面的一切必须先要在测试\n环境操作，测试妥当了再按照同样的方案谨慎操作生产环境。 有一些后台体系错综复杂，\n一次调用涉及多个服务，业务逻辑扑朔迷离，这时候尽量控制变量一段时间内仅仅做出一次\n改动，等观察一段时间后，情况稳定下来了再做下一步操作。或者可以把大的步骤拆分成几\n个小的步骤，一段时间内只做一小步，观察一段时间后再继续推进下一小步。这样不容易出\n错。 接下来，如果灰度验证通过了（一般持续一个星期）。\u003c/p\u003e\n\u003cp\u003e接下来就是切换路由了。切换路由就是将云上的服务节点的路由直接切换到云上节点，在这\n以后其他云上的主调服务将直接访问该服务的云上节点，而不会再去访问云下节点。在切换\n路由的时候必须特别小心，因为大量的线上流量会直接打在云上节点上。在操作之前首先，\n需要根据往常数据计算云上各个地域下需要的节点数量，一般是按照高峰期流量水平来计\n算，如果不够要扩容，以免由于容量不够造成线上大量超时。这个时候，云上节点数宁愿多\n一些，因为多了后续还能够慢慢缩回来，成本不高。但是如果少了造成大量超时的话，节点\n扩容是需要时间的（主要是资源调度与服务启动耗时），这个时候容易造成用户投诉。如果\n由于超时引发客户端大量重试，就有可能带垮整个服务，引发线上事故。当时，我是挑了一\n个流量少的时候操作，这样能够减少由于切换抖动造成的影响。 切换了路由后，云下节点\n就可以逐渐全部换成转发节点，将云下的流量都转发到云上来处理。然后通知主调服务的负\n责人，尽量把服务上云。因为，转发节点转发到云上是有开销的，会增加延迟。\u003c/p\u003e","title":"二零二二年十二月第一周技术周报"},{"content":"这一周，我主要在优化一个服务。这个服务是用 Java 编写的。生产环境流量不大的时候， 也会出现调用批量超时的现象。而且发送超时的时候，CPU 占用率很低。经过观察，CPU 占 用一直就上不去。这个时候就推测是不是线程都阻塞在了某个操作上，导致这个问题。我所 接触到的大多数服务，包括这个服务，都是 IO 密集型的服务。\n这类服务涉及大量的 RPC 调用，当 RPC 调用的时候，工作线程会阻塞，导致无法处理其他 请求。所以这类服务的工作线程数都会设置得很大，确保有多余线程来处理 IO 请求，防止 由于大部分线程阻塞导致后续请求得不到处理，最终导致大批量超时。 这其实是有问题 的，虽然说目前 Java 在处理 Socket 和解析 Request 的时候已经采取了 NIO 的模式，但 是对于请求的逻辑处理依然采用工作线程池的方式。当遇到工作线程阻塞在 IO 请求上的时 候，这个工作线程只能等待 IO 请求完成或者超时。如果说，IO 请求所返回的结果并不是 该请求最终结果的依赖，这种情况会将请求提交到其他线程池处理。这个时候它的处理对于 该工作线程来说是异步的。\n最后经过排查，这个服务由于框架问题，有关于工作线程数量的配置并未被读取。这导致了 该服务启动时采取了默认的工作线程的数量设置，也就是工作线程数和 CPU 核心数相同。 所以，工作线程在处理下游 RPC 调用时，只要下游接口的耗时稍微有波动，就会导致大量 请求超时。这个时候，由于工作线程大部分时间阻塞在等待 IO 操作返回上，所以 CPU 占 用也是很少的。 如何排查的呢，首先需要在日志框架中设置打印当前线程名称，对于 logback 日志框架，就是在日志格式设置中加上%t。然后，对单个节点在测试环境进行压 测，查看在一定 QPS 下，该服务对于请求的处理情况。最好能给这个请求的逻辑代码加上 StopWatch，辅助进行分析。最后发现，输出日志的线程只有几个，不对劲。然后，使用 jstack 过滤出线程数量。发现确实只有四个。\n可以解释一下，这个 Java 服务使用的框架采用了 netty 框架进行请求的处理，最后会转 交到 netty 的工作线程池对请求的逻辑进行处理。在这里，netty 工作线程池对应的前缀 就是 nioEventLoopGroup-5。另外可以从 cpu 累计时间来辅助证明，确实只有这 4 个线程 在处理所有的请求的主要逻辑。对于这种服务来说，生产环境下，工作线程数量一般设置为 800~1000，并且会严格设置 RPC 调用的超时时间防止大量工作线程被阻塞，最终导致节点 的吞吐量急剧下降。 在更新框架后，该问题得到了解决。另外，我还发发现了该服务的一 个主要接口涉及基于 http 协议的下游调用。该调用通过操作 OkHttp 库来进行，我发现调 用并未设置超时时间。这是错误的情况，如果遇到极端情况，下游迟迟不返回，那么工作线 程就会阻塞很久。所以，必须对下游调用设置一个合理的超时时间来保护上下游调用之间的 通畅，防止雪崩现象的发生。超时时间一般分为三种，连接超时、读超时、写超时，都需要 设置。另外，对于可能存在大量的 Http 调用情况，我开启了 OkHttp 的 ConnectionPool。\n根据文档，ConnectionPool 的优势在于，多个同一地址的 http 或者 http/2 请求可以共 用同一个连接。但需要注意，这个共享的前提是服务端支持 http 长连接。 另外，这周还 主要去看了腾讯云的高级架构师 TCP 的相关内容，因为考试安排在周末。这个 TCP 考试比 原来的架构师、从业者考试难度大了些，还是需要好好准备的。上班没太多时间看，所以我 重物直接复习到周六凌晨 5 点，所幸最后考过了。我应该会写一篇文章来专门谈谈这个考 试。\n","permalink":"https://blog.bktus.com/archives/4a64k4/","summary":"\u003cp\u003e这一周，我主要在优化一个服务。这个服务是用 Java 编写的。生产环境流量不大的时候，\n也会出现调用批量超时的现象。而且发送超时的时候，CPU 占用率很低。经过观察，CPU 占\n用一直就上不去。这个时候就推测是不是线程都阻塞在了某个操作上，导致这个问题。我所\n接触到的大多数服务，包括这个服务，都是 IO 密集型的服务。\u003c/p\u003e\n\u003cp\u003e这类服务涉及大量的 RPC 调用，当 RPC 调用的时候，工作线程会阻塞，导致无法处理其他\n请求。所以这类服务的工作线程数都会设置得很大，确保有多余线程来处理 IO 请求，防止\n由于大部分线程阻塞导致后续请求得不到处理，最终导致大批量超时。 这其实是有问题\n的，虽然说目前 Java 在处理 Socket 和解析 Request 的时候已经采取了 NIO 的模式，但\n是对于请求的逻辑处理依然采用工作线程池的方式。当遇到工作线程阻塞在 IO 请求上的时\n候，这个工作线程只能等待 IO 请求完成或者超时。如果说，IO 请求所返回的结果并不是\n该请求最终结果的依赖，这种情况会将请求提交到其他线程池处理。这个时候它的处理对于\n该工作线程来说是异步的。\u003c/p\u003e\n\u003cp\u003e最后经过排查，这个服务由于框架问题，有关于工作线程数量的配置并未被读取。这导致了\n该服务启动时采取了默认的工作线程的数量设置，也就是工作线程数和 CPU 核心数相同。\n所以，工作线程在处理下游 RPC 调用时，只要下游接口的耗时稍微有波动，就会导致大量\n请求超时。这个时候，由于工作线程大部分时间阻塞在等待 IO 操作返回上，所以 CPU 占\n用也是很少的。 如何排查的呢，首先需要在日志框架中设置打印当前线程名称，对于\nlogback 日志框架，就是在日志格式设置中加上%t。然后，对单个节点在测试环境进行压\n测，查看在一定 QPS 下，该服务对于请求的处理情况。最好能给这个请求的逻辑代码加上\nStopWatch，辅助进行分析。最后发现，输出日志的线程只有几个，不对劲。然后，使用\njstack 过滤出线程数量。发现确实只有四个。\u003c/p\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"https://image.cdn.bktus.com/i/2/2025/04/28/f9fa0b0d-8d8a-a7fa-2df4-eadfb3e171c4-2.webp\"\u003e\u003c/p\u003e\n\u003cp\u003e可以解释一下，这个 Java 服务使用的框架采用了 netty 框架进行请求的处理，最后会转\n交到 netty 的工作线程池对请求的逻辑进行处理。在这里，netty 工作线程池对应的前缀\n就是 nioEventLoopGroup-5。另外可以从 cpu 累计时间来辅助证明，确实只有这 4 个线程\n在处理所有的请求的主要逻辑。对于这种服务来说，生产环境下，工作线程数量一般设置为\n800~1000，并且会严格设置 RPC 调用的超时时间防止大量工作线程被阻塞，最终导致节点\n的吞吐量急剧下降。 在更新框架后，该问题得到了解决。另外，我还发发现了该服务的一\n个主要接口涉及基于 http 协议的下游调用。该调用通过操作 OkHttp 库来进行，我发现调\n用并未设置超时时间。这是错误的情况，如果遇到极端情况，下游迟迟不返回，那么工作线\n程就会阻塞很久。所以，必须对下游调用设置一个合理的超时时间来保护上下游调用之间的\n通畅，防止雪崩现象的发生。超时时间一般分为三种，连接超时、读超时、写超时，都需要\n设置。另外，对于可能存在大量的 Http 调用情况，我开启了 OkHttp 的\nConnectionPool。\u003c/p\u003e","title":"二零二二年十一月第四周技术周报"},{"content":"Portainer 是一个好用的 Docker 容器控制面板。它的代码仓库在这 里。它支持单节点和集群部署。\n在此之前，我已经将个人服务器完全容器化了。如果能够可视化操作、监控各种容器，对于 日常的运维帮助很大，感觉一切尽在掌握。 可以看看搭建好后的效果，界面还是很美观 的。\n可以快速拉取镜像\n可以可视化发布容器，并灵活设置环境变量、网络、重启策略等等参数。\n可以在镜像更新的时候，快速用新镜像更新容器。\n还可以快速进入接入容器命令行，查看容器日志。\n可以说，我日常需要的功能都有，感觉很完善了。\n搭建 那么对于单点服务器来说，该如何搭建呢，首先你要确认你的服务器目前有安装 Docker。 可以输入docker -v来看看当前环境中是否存在 docker。 如果不存在，需要安装 docker，你可以在 Google 找找教程。对于 AWS 的 EC2，可以输入sudo yum install docker -y来安装。 确认安装好 Docker 后。确认一下 Docker 是否启动。然后看看 /var/run 路径下是否有 docker.sock 文件。 然后，确认 8000 端口和 9443 端口都没有 被占用，因为 Portainer 后续要用到这两个端口。\n将应用数据存储在 Docker Volume 如果你想将 Portainer 产生的应用数据存在 Docker Volume 中，你可以直接执行下面的命 令。后续部署完成后，Portainer 所使用的 Docker Volume 也可以在 Portainer 看到并管 理。如果 8000 端口被占用了，可以将-p 8000:8000改成其他端口，类似-p 12345:8000。9443 端口也是类似。 如果你想指定容器名为其他，可以修改\u0026ndash;name 参数， 比如说--name myportainer。\n% docker volume create portainer\\_data % docker run -d -p 8000:8000 -p 9443:9443 --name portainer --restart=always -v /var/run/docker.sock:/var/run/docker.sock -v portainer\\_data:/data portainer/portainer-ce:latest 将应用数据映射到宿主机的其他地方 你可以在/data 下建立目录 portainer，所以说，最终的路径是/data/portainer。我最终 选择了这种方式，因为我单独购买了 AWS 的数据盘来存储我的服务器的应用数据。所以我 倾向于将应用数据都存在数据盘中。 其他参数设置和上面的部分一致。\n% docker run -d -p 8000:8000 -p 9443:9443 --name portainer --restart=always -v /var/run/docker.sock:/var/run/docker.sock -v /data/portainer:/data portainer/portainer-ce:latest 确认部署状态 然后执行docker ps来确认 Portainer 是否正常启动。可以参考我的截图中的红框中的信 息。\n如果你等待几分钟，看到 Status 为 Up xxx minutes 的话，说明 Portainer 容器已经成 功启动了。 然后，你可以访问 https://localhost:9443 来访问面板，并设置管理员密 码。\n","permalink":"https://blog.bktus.com/archives/qzi6jo/","summary":"\u003cp\u003ePortainer 是一个好用的 Docker 容器控制面板。它的代码仓库在\u003ca href=\"https://github.com/portainer/portainer\"\u003e这\n里\u003c/a\u003e。它支持单节点和集群部署。\u003c/p\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"https://image.cdn.bktus.com/i/2/2025/04/28/8f49c1d6-b79a-d99b-36d8-7a0f0ff532fc-2.webp\"\u003e\u003c/p\u003e\n\u003cp\u003e在此之前，我已经将个人服务器完全容器化了。如果能够可视化操作、监控各种容器，对于\n日常的运维帮助很大，感觉一切尽在掌握。 可以看看搭建好后的效果，界面还是很美观\n的。\u003c/p\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"https://image.cdn.bktus.com/i/2/2025/04/28/01b588ea-1159-82f3-dcb0-7992f847d3d1-2.webp\"\u003e\u003c/p\u003e\n\u003cp\u003e可以快速拉取镜像\u003c/p\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"https://image.cdn.bktus.com/i/2/2025/04/28/46b1c7b3-b99c-5d63-0cbb-8234d1c63d32-2.webp\"\u003e\u003c/p\u003e\n\u003cp\u003e可以可视化发布容器，并灵活设置环境变量、网络、重启策略等等参数。\u003c/p\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"https://image.cdn.bktus.com/i/2/2025/04/28/38e2f35a-e084-a885-8322-a8918bcdbfda-2.webp\"\u003e\u003c/p\u003e\n\u003cp\u003e可以在镜像更新的时候，快速用新镜像更新容器。\u003c/p\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"https://image.cdn.bktus.com/i/2/2025/04/28/4643c945-dad8-0874-acb0-e7890262cc90-2.webp\"\u003e\u003c/p\u003e\n\u003cp\u003e还可以快速进入接入容器命令行，查看容器日志。\u003c/p\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"https://image.cdn.bktus.com/i/2/2025/04/28/0eedb2f6-8660-c25f-4bc2-dc10d8d400d9-2.webp\"\u003e\u003c/p\u003e\n\u003cp\u003e可以说，我日常需要的功能都有，感觉很完善了。\u003c/p\u003e\n\u003ch2 id=\"搭建\"\u003e搭建\u003c/h2\u003e\n\u003cp\u003e那么对于单点服务器来说，该如何搭建呢，首先你要确认你的服务器目前有安装 Docker。\n可以输入\u003ccode\u003edocker -v\u003c/code\u003e来看看当前环境中是否存在 docker。 如果不存在，需要安装\ndocker，你可以在 Google 找找教程。对于 AWS 的 EC2，可以输入\u003ccode\u003esudo yum install docker -y\u003c/code\u003e来安装。 确认安装好 Docker 后。确认一下 Docker 是否启动。然后看看\n/var/run 路径下是否有 docker.sock 文件。 然后，确认 8000 端口和 9443 端口都没有\n被占用，因为 Portainer 后续要用到这两个端口。\u003c/p\u003e\n\u003ch3 id=\"将应用数据存储在-docker-volume\"\u003e将应用数据存储在 Docker Volume\u003c/h3\u003e\n\u003cp\u003e如果你想将 Portainer 产生的应用数据存在 Docker Volume 中，你可以直接执行下面的命\n令。后续部署完成后，Portainer 所使用的 Docker Volume 也可以在 Portainer 看到并管\n理。如果 8000 端口被占用了，可以将\u003ccode\u003e-p 8000:8000\u003c/code\u003e改成其他端口，类似\u003ccode\u003e-p 12345:8000\u003c/code\u003e。9443 端口也是类似。 如果你想指定容器名为其他，可以修改\u0026ndash;name 参数，\n比如说\u003ccode\u003e--name myportainer\u003c/code\u003e。\u003c/p\u003e","title":"搭建一个免费开源好用的Docker面板 Portainer"},{"content":"这是我的第一篇有关于工作中遇到的技术问题的周期性回顾，所以本期的技术回顾主要是总 结长期以来的经历，为以后的技术回顾开一个好头。 来公司工作了快半年了，最近我从客 户端开发转成了后台开发。这是我希望的，但其实不是我要求的。因为个人感觉，在中国科 技行业目前的职业生涯规划中，后台开发可以探索的东西会稍微多一些，接触问题的规模也 会大很多。其实客户端也大有可为，我的第一个比较成熟的开源项目 GpgFrontend 就是一 个客户端项目。我在其中投入了大量的时间，解决了大量的问题。尤其是编译问题、平台兼 容性问题、稳定性问题。其探索的深度，问题的难度，都是不小的。\n我目前觉得，为什么客户端和后端开发会被分出高下来，那是因为心没静下来，总想着往上 爬。另外，对于技术的追求本来无所谓客户端、前端或者后台，他们背后的技术思想本质上 是通用的，但是目前将职业看得太重，反而以职业分工来将技术划分地分明。我觉得这是不 好的，所以虽然我是目前是一个后台开发，但我绝不能认为我仅仅是个后台开发，其他技术 看都不看，学都不学，自己把自己的思维框起来。 现在职位转成了后台开发，我反而不知 道该怎么做了。后台开发，我目前记得，我自己写过的最早的后台项目，是我 15 岁的时候 用 Nodejs 写的一个天文论坛 Stelescope 。用了当时流行的 Express 框架。现在我还记得，当时第一次接触 MongoDB、登录登出的状态保存、异步回调 等等技术概念。我印象最深的是异步回调，当时用了很久才理解。因为我当时连进程、线程 的基本概念都搞得不是很清楚，更别说什么异步回调和闭包了。\n那时，网上还在争论 Nodejs 和 PHP 孰优孰劣，Nodejs 到底适合什么场景，然后就是异步 回调和多进程并行孰优孰劣等等。当时记得异步回调不能被阻塞，如果遇到阻塞操作会使用 一种非阻塞的 API，这种 API 能够立即返回，将阻塞操作放到一边处理，主线程依然能够 继续处理后续的代码。等阻塞操作处理完了，就会通知主线程执行回调函数来处理阻塞操作 的结果。 后续，我还接触了 Python 的后台开发，写了一个简单的班级学业考勤管理系统 （SP）。在这里，我接触了 MVC 的思想，我理 解为模型、视图和控制器。这是一种重要的思想，那时候前端的概念还不是很明细，前后端 分离还不是主流思想。在当时，服务器负责动态页面的生成，由请求触发控制器做出响应。 控制器基于模型进行计算，并最终通过模板引擎将模型渲染成页面，而后页面被返回到用户 浏览器。就这么一个过程。记得当时，会在服务端定义很多模板，模板里选一些地方镂空， 待会存放数据。或者是定义一个小的卡片控件，放在 for 语句中，待会用模板引擎生成出 很多卡片出来。在当时，我作为后台开发要考虑方方面面，包括页面漂不漂亮，数据安全， 响应快不快等等。\n然后大一下开始，我就接触了 Spring 框架，具体用的就是 SpringBoot。这个时候，才真 正了解到了关系型数据库。原来我对于关系型数据库的理解，仅仅是会安装，配置。记得很 深刻，当时接触到了前后端分离的思想，觉得这是个好东西。拿出来和王老师讨论，说我们 的全员育人管理系统用前后端分离最好。王老师非常开明，和我聊了很多，同意了我的方 案。前后端分离，顾名思义，就是将动态页面的渲染放到用户那边，服务器仅仅负责数据的 处理和存储。这样有助于分工和解耦，虽然当时网上也有很多质疑的声音，我还是觉得这是 趋势。当时害痴迷于一种 RestfulAPI 的接口规范，觉得如果这样做我们连写项目文档也可 以免了。但是现实很骨感，在实践中对于一些复杂的情况就难以保持 RestfulAPI 的风格。 通过与 SpringBoot 打交道，我学会了很多有关后台的东西，目前我也在用这些原来的经 验。我在大学本科生涯中，写了很多的 SpringBoot 项目。到现在，我进了公司发现部门的 技术栈就是 Java，新的项目用的框架都是 SpringBoot，反倒用上了。虽然说我当时很厌恶 Java，觉得它臃肿与繁琐，包括现在也没有什么好感，但是 Java 的生态确实是很强大的， 想找个什么组件很容易，而且 Java 组件都是成熟的、良好维护的并且文档齐全。Java 技 术栈对于面向生产的后台项目来说，确实是个省心、好找工作的技术栈。\n在公司工作半年，其实算上实习都快一年了。总的来说，对于后台这块，我一直在捣鼓优 化、缓存、线程这些东西。每天都在分析各种告警，有些是业务上的，有些是技术上问题。 对于业务上的问题，只能好好地了解背景。对于技术上的问题，需要扩展自己的知识面，静 下心来研究。包括一些目前自己感觉难以解决的问题，比如说在容器化部署过程中，总有一 些容器存在偶发的超时问题，一直不能确定到底是容器的问题还是后台应用的问题。我现在 思考，这一块需要一些更加深入的知识，比如说 CPU、内存、网络的虚拟化问题。 今天先 这样吧，还有点其他事情要做。\n","permalink":"https://blog.bktus.com/archives/c1vcs4/","summary":"\u003cp\u003e这是我的第一篇有关于工作中遇到的技术问题的周期性回顾，所以本期的技术回顾主要是总\n结长期以来的经历，为以后的技术回顾开一个好头。 来公司工作了快半年了，最近我从客\n户端开发转成了后台开发。这是我希望的，但其实不是我要求的。因为个人感觉，在中国科\n技行业目前的职业生涯规划中，后台开发可以探索的东西会稍微多一些，接触问题的规模也\n会大很多。其实客户端也大有可为，我的第一个比较成熟的开源项目 GpgFrontend 就是一\n个客户端项目。我在其中投入了大量的时间，解决了大量的问题。尤其是编译问题、平台兼\n容性问题、稳定性问题。其探索的深度，问题的难度，都是不小的。\u003c/p\u003e\n\u003cp\u003e我目前觉得，为什么客户端和后端开发会被分出高下来，那是因为心没静下来，总想着往上\n爬。另外，对于技术的追求本来无所谓客户端、前端或者后台，他们背后的技术思想本质上\n是通用的，但是目前将职业看得太重，反而以职业分工来将技术划分地分明。我觉得这是不\n好的，所以虽然我是目前是一个后台开发，但我绝不能认为我仅仅是个后台开发，其他技术\n看都不看，学都不学，自己把自己的思维框起来。 现在职位转成了后台开发，我反而不知\n道该怎么做了。后台开发，我目前记得，我自己写过的最早的后台项目，是我 15 岁的时候\n用 Nodejs 写的一个天文论坛\n\u003ca href=\"https://git.bktus.com/Saturneric/Stelescope\"\u003eStelescope\u003c/a\u003e 。用了当时流行的\nExpress 框架。现在我还记得，当时第一次接触 MongoDB、登录登出的状态保存、异步回调\n等等技术概念。我印象最深的是异步回调，当时用了很久才理解。因为我当时连进程、线程\n的基本概念都搞得不是很清楚，更别说什么异步回调和闭包了。\u003c/p\u003e\n\u003cp\u003e那时，网上还在争论 Nodejs 和 PHP 孰优孰劣，Nodejs 到底适合什么场景，然后就是异步\n回调和多进程并行孰优孰劣等等。当时记得异步回调不能被阻塞，如果遇到阻塞操作会使用\n一种非阻塞的 API，这种 API 能够立即返回，将阻塞操作放到一边处理，主线程依然能够\n继续处理后续的代码。等阻塞操作处理完了，就会通知主线程执行回调函数来处理阻塞操作\n的结果。 后续，我还接触了 Python 的后台开发，写了一个简单的班级学业考勤管理系统\n（\u003ca href=\"https://git.bktus.com/Saturneric/SP\"\u003eSP\u003c/a\u003e）。在这里，我接触了 MVC 的思想，我理\n解为模型、视图和控制器。这是一种重要的思想，那时候前端的概念还不是很明细，前后端\n分离还不是主流思想。在当时，服务器负责动态页面的生成，由请求触发控制器做出响应。\n控制器基于模型进行计算，并最终通过模板引擎将模型渲染成页面，而后页面被返回到用户\n浏览器。就这么一个过程。记得当时，会在服务端定义很多模板，模板里选一些地方镂空，\n待会存放数据。或者是定义一个小的卡片控件，放在 for 语句中，待会用模板引擎生成出\n很多卡片出来。在当时，我作为后台开发要考虑方方面面，包括页面漂不漂亮，数据安全，\n响应快不快等等。\u003c/p\u003e\n\u003cp\u003e然后大一下开始，我就接触了 Spring 框架，具体用的就是 SpringBoot。这个时候，才真\n正了解到了关系型数据库。原来我对于关系型数据库的理解，仅仅是会安装，配置。记得很\n深刻，当时接触到了前后端分离的思想，觉得这是个好东西。拿出来和王老师讨论，说我们\n的全员育人管理系统用前后端分离最好。王老师非常开明，和我聊了很多，同意了我的方\n案。前后端分离，顾名思义，就是将动态页面的渲染放到用户那边，服务器仅仅负责数据的\n处理和存储。这样有助于分工和解耦，虽然当时网上也有很多质疑的声音，我还是觉得这是\n趋势。当时害痴迷于一种 RestfulAPI 的接口规范，觉得如果这样做我们连写项目文档也可\n以免了。但是现实很骨感，在实践中对于一些复杂的情况就难以保持 RestfulAPI 的风格。\n通过与 SpringBoot 打交道，我学会了很多有关后台的东西，目前我也在用这些原来的经\n验。我在大学本科生涯中，写了很多的 SpringBoot 项目。到现在，我进了公司发现部门的\n技术栈就是 Java，新的项目用的框架都是 SpringBoot，反倒用上了。虽然说我当时很厌恶\nJava，觉得它臃肿与繁琐，包括现在也没有什么好感，但是 Java 的生态确实是很强大的，\n想找个什么组件很容易，而且 Java 组件都是成熟的、良好维护的并且文档齐全。Java 技\n术栈对于面向生产的后台项目来说，确实是个省心、好找工作的技术栈。\u003c/p\u003e","title":"二零二二年十一月第三周技术回顾"},{"content":"本文是 AI 基于原文分析后生成的。\n拥有广泛而深入的知识对个人发展至关重要，因为它不仅提升了我们的思维深度，还增强了 我们处理复杂问题的能力。知识的获取并不是简单地吸收外部信息，而是需要经过个人的深 思熟虑和批判性分析。在这个过程中，理解知识的相对性和条件性变得尤为重要。\n知识的相对性和条件性 知识的不确定性：历史上许多被认为是不变真理的知识，随着时间的推移和科学的 发展，被新的发现所颠覆。例如，牛顿力学被认为是解释物理现象的绝对框架，直到爱 因斯坦的相对论的出现。 知识的多元性：在不同文化和社会中，知识和真理的理解各有差异。例如，东西方 哲学传统在对待知识和真理的问题上就有很大的不同。 知识应用的重要性 理论与实践的结合：理论知识如果不能应用于实际生活中，其价值便大打折扣。应 用知识解决实际问题是知识价值的重要体现。 知识转化为智慧：通过个人经验和深思的过程，知识可以转化为智慧。这种转化使 我们能够更有效地解决问题，对复杂的生活和工作环境有更深的理解和应对能力。 成功的个性化路径 成功方法的多样性：没有普适的成功方法。每个人的背景、经历和目标都不同，因 此，成功的路径也应该是个性化的。例如，一些企业家通过独特的方法找到了商业成 功，而艺术家则通过自己独特的风格表达自己的创意。 适应环境的变化：变化是生活的常态。我们需要根据环境的变化不断调整自己的方 法和策略。例如，在快速变化的科技领域，不断学习新技能和适应新趋势是成功的关 键。 结论 综上所述，知识的积累和应用对于个人的成长和成功至关重要。我们需要认识到知识的相对 性和条件性，通过批判性思考和深度分析来吸收和应用知识。同时，我们也要理解成功没有 统一的模式，每个人都需要根据自己的情况创造适合自己的成功路径。在这个过程中，保持 对知识的开放态度和适应环境变化的能力是关键。通过这种方式，我们可以更有效地应对生 活中的挑战，实现个人目标。\n","permalink":"https://blog.bktus.com/archives/7gmavw/","summary":"\u003cp\u003e\u003cstrong\u003e本文是 AI 基于\u003ca href=\"https://blog.bktus.com/archives/0tui6f\"\u003e原文\u003c/a\u003e分析后生成的。\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e拥有广泛而深入的知识对个人发展至关重要，因为它不仅提升了我们的思维深度，还增强了\n我们处理复杂问题的能力。知识的获取并不是简单地吸收外部信息，而是需要经过个人的深\n思熟虑和批判性分析。在这个过程中，理解知识的相对性和条件性变得尤为重要。\u003c/p\u003e\n\u003ch2 id=\"知识的相对性和条件性\"\u003e知识的相对性和条件性\u003c/h2\u003e\n\u003col\u003e\n\u003cli\u003e\u003cstrong\u003e知识的不确定性\u003c/strong\u003e：历史上许多被认为是不变真理的知识，随着时间的推移和科学的\n发展，被新的发现所颠覆。例如，牛顿力学被认为是解释物理现象的绝对框架，直到爱\n因斯坦的相对论的出现。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e知识的多元性\u003c/strong\u003e：在不同文化和社会中，知识和真理的理解各有差异。例如，东西方\n哲学传统在对待知识和真理的问题上就有很大的不同。\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch2 id=\"知识应用的重要性\"\u003e知识应用的重要性\u003c/h2\u003e\n\u003col\u003e\n\u003cli\u003e\u003cstrong\u003e理论与实践的结合\u003c/strong\u003e：理论知识如果不能应用于实际生活中，其价值便大打折扣。应\n用知识解决实际问题是知识价值的重要体现。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e知识转化为智慧\u003c/strong\u003e：通过个人经验和深思的过程，知识可以转化为智慧。这种转化使\n我们能够更有效地解决问题，对复杂的生活和工作环境有更深的理解和应对能力。\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch2 id=\"成功的个性化路径\"\u003e成功的个性化路径\u003c/h2\u003e\n\u003col\u003e\n\u003cli\u003e\u003cstrong\u003e成功方法的多样性\u003c/strong\u003e：没有普适的成功方法。每个人的背景、经历和目标都不同，因\n此，成功的路径也应该是个性化的。例如，一些企业家通过独特的方法找到了商业成\n功，而艺术家则通过自己独特的风格表达自己的创意。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e适应环境的变化\u003c/strong\u003e：变化是生活的常态。我们需要根据环境的变化不断调整自己的方\n法和策略。例如，在快速变化的科技领域，不断学习新技能和适应新趋势是成功的关\n键。\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch2 id=\"结论\"\u003e结论\u003c/h2\u003e\n\u003cp\u003e综上所述，知识的积累和应用对于个人的成长和成功至关重要。我们需要认识到知识的相对\n性和条件性，通过批判性思考和深度分析来吸收和应用知识。同时，我们也要理解成功没有\n统一的模式，每个人都需要根据自己的情况创造适合自己的成功路径。在这个过程中，保持\n对知识的开放态度和适应环境变化的能力是关键。通过这种方式，我们可以更有效地应对生\n活中的挑战，实现个人目标。\u003c/p\u003e","title":"知识的相对性和条件性"},{"content":"本文是 AI 基于原文分析后生成的。\n论述 在我们追求目标的旅途中，常常希望能迅速实现这些目标，但这种心态往往会导致我们感到 急躁和焦虑。相反，我们应该培养一种内心的平和和耐心，同时在心中保持对目标的清晰认 识。通过这种方式，我们可以保持冷静，减少外界干扰，从而更加全面和周密地规划我们的 行动，增加成功的可能性。 在采取行动的过程中，专注是至关重要的。我们需要全神贯 注，集中精力于目标上。只有这样，我们才能发挥出全部的潜力和思考能力，从而在所追求 的领域中更加专业和专注。为了实现这一点，我们需要在行动之前对目标进行充分的了解和 思考，制定出科学合理的行动计划。当我们的思路清晰，计划周到时，我们的心思就能更加 集中，减少被杂念和外部干扰的可能性。 最后，成功的实现还需要耐心和毅力。在追求目 标的过程中，我们需要保持平和的心态，避免焦虑和急躁。在行动过程中，我们应不断地总 结经验，调整策略，从而稳步推进，达成最终的成功目标。 这种方法不仅适用于个人目标 的追求，也适用于更广泛的领域，如学术研究、职业发展甚至是艺术创作。在这些领域中， 心境的平和、行动的专注和过程中的耐心同样是成功的关键要素。通过这种方式，我们不仅 能够更有效地达成目标，还能在过程中发现更多的乐趣和价值，使得整个旅程成为一种丰富 和有益的经历。\n论证 追求目标的过程中保持内心平静、专注以及耐心的重要性可以从哲学和心理学的角度得到深 入的论证。\n哲学角度 斯多葛哲学：斯多葛学派强调内心的平和与自我控制。他们认为，通过理性控制情 绪，人可以达到一种内心的平静和满足。这种心态有助于我们在追求目标时保持冷静和 客观，避免被短期的情绪波动干扰。 亚里士多德的中庸之道：亚里士多德提出的中庸之道强调在行为和情感上寻找适 度。过度急躁或过分冷漠都不利于目标的实现。保持平衡的情绪和行动是达成目标的关 键。 心理学角度 心流理论：心理学家米哈伊·契克森米哈伊的心流理论指出，在完全专注于一项活 动时，个人可以达到最佳的心理状态，即“心流”。在这种状态下，人们不仅能够最大限 度地发挥潜能，还能从活动中获得极大的满足和快乐。 目标设定理论：根据心理学家埃德温·洛克的目标设定理论，明确且具有挑战性的 目标更容易激发个人的动力。同时，持续的关注和耐心是实现这些目标的关键。 实践中的应用 冥想和自我反省：定期进行冥想和自我反省可以帮助个人发展内心的平静和专注 力。这些实践有助于提升我们在面对挑战时的心理韧性和专注能力。 长期目标与短期行动的平衡：在追求长期目标的同时，设定可实现的短期目标可以 帮助我们保持动力和专注。这种方法可以使我们在追求目标的过程中保持耐心，逐步取 得进展。 结论 从哲学和心理学的角度来看，内心的平静、专注和耐心对于成功追求和实现目标至关重要。 这些心理状态不仅有助于我们在追求目标的过程中保持清晰的思路和有效的行动，而且能够 使我们在这个过程中保持积极的心态和心理健康。通过实践这些原则，我们可以在个人成长 和目标实现的旅程中获得更多的满足和成就感。\n","permalink":"https://blog.bktus.com/archives/dv99w5/","summary":"\u003cp\u003e\u003cstrong\u003e本文是 AI 基于\u003ca href=\"https://blog.bktus.com/archives/er6eer\"\u003e原文\u003c/a\u003e分析后生成的。\u003c/strong\u003e\u003c/p\u003e\n\u003ch2 id=\"论述\"\u003e论述\u003c/h2\u003e\n\u003cp\u003e在我们追求目标的旅途中，常常希望能迅速实现这些目标，但这种心态往往会导致我们感到\n急躁和焦虑。相反，我们应该培养一种内心的平和和耐心，同时在心中保持对目标的清晰认\n识。通过这种方式，我们可以保持冷静，减少外界干扰，从而更加全面和周密地规划我们的\n行动，增加成功的可能性。 在采取行动的过程中，专注是至关重要的。我们需要全神贯\n注，集中精力于目标上。只有这样，我们才能发挥出全部的潜力和思考能力，从而在所追求\n的领域中更加专业和专注。为了实现这一点，我们需要在行动之前对目标进行充分的了解和\n思考，制定出科学合理的行动计划。当我们的思路清晰，计划周到时，我们的心思就能更加\n集中，减少被杂念和外部干扰的可能性。 最后，成功的实现还需要耐心和毅力。在追求目\n标的过程中，我们需要保持平和的心态，避免焦虑和急躁。在行动过程中，我们应不断地总\n结经验，调整策略，从而稳步推进，达成最终的成功目标。 这种方法不仅适用于个人目标\n的追求，也适用于更广泛的领域，如学术研究、职业发展甚至是艺术创作。在这些领域中，\n心境的平和、行动的专注和过程中的耐心同样是成功的关键要素。通过这种方式，我们不仅\n能够更有效地达成目标，还能在过程中发现更多的乐趣和价值，使得整个旅程成为一种丰富\n和有益的经历。\u003c/p\u003e\n\u003ch2 id=\"论证\"\u003e论证\u003c/h2\u003e\n\u003cp\u003e追求目标的过程中保持内心平静、专注以及耐心的重要性可以从哲学和心理学的角度得到深\n入的论证。\u003c/p\u003e\n\u003ch3 id=\"哲学角度\"\u003e哲学角度\u003c/h3\u003e\n\u003col\u003e\n\u003cli\u003e\u003cstrong\u003e斯多葛哲学\u003c/strong\u003e：斯多葛学派强调内心的平和与自我控制。他们认为，通过理性控制情\n绪，人可以达到一种内心的平静和满足。这种心态有助于我们在追求目标时保持冷静和\n客观，避免被短期的情绪波动干扰。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e亚里士多德的中庸之道\u003c/strong\u003e：亚里士多德提出的中庸之道强调在行为和情感上寻找适\n度。过度急躁或过分冷漠都不利于目标的实现。保持平衡的情绪和行动是达成目标的关\n键。\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch3 id=\"心理学角度\"\u003e心理学角度\u003c/h3\u003e\n\u003col\u003e\n\u003cli\u003e\u003cstrong\u003e心流理论\u003c/strong\u003e：心理学家米哈伊·契克森米哈伊的心流理论指出，在完全专注于一项活\n动时，个人可以达到最佳的心理状态，即“心流”。在这种状态下，人们不仅能够最大限\n度地发挥潜能，还能从活动中获得极大的满足和快乐。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e目标设定理论\u003c/strong\u003e：根据心理学家埃德温·洛克的目标设定理论，明确且具有挑战性的\n目标更容易激发个人的动力。同时，持续的关注和耐心是实现这些目标的关键。\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch3 id=\"实践中的应用\"\u003e实践中的应用\u003c/h3\u003e\n\u003col\u003e\n\u003cli\u003e\u003cstrong\u003e冥想和自我反省\u003c/strong\u003e：定期进行冥想和自我反省可以帮助个人发展内心的平静和专注\n力。这些实践有助于提升我们在面对挑战时的心理韧性和专注能力。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e长期目标与短期行动的平衡\u003c/strong\u003e：在追求长期目标的同时，设定可实现的短期目标可以\n帮助我们保持动力和专注。这种方法可以使我们在追求目标的过程中保持耐心，逐步取\n得进展。\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch3 id=\"结论\"\u003e结论\u003c/h3\u003e\n\u003cp\u003e从哲学和心理学的角度来看，内心的平静、专注和耐心对于成功追求和实现目标至关重要。\n这些心理状态不仅有助于我们在追求目标的过程中保持清晰的思路和有效的行动，而且能够\n使我们在这个过程中保持积极的心态和心理健康。通过实践这些原则，我们可以在个人成长\n和目标实现的旅程中获得更多的满足和成就感。\u003c/p\u003e","title":"浅论专注于目标并尝试实现它"},{"content":"本文是 AI 基于原文分析后生成的。\n在现实行动中，我们常常误以为仅凭掌握知识就足以保证成功。然而，实际情况远比我们的 理论复杂得多。事物的变化和不确定性是常态，这些变化往往会成为我们实际行动中的障 碍。因此，我们在行动时必须保持谨慎，以应对可能出现的意外情况。\n知识与行动的不一致性 知识的局限性：我们通常认为知识等同于成功的保障，但这只是一种理想化的想 法。实际上，我们所学习的知识通常基于理论和主观想象，这些知识很难涵盖所有可能 的情况和细节。因此，成功的实现需要我们在实践中不断尝试和积累经验。 实践中的挑战：在实际行动过程中，我们经常会遇到各种挑战和反对意见。面对这 些批评，我们既不能盲目接受，也不能完全忽视。适当倾听他人的意见，并结合自己的 情况进行反思，是达成成功的重要环节。 批判性思维的重要性 对他人意见的审慎处理：他人提供的建议或批评可能基于他们自身的经验和视角， 这些可能并不完全适用于我们的具体情况。在接受建议时，我们需要审慎考虑，并结合 自己的实际情况作出决策。 实践与验证：通过实际行动检验自己的知识和决策的正确性是非常重要的。只有通 过实践，我们才能真正地理解知识的应用和自己决策的有效性。 持续的自我提升 应对变化和挑战：成功的路径并不是一成不变的。我们需要根据自己的实际情况和 遇到的具体挑战灵活调整行动策略。 不断学习和成长：在不断的行动过程中，我们应该持续地学习新知识，提升自己的 能力和技巧，以更好地应对不断变化的环境和挑战。 结论 综上所述，虽然知识是成功的重要基础，但实际行动中的成功取决于我们如何应用这些知 识，并根据实际情况不断调整和完善自己的行动策略。保持谨慎、开放的心态，不断地学习 和实践，以及有效地整合外界的意见和反馈，是走向成功的关键。通过这种方式，我们不仅 能够应对眼前的挑战，还能为未来的成功打下坚实的基础。\n","permalink":"https://blog.bktus.com/archives/8zkrzg/","summary":"\u003cp\u003e\u003cstrong\u003e本文是 AI 基于\u003ca href=\"https://blog.bktus.com/archives/9h5dq9\"\u003e原文\u003c/a\u003e分析后生成的。\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e在现实行动中，我们常常误以为仅凭掌握知识就足以保证成功。然而，实际情况远比我们的\n理论复杂得多。事物的变化和不确定性是常态，这些变化往往会成为我们实际行动中的障\n碍。因此，我们在行动时必须保持谨慎，以应对可能出现的意外情况。\u003c/p\u003e\n\u003ch3 id=\"知识与行动的不一致性\"\u003e知识与行动的不一致性\u003c/h3\u003e\n\u003col\u003e\n\u003cli\u003e\u003cstrong\u003e知识的局限性\u003c/strong\u003e：我们通常认为知识等同于成功的保障，但这只是一种理想化的想\n法。实际上，我们所学习的知识通常基于理论和主观想象，这些知识很难涵盖所有可能\n的情况和细节。因此，成功的实现需要我们在实践中不断尝试和积累经验。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e实践中的挑战\u003c/strong\u003e：在实际行动过程中，我们经常会遇到各种挑战和反对意见。面对这\n些批评，我们既不能盲目接受，也不能完全忽视。适当倾听他人的意见，并结合自己的\n情况进行反思，是达成成功的重要环节。\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch3 id=\"批判性思维的重要性\"\u003e批判性思维的重要性\u003c/h3\u003e\n\u003col\u003e\n\u003cli\u003e\u003cstrong\u003e对他人意见的审慎处理\u003c/strong\u003e：他人提供的建议或批评可能基于他们自身的经验和视角，\n这些可能并不完全适用于我们的具体情况。在接受建议时，我们需要审慎考虑，并结合\n自己的实际情况作出决策。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e实践与验证\u003c/strong\u003e：通过实际行动检验自己的知识和决策的正确性是非常重要的。只有通\n过实践，我们才能真正地理解知识的应用和自己决策的有效性。\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch3 id=\"持续的自我提升\"\u003e持续的自我提升\u003c/h3\u003e\n\u003col\u003e\n\u003cli\u003e\u003cstrong\u003e应对变化和挑战\u003c/strong\u003e：成功的路径并不是一成不变的。我们需要根据自己的实际情况和\n遇到的具体挑战灵活调整行动策略。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e不断学习和成长\u003c/strong\u003e：在不断的行动过程中，我们应该持续地学习新知识，提升自己的\n能力和技巧，以更好地应对不断变化的环境和挑战。\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch3 id=\"结论\"\u003e结论\u003c/h3\u003e\n\u003cp\u003e综上所述，虽然知识是成功的重要基础，但实际行动中的成功取决于我们如何应用这些知\n识，并根据实际情况不断调整和完善自己的行动策略。保持谨慎、开放的心态，不断地学习\n和实践，以及有效地整合外界的意见和反馈，是走向成功的关键。通过这种方式，我们不仅\n能够应对眼前的挑战，还能为未来的成功打下坚实的基础。\u003c/p\u003e","title":"浅论知行合一"},{"content":"实际发表时间：2018-05-25 原文\n在日常生活中，我偶然开始书写这些文字。写作对我来说，不仅是一种心灵的抚慰，更是一 种思考的深化。通过书写，我能够整理自己的思绪，静下心来深思。这些文字仿佛是一套哲 学的装备，既是理论的锐利武器，也是坚固的防御盾牌。它们帮助我面对生活中的挑战和困 惑，其中包括知行论的思考。这些想法源于我的日常生活、阅读的书籍和个人的反思。\n感官知识的局限性 在我们试图理解一个对象时，我们通常会依赖自己的感官，例如用眼睛观察、用耳朵聆听、 用手触摸来感受其质感。我们往往认为，通过感官的作用，我们能够捕捉到该对象的存在和 特性。 然而，我们的感官只能捕获有限的信息，并且这些信息可能会受到感官自身的局限 性和外界环境的影响。因此，我们无法完全确定自己感知到的是否代表了对象的真实本质。 更深入地探讨，如果存在一种与我们的感官系统截然不同的感官系统，那么它所感知到的对 象可能与我们的感知完全不同。\n康德的感官知识论：康德在其《纯粹理性批判》中指出，我们的感官知识受到时间 和空间这两个先验条件的限制，我们所能感知的只是事物的现象，而非其自身。 赫拉克利特关于变化的观点：古希腊哲学家赫拉克利特曾说：“万物流转，无物常 在。”这意味着我们的感官所捕捉的只是事物不断变化的状态，而非其恒定的本质。 思维的主观性和文化相对性 此外，我们的思考也受到我们的经历、背景和价值观等因素的影响。因此，我们对问题的思 考可能基于自身的视角，而不一定能深入到对象的本质。即使我们认为自己的思考已经很深 入，实际上它也可能仅仅是基于我们对对象的感官经验和个人偏好的理解。\n黑格尔的辩证法：黑格尔的辩证法认为，真理是通过对立统一的过程中不断展开 的。我们的思维受限于当前的知识结构和文化背景，因此总是处于发展和变化之中。 萨特的存在主义：萨特认为，人是自由而孤立的存在，我们的思考和价值观是主观 构建的。我们对事物的理解是基于个人的经验和选择，而非客观的真理。 知识的传递与解构 最终，我们接触到的知识也往往来源于他人的思维和传授。因此，我们所理解的对象可能仅 仅是从某些特定角度去解读，而并不一定是对象本身的真实本质。\n达尔文的进化论：在生物学上，达尔文的进化论指出，知识和能力的传递是一个适 应环境的过程。这同样适用于文化和知识的传递，知识不断地适应和改变。 德里达的解构主义：德里达的解构主义强调文本和语言的多义性，主张解构固有的 意义结构，揭示知识传递中的隐含偏见和局限性。 结论 综上所述，我们的感知和思考是复杂且多层次的。它们不仅受限于感官的局限性，还受到个 人经验、文化背景和思维方式的影响。因此，我们对事物的认识总是部分的、主观的，并且 不断变化。这些哲学理论提醒我们，对知识的追求应当是一个不断探索、质疑和重构的过 程，而非寻求一个确定无疑的终极真理。通过这种方式，我们可以更加深刻和全面地理解周 围的世界，并在这个过程中发现自身的价值和位置。\n","permalink":"https://blog.bktus.com/archives/219pfm/","summary":"\u003cp\u003e\u003cstrong\u003e实际发表时间：2018-05-25 \u003ca href=\"https://blog.bktus.com/archives/3794\"\u003e原文\u003c/a\u003e\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e在日常生活中，我偶然开始书写这些文字。写作对我来说，不仅是一种心灵的抚慰，更是一\n种思考的深化。通过书写，我能够整理自己的思绪，静下心来深思。这些文字仿佛是一套哲\n学的装备，既是理论的锐利武器，也是坚固的防御盾牌。它们帮助我面对生活中的挑战和困\n惑，其中包括知行论的思考。这些想法源于我的日常生活、阅读的书籍和个人的反思。\u003c/p\u003e\n\u003ch3 id=\"感官知识的局限性\"\u003e感官知识的局限性\u003c/h3\u003e\n\u003cp\u003e在我们试图理解一个对象时，我们通常会依赖自己的感官，例如用眼睛观察、用耳朵聆听、\n用手触摸来感受其质感。我们往往认为，通过感官的作用，我们能够捕捉到该对象的存在和\n特性。 然而，我们的感官只能捕获有限的信息，并且这些信息可能会受到感官自身的局限\n性和外界环境的影响。因此，我们无法完全确定自己感知到的是否代表了对象的真实本质。\n更深入地探讨，如果存在一种与我们的感官系统截然不同的感官系统，那么它所感知到的对\n象可能与我们的感知完全不同。\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\u003cstrong\u003e康德的感官知识论\u003c/strong\u003e：康德在其《纯粹理性批判》中指出，我们的感官知识受到时间\n和空间这两个先验条件的限制，我们所能感知的只是事物的现象，而非其自身。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e赫拉克利特关于变化的观点\u003c/strong\u003e：古希腊哲学家赫拉克利特曾说：“万物流转，无物常\n在。”这意味着我们的感官所捕捉的只是事物不断变化的状态，而非其恒定的本质。\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch3 id=\"思维的主观性和文化相对性\"\u003e思维的主观性和文化相对性\u003c/h3\u003e\n\u003cp\u003e此外，我们的思考也受到我们的经历、背景和价值观等因素的影响。因此，我们对问题的思\n考可能基于自身的视角，而不一定能深入到对象的本质。即使我们认为自己的思考已经很深\n入，实际上它也可能仅仅是基于我们对对象的感官经验和个人偏好的理解。\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\u003cstrong\u003e黑格尔的辩证法\u003c/strong\u003e：黑格尔的辩证法认为，真理是通过对立统一的过程中不断展开\n的。我们的思维受限于当前的知识结构和文化背景，因此总是处于发展和变化之中。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e萨特的存在主义\u003c/strong\u003e：萨特认为，人是自由而孤立的存在，我们的思考和价值观是主观\n构建的。我们对事物的理解是基于个人的经验和选择，而非客观的真理。\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch3 id=\"知识的传递与解构\"\u003e知识的传递与解构\u003c/h3\u003e\n\u003cp\u003e最终，我们接触到的知识也往往来源于他人的思维和传授。因此，我们所理解的对象可能仅\n仅是从某些特定角度去解读，而并不一定是对象本身的真实本质。\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\u003cstrong\u003e达尔文的进化论\u003c/strong\u003e：在生物学上，达尔文的进化论指出，知识和能力的传递是一个适\n应环境的过程。这同样适用于文化和知识的传递，知识不断地适应和改变。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e德里达的解构主义\u003c/strong\u003e：德里达的解构主义强调文本和语言的多义性，主张解构固有的\n意义结构，揭示知识传递中的隐含偏见和局限性。\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch3 id=\"结论\"\u003e结论\u003c/h3\u003e\n\u003cp\u003e综上所述，我们的感知和思考是复杂且多层次的。它们不仅受限于感官的局限性，还受到个\n人经验、文化背景和思维方式的影响。因此，我们对事物的认识总是部分的、主观的，并且\n不断变化。这些哲学理论提醒我们，对知识的追求应当是一个不断探索、质疑和重构的过\n程，而非寻求一个确定无疑的终极真理。通过这种方式，我们可以更加深刻和全面地理解周\n围的世界，并在这个过程中发现自身的价值和位置。\u003c/p\u003e","title":"论什么是事物本质"},{"content":"本文是 AI 基于原文分析后生成的。\n理解反质变的现象，需要深入探讨其背后的心理和哲学机制。反质变不仅是一个简单的心理 反应，而是一个涉及复杂情感、信念和认知的过程。它揭示了人类认知的深层结构，以及我 们如何处理信息和情感的方式。这种现象在日常生活中无处不在，从我们对人际关系的理解 到我们对世界的看法，都可能受到反质变的影响。\n反质变的心理动力学 认知失调理论：心理学家列昂·费斯廷格提出的认知失调理论解释了个人在面对矛 盾信息时的心理状态。当我们的信念与新的信息冲突时，可能会产生心理不适。在某些 情况下，为了减少这种不适，个人可能会改变原有的信念，这就是反质变的心理基础。 信任的脆弱性：信任建立在一系列复杂的心理过程之上，包括预期、信念和情感。 当这些预期被打破时，信任可能会迅速崩溃。这种崩溃不仅是对特定事件的反应，也是 对整个信任体系的重估。 哲学视角下的反质变 康德的认知论：康德在其作品中探讨了人类认知的限制。他认为，我们对事物的认 识受限于我们的感官和理性。在反质变的过程中，我们可能会意识到之前的认知是有限 的，从而导致信念的根本改变。 萨特的自由意志：萨特的存在主义哲学强调自由意志的重要性。他认为，人是自由 选择自己生活方式的。在反质变的过程中，这种自由意志可能被激活，促使个人重新评 估并选择不同的信念和行为模式。 反质变的社会文化因素 文化相对主义：不同的文化背景可能导致对同一事件的不同解读。在一个文化中被 视为信任破裂的事件，在另一个文化中可能被视为正常。这种文化差异可能影响个人对 事件的解读和反应。 社会认同理论：社会心理学家提出的社会认同理论指出，个人的自我认同部分基于 其所属的社会群体。当这些群体的信念或行为发生变化时，个人的认同也可能发生改 变，从而导致反质变。 反质变的实际应用 心理健康：理解反质变的心理过程对于心理健康领域至关重要。它有助于心理学家 和治疗师更好地理解和处理人们在面对重大生活事件时的心理反应。 冲突解决：在社会和政治冲突中，理解各方的反质变过程可以帮助中介者和冲突解 决者更有效地解决争端。 结论 总的来说，反质变的现象揭示了人类认知和情感的复杂性。在理解和处理信任、信念和情感 方面，我们需要认识到这些变化的非线性和不可预测性。对于反质变的深刻理解可以帮助我 们更好地理解人类心理和行为的复杂性，以及在个人和社会关系中如何更有效地建立和维护 信任。\n","permalink":"https://blog.bktus.com/archives/jiuc9z/","summary":"\u003cp\u003e\u003cstrong\u003e本文是 AI 基于\u003ca href=\"https://blog.bktus.com/archives/rr970y\"\u003e原文\u003c/a\u003e分析后生成的。\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e理解反质变的现象，需要深入探讨其背后的心理和哲学机制。反质变不仅是一个简单的心理\n反应，而是一个涉及复杂情感、信念和认知的过程。它揭示了人类认知的深层结构，以及我\n们如何处理信息和情感的方式。这种现象在日常生活中无处不在，从我们对人际关系的理解\n到我们对世界的看法，都可能受到反质变的影响。\u003c/p\u003e\n\u003ch3 id=\"反质变的心理动力学\"\u003e反质变的心理动力学\u003c/h3\u003e\n\u003col\u003e\n\u003cli\u003e\u003cstrong\u003e认知失调理论\u003c/strong\u003e：心理学家列昂·费斯廷格提出的认知失调理论解释了个人在面对矛\n盾信息时的心理状态。当我们的信念与新的信息冲突时，可能会产生心理不适。在某些\n情况下，为了减少这种不适，个人可能会改变原有的信念，这就是反质变的心理基础。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e信任的脆弱性\u003c/strong\u003e：信任建立在一系列复杂的心理过程之上，包括预期、信念和情感。\n当这些预期被打破时，信任可能会迅速崩溃。这种崩溃不仅是对特定事件的反应，也是\n对整个信任体系的重估。\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch3 id=\"哲学视角下的反质变\"\u003e哲学视角下的反质变\u003c/h3\u003e\n\u003col\u003e\n\u003cli\u003e\u003cstrong\u003e康德的认知论\u003c/strong\u003e：康德在其作品中探讨了人类认知的限制。他认为，我们对事物的认\n识受限于我们的感官和理性。在反质变的过程中，我们可能会意识到之前的认知是有限\n的，从而导致信念的根本改变。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e萨特的自由意志\u003c/strong\u003e：萨特的存在主义哲学强调自由意志的重要性。他认为，人是自由\n选择自己生活方式的。在反质变的过程中，这种自由意志可能被激活，促使个人重新评\n估并选择不同的信念和行为模式。\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch3 id=\"反质变的社会文化因素\"\u003e反质变的社会文化因素\u003c/h3\u003e\n\u003col\u003e\n\u003cli\u003e\u003cstrong\u003e文化相对主义\u003c/strong\u003e：不同的文化背景可能导致对同一事件的不同解读。在一个文化中被\n视为信任破裂的事件，在另一个文化中可能被视为正常。这种文化差异可能影响个人对\n事件的解读和反应。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e社会认同理论\u003c/strong\u003e：社会心理学家提出的社会认同理论指出，个人的自我认同部分基于\n其所属的社会群体。当这些群体的信念或行为发生变化时，个人的认同也可能发生改\n变，从而导致反质变。\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch3 id=\"反质变的实际应用\"\u003e反质变的实际应用\u003c/h3\u003e\n\u003col\u003e\n\u003cli\u003e\u003cstrong\u003e心理健康\u003c/strong\u003e：理解反质变的心理过程对于心理健康领域至关重要。它有助于心理学家\n和治疗师更好地理解和处理人们在面对重大生活事件时的心理反应。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e冲突解决\u003c/strong\u003e：在社会和政治冲突中，理解各方的反质变过程可以帮助中介者和冲突解\n决者更有效地解决争端。\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch3 id=\"结论\"\u003e结论\u003c/h3\u003e\n\u003cp\u003e总的来说，反质变的现象揭示了人类认知和情感的复杂性。在理解和处理信任、信念和情感\n方面，我们需要认识到这些变化的非线性和不可预测性。对于反质变的深刻理解可以帮助我\n们更好地理解人类心理和行为的复杂性，以及在个人和社会关系中如何更有效地建立和维护\n信任。\u003c/p\u003e","title":"论心理上的一种“反质变”现象"},{"content":"实际撰写时间：2017-05-20 20:57:23\n不得不承认，去年是一个低落的一年，不管是生活上，学习上，还是精神上。眼看着和目标 渐行渐远，自己又无力改变。\n但是，现在不同了。在度过了种种困难后，终于在艰难种找到了一种新的思考问题的方式， 也第一次发现了自我。在解决了一系列问题后，内忧趋于瓦解，我觉得，终于可以集中自己 的所有能力而不会受制于自己本身了。\n该找到的答案，我已经找到了。这个时候，内心感到宁静。是时候开始奔跑了。学会奔跑的 我，再也不会是被自己折磨苟且爬着和别人比赛的我。在我学会奔跑后，被我超越的，再也 不可能赶超回来。\n","permalink":"https://blog.bktus.com/archives/ypbzh5/","summary":"\u003cp\u003e\u003cstrong\u003e实际撰写时间：2017-05-20 20:57:23\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e不得不承认，去年是一个低落的一年，不管是生活上，学习上，还是精神上。眼看着和目标\n渐行渐远，自己又无力改变。\u003c/p\u003e\n\u003cp\u003e但是，现在不同了。在度过了种种困难后，终于在艰难种找到了一种新的思考问题的方式，\n也第一次发现了自我。在解决了一系列问题后，内忧趋于瓦解，我觉得，终于可以集中自己\n的所有能力而不会受制于自己本身了。\u003c/p\u003e\n\u003cp\u003e该找到的答案，我已经找到了。这个时候，内心感到宁静。是时候开始奔跑了。学会奔跑的\n我，再也不会是被自己折磨苟且爬着和别人比赛的我。在我学会奔跑后，被我超越的，再也\n不可能赶超回来。\u003c/p\u003e","title":"有关下一阶段的计划"},{"content":"很多人都会想养成一个好习惯，因为一个好习惯能够然仍长期做一件好的事情。这件好的事 情，每做一次都会带来一定的好的影响。而一个好的影响的一次次叠加，经过比较长的一段 时间，就能使得一个人的某个方面产生巨大的良性改变。而很多人想养成习惯却难以坚持下 来。反而求助于很多外力来“监督”自己，这样反倒让自己变得更加痛苦。 我觉得想养成习 惯而不得根本原因是缺乏内在的强大的动机。比如说，你想学习一门外语。你知道，学一门 外语能够有种种好处。但是，你其实内心清楚，这些好处只不过是锦上添花，并不会影响你 的前途。自己是骗不了自己的，我这里不是要说要不断暗示自己学习一门外语很重要。\n我这里想建议，你要让学习一门外语真的对你来说很重要，这才是有效的。比如说，你报了 一个出国留学的计划，是你人生规划的一部分，花了很多钱签订了合同。你现在是不学也不 行了，不学，你的人生计划就完不成，为了签订合同所花费的钱也打了水漂。这样，才会产 生强大的内驱力。这种内驱力一旦产生，它会在你的内心不断提醒你，不断鞭策你，让你自 动去拿起外语书。除非，你有什么拒绝它的理由，不然你的怠惰在你自己这里就过不了关。 另一个我发现的重要一点是，习惯的订立要顺应自然，最主要的是要顺应自己的内心。不能 说，给自己定一个自己不喜欢的习惯。你不喜欢某件事情，你非要去强迫自己做，那最终是 做不成的。喜不喜欢一件事情，主要看你先做这件事情前会不会有一种愉悦感，你能想到你 做这件事后，你能收获什么，这让你感到愉悦。有一种愉悦感，能够时不时地提醒你去做一 件事。 第三点，长期坚持而非一直坚持。不能说要强迫自己一直坚持某种习惯，因为随着 自己生活的继续，习惯是会被慢慢改变的。长期坚持，这意味着在一段很长的时间内，你是 会经常做这个事情的。\n偶尔的一两次没有做，不代表习惯没有了或者是习惯被打破了。因为事务的发展不是一帆风 顺的，总有反复。所以给自己一点做不一样事情或者不做这件事情的时间，让自己处于一种 比较放松的状态。记住，盈满则亏，让自己总是游刃有余一些，容许一些小的偷懒，养成习 惯的过程中就不会出现大的报复性波动。在养成习惯的过程中，你可能会发现这个习惯并不 是一个好习惯，或者并不适合你。这个时候请果断放弃这个习惯。因为养成习惯是一个正向 反馈，是自己不断激励自己，不断有所收获，而不是一个自己和自己内心拉扯的过程。 还 有一点就是，习惯的养成不能影响自己的正常作息。不能说我今天要背单词，由于一些突发 情况没有做成，然后在深夜也要背完，挤占了自己的睡觉时间。这是得不偿失的，反而会因 为自己的生活周期的失调，陷入一种疲于奔命的情况，导致习惯越来越难以养成。\n最后，如何知道一个习惯是否已经养成？当你发现，你今天不做这件事情的时候，好像缺了 点什么的时候。或者，某个时间会自动地想起去做这件事的时候。这个时候就可以认为习惯 养成了，你只要顺着来，享受习惯的惯性带给你的快乐，在这个过程中，你就能不断收获自 己在某一方面的成长和快乐。\n","permalink":"https://blog.bktus.com/archives/chxt92/","summary":"\u003cp\u003e很多人都会想养成一个好习惯，因为一个好习惯能够然仍长期做一件好的事情。这件好的事\n情，每做一次都会带来一定的好的影响。而一个好的影响的一次次叠加，经过比较长的一段\n时间，就能使得一个人的某个方面产生巨大的良性改变。而很多人想养成习惯却难以坚持下\n来。反而求助于很多外力来“监督”自己，这样反倒让自己变得更加痛苦。 我觉得想养成习\n惯而不得根本原因是缺乏内在的强大的动机。比如说，你想学习一门外语。你知道，学一门\n外语能够有种种好处。但是，你其实内心清楚，这些好处只不过是锦上添花，并不会影响你\n的前途。自己是骗不了自己的，我这里不是要说要不断暗示自己学习一门外语很重要。\u003c/p\u003e\n\u003cp\u003e我这里想建议，你要让学习一门外语真的对你来说很重要，这才是有效的。比如说，你报了\n一个出国留学的计划，是你人生规划的一部分，花了很多钱签订了合同。你现在是不学也不\n行了，不学，你的人生计划就完不成，为了签订合同所花费的钱也打了水漂。这样，才会产\n生强大的内驱力。这种内驱力一旦产生，它会在你的内心不断提醒你，不断鞭策你，让你自\n动去拿起外语书。除非，你有什么拒绝它的理由，不然你的怠惰在你自己这里就过不了关。\n另一个我发现的重要一点是，习惯的订立要顺应自然，最主要的是要顺应自己的内心。不能\n说，给自己定一个自己不喜欢的习惯。你不喜欢某件事情，你非要去强迫自己做，那最终是\n做不成的。喜不喜欢一件事情，主要看你先做这件事情前会不会有一种愉悦感，你能想到你\n做这件事后，你能收获什么，这让你感到愉悦。有一种愉悦感，能够时不时地提醒你去做一\n件事。 第三点，长期坚持而非一直坚持。不能说要强迫自己一直坚持某种习惯，因为随着\n自己生活的继续，习惯是会被慢慢改变的。长期坚持，这意味着在一段很长的时间内，你是\n会经常做这个事情的。\u003c/p\u003e\n\u003cp\u003e偶尔的一两次没有做，不代表习惯没有了或者是习惯被打破了。因为事务的发展不是一帆风\n顺的，总有反复。所以给自己一点做不一样事情或者不做这件事情的时间，让自己处于一种\n比较放松的状态。记住，盈满则亏，让自己总是游刃有余一些，容许一些小的偷懒，养成习\n惯的过程中就不会出现大的报复性波动。在养成习惯的过程中，你可能会发现这个习惯并不\n是一个好习惯，或者并不适合你。这个时候请果断放弃这个习惯。因为养成习惯是一个正向\n反馈，是自己不断激励自己，不断有所收获，而不是一个自己和自己内心拉扯的过程。 还\n有一点就是，习惯的养成不能影响自己的正常作息。不能说我今天要背单词，由于一些突发\n情况没有做成，然后在深夜也要背完，挤占了自己的睡觉时间。这是得不偿失的，反而会因\n为自己的生活周期的失调，陷入一种疲于奔命的情况，导致习惯越来越难以养成。\u003c/p\u003e\n\u003cp\u003e最后，如何知道一个习惯是否已经养成？当你发现，你今天不做这件事情的时候，好像缺了\n点什么的时候。或者，某个时间会自动地想起去做这件事的时候。这个时候就可以认为习惯\n养成了，你只要顺着来，享受习惯的惯性带给你的快乐，在这个过程中，你就能不断收获自\n己在某一方面的成长和快乐。\u003c/p\u003e","title":"自己总结出养成习惯的一些实用经验"},{"content":"如果你在安装了 Ubuntu 和 Windows 双系统后，又重装了 Windows 系统。那么 Grub 引导 项大概率会被 Windows Boot Manager 覆盖，这时候你就进不了 Ubuntu 了。但不要慌，按 照下面的步骤可以修复 grub2 引导，而且不需要安装额外的软件。\n制作 Ubuntu 的 U 盘启动盘 为了修复引导项，我们需要用到 Ubuntu 系统提供的软件工具。虽然我们暂时进不去原来的 Ubuntu 系统，但是我们可以使用写在 Ubuntu U 盘中的镜像来获得一个可用的基本 Ubuntu 环境。 在Ubuntu 官网下载镜像文件。 制作 U 盘启动盘可以使用免安装、小巧好用的rufus。你可以使 用 rufus 快速创建一个 U 盘启动盘，注意选择 GPT 模式。 引导 U 盘启动盘 重启电脑，在 BIOS 引导设置中引导 U 盘，然后进入 Ubuntu 引导安装程序后，在安装界 面选择 Try Ubuntu 进入 Ubuntu Live。\n执行 Grub2 重建操作 使用 fdisk，找到 EFI 分区，/boot 挂载点所在的分区（如果你没有设置/boot 挂载点则 为/挂载点）。 挂载/boot 挂载点所在的分区到/mnt 下（我的分区在 nvme 固态硬盘 上）。\n% sudo mount /dev/nvme0n1p6 /mnt/ 挂载 EFI 分区，在这里你需要知道在你的硬盘中那一个分区是 EFI 系统分区（我这里是第 五个分区）。\n% sudo mount /dev/nvme0n1p5 /mnt/boot/efi 连接其他必要的目录\n% sudo mount --bind /dev /mnt/dev % sudo mount --bind /dev/pts /mnt/dev/pts % sudo mount --bind /proc /mnt/proc % sudo mount --bind /sys /mnt/sys 转换根目录到/mnt 下\n% sudo chroot /mnt 修复 Grub2 引导 对于 32 位系统和 64 位系统，在这里你需要运行的命令是不同的。下面分别给出了它们对 应的命令。 32 位系统\n% sudo grub-install --target=i386-efi /dev/nvme0n1 64 位系统\n% sudo grub-install --target=x86\\_64-efi /dev/nvme0n1 执行完上面命令当中的一条后，再执行一下 Recheck\n% grub-install --recheck /dev/nvme0n1 后续清理步骤（可要可不要）\n% exit % sudo umount /mnt/sys % sudo umount /mnt/proc % sudo umount /mnt/dev/pts % sudo umount /mnt/dev % sudo umount /mnt 然后重新启动，你会发现已经能够成功进入 grub2 界面，并且 grub2 已经识别到了 Ubuntu 系统和 Windows Boot Manager。\n重建 Grub2 中的 Windows Boot Manager 引导项 重启电脑，通过 Grub 进入 Ubuntu 系统，运行以下命令重新生成配置文件\n% sudo update-grub ","permalink":"https://blog.bktus.com/archives/k3xpne/","summary":"\u003cp\u003e如果你在安装了 Ubuntu 和 Windows 双系统后，又重装了 Windows 系统。那么 Grub 引导\n项大概率会被 Windows Boot Manager 覆盖，这时候你就进不了 Ubuntu 了。但不要慌，按\n照下面的步骤可以修复 grub2 引导，而且不需要安装额外的软件。\u003c/p\u003e\n\u003ch2 id=\"制作-ubuntu-的-u-盘启动盘\"\u003e制作 Ubuntu 的 U 盘启动盘\u003c/h2\u003e\n\u003cp\u003e为了修复引导项，我们需要用到 Ubuntu 系统提供的软件工具。虽然我们暂时进不去原来的\nUbuntu 系统，但是我们可以使用写在 Ubuntu U 盘中的镜像来获得一个可用的基本 Ubuntu\n环境。 在\u003ca href=\"https://ubuntu.com/download/desktop\"\u003eUbuntu 官网\u003c/a\u003e下载镜像文件。\n\u003cimg loading=\"lazy\" src=\"https://image.cdn.bktus.com/i/2/2025/04/28/c504c695-aa38-05e7-19f4-879f896c56ad-2.webp\"\u003e\n制作 U 盘启动盘可以使用免安装、小巧好用的\u003ca href=\"https://rufus.ie/zh/\"\u003erufus\u003c/a\u003e。你可以使\n用 rufus 快速创建一个 U 盘启动盘，注意选择 GPT 模式。\n\u003cimg loading=\"lazy\" src=\"https://image.cdn.bktus.com/i/2/2025/04/28/61cd20f7-cee4-cdc6-de00-9bd832b81613-2.webp\"\u003e\u003c/p\u003e\n\u003ch2 id=\"引导-u-盘启动盘\"\u003e引导 U 盘启动盘\u003c/h2\u003e\n\u003cp\u003e重启电脑，在 BIOS 引导设置中引导 U 盘，然后进入 Ubuntu 引导安装程序后，在安装界\n面选择 Try Ubuntu 进入 Ubuntu Live。\u003c/p\u003e\n\u003ch2 id=\"执行-grub2-重建操作\"\u003e执行 Grub2 重建操作\u003c/h2\u003e\n\u003cp\u003e使用 fdisk，找到 EFI 分区，/boot 挂载点所在的分区（如果你没有设置/boot 挂载点则\n为/挂载点）。 挂载/boot 挂载点所在的分区到/mnt 下（我的分区在 nvme 固态硬盘\n上）。\u003c/p\u003e","title":"Ubuntu双系统重装Windows后修复grub2引导  UEFI"},{"content":"在国内使用 yarn 安装 electron、chromedriver 是经常出现无法连接的问题，这个时候， 相对于设置代理，我们可能更希望使用在国内的镜像仓库来加速这个过程。并且安装 electron 的时候，单单设置 yarn 的全局代理是没有用的。那如何根据不同的软件包设置 代理呢？你可以按照以下列表中提供的命令样例来设置。\nyarn 全局设置 设置 yarn 安装一般的软件包时使用的仓库地址。\nyarn config set registry https://r.npm.taobao.org 注册模块镜像 yarn config set disturl https://npm.taobao.org/dist # node-gyp 编译依赖的 node 源码镜像 这是运行以上命令的结果，你可以很清楚地看到设置是否成功。\nyarn 安装特定软件包设置 设置 yarn 安装 electron 、chromedriver 等软件包安装使用到的仓库地址。你可以按照 需要选择一两个命令来设置，一股脑全部加上是被必要的。\nyarn config set sass\\_binary\\_site https://npm.taobao.org/mirrors/node-sass # node-sass 二进制包镜像 yarn config set electron\\_mirror https://npm.taobao.org/mirrors/electron/ # electron 二进制包镜像 yarn config set puppeteer\\_download\\_host https://npm.taobao.org/mirrors # puppeteer 二进制包镜像 yarn config set chromedriver\\_cdnurl https://npm.taobao.org/mirrors/chromedriver # chromedriver 二进制包镜像 yarn config set operadriver\\_cdnurl https://npm.taobao.org/mirrors/operadriver # operadriver 二进制包镜像 yarn config set phantomjs\\_cdnurl https://npm.taobao.org/mirrors/phantomjs # phantomjs 二进制包镜像 yarn config set selenium\\_cdnurl https://npm.taobao.org/mirrors/selenium # selenium 二进制包镜像 yarn config set node\\_inspector\\_cdnurl https://npm.taobao.org/mirrors/node-inspector # node-inspector 二进制包镜像 和上面的类似，运行命令后你可以很清楚地看到设置是否成功。\n","permalink":"https://blog.bktus.com/archives/fs5oy3/","summary":"\u003cp\u003e在国内使用 yarn 安装 electron、chromedriver 是经常出现无法连接的问题，这个时候，\n相对于设置代理，我们可能更希望使用在国内的镜像仓库来加速这个过程。并且安装\nelectron 的时候，单单设置 yarn 的全局代理是没有用的。那如何根据不同的软件包设置\n代理呢？你可以按照以下列表中提供的命令样例来设置。\u003c/p\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"https://image.cdn.bktus.com/i/2025/05/05/01209ec0-4f98-56db-4453-f1074273e7a5.webp\"\u003e\u003c/p\u003e\n\u003ch2 id=\"yarn-全局设置\"\u003eyarn 全局设置\u003c/h2\u003e\n\u003cp\u003e设置 yarn 安装一般的软件包时使用的仓库地址。\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003eyarn config set registry https://r.npm.taobao.org 注册模块镜像\nyarn config set disturl https://npm.taobao.org/dist # node-gyp 编译依赖的 node 源码镜像\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003e这是运行以上命令的结果，你可以很清楚地看到设置是否成功。\u003c/p\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"https://image.cdn.bktus.com/i/2025/05/05/6a35e2d0-9204-3dbb-3565-d2ad83c52cc5.webp\"\u003e\u003c/p\u003e\n\u003ch2 id=\"yarn-安装特定软件包设置\"\u003eyarn 安装特定软件包设置\u003c/h2\u003e\n\u003cp\u003e设置 yarn 安装 electron 、chromedriver 等软件包安装使用到的仓库地址。你可以按照\n需要选择一两个命令来设置，一股脑全部加上是被必要的。\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003eyarn config set sass\\_binary\\_site https://npm.taobao.org/mirrors/node-sass # node-sass 二进制包镜像\nyarn config set electron\\_mirror https://npm.taobao.org/mirrors/electron/ # electron 二进制包镜像\nyarn config set puppeteer\\_download\\_host https://npm.taobao.org/mirrors # puppeteer 二进制包镜像\nyarn config set chromedriver\\_cdnurl https://npm.taobao.org/mirrors/chromedriver # chromedriver 二进制包镜像\nyarn config set operadriver\\_cdnurl https://npm.taobao.org/mirrors/operadriver # operadriver 二进制包镜像\nyarn config set phantomjs\\_cdnurl https://npm.taobao.org/mirrors/phantomjs # phantomjs 二进制包镜像\nyarn config set selenium\\_cdnurl https://npm.taobao.org/mirrors/selenium # selenium 二进制包镜像\nyarn config set node\\_inspector\\_cdnurl https://npm.taobao.org/mirrors/node-inspector # node-inspector 二进制包镜像\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003e和上面的类似，运行命令后你可以很清楚地看到设置是否成功。\u003c/p\u003e","title":"解决yarn安装electron、chromedriver等软件包超时、缓慢的问题"},{"content":"oh my zsh 是十分好用的基于 zsh 的配置框架，它能简化 Linux 终端用户的很多日常操 作。而且，它的界面相对于原始的 bash 来说，非常好看。这篇文章就是用来介绍如何在 Ubuntu 下安装并配置 oh my zsh 及其常用插件的。\noh my zsh 安装与基本配置 在 oh my zsh 安装前需要先执行以下命令安装下列 git、curl、zsh。\n% sudo apt install git curl zsh 安装完依赖项目后，直接执行下面的命令来一键安装：\n% sh -c \u0026#34;$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)\u0026#34; 设置 zsh 为默认 shell：\n% chsh 按照提示一步一来，输入/bin/zsh 即可。\n常用插件安装 单单配置 oh my zsh 本身是远远不够的，你可能需要以下常用插件来真正达到简化操作的 目的。\nz 历史目录管理 zsh-autosuggestions 命令提示 zsh-syntax-highlighting 高亮 sudo 忘记加 sudo 前缀之时 extract 一个命令解压几乎所有安装包 z 无需额外安装，直接在 plugins 中填写即可。\nzsh-autosuggestions 先执行以下命令，然后在 plugins 中填写。\n% git clone git://github.com/zsh-users/zsh-autosuggestions ${ZSH\\_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions zsh-syntax-highlighting 先执行以下命令，然后在 plugins 中填写。\n% git clone https://github.com/zsh-users/zsh-syntax-highlighting.git ${ZSH\\_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting sudo 无需额外安装，直接在 plugins 中填写即可。\nextract 无需额外安装，直接在 plugins 中填写即可。\n设置代理 我们可以通过在.zshrc 中加入以下配置来启动可开关的代理功能：\nproxy() { export https\\_proxy=http://127.0.0.1:1234 export http\\_proxy=http://127.0.0.1:1234 export all\\_proxy=socks5://127.0.0.1:1234 } unproxy() { unset https\\_proxy unset http\\_proxy export all\\_proxy } 你可以将 1234 改为你任何想要的端口。\n刷新配置 执行以下命令来刷新配置。\n% source ~/.zshrc ","permalink":"https://blog.bktus.com/archives/2hlujj/","summary":"\u003cp\u003eoh my zsh 是十分好用的基于 zsh 的配置框架，它能简化 Linux 终端用户的很多日常操\n作。而且，它的界面相对于原始的 bash 来说，非常好看。这篇文章就是用来介绍如何在\nUbuntu 下安装并配置 oh my zsh 及其常用插件的。\u003c/p\u003e\n\u003ch2 id=\"oh-my-zsh-安装与基本配置\"\u003eoh my zsh 安装与基本配置\u003c/h2\u003e\n\u003cp\u003e在 oh my zsh 安装前需要先执行以下命令安装下列 git、curl、zsh。\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e% sudo apt install git curl zsh\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003e安装完依赖项目后，直接执行下面的命令来一键安装：\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e% sh -c \u0026#34;$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)\u0026#34;\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003e设置 zsh 为默认 shell：\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e% chsh\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003e按照提示一步一来，输入/bin/zsh 即可。\u003c/p\u003e\n\u003ch2 id=\"常用插件安装\"\u003e常用插件安装\u003c/h2\u003e\n\u003cp\u003e单单配置 oh my zsh 本身是远远不够的，你可能需要以下常用插件来真正达到简化操作的\n目的。\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003ez 历史目录管理\u003c/li\u003e\n\u003cli\u003ezsh-autosuggestions 命令提示\u003c/li\u003e\n\u003cli\u003ezsh-syntax-highlighting 高亮\u003c/li\u003e\n\u003cli\u003esudo 忘记加 sudo 前缀之时\u003c/li\u003e\n\u003cli\u003eextract 一个命令解压几乎所有安装包\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"z\"\u003ez\u003c/h3\u003e\n\u003cp\u003e无需额外安装，直接在 plugins 中填写即可。\u003c/p\u003e\n\u003ch3 id=\"zsh-autosuggestions\"\u003ezsh-autosuggestions\u003c/h3\u003e\n\u003cp\u003e先执行以下命令，然后在 plugins 中填写。\u003c/p\u003e","title":"oh my zsh 在Ubuntu下的配置教程"},{"content":"一直以来我都使用一些成熟的配置文件来帮助我配置、美化 vim 编辑器，然而我原来使用 的 spf13-vim.sh 已经很久不更新了，它的插件列表中有些插件已经不能正常下载了。所以 我想寻找它的替代品，最后发现 space-vim-dark 还不错。\nspace-vim-dark 基本介绍 经过我的寻找，找到了一个比较好用且美观的配置文件 space-vim-dark。这个 space-vim-dark 可能是国人编写的 vim 配置文件，它能很好使得 vim 适应暗色模式。 先 贴出仓库地址：https://github.com/liuchengxu/space-vim-dark 这是它的效果图，整体 看还是不错的。\n用它配置、美化 vim 编辑器 设置 space-vim-dark 非常简单，你只需要在你的 vim 插件管理器中添加下面的设置。\nPlug \u0026#39;liuchengxu/space-vim-dark\u0026#39; 整体使用下来，我觉得该有的插件还是有的，并且用起来顺手不少。\n加入额外配置 在~/.spacevim 中可以加入你自己的一些配置。\n","permalink":"https://blog.bktus.com/archives/wwztim/","summary":"\u003cp\u003e一直以来我都使用一些成熟的配置文件来帮助我配置、美化 vim 编辑器，然而我原来使用\n的 spf13-vim.sh 已经很久不更新了，它的插件列表中有些插件已经不能正常下载了。所以\n我想寻找它的替代品，最后发现 space-vim-dark 还不错。\u003c/p\u003e\n\u003ch2 id=\"space-vim-dark-基本介绍\"\u003espace-vim-dark 基本介绍\u003c/h2\u003e\n\u003cp\u003e经过我的寻找，找到了一个比较好用且美观的配置文件 space-vim-dark。这个\nspace-vim-dark 可能是国人编写的 vim 配置文件，它能很好使得 vim 适应暗色模式。 先\n贴出仓库地址：https://github.com/liuchengxu/space-vim-dark 这是它的效果图，整体\n看还是不错的。\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"配置vim编辑器的效\\n果\" loading=\"lazy\" src=\"https://image.cdn.bktus.com/i/2025/05/05/a84e404a-54ea-5d41-549e-24f225375c5c.webp\"\u003e\u003c/p\u003e\n\u003ch2 id=\"用它配置美化-vim-编辑器\"\u003e用它配置、美化 vim 编辑器\u003c/h2\u003e\n\u003cp\u003e设置 space-vim-dark 非常简单，你只需要在你的 vim 插件管理器中添加下面的设置。\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003ePlug \u0026#39;liuchengxu/space-vim-dark\u0026#39;\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003e整体使用下来，我觉得该有的插件还是有的，并且用起来顺手不少。\u003c/p\u003e\n\u003ch2 id=\"加入额外配置\"\u003e加入额外配置\u003c/h2\u003e\n\u003cp\u003e在~/.spacevim 中可以加入你自己的一些配置。\u003c/p\u003e","title":"使用 space-vim-dark 一键配置、美化vim编辑器"},{"content":"由于要数学建模中需要解决一个三维装箱的问题，我经过搜寻选定了张德富教授等在计算机 学报上发表的《求解三维装箱问题的混合模拟退火算法》这篇论文作为解决问题的理论基 础。\n该论文的摘要如下： 提出了一个高效求解三维装箱问题(Three Dimensional Container Loading Problem 3D-CLP)的混合模拟 退火 算法 .三维装箱问题要求装载给定箱子集合的一个子 集到容器 中 , 使得被装载 的箱子总体 积最大 .文中介绍 的 混合 模拟退火算法基于三个重要算 法 :(1)复合块生成算法 , 与传统算法 不同的是文中提出的复合块 不只包含单 一 种类 的箱子 , 而是可以在一定的限制条件下包含 任意种类的箱子 .(2)基础启发式算法 , 该 算法 基于块装 载 , 可以 按 照指定装载序列生成放置方案.(3)模拟退火算法,以复合块 生成和基础启发式算法为基础, 将装载序列作为可行 放置 方案的编码 , 在编码空间中 采用模拟退火算法进行搜 索以寻找问题的近似最 优解 .文 中采用 1 50 0 个弱异构 和 强异构的装箱问题数据对算法进行测试 .实验结 果表明 , 混合模拟退火算法的填充率超 过了目前已知的 优秀算法 .\nDOI号: 10.3724/SP.J.1016.2009.02147\n本文算法实现其计算结果 该实现的核心函数 MATLAB 代码 function \\[solution, newPlan, rate\\] = container\\_packeting(bcon, plan, blockTable, blockNeed) %UNTITLED2 根据大小容器的规格以及小容器的数量信息计算出打包的方案 % bigContainer 大容器的规格向量 \\[L W H\\] % smallContainers 小容器的规格矩阵\\[L W H;\\] % smallContainersNumber 小容器的数量向量 \\[n\\] solution = {}; newPlan = \\[\\]; searchDeep = 40960; clf; draw\\_container(\\[0,0,0\\], bcon); % 剩余空间栈 lspace\\_s = get\\_stack(); % 空隙栈 iterval\\_s = get\\_stack(); itervalSpace\\_s = get\\_stack(); % 初始化剩余空间为大容器空间 lspace\\_s = stack\\_push(lspace\\_s, get\\_space(\\[0,0,0\\], bcon)); psCount = 0; while lspace\\_s.count \u0026gt; 0 psCount = psCount + 1; % 从剩余空间栈中取得可用的剩余空间 lspace = stack\\_top(lspace\\_s); lspace\\_s = stack\\_pop(lspace\\_s); lspace\\_size = lspace.size; lspace\\_base = lspace.base\\_point; if\\_input = 0; \\[blockList, blockListNeed\\] = get\\_block\\_list(blockTable, blockNeed, lspace\\_size(1), lspace\\_size(2), lspace\\_size(3)); theBlockNeed = NaN; find\\_idx = 1; if size(blockList, 1) == 0 find\\_avaliable = 0; else find\\_avaliable = 1; if plan(psCount) \u0026lt;= size(blockListNeed, 1) theBlockNeed = blockListNeed(plan(psCount),:); find\\_idx = plan(psCount); else theBlockNeed = blockListNeed(size(blockListNeed, 1),:); find\\_idx = size(blockListNeed, 1); end newPlan = \\[newPlan, find\\_idx\\]; end %% locate container if find\\_avaliable == 1 newLocatedBlock = {lspace\\_base, blockList(find\\_idx, :), theBlockNeed{1}, theBlockNeed{2}}; solution(size(solution, 1) + 1, :) = newLocatedBlock; spaceInfo = newLocatedBlock{4}; spaceLastInfo = spaceInfo(size(spaceInfo,1), :); locateInfo = newLocatedBlock{2}; hold on; draw\\_container(lspace\\_base, locateInfo(1:3)); itervalSpace\\_s = stack\\_push(itervalSpace\\_s, (1-spaceLastInfo(1)) \\* prod(locateInfo(1:3))); if\\_input = 1; % 剩余空间计算 % H方向剩余空间 location = lspace\\_base; location(2) = location(2) + locateInfo(2); iterval\\_space = get\\_space(location, \\[locateInfo(1) , lspace\\_size(2) - locateInfo(2), locateInfo(3)\\]); lspace\\_s = stack\\_push(lspace\\_s, iterval\\_space); % L方向剩余空间 location = lspace\\_base; location(1) = location(1) + locateInfo(1); left\\_space = get\\_space(location, \\[lspace\\_size(1) - locateInfo(1), lspace\\_size(2), lspace\\_size(3)\\]); lspace\\_s = stack\\_push(lspace\\_s, left\\_space); % W方向剩余空间 location = lspace\\_base; location(3) = location(3) + locateInfo(3); iterval\\_space = get\\_space(location, \\[locateInfo(1) , lspace\\_size(2), lspace\\_size(3) - locateInfo(3)\\]); lspace\\_s = stack\\_push(lspace\\_s, iterval\\_space); end if if\\_input == 1 else % 压入空隙栈 iterval\\_s = stack\\_push(iterval\\_s, lspace); % lspace\\_s = stack\\_push(lspace\\_s, lspace); end end rate = space\\_used\\_rate(bcon, solution); end function rate = space\\_used\\_rate(container, solution) spaceHasTotal = 0.0; for i = 1:1:size(solution, 1) mergeMessage = solution{i, 4}; for k = 1:1:size(mergeMessage,1) spaceHasTotal = spaceHasTotal + prod(mergeMessage(k, 3:5)); end end rate = spaceHasTotal / prod(container); end 箱体产生函数 function \\[blockTable, boxNeedTable\\] = simple\\_block\\_generate\\_indepent(container, box, num) simBlockCount = 0; simBlockTable = zeros(256, 8); for type = 1:1:size(box, 1) for nx = 1:1:num(type) for ny = 1:1:num(type)/nx for nz = 1:1:num(type)/ny/nx if box(type,3) \\* nx \u0026lt; container(3) \u0026amp;\u0026amp; box(type, 1) \\* ny \u0026lt; container(1) \u0026amp;\u0026amp; box(type, 2) \\* nz \u0026lt; container(2) newSimBlock = \\[box(type, :), nx, ny, nz, type, prod(box(type, :)) \\* nx \\* ny \\* nz\\]; simBlockTable(simBlockCount + 1, :) = newSimBlock; simBlockCount = simBlockCount + 1; end end end end end simBlockTable = simBlockTable(1:simBlockCount, :); simBlockTable = sortrows(simBlockTable, 8); simBlockTable = simBlockTable(:, 1:7); % 处理成启发式算法可以处理的格式 simBlockTable(:, 13) = ones(size(simBlockTable, 1), 1); simBlockTable(:, 11) = simBlockTable(:,4) .\\* simBlockTable(:,3); simBlockTable(:, 12) = simBlockTable(:,5) .\\* simBlockTable(:,1); % ly = ay simBlockTable(:, 8) = simBlockTable(:, 12); % lx = ax simBlockTable(:, 10) = simBlockTable(:, 11); % lz = lz0 \\* nz simBlockTable(:, 9) = simBlockTable(:, 2) .\\* simBlockTable(:, 6); boxNeedTable = cell(size(simBlockTable, 1), 2); for i = 1:1:size(simBlockTable, 1) boxNeedTable{i, 1} = zeros(1, size(box ,1)); boxNeedTable{i, 1}(simBlockTable(i, 7)) = simBlockTable(i, 4) \\* simBlockTable(i, 5) \\* simBlockTable(i, 6); boxNeedTable{i, 2} = \\[simBlockTable(i, 7), 0, simBlockTable(i, 8:10)\\]; end blockTable = simBlockTable(:, 8:13); blockTable(:,7) = blockTable(:, 1) .\\* blockTable(:, 2) .\\* blockTable(:, 3); \\[blockTable, idx\\] = sortrows(blockTable, \\[ 7 \\]); boxNeedTable = boxNeedTable(idx, :); end 箱体组合产生函数 function \\[blockTable, boxNeedTable\\] = complex\\_block\\_genrate(container, box, num, level) simBlockTable = simple\\_block\\_generate(container, box, num); simBlockTable(:, 13) = ones(size(simBlockTable, 1), 1); simBlockTable(:, 11) = simBlockTable(:,4) .\\* simBlockTable(:,3); simBlockTable(:, 12) = simBlockTable(:,5) .\\* simBlockTable(:,1); spaceUsedRateMin = 0.90; % ly = ay simBlockTable(:, 8) = simBlockTable(:, 12); % lx = ax simBlockTable(:, 10) = simBlockTable(:, 11); % lz = lz0 \\* nz simBlockTable(:, 9) = simBlockTable(:, 2) .\\* simBlockTable(:, 6); boxNeedTable = cell(size(simBlockTable, 1), 2); for i = 1:1:size(simBlockTable, 1) boxNeedTable{i, 1} = zeros(1, size(box ,1)); boxNeedTable{i, 1}(simBlockTable(i, 7)) = simBlockTable(i, 4) \\* simBlockTable(i, 5) \\* simBlockTable(i, 6); boxNeedTable{i, 2} = \\[simBlockTable(i, 7), 0, simBlockTable(i, 8:10)\\]; end blockTable = simBlockTable(:, 8:13); for level = 1:1:level newBlockTable = zeros(128,6); newBlockCount = 1; boxNeedCount = size(boxNeedTable, 1); for a = 1:1:size(blockTable,1) for b = a:1:size(blockTable, 1) if b == a continue; end if blockTable(a, 6) == blockTable(b, 6) % x 方向合并 if blockTable(a, 1) == blockTable(a, 5) \u0026amp;\u0026amp; blockTable(a, 3) == blockTable(a, 4) \u0026amp;\u0026amp; blockTable(a, 2) == blockTable(b, 2) tempBlockMerged = \\[max(blockTable(a,1), blockTable(b,1)), max(blockTable(a,2), blockTable(b, 2)), blockTable(a,3) + blockTable(b,3), blockTable(a,4) + blockTable(b,4), min(blockTable(a,5), blockTable(b,5)), max(blockTable(a, 6), blockTable(b, 6)) + 1\\]; rate = space\\_used\\_rate(tempBlockMerged(1:3), blockTable(a,1:3), blockTable(b,1:3)); if rate \u0026gt; spaceUsedRateMin newBlockTable(newBlockCount, :) = tempBlockMerged; boxNeedCount = boxNeedCount + 1; newBlockCount = newBlockCount + 1; boxNeedTable{boxNeedCount, 1} = boxNeedTable{a,1} + boxNeedTable{b,1}; boxNeedTable{boxNeedCount, 2} = \\[boxNeedTable{a, 2}; rate, 1, blockTable(b, 1:3)\\]; end end % y 方向合并 if blockTable(a, 5) == blockTable(a, 1) \u0026amp;\u0026amp; blockTable(b, 5) == blockTable(b, 1) \u0026amp;\u0026amp; blockTable(a, 2) == blockTable(b, 2) tempBlockMerged = \\[blockTable(a,1) + blockTable(b,1), max(blockTable(a, 2), blockTable(b, 2)), max(blockTable(a, 3), blockTable(b, 3)), min(blockTable(a, 4), blockTable(b, 4)), blockTable(a, 5) + blockTable(b, 5), max(blockTable(a, 6), blockTable(b, 6)) + 1\\]; rate = space\\_used\\_rate(tempBlockMerged(1:3), blockTable(a,1:3), blockTable(b,1:3)); if rate \u0026gt; spaceUsedRateMin newBlockTable(newBlockCount, :) = tempBlockMerged; newBlockCount = newBlockCount + 1; boxNeedCount = boxNeedCount + 1; boxNeedTable{boxNeedCount, 1} = boxNeedTable{a,1} + boxNeedTable{b,1}; boxNeedTable{boxNeedCount, 2} = \\[boxNeedTable{a, 2}; rate, 2, blockTable(b, 1:3)\\]; end end % z 方向合并 if blockTable(a,4) \u0026gt;= blockTable(b,3) \u0026amp;\u0026amp; blockTable(a, 5) \u0026gt;= blockTable(b, 1) tempBlockMerged = \\[max(blockTable(a,1), blockTable(b, 1)), blockTable(a, 2) + blockTable(b, 2), max(blockTable(a, 3), blockTable(b, 3)), blockTable(b, 4), blockTable(b, 5), max(blockTable(a, 6), blockTable(b, 6)) + 1\\]; rate = space\\_used\\_rate(tempBlockMerged(1:3), blockTable(a,1:3), blockTable(b,1:3)); if rate \u0026gt; spaceUsedRateMin newBlockTable(newBlockCount, :) = tempBlockMerged; newBlockCount = newBlockCount + 1; boxNeedCount = boxNeedCount + 1; boxNeedTable{boxNeedCount, 1} = boxNeedTable{a,1} + boxNeedTable{b,1}; boxNeedTable{boxNeedCount, 2} = \\[boxNeedTable{a, 2}; rate, 3, blockTable(b, 1:3)\\]; end end end end end blockTable = cat(1, blockTable, newBlockTable(1:newBlockCount-1, :)); % 消除等价复杂块 blockTableTemp = blockTable(:, 1:5); \\[~, ia\\] = unique(blockTableTemp, \u0026#39;stable\u0026#39;, \u0026#39;rows\u0026#39;); blockTable = blockTable(ia, :); boxNeedTable = boxNeedTable(ia, :); end blockTable(:,7) = blockTable(:, 1) .\\* blockTable(:, 2) .\\* blockTable(:, 3); \\[blockTable, idx\\] = sortrows(blockTable, \\[ 7 \\]); boxNeedTable = boxNeedTable(idx, :); end function \\[rate\\] = space\\_used\\_rate(container, box1, box2) rate = (prod(box1) + prod(box2)) /prod(container); end Main 函数 ts = 1; tf = 0.005; dt = 0.98; length = 50; maxSeq = 64; maxChoice = 1024; click = 1; tempRate = \\[\\]; tempRateX = \\[\\]; numList = \\[64,64,64,64,64,64,64,128,128,128\\]; container = StandardISOContainer; % \\[blockTable, blockNeed\\] = complex\\_block\\_genrate(StandardISOContainer, Packets, numList, 1); ps = ones(1, maxSeq); ps(1) = 1; \\[solution, plan, rate\\] = container\\_packeting(container, ps, blockTable, blockNeed); best = plan; best\\_rate = rate; best\\_solution = solution; t = ts; while t \u0026gt; tf for i = 1:1:length k = randi(size(ps)); nps = ps; nps(k) = randi(maxChoice); \\[solution, nplan, nrate\\] = container\\_packeting(container, nps, blockTable, blockNeed); % disp(\\[\u0026#39;TempPlan: \u0026#39;, mat2str(nplan)\\]); % fprintf(\u0026#34;TempRate: %f\\\\n\u0026#34;,nrate); click = click + 1; if nrate \u0026gt; rate ps = nps; plan = nplan; rate = nrate; tempRateX = \\[tempRateX, click\\]; tempRate = \\[tempRate, nrate\\]; elseif rand(0, 1) \u0026gt; exp((nrate - rate) \\* 10 / t) ps = nps; plan = nplan; rate = nrate; end if nrate \u0026gt; best\\_rate best = nplan; best\\_rate = nrate; best\\_solution = solution; disp(\\[\u0026#39;BestPlan: \u0026#39;, mat2str(best)\\]); fprintf(\u0026#34;BestRate: %f\\\\n\u0026#34;,best\\_rate); end end t = (1 - t \\* dt) \\* t; end disp(\\[\u0026#39;BestPlan: \u0026#39;, mat2str(best)\\]); fprintf(\u0026#34;BestRate: %f\\\\n\u0026#34;,best\\_rate); figure(1) draw\\_solution(container, best\\_solution); figure(2) plot(tempRateX, tempRate); save Solution best\\_solution best\\_rate tempRateX tempRate ","permalink":"https://blog.bktus.com/archives/kl9byw/","summary":"\u003cp\u003e由于要数学建模中需要解决一个三维装箱的问题，我经过搜寻选定了张德富教授等在计算机\n学报上发表的《求解三维装箱问题的混合模拟退火算法》这篇论文作为解决问题的理论基\n础。\u003c/p\u003e\n\u003ch3 id=\"该论文的摘要如下\"\u003e该论文的摘要如下：\u003c/h3\u003e\n\u003cblockquote\u003e\n\u003cp\u003e提出了一个高效求解三维装箱问题(Three Dimensional Container Loading Problem\n3D-CLP)的混合模拟 退火 算法 .三维装箱问题要求装载给定箱子集合的一个子 集到容器\n中 , 使得被装载 的箱子总体 积最大 .文中介绍 的 混合 模拟退火算法基于三个重要算\n法 :(1)复合块生成算法 , 与传统算法 不同的是文中提出的复合块 不只包含单 一 种类\n的箱子 , 而是可以在一定的限制条件下包含 任意种类的箱子 .(2)基础启发式算法 , 该\n算法 基于块装 载 , 可以 按 照指定装载序列生成放置方案.(3)模拟退火算法,以复合块\n生成和基础启发式算法为基础, 将装载序列作为可行 放置 方案的编码 , 在编码空间中\n采用模拟退火算法进行搜 索以寻找问题的近似最 优解 .文 中采用 1 50 0 个弱异构 和\n强异构的装箱问题数据对算法进行测试 .实验结 果表明 , 混合模拟退火算法的填充率超\n过了目前已知的 优秀算法 .\u003c/p\u003e\u003c/blockquote\u003e\n\u003cp\u003eDOI号: 10.3724/SP.J.1016.2009.02147\u003c/p\u003e","title":"求解三维装箱问题的混合模拟退火算法的实现 MATLAB"},{"content":"最近，我阅读了一篇名为 TransRepair: Automatic Testing and Improvement of Machine Translation 的研究论文。该论文介绍了一种名为 TransRepair 的方法，用于 在软件测试领域下自动测试机器翻译模型。下面，我将从几个方面对论文的内容进行总结， 并讨论其中的关键点。\nTransRepair 简介 TransRepair 是一种用于自动检测和修复机器翻译软件一致性问题的方法。它提供了黑盒和 灰盒两种方法来解决机器翻译软件的一致性问题。TransRepair 的主要步骤包括生成测试用 例、创建测试准则和自动修复过程。该方法提供了清晰、严谨和详细的测试用例生成算法， 并采用了四种句子差异的量化方法进行比较。此外，TransRepair 还运用了结构一致性原理 作为断言，并提供了全面的实验设计和多样化的结果。\n关键问题的理解 一致性问题是指机器翻译软件在处理一组具有相似语义和结构但某些特定词语上略有不 同的句子时，在对应翻译句集合中的某一句或几句中的某个或几个部分出现的语义、结 构不一致的现象。 TransRepair 生成测试用例的方法是对输入的原句进行词语替换，形成突变句组。为了 实现这一操作，TransRepair 使用了词向量模型来计算词语之间的关联性。在选择候选 词后，还会将其带入句子中进行成分分析，以确定句子的语义和语法是否发生较大变 化。 在验证测试用例输出句子对的一致性时，TransRepair 首先使用 Widiff 进行字符串成 分的差异性比较分析。为了增强相似度量化的可靠性，TransRepair 还构造了原句和翻 译句中涉及的差异成分的部分删除集合，并计算集合中每个元素之间的相似度，选择最 大相似值。论文中使用了四种不同的方法来量化相似度，其中部分方法与之前提到的 SIT 方法有相似之处。 论文中的实验设计具有独特的特点。它首先提出问题并对解决方法进行探讨，然后围绕 这四个问题设计实验并提供适当形式的实验数据。实验从多个角度论证了该方法的有效 性，包括准确度、有效性、修复能力以及与人工方式的对比等。实验数据的呈现直观易 懂。 TransRepair 在处理阈值上与 SIT 方法不同。它通过机器小步遍历运算来获得统计上 最优的阈值，并采用人工辅助和统计学分析的方式进行一致性判别，其阈值设定逻辑更 具说服力。而 SIT 方法的阈值设定大多依靠经验，说服力和可操作性较低。 在 TransRepair 中，自动修复可以分为黑盒和灰盒两种方式。黑盒对应于 Google Translate，由于该软件未开源，对于输入输出的相关参数了解有限，因此只能对输入 和输出本身进行操作。灰盒对应于 Transformer，它的源码和训练集可获取，因此可以 对其输出结果的可能性进行把握，并在训练集和模型结构上进行修复操作。 TransRepair 的优势在于对一致性问题的自动检测和修复。该方法具有高准确度、可行 性和可复现性，这与其准确的实施方法以及对现有方法缺陷的考虑和补充密切相关。然 而，该方法的效率较低，有效性仅限于一致性问题。 总体而言，论文 TransRepair 介绍了 TransRepair 方法作为一种有效的自动测试和改 进机器翻译软件的方法，特别解决了一致性问题。论文详细解释了该方法，并提供了实验证 据和比较分析。\n","permalink":"https://blog.bktus.com/archives/f6adzy/","summary":"\u003cp\u003e最近，我阅读了一篇名为 \u003cstrong\u003eTransRepair: Automatic Testing and Improvement of\nMachine Translation\u003c/strong\u003e 的研究论文。该论文介绍了一种名为 TransRepair 的方法，用于\n在软件测试领域下自动测试机器翻译模型。下面，我将从几个方面对论文的内容进行总结，\n并讨论其中的关键点。\u003c/p\u003e\n\u003ch2 id=\"transrepair-简介\"\u003eTransRepair 简介\u003c/h2\u003e\n\u003cp\u003eTransRepair 是一种用于自动检测和修复机器翻译软件一致性问题的方法。它提供了黑盒和\n灰盒两种方法来解决机器翻译软件的一致性问题。TransRepair 的主要步骤包括生成测试用\n例、创建测试准则和自动修复过程。该方法提供了清晰、严谨和详细的测试用例生成算法，\n并采用了四种句子差异的量化方法进行比较。此外，TransRepair 还运用了结构一致性原理\n作为断言，并提供了全面的实验设计和多样化的结果。\u003c/p\u003e\n\u003ch2 id=\"关键问题的理解\"\u003e关键问题的理解\u003c/h2\u003e\n\u003col\u003e\n\u003cli\u003e一致性问题是指机器翻译软件在处理一组具有相似语义和结构但某些特定词语上略有不\n同的句子时，在对应翻译句集合中的某一句或几句中的某个或几个部分出现的语义、结\n构不一致的现象。\u003c/li\u003e\n\u003cli\u003eTransRepair 生成测试用例的方法是对输入的原句进行词语替换，形成突变句组。为了\n实现这一操作，TransRepair 使用了词向量模型来计算词语之间的关联性。在选择候选\n词后，还会将其带入句子中进行成分分析，以确定句子的语义和语法是否发生较大变\n化。\u003c/li\u003e\n\u003cli\u003e在验证测试用例输出句子对的一致性时，TransRepair 首先使用 Widiff 进行字符串成\n分的差异性比较分析。为了增强相似度量化的可靠性，TransRepair 还构造了原句和翻\n译句中涉及的差异成分的部分删除集合，并计算集合中每个元素之间的相似度，选择最\n大相似值。论文中使用了四种不同的方法来量化相似度，其中部分方法与之前提到的\nSIT 方法有相似之处。\u003c/li\u003e\n\u003cli\u003e论文中的实验设计具有独特的特点。它首先提出问题并对解决方法进行探讨，然后围绕\n这四个问题设计实验并提供适当形式的实验数据。实验从多个角度论证了该方法的有效\n性，包括准确度、有效性、修复能力以及与人工方式的对比等。实验数据的呈现直观易\n懂。\u003c/li\u003e\n\u003cli\u003eTransRepair 在处理阈值上与 SIT 方法不同。它通过机器小步遍历运算来获得统计上\n最优的阈值，并采用人工辅助和统计学分析的方式进行一致性判别，其阈值设定逻辑更\n具说服力。而 SIT 方法的阈值设定大多依靠经验，说服力和可操作性较低。\u003c/li\u003e\n\u003cli\u003e在 TransRepair 中，自动修复可以分为黑盒和灰盒两种方式。黑盒对应于 Google\nTranslate，由于该软件未开源，对于输入输出的相关参数了解有限，因此只能对输入\n和输出本身进行操作。灰盒对应于 Transformer，它的源码和训练集可获取，因此可以\n对其输出结果的可能性进行把握，并在训练集和模型结构上进行修复操作。\u003c/li\u003e\n\u003cli\u003eTransRepair 的优势在于对一致性问题的自动检测和修复。该方法具有高准确度、可行\n性和可复现性，这与其准确的实施方法以及对现有方法缺陷的考虑和补充密切相关。然\n而，该方法的效率较低，有效性仅限于一致性问题。\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e总体而言，论文 \u003cstrong\u003eTransRepair\u003c/strong\u003e 介绍了 TransRepair 方法作为一种有效的自动测试和改\n进机器翻译软件的方法，特别解决了一致性问题。论文详细解释了该方法，并提供了实验证\n据和比较分析。\u003c/p\u003e","title":"TransRepair: Automatic Testing and Improvement of Machine Translation（机器翻译的自动化测试和改进）"},{"content":"我之前阅读了 Structure-Invariant Testing for Machine Translation 这篇论文，它提 出了一种关于机器翻译软件系统鲁棒性问题的检测方法。下面我将从几个方面详细介绍我对 其中内容的理解。\n主要内容 SIT 是关于机器翻译软件系统鲁棒性问题的检测方法。这种方法利用了一个蜕变测试中的蜕 变关系，即\u0026quot;结构不变性\u0026quot;。通过选择原始句子、生成相似句子、从翻译软件获取结果、进行 成分解析并量化句子差异、根据设定的阈值筛选并发现问题，SIT 可以高效地检测出机器翻 译软件系统的鲁棒性问题。根据实验结果，SIT 在 19 秒内可以处理 2k+句子，并且对于 Google/Bing Translate 的准确度达到了 70%。然而，仍有提升的空间，可能是由于阈值选 择的原因。\n对几个关键问题的理解 为什么机器翻译软件存在鲁棒性问题？ 机器翻译软件系统的核心模块通常采用深度学 习方法或技术。深度学习模型中每层的维度较高，导致训练模型在向量空间中对不同标 签区域的界定可能模糊不清。当输入值接近边界时，稍微做出微小改变可能导致模型输 出剧烈变化。 什么是结构不变性？ 结构不变性是指经过对某种语言的句子进行一些特定且微小的词 单位修改后，其语义和语法上的结构在转换为对应翻译后通常保持不变。结构不变性是 研究机器翻译软件系统相关问题的经验和统计学意义上的一个切入点。 为什么要引入结构不变性？ 引入结构不变性是为了进行蜕变测试，以探索机器翻译软 件系统的鲁棒性问题。引入结构不变性的目的有两个：一是由于自然语言关系和变化复 杂多样，难以得到一种通用的测试定理作为基准进行测试，因此通过控制变量，得到类 似于经验或统计意义上正确的起点，展开测试研究；二是自然语言相关测试的测试用例 难以人工构建，引入结构不变性可以方便地利用现有少量样本生成大量测试用例。 如何利用结构不变性生成语义与语法相似的语句？ 在 SIT 中，使用了 BERT 模型来生 成语义与语法相似的语句。SIT 依赖于 BERT 的大型语料训练以及遮罩和双向反馈学习 等技术，以抑制词语替换后整个句子的语义改变或不符合语法和使用习惯等问题。SIT 通过在 BERT 之后增加一个轻量级的分类器来辅助生成预备替换词语的候选列表。 如何量化句子的差异以判断机器翻译软件系统是否存在鲁棒性问题？ SIT 使用了三种 方法来量化句子差异：字符串差异分析、成分解析树分析和依存解析树分析。SIT 直接 对翻译软件的输出结果进行以上三种分析，并对它们的效果进行比较。然而，这三种句 子差异分析方法都有一定的局限性，可以在进一步的工作中探索综合使用这三种方法进 行判定的方式。 SIT 具有哪些优势？有哪些不足？ 在论文中，作者讨论了 SIT 的优势和不足。总的来 说，SIT 的优势在于其能够检测多种类型错误（未翻译、过度翻译、错误调整、逻辑不 清）。然而，我认为其测试用例的生成方式、错误量化和检测方法相对粗糙，导致实验 下准确性并不高。修复和阈值设定需要人工参与，这也是其另一个不足之处。 SIT 可以有哪些应用？ SIT 主要应用于对运用了 AI 模型的机器翻译软件系统进行鲁 棒性测试。通过 SIT 的自动检测和人工修复训练样本，机器翻译软件的鲁棒性可以得 到提升。 总结 SIT 是一种检测机器翻译软件系统鲁棒性问题的方法。通过选择原始句子、生成相似句子、 获取翻译结果、进行成分解析和量化句子差异，SIT 可以高效地检测机器翻译软件系统的鲁 棒性问题。实验结果显示，SIT 可以在 19 秒内处理 2k+句子，并且对于 Google/Bing Translate 的准确度达到了 70%。然而，仍有改进的空间，可能是由于阈值选择的原 因。SIT 利用 BERT 模型生成语义和语法相似的语句，并使用三种方法来量化句子差异。总 体而言，SIT 的优势在于能够检测多种类型的错误，但其测试用例生成方式和检测方法仍有 改进空间。SIT 主要应用于对应用 AI 模型的机器翻译软件系统进行鲁棒性测试，并通过自 动检测和人工修复训练样本来提升鲁棒性。\n","permalink":"https://blog.bktus.com/archives/e6oo4m/","summary":"\u003cp\u003e我之前阅读了 Structure-Invariant Testing for Machine Translation 这篇论文，它提\n出了一种关于机器翻译软件系统鲁棒性问题的检测方法。下面我将从几个方面详细介绍我对\n其中内容的理解。\u003c/p\u003e\n\u003ch2 id=\"主要内容\"\u003e主要内容\u003c/h2\u003e\n\u003cp\u003eSIT 是关于机器翻译软件系统鲁棒性问题的检测方法。这种方法利用了一个蜕变测试中的蜕\n变关系，即\u0026quot;结构不变性\u0026quot;。通过选择原始句子、生成相似句子、从翻译软件获取结果、进行\n成分解析并量化句子差异、根据设定的阈值筛选并发现问题，SIT 可以高效地检测出机器翻\n译软件系统的鲁棒性问题。根据实验结果，SIT 在 19 秒内可以处理 2k+句子，并且对于\nGoogle/Bing Translate 的准确度达到了 70%。然而，仍有提升的空间，可能是由于阈值选\n择的原因。\u003c/p\u003e\n\u003ch2 id=\"对几个关键问题的理解\"\u003e对几个关键问题的理解\u003c/h2\u003e\n\u003col\u003e\n\u003cli\u003e为什么机器翻译软件存在鲁棒性问题？ 机器翻译软件系统的核心模块通常采用深度学\n习方法或技术。深度学习模型中每层的维度较高，导致训练模型在向量空间中对不同标\n签区域的界定可能模糊不清。当输入值接近边界时，稍微做出微小改变可能导致模型输\n出剧烈变化。\u003c/li\u003e\n\u003cli\u003e什么是结构不变性？ 结构不变性是指经过对某种语言的句子进行一些特定且微小的词\n单位修改后，其语义和语法上的结构在转换为对应翻译后通常保持不变。结构不变性是\n研究机器翻译软件系统相关问题的经验和统计学意义上的一个切入点。\u003c/li\u003e\n\u003cli\u003e为什么要引入结构不变性？ 引入结构不变性是为了进行蜕变测试，以探索机器翻译软\n件系统的鲁棒性问题。引入结构不变性的目的有两个：一是由于自然语言关系和变化复\n杂多样，难以得到一种通用的测试定理作为基准进行测试，因此通过控制变量，得到类\n似于经验或统计意义上正确的起点，展开测试研究；二是自然语言相关测试的测试用例\n难以人工构建，引入结构不变性可以方便地利用现有少量样本生成大量测试用例。\u003c/li\u003e\n\u003cli\u003e如何利用结构不变性生成语义与语法相似的语句？ 在 SIT 中，使用了 BERT 模型来生\n成语义与语法相似的语句。SIT 依赖于 BERT 的大型语料训练以及遮罩和双向反馈学习\n等技术，以抑制词语替换后整个句子的语义改变或不符合语法和使用习惯等问题。SIT\n通过在 BERT 之后增加一个轻量级的分类器来辅助生成预备替换词语的候选列表。\u003c/li\u003e\n\u003cli\u003e如何量化句子的差异以判断机器翻译软件系统是否存在鲁棒性问题？ SIT 使用了三种\n方法来量化句子差异：字符串差异分析、成分解析树分析和依存解析树分析。SIT 直接\n对翻译软件的输出结果进行以上三种分析，并对它们的效果进行比较。然而，这三种句\n子差异分析方法都有一定的局限性，可以在进一步的工作中探索综合使用这三种方法进\n行判定的方式。\u003c/li\u003e\n\u003cli\u003eSIT 具有哪些优势？有哪些不足？ 在论文中，作者讨论了 SIT 的优势和不足。总的来\n说，SIT 的优势在于其能够检测多种类型错误（未翻译、过度翻译、错误调整、逻辑不\n清）。然而，我认为其测试用例的生成方式、错误量化和检测方法相对粗糙，导致实验\n下准确性并不高。修复和阈值设定需要人工参与，这也是其另一个不足之处。\u003c/li\u003e\n\u003cli\u003eSIT 可以有哪些应用？ SIT 主要应用于对运用了 AI 模型的机器翻译软件系统进行鲁\n棒性测试。通过 SIT 的自动检测和人工修复训练样本，机器翻译软件的鲁棒性可以得\n到提升。\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch2 id=\"总结\"\u003e总结\u003c/h2\u003e\n\u003cp\u003eSIT 是一种检测机器翻译软件系统鲁棒性问题的方法。通过选择原始句子、生成相似句子、\n获取翻译结果、进行成分解析和量化句子差异，SIT 可以高效地检测机器翻译软件系统的鲁\n棒性问题。实验结果显示，SIT 可以在 19 秒内处理 2k+句子，并且对于 Google/Bing\nTranslate 的准确度达到了 70%。然而，仍有改进的空间，可能是由于阈值选择的原\n因。SIT 利用 BERT 模型生成语义和语法相似的语句，并使用三种方法来量化句子差异。总\n体而言，SIT 的优势在于能够检测多种类型的错误，但其测试用例生成方式和检测方法仍有\n改进空间。SIT 主要应用于对应用 AI 模型的机器翻译软件系统进行鲁棒性测试，并通过自\n动检测和人工修复训练样本来提升鲁棒性。\u003c/p\u003e","title":"Structure-Invariant Testing for Machine Translation (SIT) 论文阅读总结"},{"content":"最近由于工作需要，我阅读了很多关于 C++右值方面的材料。在 C++中，右值是一个非常重 要的概念，它对于理解 C++的内部机制和实现高效代码至关重要。本文将总结了 10 个关于 右值的实践经验。\nC++右值有两种：纯右值、将亡值。右值引用可以延长将亡值的生命周期，使得将亡值 能够被正常使用，而不会被错误地释放。 右值引用的一个作用是延长右值的生命周期。右值引用可以延长将亡值的生命周期，使 得将亡值能够被正常使用，而不会被错误地释放。 临时对象作为右值处理。这种对象往往会在一些表达式中自动创建，然后被立即使用。 使用右值引用来绑定临时对象，可以让程序更高效地处理这些对象，避免了不必要的内 存拷贝操作。 移动构造函数尽可绑定右值非常量。这是因为右值非常量可以被修改，而右值常量则不 能。移动构造函数会破坏源对象，所以只有右值非常量才能被移动构造函数绑定。 对于一个右值对象，允许调用成员函数。这与左值对象类似。但需要注意的是，对于一 个将亡值，如果其生命周期结束，那么调用其成员函数可能会导致程序出现未定义行 为。 右值可以被修改（这也说明了其可以被破坏）。因此，在使用右值时需要谨慎处理，以 避免因为修改了右值而导致程序出现问题。 右值不能当作左值使用，左值可以当作右值使用。这是因为左值具有实际地址，并且与 右值有同样地表现。 常量左值引用可以绑定到右值。这种引用可以避免将右值对象修改的风险，同时还可以 延长右值的生命周期，使其能够被正常使用。 返回右值引用的函数在几乎所有情况下是糟糕的。因为返回右值引用往往会导致右值的 生命周期延长，从而使其可能被错误地使用。对于返回右值的函数，建议使用值返回的 方式。 在大部分情况下 return 中使用是 std::move 并不会将事情变得更好，相反该操作会 阻止编译器进行返回值优化。 ","permalink":"https://blog.bktus.com/archives/u2cy96/","summary":"\u003cp\u003e最近由于工作需要，我阅读了很多关于 C++右值方面的材料。在 C++中，右值是一个非常重\n要的概念，它对于理解 C++的内部机制和实现高效代码至关重要。本文将总结了 10 个关于\n右值的实践经验。\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eC++右值有两种：纯右值、将亡值。右值引用可以延长将亡值的生命周期，使得将亡值\n能够被正常使用，而不会被错误地释放。\u003c/li\u003e\n\u003cli\u003e右值引用的一个作用是延长右值的生命周期。右值引用可以延长将亡值的生命周期，使\n得将亡值能够被正常使用，而不会被错误地释放。\u003c/li\u003e\n\u003cli\u003e临时对象作为右值处理。这种对象往往会在一些表达式中自动创建，然后被立即使用。\n使用右值引用来绑定临时对象，可以让程序更高效地处理这些对象，避免了不必要的内\n存拷贝操作。\u003c/li\u003e\n\u003cli\u003e移动构造函数尽可绑定右值非常量。这是因为右值非常量可以被修改，而右值常量则不\n能。移动构造函数会破坏源对象，所以只有右值非常量才能被移动构造函数绑定。\u003c/li\u003e\n\u003cli\u003e对于一个右值对象，允许调用成员函数。这与左值对象类似。但需要注意的是，对于一\n个将亡值，如果其生命周期结束，那么调用其成员函数可能会导致程序出现未定义行\n为。\u003c/li\u003e\n\u003cli\u003e右值可以被修改（这也说明了其可以被破坏）。因此，在使用右值时需要谨慎处理，以\n避免因为修改了右值而导致程序出现问题。\u003c/li\u003e\n\u003cli\u003e右值不能当作左值使用，左值可以当作右值使用。这是因为左值具有实际地址，并且与\n右值有同样地表现。\u003c/li\u003e\n\u003cli\u003e常量左值引用可以绑定到右值。这种引用可以避免将右值对象修改的风险，同时还可以\n延长右值的生命周期，使其能够被正常使用。\u003c/li\u003e\n\u003cli\u003e返回右值引用的函数在几乎所有情况下是糟糕的。因为返回右值引用往往会导致右值的\n生命周期延长，从而使其可能被错误地使用。对于返回右值的函数，建议使用值返回的\n方式。\u003c/li\u003e\n\u003cli\u003e在大部分情况下 return 中使用是 std::move 并不会将事情变得更好，相反该操作会\n阻止编译器进行返回值优化。\u003c/li\u003e\n\u003c/ol\u003e","title":"掌握C++右值引用的十大要点：如何正确处理纯右值和将亡值"},{"content":"处理器中断处理是计算机体系架构中必须掌握的知识之一。在 Intel 的 x86 处理器下，中 断可以分为外部中断、异常和陷入。外部中断来自硬件，是随机发生的，而异常则是处理器 内部执行指令过程中检测到错误条件的结果。陷入则是由程序产生的，通常是由 INT n、INTO 等指令触发的。\n在 x86 处理器中，中断处理程序负责处理中断，异常处理程序负责处理异常，而系统调用 服务程序则负责处理陷入。这些处理程序可以位于内存空间的任何位置，并且可以有不同的 特权级。\nIntel 处理器使用中断门、陷阱门和任务门来定义处理程序的入口地址。其中，中断门和陷 阱门是进入异常处理程序的门户。选择符和偏移量合起来定义了一个处理程序的入口地址。 中断门进入处理程序时 IF 标志被清掉，而陷阱门进入处理程序时 IF 标志保持不变。\nIntel 处理器为每个中断和异常定义了一个中断向量号，并通过中断描述符表（IDT）建立 中断向量号和门之间的对应关系。IDT 可以驻留在线性地址空间的任何位置。Intel 处理器 专门提供了一个 IDTR 寄存器来记录 IDT 的基地址和界限信息。\nIntel 处理器定义了 256 个中断向量号，其中 0~31 被处理器保留。 处理器中的异常可以 分为故障类异常和终止类异常。故障类异常可以被更正，而终止类异常则是无法被处理器自 行解决的严重错误。为了保证安全性，通过中断门或陷阱门只能向同级或更高特权级的代码 段转移控制。通常情况下，处理程序定义在内核代码段中（0 特权级代码段）。 在中断发 生时，处理器会自动在栈顶压入一些参数，其中 EFLAGS 是中断或异常发生前的系统状 态，SS:ESP 是中断或者异常发生前用户堆栈的栈顶，CS:EIP 是中断或者异常的返回地址。\n64 位模式中，处理程序必须在 64 位代码段中，因而中断和陷阱门描述符被扩充到了 16 字节，其中偏移量被扩充到了 64 位；IDT 找那个仅有新格式的门描述符；堆栈宽度变成了 64 位，而且当中断发生时，会无条件地压入栈指针（SS:RSP）当需要切换堆栈时 SS 被强 制设置为 NULL；新增了中断堆栈表（IST）机制，允许特定的中断或者异常指定专门的堆 栈。\n","permalink":"https://blog.bktus.com/archives/eicrzq/","summary":"\u003cp\u003e处理器中断处理是计算机体系架构中必须掌握的知识之一。在 Intel 的 x86 处理器下，中\n断可以分为外部中断、异常和陷入。外部中断来自硬件，是随机发生的，而异常则是处理器\n内部执行指令过程中检测到错误条件的结果。陷入则是由程序产生的，通常是由 INT\nn、INTO 等指令触发的。\u003c/p\u003e\n\u003cp\u003e在 x86 处理器中，中断处理程序负责处理中断，异常处理程序负责处理异常，而系统调用\n服务程序则负责处理陷入。这些处理程序可以位于内存空间的任何位置，并且可以有不同的\n特权级。\u003c/p\u003e\n\u003cp\u003eIntel 处理器使用中断门、陷阱门和任务门来定义处理程序的入口地址。其中，中断门和陷\n阱门是进入异常处理程序的门户。选择符和偏移量合起来定义了一个处理程序的入口地址。\n中断门进入处理程序时 IF 标志被清掉，而陷阱门进入处理程序时 IF 标志保持不变。\u003c/p\u003e\n\u003cp\u003eIntel 处理器为每个中断和异常定义了一个中断向量号，并通过中断描述符表（IDT）建立\n中断向量号和门之间的对应关系。IDT 可以驻留在线性地址空间的任何位置。Intel 处理器\n专门提供了一个 IDTR 寄存器来记录 IDT 的基地址和界限信息。\u003c/p\u003e\n\u003cp\u003eIntel 处理器定义了 256 个中断向量号，其中 0~31 被处理器保留。 处理器中的异常可以\n分为故障类异常和终止类异常。故障类异常可以被更正，而终止类异常则是无法被处理器自\n行解决的严重错误。为了保证安全性，通过中断门或陷阱门只能向同级或更高特权级的代码\n段转移控制。通常情况下，处理程序定义在内核代码段中（0 特权级代码段）。 在中断发\n生时，处理器会自动在栈顶压入一些参数，其中 EFLAGS 是中断或异常发生前的系统状\n态，SS:ESP 是中断或者异常发生前用户堆栈的栈顶，CS:EIP 是中断或者异常的返回地址。\u003c/p\u003e\n\u003cp\u003e64 位模式中，处理程序必须在 64 位代码段中，因而中断和陷阱门描述符被扩充到了 16\n字节，其中偏移量被扩充到了 64 位；IDT 找那个仅有新格式的门描述符；堆栈宽度变成了\n64 位，而且当中断发生时，会无条件地压入栈指针（SS:RSP）当需要切换堆栈时 SS 被强\n制设置为 NULL；新增了中断堆栈表（IST）机制，允许特定的中断或者异常指定专门的堆\n栈。\u003c/p\u003e","title":"探究计算机中断处理：了解Intel x86处理器下的外部中断、异常和陷入"},{"content":"一旦处理器内存保护机制被启动，处理器就会对每一次内存访问进行保护性检查，以确保所 有的访问都满足保护策略。保护检查和地址转换是并行进行的。\n保护检查包含段级检查和页级检查。检查顺序是先段后页，检查依据是段描述符，页目录和 页表，检查的基础是特权级。\n特权级是 Intel 为实现保护而定义的特权编号。\n段一级的检查包括段界限检查，段类型检查，特权级检查，长指针检查等。段一级检查的原 则是：\n低特权级的代码不能访问高特权级的数据 高特权级的代码可以访问低特权级的数据 代码只能使用与其特权级相同的堆栈，当特权级切换时，堆栈也要随之切换。 只能向具有相同特权级的非相容代码段转移控制（长 JMP 和长 CALL） 可以向同等或者较高特权级的相容代码段转移控制，但不能向低特权级的相容代码段转 移控制（长 JMP 和长 CALL）。 即使调用门、中断门、陷阱门，也不能从高特权级向低特权级转移控制。 不允许使用长 RET 向高特权级转移控制。 页一级的检查包括特权级检查和读写检查。相关标志是页目录/页表项中的 U/S 和 R/W 位。U/S 位 0 的页是超级页，为 1 是代表用户页。一般情况下，超级页中的代码可以访问 所有页（不管 R/W 标志），用户页中的代码只能访问用户页。当 CR0.WP 被设置为 1 时， 超级页中的代码也不能写只读用户页。\nNXB 为 1 的页只能用作数据页，试图执行数据页的指令会引起处理器异常。\n","permalink":"https://blog.bktus.com/archives/71tr8x/","summary":"\u003cp\u003e一旦处理器内存保护机制被启动，处理器就会对每一次内存访问进行保护性检查，以确保所\n有的访问都满足保护策略。保护检查和地址转换是并行进行的。\u003c/p\u003e\n\u003cp\u003e保护检查包含段级检查和页级检查。检查顺序是先段后页，检查依据是段描述符，页目录和\n页表，检查的基础是特权级。\u003c/p\u003e\n\u003cp\u003e特权级是 Intel 为实现保护而定义的特权编号。\u003c/p\u003e\n\u003cp\u003e段一级的检查包括段界限检查，段类型检查，特权级检查，长指针检查等。段一级检查的原\n则是：\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e低特权级的代码不能访问高特权级的数据\u003c/li\u003e\n\u003cli\u003e高特权级的代码可以访问低特权级的数据\u003c/li\u003e\n\u003cli\u003e代码只能使用与其特权级相同的堆栈，当特权级切换时，堆栈也要随之切换。\u003c/li\u003e\n\u003cli\u003e只能向具有相同特权级的非相容代码段转移控制（长 JMP 和长 CALL）\u003c/li\u003e\n\u003cli\u003e可以向同等或者较高特权级的相容代码段转移控制，但不能向低特权级的相容代码段转\n移控制（长 JMP 和长 CALL）。\u003c/li\u003e\n\u003cli\u003e即使调用门、中断门、陷阱门，也不能从高特权级向低特权级转移控制。\u003c/li\u003e\n\u003cli\u003e不允许使用长 RET 向高特权级转移控制。\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e页一级的检查包括特权级检查和读写检查。相关标志是页目录/页表项中的 U/S 和 R/W\n位。U/S 位 0 的页是超级页，为 1 是代表用户页。一般情况下，超级页中的代码可以访问\n所有页（不管 R/W 标志），用户页中的代码只能访问用户页。当 CR0.WP 被设置为 1 时，\n超级页中的代码也不能写只读用户页。\u003c/p\u003e\n\u003cp\u003eNXB 为 1 的页只能用作数据页，试图执行数据页的指令会引起处理器异常。\u003c/p\u003e","title":"Intel x86处理器内存保护 概要知识点"},{"content":"内存管理是操作系统中非常重要的一个部分。随着计算机技术的不断发展，内存管理的方式 也在不断演变。本文将介绍 intel 64 体系几种常见的内存管理方式。\n平板式内存管理是一种比较简单的内存管理方式。它采用页式管理，屏蔽掉段式管理。具体 来说，它将逻辑地址直接映射为线性地址，定义一个代码段和一个数据段，两个段的大小都 是 4GB。这种方式的好处是简单易懂，但是它的缺点是不能进行多进程的内存隔离。\n为了解决多进程内存隔离的问题，可以采用保护平板式内存管理的方式。具体来说，它定义 了内核代码段、内核数据段、用户代码段和用户数据段，每个段的基地址都是 0，大小为 4GB。进程执行内核代码时使用内核段，执行用户代码时使用用户段。这种方式的好处是可 以进行多进程的内存隔离，但是它的缺点是需要进行地址转化，影响效率。多段式内存管理 是一种完全采用段式管理的方式，屏蔽掉页式管理。它可以进行多进程的内存隔离，但是需 要进行地址转化，也会影响效率。\n基于物理地址拓展的页式内存管理是一种支持更大物理地址空间的方式。它引入了物理地址 拓展（PAE）机制以支持 36 位物理地址。该管理模式中，物理地址空间被扩充到了 64GB， 但线性地址空间仍然为 4GB。页目录和页表项被扩充到了 64 位，因而一个页目录或者页表 中的项数变成了 512，一个页目录仅能描述 1GB 线性地址空间。所以引入了一个只有四个 表项的页目录指针表（PDP）。CR3 指向 PDP。地址转换机制被修改。当页目录项中的 PS 位被设置为 1 时，它描述的页变成了 2MB 页。\n在 64 位模式中，段通常被关闭，不再做段界限检查。CS，DS，ES，SS 的基地址处理器统 统看作 0。但 FS 和 GS 可以不是 0，将逻辑地址转换为先行地址的时候要加上 FS 或者 GS 的基地址。FS 和 GS 的基地址位 64 位地址，兼容模式中只使用它的低 32 位，记录在 MSR 中。在 64 位模式中，对于内存的管理完全依靠分页机制。\nIntel64 体系结构扩展了 PAE 机制，使之能够支持 64 位线性地址和 52 位物理地址。拓 展包括：\n页目录指针表被扩充到了 512 项。 引入了一个第四级页映射表 PML4，它的每一个表项可以指向一个 PDP。 所有的四级页表表项被扩充到了 64 位。 页目录项中的 PS 标志用于控制 4KB 和 2MB 页。 所有页表项在第 63 位上新增了一个执行禁止标志 EXB。 ","permalink":"https://blog.bktus.com/archives/l5g4e0/","summary":"\u003cp\u003e内存管理是操作系统中非常重要的一个部分。随着计算机技术的不断发展，内存管理的方式\n也在不断演变。本文将介绍 intel 64 体系几种常见的内存管理方式。\u003c/p\u003e\n\u003cp\u003e平板式内存管理是一种比较简单的内存管理方式。它采用页式管理，屏蔽掉段式管理。具体\n来说，它将逻辑地址直接映射为线性地址，定义一个代码段和一个数据段，两个段的大小都\n是 4GB。这种方式的好处是简单易懂，但是它的缺点是不能进行多进程的内存隔离。\u003c/p\u003e\n\u003cp\u003e为了解决多进程内存隔离的问题，可以采用保护平板式内存管理的方式。具体来说，它定义\n了内核代码段、内核数据段、用户代码段和用户数据段，每个段的基地址都是 0，大小为\n4GB。进程执行内核代码时使用内核段，执行用户代码时使用用户段。这种方式的好处是可\n以进行多进程的内存隔离，但是它的缺点是需要进行地址转化，影响效率。多段式内存管理\n是一种完全采用段式管理的方式，屏蔽掉页式管理。它可以进行多进程的内存隔离，但是需\n要进行地址转化，也会影响效率。\u003c/p\u003e\n\u003cp\u003e基于物理地址拓展的页式内存管理是一种支持更大物理地址空间的方式。它引入了物理地址\n拓展（PAE）机制以支持 36 位物理地址。该管理模式中，物理地址空间被扩充到了 64GB，\n但线性地址空间仍然为 4GB。页目录和页表项被扩充到了 64 位，因而一个页目录或者页表\n中的项数变成了 512，一个页目录仅能描述 1GB 线性地址空间。所以引入了一个只有四个\n表项的页目录指针表（PDP）。CR3 指向 PDP。地址转换机制被修改。当页目录项中的 PS\n位被设置为 1 时，它描述的页变成了 2MB 页。\u003c/p\u003e\n\u003cp\u003e在 64 位模式中，段通常被关闭，不再做段界限检查。CS，DS，ES，SS 的基地址处理器统\n统看作 0。但 FS 和 GS 可以不是 0，将逻辑地址转换为先行地址的时候要加上 FS 或者\nGS 的基地址。FS 和 GS 的基地址位 64 位地址，兼容模式中只使用它的低 32 位，记录在\nMSR 中。在 64 位模式中，对于内存的管理完全依靠分页机制。\u003c/p\u003e\n\u003cp\u003eIntel64 体系结构扩展了 PAE 机制，使之能够支持 64 位线性地址和 52 位物理地址。拓\n展包括：\u003c/p\u003e","title":"intel 64 体系常见内存管理方式"},{"content":"段页式内存管理中，段的线性地址被分割成大小相等的线性页（4KB，4MB 或者 2MB 等）。 物理内存空间同样也被分成相同大小的物理页。操作系统维护一个页表，用于管理线性页到 物理页的映射。页表在 IA-32 体系结构中分为两级，即页目录和页表\n页目录是一个数组，其元素叫做页目录项（PDE），每个页目录项描述一个页表。页目录的 大小为一页（4KB），一个页目录中有 1024 个页目录项。页目录项的大小为 4 字节。页表 大小为一页（4KB）。页表项的大小为 4 字节，所以一个页表最多可以描述 1024 个线性 页。\n物理页是预先划分好的，其开始位置一定在 4KB（2^12）的边界上。所以低 12 位全部是 零。然后低 12 位就可以储存而外的信息。\nP 是存在位。R/W 是读写标志位。U/S 是用户标志位，0 表示超级用户。A 是存取标志 位。D 是脏标志位。\n在页目录项中 PAT 标志换成了 PS 标志，表示物理页尺寸。\nCR3 寄存器专门存放当前使用的页目录的物理地址，因此 CR3 又叫做页目录基地址寄存 器。只要进程在活动，它的页目录就应该一直驻留在内存。\n页目录项也可以直接指向物理页，加快地址转换速度，通常将操作系统内核所占用的页设置 为 4MB 页。该页目录项的 PS 地址为 1 事，它所描述的是一个 4MB 的页而不再是一个页 表。\n页式管理机制是操作系统内核启动的，启动方法是将 CR0 中的 PG 标志设置为 1。\n启动分页机制后，每个线性地址都需要经过页目录和页表的转换。IA-32 体系结构加入了一 个高速缓存 TIB，其中储存最近使用的页目录和页表项。TIB 的经常的刷新工作由操作系统 内核负责。当页目录和页表项改变时，内核必须使得 TIB 相应的项失效。当 CR3 改变 时，TIB 中的所有内容（Global 页除外）会自动失效。INVLPG 指令可以将 TLB 中的指定 项设置为无效。\n","permalink":"https://blog.bktus.com/archives/neyb3m/","summary":"\u003cp\u003e段页式内存管理中，段的线性地址被分割成大小相等的线性页（4KB，4MB 或者 2MB 等）。\n物理内存空间同样也被分成相同大小的物理页。操作系统维护一个页表，用于管理线性页到\n物理页的映射。页表在 IA-32 体系结构中分为两级，即页目录和页表\u003c/p\u003e\n\u003cp\u003e页目录是一个数组，其元素叫做页目录项（PDE），每个页目录项描述一个页表。页目录的\n大小为一页（4KB），一个页目录中有 1024 个页目录项。页目录项的大小为 4 字节。页表\n大小为一页（4KB）。页表项的大小为 4 字节，所以一个页表最多可以描述 1024 个线性\n页。\u003c/p\u003e\n\u003cp\u003e物理页是预先划分好的，其开始位置一定在 4KB（2^12）的边界上。所以低 12 位全部是\n零。然后低 12 位就可以储存而外的信息。\u003c/p\u003e\n\u003cp\u003eP 是存在位。R/W 是读写标志位。U/S 是用户标志位，0 表示超级用户。A 是存取标志\n位。D 是脏标志位。\u003c/p\u003e\n\u003cp\u003e在页目录项中 PAT 标志换成了 PS 标志，表示物理页尺寸。\u003c/p\u003e\n\u003cp\u003eCR3 寄存器专门存放当前使用的页目录的物理地址，因此 CR3 又叫做页目录基地址寄存\n器。只要进程在活动，它的页目录就应该一直驻留在内存。\u003c/p\u003e\n\u003cp\u003e页目录项也可以直接指向物理页，加快地址转换速度，通常将操作系统内核所占用的页设置\n为 4MB 页。该页目录项的 PS 地址为 1 事，它所描述的是一个 4MB 的页而不再是一个页\n表。\u003c/p\u003e\n\u003cp\u003e页式管理机制是操作系统内核启动的，启动方法是将 CR0 中的 PG 标志设置为 1。\u003c/p\u003e\n\u003cp\u003e启动分页机制后，每个线性地址都需要经过页目录和页表的转换。IA-32 体系结构加入了一\n个高速缓存 TIB，其中储存最近使用的页目录和页表项。TIB 的经常的刷新工作由操作系统\n内核负责。当页目录和页表项改变时，内核必须使得 TIB 相应的项失效。当 CR3 改变\n时，TIB 中的所有内容（Global 页除外）会自动失效。INVLPG 指令可以将 TLB 中的指定\n项设置为无效。\u003c/p\u003e","title":"段页式内存管理概要"},{"content":"IA-32 体系中提供了段页式内存管理机制，先分段再分页。提供页式是为了支持虚拟内存。\n段：处理器的可寻址的线性内存空间被划分成了若干个大小不同的段。一个段是线性地址 空间中的一个连续的区间。段中可保存代码、数据、堆栈或者其他数据结构。段的属性信 息由与之对应的段描述符描述。段描述符是一个数据结构。Intel 用段描述符表来管理。 段描述符表最大可达 64KB。\n当 G 为 0 时，段以字节为单位，最大的段长为 1MB。当 G 为 1 时，段以页（4kb） 为单位。最大的段长为 4GB。\nDPL 是段的特权级，其值在 0~3 之间。\nS 是系统标志，用于区分段的类别。0 表示系统段，1 表示用户段。\nType 是段的类型：\n对于系统段。类型域由 4 位组成，可表示 16 个系统段类型之一。\n对于用户段。\n第三位为 0 表示数据段，此时第二位表示地址的拓展方向（0 表示大扩展方 向），第一位表示段是否可写。 第三位为 1 表示代码段，此时第二位是相容标志（0 表示非相容）。第一位是可 读位。 第 0 位是存取位。0 表示段尚未存取过。 D/B 标志表示有效地址和操作数长度。\nL 标志仅出现在 IA-32e 模式的代码段中。1 表示 64 位模式\n堆栈段通常是向下扩展的、可读写的数据段。\n段描述符表：\n全局描述符表（GDT）。在系统进入保护模式前必须为其定义一个 GDT。IA-32 体系结 构专门定义了一个 GDTR 寄存器来存放当前的 GDT 的信息。 局部描述符表（LDT）是系统段，其中可以存放局部描述符，如进程自己的代码段，数 据段等。IA-32 体系专门提供了一个 LDTR 寄存器，用于保存当前使用的 LDT 信息。 可以用段描述符在段描述符表中的索引来标识它，标识称作段选择符。段选择符是 16 位标 识符。第二位是指示器（TI）表示索引所对的描述符表（0 表示 GDT）。第 3-15 位示索 引，标定位置。第 0、1 位是请求特权级 RPL。一个段选择符加上一个偏移量可以唯一地标 识一个逻辑地址。逻辑地址是程序使用的地址，不是线性地址，也不是物理地址。\nIA-32 体系提供了 6 个段寄存器，即 CS，SS，DS，ES，FS 和 GS。每一个段寄存器可以缓 存一个段描述符。\n","permalink":"https://blog.bktus.com/archives/9fsajz/","summary":"\u003cp\u003eIA-32 体系中提供了段页式内存管理机制，先分段再分页。提供页式是为了支持虚拟内存。\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e段：处理器的可寻址的线性内存空间被划分成了若干个大小不同的段。一个段是线性地址\n空间中的一个连续的区间。段中可保存代码、数据、堆栈或者其他数据结构。段的属性信\n息由与之对应的段描述符描述。段描述符是一个数据结构。Intel 用段描述符表来管理。\n段描述符表最大可达 64KB。\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e当 G 为 0 时，段以字节为单位，最大的段长为 1MB。当 G 为 1 时，段以页（4kb）\n为单位。最大的段长为 4GB。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eDPL 是段的特权级，其值在 0~3 之间。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eS 是系统标志，用于区分段的类别。0 表示系统段，1 表示用户段。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eType 是段的类型：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e对于系统段。类型域由 4 位组成，可表示 16 个系统段类型之一。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e对于用户段。\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e第三位为 0 表示数据段，此时第二位表示地址的拓展方向（0 表示大扩展方\n向），第一位表示段是否可写。\u003c/li\u003e\n\u003cli\u003e第三位为 1 表示代码段，此时第二位是相容标志（0 表示非相容）。第一位是可\n读位。\u003c/li\u003e\n\u003cli\u003e第 0 位是存取位。0 表示段尚未存取过。\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eD/B 标志表示有效地址和操作数长度。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eL 标志仅出现在 IA-32e 模式的代码段中。1 表示 64 位模式\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e堆栈段通常是向下扩展的、可读写的数据段。\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e段描述符表：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e全局描述符表（GDT）。在系统进入保护模式前必须为其定义一个 GDT。IA-32 体系结\n构专门定义了一个 GDTR 寄存器来存放当前的 GDT 的信息。\u003c/li\u003e\n\u003cli\u003e局部描述符表（LDT）是系统段，其中可以存放局部描述符，如进程自己的代码段，数\n据段等。IA-32 体系专门提供了一个 LDTR 寄存器，用于保存当前使用的 LDT 信息。\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e可以用段描述符在段描述符表中的索引来标识它，标识称作段选择符。段选择符是 16 位标\n识符。第二位是指示器（TI）表示索引所对的描述符表（0 表示 GDT）。第 3-15 位示索\n引，标定位置。第 0、1 位是请求特权级 RPL。一个段选择符加上一个偏移量可以唯一地标\n识一个逻辑地址。逻辑地址是程序使用的地址，不是线性地址，也不是物理地址。\u003c/p\u003e","title":"段内存管理概要"},{"content":" 内存是处理器可以直接访问的储存空间。为了加快内存访问速度，计算机系统中通常提供 了一些高速缓存（Cache），高速缓存通常由硬件管理。 I/O 设备由 I/O 控制器和物理设备组成，处理器通过 I/O 控制器管理物理设备。I/O 控 制器主要由控制与状态寄存器（CSR）和数据寄存器组成。处理器通过读 CSR 来获得设备 状态，通过写 CSR 来控制设备动作，通过读写数据寄存器来交换数据。内核通常将 I/O 设备抽象成一组寄存器，并给一个寄存器一个 I/O 地址。处理器通过 I/O 地址访问 I/O 寄存器。 现代计算机系统中许多设备寄存器可以被映射到物理地址空间中。此时，每个设备寄存器 都有一个物理内存地址。这种方式的 I/O 称为内存映射 I/O。使用方便但是会消耗物理 内存地址。 通常把外存抽象成一个数据块的数组，每个数据块都有一个序号处理器可以通过序号随机 读、写外存中的任何一个数据块。对于外存的操作通常以块为单位，因此又称外存为块设 备。对应地，其他 I/O 设备称为字符设备。简而言之，块设备以块为单位进行数据的读 写操作，如硬盘、闪存等；字符设备以字符为单位进行数据的读写操作，如键盘、鼠标、 打印机等。 总线负责将处理器，内存，I/O 控制器连接起来。常用的总线有 ISA、PCI、PCI-E、AGP、ATA、SCSI 等。总线还负责检测，枚举接在其上的设备。 ","permalink":"https://blog.bktus.com/archives/kk9cvb/","summary":"\u003cul\u003e\n\u003cli\u003e内存是处理器可以直接访问的储存空间。为了加快内存访问速度，计算机系统中通常提供\n了一些高速缓存（Cache），高速缓存通常由硬件管理。\u003c/li\u003e\n\u003cli\u003eI/O 设备由 I/O 控制器和物理设备组成，处理器通过 I/O 控制器管理物理设备。I/O 控\n制器主要由控制与状态寄存器（CSR）和数据寄存器组成。处理器通过读 CSR 来获得设备\n状态，通过写 CSR 来控制设备动作，通过读写数据寄存器来交换数据。内核通常将 I/O\n设备抽象成一组寄存器，并给一个寄存器一个 I/O 地址。处理器通过 I/O 地址访问 I/O\n寄存器。\u003c/li\u003e\n\u003cli\u003e现代计算机系统中许多设备寄存器可以被映射到物理地址空间中。此时，每个设备寄存器\n都有一个物理内存地址。这种方式的 I/O 称为内存映射 I/O。使用方便但是会消耗物理\n内存地址。\u003c/li\u003e\n\u003cli\u003e通常把外存抽象成一个数据块的数组，每个数据块都有一个序号处理器可以通过序号随机\n读、写外存中的任何一个数据块。对于外存的操作通常以块为单位，因此又称外存为块设\n备。对应地，其他 I/O 设备称为字符设备。简而言之，块设备以块为单位进行数据的读\n写操作，如硬盘、闪存等；字符设备以字符为单位进行数据的读写操作，如键盘、鼠标、\n打印机等。\u003c/li\u003e\n\u003cli\u003e总线负责将处理器，内存，I/O 控制器连接起来。常用的总线有\nISA、PCI、PCI-E、AGP、ATA、SCSI 等。总线还负责检测，枚举接在其上的设备。\u003c/li\u003e\n\u003c/ul\u003e","title":"了解计算机系统内部运作：内存、I/O设备、总线和外存是如何协同工作的？"},{"content":"IA-32 体系结构中由三种模式和一种准操作结构：\n实模式：与 8086 兼容的操作模式，有一些拓展。 保护模式：处理器的一种最基本的操作模式，在这种模式中，处理器的所有指令及体系结 构中的所有特色都是可用的，并且能够达到最高性能。 系统管理模式，提供给操作系统的一种透明的管理机制，用于实现电源管理等特殊操作。 虚拟 8086 模式是一个准操作模式，允许处理器在保护模式中执行实模式的程序。 Intel 64 体系结构新增一种 IA-32e 操作模式包含两个子模式\n兼容模式，在该模式下可以不加修改地运行大多数 IA-32 体系结构的程序 64 位模式，可以使用 64 位线性地址空间和一些新增加的特性。IA-32e 不再支持虚拟 8086 模式 处理器加电或者 Reset 后默认操作模式是实模式。\n实模式和保护模式之间的转换由控制寄存器 CR0 中的 PE 位控制。 保护模式和 IA-32e 模式之间的转换由 IA32_EFER 寄存器中的 LME 和 CR0 中的 PE 位 控制。 兼容模式和 64 位模式之间的转换由代码段寄存器 CS 和 L 位控制。 保护模式和虚拟 8086 模式之间的转换由标志寄存器 EFLAGS 中的 VM 位控制。 进入系统管理模式的唯一途径是 SMI 中断，在系统管理模式中执行指令 RSM 会将处理器切 换回原来的操作模式。\n","permalink":"https://blog.bktus.com/archives/lf80ly/","summary":"\u003cp\u003eIA-32 体系结构中由三种模式和一种准操作结构：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e实模式：与 8086 兼容的操作模式，有一些拓展。\u003c/li\u003e\n\u003cli\u003e保护模式：处理器的一种最基本的操作模式，在这种模式中，处理器的所有指令及体系结\n构中的所有特色都是可用的，并且能够达到最高性能。\u003c/li\u003e\n\u003cli\u003e系统管理模式，提供给操作系统的一种透明的管理机制，用于实现电源管理等特殊操作。\u003c/li\u003e\n\u003cli\u003e虚拟 8086 模式是一个准操作模式，允许处理器在保护模式中执行实模式的程序。\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eIntel 64 体系结构新增一种 IA-32e 操作模式包含两个子模式\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e兼容模式，在该模式下可以不加修改地运行大多数 IA-32 体系结构的程序\u003c/li\u003e\n\u003cli\u003e64 位模式，可以使用 64 位线性地址空间和一些新增加的特性。IA-32e 不再支持虚拟\n8086 模式\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e处理器加电或者 Reset 后默认操作模式是实模式。\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e实模式和保护模式之间的转换由控制寄存器 CR0 中的 PE 位控制。\u003c/li\u003e\n\u003cli\u003e保护模式和 IA-32e 模式之间的转换由 IA32_EFER 寄存器中的 LME 和 CR0 中的 PE 位\n控制。\u003c/li\u003e\n\u003cli\u003e兼容模式和 64 位模式之间的转换由代码段寄存器 CS 和 L 位控制。\u003c/li\u003e\n\u003cli\u003e保护模式和虚拟 8086 模式之间的转换由标志寄存器 EFLAGS 中的 VM 位控制。\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e进入系统管理模式的唯一途径是 SMI 中断，在系统管理模式中执行指令 RSM 会将处理器切\n换回原来的操作模式。\u003c/p\u003e","title":"IA-32体系概要"},{"content":"明清时期，是中国古代的封建社会的晚期。这是封建专制主义中央集权制度的强化的时期， 也是中国的封建皇权走向巅峰的时期。另一方面，这也是中华民族逐渐落后与历史潮流的时 期。理学的出现与统治阶级对于理学的强化，逐渐束缚了我们民族的人格与思想。\n为什么中华民族会进入这样一段历史时期。其实这并不是这些皇帝个人的意志的体现，而是 传统文化影响下中华民族的性格与历史大环境所造就的。当中国封建制度走到宋代的时候， 相对于前代唐代，能很明显地感觉到文化与制度双重影响下的人格的束缚越来越强。我认 为，出现这种情况的正显现了封建制度已经越来越不能适应于生产力与思想发展后人们自然 产生的对于新兴的生产关系，人格自由的需求。这类新兴事物，在明王朝对于社会的思想控 制统治相对薄弱的中后期可以窥见，在这个时期涌现了一大批思想家，像李贽、黄宗羲。李 贽将矛头直指封建礼教和整个正统思想，在一定程度反映了基本主义萌芽时代的要求。黄宗 羲，他主张的“天下为主，君为客”之说，反映了由生产力发展所导致的传统社会生产关系的 变化。具有反专制主义的特征。\n面对新兴的事物，传统封建统治阶级同他们所设计的集权化的封建制度感到眼花缭乱。在落 后的世界观与对于落后生产关系的理解的影响下，他们对接受、利用与管理这些新兴事物显 得力不从心。草率地将这些新兴事物纳入他们熟悉的统治体系是不可能做到的，他们的统治 体系与他们的思想本身并没有这些基因。他们对这种令他们感到纷乱的事物产生了本能的恐 惧，所以他们要清除这些让他们感到烦心的事物，让社会与人们的思想重新回到他们认为的 样子中去。只有这样这样人民的一举一动才能在他们的掌握之中，他们才能在既定框架下更 有效地实现他们的统治。\n于是到了明代，底层农民出身的朱元璋对于元末的混乱有深刻的体会。在中国传统文化影响 下，他的不可避免地渴望社会的秩序与道德的重建，建立一个秩序的王朝。但是，在他的那 个时期，新兴事物与思想的出现让社会变得复杂，社会本身已经不可能恢复到秦汉与隋唐时 期。这让他本能地走向了对于人民更严格的控制。兴八股，造户籍，明律法，这是他采取的 措施。以孝治天下的主张，更是希望国家臣民能够在伦理道德的强力约束下接受统治。选拔 国家人才的科举制度，也成为了知识分子思想上的一道重重的枷锁。对于这些知识分子出身 的官员，他们的思想举动被牢牢锁定在私塾五经的范畴。丞相的废除与锦衣卫，东厂的设 立，更加体现了中央皇权的加强，中央皇权对于社会的控制更加牢固。于是在明初这种恐怖 环境下，新兴事物根本无力与已经占据绝对优势的全副武装的封建秩序对抗。需要注意，古 代中国的律法，一直被人民视为对于臣民严重违反社会道德规范的严厉惩罚与对于臣民行为 的在道德以外的严格规定，并不是现代意义上保护人民权利的法律。\n在思想方面，中华传统的文化本身具有它的“趋同”的特征，在这种环境下新兴事物的传播本 来就缓慢。随着传统的儒家文化的逐渐地封建化与经文化，在人们的思想中面对这些新兴的 东西时是麻木的，不理解的。对于接受这些新兴思想的少数人，一直被排除在主流社会之 中，不仅受到道德与封建法律的联合打压，而且受到主流思想的排挤与污蔑，更受到在封建 世界观影响下成长起来的自己对于自己思想的怀疑。再者，古代中国人对于“天下”，“朝 廷”，“君父”的根生蒂固的认识下，在王朝更替，集权封建制度控制相对较弱的时期，仍然 会自然地选择走向另一个新的集权封建制度的统治。\n从制度与文化两方面看，如果不受到外力，在相当长的历史时期，中华民族会生活在在这种 集权封建体制与封建化的文化中。在这段时期，新兴事物的传播是及其缓慢的。而恰恰是这 些新兴制度，很自然的代表了人类社会发展的根本方向。但不能否认，随着生产力的继续发 展，新兴的生产关系与相对应产生的思想绝不会被扑灭扼杀。相反，在不断积累中，它的力 量会越来越强大，直到在未来某个历史时期，它将取代传统的封建生产关系。\n","permalink":"https://blog.bktus.com/archives/m1pjw6/","summary":"\u003cp\u003e明清时期，是中国古代的封建社会的晚期。这是封建专制主义中央集权制度的强化的时期，\n也是中国的封建皇权走向巅峰的时期。另一方面，这也是中华民族逐渐落后与历史潮流的时\n期。理学的出现与统治阶级对于理学的强化，逐渐束缚了我们民族的人格与思想。\u003c/p\u003e\n\u003cp\u003e为什么中华民族会进入这样一段历史时期。其实这并不是这些皇帝个人的意志的体现，而是\n传统文化影响下中华民族的性格与历史大环境所造就的。当中国封建制度走到宋代的时候，\n相对于前代唐代，能很明显地感觉到文化与制度双重影响下的人格的束缚越来越强。我认\n为，出现这种情况的正显现了封建制度已经越来越不能适应于生产力与思想发展后人们自然\n产生的对于新兴的生产关系，人格自由的需求。这类新兴事物，在明王朝对于社会的思想控\n制统治相对薄弱的中后期可以窥见，在这个时期涌现了一大批思想家，像李贽、黄宗羲。李\n贽将矛头直指封建礼教和整个正统思想，在一定程度反映了基本主义萌芽时代的要求。黄宗\n羲，他主张的“天下为主，君为客”之说，反映了由生产力发展所导致的传统社会生产关系的\n变化。具有反专制主义的特征。\u003c/p\u003e\n\u003cp\u003e面对新兴的事物，传统封建统治阶级同他们所设计的集权化的封建制度感到眼花缭乱。在落\n后的世界观与对于落后生产关系的理解的影响下，他们对接受、利用与管理这些新兴事物显\n得力不从心。草率地将这些新兴事物纳入他们熟悉的统治体系是不可能做到的，他们的统治\n体系与他们的思想本身并没有这些基因。他们对这种令他们感到纷乱的事物产生了本能的恐\n惧，所以他们要清除这些让他们感到烦心的事物，让社会与人们的思想重新回到他们认为的\n样子中去。只有这样这样人民的一举一动才能在他们的掌握之中，他们才能在既定框架下更\n有效地实现他们的统治。\u003c/p\u003e\n\u003cp\u003e于是到了明代，底层农民出身的朱元璋对于元末的混乱有深刻的体会。在中国传统文化影响\n下，他的不可避免地渴望社会的秩序与道德的重建，建立一个秩序的王朝。但是，在他的那\n个时期，新兴事物与思想的出现让社会变得复杂，社会本身已经不可能恢复到秦汉与隋唐时\n期。这让他本能地走向了对于人民更严格的控制。兴八股，造户籍，明律法，这是他采取的\n措施。以孝治天下的主张，更是希望国家臣民能够在伦理道德的强力约束下接受统治。选拔\n国家人才的科举制度，也成为了知识分子思想上的一道重重的枷锁。对于这些知识分子出身\n的官员，他们的思想举动被牢牢锁定在私塾五经的范畴。丞相的废除与锦衣卫，东厂的设\n立，更加体现了中央皇权的加强，中央皇权对于社会的控制更加牢固。于是在明初这种恐怖\n环境下，新兴事物根本无力与已经占据绝对优势的全副武装的封建秩序对抗。需要注意，古\n代中国的律法，一直被人民视为对于臣民严重违反社会道德规范的严厉惩罚与对于臣民行为\n的在道德以外的严格规定，并不是现代意义上保护人民权利的法律。\u003c/p\u003e\n\u003cp\u003e在思想方面，中华传统的文化本身具有它的“趋同”的特征，在这种环境下新兴事物的传播本\n来就缓慢。随着传统的儒家文化的逐渐地封建化与经文化，在人们的思想中面对这些新兴的\n东西时是麻木的，不理解的。对于接受这些新兴思想的少数人，一直被排除在主流社会之\n中，不仅受到道德与封建法律的联合打压，而且受到主流思想的排挤与污蔑，更受到在封建\n世界观影响下成长起来的自己对于自己思想的怀疑。再者，古代中国人对于“天下”，“朝\n廷”，“君父”的根生蒂固的认识下，在王朝更替，集权封建制度控制相对较弱的时期，仍然\n会自然地选择走向另一个新的集权封建制度的统治。\u003c/p\u003e\n\u003cp\u003e从制度与文化两方面看，如果不受到外力，在相当长的历史时期，中华民族会生活在在这种\n集权封建体制与封建化的文化中。在这段时期，新兴事物的传播是及其缓慢的。而恰恰是这\n些新兴制度，很自然的代表了人类社会发展的根本方向。但不能否认，随着生产力的继续发\n展，新兴的生产关系与相对应产生的思想绝不会被扑灭扼杀。相反，在不断积累中，它的力\n量会越来越强大，直到在未来某个历史时期，它将取代传统的封建生产关系。\u003c/p\u003e","title":"中华传统文化能否带领封建制度中的中华民族走向近代"},{"content":"在讨论传统文化之前，先来讨论民族。先来看看民族的概念：民族是指经长期历史发展而形 成的稳定共同体，一群基于历史、文化、语言与其它人群有所区别的群体。根据定义可以知 道历史、文化、语言是区本民族别于其他民族的重要的三个元素。而这三个元素并不是相互 独立的，相反，它们是相互影响，相互交融的，谁也离不开谁。\n从文化来看，一个民族的文化渗入它的历史之中，影响着本民族个体乃至全体的思维，进而 影响在特定历史条件下本民族面对历史提出的问题所作出的决定。类似地，文化渗入语言， 一个民族的语言的文法，词汇，表达中都有明显的文化痕迹。所以，文化极大地塑造了一个 民族的性格，赋予一个民族它的灵魂，是一个民族区别于其他民族的极为重要的因素。中华 传统文化之于中华民族来说亦是如此。\n但是近代以来，中华传统文化在中国人心中逐渐地淡化，甚至于个别个体全然忘却。近来频 繁地出现了种种违反道德的社会事件，从表面看仅仅是违反道德而已。但是深究其根本，则 是社会道德的母体传统文化本身，被本民族个别个体所轻视，淡忘所导致的道德约束力的下 降。本民族的传统文化在民族个体在内心不被认同，将导致中华民族的凝聚力与认同感的下 降，对于中华民族来说将是灾难性的。就像当下，面对日美文化，中国青年热衷接受；面对 传统文化，中国青年则一脸茫然。但这又是为什么？中华传统文化到底是什么？未来将何去 何从？\n在近现代中国人的理解中，传统文化似乎制约了社会的发展。诚然，传统文化中三常五纲， 逆来顺受，宗法等级确实在一个现代人看来是落后的。并且传统文化中缺乏对于论断的进行 思维论证的态度也是不可取的。既然对于传统文化产生了这样的疑问。那么在封建时期，传 统文化的状态就是我们应当首先讨论的问题。\n中华传统文化在近代为什么不符合社会的发展潮流，是因为传统文化本身被很大程度地封建 化。明清时期，是中国古代的封建社会的晚期。这是封建专制主义中央集权制度的强化的时 期，也是中国的封建皇权走向巅峰的时期。理学的出现与统治阶级对于理学的强化，逐渐束 缚了我们民族的人格与思想。在封建专制主义制度的强力作用下，传统文化很大程度上向着 适应封建统治的方向上发展。为什么中华民族会进入这样一段历史时期。其实这并不是这些 皇帝个人的意志的体现，而是传统文化影响下中华民族的性格与历史大环境所造就的。 其 实，当中国封建制度走到宋代的时候，相对于前代唐代，能很明显地感觉到文化与制度双重 影响下的人格的束缚越来越强。\n我认为，出现这种情况的正显现了封建制度已经越来越不能适应于生产力与思想发展后人们 自然产生的对于新兴的生产关系，人格自由的需求。面对新兴的事物，传统封建统治阶级在 落后的世界观与对于落后生产关系的理解的影响下，对接受、利用与管理这些新兴事物显得 力不从心。草率地将这些新兴事物纳入他们熟悉的统治体系是不可能做到的，他们的统治体 系与他们的思想本身并没有这些基因。他们对这种令他们感到纷乱的事物产生了本能的恐 惧，努力让社会与人们的思想重新回到他们认为的样子中去。\n于是到了明代，底层农民出身的朱元璋封建文化与历史的巨大惯性的影响下，不可避免地渴 望社会的秩序与道德的重建，建立一个统一的君主专制的封建的王朝。但是，在他的那个时 期，新兴事物与思想的出现让社会变得复杂，社会本身已经不可能恢复到秦汉与隋唐时期。 这让他本能地走向了对于人民更严格的控制。兴八股，造户籍，明律法，这是他采取的措 施。纯粹的以孝治天下的主张，更是希望国家臣民能够在伦理道德的强力约束下接受统治。 选拔国家人才的科举制度，也成为了知识分子思想上的一道重重的枷锁。对于这些知识分子 出身的官员，他们的思想举动被牢牢锁定在私塾五经的范畴。丞相的废除与锦衣卫，东厂的 设立，更加体现了中央皇权的加强，中央皇权对于社会的控制更加牢固。于是在明初这种恐 怖环境下，新兴事物根本无力与已经占据绝对优势的全副武装的封建秩序对抗。\n需要注意，古代中国的律法，一直被人民视为对于臣民严重违反社会道德规范的严厉惩罚与 对于臣民行为的在道德以外的严格规定，并不是现代意义上保护人民权利的法律。 在思想 方面，封建化的中华传统的文化逐渐强化了它的“趋同”的特征，在这种环境下新兴事物的传 播本来就缓慢。随着传统的儒家文化的逐渐地封建化与经文化，对于接受这些新兴思想的少 数人，一直被排除在主流社会之中，不仅受到道德与封建法律的联合打压，更受到在封建世 界观影响下成长起来的自己对于自己思想的怀疑。再者，古代中国人对于“天下”，“朝 廷”，“君父”的根生蒂固的认识下，在王朝更替，集权封建制度控制相对较弱的时期，仍然 会自然地选择走向另一个新的集权封建制度的统治。 所以，在被封建地主阶级改造的儒家 思想的巨大惯性下运转中国，两千年的对中华民族的思想的改造与对于儒家文化的单方面阐 释下，使得以儒家思想为主体的传统文化已经变成他们他们维系统治的有力工具。以儒家思 想为主体的中华传统文化在两千年的封建社会的进程中逐渐向它的“趋同”与“秩序”的方向上 倾斜，越来越不利于生产力与新思想的发展，社会进入了相对稳定的停滞时期。\n在明朝后期到乾隆时期，皇帝对于西方传来的闻之未闻的科学技术始终是抱着玩的态度的原 因也出自于这。在君君臣臣父父子子的不断的强化中，中国的封建制度走到了它的尽头。 既然前面阐释了传统文化并不能将我们中华民族带入近代的原因，那么我们的传统文化与西 方文化的究竟有没有优劣之分？\n西方文化与我们的传统文化绝无优劣之分。西方文化个性化契约化的文化产生有它的历史渊 源。日耳曼首领靠他们的战士为其战斗，但这并不是无偿的。相对地，首领必须定期为战士 们提供他们理应的到的战利品。这是领主应该做的，也是必须做的。这样，首领与战士之间 的关系依靠着这种简单的契约关系维持。在这种关系下，战士的人格本身独立的，而首领无 权直接干涉战士的私人生活。到了中世纪，首领变成了领主，战士变成了封臣，这种习惯仍 然存在。领主分给封臣，封臣也必须履行他们的封建义务。封臣的封臣不是领主的封臣，这 是因为他们在越级之间没有契约关系。农民与封建主的关系大抵如此。在这种社会运作制度 下，个人有相对的独立性。西方的封建制度并不像封建时代的中国一样，形成了相对集中的 中央封建强权。契约观念与罗马法的巨大影响共同作用下产生了西方现代意义上的法律，更 好地保证了人的权利。在中世纪与近代之交的西欧的城市法律下自由的气息更加明显，为新 事物的产生创造了有利的条件，加速了西方的近代化。这是《自由论》的作者密尔所认为有 利于人类社会的发展的趋势。西方比我们提早进入近代，并不是西方的文化就是好的，就该 拿来作为我们的文化。没有相应的历史积累沉淀，生搬硬套注定无法成功，无法与我们的民 族的本性相适应。\n中国在 19 世纪的近代落后于西方并不是传统文化本身的过错，其根本原因是封建化的社会 制度与思想的阻碍。新兴思想的仍然在不断地产生与发展，在明王朝对于社会的思想控制统 治相对薄弱的中后期可以窥见，在这个时期涌现了一大批思想家，像李贽、黄宗羲。李贽将 矛头直指封建礼教和整个正统思想，在一定程度反映了基本主义萌芽时代的要求。黄宗羲， 他主张的“天下为主，君为客”之说，反映了由生产力发展所导致的传统社会生产关系的变 化。具有反专制主义的特征。通过上面的举例我们可以窥见中国封建时代晚期的社会仍然不 可阻挡地产生了新的社会生产关系，相应的产生了一些带有人文特征的思想。这种大的趋势 只能通过加强专制主义制度及其思想控制来延缓，但是并不能阻挡。在没有外力影响下，随 着生产力的发展，中华民族也将会在稍微后一些的历史时期进入近代。所以，中国在 19 世 纪的近代落后于西方并不是传统文化本身的过错，其根本原因是封建化的社会制度与思想的 阻碍。 所以，不能由这些历史遗留的糟粕的存在将整个中华传统文化看做陈旧而理应被抛 弃的文化。在文化进化过程中，文化的形式会改变，但文化的核心内涵是难以改变的。诚 然，作为传统文化主体的儒家文化核心中带有一些宗法特征，但是这是产生于那个时代不可 避免的。而恰恰是文化的内涵决定了文化的特点，从而塑造了民族的性格。\n中华传统文化以儒家文化为主体，所以解决儒家文化的形式与现代社会的矛盾并加以改造， 找到儒家文化的根本内涵并取其中最重要的进行现代阐释而后加以弘扬，才能抓住振兴中华 传统文化之关键。 打过预防针之后，接下来的问题是，我们的传统文化的核心到底是什 么？该怎样抓住它？ 中华传统文化以儒家思想为主体，研究儒家思想的核心意义，解决儒 家思想与现代社会文化的种种问题，才能从根本上解决中华传统文化的传承问题。儒家思想 的核心分别为仁、义、礼、智、信、恕、忠、孝、悌。其中仁与义是儒家思想的核心中的核 心。忠、孝与礼是核心中与现代文化观念矛盾与冲突比较大的部分。\n那首先来看，“仁”与“义”的是什么？在《论语》等文献中，孔子对于“仁”的阐释如下：\n克己復禮為仁。 一日克己復禮，天下歸仁焉。為仁由己，而由人乎哉？ 出门如见大宾，使民如承大祭；己所不欲，勿施于人；在邦无怨，在家无怨。 仁者先难而后获，可谓仁矣。 居处恭，执事敬，与人忠。虽之夷狄，不可弃也。 爱人。 恭、宽、信、敏、惠。 仁者，其言也讱。 由以上孔子对仁的阐述，我们可以归纳为以下三点：\n仁代表了美好的德行。 仁代表了对社会秩序的遵守。 仁代表了对于社会的其他成员的爱。 应该注意的是，其中的第三点中的爱，并不是无差别的爱。根据孔子原本的思想，这种爱是 分三等级的，首先是父母，这就对应了“孝”；接着是家族中的其他成员；最后才是其他无血 缘关系的人。第二点与第三点合起来看，儒家的仁的思想中透露出了一种希望：在一种在温 和高尚友爱的环境下，人人对于社会秩序都自觉遵守，对于等级也自觉维护。把第一与第三 点合起来看，这些社会秩序与等级的维护可以通过恭、宽、信、敏、惠、忠等德行在社会中 教育与提倡来使人在内心中认同直至与自觉维护。孔子与后世的儒家不仅仅提倡用德行风尚 的提倡来使人们自觉向仁靠拢，他们还提倡“礼”的道德规范作用。\n从这三点来分析，“仁”的灵魂在于社会中的人对于社会秩序等级的遵守，在于社会的和谐友 爱。从逻辑上来讲“仁”，逻辑的起点是宗法等级中的血缘情爱，逻辑的展开是“爱人”，逻辑 的准则是礼，逻辑的最终结果是对自我人格的体知语修炼，以达到知天命、耳顺、从心所欲 不逾矩的完美境地。 那“义”又是什么？\n在先秦时期，“义”主要有以下含义：\n义为仪。《说文》：“义者，己之威仪也。”《尚书大传》“尚考太室之义，唐为虞宾”， 郑玄注曰：“义，当为仪；仪，礼仪也。” 适宜、适当。《释名·释言语》：“义，宜也。裁制事物使合宜也。” 道德原则的总称。《孟子·公孙丑上》：“其为气也，配义与道。”赵歧注曰：“义谓仁 义，所以立德之本也。” 有利、利益。《墨子·经说下》：“义，利也。” 由此可以看出，“义”从祭祀礼仪之威仪的本义，被引申为合宜、有利等伦理道德的意义。 而孔子所言的“义”，其实便是通过个体的自觉来约束自己的行为，使之合乎礼制。“义”成为 一种行为规范和道德、价值的原则和标准。在核心中智、信、恕、忠，可以由“义”阐发出 来。 由此看来，“仁”与“义”相结合，奠定了儒家思想的基本格调。\n“温和下的秩序”在我看来能够简要地概括儒家的思想。而“礼”则作为这种“社会秩序”在道德 上的约束，规范人们的行为。相较于“法”的外在的残酷与刚硬，儒家的“礼”显得内柔却不失 其锋利，因为“礼”的着力点是人的思想。“礼”的教化，与其所代表的社会道德，对于一个人 的影响是终生的。所以，“仁义”与“礼”，在古代思想道德方面对于社会的稳定的维护作用是 巨大的。这是古代中国统一多民族大封建国家在相对落后的交通条件与现代国家与民族观念 缺乏下能够维系的秘诀，也是汉民族乃至于中华民族在如此长的复杂的历史时期中能够长期 存在并维持相对的团结状态的关键。\n在儒家思想及其影响下形成的文化下，中华传统文化有鲜明的包容性与温和性。中华文化本 身也融合了许多的其他的文化思想。中国人向来以和为贵，“和”很大程度上收到了这种影 响。这就让中华传统文化能够不断吸收其他文化内涵，不断丰富自己的内涵。相较于西欧与 东方的穆斯林长期的相互征战，中华大地上和谐的氛围与民族间的相互融合向来是主流。 我们面对传统文化，我们在现代该怎样对待？怎样让它从封建时代脱离出来，获得现代生 命？\n让传统文化获得现代生命的唯一途径就是让传统文化获得其现代的阐释。让传统文化获得现 代阐释的途径是研究传统文化的核心本质，发掘本质内涵中与中国现代社会相适应的部分进 行重新阐释与发扬。对于传统文化的封建化与一些落后的元素，我们应该淡化其影响。进行 重新阐释时必须把握我们传统文化的核心，所进行的阐释才能不偏离传统文化的根本，才能 根本适应我们民族的灵魂性格。在传统文化的核心的价值观中在现代仍然能够适应的与重新 阐释的，我认为有基本的两个观点，礼仪道德的约束与社会成员之间的爱。 礼仪道德的约 束在现代法治社会建设中仍然有不可替代的作用，在中国人眼里，礼是可以凝聚不同民族和 地域的一种精神习惯，是社会生活中一切行为的准则。因此，以礼为核心的中国传统礼仪蕴 含着特殊的意义，涉及政治、道德和社会等各个方面。周公制礼，作为周王朝最根本的国家 制度，对政治稳定产生了重要作用。特别是《周礼》、《礼记》等书的出现，使礼和礼仪具 有系统的文本形态，从而用来教化人民。另外，中国传统礼仪也是一种道德文化，学习和遵 守礼仪是达成善良人格的重要途径。相对于法律，礼相对灵敏与迅速。再者，礼仪与道德的 教化能够深入到社会的底层，相对于冷冰冰与不易学习的法律，群众在日常生活中也更加希 望用温情的礼仪与道德来与其他人打交道。\n面对社会中的一些事件，群众也首先从社会道德方面对其做评判。礼与道德在中国社会中有 很深的基础，是社会主流舆论的导向。所以在今天提倡礼与道德，特别是在人的成长阶段， 对于培养一个健全公民是有现实意义的。提倡礼与道德，必须先在人的头脑中树立正确的价 值观，这样才能对现行的道德产生共鸣。现代社会的新道德与新礼仪应当与我们的“八荣八 耻”与“社会主义核心价值观” 相结合，吸纳现代社会的平等，开放，个性的元素。在生活的 细微处贯穿新的礼与礼仪的教育，养成讲礼的习惯，耳濡目染，内心才能真正地接受礼的教 化。新礼、新道德与新的正确价值观的相互作用才能真正在让人形成良好的人格，做一位人 格健全的公民。这不同于传统文化中纯粹的“忠”，礼与道德像法律，不以个人的意志为转 移，它们也像法律，维护着每个人的尊严。\n结合中华民族的具体情况，这对于维系一个人与社会的关系，形成良好的社会氛围，维系社 会与政治的稳定有着重要作用。 社会成员之间的相互的爱也非常重要，这个有差别的爱在 现代应当阐释为对父母的敬爱，对于妻子孩子的亲爱，对于其他成员的友爱。对于现代社会 中的冷漠与欺骗，一方面我们需要法律与道德的规范与约束，另一方面对于社会成员间的爱 的提倡将形成一种温情，和谐的社会氛围来感化这样的黑暗。这对于我们现在所倡导的和谐 社会建设有巨大的现实意义。在友爱的教育下，社会成员相互之间的第一反应将会是友善 的，在友善的第一印象下，很难会产生大恨。再者，己所不欲，勿施于人。将心比心，将更 好地化解人民内部矛盾。对于父母的敬爱不同于传统纯粹的“孝”，它不是上而下的封建的义 务与长辈一言堂，而是现代社会的父母与孩子之间最真挚的爱。对于妻子与孩子的亲爱，当 然不会是“夫为妻纲”，“父为子纲”。这种亲爱的是由现代社会的平等与和睦来阐述的，其形 式是自由的。这些新的阐述在摒弃了封建的糟粕的同时，也能让传统文化更好地适应与服务 与现代社会的建设。\n发挥好传统文化的这两个作用，顺应我们社会的特点，对于社会的积极健康向上发展，提升 文化软实力有着不可替代的作用。中华传统文化不是一成不变的，它不和特定的政治经济绑 定，它需要不断顺应生产力与社会的发展，永远地伴随我们中华民族。\n","permalink":"https://blog.bktus.com/archives/2pqkja/","summary":"\u003cp\u003e在讨论传统文化之前，先来讨论民族。先来看看民族的概念：民族是指经长期历史发展而形\n成的稳定共同体，一群基于历史、文化、语言与其它人群有所区别的群体。根据定义可以知\n道历史、文化、语言是区本民族别于其他民族的重要的三个元素。而这三个元素并不是相互\n独立的，相反，它们是相互影响，相互交融的，谁也离不开谁。\u003c/p\u003e\n\u003cp\u003e从文化来看，一个民族的文化渗入它的历史之中，影响着本民族个体乃至全体的思维，进而\n影响在特定历史条件下本民族面对历史提出的问题所作出的决定。类似地，文化渗入语言，\n一个民族的语言的文法，词汇，表达中都有明显的文化痕迹。所以，文化极大地塑造了一个\n民族的性格，赋予一个民族它的灵魂，是一个民族区别于其他民族的极为重要的因素。中华\n传统文化之于中华民族来说亦是如此。\u003c/p\u003e\n\u003cp\u003e但是近代以来，中华传统文化在中国人心中逐渐地淡化，甚至于个别个体全然忘却。近来频\n繁地出现了种种违反道德的社会事件，从表面看仅仅是违反道德而已。但是深究其根本，则\n是社会道德的母体传统文化本身，被本民族个别个体所轻视，淡忘所导致的道德约束力的下\n降。本民族的传统文化在民族个体在内心不被认同，将导致中华民族的凝聚力与认同感的下\n降，对于中华民族来说将是灾难性的。就像当下，面对日美文化，中国青年热衷接受；面对\n传统文化，中国青年则一脸茫然。但这又是为什么？中华传统文化到底是什么？未来将何去\n何从？\u003c/p\u003e\n\u003cp\u003e在近现代中国人的理解中，传统文化似乎制约了社会的发展。诚然，传统文化中三常五纲，\n逆来顺受，宗法等级确实在一个现代人看来是落后的。并且传统文化中缺乏对于论断的进行\n思维论证的态度也是不可取的。既然对于传统文化产生了这样的疑问。那么在封建时期，传\n统文化的状态就是我们应当首先讨论的问题。\u003c/p\u003e\n\u003cp\u003e中华传统文化在近代为什么不符合社会的发展潮流，是因为传统文化本身被很大程度地封建\n化。明清时期，是中国古代的封建社会的晚期。这是封建专制主义中央集权制度的强化的时\n期，也是中国的封建皇权走向巅峰的时期。理学的出现与统治阶级对于理学的强化，逐渐束\n缚了我们民族的人格与思想。在封建专制主义制度的强力作用下，传统文化很大程度上向着\n适应封建统治的方向上发展。为什么中华民族会进入这样一段历史时期。其实这并不是这些\n皇帝个人的意志的体现，而是传统文化影响下中华民族的性格与历史大环境所造就的。 其\n实，当中国封建制度走到宋代的时候，相对于前代唐代，能很明显地感觉到文化与制度双重\n影响下的人格的束缚越来越强。\u003c/p\u003e\n\u003cp\u003e我认为，出现这种情况的正显现了封建制度已经越来越不能适应于生产力与思想发展后人们\n自然产生的对于新兴的生产关系，人格自由的需求。面对新兴的事物，传统封建统治阶级在\n落后的世界观与对于落后生产关系的理解的影响下，对接受、利用与管理这些新兴事物显得\n力不从心。草率地将这些新兴事物纳入他们熟悉的统治体系是不可能做到的，他们的统治体\n系与他们的思想本身并没有这些基因。他们对这种令他们感到纷乱的事物产生了本能的恐\n惧，努力让社会与人们的思想重新回到他们认为的样子中去。\u003c/p\u003e\n\u003cp\u003e于是到了明代，底层农民出身的朱元璋封建文化与历史的巨大惯性的影响下，不可避免地渴\n望社会的秩序与道德的重建，建立一个统一的君主专制的封建的王朝。但是，在他的那个时\n期，新兴事物与思想的出现让社会变得复杂，社会本身已经不可能恢复到秦汉与隋唐时期。\n这让他本能地走向了对于人民更严格的控制。兴八股，造户籍，明律法，这是他采取的措\n施。纯粹的以孝治天下的主张，更是希望国家臣民能够在伦理道德的强力约束下接受统治。\n选拔国家人才的科举制度，也成为了知识分子思想上的一道重重的枷锁。对于这些知识分子\n出身的官员，他们的思想举动被牢牢锁定在私塾五经的范畴。丞相的废除与锦衣卫，东厂的\n设立，更加体现了中央皇权的加强，中央皇权对于社会的控制更加牢固。于是在明初这种恐\n怖环境下，新兴事物根本无力与已经占据绝对优势的全副武装的封建秩序对抗。\u003c/p\u003e\n\u003cp\u003e需要注意，古代中国的律法，一直被人民视为对于臣民严重违反社会道德规范的严厉惩罚与\n对于臣民行为的在道德以外的严格规定，并不是现代意义上保护人民权利的法律。 在思想\n方面，封建化的中华传统的文化逐渐强化了它的“趋同”的特征，在这种环境下新兴事物的传\n播本来就缓慢。随着传统的儒家文化的逐渐地封建化与经文化，对于接受这些新兴思想的少\n数人，一直被排除在主流社会之中，不仅受到道德与封建法律的联合打压，更受到在封建世\n界观影响下成长起来的自己对于自己思想的怀疑。再者，古代中国人对于“天下”，“朝\n廷”，“君父”的根生蒂固的认识下，在王朝更替，集权封建制度控制相对较弱的时期，仍然\n会自然地选择走向另一个新的集权封建制度的统治。 所以，在被封建地主阶级改造的儒家\n思想的巨大惯性下运转中国，两千年的对中华民族的思想的改造与对于儒家文化的单方面阐\n释下，使得以儒家思想为主体的传统文化已经变成他们他们维系统治的有力工具。以儒家思\n想为主体的中华传统文化在两千年的封建社会的进程中逐渐向它的“趋同”与“秩序”的方向上\n倾斜，越来越不利于生产力与新思想的发展，社会进入了相对稳定的停滞时期。\u003c/p\u003e\n\u003cp\u003e在明朝后期到乾隆时期，皇帝对于西方传来的闻之未闻的科学技术始终是抱着玩的态度的原\n因也出自于这。在君君臣臣父父子子的不断的强化中，中国的封建制度走到了它的尽头。\n既然前面阐释了传统文化并不能将我们中华民族带入近代的原因，那么我们的传统文化与西\n方文化的究竟有没有优劣之分？\u003c/p\u003e\n\u003cp\u003e西方文化与我们的传统文化绝无优劣之分。西方文化个性化契约化的文化产生有它的历史渊\n源。日耳曼首领靠他们的战士为其战斗，但这并不是无偿的。相对地，首领必须定期为战士\n们提供他们理应的到的战利品。这是领主应该做的，也是必须做的。这样，首领与战士之间\n的关系依靠着这种简单的契约关系维持。在这种关系下，战士的人格本身独立的，而首领无\n权直接干涉战士的私人生活。到了中世纪，首领变成了领主，战士变成了封臣，这种习惯仍\n然存在。领主分给封臣，封臣也必须履行他们的封建义务。封臣的封臣不是领主的封臣，这\n是因为他们在越级之间没有契约关系。农民与封建主的关系大抵如此。在这种社会运作制度\n下，个人有相对的独立性。西方的封建制度并不像封建时代的中国一样，形成了相对集中的\n中央封建强权。契约观念与罗马法的巨大影响共同作用下产生了西方现代意义上的法律，更\n好地保证了人的权利。在中世纪与近代之交的西欧的城市法律下自由的气息更加明显，为新\n事物的产生创造了有利的条件，加速了西方的近代化。这是《自由论》的作者密尔所认为有\n利于人类社会的发展的趋势。西方比我们提早进入近代，并不是西方的文化就是好的，就该\n拿来作为我们的文化。没有相应的历史积累沉淀，生搬硬套注定无法成功，无法与我们的民\n族的本性相适应。\u003c/p\u003e","title":"中华传统文化与民族近代命运的关系"},{"content":"Chromium 浏览器内核中，来自前端的内容如何在最终转换成为屏幕上的各个像素点，也就 是浏览器内核渲染过程，这是一个复杂的工程上的一系列步骤。对于这些复杂的步骤我们需 要把握的方面包括该过程每个阶段的设计思想、数据模型、数据模型的交互。深入而仔细地 理解上述内容，有利与我们阅读庞大源码中始终保持清醒找准定位。下面我将基于我对 Life of a Pixel 的理解仔细来讨论这个过程。\nLife of a Pixel 输入与输出 首先需要谈谈输入与输出，这一系列步骤的输入成为 Web Content。它由一系列现有协议所 定义的一套描述 Web 内容的文本主要组成，当然也包括其他引用内容。现有的协议（我们 常称为编程语言），通常是 HTML、CSS 与 JavaScript。这三者分别有定义了 Web 内容的 结构内容、样式、逻辑。但这并非严格分工，在当下流行的前端设计思想（前后端分离）中 JavaScript 正在承担着越来越多的职责。JavaScript 本身也正在不断地变得独立，近年来 逐渐跳出浏览器这个平台来独立发挥其作用（node.js、react-vr）。 另一方面，谈谈输 出。如何绘制屏幕上的像素，涉及到计算机图形学方面的理论与工程。在传统上，我们可以 认为计算机向屏幕输出内容经历了以下步骤。应用软件将其想要表达的图形内容转换成为对 操作系统与图形相关的函数库的调用（OpenGL、Direct X 等），这些函数库通过操作系统 中安装驱动程序及其他操作系统有关服务向硬件（GPU 等）传送数据与指令，并操纵硬件的 计算核心与存储器（GPU 等）完成光栅化等步骤、最终将硬件（GPU 等）存储器中的最终内 容转化为向屏幕输出的信号。在这方面，由函数库（OpenGL）所提供的 API 都是比较低级 的。 举个例子，原来我也做过 OpenGL 相关的编程，虽然现代 OpenGL 提供一些模型与协 议（如管线等）来简化工作，但是其提供的模型以及依照模型设计的 API 带有明显的硬件 气息。对于 OpenGL 来说，调用者要精确输入预设的或者利用 GPU 程序计算出的数据其所 绘制内容的在几何坐标系下的位置、颜色以及绘制顺序。 由输入输出来大致推断，浏览器 的工作是按照先行前端协议（前端编程语言）及其附属多媒体、数据等方面的内容精确理解 前端开发者对于 Web 页面的描述，并通过数据的层层流动与转换计算推断出图形函数库所 需的各类信息。现行前端协议的复杂性与兼容性与鲁棒性要求使得这一步骤的实现十分复杂 与庞大。而对于性能与稳定的要求更提升了设计与实现上的难度。实现这一系列不仅仅是技 术上，而且也是软件工程上的难题。\n页面生命周期综述 由上述对于输入与输出的讨论，我们可以理解 Chromium 团队提出以下有关于页面生命周 期。\n基于 Web Content 经过若干步骤产生相关数据模型。 数据模型随时间、交互等因素的不断更新。 其中，对于第二点要求尽可能少地快速地修改由初由第一步骤产生的数据模型，尽可能降低 计算成本。其原因在于，第一步骤产生数据模型所进行的相关计算与数据模型的交互对于现 有平均性能水平来说依然十分昂贵，所以不断重复执行第一步骤在实际环境中不可取。\n初步渲染步骤概论 对于上面提及过的在输入与输出之间的相关步骤，我们将在下面按照前后顺序进行有关论 述。\nDOM 对于 HTML 来说，其语法规则有着明显的树状特征。这使得利用 HTML 能够方便地描述出文 档结构并附带部分内容。所以我们需要提取出其中地结构信息与内容信息。在这一步 中，HTML 文档解析器将解析 HTML 文档中的文本并转换其为 DOM 树。 有关于 DOM 树，不 得不提的是 JavaScript 对于 DOM 树的操作能力，我认为这也是 JavaScript 的核心内容 之一。JavaScript 借由该能力能够对页面进行控制、更新与内容添加、更改、删除等操 作，我认为这是前后端分离思想的技术基础。上述能力对 DOM 树的实际操作由 V8 引擎具 体完成，该引擎实现了 JavaScript 的操作 DOM 树的 API，使得 JavaScript 具备该能 力。\nCSS（style） CSS 有着两方面的主要作用，筛选或者指定其作用的 HTML 标签，定义其所对应 HTML 标签 的内容。对于我们来说，其本质在于筛选或者查找出 DOM 节点，并将样式信息与 DOM 节点 对应起来。 其中有个问题需要注意，在 CSS 文件对于某一个或者几个 DOM 节点的样式的 描述中，可能存在着未定义、重复定义、冲突、无效的样式定义。针对这个问题，Chromium 团队引入了重计算（recalc）针对 DOM 树每一个节点计算所有对应的 Computed Style。\nLayout 有了上面两个步骤提供的信息，我们需要进一步的转换。将 DOM 节点与 Compute Style 一 道转换为视觉几何结构（Layout）。在这一步中我们需要解决的问题包括文字、表格、布局 等等元素的在页面最终位置、排布及大小。为了能够对这些信息进行有效计算并且整 理，Chromium 构造了 Layout Tree。这个数据结构旨在容纳结构有关信息并且进行上述工 作。 在代码中，以下结构描述了有关信息。\n// A LayoutRect contains the information needed to generate a CGRect that may or // may not be flipped if positioned in RTL or LTR contexts. |boundingWidth| is // the width of the bounding coordinate space in which the resulting rect will // be used. |position| is used to describe the location of the resulting frame, // and |size| is the size of resulting frame. struct LayoutRect { CGFloat boundingWidth; LayoutRectPosition position; CGSize size; }; Layout 树与 DOM 树的节点关系并非一对一，有一些情况下 DOM 节点并不需要有其对应的 Layout 对象或者其可以放入其他有关的 Layout 对象中（通常为父节点对应的 Layout 对 象）。其中有一点正在变化的内容需要注意，legacy layout object 与 LayoutNG。LayoutNG 的提出，是为了解决当前的 Layout 对象中输入输出及其他中间内容 混杂并且在计算过程中父子节点相互引用的问题。原先的设计首先带来了节点数据有效性的 确定的问题，正在计算的节点需要判断其引用的数据是其需要的最终状态，不然节点当前所 计算出的数据依然可能要在稍后重新计算，这提升了算法设计的复杂度。与此对 应，LayoutNG 的输入与输出分离，而且输出一旦产生其状态就已经确定且不可以修改，结 构清晰，所以在算法设计上相对简单有效率。\nPaint 依据 Layout 树提供的信息，我们可以计算出一些更加基本的信息如坐标系位置、大小、颜 色、绘制顺序等。在 Paint 步骤，我们将 Layout 树转换为一组绘制的操作列表。而在更 新过程中，前一步操作会将 Layout 树拆分为一个个独立的图层，而在该步骤中针对每一个 图层都会生成其独立的绘制操作列表。在该过程期间，我们需要将结构信息转换为绘制的堆 栈顺序，堆栈内部的各个节点是有着相对独立的绘制过程的 Paint Phrase。具体地结合代 码来说，Paint 步骤将产生一组 DrawXXXOp 的数据结构。\nRaster 光栅化将绘制信息转换为在内存（通常为现存）中的位图。由上面步骤产生的 DrawXXXOp 数据结构通过 raster 步骤转换为 ImageDecodeCache。其中 raster 步骤并不在渲染进程 中进行，而在 GPU 进程（设计上的概念）中进行。其原因在于该步骤需要产生 GL 调用 （一种在运行时动态绑定到 OpenGL API 地址的代理函数），而由于浏览器的沙箱策略禁止 渲染进程直接进行 GL 调用。这能有效避免恶意代码对实际的 OpenGL API 漏洞的利用，并 且防止 GL 或者驱动程序中的一些缺陷带来的不稳定因素使得渲染进程崩溃，进而降低浏览 器整体的稳定性。在这个过程中，DrawXXXOp 数据结构将由渲染进程传递到 GPU 进程。GPU 进程将进行该数据结构的 DrawRectOpResult()的调用。 DrawRectOpResult 结构操作 SKIA 提供的接口产生进而 GL 调用，相较于 GL 函数组提供的功能，SKIA 能够提供进行一些更 加高级的计算机图形学计算。SKIA 也运用到了 Google 的其他项目中，如 Android。 在此 仍然需要提到 GPU 进程的有关一些要点。GPU 进程能够在崩溃后被浏览器重新启动，这一 切不被用户感知，并且一个 GPU 进程能够服务于多个 Web Content 渲染进程，这也包括 UI 的渲染进程。而在 Windows 平台，GL 调用会最终通过 ANGLE 翻译成 Direct X 调用。 其原因在于 OpenGL 的支持在 Windows 下实际效果（个人推断为性能、支持程度等方面的 因素）并不理想，而 Direct X 虽然在精度上略逊于 OpenGL（专业工业设计软件等大多使 用 OpenGL 调用），但是其在显卡驱动的兼容、性能方面等强于 OpenGL（部分 OpenGL API 在普通显卡的驱动上支持不良甚至不被支持，这也是有昂贵专业显卡硬件及其驱动产品的原 因之一）。值得一提的是，Direct X 就是微软自己发起并不断维护的。\n结论说明 初步渲染的结果依然保存在内存（内存、显存）中，这也代表了相关数据结构及其需要的数 据以及被计算或者生成完毕。这些数据结构与其中的数据将运用到下面的更新过程中，作为 数据输入与支撑。\n更新步骤概论 由于在涉及范围较大时，Paint 与 Raster 这两部操作十分昂贵。针对浏览器应用，要保证 60 FPS 甚至更高，如果低于这个线用户可能会感受到卡顿影响用户体验。所以更新步骤的 设计思想主要是对尽可能少的数据结构进行 Paint 与 Raster 步骤。而该设计思想下，主 要努力的方向我认为分为以下两点：细化操作粒度、为操作设置优先级来确保用户能看到的 体验。还有一个方面是，由于 JavaScript 的单线程性质，如何在 JavaScript 做昂贵操作 阻塞主线程的情况下，尽可能快的处理一些能利用已有信息当下能够处理的相应，这是一个 难题。更新步骤中设计有关机制与设计思想，比较难以理解。我将在下面的内容中，基于我 的理解论述这一系列的步骤。\n合成（compositing） Chromium 针对上述设计思想与难点引入和 compositing 这个解决方案。在该解决方案下， 页面将被分解成各自独立渲染的图层。这些图层由一个叫做合成线程的线程进行处理并绘 制。在主线程上，表现为将 Layer 树经由 Paint Layer 转换为 cc::Layer，而 cc::Layer 就是上面提到的独立额图层。cc::Layer 也是合成器操作的基本单元。Paint Layer 可以理 解成候选的 cc::Layer，它通过上下文及相应机制进行宣选择、合并与转换。cc::Layer 是 一个列表类型的数据结构，由 Graphics Layer 通过 UpdateAssignmentIfNeeded 进行构 建。以上步骤均位于 compositing assignment 步骤中，但是未来将会计划被移到 Paint 之后，具体原因不明。\n// Base class for composited layers. Special layer types are derived from // this class. Each layer is an independent unit in the compositor, be that // for transforming or for content. If a layer has content it can be // transformed efficiently without requiring the content to be recreated. // Layers form a tree, with each layer having 0 or more children, and a single // parent (or none at the root). Layers within the tree, other than the root // layer, are kept alive by that tree relationship, with refpointer ownership // from parents to children. class CC\\_EXPORT Layer : public base::RefCounted\u0026lt;Layer\u0026gt; { ... }; Prepaint 该步骤会将一些绘制属性引用到某些图层（cc::Layer）中，绘制属性在这时会与图层绑 定。该步骤的材料所提供的信息不多，但是个人认为可以参考 Photoshop 中的图层模型和 对应绘制属性（黑白、亮度、对比度等等）来推断。日后，针对该方便的研究需要通过额外 的文档或者源代码来进行。\nCommit\u0026amp;Tilling 在主线程绘制完成后，会将更新后的数据结构 cc::Layer 列表与属性树与合成线程 impl（也就是上面提到的合成线程）同步。impl 在同步操作后，会将图层需要绘制的部分 提取出来，变成更小粒度的 Tile。而 Tile 是栅格化工作的基本单元，记录了需要被栅格 化的部分在页面中的位置以及绘制步骤等相关信息。Tile 生成后会被放入 Tile 池中，由 栅格化线程根据优先级进行栅格化操作。栅格化优先级是有浏览器视窗距离 Tile 所指定位 置的距离推断的。栅格化线程可能有多个，存在于 GPU 进程中。 在栅格化过程后，Tile 会生成 Quad，而 Quad 是在屏幕特定位置绘制 Tile（已经栅格化完成）的命令。其具体路 径是 Tiles-\u0026gt;AppendQuads()-\u0026gt;CompositorFrame。而 CompositorFrame 中包含了 DrawQuardList。值 得注意的是，CompositorFrame（合成帧）是渲染进程的输出，也是后 面渲染进程与 GPU 进程之间传递的数据结构。而 Tile 的栅格化操作一般在 GPU 进程中进 行，这样可以获得更好的性能。\nActivate\u0026amp;Draw 为了进一步优化 Commit\u0026amp;Tilling 效率，缓解渲染进程与 GPU 线程可能出现的不协同 性，Chromium 在渲染进程的 impl 线程引入了 Pending Tree 与 Active Tree 这两种工作 流。Pending Tree 中接收的的是由主线程提交到 impl 线程的图层（包含图层和属性树） 列表，并适时进行栅格化。Active Tree 中接收栅格化后的图层（包含栅格化结果），并进 行绘制操作。该多工作流模型的引入使得一边 raster 一边 draw 成为了可能，这提高了吞 吐量。\nDisplay 这里需要扩展描述一个模型，在 Chromium，渲染进程与 GPU 进程之间并不是一对一的。在 实际模型中，很可能是多个渲染进程对应一个 GPU 进程。在这里，个人推断是每个标签页 对应一个渲染进程。同时需要注意，浏览器进程里的 UI 框架中的合成器也会与 GPU 进程 通信。所以可以这么理解，GPU 进程在运行时负责整个软件的栅格化与绘制操作。 GPU 进 程与其对应通信的模块之间传递的是合成帧，合成帧与 surface（它出现在屏幕中的位置） 有关。这其中有一个表面聚合的概念，现有材料提供的信息不多，个人认为是涉及合成帧之 间的重叠位置的处理与优化。 剩下的操作在 GPU 进程中。它会利用合成帧中的 Quad 提供 的信息生成并执行一组 GL 调用。而 GL 调用会通过命令缓冲区进行序列化并进行代理调 用。上述过程在 via 线程中进行。而真正的 GL 调用将会在 gpu 线程（与 GPU 进程区分 开）中进行，该线程最终将通过 OpenGL 的 API，来进行实际的屏幕绘制操作。 在新的 Display 操作模式下，via 线程将使用 SKIA 进行绘制操作，并将结果（deferred display list 数据结构）传递给 gpu 线程，最终位于 gpu 线程中的 SKIA 后端会根据所获得的信 息做实际的 GL 调用（或者 Valkan 调用）。\n最后的一些细节 由于现代显示器一般使用双缓冲区，在特定时刻一个缓冲区的用于绘制，一个缓冲区的用于 显示。在前者完成绘制后，通过 Swap 操作，前者内容（帧）将会被显示到屏幕中而后者会 被用于下一帧的绘制。不断地如此往复。 栅格化操作和显示操作均在 GPU 进程中进行，前 者原因在于利用 GPU 对栅格化操作进行加速。\n","permalink":"https://blog.bktus.com/archives/oams24/","summary":"\u003cp\u003eChromium 浏览器内核中，来自前端的内容如何在最终转换成为屏幕上的各个像素点，也就\n是浏览器内核渲染过程，这是一个复杂的工程上的一系列步骤。对于这些复杂的步骤我们需\n要把握的方面包括该过程每个阶段的设计思想、数据模型、数据模型的交互。深入而仔细地\n理解上述内容，有利与我们阅读庞大源码中始终保持清醒找准定位。下面我将基于我对\nLife of a Pixel 的理解仔细来讨论这个过程。\u003c/p\u003e\n\u003ch2 id=\"life-of-a-pixel-输入与输出\"\u003eLife of a Pixel 输入与输出\u003c/h2\u003e\n\u003cp\u003e首先需要谈谈输入与输出，这一系列步骤的输入成为 Web Content。它由一系列现有协议所\n定义的一套描述 Web 内容的文本主要组成，当然也包括其他引用内容。现有的协议（我们\n常称为编程语言），通常是 HTML、CSS 与 JavaScript。这三者分别有定义了 Web 内容的\n结构内容、样式、逻辑。但这并非严格分工，在当下流行的前端设计思想（前后端分离）中\nJavaScript 正在承担着越来越多的职责。JavaScript 本身也正在不断地变得独立，近年来\n逐渐跳出浏览器这个平台来独立发挥其作用（node.js、react-vr）。 另一方面，谈谈输\n出。如何绘制屏幕上的像素，涉及到计算机图形学方面的理论与工程。在传统上，我们可以\n认为计算机向屏幕输出内容经历了以下步骤。应用软件将其想要表达的图形内容转换成为对\n操作系统与图形相关的函数库的调用（OpenGL、Direct X 等），这些函数库通过操作系统\n中安装驱动程序及其他操作系统有关服务向硬件（GPU 等）传送数据与指令，并操纵硬件的\n计算核心与存储器（GPU 等）完成光栅化等步骤、最终将硬件（GPU 等）存储器中的最终内\n容转化为向屏幕输出的信号。在这方面，由函数库（OpenGL）所提供的 API 都是比较低级\n的。 举个例子，原来我也做过 OpenGL 相关的编程，虽然现代 OpenGL 提供一些模型与协\n议（如管线等）来简化工作，但是其提供的模型以及依照模型设计的 API 带有明显的硬件\n气息。对于 OpenGL 来说，调用者要精确输入预设的或者利用 GPU 程序计算出的数据其所\n绘制内容的在几何坐标系下的位置、颜色以及绘制顺序。 由输入输出来大致推断，浏览器\n的工作是按照先行前端协议（前端编程语言）及其附属多媒体、数据等方面的内容精确理解\n前端开发者对于 Web 页面的描述，并通过数据的层层流动与转换计算推断出图形函数库所\n需的各类信息。现行前端协议的复杂性与兼容性与鲁棒性要求使得这一步骤的实现十分复杂\n与庞大。而对于性能与稳定的要求更提升了设计与实现上的难度。实现这一系列不仅仅是技\n术上，而且也是软件工程上的难题。\u003c/p\u003e\n\u003ch2 id=\"页面生命周期综述\"\u003e页面生命周期综述\u003c/h2\u003e\n\u003cp\u003e由上述对于输入与输出的讨论，我们可以理解 Chromium 团队提出以下有关于页面生命周\n期。\u003c/p\u003e","title":"Life of a Pixel Chromium浏览器内核渲染原理 学习笔记"},{"content":"MacOS 现在默认采用 Zsh，所以在用户家目录下寻找.zshrc 配置文件，如果没有则自行创 建。 编辑.zshrc 文件，在最后加入下列代码\n\\# set proxy alias proxy=\u0026#39;export all\\_proxy=socks5://127.0.0.1:1234\u0026#39; alias unproxy=\u0026#39;unset all\\_proxy\u0026#39; 添加完毕后，保存，在终端输入以下命令\n% source ～/.zshrc 完毕。 如果需要使用代理，则预先输入 proxy 命令。如果需要取消代理，则输入 unproxy 命令。 或者如果需要默认启用代理的话，在.zshrc 配置文件末尾添加\n% export https\\_proxy=http://127.0.0.1:7890 http\\_proxy=http://127.0.0.1:7890 all\\_proxy=socks5://127.0.0.1:7890 保存。\n% source ～/.zshrc 完毕。\n","permalink":"https://blog.bktus.com/archives/5ytwua/","summary":"\u003cp\u003eMacOS 现在默认采用 Zsh，所以在用户家目录下寻找.zshrc 配置文件，如果没有则自行创\n建。 编辑.zshrc 文件，在最后加入下列代码\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e\\# set proxy\nalias proxy=\u0026#39;export all\\_proxy=socks5://127.0.0.1:1234\u0026#39;\nalias unproxy=\u0026#39;unset all\\_proxy\u0026#39;\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003e添加完毕后，保存，在终端输入以下命令\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e% source ～/.zshrc\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003e完毕。 如果需要使用代理，则预先输入 proxy 命令。如果需要取消代理，则输入 unproxy\n命令。 或者如果需要默认启用代理的话，在.zshrc 配置文件末尾添加\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e% export https\\_proxy=http://127.0.0.1:7890 http\\_proxy=http://127.0.0.1:7890 all\\_proxy=socks5://127.0.0.1:7890\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003e保存。\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e% source ～/.zshrc\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003e完毕。\u003c/p\u003e","title":"为MacOS的终端加上代理设置"},{"content":"在 HyperV 下 Windows 系虚拟机默认就能够开启增强会话。在增强会话下可以方便地和宿 主机共享剪切板与文件，并且在增强会话开启时也能够比较方便地调整分辨率。而 Ubuntu 等 Linux 系统下则默认为基本会话，分辨率的调整需要修改引导文件，而剪切板和宿主机 是独立的非常不方便。 进过查找，找到了一种可以在 Ubuntu 20.04 下开启增强会话的方 法，使用后有效，遂在此记录。 首先需要开启增强会话的话必须先在创建虚拟机的时候开 启第二代虚拟机选项，在首次启动的时候关闭 Secure Boot。记得在安装 Ubuntu 的时候不 要开启自动登录选项。 在终端下输入\n% wget https://raw.githubusercontent.com/Hinara/linux-vm-tools/ubuntu20-04/ubuntu/20.04/install.sh % sudo chmod +x install.sh % sudo ./install.sh 在 Windows Power Shell 运行以下命令，然后重启。\n\\\u0026gt; Set-VM -VMName \u0026lt;your\\_vm\\_name\u0026gt; -EnhancedSessionTransportType HvSocket 重启后，就能看到链接虚拟机后会话已经为增强会话模式而后 Ubuntu 进入的 XRDP 登录页 面，此时正常输入用户名密码即可。\n","permalink":"https://blog.bktus.com/archives/hx2req/","summary":"\u003cp\u003e在 HyperV 下 Windows 系虚拟机默认就能够开启增强会话。在增强会话下可以方便地和宿\n主机共享剪切板与文件，并且在增强会话开启时也能够比较方便地调整分辨率。而 Ubuntu\n等 Linux 系统下则默认为基本会话，分辨率的调整需要修改引导文件，而剪切板和宿主机\n是独立的非常不方便。 进过查找，找到了一种可以在 Ubuntu 20.04 下开启增强会话的方\n法，使用后有效，遂在此记录。 首先需要开启增强会话的话必须先在创建虚拟机的时候开\n启第二代虚拟机选项，在首次启动的时候关闭 Secure Boot。记得在安装 Ubuntu 的时候不\n要开启自动登录选项。 在终端下输入\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e% wget https://raw.githubusercontent.com/Hinara/linux-vm-tools/ubuntu20-04/ubuntu/20.04/install.sh\n% sudo chmod +x install.sh\n% sudo ./install.sh\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003e在 Windows Power Shell 运行以下命令，然后重启。\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e\\\u0026gt; Set-VM -VMName \u0026lt;your\\_vm\\_name\u0026gt;  -EnhancedSessionTransportType HvSocket\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003e重启后，就能看到链接虚拟机后会话已经为增强会话模式而后 Ubuntu 进入的 XRDP 登录页\n面，此时正常输入用户名密码即可。\u003c/p\u003e","title":"HyperV Ubuntu虚拟机开启增强会话 调整分辨率 启用剪切板"},{"content":"安装 zsh 插件 zsh-autosuggestions 或者 zsh-syntax-highlighting 的时候，我们一般 会遇到 oh-my-zsh plugin \u0026lsquo;xxx\u0026rsquo; not found 的问题。现在，我们分析并解决 oh-my-zsh plugin \u0026lsquo;zsh-autosuggestions\u0026rsquo; not found 与 plugin \u0026lsquo;zsh-syntax-highlighting\u0026rsquo; not found 问题。 问题产生的原因是，没有把插件的代码仓库克隆到本地位置上，所以你想要 的插件其实并没有被安装。 解决问题非常简单，只需要简单输入以下命令：\n$ git clone https://github.com/zsh-users/zsh-autosuggestions ~/.oh-my-zsh/custom/plugins/zsh-autosuggestions $ git clone https://github.com/zsh-users/zsh-syntax-highlighting.git ~/.oh-my-zsh/custom/plugins/zsh-syntax-highlighting $ source ~/.zshrc 然后，你可以重新打开 shell，可以发现问题已解决。你也可以用该方法解决其他插件的此 类问题。\n","permalink":"https://blog.bktus.com/archives/9s3ea1/","summary":"\u003cp\u003e安装 zsh 插件 zsh-autosuggestions 或者 zsh-syntax-highlighting 的时候，我们一般\n会遇到 oh-my-zsh plugin \u0026lsquo;xxx\u0026rsquo; not found 的问题。现在，我们分析并解决 oh-my-zsh\nplugin \u0026lsquo;zsh-autosuggestions\u0026rsquo; not found 与 plugin \u0026lsquo;zsh-syntax-highlighting\u0026rsquo; not\nfound 问题。 问题产生的原因是，没有把插件的代码仓库克隆到本地位置上，所以你想要\n的插件其实并没有被安装。 解决问题非常简单，只需要简单输入以下命令：\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e$ git clone https://github.com/zsh-users/zsh-autosuggestions ~/.oh-my-zsh/custom/plugins/zsh-autosuggestions\n$ git clone https://github.com/zsh-users/zsh-syntax-highlighting.git ~/.oh-my-zsh/custom/plugins/zsh-syntax-highlighting\n$ source ~/.zshrc\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003e然后，你可以重新打开 shell，可以发现问题已解决。你也可以用该方法解决其他插件的此\n类问题。\u003c/p\u003e","title":"解决oh-my-zsh plugin 'zsh-autosuggestions' not found 与 plugin 'zsh-syntax-highlighting' not found问题"},{"content":"经过一段时间对于 Synology Drive 的版本控制功能的使用，总结出以下不建议使用版本控 制的常见使用场景：\nSynology Drive 版本控制不适用于对音乐库与视频库的版本控制。这两种场景适合使 用回收站机制来管理。因为音乐和视频库的容量变动幅度较大，在一段时间内进行大量 的删除或者格式转换操作后，磁盘可用空间将迅速下降。不建议将音乐库与视频库加入 到版本控制。 版本控制不适用于对于应用程序相关数据文件（例如 Docker 容器的映射目录下的相关 文件）的备份，经过长期观察，版本控制对文件修改并不敏感。我往往发现经常变动的 应用程序数据文件一直维持在较老的版本，并没有在应用程序对该文件作出修改后立即 备份上一版本。建议使用 Cloud Sync 或者 Hyper Backup 对应用程序数据文件进行备 份。 上述问题经过长期使用总结出，如果发现其他问题则会更新此文章。\n","permalink":"https://blog.bktus.com/archives/d010i6/","summary":"\u003cp\u003e经过一段时间对于 Synology Drive 的版本控制功能的使用，总结出以下不建议使用版本控\n制的常见使用场景：\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eSynology Drive 版本控制不适用于对音乐库与视频库的版本控制。这两种场景适合使\n用回收站机制来管理。因为音乐和视频库的容量变动幅度较大，在一段时间内进行大量\n的删除或者格式转换操作后，磁盘可用空间将迅速下降。不建议将音乐库与视频库加入\n到版本控制。\u003c/li\u003e\n\u003cli\u003e版本控制不适用于对于应用程序相关数据文件（例如 Docker 容器的映射目录下的相关\n文件）的备份，经过长期观察，版本控制对文件修改并不敏感。我往往发现经常变动的\n应用程序数据文件一直维持在较老的版本，并没有在应用程序对该文件作出修改后立即\n备份上一版本。建议使用 Cloud Sync 或者 Hyper Backup 对应用程序数据文件进行备\n份。\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e上述问题经过长期使用总结出，如果发现其他问题则会更新此文章。\u003c/p\u003e","title":"Synology Drive版本控制功能使用心得"},{"content":"联系我 我通过电子邮件与陌生人通信。若需要联系我，请发送邮件至 eric@bktus.com（邮箱服务商位于德国并已使用各种措施加强 隐私保护）。如果你不想用明文与我通信，可使用页面底部的 PGP 公钥加密你的邮件。我 一般 2 天内就会回邮件，如果涉及内容较多，我一般会在周末回复。此外，不要发送垃圾 邮件！你可以使用汉语（简或繁）、英语来写邮件。任何情况下，邮件内容须符合欧盟与 德国法律。\nPGP 公钥 我通过邮箱地址 eric@bktus.com 发出的邮件一般会附上这个 PGP 密钥的签名，方便邮件接收者查验身份和内容完整性。你也可以通过这个密钥来加密你 的邮件。\n-----BEGIN PGP PUBLIC KEY BLOCK----- mDMEZq5pTxYJKwYBBAHaRw8BAQdA+YEVawOeMQaAPI4rMfycgbDKA7ebPJ0V2r2J +HPKbz60KnNhdHVybmVyaWMoZm9yIGVtYWlsIG9ubHkpPGVyaWNAYmt0dXMuY29t PoiZBBMWCgBBFiEEbygCyZlmJ+PDmQF8WJGHNplm8CsFAmauaU8CGyMFCQPCZsoF CwkIBwICIgIGFQoJCAsCBBYCAwECHgcCF4AACgkQWJGHNplm8CtkrQEA3/PN483C Ag9+cHR4xZ+GvtLqIDIQsltNlSq77aSWd80A/3lp0WKcd8Ypfc650WgLh9GFZw2N IcYIqjoO7K2tm7IJuDgEZq5pbxIKKwYBBAGXVQEFAQEHQB5fS+kyqWm+7JvVRIL8 7hzP4Zi1WdQ85Tkr7JbfEOxFAwEIB4h+BBgWCgAmFiEEbygCyZlmJ+PDmQF8WJGH Nplm8CsFAmauaW8CGwwFCQPCZvkACgkQWJGHNplm8Cs2fgEAlHPl0ucJlgrxOqy9 c21JXPESJBPBENmgBh6vr0VkF/gA/0Mv8aUVpjy4yceRkCxCVpBT6I/9nPqyHlob NBzq630J =hYH/ -----END PGP PUBLIC KEY BLOCK----- ","permalink":"https://blog.bktus.com/contact-and-verification/","summary":"\u003ch2 id=\"联系我\"\u003e联系我\u003c/h2\u003e\n\u003cp\u003e我通过电子邮件与陌生人通信。若需要联系我，请发送邮件至\n\u003ca href=\"mailto:eric@bktus.com\"\u003eeric@bktus.com\u003c/a\u003e（邮箱服务商位于德国并已使用各种措施加强\n隐私保护）。如果你不想用明文与我通信，可使用页面底部的 PGP 公钥加密你的邮件。我\n一般 2 天内就会回邮件，如果涉及内容较多，我一般会在周末回复。此外，不要发送垃圾\n邮件！你可以使用汉语（简或繁）、英语来写邮件。任何情况下，邮件内容须符合\u003cstrong\u003e欧盟与\n德国\u003c/strong\u003e法律。\u003c/p\u003e\n\u003ch2 id=\"pgp-公钥\"\u003ePGP 公钥\u003c/h2\u003e\n\u003cp\u003e我通过邮箱地址 \u003ca href=\"mailto:eric@bktus.com\"\u003eeric@bktus.com\u003c/a\u003e 发出的邮件一般会附上这个\nPGP 密钥的签名，方便邮件接收者查验身份和内容完整性。你也可以通过这个密钥来加密你\n的邮件。\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmDMEZq5pTxYJKwYBBAHaRw8BAQdA+YEVawOeMQaAPI4rMfycgbDKA7ebPJ0V2r2J\n+HPKbz60KnNhdHVybmVyaWMoZm9yIGVtYWlsIG9ubHkpPGVyaWNAYmt0dXMuY29t\nPoiZBBMWCgBBFiEEbygCyZlmJ+PDmQF8WJGHNplm8CsFAmauaU8CGyMFCQPCZsoF\nCwkIBwICIgIGFQoJCAsCBBYCAwECHgcCF4AACgkQWJGHNplm8CtkrQEA3/PN483C\nAg9+cHR4xZ+GvtLqIDIQsltNlSq77aSWd80A/3lp0WKcd8Ypfc650WgLh9GFZw2N\nIcYIqjoO7K2tm7IJuDgEZq5pbxIKKwYBBAGXVQEFAQEHQB5fS+kyqWm+7JvVRIL8\n7hzP4Zi1WdQ85Tkr7JbfEOxFAwEIB4h+BBgWCgAmFiEEbygCyZlmJ+PDmQF8WJGH\nNplm8CsFAmauaW8CGwwFCQPCZvkACgkQWJGHNplm8Cs2fgEAlHPl0ucJlgrxOqy9\nc21JXPESJBPBENmgBh6vr0VkF/gA/0Mv8aUVpjy4yceRkCxCVpBT6I/9nPqyHlob\nNBzq630J\n=hYH/\n-----END PGP PUBLIC KEY BLOCK-----\n\u003c/code\u003e\u003c/pre\u003e","title":"联系与验证"},{"content":"介绍 最近在研究有关 UNIX 的相关理论知识，在此记录相关重要概念备忘。\n概述 伪终端是指，对于一个应用程序而言，它看似一个终端，但事实上它并不是一个真正的终 端。 通常一个进程打开伪终端主设备，调用 fork。子进程建立一个新的会话，打开一个相 应的伪终端从设备，将其文件描述符复制到标准输入输出错误，然后调用 exec。伪终端从 设备称为子进程的控制终端。 看起来像一个双向管道，从设备上的终端行规程使得我 们拥有普通管道没有的其他处理能力。\n伪终端的典型用途 网络登录服务器 最典型的例子是 telnetd 和 rlogind 服务器。 在 rlogind 服务器和登录 shell 之间有 两个 exec 调用，这是因为 login 程序通常在两个 exec 之间检验用户是否合法。\n窗口系统终端模拟 终端模拟器最为 shell 和窗口管理器之间的媒介。每个 shell 在自己的窗口中执行。 shell 将自己的标准输入、标准输出、标准错误连接到 PTY 的从设备端。\nscript 程序 script 程序将终端会话期间的所有输入和输出信息复制到一个文件中。 使用 script 的不 足是必须处理文件中的控制字符。\nexpect 程序 在非交互模式中驱动交互模式运行。\n运行协同进程 当通过管道与协同进程通信时，标准 I/O 库会完全缓冲标准输入和标准输出，从而引起死 锁。 现在协同进程的标准输入和标准输出就像终端设备一样，所以标准 I/O 库会将这两个 流设置为行缓冲。\n观看长时间运行程序的输出 由于重定向到文件时，标准 I/O 库将完全缓冲它的标准输出。\n打开伪终端设备 PTY 表现得就像物理终端设备一样，因此应用程序就无需在意它们在使用的是何种设备。 在伪设备可用之前，它的权限必须设置，以便应用程序可以访问它。\n","permalink":"https://blog.bktus.com/archives/nwkiqz/","summary":"\u003ch2 id=\"介绍\"\u003e介绍\u003c/h2\u003e\n\u003cp\u003e最近在研究有关 UNIX 的相关理论知识，在此记录相关重要概念备忘。\u003c/p\u003e\n\u003ch2 id=\"概述\"\u003e概述\u003c/h2\u003e\n\u003cp\u003e伪终端是指，对于一个应用程序而言，它看似一个终端，但事实上它并不是一个真正的终\n端。 通常一个进程打开伪终端主设备，调用 fork。子进程建立一个新的会话，打开一个相\n应的伪终端从设备，将其文件描述符复制到标准输入输出错误，然后调用 exec。伪终端从\n设备称为子进程的控制终端。 看起来像一个双向管道，从设备上的\u003cstrong\u003e终端行规程\u003c/strong\u003e使得我\n们拥有普通管道没有的其他处理能力。\u003c/p\u003e\n\u003ch3 id=\"伪终端的典型用途\"\u003e伪终端的典型用途\u003c/h3\u003e\n\u003ch4 id=\"网络登录服务器\"\u003e网络登录服务器\u003c/h4\u003e\n\u003cp\u003e最典型的例子是 telnetd 和 rlogind 服务器。 在 rlogind 服务器和登录 shell 之间有\n两个 exec 调用，这是因为 login 程序通常在两个 exec 之间检验用户是否合法。\u003c/p\u003e\n\u003ch4 id=\"窗口系统终端模拟\"\u003e窗口系统终端模拟\u003c/h4\u003e\n\u003cp\u003e终端模拟器最为 shell 和窗口管理器之间的媒介。每个 shell 在自己的窗口中执行。\nshell 将自己的标准输入、标准输出、标准错误连接到 PTY 的从设备端。\u003c/p\u003e\n\u003ch4 id=\"script-程序\"\u003escript 程序\u003c/h4\u003e\n\u003cp\u003escript 程序将终端会话期间的所有输入和输出信息复制到一个文件中。 使用 script 的不\n足是必须处理文件中的控制字符。\u003c/p\u003e\n\u003ch4 id=\"expect-程序\"\u003eexpect 程序\u003c/h4\u003e\n\u003cp\u003e在非交互模式中驱动交互模式运行。\u003c/p\u003e\n\u003ch4 id=\"运行协同进程\"\u003e运行协同进程\u003c/h4\u003e\n\u003cp\u003e当通过管道与协同进程通信时，标准 I/O 库会完全缓冲标准输入和标准输出，从而引起死\n锁。 现在协同进程的标准输入和标准输出就像终端设备一样，所以标准 I/O 库会将这两个\n流设置为行缓冲。\u003c/p\u003e\n\u003ch4 id=\"观看长时间运行程序的输出\"\u003e观看长时间运行程序的输出\u003c/h4\u003e\n\u003cp\u003e由于重定向到文件时，标准 I/O 库将完全缓冲它的标准输出。\u003c/p\u003e\n\u003ch3 id=\"打开伪终端设备\"\u003e打开伪终端设备\u003c/h3\u003e\n\u003cp\u003ePTY 表现得就像物理终端设备一样，因此应用程序就无需在意它们在使用的是何种设备。\n在伪设备可用之前，它的权限必须设置，以便应用程序可以访问它。\u003c/p\u003e","title":"UNIX伪终端"},{"content":"介绍 我阅读并且学习了有关于 UNIX 系统终端 I/O 相关的内容。在此记录一些比较关键的概 念。\n引论 终端 I/O 十分复杂，原因之一是它应用于许多事物。\n综述 终端 I/O 的两种工作模式：规范模式输入处理（默认）、非规范模式输入处理。\n规范模式下，对于每一个读请求，终端驱动程序最多返回一行。\n可以认为终端设备是由内核中的终端驱动程序控制的，每一个终端设备都有一个输入队列和 一个输出队列。\n开启回显功能时，在输入队列和输出队列之间有一个隐含连接。\n输入队列长度是有限值。\n输出队列虽然也有限，但是程序并不能获取这个值。在输出队列要填满时，内核会直接将写 进程休眠。\n大多是 UNIX 系统在一个称为终端行规程的模块中进行全部的规范处理。该模块在内核通用 读写函数和实际设备驱动程序之间。\n所有可以检测和更改的终端设备特性都包含在 termios 结构中。\n使用 tcsetattr 和 tcgetattr 可以设置终端选项进而控制终端。\n特殊输入字符 POSIX 定义了 11 个在输入时要特殊处理的字符。在这 11 个特殊字符中，9 个字符的值可 以任意更改。\n为了更改特殊字符，需要修改 termios 结构中的 c_cc 数组的相应项。\nPOSIX 允许禁止使用这些字符。\n终端标识 在大多数 UNIX 系统版本中，控制终端名字一直是/dev/tty。\n规范模式与非规范模式 规范模式：发送一个请求，当一行已经输入以后，终端程序立即返回。以下几个条件造成读 返回：\n所请求的字节数已经读到时，无需读一个完整的行。 当读到一个行界定符号 捕捉到信号，并且该函数不再自动重启 非规范模式：时输入数据不装配成行，不处理特殊字符。当读取指定量的数据后，或者超过 指定量的时间后，通知系统返回。该模式下没有关闭对信号的处理，用户始终可以键入一个 触发终端产生信号的字符。\n终端窗口大小 大多数 UNIX 系统都提供了一种跟踪当前终端窗口大小的方法，当窗口大小变化的时候，使 内核通知前台进程组。内核未每个终端和伪终端都维护了一个 winsize 结构。\n","permalink":"https://blog.bktus.com/archives/0flvi2/","summary":"\u003ch2 id=\"介绍\"\u003e介绍\u003c/h2\u003e\n\u003cp\u003e我阅读并且学习了有关于 UNIX 系统终端 I/O 相关的内容。在此记录一些比较关键的概\n念。\u003c/p\u003e\n\u003ch2 id=\"引论\"\u003e引论\u003c/h2\u003e\n\u003cp\u003e终端 I/O 十分复杂，原因之一是它应用于许多事物。\u003c/p\u003e\n\u003ch2 id=\"综述\"\u003e综述\u003c/h2\u003e\n\u003cp\u003e终端 I/O 的两种工作模式：规范模式输入处理（默认）、非规范模式输入处理。\u003c/p\u003e\n\u003cp\u003e规范模式下，对于每一个读请求，终端驱动程序最多返回一行。\u003c/p\u003e\n\u003cp\u003e可以认为终端设备是由内核中的终端驱动程序控制的，每一个终端设备都有一个输入队列和\n一个输出队列。\u003c/p\u003e\n\u003cp\u003e开启回显功能时，在输入队列和输出队列之间有一个隐含连接。\u003c/p\u003e\n\u003cp\u003e输入队列长度是有限值。\u003c/p\u003e\n\u003cp\u003e输出队列虽然也有限，但是程序并不能获取这个值。在输出队列要填满时，内核会直接将写\n进程休眠。\u003c/p\u003e\n\u003cp\u003e大多是 UNIX 系统在一个称为终端行规程的模块中进行全部的规范处理。该模块在内核通用\n读写函数和实际设备驱动程序之间。\u003c/p\u003e\n\u003cp\u003e所有可以检测和更改的终端设备特性都包含在 termios 结构中。\u003c/p\u003e\n\u003cp\u003e使用 tcsetattr 和 tcgetattr 可以设置终端选项进而控制终端。\u003c/p\u003e\n\u003ch2 id=\"特殊输入字符\"\u003e特殊输入字符\u003c/h2\u003e\n\u003cp\u003ePOSIX 定义了 11 个在输入时要特殊处理的字符。在这 11 个特殊字符中，9 个字符的值可\n以任意更改。\u003c/p\u003e\n\u003cp\u003e为了更改特殊字符，需要修改 termios 结构中的 c_cc 数组的相应项。\u003c/p\u003e\n\u003cp\u003ePOSIX 允许禁止使用这些字符。\u003c/p\u003e\n\u003ch2 id=\"终端标识\"\u003e终端\u003cstrong\u003e标识\u003c/strong\u003e\u003c/h2\u003e\n\u003cp\u003e在大多数 UNIX 系统版本中，控制终端名字一直是/dev/tty。\u003c/p\u003e\n\u003ch2 id=\"规范模式与非规范模式\"\u003e规范模式\u003cstrong\u003e与非规范模式\u003c/strong\u003e\u003c/h2\u003e\n\u003cp\u003e规范模式：发送一个请求，当一行已经输入以后，终端程序立即返回。以下几个条件造成读\n返回：\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e所请求的字节数已经读到时，无需读一个完整的行。\u003c/li\u003e\n\u003cli\u003e当读到一个行界定符号\u003c/li\u003e\n\u003cli\u003e捕捉到信号，并且该函数不再自动重启\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e非规范模式：时输入数据不装配成行，不处理特殊字符。当读取指定量的数据后，或者超过\n指定量的时间后，通知系统返回。该模式下没有关闭对信号的处理，用户始终可以键入一个\n触发终端产生信号的字符。\u003c/p\u003e\n\u003ch2 id=\"终端窗口大小\"\u003e终端窗口大小\u003c/h2\u003e\n\u003cp\u003e大多数 UNIX 系统都提供了一种跟踪当前终端窗口大小的方法，当窗口大小变化的时候，使\n内核通知前台进程组。内核未每个终端和伪终端都维护了一个 winsize 结构。\u003c/p\u003e","title":"UNIX 终端I/O"},{"content":"介绍 手上有一张安装了 Openwrt 的 8G 的 TF 卡，根分区的容量已经不太能够满足我的使用需 求了。所以开始着手扩大根分区的大小。根分区的所使用的的文件系统格式是 Ext4。首 先，尝试使用 DiskGenius 等 Windows 下的工具进行扩容，很遗憾都不可以。于是准备使 用 Gparted 进行扩容。由于该工具需要在 Linux 环境下运行，我不太想安装为此安装一个 Linux 发行版虚拟机所以使用 Gparted 的轻量 ISO 镜像配合 Virtual Box 虚拟机进行使 用。\n准备工作 首先下载安装 VritualBox 虚拟机和 VM VirtualBox Extension Pack。VirtualBox 并默认 不支持 USB 外设操作，所以要安装而外后者来为 VirtualBox 添加 USB2.0、USB3.0 等外 设支持。 VirtualBox 下载地址 VM VirtualBox Extension Pack 以上两者下 载完后，先安装 VirtualBox。 然后安装 VM VirtualBox Extension Pack，具体在首选项 界面扩展栏目中安装。 下载 GParted Live CD/USB/HD/PXE Bootable Image，下载链 接。\n步骤 创建虚拟机，分配单核、内存 512MB 即可，可以不创建虚拟硬盘。 在虚拟机设置界面中， 在 USB 设备栏目中启用 USB 控制器，根据需要选择 USB2.0 或者 USB3.0。 在储存栏目光 驱下，选择下载好的 ISO 镜像，启动虚拟机。 在第一个引导菜单中选择第一项。 而 后，按步骤一次选择或者输入Dont touch keymap， 26（简体中文），0（启动 X Window 系统）。 然后就能够进入图形界面了，点击桌面上的 Gparted 工具镜像分区操作。 分 区后，记得点击应用。\n","permalink":"https://blog.bktus.com/archives/b777y7/","summary":"\u003ch2 id=\"介绍\"\u003e介绍\u003c/h2\u003e\n\u003cp\u003e手上有一张安装了 Openwrt 的 8G 的 TF 卡，根分区的容量已经不太能够满足我的使用需\n求了。所以开始着手扩大根分区的大小。根分区的所使用的的文件系统格式是 Ext4。首\n先，尝试使用 DiskGenius 等 Windows 下的工具进行扩容，很遗憾都不可以。于是准备使\n用 Gparted 进行扩容。由于该工具需要在 Linux 环境下运行，我不太想安装为此安装一个\nLinux 发行版虚拟机所以使用 Gparted 的轻量 ISO 镜像配合 Virtual Box 虚拟机进行使\n用。\u003c/p\u003e\n\u003ch2 id=\"准备工作\"\u003e准备工作\u003c/h2\u003e\n\u003cp\u003e首先下载安装 VritualBox 虚拟机和 VM VirtualBox Extension Pack。VirtualBox 并默认\n不支持 USB 外设操作，所以要安装而外后者来为 VirtualBox 添加 USB2.0、USB3.0 等外\n设支持。 \u003ca href=\"https://www.virtualbox.org/wiki/Downloads\"\u003eVirtualBox 下载地址\u003c/a\u003e \u003ca href=\"https://www.virtualbox.org/wiki/Downloads\"\u003eVM\nVirtualBox Extension Pack\u003c/a\u003e 以上两者下\n载完后，先安装 VirtualBox。 然后安装 VM VirtualBox Extension Pack，具体在首选项\n界面扩展栏目中安装。 下载 GParted Live CD/USB/HD/PXE Bootable Image，\u003ca href=\"https://gparted.org/download.php\"\u003e下载链\n接\u003c/a\u003e。\u003c/p\u003e\n\u003ch2 id=\"步骤\"\u003e步骤\u003c/h2\u003e\n\u003cp\u003e创建虚拟机，分配单核、内存 512MB 即可，可以不创建虚拟硬盘。 在虚拟机设置界面中，\n在 USB 设备栏目中启用 USB 控制器，根据需要选择 USB2.0 或者 USB3.0。 在储存栏目光\n驱下，选择下载好的 ISO 镜像，启动虚拟机。 在第一个引导菜单中选择\u003cstrong\u003e第一项\u003c/strong\u003e。 而\n后，按步骤一次选择或者输入\u003cstrong\u003eDont touch keymap， 26（简体中文），0（启动 X Window\n系统）\u003c/strong\u003e。 然后就能够进入图形界面了，点击桌面上的 Gparted 工具镜像分区操作。 分\n区后，记得点击应用。\u003c/p\u003e","title":"通过Gparted Live ISO可引导镜像调整硬盘分区大小"},{"content":"YouCompleteMe 介绍 如果安装完YouCompleteMe并配置好后 （本站有过程记录，点击这里查看），就可以 使用单文件来检测代码提示效果了。但是，当 S\u0026amp;E 打开他的 C/C++工程时，却发现代码提 示、跳转等功能不能正常使用。在查阅文档后，S\u0026amp;E 发现原来是 YouCompleteMe 相关的索 引数据库没有建立，相关的编译选项并不正确，所以 YouCompleteMe 所使用的的 clangd 就不能将多个源文件联系起来。\n建立 C/C++ VIM 代码提示索引的方法 如果是使用 CMake 建立工程，则可以在 CMakeList.txt 中加入\nset( CMAKE\\_EXPORT\\_COMPILE\\_COMMANDS ON ) 或者在命令行 cmake 后加上-DCMAKE_EXPORT_COMPILE_COMMANDS=ON也行\n建立索引的原理 YouCompleteMe 读取由构建系统 CMake 生成的编译数据库（通常名字叫做 compile_commands.json），这样就可以完成对于工程文件的索引。而编译数据库包含项目 中每个编译单元的编译器调用。YouCompleteMe 会寻找打开文件所在目录中的 compile_commands.json，如果没找到会递归地向上查找这个文件。如果在它找到 .ycm_extra_conf.py 之前，找到了 compile_commands.json，那么它就会停止搜索，然 后，让 clangd 处理接管并处理该文件中含有的标志。 在完成上述这些操作 后，YouCompleteMe 的相关功能就可以正常使用了。\n","permalink":"https://blog.bktus.com/archives/st6w66/","summary":"\u003ch2 id=\"youcompleteme-介绍\"\u003eYouCompleteMe 介绍\u003c/h2\u003e\n\u003cp\u003e如果安装完\u003ca href=\"https://github.com/ycm-core/YouCompleteMe\"\u003eYouCompleteMe\u003c/a\u003e并配置好后\n（本站有过程记录，\u003ca href=\"https://blog.bktus.com/archives/2658\"\u003e点击这里查看\u003c/a\u003e），就可以\n使用单文件来检测代码提示效果了。但是，当 S\u0026amp;E 打开他的 C/C++工程时，却发现代码提\n示、跳转等功能不能正常使用。在查阅文档后，S\u0026amp;E 发现原来是 YouCompleteMe 相关的索\n引数据库没有建立，相关的编译选项并不正确，所以 YouCompleteMe 所使用的的 clangd\n就不能将多个源文件联系起来。\u003c/p\u003e\n\u003ch2 id=\"建立-cc-vim-代码提示索引的方法\"\u003e建立 C/C++ VIM 代码提示索引的方法\u003c/h2\u003e\n\u003cp\u003e如果是使用 CMake 建立工程，则可以在 CMakeList.txt 中加入\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003eset( CMAKE\\_EXPORT\\_COMPILE\\_COMMANDS ON )\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003e或者在命令行 cmake 后加上\u003ccode\u003e-DCMAKE_EXPORT_COMPILE_COMMANDS=ON\u003c/code\u003e也行\u003c/p\u003e\n\u003ch2 id=\"建立索引的原理\"\u003e建立索引的原理\u003c/h2\u003e\n\u003cp\u003eYouCompleteMe 读取由构建系统 CMake 生成的编译数据库（通常名字叫做\ncompile_commands.json），这样就可以完成对于工程文件的索引。而编译数据库包含项目\n中每个编译单元的编译器调用。YouCompleteMe 会寻找打开文件所在目录中的\ncompile_commands.json，如果没找到会递归地向上查找这个文件。如果在它找到\n.ycm_extra_conf.py 之前，找到了 compile_commands.json，那么它就会停止搜索，然\n后，让 clangd 处理接管并处理该文件中含有的标志。 在完成上述这些操作\n后，YouCompleteMe 的相关功能就可以正常使用了。\u003c/p\u003e","title":"YouCompleteMe C/C++ VIM  CMake 工程 代码提示不可用问题"},{"content":"介绍 对于在 Vim 下的 C/C++程序编写，如果有代码提示插件会大大提高编写效率。大型 IDE 用 的多了，刚回归 Vim 的 S\u0026amp;E 比较依赖这个。正好 YouCompleteMe 能够满足他的相关需 求。索性把安装与配置的过程记录在这里，以供下次回忆使用。 除了 C/C++，YouCompleteMe 支持 Java、Go、C#、Objective-C、CUDA 等，可以说是比较强大 了。但是话说，写 Java 为什么要用 Vim 呢？IDEA 貌似更好。 使用后发现这插件还支持 代码跳转、引用查找、修改函数名和格式调整等操作，挺方便。 Giuhub 仓库地 址：https://github.com/ycm-core/YouCompleteMe\n前置条件 安装最新的 YouCompleteMe 插件需要满足一些条件\nVim 8.1 以上 并且启用了 Python3 扩展支持（本站有过程记录，点击这里查 看） GCC 8 以上 或者 Clang 7 以上 Python3.6 以上编译的时候有\u0026ndash;enable-shared 选项（一般包管理器安装都带有） 对于 Debian 10， 通过包管理器安装的 Gcc 版本为 8.3.0。 然后可以通过以下命令查看 编译器版本\n% cc -v 对于 python3 的版本，使用以下命令查看\n% python3 --version 对于 Vim 版本，使用执行以下命令查看\n% vim --version 检查完以上依赖后，也执行一下以下命令确认安装相关依赖\n% sudo apt install build-essential cmake vim-nox python3-dev 安装 先确认用过了 Vim 插件管理器安装了 YouCompleteMe，推荐使用 Vundle。 确认安装了 YouCompleteMe 后，进行下面的步骤 如果你想要安装所有的功能，包括 Java（JDK 8），Go、C#等代码提示功能，可以直接执行以下命令安装\n% cd ~/.vim/bundle/YouCompleteMe % python3 install.py --clangd-completer 如果你只想使用 C/C++代码提示功能，则执行以下命令\n% cd ~/.vim/bundle/YouCompleteMe % python3 install.py --clangd-completer ","permalink":"https://blog.bktus.com/archives/xocjog/","summary":"\u003ch2 id=\"介绍\"\u003e介绍\u003c/h2\u003e\n\u003cp\u003e对于在 Vim 下的 C/C++程序编写，如果有代码提示插件会大大提高编写效率。大型 IDE 用\n的多了，刚回归 Vim 的 S\u0026amp;E 比较依赖这个。正好 YouCompleteMe 能够满足他的相关需\n求。索性把安装与配置的过程记录在这里，以供下次回忆使用。 除了\nC/C++，YouCompleteMe 支持 Java、Go、C#、Objective-C、CUDA 等，可以说是比较强大\n了。但是话说，写 Java 为什么要用 Vim 呢？IDEA 貌似更好。 使用后发现这插件还支持\n代码跳转、引用查找、修改函数名和格式调整等操作，挺方便。 Giuhub 仓库地\n址：\u003ca href=\"https://github.com/ycm-core/YouCompleteMe\"\u003ehttps://github.com/ycm-core/YouCompleteMe\u003c/a\u003e\u003c/p\u003e\n\u003ch2 id=\"前置条件\"\u003e前置条件\u003c/h2\u003e\n\u003cp\u003e安装最新的 YouCompleteMe 插件需要满足一些条件\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eVim 8.1 以上 并且启用了 Python3 扩展支持（本站有过程记录，\u003ca href=\"https://bktus.com/archives/2646\"\u003e点击这里查\n看\u003c/a\u003e）\u003c/li\u003e\n\u003cli\u003eGCC 8 以上 或者 Clang 7 以上\u003c/li\u003e\n\u003cli\u003ePython3.6 以上编译的时候有\u0026ndash;enable-shared 选项（一般包管理器安装都带有）\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e对于 Debian 10， 通过包管理器安装的 Gcc 版本为 8.3.0。 然后可以通过以下命令查看\n编译器版本\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e% cc -v\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003e对于 python3 的版本，使用以下命令查看\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e% python3 --version\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003e对于 Vim 版本，使用执行以下命令查看\u003c/p\u003e","title":"Vim 代码提示插件 YouCompleteMe 安装与配置"},{"content":"Vim 主题介绍 我经常使用 vim 为在 Linux 下的主要的代码编辑器。每当 SSH 进入一个新的开发系统环 境，为了编写方便，他总是需要手动安装 Vim 并配置各类常用插件。spf13-vim.sh 是一个 使用于 Vim 的插件和相关资源的发行版。一条命令，即可以自动安装常用的配套的插件环 境。 Github 仓库地 址：https://github.com/spf13/spf13-vim\n心得 我感觉这个就好像快速安装了一个以 Vim 为核心的 IDE（可以类比 Vscode）。然 后，spf13-vim.sh 产生一个比较规范的 vim 配置文件层次结构，方便后来再添加其他插 件，也避免无规划地手动添加插件产生的杂乱的配置文件。而且，把相关插件的优化和配置 工作丢给它后，省心了不少。\n安装 前置条件：\ngit curl vim 7.3 以上 本站有安装 Vim 8.2 的相关过程记录：点击这里查 看 对于 Debian 10 可以简单地执行以下命令安装 git 和 curl\n% sudo apt install git curl 然后对于 Unix/Linux、Mac OS 等，执行以下命令一键安装 spf13-vim.sh\n% curl https://j.mp/spf13-vim3 -L \u0026gt; spf13-vim.sh \u0026amp;\u0026amp; sh spf13-vim.sh （对于 Windows 系统，请参考 Github 仓库中的 ReadME.md）\n更新方法 可以通过执行以下命令一键更新\n% curl https://j.mp/spf13-vim3 -L -o - | sh 个性化定制 spf13-vim.sh 是一个配套的常用插件发行版，并没有安装 YouCompleteMe 这类的插件，这 就需要我后期加上去。可以在当前用户主目录下，执行以下命令添加一个个性化配置文件:\n% touch ~/.vimrc.local 然后，可以编辑这个文件加入自己的配置。 对于需要预先执行的一些配置，则可以执行以 下命令下入对应的配置文件\n% ~/.vimrc.before.local 添加插件 比如说要添加 spf13/vim-colors 这款插件，按照 ReadME.md 中的实例是这样的\n% echo Bundle \\\\\u0026#39;spf13/vim-colors\\\\\u0026#39; \u0026gt;\u0026gt; ~/.vimrc.bundles.local 移除插件 移除插件只要在上面的命令中的 Bundle 前面加一个 Un 即可\n% echo UnBundle \\\\\u0026#39;AutoClose\\\\\u0026#39; \u0026gt;\u0026gt; ~/.vimrc.bundles.local 常用插件的使用 以下介绍一些常用插件的使用操作方法，这里就不写全了，根据我的使用慢慢加上。\nNERDTree 通过 Crtl-E 调出菜单\n","permalink":"https://blog.bktus.com/archives/1avhtp/","summary":"\u003ch2 id=\"vim-主题介绍\"\u003eVim 主题介绍\u003c/h2\u003e\n\u003cp\u003e我经常使用 vim 为在 Linux 下的主要的代码编辑器。每当 SSH 进入一个新的开发系统环\n境，为了编写方便，他总是需要手动安装 Vim 并配置各类常用插件。spf13-vim.sh 是一个\n使用于 Vim 的插件和相关资源的发行版。一条命令，即可以自动安装常用的配套的插件环\n境。 Github 仓库地\n址：\u003ca href=\"https://github.com/spf13/spf13-vim\"\u003ehttps://github.com/spf13/spf13-vim\u003c/a\u003e\u003c/p\u003e\n\u003ch2 id=\"心得\"\u003e心得\u003c/h2\u003e\n\u003cp\u003e我感觉这个就好像快速安装了一个以 Vim 为核心的 IDE（可以类比 Vscode）。然\n后，spf13-vim.sh 产生一个比较规范的 vim 配置文件层次结构，方便后来再添加其他插\n件，也避免无规划地手动添加插件产生的杂乱的配置文件。而且，把相关插件的优化和配置\n工作丢给它后，省心了不少。\u003c/p\u003e\n\u003ch2 id=\"安装\"\u003e安装\u003c/h2\u003e\n\u003cp\u003e前置条件：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003egit\u003c/li\u003e\n\u003cli\u003ecurl\u003c/li\u003e\n\u003cli\u003evim 7.3 以上\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e本站有安装 Vim 8.2 的相关过程记录：\u003ca href=\"https://bktus.com/archives/2646\"\u003e点击这里查\n看\u003c/a\u003e 对于 Debian 10 可以简单地执行以下命令安装\ngit 和 curl\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e% sudo apt install git curl\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003e然后对于 Unix/Linux、Mac OS 等，执行以下命令一键安装 spf13-vim.sh\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e% curl https://j.mp/spf13-vim3 -L \u0026gt; spf13-vim.sh \u0026amp;\u0026amp; sh spf13-vim.sh\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003e（对于 Windows 系统，请参考 Github 仓库中的 ReadME.md）\u003c/p\u003e","title":"Vim主题 spf13-vim.sh 的安装与使用"},{"content":"介绍 为了安装 YouCompleteMe 这类插件，需要将 Vim 升级至比较高的版本并且启用 Python3扩 展。所以，这里提供的是通过源码安装 Vim 最新版本的过程记录。 这里使用到的 Vim源代 码仓库地址：https://github.com/vim/vim/\n编译安装默认版本 在编译安装之前先要安装相关的工具以及依赖\n% sudo apt install git make clang libpython3-dev 选择适当的目录执行克隆命令\n% git clone https://github.com/vim/vim.git 克隆成功后会出现 vim 目录，进入该目录下的 src 目录\n% cd vim/src 执行编译（为了加快速度可以替换执行% make -j4来并行编译）\n% make 执行测试\n% make test 默认安装到/usr/local 目录下\n% sudo make install 开启Python3扩展支持 在上述操作完成后，打开该目录下的Makefile文件，找到\u0026quot;CONF_OPT_PYTHON3 = --enable-python3interp\u0026quot;并取消注释该行，保存。 执行命令更新编译参数make reconfig\n","permalink":"https://blog.bktus.com/archives/4vkp8w/","summary":"\u003ch2 id=\"介绍\"\u003e介绍\u003c/h2\u003e\n\u003cp\u003e为了安装 YouCompleteMe 这类插件，需要将 Vim 升级至比较高的版本并且启用 Python3扩\n展。所以，这里提供的是通过源码安装 Vim 最新版本的过程记录。 这里使用到的 Vim源代\n码仓库地址：\u003ca href=\"https://github.com/vim/vim/\"\u003ehttps://github.com/vim/vim/\u003c/a\u003e\u003c/p\u003e\n\u003ch2 id=\"编译安装默认版本\"\u003e编译安装默认版本\u003c/h2\u003e\n\u003cp\u003e在编译安装之前先要安装相关的工具以及依赖\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e% sudo apt install git make clang libpython3-dev\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003e选择适当的目录执行克隆命令\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e% git clone https://github.com/vim/vim.git\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003e克隆成功后会出现 vim 目录，进入该目录下的 src 目录\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e% cd vim/src\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003e执行编译（为了加快速度可以替换执行\u003ccode\u003e% make -j4\u003c/code\u003e来并行编译）\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e% make\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003e执行测试\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e% make test\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003e默认安装到/usr/local 目录下\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e% sudo make install\n\u003c/code\u003e\u003c/pre\u003e\u003ch2 id=\"开启python3扩展支持\"\u003e开启Python3扩展支持\u003c/h2\u003e\n\u003cp\u003e在上述操作完成后，打开该目录下的Makefile文件，找到\u003ccode\u003e\u0026quot;CONF_OPT_PYTHON3 = --enable-python3interp\u0026quot;\u003c/code\u003e并取消注释该行，保存。 执行命令更新编译参数\u003ccode\u003emake reconfig\u003c/code\u003e\u003c/p\u003e","title":"Vim 8.2 源码编译安装 添加Python3扩展"},{"content":"非营利性个人博客 本网站是一个非营利性个人博客，专注于分享知识和信息，与商业或政治活动无关。我致力 于为访问者提供有价值的内容，以促进学习和交流。\n版权所有 本站所有内容，包括文字、图片、视频、链接和代码等，均为本站所有人Saturneric的财 产。Saturneric授予访问者浏览这些内容的权利，以便访问者能够从中获取知识和启发。除 非另有明确授权，否则Saturneric保留对上述内容的一切权利。\n使用条款 访问本站及其内容的访问者应该明确承认并接受以下条款：我不对使用本站提供的方法、技 术或其他内容可能导致的任何直接或间接损失负责。访问者在使用本站提供的任何信息或资 源时应自行承担风险。如果您不同意本声明的任何条款，请立即停止访问本站。\n转载授权和法律责任 如果您希望转载本站的内容，您需要事先获得我的书面授权，并在转载内容末尾明确标注本 站的来源网址。未经授权擅自转载本站内容的行为将承担法律责任，我保留追究法律责任的 权利。我鼓励合法合规的转载行为，但同时也希望转载者能够尊重原创作者的劳动成果。\n侵权投诉 如果任何单位或个人认为本站的内容侵犯了其合法权益，应及时向我提交书面反馈，并提供 有效的身份证明、权属证明以及详细的侵权情况证明。我将尽快处理侵权投诉，并采取适当 的措施以移除涉嫌侵权的内容。\n法律适用 本站点的服务器和数据都位于德国境内，所以本站点遵守德国法律和欧洲联盟 法律。对于本声明未涵盖的事项，我将遵循适用的法律规定。如果本声明的内容与上述法律 存在任何冲突，我将以上述法律的规定为准。我将尽最大努力确保本站内容的合法性和合规 性，但对于访问者在其他法域下的访问或使用行为，我不承担任何法律责任。\n免责声明 本站的内容仅供参考和学习，不构成任何形式的专业意见或建议。访问者在使用本站提供的 信息时应自行判断和决策，我不对任何因使用本站信息而导致的任何损失负责。对于本站链 接到的其他网站或第三方提供的内容，我不对其准确性、完整性或可靠性进行担保。\n","permalink":"https://blog.bktus.com/site-declaration/","summary":"\u003ch2 id=\"非营利性个人博客\"\u003e非营利性个人博客\u003c/h2\u003e\n\u003cp\u003e本网站是一个非营利性个人博客，专注于分享知识和信息，与商业或政治活动无关。我致力\n于为访问者提供有价值的内容，以促进学习和交流。\u003c/p\u003e\n\u003ch2 id=\"版权所有\"\u003e版权所有\u003c/h2\u003e\n\u003cp\u003e本站所有内容，包括文字、图片、视频、链接和代码等，均为本站所有人Saturneric的财\n产。Saturneric授予访问者浏览这些内容的权利，以便访问者能够从中获取知识和启发。除\n非另有明确授权，否则Saturneric保留对上述内容的一切权利。\u003c/p\u003e\n\u003ch2 id=\"使用条款\"\u003e使用条款\u003c/h2\u003e\n\u003cp\u003e访问本站及其内容的访问者应该明确承认并接受以下条款：我不对使用本站提供的方法、技\n术或其他内容可能导致的任何直接或间接损失负责。访问者在使用本站提供的任何信息或资\n源时应自行承担风险。如果您不同意本声明的任何条款，请立即停止访问本站。\u003c/p\u003e\n\u003ch2 id=\"转载授权和法律责任\"\u003e转载授权和法律责任\u003c/h2\u003e\n\u003cp\u003e如果您希望转载本站的内容，您需要事先获得我的书面授权，并在转载内容末尾明确标注本\n站的来源网址。未经授权擅自转载本站内容的行为将承担法律责任，我保留追究法律责任的\n权利。我鼓励合法合规的转载行为，但同时也希望转载者能够尊重原创作者的劳动成果。\u003c/p\u003e\n\u003ch2 id=\"侵权投诉\"\u003e侵权投诉\u003c/h2\u003e\n\u003cp\u003e如果任何单位或个人认为本站的内容侵犯了其合法权益，应及时向我提交书面反馈，并提供\n有效的身份证明、权属证明以及详细的侵权情况证明。我将尽快处理侵权投诉，并采取适当\n的措施以移除涉嫌侵权的内容。\u003c/p\u003e\n\u003ch2 id=\"法律适用\"\u003e法律适用\u003c/h2\u003e\n\u003cp\u003e本站点的服务器和数据都位于\u003cstrong\u003e德国境内\u003c/strong\u003e，所以本站点遵守\u003cstrong\u003e德国法律和欧洲联盟\u003c/strong\u003e\n法律。对于本声明未涵盖的事项，我将遵循适用的法律规定。如果本声明的内容与上述法律\n存在任何冲突，我将以上述法律的规定为准。我将尽最大努力确保本站内容的合法性和合规\n性，但对于访问者在其他法域下的访问或使用行为，我不承担任何法律责任。\u003c/p\u003e\n\u003ch2 id=\"免责声明\"\u003e免责声明\u003c/h2\u003e\n\u003cp\u003e本站的内容仅供参考和学习，不构成任何形式的专业意见或建议。访问者在使用本站提供的\n信息时应自行判断和决策，我不对任何因使用本站信息而导致的任何损失负责。对于本站链\n接到的其他网站或第三方提供的内容，我不对其准确性、完整性或可靠性进行担保。\u003c/p\u003e","title":"站点声明"},{"content":"所知，虽然是对于一个人的所知。但是对于处世是有益处的。所知愈多，所思就会越深刻。 但是这种知不是一味的去知。要知道世界上没有绝对正确的知，没有全部清楚的知，没有绝 对权威的知，没有处于本真的知。有人要是说他有真知，这是不可能的。神要是说他有真 知，我不知道它到底有没有。如果一味接受外来的知，这些知就是死的知。死的知是没有去 进行析的过程的知。没有我用我所知去析这些知，转化为我之知而被我所用。我不这样做。 这种知不是我之知，就不能真正被我灵活运用，我只是知罢了。若如果没办法运用的的话， 就不能将它用于思。如果一味的接受外来的知，我是没有能力全部析这些知的。一味接受外 来的知的人，也是没有析这个过程的，这些知是不能够被他们灵活运用的。我不这样做。能 够析，有所领悟是一件快乐的事。能有所知，有所析，有所知，有所法，行才能够成功。知 之，析之，思之，是知的道理。 所行，成功了。如果我们以为所行之法就是通用的，认为 此法是绝对了，这就错了。成功之法，是根据所知，所思产生的。成功的道路上有的变数是 我所不知道的。变数没有改变，法是畅行的的。如果改变的话，法就不能畅通，就不能让行 成功。所以说古无定知，行无定法。我得到了法，我按照它来行，它让事物发生了改变。但 是事物的改变确实是它本源发生了改变，但是我只能感知到我所能够感知的变化。简单的变 化可以感知，但是其复杂的变化是难以感知的。所以，行要谨慎，不骄傲。谨慎的人是有很 大的灵活性的。使用法，骄傲的人是不会变通的。但是谨慎的人能够应对多样的变化。谨慎 的人得以使用他们全部所知，全部所思来使用法。不谨慎的人只能看到事物的表象武断地判 断。这样会有所损失的。所以谨慎而行，不轻率，成功的方法。谨慎的人可以应对万变，能 够运用上他们全部的所知所思，容易让行法的过程顺畅。法也在他们使用的过程中畅顺。\n","permalink":"https://blog.bktus.com/archives/0tui6f/","summary":"\u003cp\u003e所知，虽然是对于一个人的所知。但是对于处世是有益处的。所知愈多，所思就会越深刻。\n但是这种知不是一味的去知。要知道世界上没有绝对正确的知，没有全部清楚的知，没有绝\n对权威的知，没有处于本真的知。有人要是说他有真知，这是不可能的。神要是说他有真\n知，我不知道它到底有没有。如果一味接受外来的知，这些知就是死的知。死的知是没有去\n进行析的过程的知。没有我用我所知去析这些知，转化为我之知而被我所用。我不这样做。\n这种知不是我之知，就不能真正被我灵活运用，我只是知罢了。若如果没办法运用的的话，\n就不能将它用于思。如果一味的接受外来的知，我是没有能力全部析这些知的。一味接受外\n来的知的人，也是没有析这个过程的，这些知是不能够被他们灵活运用的。我不这样做。能\n够析，有所领悟是一件快乐的事。能有所知，有所析，有所知，有所法，行才能够成功。知\n之，析之，思之，是知的道理。 所行，成功了。如果我们以为所行之法就是通用的，认为\n此法是绝对了，这就错了。成功之法，是根据所知，所思产生的。成功的道路上有的变数是\n我所不知道的。变数没有改变，法是畅行的的。如果改变的话，法就不能畅通，就不能让行\n成功。所以说古无定知，行无定法。我得到了法，我按照它来行，它让事物发生了改变。但\n是事物的改变确实是它本源发生了改变，但是我只能感知到我所能够感知的变化。简单的变\n化可以感知，但是其复杂的变化是难以感知的。所以，行要谨慎，不骄傲。谨慎的人是有很\n大的灵活性的。使用法，骄傲的人是不会变通的。但是谨慎的人能够应对多样的变化。谨慎\n的人得以使用他们全部所知，全部所思来使用法。不谨慎的人只能看到事物的表象武断地判\n断。这样会有所损失的。所以谨慎而行，不轻率，成功的方法。谨慎的人可以应对万变，能\n够运用上他们全部的所知所思，容易让行法的过程顺畅。法也在他们使用的过程中畅顺。\u003c/p\u003e","title":"知行论（四）"},{"content":"行的过程中，我们一定有目标。但是众人渴求目标迫切想要达到目标，是不容易得到的。应 当处在不求的状态，但心中知道有这个目标。迫切想达到，心里必然渴，这样就不能达到静 的状态，不静就会浮躁，浮躁就会缺失很多东西。这样所知是片面的，所思也是不周全的。 所以，处于静的状态不渴求才是行的方法。心思不全放在目标上，这样知的范围就可以广很 多，像平常一样，这样才可能成功。 行的过程中，要用心。用心，就必然要专心。心不专 一是达不到目标的。心不专一，我所知与所思不能全部发挥出来。怎么心专一，行事前一定 要有所知，有所思，让所思产生法。有所知，还没有行之时心就能专一。有所思，有所法而 行，心就能定。无思则无法，心就会乱。能够聚集心神的心就能专心。心神如果是散开的 话，心就是乱的。什么是心专一，心里没有杂念，没有其他的事。专心的人行的时候才能够 顺畅，才可能成功。\n","permalink":"https://blog.bktus.com/archives/er6eer/","summary":"\u003cp\u003e行的过程中，我们一定有目标。但是众人渴求目标迫切想要达到目标，是不容易得到的。应\n当处在不求的状态，但心中知道有这个目标。迫切想达到，心里必然渴，这样就不能达到静\n的状态，不静就会浮躁，浮躁就会缺失很多东西。这样所知是片面的，所思也是不周全的。\n所以，处于静的状态不渴求才是行的方法。心思不全放在目标上，这样知的范围就可以广很\n多，像平常一样，这样才可能成功。 行的过程中，要用心。用心，就必然要专心。心不专\n一是达不到目标的。心不专一，我所知与所思不能全部发挥出来。怎么心专一，行事前一定\n要有所知，有所思，让所思产生法。有所知，还没有行之时心就能专一。有所思，有所法而\n行，心就能定。无思则无法，心就会乱。能够聚集心神的心就能专心。心神如果是散开的\n话，心就是乱的。什么是心专一，心里没有杂念，没有其他的事。专心的人行的时候才能够\n顺畅，才可能成功。\u003c/p\u003e","title":"知行论（三）"},{"content":"论及行。行，是难做到的。我们认为我们获得了知，我们就能够行。并不是这样的，事物的 变化难以预料，这不是我们可以预先知道的。所以行必须谨慎，用来防范难以预料的变化。 事物的变数多是行难的原因之一。 知行并不是合二为一的。我们认为知了就行，认为行一 定会成功。知道了规律方法后就去行。但我们所知道的规律方法，其运用到的情况都是我们 构想出来的，我们所举得出的例子，所能够想得出的细节，都是源于我们的思。我们所思， 会出现荒谬的地方，不周全的地方。若果就这样行的话，如果遇到我们所思之外的细节，我 们就会慌。如果出现我们所思之外的例子，我们的行就会停止。我们时常行，才是成功的方 法。怎么行，先知然后行。怎么知，行后自然知。 我行的途中，一定会有言论制止我。会 有言论论及我的行的错误之处，我的知的错误之处。如果我就这样听从了，停止了，是不能 成功的。如果我一点都听不进去，一意孤行，也难以成功。该怎么办。他人所论及的，他人 所知的，都是处于他人所感知的，所思，所行而形成的。没有所谓的绝对真知，也没有所谓 的权威之知，更没有什么永恒不变之知。我会观察这些论断的表象，揣测它们的本意，虽然 不能得到它们本源的意思，但是一定会有所收获。所以，我们不妨听之，思之，用行来检 验。如果确实有用，我们就用，但不是盲从。如果不行，就坚持我们的见解。知之，在行之 的时候要时常思之，这是好事。行有了成功的结果，就是行就是成功的。虽然造成成功的结 果的方法可能不适用于所有情况，但对于我自己的角度，它就是成功的。所以，我之行能否 成功，是不能凭借他人所说来预测的。\n","permalink":"https://blog.bktus.com/archives/9h5dq9/","summary":"\u003cp\u003e论及行。行，是难做到的。我们认为我们获得了知，我们就能够行。并不是这样的，事物的\n变化难以预料，这不是我们可以预先知道的。所以行必须谨慎，用来防范难以预料的变化。\n事物的变数多是行难的原因之一。 知行并不是合二为一的。我们认为知了就行，认为行一\n定会成功。知道了规律方法后就去行。但我们所知道的规律方法，其运用到的情况都是我们\n构想出来的，我们所举得出的例子，所能够想得出的细节，都是源于我们的思。我们所思，\n会出现荒谬的地方，不周全的地方。若果就这样行的话，如果遇到我们所思之外的细节，我\n们就会慌。如果出现我们所思之外的例子，我们的行就会停止。我们时常行，才是成功的方\n法。怎么行，先知然后行。怎么知，行后自然知。 我行的途中，一定会有言论制止我。会\n有言论论及我的行的错误之处，我的知的错误之处。如果我就这样听从了，停止了，是不能\n成功的。如果我一点都听不进去，一意孤行，也难以成功。该怎么办。他人所论及的，他人\n所知的，都是处于他人所感知的，所思，所行而形成的。没有所谓的绝对真知，也没有所谓\n的权威之知，更没有什么永恒不变之知。我会观察这些论断的表象，揣测它们的本意，虽然\n不能得到它们本源的意思，但是一定会有所收获。所以，我们不妨听之，思之，用行来检\n验。如果确实有用，我们就用，但不是盲从。如果不行，就坚持我们的见解。知之，在行之\n的时候要时常思之，这是好事。行有了成功的结果，就是行就是成功的。虽然造成成功的结\n果的方法可能不适用于所有情况，但对于我自己的角度，它就是成功的。所以，我之行能否\n成功，是不能凭借他人所说来预测的。\u003c/p\u003e","title":"知行论（二）"},{"content":"出于偶然，我写下了这些文字。由于一些原因，我比较浮躁，而写这些东西能让我静得下心 来。这些文字关于一些理论。这也是一把哲学的武器与防具，集合成理论后就能够变得锋 利，变得坚固。它能够帮助我经受住生活上的挑战。知行论只是其中之一。它源于我的生 活，我读过的书，我的思考。以下进入正文。 论及知，我们用眼睛观察事物，用耳朵听它 的声音，用手去抚摸它感受它，我们就认为我们感知了这个事物。我们用大脑思考这件事， 用心去感知它，我们就认为我们了解了这件事物。但是，我们所看见的，所听到的，所抚摸 到的，都是我们的感官能够感觉到的。我们怎么知道我们的感官感觉到的是事物的本真呢？ 我们感知事物只能够凭借感官，它们感知的事物是怎么样的源于它的构造，哪能不能够存在 一套构造完全不同的感官，它们感觉到的事物的样子与我们的毫不相同呢。这是存在的，那 么说明我们没有能力知道事物的本质。 我们所思想的，我们所能够感知的都出自于我们的 脑心，但是我们的脑心都是外力所塑造的，并且我们所思都是含有我们的愿望在其中的。什 么是我们的愿望呢？我们的心思是寄托在我们的身体上的。我们身体受到伤害，伤害我们的 脑心会觉得伤心；我们的身体得到了什么，我们的脑心也会欢喜。我们的所思也蕴含有其他 人的思想。我们生下来是一张白纸，我们的老师教我们知识，我们老师所教的知识，也是我 们老师的老师教给他的。所以我们知识源于他人的脑心，他人所思蕴含于知识中并通过教授 传给我。所以我们所思都是从我们的角度出发的，并不是关于事物本质的思。我常常以为思 考的越深入就越接近事物的本真，然而并不是这样。我所思，其实是将事物用我的感觉读入 脑心之中，然后以我的方式加工。 这样所感知的事物，所了解的事物都不是事物的本质。 这样我就知道了，事物的本源是不能准确被感知的。事物的真理也是不能被准确思考的。这 就是知的局限性。\n","permalink":"https://blog.bktus.com/archives/y8d47o/","summary":"\u003cp\u003e出于偶然，我写下了这些文字。由于一些原因，我比较浮躁，而写这些东西能让我静得下心\n来。这些文字关于一些理论。这也是一把哲学的武器与防具，集合成理论后就能够变得锋\n利，变得坚固。它能够帮助我经受住生活上的挑战。知行论只是其中之一。它源于我的生\n活，我读过的书，我的思考。以下进入正文。 论及知，我们用眼睛观察事物，用耳朵听它\n的声音，用手去抚摸它感受它，我们就认为我们感知了这个事物。我们用大脑思考这件事，\n用心去感知它，我们就认为我们了解了这件事物。但是，我们所看见的，所听到的，所抚摸\n到的，都是我们的感官能够感觉到的。我们怎么知道我们的感官感觉到的是事物的本真呢？\n我们感知事物只能够凭借感官，它们感知的事物是怎么样的源于它的构造，哪能不能够存在\n一套构造完全不同的感官，它们感觉到的事物的样子与我们的毫不相同呢。这是存在的，那\n么说明我们没有能力知道事物的本质。 我们所思想的，我们所能够感知的都出自于我们的\n脑心，但是我们的脑心都是外力所塑造的，并且我们所思都是含有我们的愿望在其中的。什\n么是我们的愿望呢？我们的心思是寄托在我们的身体上的。我们身体受到伤害，伤害我们的\n脑心会觉得伤心；我们的身体得到了什么，我们的脑心也会欢喜。我们的所思也蕴含有其他\n人的思想。我们生下来是一张白纸，我们的老师教我们知识，我们老师所教的知识，也是我\n们老师的老师教给他的。所以我们知识源于他人的脑心，他人所思蕴含于知识中并通过教授\n传给我。所以我们所思都是从我们的角度出发的，并不是关于事物本质的思。我常常以为思\n考的越深入就越接近事物的本真，然而并不是这样。我所思，其实是将事物用我的感觉读入\n脑心之中，然后以我的方式加工。 这样所感知的事物，所了解的事物都不是事物的本质。\n这样我就知道了，事物的本源是不能准确被感知的。事物的真理也是不能被准确思考的。这\n就是知的局限性。\u003c/p\u003e","title":"知行论（一）"},{"content":"虽然说，量变是质变的必要准备。但有一些特定情况下，量变的积累会由于突发事件的发生 导致反质变。这种反质变是威力巨大的，非常快速的，而且是几乎不可逆转的。量积累越 深，反质变来的越猛烈。反质变的条件不仅仅是突发事件这个表面原因。突然触发这个事件 后，由于量变大量地积累，以及自我对于量变的内部的具体信息把握缺失从而产生的怀疑， 最终导致了反质变的发生。反质变发生后，表面上，在他人看来，自我的外在表现可能并没 有改变，但是自我对于事物的看法迅速发生了根本的改变。这种反质变对于自我的思想的冲 击是非常大的，而且在但时间内会对自我的生理心理造成巨大的冲击。日后，虽然量变还在 可能继续积累，但是反质变对于自我的思想的改变以及无法挽回。 举个例子，比如我认为 一个人是可以相信的，所以我觉得我不用去详细了解这个人的具体思想。现实的表现也逐步 印证我的直觉，我继续相信这个人，信任逐渐加深。这样，我对于这个人的信任就像操作黑 盒子一样，不用了解内部运作也知道这个人是可信任的。这样的信任是程度很大的，但也是 极其敏感的。所以，一旦有一次发生了失信事件，不管这个人是不是真正失信，也不管事件 多小，只要从我的角度观察这个事件的表现，然后以我单方面判断后认为这很有可能是失信 表现后，反质变就发生了。这个人的行为首先冲击我的身心，直接造成我产生了被欺骗的感 觉。然后，我对于这个人的看法也发生了根本的改变，虽然表面上我仍然对这个人以微笑。 以后这个人的表现是否还是值得信任的表现对于这个反质变的作用是微乎其微的。当我还想 认为可以相信这个人的时候，这个反质变的作用必然会形成一种强大阻力。 所以，当收到 别人无猜的信任的时候要珍惜，因为这种信任是很深的，也是极其敏感的。虽然自认为行为 是在理论上可以理解的，但是对于对方并不是这样。一旦发生突发事件，这段信任会发生不 可逆转的转变。 这种过程对于其他相同或者相似的条件仍然成立。\n","permalink":"https://blog.bktus.com/archives/rr970y/","summary":"\u003cp\u003e虽然说，量变是质变的必要准备。但有一些特定情况下，量变的积累会由于突发事件的发生\n导致反质变。这种反质变是威力巨大的，非常快速的，而且是几乎不可逆转的。量积累越\n深，反质变来的越猛烈。反质变的条件不仅仅是突发事件这个表面原因。突然触发这个事件\n后，由于量变大量地积累，以及自我对于量变的内部的具体信息把握缺失从而产生的怀疑，\n最终导致了反质变的发生。反质变发生后，表面上，在他人看来，自我的外在表现可能并没\n有改变，但是自我对于事物的看法迅速发生了根本的改变。这种反质变对于自我的思想的冲\n击是非常大的，而且在但时间内会对自我的生理心理造成巨大的冲击。日后，虽然量变还在\n可能继续积累，但是反质变对于自我的思想的改变以及无法挽回。 举个例子，比如我认为\n一个人是可以相信的，所以我觉得我不用去详细了解这个人的具体思想。现实的表现也逐步\n印证我的直觉，我继续相信这个人，信任逐渐加深。这样，我对于这个人的信任就像操作黑\n盒子一样，不用了解内部运作也知道这个人是可信任的。这样的信任是程度很大的，但也是\n极其敏感的。所以，一旦有一次发生了失信事件，不管这个人是不是真正失信，也不管事件\n多小，只要从我的角度观察这个事件的表现，然后以我单方面判断后认为这很有可能是失信\n表现后，反质变就发生了。这个人的行为首先冲击我的身心，直接造成我产生了被欺骗的感\n觉。然后，我对于这个人的看法也发生了根本的改变，虽然表面上我仍然对这个人以微笑。\n以后这个人的表现是否还是值得信任的表现对于这个反质变的作用是微乎其微的。当我还想\n认为可以相信这个人的时候，这个反质变的作用必然会形成一种强大阻力。 所以，当收到\n别人无猜的信任的时候要珍惜，因为这种信任是很深的，也是极其敏感的。虽然自认为行为\n是在理论上可以理解的，但是对于对方并不是这样。一旦发生突发事件，这段信任会发生不\n可逆转的转变。 这种过程对于其他相同或者相似的条件仍然成立。\u003c/p\u003e","title":"反质变的直接促成"},{"content":"在现实生活中，我不断遇到各种挑战和难题。当面对这些问题时，我经常感到震惊和无措。 然而，当我冷静下来并对问题进行深入分析后，我通常会尝试在现有的体系和框架内寻找解 决方案。对于一些常规问题，这种方法可能有效。但有时，问题的根源恰恰在于这个体系本 身。我过去常常试图通过加强现有体系来解决问题，但这往往效果有限，甚至无法根本上解 决问题。这样的做法可能导致我越陷越深，最终无法摆脱困境。\n学习模式的挑战 我的学习过程中从未真正质疑过我所遵循的学习模式。然而，在一次严重的挫折之后，我开 始意识到这可能是个人表现问题的根源。随着问题的加剧，我发现自己面临前所未有的挑 战。起初，我感到非常迷茫，认为一切都很荒谬。尝试了一些方法后，我发现仅仅加强原有 的学习模式并不能带来显著的改进。最终，我几乎不知该如何应对。\n直面恐惧 问题依然存在，我必须找到解决它的方法。面对恐惧的唯一方法就是勇敢直面。在最近的一 次挫折之后，我决定挑战我的学习模式。正如量子理论颠覆了传统的物理观念，我也意识到 需要改变我的学习方式。我深入分析了我的学习问题，发现其根源不仅仅是粗心或运气不 佳。实际上，我缺乏勇气去面对更深层次的基础问题。我一直相信自己的智力没有问题，但 从小学到初中，我的基础问题已经多次显露，只是我没有给予足够的重视。 在初中时期， 我的信息学老师曾提醒我，我在数学方面的粗心可能是基础问题的表现。这是我第一次听到 这样的观点，但当时我并没有重视，因为我缺乏深入分析问题的能力。 进入高中后，这个 问题变得更加明显，我的基础知识缺陷完全暴露。高中阶段的考试对基础知识的要求更高， 仅凭智力已无法掩盖基础问题的严重性。那么，基础到底包括哪些方面呢？它涉及解决问题 的思路的严谨性、操作的准确性，以及基础知识的掌握程度。现在，我可以坦率地说，我的 基础知识确实存在严重问题，这是我以前一直不敢面对的。 例如，在科学计数法、单位转 换、基本物理单位概念和数学除法运算等方面，我存在明显的不足。以前，我解决问题时往 往忽视细节，比如是否可以取零、是否有意义等。尤其是在基础知识和概念方面，我从未重 视过，甚至轻视它们。我错误地认为这些知识在具体问题中用处不大。但现在，我认识到这 是一个严重的误解。\n重新审视学习方法 我发现自己的学习方法存在问题，我在问题分析上不够细致和深入。面对问题时，我未能从 根本上进行分析，也缺乏对问题背后原因的深入思考。我意识到，对问题的分析不仅仅是表 面的处理，而是需要找到其背后的深层原因，并对自己的现状进行批判性的思考。这也表 明，在面对挫折时，我们需要保持心理承受能力，并从中吸取经验教训。 因此，学习中的 缺失提醒了我，必须从根本上解决问题，回归到基础知识的学习上。忽视基础知识和概念是 错误的，这是我们不能忽视的关键因素。我现在认识到，只有充分理解和掌握基础知识和概 念，才能更好地解决问题并取得进步。通过这种方式，我可以在学习和生活中取得更大的成 就，更好地应对未来的挑战。\n","permalink":"https://blog.bktus.com/archives/lb5b92/","summary":"\u003cp\u003e在现实生活中，我不断遇到各种挑战和难题。当面对这些问题时，我经常感到震惊和无措。\n然而，当我冷静下来并对问题进行深入分析后，我通常会尝试在现有的体系和框架内寻找解\n决方案。对于一些常规问题，这种方法可能有效。但有时，问题的根源恰恰在于这个体系本\n身。我过去常常试图通过加强现有体系来解决问题，但这往往效果有限，甚至无法根本上解\n决问题。这样的做法可能导致我越陷越深，最终无法摆脱困境。\u003c/p\u003e\n\u003ch3 id=\"学习模式的挑战\"\u003e学习模式的挑战\u003c/h3\u003e\n\u003cp\u003e我的学习过程中从未真正质疑过我所遵循的学习模式。然而，在一次严重的挫折之后，我开\n始意识到这可能是个人表现问题的根源。随着问题的加剧，我发现自己面临前所未有的挑\n战。起初，我感到非常迷茫，认为一切都很荒谬。尝试了一些方法后，我发现仅仅加强原有\n的学习模式并不能带来显著的改进。最终，我几乎不知该如何应对。\u003c/p\u003e\n\u003ch3 id=\"直面恐惧\"\u003e直面恐惧\u003c/h3\u003e\n\u003cp\u003e问题依然存在，我必须找到解决它的方法。面对恐惧的唯一方法就是勇敢直面。在最近的一\n次挫折之后，我决定挑战我的学习模式。正如量子理论颠覆了传统的物理观念，我也意识到\n需要改变我的学习方式。我深入分析了我的学习问题，发现其根源不仅仅是粗心或运气不\n佳。实际上，我缺乏勇气去面对更深层次的基础问题。我一直相信自己的智力没有问题，但\n从小学到初中，我的基础问题已经多次显露，只是我没有给予足够的重视。 在初中时期，\n我的信息学老师曾提醒我，我在数学方面的粗心可能是基础问题的表现。这是我第一次听到\n这样的观点，但当时我并没有重视，因为我缺乏深入分析问题的能力。 进入高中后，这个\n问题变得更加明显，我的基础知识缺陷完全暴露。高中阶段的考试对基础知识的要求更高，\n仅凭智力已无法掩盖基础问题的严重性。那么，基础到底包括哪些方面呢？它涉及解决问题\n的思路的严谨性、操作的准确性，以及基础知识的掌握程度。现在，我可以坦率地说，我的\n基础知识确实存在严重问题，这是我以前一直不敢面对的。 例如，在科学计数法、单位转\n换、基本物理单位概念和数学除法运算等方面，我存在明显的不足。以前，我解决问题时往\n往忽视细节，比如是否可以取零、是否有意义等。尤其是在基础知识和概念方面，我从未重\n视过，甚至轻视它们。我错误地认为这些知识在具体问题中用处不大。但现在，我认识到这\n是一个严重的误解。\u003c/p\u003e\n\u003ch3 id=\"重新审视学习方法\"\u003e重新审视学习方法\u003c/h3\u003e\n\u003cp\u003e我发现自己的学习方法存在问题，我在问题分析上不够细致和深入。面对问题时，我未能从\n根本上进行分析，也缺乏对问题背后原因的深入思考。我意识到，对问题的分析不仅仅是表\n面的处理，而是需要找到其背后的深层原因，并对自己的现状进行批判性的思考。这也表\n明，在面对挫折时，我们需要保持心理承受能力，并从中吸取经验教训。 因此，学习中的\n缺失提醒了我，必须从根本上解决问题，回归到基础知识的学习上。忽视基础知识和概念是\n错误的，这是我们不能忽视的关键因素。我现在认识到，只有充分理解和掌握基础知识和概\n念，才能更好地解决问题并取得进步。通过这种方式，我可以在学习和生活中取得更大的成\n就，更好地应对未来的挑战。\u003c/p\u003e","title":"对一段挫折进行反思"},{"content":"好几个月前，思想中浮现了一种新的东西。按照我的哲学观念，我把它命名为God Engine。依照现在的技术，这个东西还没有足够的条件存在。希望以后我可以亲手缔造。 所以，姑且把这东西写出来备忘。\n概述 God Engine是具有原子微观性模型宏观性智慧性多空间时间性异步计算性的模拟引擎。 其底层直接与硬件沟通，其通过网络与其他应用程序沟通。通过基础宇宙模型的标准数学物 理法则定义，配以宇宙模型插件，依据时间线，进行详尽的对象间互相的的关系作用的分 析，从而提供可靠的对象间的稳定关系及对象组的可靠宏观性质。操作者通过在对象组之间 建立联系，并提供基础的模型，引擎将根据这些信息逐步构建庞大而复杂的对象组，引擎将 会进行对象组之间原子微观的关系分析学习，得出较大对象组的稳定宏观性质。这些初步形 成的可靠的模型将储存到数据库中，为其他模型的分析提供依据。最终，引擎得出使用者希 望得到的最终稳定的模型，然后其将对历史信息进行最后的分析筛选，得出模型的详细定义 及逻辑描述，为人类社会提供可靠的成品模型。\n简而言之，这个引擎具备了原子微观性模型、宏观性智慧性、多空间时间性和异步计算性。 它可以通过与其他应用程序的网络沟通，将底层直接与硬件沟通的操作，转化为人们可以理 解的信息。该引擎是基于宇宙模型的标准数学物理法则定义的，并配以宇宙模型插件。它能 够对时间线上各对象间的关系作用进行详尽的分析，并提供可靠的对象间的稳定关系及对象 组的可靠宏观性质。操作者可以在对象组之间建立联系，并提供基础的模型，引擎将根据这 些信息逐步构建庞大而复杂的对象组，并进行对象组之间原子微观的关系分析学习，得出较 大对象组的稳定宏观性质。这些初步形成的可靠的模型将储存到数据库中，为其他模型的分 析提供依据。\n逻辑组成部分 数学运算模块：为引擎提供数学运算支持，处理各种数学上的原子运算。 逻辑运算模块：为引擎提供逻辑运算支持，处理各种逻辑上的原子运算。 数理控制器：将复杂的数学，逻辑上的问题转化为原子运算，协调数学运算模块与逻辑运 算模块的工作。 空间控制器：管理引擎实例中所使用到的特定关联的空间，定义空间属性，协调空间内的 运算资源占用，收集模型控制器中产生的信息，汇总成初级信息。 模型控制器：定义空间中的特定关联的模型，处理各模型之间的相互作用，收集模型产生 的原始信息，并进行初始整理。 时间线控制器：每个空间对应唯一的时间线控制器，协调空间内的秩序，控制和记录空间 模拟进程。 信息处理模块：具有信息筛选，分析，概括，整理能力。处理各个空间控制器所提交的初 级信息，产生有效信息，归档到实例数据库内。 实例数据库：储存实例的基础信息，及产生的原始信息，有效信息，最终信息。 信息归纳模块：根据数据库中储存的各种有效信息，进行分析汇总，归纳出最终信息，并 储存到实例数据库内。 实例指导模块：根据数据库中的最终信息，进行最终分析，并依据分析对实例模拟进程进 行调节，让模拟进程向最终结果方向发展。 ","permalink":"https://blog.bktus.com/archives/j8cnyr/","summary":"\u003cp\u003e好几个月前，思想中浮现了一种新的东西。按照我的哲学观念，我把它命名为\u003cstrong\u003eGod\nEngine\u003c/strong\u003e。依照现在的技术，这个东西还没有足够的条件存在。希望以后我可以亲手缔造。\n所以，姑且把这东西写出来备忘。\u003c/p\u003e\n\u003ch2 id=\"概述\"\u003e概述\u003c/h2\u003e\n\u003cp\u003e\u003cstrong\u003eGod Engine\u003c/strong\u003e是具有原子微观性模型宏观性智慧性多空间时间性异步计算性的模拟引擎。\n其底层直接与硬件沟通，其通过网络与其他应用程序沟通。通过基础宇宙模型的标准数学物\n理法则定义，配以宇宙模型插件，依据时间线，进行详尽的对象间互相的的关系作用的分\n析，从而提供可靠的对象间的稳定关系及对象组的可靠宏观性质。操作者通过在对象组之间\n建立联系，并提供基础的模型，引擎将根据这些信息逐步构建庞大而复杂的对象组，引擎将\n会进行对象组之间原子微观的关系分析学习，得出较大对象组的稳定宏观性质。这些初步形\n成的可靠的模型将储存到数据库中，为其他模型的分析提供依据。最终，引擎得出使用者希\n望得到的最终稳定的模型，然后其将对历史信息进行最后的分析筛选，得出模型的详细定义\n及逻辑描述，为人类社会提供可靠的成品模型。\u003c/p\u003e\n\u003cp\u003e简而言之，这个引擎具备了原子微观性模型、宏观性智慧性、多空间时间性和异步计算性。\n它可以通过与其他应用程序的网络沟通，将底层直接与硬件沟通的操作，转化为人们可以理\n解的信息。该引擎是基于宇宙模型的标准数学物理法则定义的，并配以宇宙模型插件。它能\n够对时间线上各对象间的关系作用进行详尽的分析，并提供可靠的对象间的稳定关系及对象\n组的可靠宏观性质。操作者可以在对象组之间建立联系，并提供基础的模型，引擎将根据这\n些信息逐步构建庞大而复杂的对象组，并进行对象组之间原子微观的关系分析学习，得出较\n大对象组的稳定宏观性质。这些初步形成的可靠的模型将储存到数据库中，为其他模型的分\n析提供依据。\u003c/p\u003e\n\u003ch2 id=\"逻辑组成部分\"\u003e逻辑组成部分\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e数学运算模块：为引擎提供数学运算支持，处理各种数学上的原子运算。\u003c/li\u003e\n\u003cli\u003e逻辑运算模块：为引擎提供逻辑运算支持，处理各种逻辑上的原子运算。\u003c/li\u003e\n\u003cli\u003e数理控制器：将复杂的数学，逻辑上的问题转化为原子运算，协调数学运算模块与逻辑运\n算模块的工作。\u003c/li\u003e\n\u003cli\u003e空间控制器：管理引擎实例中所使用到的特定关联的空间，定义空间属性，协调空间内的\n运算资源占用，收集模型控制器中产生的信息，汇总成初级信息。\u003c/li\u003e\n\u003cli\u003e模型控制器：定义空间中的特定关联的模型，处理各模型之间的相互作用，收集模型产生\n的原始信息，并进行初始整理。\u003c/li\u003e\n\u003cli\u003e时间线控制器：每个空间对应唯一的时间线控制器，协调空间内的秩序，控制和记录空间\n模拟进程。\u003c/li\u003e\n\u003cli\u003e信息处理模块：具有信息筛选，分析，概括，整理能力。处理各个空间控制器所提交的初\n级信息，产生有效信息，归档到实例数据库内。\u003c/li\u003e\n\u003cli\u003e实例数据库：储存实例的基础信息，及产生的原始信息，有效信息，最终信息。\u003c/li\u003e\n\u003cli\u003e信息归纳模块：根据数据库中储存的各种有效信息，进行分析汇总，归纳出最终信息，并\n储存到实例数据库内。\u003c/li\u003e\n\u003cli\u003e实例指导模块：根据数据库中的最终信息，进行最终分析，并依据分析对实例模拟进程进\n行调节，让模拟进程向最终结果方向发展。\u003c/li\u003e\n\u003c/ul\u003e","title":"God Engine 标准定义（一）"},{"content":"上一篇文章粗略的谈到了我的脑海中的萌发的 关于物联网形式去中心化的构想，这周我对我心中的萌芽进行了进一步的思考。现在，我主 要来阐述一下在我脑海中的构想框架，可能会有些疏漏，望指正。\n**内容提示：**这篇文章主要来概述我的物联网去中心化的观点与去中心化后物联网的公有 网络主要组织形式，为了大家容易接受，不要被我脑子里的枯燥的逻辑思维所烦躁，我举了 一些例子来说明。这样损失了一些专业性，望谅解。\n在我的构想中，连接物联网中的物体都是平等的。它们都有各自的功能，比如说烧水，煮 饭，做酸奶（可能我现在有点饿了），当然，这个物体也可能是机器人。如果工程师想让它 们连接到物联网，它们不用自己从头开发，只需要将现成的物联网终端模块添加到它们的电 器（物体）中，然后依照标准化协议操作这个模块就可以了。可以说，只要添加了这个物联 网模块，这个孤独的物体就可以用它和其他拥有这个模块物体说话了。由于统一的协议，它 们可以互相听懂对方的话，可以很方便地与其它物体交流以获取必要的信息，这样省去了很 多麻烦。并且物联网模块也将会提供互联网访问的功能，方便物体获取信息。关于详细的说 明如何获取信息，怎么知道其他物体提供什么信息，以后的文章将会谈到。\n现在这个会说话的物体想去很多物体讲话，那么像人一样，它们也要组成一张关系网络，以 方便的互相交流信息。关系网络分为公有关系网络与私有关系网络。在公有关系网络中，应 为接入这个网络的物体都遵循一个公共的口令（口令这个东西，以后的文章见），物体可以 很广泛的互相交流信息。因为公有关系网络接入不需要像私有网络一样需要严格安全检查以 及没有使用以私有密码加密的数据包，可以看出公有网络的接入是非常方便的，所以公有网 络将会变得很大，拥有公共服务功能的物体一般会接入这个网络，像什么红绿灯，自动气象 台，公共服务机器人，公交车等等，所以物体可以获取的信息种类就非常多。比如说，我公 共汽车站台就可以连入公有网络，通过公用网络很方便地从最近的自动天气站获取天气信 息，在自己的既有功能范围内，调整自己，为旅客提供更舒适环保的等车方案。如果这个公 交站旅客太多又在下雨的话的话，出了为旅客遮风避雨，站台还可以和附近接入公有网络的 手机通信，告诉正在使用手机的人，这个车站人太多，待会再来。当人，使用手机的人也可 以自己住用联系站台获取他们需要的信息。所有的通信都主要是通过无线来完成的，非常便 捷。\n这样，站台就产生了疑问：无线网络的覆盖范围就那么大，我站台怎么和几公里外的人来联 系呢？难道说我装一个大功率发射器不成？其实这是可以的，但是我有一个更好的解决方 案。在我的构想中，一个接入物联网的物体要想有福利，还要承担义务。站台在发出它的信 息的时候，在离他很近的接入物联网的物体都会接到这个消息它们需要负责转发这条信息， 把它传播给其它在它们附近的物体。如果网中物体足够多，消息就可以传达到比较远的地 方。但这种方式不能够解决所有问题，那么为了信息及时传递，在有必要的的情况下，站台 可以直接让物联网模块接入互联网，通过互联网把信息告诉其它物体。其实，在实在没办法 的情况下，比如说处于边缘的地方，物体还可以过有线通信，卫星通信等方式。只是这样的 代价会更大而已。\n这样可以说，物联网的通信方式是非常灵活的，有能力将获取的信息及时传达。还有的是， 公有网络中的每一个物体都有自己的唯一代号，出生的时候就规定好了，这样在公有网络中 的物体就可以定位查找某一样的物体了。除了自己的唯一的代号，物体拥有一个标识叫功能 标识。为什么要有这个标识呢？因为别的物体在与这个物体交流的时候，需要知道这个物体 是干什么的。 来一个综合的例子吧。比如一个人家一个新买的可以说话（接入了物联网） 的电饭煲，人生地不熟，想知道我旁边的物体都可以干什么。我就可以让它们亮出自己的功 能标识，然后这个电饭煲知道自己旁边的仁兄是一个插座，另一位是一个冰箱。显然，电饭 煲对冰箱很感兴趣。由于知道他是电冰箱，电饭煲就可以用本行话与冰箱说话了。他从冰箱 那里获得了冰箱里储存的食物，以及他们的新鲜程度，价格等。它查找了工程师告诉自己的 上网的方法，然后告诉物联网模块用互联网查找一下用这些冰箱里的东西可以做什么。\n互联网告诉他，排骨瘦肉粥，红米粥，绿豆粥（现在的我好饿啊）都不错。那么他再去问问 自己，主人一般喜欢吃啥，从主人手机里知道主人是个江西人，问问互联网，江西人喜欢吃 什么。大数据，以及他自己的直觉（人工智能），告诉他，排骨瘦肉粥不错。那么他告诉主 人的手机（用过功能标识知道的），向主人推荐排骨瘦肉粥作为明天的早餐。十分钟后，主 人通过手机批准了它的推荐。他就在适当的时候，告诉家里的机器人，把冰箱里的原料拿出 来处理好，通过洗菜机洗菜，淘米机淘米，剁肉机剁肉,机器人大厨亲自下厨，经过它们的 共同努力排骨瘦肉粥就做好了。等呈上餐桌，主人吃完。电饭煲第一天的任务就算结束了。 这一趟下来他认识了许多伙伴，这都要归功于物联网。\n当然，以上这些物体交流的信息如果都是通过公有关系网络来的话，就等于把自己家里的信 息公开了。这当然是不允许的，所以我们就有了私有关系网络，来保护我们使用者的隐私与 安全。先写到这里了，下一篇文章将详细介绍私有关系网络的运作原理。 （ps：打字好 累，如果脑子里的东西可以直接变为文本多好，憋着实在难受）\n","permalink":"https://blog.bktus.com/archives/3vqrz7/","summary":"\u003cp\u003e\u003ca href=\"https://blog.bktus.com/archives/3116\"\u003e上一篇文章\u003c/a\u003e粗略的谈到了我的脑海中的萌发的\n关于物联网形式去中心化的构想，这周我对我心中的萌芽进行了进一步的思考。现在，我主\n要来阐述一下在我脑海中的构想框架，可能会有些疏漏，望指正。\u003c/p\u003e\n\u003cp\u003e**内容提示：**这篇文章主要来概述我的物联网去中心化的观点与去中心化后物联网的公有\n网络主要组织形式，为了大家容易接受，不要被我脑子里的枯燥的逻辑思维所烦躁，我举了\n一些例子来说明。这样损失了一些专业性，望谅解。\u003c/p\u003e\n\u003cp\u003e在我的构想中，连接物联网中的物体都是平等的。它们都有各自的功能，比如说烧水，煮\n饭，做酸奶（可能我现在有点饿了），当然，这个物体也可能是机器人。如果工程师想让它\n们连接到物联网，它们不用自己从头开发，只需要将现成的物联网终端模块添加到它们的电\n器（物体）中，然后依照标准化协议操作这个模块就可以了。可以说，只要添加了这个物联\n网模块，这个孤独的物体就可以用它和其他拥有这个模块物体说话了。由于统一的协议，它\n们可以互相听懂对方的话，可以很方便地与其它物体交流以获取必要的信息，这样省去了很\n多麻烦。并且物联网模块也将会提供互联网访问的功能，方便物体获取信息。关于详细的说\n明如何获取信息，怎么知道其他物体提供什么信息，以后的文章将会谈到。\u003c/p\u003e\n\u003cp\u003e现在这个会说话的物体想去很多物体讲话，那么像人一样，它们也要组成一张关系网络，以\n方便的互相交流信息。关系网络分为公有关系网络与私有关系网络。在公有关系网络中，应\n为接入这个网络的物体都遵循一个公共的口令（口令这个东西，以后的文章见），物体可以\n很广泛的互相交流信息。因为公有关系网络接入不需要像私有网络一样需要严格安全检查以\n及没有使用以私有密码加密的数据包，可以看出公有网络的接入是非常方便的，所以公有网\n络将会变得很大，拥有公共服务功能的物体一般会接入这个网络，像什么红绿灯，自动气象\n台，公共服务机器人，公交车等等，所以物体可以获取的信息种类就非常多。比如说，我公\n共汽车站台就可以连入公有网络，通过公用网络很方便地从最近的自动天气站获取天气信\n息，在自己的既有功能范围内，调整自己，为旅客提供更舒适环保的等车方案。如果这个公\n交站旅客太多又在下雨的话的话，出了为旅客遮风避雨，站台还可以和附近接入公有网络的\n手机通信，告诉正在使用手机的人，这个车站人太多，待会再来。当人，使用手机的人也可\n以自己住用联系站台获取他们需要的信息。所有的通信都主要是通过无线来完成的，非常便\n捷。\u003c/p\u003e\n\u003cp\u003e这样，站台就产生了疑问：无线网络的覆盖范围就那么大，我站台怎么和几公里外的人来联\n系呢？难道说我装一个大功率发射器不成？其实这是可以的，但是我有一个更好的解决方\n案。在我的构想中，一个接入物联网的物体要想有福利，还要承担义务。站台在发出它的信\n息的时候，在离他很近的接入物联网的物体都会接到这个消息它们需要负责转发这条信息，\n把它传播给其它在它们附近的物体。如果网中物体足够多，消息就可以传达到比较远的地\n方。但这种方式不能够解决所有问题，那么为了信息及时传递，在有必要的的情况下，站台\n可以直接让物联网模块接入互联网，通过互联网把信息告诉其它物体。其实，在实在没办法\n的情况下，比如说处于边缘的地方，物体还可以过有线通信，卫星通信等方式。只是这样的\n代价会更大而已。\u003c/p\u003e\n\u003cp\u003e这样可以说，物联网的通信方式是非常灵活的，有能力将获取的信息及时传达。还有的是，\n公有网络中的每一个物体都有自己的唯一代号，出生的时候就规定好了，这样在公有网络中\n的物体就可以定位查找某一样的物体了。除了自己的唯一的代号，物体拥有一个标识叫功能\n标识。为什么要有这个标识呢？因为别的物体在与这个物体交流的时候，需要知道这个物体\n是干什么的。 来一个综合的例子吧。比如一个人家一个新买的可以说话（接入了物联网）\n的电饭煲，人生地不熟，想知道我旁边的物体都可以干什么。我就可以让它们亮出自己的功\n能标识，然后这个电饭煲知道自己旁边的仁兄是一个插座，另一位是一个冰箱。显然，电饭\n煲对冰箱很感兴趣。由于知道他是电冰箱，电饭煲就可以用本行话与冰箱说话了。他从冰箱\n那里获得了冰箱里储存的食物，以及他们的新鲜程度，价格等。它查找了工程师告诉自己的\n上网的方法，然后告诉物联网模块用互联网查找一下用这些冰箱里的东西可以做什么。\u003c/p\u003e\n\u003cp\u003e互联网告诉他，排骨瘦肉粥，红米粥，绿豆粥（现在的我好饿啊）都不错。那么他再去问问\n自己，主人一般喜欢吃啥，从主人手机里知道主人是个江西人，问问互联网，江西人喜欢吃\n什么。大数据，以及他自己的直觉（人工智能），告诉他，排骨瘦肉粥不错。那么他告诉主\n人的手机（用过功能标识知道的），向主人推荐排骨瘦肉粥作为明天的早餐。十分钟后，主\n人通过手机批准了它的推荐。他就在适当的时候，告诉家里的机器人，把冰箱里的原料拿出\n来处理好，通过洗菜机洗菜，淘米机淘米，剁肉机剁肉,机器人大厨亲自下厨，经过它们的\n共同努力排骨瘦肉粥就做好了。等呈上餐桌，主人吃完。电饭煲第一天的任务就算结束了。\n这一趟下来他认识了许多伙伴，这都要归功于物联网。\u003c/p\u003e\n\u003cp\u003e当然，以上这些物体交流的信息如果都是通过公有关系网络来的话，就等于把自己家里的信\n息公开了。这当然是不允许的，所以我们就有了私有关系网络，来保护我们使用者的隐私与\n安全。先写到这里了，下一篇文章将详细介绍私有关系网络的运作原理。 （ps：打字好\n累，如果脑子里的东西可以直接变为文本多好，憋着实在难受）\u003c/p\u003e","title":"关于物联网形式的思考（二）"},{"content":"最近在学校里学习任务繁重，感觉都没有时间来思考了，这和初中那段时间可以有大块时间 想与学习无关的事情不同了，毕竟高二了，也要为学习想想了。\n但是，即使是在这样时间紧的情况下，我还是忍不住花些时间来思考。思考的内容就是今天 我在这篇文章探讨的东西。物联网，这个正在不断发展的东西，在未来将会使人类社会的信 息化程度不断提升。但现在，我认为，国内物联网还不成熟，一是没有统一的协议，二是物 联网的概念在国内并不深刻。什么是物联网？先看看维基百科的定义：\n物联网（英语：Internet of Things，缩写 IoT）是互联网、传统电信网等資訊承载体， 让所有能行使独立功能的普通物體实现互联互通的网络。[1]\n关键的是，所有可以行使独立功能的物体互联互通。这就说明，在物联网这张网上，所有接 入这个网络的物体都可以实现互联互通，互相交流信息，然后运用自己本身自带的功能加上 通过物联网获取的信息使运作更加便捷、快速。在此之前，我第一个想到的是智能家居。智 能家居，通过在开关，门锁上加入通信装置以及必要的功能装置，然后通过手机连接无线主 机对这些只能家居进行控制。在这种模式下，为了安全，所有的通信都必须通过类似无线主 机这样的装置来操作。我在淘宝上看了一下，这种系统以及有很多卖家在卖了。包括小米 等，都已经在这方面探索了。但我认为，这样的思维其实是将物联网的中心化了。\n中心化自然有中心化的好处，也有它的弊端。好处就是，通过无线主机来操控的原理比较简 单，只要把手机连接 wifi 的那一套搬过来就可以了。而且相对来说也比较安全。但是，思 考一下，如果黑客把家里的无线主机攻破了，那么他是不是就等于操纵了你的家里所有的智 能家居？你的所有个人信息黑客都将一览无余。而且这样做的话，物联网这张网的大小就要 被限制了。如果互联网有中心，那么互联网还会达到今天的发达程度吗？显然，物联网要快 速发展，去中心化应该是趋势。去中心化后，两个遵循统一协议的物体之间将可以交流信 息。而且每一个物体的加入，都可以拓展这张网，如同 TCP／IP 协议一样，每一个物体既 可发送信息，接受信息也可以传递信息。我们所要做的就是，将这张网更好得与互联网连接 起来，将它与互联网融合，成为互联网的新的终端。如果能这样做，那么人类的信息获取能 力，传播能力将会大大提高。这样，全人类都可以分享关于这个世界的信息。在这样的情况 下，每个有特定功能的物体就好像获得了智慧一样，他们可以先获取足够的信息，再来进行 更好操作。它们可以互相分享信息，然后更好的服务人类。工程师可以通过这张网，来对它 们的智慧进行改进更新，减少物品换代速率。这样物联网的适用情况就不局限于智能家居等 情况了，随着物品的智能化程度的拓展，物联网会将触角伸向各个角落。在这张网下，每一 个物品不仅仅可以帮助人类完成它们既定的功能，它们还是互联网信息的贡献者，它们会将 它们知道的信息在许可的情况下贡献给互联网，贡献给人类社会。这样的物联网形式将会大 大提升人类社会的信息程度，，信息获取交流将会大大加快，生活会更加便捷。这是物联网 发展的趋势。但不可回避的是，它会带来隐私问题，协议问题，以及安全问题，但这种问题 是可以预防的，在接下来的几篇文章中我将会讨论这些问题。\n注：这篇文章是在匆忙之下完成的，有些遗漏，日后我会完善这篇文章。如果有错误请指 出，谢谢。\n","permalink":"https://blog.bktus.com/archives/02ziks/","summary":"\u003cp\u003e最近在学校里学习任务繁重，感觉都没有时间来思考了，这和初中那段时间可以有大块时间\n想与学习无关的事情不同了，毕竟高二了，也要为学习想想了。\u003c/p\u003e\n\u003cp\u003e但是，即使是在这样时间紧的情况下，我还是忍不住花些时间来思考。思考的内容就是今天\n我在这篇文章探讨的东西。物联网，这个正在不断发展的东西，在未来将会使人类社会的信\n息化程度不断提升。但现在，我认为，国内物联网还不成熟，一是没有统一的协议，二是物\n联网的概念在国内并不深刻。什么是物联网？先看看维基百科的定义：\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e物联网（英语：Internet of Things，缩写 IoT）是互联网、传统电信网等資訊承载体，\n让所有能行使独立功能的普通物體实现互联互通的网络。[1]\u003c/p\u003e\u003c/blockquote\u003e\n\u003cp\u003e关键的是，所有可以行使独立功能的物体互联互通。这就说明，在物联网这张网上，所有接\n入这个网络的物体都可以实现互联互通，互相交流信息，然后运用自己本身自带的功能加上\n通过物联网获取的信息使运作更加便捷、快速。在此之前，我第一个想到的是智能家居。智\n能家居，通过在开关，门锁上加入通信装置以及必要的功能装置，然后通过手机连接无线主\n机对这些只能家居进行控制。在这种模式下，为了安全，所有的通信都必须通过类似无线主\n机这样的装置来操作。我在淘宝上看了一下，这种系统以及有很多卖家在卖了。包括小米\n等，都已经在这方面探索了。但我认为，这样的思维其实是将物联网的中心化了。\u003c/p\u003e\n\u003cp\u003e中心化自然有中心化的好处，也有它的弊端。好处就是，通过无线主机来操控的原理比较简\n单，只要把手机连接 wifi 的那一套搬过来就可以了。而且相对来说也比较安全。但是，思\n考一下，如果黑客把家里的无线主机攻破了，那么他是不是就等于操纵了你的家里所有的智\n能家居？你的所有个人信息黑客都将一览无余。而且这样做的话，物联网这张网的大小就要\n被限制了。如果互联网有中心，那么互联网还会达到今天的发达程度吗？显然，物联网要快\n速发展，去中心化应该是趋势。去中心化后，两个遵循统一协议的物体之间将可以交流信\n息。而且每一个物体的加入，都可以拓展这张网，如同 TCP／IP 协议一样，每一个物体既\n可发送信息，接受信息也可以传递信息。我们所要做的就是，将这张网更好得与互联网连接\n起来，将它与互联网融合，成为互联网的新的终端。如果能这样做，那么人类的信息获取能\n力，传播能力将会大大提高。这样，全人类都可以分享关于这个世界的信息。在这样的情况\n下，每个有特定功能的物体就好像获得了智慧一样，他们可以先获取足够的信息，再来进行\n更好操作。它们可以互相分享信息，然后更好的服务人类。工程师可以通过这张网，来对它\n们的智慧进行改进更新，减少物品换代速率。这样物联网的适用情况就不局限于智能家居等\n情况了，随着物品的智能化程度的拓展，物联网会将触角伸向各个角落。在这张网下，每一\n个物品不仅仅可以帮助人类完成它们既定的功能，它们还是互联网信息的贡献者，它们会将\n它们知道的信息在许可的情况下贡献给互联网，贡献给人类社会。这样的物联网形式将会大\n大提升人类社会的信息程度，，信息获取交流将会大大加快，生活会更加便捷。这是物联网\n发展的趋势。但不可回避的是，它会带来隐私问题，协议问题，以及安全问题，但这种问题\n是可以预防的，在接下来的几篇文章中我将会讨论这些问题。\u003c/p\u003e\n\u003cp\u003e注：这篇文章是在匆忙之下完成的，有些遗漏，日后我会完善这篇文章。如果有错误请指\n出，谢谢。\u003c/p\u003e","title":"关于物联网形式的思考（一）"}]